Ben*_*ank 449 python optimization loops list chunks
我有一个Python脚本,它将整数列表作为输入,我需要一次使用四个整数.不幸的是,我无法控制输入,或者我将它作为四元素元组列表传入.目前,我正在以这种方式迭代它:
for i in xrange(0, len(ints), 4):
# dummy op for example code
foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]
Run Code Online (Sandbox Code Playgroud)
它看起来很像"C-think",这让我怀疑有更多的pythonic方式来处理这种情况.迭代后会丢弃该列表,因此无需保留.也许这样的事情会更好吗?
while ints:
foo += ints[0] * ints[1] + ints[2] * ints[3]
ints[0:4] = []
Run Code Online (Sandbox Code Playgroud)
但是,仍然没有"感觉"正确.: - /
nos*_*klo 387
def chunker(seq, size):
return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)
Run Code Online (Sandbox Code Playgroud)
简单.简单.快速.适用于任何序列:
text = "I am a very, very helpful text"
for group in chunker(text, 7):
print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'
print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text
animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']
for group in chunker(animals, 3):
print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']
Run Code Online (Sandbox Code Playgroud)
Cra*_*raz 312
从Python的itertools文档的recipes部分修改:
from itertools import zip_longest
def grouper(iterable, n, fillvalue=None):
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
Run Code Online (Sandbox Code Playgroud)
示例
在伪代码中保持示例简洁.
grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'
Run Code Online (Sandbox Code Playgroud)
注意: izip_longest Python 2.6的新功能.在Python 3中使用zip_longest.
S.L*_*ott 123
我是粉丝
chunk_size= 4
for i in range(0, len(ints), chunk_size):
chunk = ints[i:i+chunk_size]
# process chunk of size <= chunk_size
Run Code Online (Sandbox Code Playgroud)
Mar*_*rot 20
import itertools
def chunks(iterable,size):
it = iter(iterable)
chunk = tuple(itertools.islice(it,size))
while chunk:
yield chunk
chunk = tuple(itertools.islice(it,size))
# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
foo += x1 + x2 + x3 + x4
for chunk in chunks(ints,4):
foo += sum(chunk)
Run Code Online (Sandbox Code Playgroud)
其他方式:
import itertools
def chunks2(iterable,size,filler=None):
it = itertools.chain(iterable,itertools.repeat(filler,size-1))
chunk = tuple(itertools.islice(it,size))
while len(chunk) == size:
yield chunk
chunk = tuple(itertools.islice(it,size))
# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
foo += x1 + x2 + x3 + x4
Run Code Online (Sandbox Code Playgroud)
MSe*_*ert 18
如果您不介意使用外部包,您可以使用iteration_utilities.grouperfrom 1。它支持所有可迭代对象(不仅仅是序列):iteration_utilties
from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
print(group)
Run Code Online (Sandbox Code Playgroud)
打印:
(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)
Run Code Online (Sandbox Code Playgroud)
如果长度不是 groupsize 的倍数,它还支持填充(不完整的最后一组)或截断(丢弃不完整的最后一组)最后一个:
from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)
for group in grouper(seq, 4, fillvalue=None):
print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)
for group in grouper(seq, 4, truncate=True):
print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
Run Code Online (Sandbox Code Playgroud)
我还决定比较一些提到的方法的运行时间。这是一个对数图,根据不同大小的列表分组为“10”个元素的组。对于定性结果: 越低意味着越快:
至少在这个基准测试中iteration_utilities.grouper表现最好。紧接着是克拉兹的靠近。
基准是用1创建的。用于运行此基准测试的代码是:simple_benchmark
import iteration_utilities
import itertools
from itertools import zip_longest
def consume_all(it):
return iteration_utilities.consume(it, None)
import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()
@b.add_function()
def grouper(l, n):
return consume_all(iteration_utilities.grouper(l, n))
def Craz_inner(iterable, n, fillvalue=None):
args = [iter(iterable)] * n
return zip_longest(*args, fillvalue=fillvalue)
@b.add_function()
def Craz(iterable, n, fillvalue=None):
return consume_all(Craz_inner(iterable, n, fillvalue))
def nosklo_inner(seq, size):
return (seq[pos:pos + size] for pos in range(0, len(seq), size))
@b.add_function()
def nosklo(seq, size):
return consume_all(nosklo_inner(seq, size))
def SLott_inner(ints, chunk_size):
for i in range(0, len(ints), chunk_size):
yield ints[i:i+chunk_size]
@b.add_function()
def SLott(ints, chunk_size):
return consume_all(SLott_inner(ints, chunk_size))
def MarkusJarderot1_inner(iterable,size):
it = iter(iterable)
chunk = tuple(itertools.islice(it,size))
while chunk:
yield chunk
chunk = tuple(itertools.islice(it,size))
@b.add_function()
def MarkusJarderot1(iterable,size):
return consume_all(MarkusJarderot1_inner(iterable,size))
def MarkusJarderot2_inner(iterable,size,filler=None):
it = itertools.chain(iterable,itertools.repeat(filler,size-1))
chunk = tuple(itertools.islice(it,size))
while len(chunk) == size:
yield chunk
chunk = tuple(itertools.islice(it,size))
@b.add_function()
def MarkusJarderot2(iterable,size):
return consume_all(MarkusJarderot2_inner(iterable,size))
@b.add_arguments()
def argument_provider():
for exp in range(2, 20):
size = 2**exp
yield size, simple_benchmark.MultiArgument([[0] * size, 10])
r = b.run()
Run Code Online (Sandbox Code Playgroud)
1免责声明:我是图书馆iteration_utilities和simple_benchmark.
Sha*_*ger 17
从 Python 3.12 开始,该itertools模块获得了一个batched专门涵盖迭代输入可迭代批次的函数tuple,其中最终批次可能不完整(每个批次都是 a )。根据文档中给出的示例代码:
>>> for batch in batched('ABCDEFG', 3):
... print(batch)
...
('A', 'B', 'C')
('D', 'E', 'F')
('G',)
Run Code Online (Sandbox Code Playgroud)
性能说明:
batched与迄今为止的所有函数一样,它的实现itertools位于 C 层,因此它能够进行 Python 级别代码无法比拟的优化,例如
tuple精确正确大小的 a (对于除最后一批之外的所有批次),而不是tuple通过摊销增长逐个元素地构建导致多次重新分配的摊销增长(调用tuplean的解决方案islice的方式).__next__它只需要每批查找一次底层迭代器的函数,而不是n每批查找多次(基于 - 的方法的方式zip_longest((iter(iterable),) * n))NULL检查(很简单,并且无论如何都需要处理可能的异常)goto后跟直接realloc(不复制到更小的tuple)到已知的最终大小,因为它正在跟踪它已成功拉取的元素数量(没有复杂的“创建哨兵以供使用”和fillvalue执行 Python级别if/else检查每个批次以查看其是否为空,最后一个批次需要搜索最后fillvalue出现的位置,以创建基于解决方案所需的削减tuple” zip_longest。在所有这些优点之间,它应该大大优于任何 Python 级别的解决方案(甚至是高度优化的解决方案,将大部分或所有每个项目的工作推送到 C 层),无论输入可迭代是长还是短,也无论批量大小和最终(可能不完整)批量的大小(zip_longest使用保证唯一性fillvalue以确保安全的基于解决方案是否是几乎所有情况下的最佳解决方案itertools.batched,但在不可用时,它们可能会在“少数大的病态情况下”受到影响批次,最终批次大部分,不完全,填充”,特别是 3.10 之前的版本,当bisect不能用于优化将fillvalues 从O(n)线性搜索向下切片到O(log n)二分搜索时,但batched完全避免该搜索,因此不会经历病态的情况下)。
tee*_*rna 13
在更itertools包已分块的方法,它正是这么做的:
import more_itertools
for s in more_itertools.chunked(range(9), 4):
print(s)
Run Code Online (Sandbox Code Playgroud)
印刷
[0, 1, 2, 3]
[4, 5, 6, 7]
[8]
Run Code Online (Sandbox Code Playgroud)
chunked返回列表中的项目。如果您更喜欢可迭代对象,请使用ichunked。
Ped*_*ues 11
from itertools import izip_longest
def chunker(iterable, chunksize, filler):
return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
Run Code Online (Sandbox Code Playgroud)
kaf*_*ran 11
在 Python 3.8 中,您可以使用海象运算符 和itertools.islice。
from itertools import islice
list_ = [i for i in range(10, 100)]
def chunker(it, size):
iterator = iter(it)
while chunk := list(islice(iterator, size)):
print(chunk)
Run Code Online (Sandbox Code Playgroud)
In [2]: chunker(list_, 10)
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
Run Code Online (Sandbox Code Playgroud)
bco*_*lan 10
我需要一个也适用于集合和生成器的解决方案.我无法想出任何非常简短的东西,但它至少是可读的.
def chunker(seq, size):
res = []
for el in seq:
res.append(el)
if len(res) == size:
yield res
res = []
if res:
yield res
Run Code Online (Sandbox Code Playgroud)
列表:
>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
Run Code Online (Sandbox Code Playgroud)
组:
>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
Run Code Online (Sandbox Code Playgroud)
发电机:
>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
Run Code Online (Sandbox Code Playgroud)
这个问题的理想解决方案适用于迭代器(不仅仅是序列).它也应该很快.
这是itertools文档提供的解决方案:
def grouper(n, iterable, fillvalue=None):
#"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return itertools.izip_longest(fillvalue=fillvalue, *args)
Run Code Online (Sandbox Code Playgroud)
%timeit在我的mac书籍空气中使用ipython ,我得到每循环47.5 us.
但是,这对我来说真的不起作用,因为结果被填充为偶数组.没有填充的解决方案稍微复杂一些.最天真的解决方案可能是:
def grouper(size, iterable):
i = iter(iterable)
while True:
out = []
try:
for _ in range(size):
out.append(i.next())
except StopIteration:
yield out
break
yield out
Run Code Online (Sandbox Code Playgroud)
简单,但很慢:每循环693 us
我可以提出的最佳解决方案islice用于内循环:
def grouper(size, iterable):
it = iter(iterable)
while True:
group = tuple(itertools.islice(it, None, size))
if not group:
break
yield group
Run Code Online (Sandbox Code Playgroud)
使用相同的数据集,我得到每个循环305 us.
无法以更快的速度获得纯粹的解决方案,我提供了以下解决方案,并提出了一个重要的警告:如果输入数据中包含实例,filldata则可能会得到错误的答案.
def grouper(n, iterable, fillvalue=None):
#"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
for i in itertools.izip_longest(fillvalue=fillvalue, *args):
if tuple(i)[-1] == fillvalue:
yield tuple(v for v in i if v != fillvalue)
else:
yield i
Run Code Online (Sandbox Code Playgroud)
我真的不喜欢这个答案,但速度要快得多.每循环124 us
与其他提案类似,但不完全相同,我喜欢这样做,因为它简单易读:
it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
print chunk
>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)
Run Code Online (Sandbox Code Playgroud)
这样你就不会获得最后的部分块.如果你想获得(9, None, None, None)的最后一个块,只需使用izip_longest从itertools.
由于没有人提到它,所以这里有一个zip()解决方案:
>>> def chunker(iterable, chunksize):
... return zip(*[iter(iterable)]*chunksize)
Run Code Online (Sandbox Code Playgroud)
仅当序列的长度始终可被块大小整除,或者如果您不关心尾随的块时,它才起作用。
例:
>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]
Run Code Online (Sandbox Code Playgroud)
或使用itertools.izip返回迭代器而不是列表:
>>> from itertools import izip
>>> def chunker(iterable, chunksize):
... return izip(*[iter(iterable)]*chunksize)
Run Code Online (Sandbox Code Playgroud)
可以使用@ ?????????的答案来固定填充:
>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
... it = chain(iterable, repeat(fillvalue, chunksize-1))
... args = [it] * chunksize
... return izip(*args)
Run Code Online (Sandbox Code Playgroud)
使用map()而不是zip()修复了JF Sebastian的答案中的填充问题:
>>> def chunker(iterable, chunksize):
... return map(None,*[iter(iterable)]*chunksize)
Run Code Online (Sandbox Code Playgroud)
例:
>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用 的两个参数形式iter:
from itertools import islice
def group(it, size):
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
Run Code Online (Sandbox Code Playgroud)
这可以很容易地适应使用填充(这类似于Markus Jarderot的回答):
from itertools import islice, chain, repeat
def group_pad(it, size, pad=None):
it = chain(iter(it), repeat(pad))
return iter(lambda: tuple(islice(it, size)), (pad,) * size)
Run Code Online (Sandbox Code Playgroud)
这些甚至可以组合用于可选的填充:
_no_pad = object()
def group(it, size, pad=_no_pad):
if pad == _no_pad:
it = iter(it)
sentinel = ()
else:
it = chain(iter(it), repeat(pad))
sentinel = (pad,) * size
return iter(lambda: tuple(islice(it, size)), sentinel)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
117660 次 |
| 最近记录: |