一般来说,有没有一种有效的方法可以知道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和一个简单的生成器表达式进行了测试,趋势非常相似,但我不能排除时间不会因输入类型而改变。
其他回答
这段代码应该工作:
>>> iter = (i for i in range(50))
>>> sum(1 for _ in iter)
50
尽管它确实遍历每一项并计算它们,但这是最快的方法。
它也适用于迭代器中没有项的情况:
>>> sum(1 for _ in range(0))
0
当然,对于一个无限的输入,它会一直运行,所以请记住迭代器可以是无限的:
>>> sum(1 for _ in itertools.count())
[nothing happens, forever]
此外,请注意,这样做将耗尽迭代器,并且进一步尝试使用它将看不到任何元素。这是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和一个简单的生成器表达式进行了测试,趋势非常相似,但我不能排除时间不会因输入类型而改变。
我决定在现代版本的Python上重新运行基准测试,并发现几乎完全颠倒了基准测试
我运行了以下命令:
py -m timeit -n 10000000 -s "it = iter(range(1000000))" -s "from collections import deque" -s "from itertools import count" -s "def itlen(x):" -s " return len(tuple(x))" -- "itlen(it)"
py -m timeit -n 10000000 -s "it = iter(range(1000000))" -s "from collections import deque" -s "from itertools import count" -s "def itlen(x):" -s " return len(list(x))" -- "itlen(it)"
py -m timeit -n 10000000 -s "it = iter(range(1000000))" -s "from collections import deque" -s "from itertools import count" -s "def itlen(x):" -s " return sum(map(lambda i: 1, x))" -- "itlen(it)"
py -m timeit -n 10000000 -s "it = iter(range(1000000))" -s "from collections import deque" -s "from itertools import count" -s "def itlen(x):" -s " return sum(1 for _ in x)" -- "itlen(it)"
py -m timeit -n 10000000 -s "it = iter(range(1000000))" -s "from collections import deque" -s "from itertools import count" -s "def itlen(x):" -s " d = deque(enumerate(x, 1), maxlen=1)" -s " return d[0][0] if d else 0" -- "itlen(it)"
py -m timeit -n 10000000 -s "it = iter(range(1000000))" -s "from collections import deque" -s "from itertools import count" -s "def itlen(x):" -s " counter = count()" -s " deque(zip(x, counter), maxlen=0)" -s " return next(counter)" -- "itlen(it)"
它们等价于为以下每个itlen*(it)函数计时:
it = iter(range(1000000))
from collections import deque
from itertools import count
def itlen1(x):
return len(tuple(x))
def itlen2(x):
return len(list(x))
def itlen3(x):
return sum(map(lambda i: 1, x))
def itlen4(x):
return sum(1 for _ in x)
def itlen5(x):
d = deque(enumerate(x, 1), maxlen=1)
return d[0][0] if d else 0
def itlen6(x):
counter = count()
deque(zip(x, counter), maxlen=0)
return next(counter)
在装有AMD Ryzen 7 5800H和16 GB RAM的Windows 11、Python 3.11机器上,我得到了以下输出:
10000000 loops, best of 5: 103 nsec per loop
10000000 loops, best of 5: 107 nsec per loop
10000000 loops, best of 5: 138 nsec per loop
10000000 loops, best of 5: 164 nsec per loop
10000000 loops, best of 5: 338 nsec per loop
10000000 loops, best of 5: 425 nsec per loop
这表明len(list(x))和len(tuple(x))是绑定的;后面跟着sum(map(lambda i: 1, x));然后紧靠sum(1 for _ in x);那么其他答案中提到的其他更复杂的方法和/或在基数中使用的方法至少要慢两倍。
不,任何方法都需要解析每个结果。你可以这样做
iter_length = len(list(iterable))
但是在无限迭代器上运行它当然不会返回。它还将消耗迭代器,如果你想使用内容,它将需要重置。
告诉我们你想要解决的真正问题可能会帮助我们找到更好的方法来实现你的实际目标。
编辑:使用list()会将整个可迭代对象一次性读入内存,这可能是不可取的。另一种方法是做
sum(1 for _ in iterable)
正如另一个人发布的那样。这样可以避免把它保存在记忆中。
通常的做法是将这类信息放在文件头中,并让pysam允许您访问这些信息。我不知道格式,但是你检查过API了吗?
正如其他人所说,你不能从迭代器中知道长度。
推荐文章
- 证书验证失败:无法获得本地颁发者证书
- 当使用pip3安装包时,“Python中的ssl模块不可用”
- 无法切换Python与pyenv
- Python if not == vs if !=
- 如何从scikit-learn决策树中提取决策规则?
- 为什么在Mac OS X v10.9 (Mavericks)的终端中apt-get功能不起作用?
- 将旋转的xtick标签与各自的xtick对齐
- 为什么元组可以包含可变项?
- 如何合并字典的字典?
- 如何创建类属性?
- 不区分大小写的“in”
- 在Python中获取迭代器中的元素个数
- 解析日期字符串并更改格式
- 使用try和。Python中的if
- 如何在Python中获得所有直接子目录