defaultdict 与元组

13 投票
6 回答
23281 浏览
提问于 2025-04-17 08:15

我想做以下事情:

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)

我该怎么才能实现我想要的呢?

6 个回答

22

传给 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 会正确地用新值替换旧值。

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)
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

撰写回答