一般来说,有没有一种有效的方法可以知道Python中的迭代器中有多少个元素,而不用遍历每个元素并计数?


当前回答

我认为有必要建立一个微观基准来比较这里提到的不同方法的运行时间。

免责声明:我使用simple_benchmark(我编写的库)进行基准测试,还包括iteration_utilities。Count_items(由我编写的第三方库中的函数)。

为了提供更有区别的结果,我做了两个基准测试,一个只包括不构建中间容器的方法,另一个包括以下方法:

from simple_benchmark import BenchmarkBuilder
import more_itertools as mi
import iteration_utilities as iu

b1 = BenchmarkBuilder()
b2 = BenchmarkBuilder()

@b1.add_function()
@b2.add_function()
def summation(it):
    return sum(1 for _ in it)

@b1.add_function()
def len_list(it):
    return len(list(it))

@b1.add_function()
def len_listcomp(it):
    return len([_ for _ in it])

@b1.add_function()
@b2.add_function()
def more_itertools_ilen(it):
    return mi.ilen(it)

@b1.add_function()
@b2.add_function()
def iteration_utilities_count_items(it):
    return iu.count_items(it)

@b1.add_arguments('length')
@b2.add_arguments('length')
def argument_provider():
    for exp in range(2, 18):
        size = 2**exp
        yield size, [0]*size

r1 = b1.run()
r2 = b2.run()

import matplotlib.pyplot as plt

f, (ax1, ax2) = plt.subplots(2, 1, sharex=True, figsize=[15, 18])
r1.plot(ax=ax2)
r2.plot(ax=ax1)
plt.savefig('result.png')

结果如下:

它使用log-log-轴,以便可以检查所有范围(小值,大值)。由于这些图是用于定性比较的,因此实际值并不太有趣。一般来说,y轴(垂直)表示时间,x轴(水平)表示输入“可迭代对象”中的元素数量。纵轴上越低表示越快。

上图显示了不使用中间列表的方法。这表明iteration_utilities方法是最快的,其次是more_itertools,最慢的是使用sum(1 for _ in iterator)。

下面的图还包括在中间列表上使用len()的方法,一次使用列表,一次使用列表推导式。使用len(list)的方法在这里是最快的,但与iteration_utilities方法的区别几乎可以忽略不计。使用理解式的方法比直接使用列表的方法慢得多。

总结

这里提到的任何方法都依赖于输入的长度,并且迭代遍历可迭代对象中的每个元素。没有迭代就无法获得长度(即使迭代是隐藏的)。

如果你不想要第三方扩展,那么使用len(list(iterable))绝对是测试过的方法中最快的方法,但是它会生成一个中间列表,可能会使用更多的内存。

如果你不介意额外的包,那么iteration_utilities。Count_items几乎和len(list(…))函数一样快,但不需要额外的内存。

但是需要注意的是,微基准测试使用列表作为输入。基准测试的结果可能不同,这取决于您想要获取的迭代对象的长度。我还用range和一个简单的生成器表达式进行了测试,趋势非常相似,但我不能排除时间不会因输入类型而改变。

其他回答

这违背了迭代器的定义,迭代器是一个指向对象的指针,加上如何到达下一个对象的信息。

迭代器不知道在终止之前它还能迭代多少次。这个可以是无穷,所以无穷可能是你的答案。

不。这是不可能的。

例子:

import random

def gen(n):
    for i in xrange(n):
        if random.randint(0, 1) == 0:
            yield i

iterator = gen(10)

迭代器的长度是未知的,直到迭代遍历它。

不,任何方法都需要解析每个结果。你可以这样做

iter_length = len(list(iterable))

但是在无限迭代器上运行它当然不会返回。它还将消耗迭代器,如果你想使用内容,它将需要重置。

告诉我们你想要解决的真正问题可能会帮助我们找到更好的方法来实现你的实际目标。

编辑:使用list()会将整个可迭代对象一次性读入内存,这可能是不可取的。另一种方法是做

sum(1 for _ in iterable)

正如另一个人发布的那样。这样可以避免把它保存在记忆中。

在计算机上有两种方法来获取“某物”的长度。

第一种方法是存储一个计数——这需要任何接触文件/数据的东西来修改它(或者一个只公开接口的类——但归根结底是一样的)。

另一种方法是遍历它并计算它有多大。

通常的做法是将这类信息放在文件头中,并让pysam允许您访问这些信息。我不知道格式,但是你检查过API了吗?

正如其他人所说,你不能从迭代器中知道长度。