在Python中增加大量值的最有效方法是什么?

时间:2013-09-01 18:01:41

标签: python

好的,对不起,如果我的问题看起来有点粗糙。我会试着以比喻的方式解释它,我希望这是令人满意的。

  

10个孩子.5个盒子。
每个孩子选择三个盒子。
每个盒子打开:
   - 如果它包含某些内容,所有选中的孩子都会获得1分
   - 否则,没人会得到一分。

我的问题是关于我的粗体内容。因为在我的代码中,有很多孩子和很多盒子。

目前,我按如下方式进行:

children = {"child_1" : 0, ... , "child_10": 0}

gp1 = ["child_3", "child_7", "child_10"] #children who selected the box 1
...
gp5 = ["child_2", "child_5", "child_8", "child_10"]

boxes = [(0,gp1), (0,gp2), (1,gp3), (1,gp4), (0,gp5)]

for box in boxes:
    if box[0] == 1: #something inside
        for child in box[1]:
            children[child] += 1

我主要担心for循环会为每个孩子分配一个额外的点。因为在我的最终代码中,我有很多孩子,我担心这样做会使程序变慢。

是否有更有效的方法让同一组的所有孩子都能更快地获得他们的观点?

6 个答案:

答案 0 :(得分:5)

  1. 将子代表作为数组的索引,而不是字符串:

    childrenScores = [0] * 10
    gp1 = [2,6,9] # children who selected box 1
    ...
    gp5 = [1,4,7,9]
    
    boxes = [(0,gp1), (0,gp2), (1,gp3), (1,gp4), (0,gp5)]
    
  2. 然后,您可以将childrenScores存储为NumPy数组并使用高级索引:

    childrenScores = np.zeros(10, dtype=int)
    ...
    for box in boxes:
        if box[0]:
            childrenScores[box[1]] += 1 # NumPy advanced indexing
    

    这仍然涉及到某个地方的循环,但是循环在NumPy深处,这应该提供有意义的加速。

答案 1 :(得分:2)

我能想到的唯一加速是使用numpy数组并传输sum操作。

children[child] += np.ones(len(children[child]))

您应该对操作进行基准测试,看看这对您的业务案例来说是否太慢。

答案 2 :(得分:1)

我会做什么

gpX列表中,请勿保存“孩子的姓名”(例如"child_10"),但请保存对孩子积分数的引用。

如何操作

使用列表是python中的对象的事实,您可以:

  1. 将子dict更改为:children = {"child_0": [0], "child_1": [0], ...}等等。
  2. 分配给组时,请不要指定,而是指定(例如gp1.append(children["child_0"]))。
  3. 循环应如下所示:for child in box[1]: child[0]+=1更新children字典。
  4. 编辑:

    为什么这样更快: 因为您遗漏了搜索children[child]的部分,这可能会很昂贵。

    这种技术的工作原理是,通过将总数存储在可变类型中,并将这些值附加到组列表中,dict值和每个框的列表值都将指向相同的列表条目,而更改一个将更改另一个。

答案 3 :(得分:1)

两个一般要点:

(1)根据您告诉我们的内容,没有理由将精力集中在次要性能优化上。您可以更好地花时间思考如何使您的数据结构不那么笨拙和更具沟通性。一堆相互关联的词典,列表和元组很快就难以维护。有关替代方案,请参阅下面的示例。

(2)作为游戏设计师,您了解事件遵循一定的顺序:首先孩子选择他们的盒子,稍后他们发现他们是否获得积分他们。但是你不必那样实现它。 孩子可以选择一个盒子并立即获得积分(或不是)。如果需要保持孩子对这些结果的无知,那么依赖于这种无知的算法部分可以根据需要强制执行这种保密面纱。结果是:没有必要让一个盒子绕过它的孩子,给每个孩子分数点;相反,当选中框时,立即将积分奖励给孩子。

import random

class Box(object):
    def __init__(self, name):
        self.name = name
        self.prize = random.randint(0,1)

class Child(object):
    def __init__(self, name):
        self.name = name
        self.boxes = []
        self.score = 0
        self._score = 0

    def choose(self, n, boxes):
        bs = random.sample(boxes, n)
        for b in bs:
            self.boxes.append(b)
            self._score += b.prize

    def reveal_score(self):
        self.score = self._score

boxes = [Box(i) for i in range(5)]
kids = [Child(i) for i in range(10)]

for k in kids:
    k.choose(3, boxes)

# Later in the game ...
for k in kids:
    k.reveal_score()
    print (k.name, k.score), '=>', [(b.name, b.prize) for b in k.boxes]

答案 4 :(得分:0)

不管怎样,你将会围绕孩子们进行循环,你的答案似乎是为了避免让那些没有获得任何积分的孩子循环。

使用filter或itertools.ifilter选择包含其中某些内容的框可能会稍快一些:

import itertools

...

for box in itertools.ifilter(lambda x: x[0], boxes):
    for child in box[1]
        children[child] += 1

答案 5 :(得分:0)

如果您不需要立即打印每个孩子的点数,您可以按需计算,从而节省时间。如果你只需要经常查询一个孩子它有多少点,这可能会有所帮助。您可以在获得时缓存每个结果,以便下次需要时不再计算它。

首先,您需要知道孩子属于哪些群组。我们将这些信息存储为地图,我们将调用childToGroupsMap,它将每个子节点映射到一个包含它所属的框的数组,如下所示:

childToGroupsMap = {}
for child in children:
    childToGroupsMap[child[0]] = []
for box in boxes:
    for child in box[1]:
        if (box[1] not in childToGroupsMap[child]):
            childToGroupsMap[child].append(box[1])

这构建了从子项到框的反向映射。

从每个方框到一个表示它是否已被打开的布尔值也有帮助:

boxToOpenedMap = {}
for box in boxes:
    boxToOpenedMap[box[1]] = box[0]

现在,当有人查询一个孩子有多少分时,你可以浏览它所属的每个方框(当然是使用childToGroupsMap),并简单地计算这些方框中有多少已被映射到地图1中的boxes

def countBoxesForChild(child):
    points = 0
    for box in childToGroupsMap[child]
        if boxToOpenedMap[box] == 1:
            points += 1
    return points

为了使这更好,您可以缓存得到的点数。像这样制作地图:

childToPointsCalculated = {}
for child in children:
    childToPointsCalculated[child[0]] = -1

-1表示我们还不知道这个孩子有多少分。

最后,您可以修改countBoxesForChild函数以利用缓存:

def countBoxesForChild(child):
    if childToPointsCalculated[child] != -1
        return childToPointsCalculated[child]

    points = 0
    for box in childToGroupsMap[child]
        if boxToOpenedMap[box] == 1:
            points += 1
    childToPointsCalculated[child] = points
    return points