【问题标题】:Python - Count Elements in Iterator Without ConsumingPython - 在不消耗的情况下计算迭代器中的元素
【发布时间】:2017-09-10 02:01:36
【问题描述】:

给定一个迭代器it,我想要一个函数it_count,它返回迭代器产生的元素的计数,而不破坏迭代器。例如:

ita = iter([1, 2, 3])
print(it_count(ita))
print(it_count(ita))

应该打印

3
3

有人指出,对于 all 迭代器来说,这可能不是一个定义明确的问题,所以我不是在寻找一个完全通用的解决方案,但它应该按照给定示例的预期功能.


好的,让我进一步澄清一下我的具体情况。给定以下代码:

ita = iter([1, 2, 3])
itb, itc = itertools.tee(ita)
print(sum(1 for _ in itb))
print(sum(1 for _ in itc))

...我们可以编写上面描述的it_count 函数,让它以这种方式运行吗?即使问题的答案是“那做不到”,这仍然是一个完全有效的答案。这不会使问题变得糟糕。而证明这是不可能的将远非微不足道......

【问题讨论】:

  • 到目前为止您尝试过什么?什么让你绊倒了?
  • 而不是提供 abstract 示例(这显然会导致人们告诉您对于 general 情况不能这样做),您可以请描述您实际处理的是哪种特定类型的迭代器,以及为什么需要这样做?
  • 请不要改变问题的初衷,如果您有“后续”问题,请发布一个新问题。
  • 不是对原问题的跟进或修改。这是对不了解其意图的人的澄清。
  • 迭代器的主要观点是它是惰性执行的,您可以根据需要从中获取元素,并且不一定具有明确定义的长度(所以问题不是不明确的,您要求的功能定义不明确)如果您真的是从列表文字构造迭代器,您只会得到列表的len,那么您到底想知道什么迭代器?

标签: python


【解决方案1】:

不可能。在迭代器被完全消耗之前,它没有具体的元素计数。

【讨论】:

  • 好的,那我只需要该解决方案适用于确定性迭代器(或其他相关内存修改的技术条件等......)
【解决方案2】:

获得任意迭代器长度的唯一方法是对其进行迭代,因此这里的基本问题是不明确的。如果不对其进行迭代,您将无法获得任何迭代器的长度。

迭代器本身也可能在被迭代时改变它的内容,所以计数可能不是恒定的。


但有些可能性可能会按照您的要求进行,请注意它们都不是万无一失或真正有效的:

当使用 python 3.4 或更高版本时,您可以使用operator.length_hint 并希望迭代器支持它(注意:没有多少迭代器支持它!这只是一个提示,实际长度可能不同!):

>>> from operator import length_hint

>>> it_count = length_hint

>>> ita = iter([1, 2, 3])
>>> print(it_count(ita))
3
>>> print(it_count(ita))
3

作为替代方案:您可以使用itertools.tee,但在使用之前请仔细阅读该文档。它可能会解决您的问题,但不会真正解决根本问题。

import itertools

def it_count(iterator):
    return sum(1 for _ in iterator)

ita = iter([1, 2, 3])
it1, it2 = itertools.tee(ita, 2)
print(it_count(it1))  # 3
print(it_count(it2))  # 3

但这比将其转换为 list 并在其上使用 len 效率低(内存和速度)。

【讨论】:

  • “你需要提前知道你需要多少个“subiterators”” - 它的文档记录很差,但是you can actually generate all the tees you want 只要你保留一份你没有提前的副本。
  • 啊,对,完全忘了tee支持copy
  • 我正在查看itertools.tee,但是,原始迭代器仍将被消耗。
  • @Apollys 那你需要user2357112s 回答:不可能。
【解决方案3】:

没有通用的方法来做你想做的事。迭代器可能没有明确定义的长度(例如,itertools.count 永远迭代)。或者它可能有一个预先计算的长度,所以它不会让你知道在你到达终点之前你必须走多远(例如一个文件对象,它可以被迭代产生行,这不是无需阅读整个文件的内容即可轻松计算)。

某些类型的迭代器可能会实现返回估计长度的__length_hint__ 方法,但该长度可能不准确。并不是所有的迭代器都会实现那个方法,所以你可能不能依赖它(它确实适用于列表迭代器,但不适用于许多其他迭代器)。

通常处理迭代器全部内容的最佳方法是将其转储到列表或其他容器中。完成所需的任何操作后(例如在其上调用len),您可以再次遍历列表。显然,这要求迭代器是有限的(并且它的所有内容都适合内存),但这是您必须处理的限制。

如果你只需要先看几个元素,你也许可以使用itertools.tee,但如果你需要消耗整个内容,它也比转储到一个列表中更好(因为它保留了由它返回的迭代器之一,但另一个在类似于deque 的数据结构中)。查找迭代器的长度没有任何用处。

【讨论】:

【解决方案4】:

我还没有想出一个确切的解决方案(因为迭代器可能是不可变类型),但这是我最好的尝试。根据documentationitertools.tee 的最后一段),我相信第二个应该更快。

选项 1

def it_count(it):
   tmp_it, new_it = itertools.tee(it)
   return sum(1 for _ in tmp_it), new_it

选项 2

def it_count2(it):
   lst = list(it)
   return len(lst), lst

它运行良好,但返回一对而不是简单的计数有点烦人。

ita = iter([1, 2, 3])
count, ita = it_count(ita)
print(count)

Output: 3

count, ita = it_count2(ita)
print(count)

Output: 3

count, ita = it_count(ita)
print(count)

Output: 3

print(list(ita))

Output: [1, 2, 3]

【讨论】:

  • 这在某些情况下可能会起作用,但只是给您举两个可能不起作用的例子:ita = itertools.count()l = [1, 2, 3]; ita = map(l.append, l)?请注意,这些方法在技术上“消耗”了迭代器。
  • 当然,如果生成器“无限”地继续下去,就没有答案。这就是我澄清问题的原因。
  • 但是根据您的示例,您可以只使用operator.length_hint。我们在谈论什么迭代器的“子集”?
  • 我的澄清确实阐明了......使用tee克隆迭代器然后计算sum(1 for _ in cloned_it)的作品。
猜你喜欢
  • 2021-05-08
  • 1970-01-01
  • 2017-07-27
  • 2015-11-24
  • 1970-01-01
  • 2017-09-02
  • 2021-05-04
  • 1970-01-01
  • 2020-02-26
相关资源
最近更新 更多