defaultdict和元组

13

我想要做以下事情:

d = defaultdict((int,float))
for z in range( lots_and_lots):
  d['operation one'] += (1,5.67)
  ...
  ...
  d['operation two'] += (1,4.56)

然后输出每个操作被调用的次数以及浮点值的总和。

for k,v in d.items():
  print k, 'Called', v[0], 'times, total =', v[1] 

但是我不知道如何实现这一点,因为不仅不能将元组用作defaultdict的参数,而且不能将元组添加到元组中并总计元组中的值,你只会得到额外的值在你的元组中。即:

>>> x = (1,0)
>>> x+= (2,3)
>>> x
(1, 0, 2, 3)

而不是

>>> x = (1,0)
>>> x+= (2,3)
>>> x
(3,3)

我要如何得到我想要的东西?


1
你还认为所选答案是最好的吗? - Raymond Hettinger
它曾经非常有效。现在呢?每个解决方案都应该与问题相匹配。 - Martlark
6个回答

28
你可以使用collections.Counter来累积结果:
>>> from collections import Counter, defaultdict
>>> d = defaultdict(Counter)
>>> d['operation_one'].update(ival=1, fval=5.67)
>>> d['operation_two'].update(ival=1, fval=4.56)

2
这真的应该成为被接受的答案;它利用了stdlib类,并且不需要自定义代码即可工作。既然可以免费获得任何优化和额外功能,为什么不呢? - circld

21

defaultdict的参数必须是一个“可调用对象”,该对象返回一个默认值。请按以下方式定义您的默认字典:

d = defaultdict(lambda: (0, 0.0))

intfloat 类型可以被调用并返回零,这是一种方便,但与 defaultdict 的工作方式没有任何关系。

+= 起作用会带来一些麻烦;元组之间的加法是将元组拼接起来,所以您需要采用冗长的方式来实现:

left, right = d["key"]
d["key"] = (left + 2, right + 3)

编辑:如果你一定要使用+=,那么只要你有一个具有所需操作的集合类型,就可以这样做。fileoffset建议使用numpy数组类型,这可能是个不错的主意,但你也可以通过子类化tuple并重写所需的运算符来获得一个接近的近似。下面是一个大致的草图:

class vector(tuple):
    def __add__(self, other):
        return type(self)(l+r for l, r in zip(self, other))
    def __sub__(self, other):
        return type(self)(l-r for l, r in zip(self, other))
    def __radd__(self, other):
        return type(self)(l+r for l, r in zip(self, other))
    def __lsub__(self, other):
        return type(self)(r-l for l, r in zip(self, other))

from collections import defaultdict

d = defaultdict(lambda:vector((0, 0.0)))
for k in range(5):
    for j in range(5):
        d[k] += (j, j+k)

print d

我们不需要(也不想)实际重载+=运算符本身(即__iadd__),因为tuple是不可变的。如果你提供了加法,Python会正确地用新值替换旧值。


5
我假设您有太多的操作,无法简单地将每个条目的值列表存储在其中?
d = defaultdict(list)
for z in range(lots_and_lots):
  d['operation one'].append(5.67)
  ...
  ...
  d['operation two'].append(4.56)
for k,v in d.items():
  print k, 'Called', len(v), 'times, total =', sum(v)

您可以做的一件事是创建一个自定义增量器:
class Inc(object):
    def __init__(self):
        self.i = 0
        self.t = 0.0
    def __iadd__(self, f):
        self.i += 1
        self.t += f
        return self

然后

d = defaultdict(Inc)
for z in range(lots_and_lots):
  d['operation one'] += 5.67
  ...
  ...
  d['operation two'] += 4.56
for k,v in d.items():
  print k, 'Called', v.i, 'times, total =', v.t

Inc类运行良好。感谢David。还要感谢其他所有回复的人。 - Martlark

1
如果你使用numpy数组,你可以得到所需的输出: 链接

1
编写一个类,您可以将其传递给 defaultdict,它会在您传递值时累加这些值。
class Tracker(object):
    def __init__(self):
        self.values = None
        self.count = 0

    def __iadd__(self, newvalues):
        self.count += 1
        if self.values is None:
            self.values = newvalues
        else:
            self.values = [(old + new) for old, new in zip(self.values, newvalues)]
        return self

    def __repr__(self):
        return '<Tracker(%s, %d)>' % (self.values, self.count)

这是您原始帖子中 (int, float) 的即插即用替代品。将输出循环更改为打印实例属性,如下所示:

for k,v in d.items():
    print k, 'Called', v.count, 'times, total =', v.values

...完成了!


0

试试这个:

a = (1,0)
b = (2,3)

res = tuple(sum(x) for x in zip(a,b)

例如

d = defaultdict((int,float))
for z in range( lots_and_lots):
  d['operation one'] = tuple(sum(x) for x in zip(d['operation one'], (1,5.67))
  ...
  ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接