在生成器上进行Python聚合

1 投票
4 回答
1293 浏览
提问于 2025-04-18 05:44

我有一个生成器,每次迭代都会返回一个列表。这个列表里的每个元素要么是0,要么是1。我想统计一下总共返回了多少个元素(包括0和1),还有返回了多少个1。我尝试用reduce函数来实现,代码是这样的:

t = reduce( (lambda x,y:(y[0]+1,y[1]+x)), gen_fn(), (0,0))

上面的gen_fn()就是那个生成器,它在每次yield语句中返回列表的一部分。我想用一个元组(0,0)来初始化计数。生成器返回的元素如下:

[0, 1, 1, 0, 1]

我期望的输出结果是t = (5,3)。但是我的代码出错了,错误信息是:

TypeError: unsupported operand type(s) for +: 'int' and 'tuple'

有没有人能帮我找出问题所在?我对reduce和lambda函数不太熟悉,这让我搞不清楚哪里出错了。谢谢大家!

4 个回答

0

那我们换个完全不同的思路怎么样?

t = [(len(row), len(filter(lambda x: x == 1, row))) for row in gen_fn()]
2

你的 lambda 参数顺序搞反了;第一个参数(x)是到目前为止的总和(一个元组),第二个参数(y)是新的值(一个整数)。试试这样:

t = reduce((lambda x, y: (x[0]+1, x[1]+y)), gen_fn(), (0,0))

使用一个虚拟函数:

def gen_fn():
    for x in [0, 1, 1, 0, 1]:
        yield x

我得到的结果是 (5, 3)

这个来自 文档reduce 的等效实现可能会让事情更清楚:

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        try:
            initializer = next(it)
        except StopIteration:
            raise TypeError('reduce() of empty sequence with no initial value')
    accum_value = initializer
    for x in it:
        accum_value = function(accum_value, x) # note value so far is first arg
    return accum_value
6

我觉得这里最好的答案就是保持简单:

count = 0
total = 0
for item in gen_fn():
    count += 1
    total += item

在这里使用 reduce() 只会让你的代码变得不容易读懂。

如果你的问题是想要写出最简短的代码(同时保持懒惰求值),那么你可以用:

count, total = collections.deque(zip(itertools.count(1), itertools.accumulate(gen_fn())), maxlen=1).pop()

当然,选择这种写法而不是简单的解决方案,你肯定会觉得很疯狂。

补充:

如果生成器输出多个小部分,那就直接用 itertools.chain.from_iterable(gen_fn()) 来把它们合并成一个大部分。

1

正如jonrsharpe所指出的,你在使用lambda函数的参数时顺序搞反了,这和reduce的工作方式有关。不过,如果你从生成器中得到的每个项目都是一个列表,那么在加总时可能还会有其他问题。

这个问题在于,你的y值(生成器返回的项目)并不是一个单独的数字,而是一个列表。你需要计算这个列表的长度以及里面有多少个1,所以你可能需要把你的lambda函数改成:

lambda x, y: (x[0]+len(y), x[1]+sum(y))

撰写回答