什么是最"python"的方式迭代表在大块?
-
10-07-2019 - |
题
我有一个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]
它看起来很像"C-认为",虽然这让我怀疑有一种更多的功能已大大增强的方式处理这种情况。列表中被废弃之后的迭代,因此它不需要被保留。也许这样的事情会更好吗?
while ints:
foo += ints[0] * ints[1] + ints[2] * ints[3]
ints[0:4] = []
仍然不太"感觉"的权利,虽然。:-/
相关的问题: 你怎么分列入均大小的块在蟒蛇?
其他提示
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)
简单。简单。快速。与任何序列工作的:
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']
我的风扇
chunk_size= 4
for i in range(0, len(ints), chunk_size):
chunk = ints[i:i+chunk_size]
# process chunk of size <= chunk_size
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)
另一种方法:
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
from itertools import izip_longest
def chunker(iterable, chunksize, filler):
return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
我需要一种解决方案,这也将带套和发电机工作。我不能拿出任何东西很短,很漂亮,但它是相当的可读性至少。
def chunker(seq, size):
res = []
for el in seq:
res.append(el)
if len(res) == size:
yield res
res = []
if res:
yield res
列表:
>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
集:
>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
发电机:
>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
类似的其他建议,但不完全相同的,我喜欢做这种方式,因为它很简单,易于阅读:
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)
这样,你不会得到最后的部分块。如果你想获得(9, None, None, None)
作为最后一块,只是使用izip_longest
从itertools
。
针对此问题的理想解决方案与迭代器(不仅仅是序列)的工作原理。还应当是快速的。
这是由文档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)
使用IPython中的%timeit
在我的Mac书空气,我得到47.5我们每循环。
然而,这真的不适合我,因为结果被填充到甚至规模的团体合作。没有填充的溶液是稍微复杂一些。最幼稚解决方案可能是:
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
很简单,但很慢:693我们每个环路
最好的解决方案,我可以想出用途islice
用于内循环:
def grouper(size, iterable):
it = iter(iterable)
while True:
group = tuple(itertools.islice(it, None, size))
if not group:
break
yield group
使用相同的数据集,我得到305我们每循环。
无法获取纯解决方案比这更快,我提供一个重要的警告以下解决方案:如果您输入的数据有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
我真的不喜欢这个答案,但它显著快。 124我们每环
由于没有人提到它,但这里有一个 zip()
方案:
>>> def chunker(iterable, chunksize):
... return zip(*[iter(iterable)]*chunksize)
它只能如果你的序列长度总是可分割的,由大块大小的或者你根本不关心后一块如果它不是。
例如:
>>> 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')]
或者使用 迭代.izip 返回的一个迭代替清单:
>>> from itertools import izip
>>> def chunker(iterable, chunksize):
... return izip(*[iter(iterable)]*chunksize)
填补可使用固定 @ΤΖΩΤΖΙΟΥ的答案:
>>> 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)
使用地图()而不是拉链()修复在J.F. Sebastian的答案填充问题:
>>> def chunker(iterable, chunksize):
... return map(None,*[iter(iterable)]*chunksize)
示例:
>>> 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')]
如果你不介意使用一个外部的软件包,你可以使用 iteration_utilities.grouper
从 iteration_utilties
1 。它支持所有iterables(不仅仅是序列):
from iteration_utilities import grouper
seq = list(range(20))
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, 17, 18, 19)
在的情况下的长度不是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)
1 声明:我就是包的作者
如果该列表很大,最高性能的方式来做到这将是使用生成器:
def get_chunk(iterable, chunk_size):
result = []
for item in iterable:
result.append(item)
if len(result) == chunk_size:
yield tuple(result)
result = []
if len(result) > 0:
yield tuple(result)
for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
print x
(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)
使用小功能,事情真的不吸引我;我更愿意只使用切片:
data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
...
另一种方法将是使用的iter
两个参数形式:
from itertools import islice
def group(it, size):
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
此可以很容易地适合于使用填充(这类似于马库斯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)
这些甚至可以合并为可选的填充:
_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)
使用NumPy的很简单:
ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
print(int1, int2)
输出:
1 2
3 4
5 6
7 8
除非我错过的东西,与发电机表达式以下简单的解决方案没有被提及。它假定的大小和块的数量是已知的(其通常的情况),并且没有填充是必需的:
def chunks(it, n, m):
"""Make an iterator over m first chunks of size n.
"""
it = iter(it)
# Chunks are presented as tuples.
return (tuple(next(it) for _ in range(n)) for _ in range(m))
在第二个方法中,我会推进到4的下一组通过执行这样的:
ints = ints[4:]
不过,我没有做任何性能测试,所以我不知道哪一个可能更有效。
话虽如此,我会通常选择第一种方法。这不是很漂亮,但是这往往与外界连接的结果。
又一答案,其优点是:
1)易于理解,点击 2)适用于任何可迭代,而不只是序列(上面的一些答案将窒息文件句柄),点击 3)块不加载到内存中一次全部结果 4)不会对内存中的同迭代器引用的一大块,一长串结果 5)在列表的末尾填充值的未填充
这就是说,我还没有超时,以便它可能比某些更巧妙的方法慢,并且一些优点可以给出了使用的情况下是不相关的。
def chunkiter(iterable, size):
def inneriter(first, iterator, size):
yield first
for _ in xrange(size - 1):
yield iterator.next()
it = iter(iterable)
while True:
yield inneriter(it.next(), it, size)
In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:
for c in ii:
print c,
print ''
...:
a b c
d e f
g h
<强>更新强>结果
一对夫妇由于内环和外环是由相同的迭代器拉动值的事实弊端:点击
1)继续按预期在外环不工作 - 它只是继续到下一个项目,而不是跳过块。然而,这似乎并不像一个问题,因为没有什么在外环测试。结果
控制将在内部循环再次在迭代器的下一个项目拉闸 - 2)预计将在内环休息不工作。要跳过整个块,无论是在一个元组包裹内迭代器(上述ii)中,例如for c in tuple(ii)
,或设置一个标志和排出迭代器。结果,
def group_by(iterable, size):
"""Group an iterable into lists that don't exceed the size given.
>>> group_by([1,2,3,4,5], 2)
[[1, 2], [3, 4], [5]]
"""
sublist = []
for index, item in enumerate(iterable):
if index > 0 and index % size == 0:
yield sublist
sublist = []
sublist.append(item)
if sublist:
yield sublist
要避免所有转换的列表import itertools
和
>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
... list(g)
产地:
...
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>>
我检查groupby
,并没有转换为列表或使用len
所以我(觉得)这将延迟每个值的分辨率,直到它被实际使用。遗憾的是没有可用的答案(此时)的似乎提供该变型
显然,如果需要处理反过来巢每个项目遍历克:
for k,g in itertools.groupby(xrange(35), lambda x: x/10):
for i in g:
# do what you need to do with individual items
# now do what you need to do with the whole group
我在这个特定的兴趣是要消耗发电机提交多达1000批次更改Gmail的API的需要:
messages = a_generator_which_would_not_be_smart_as_a_list
for idx, batch in groupby(messages, lambda x: x/1000):
batch_request = BatchHttpRequest()
for message in batch:
batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
http = httplib2.Http()
self.credentials.authorize(http)
batch_request.execute(http=http)
关于溶液,得到由J.F. Sebastian
此处:
def chunker(iterable, chunksize):
return zip(*[iter(iterable)]*chunksize)
这是聪明的,但有一个缺点 - 总是返回元组。如何得到字符串呢?结果
当然,你可以写''.join(chunker(...))
,但临时元组被反正构成。
您可以通过编写自己的zip
,这样摆脱了暂时的元组:
class IteratorExhausted(Exception):
pass
def translate_StopIteration(iterable, to=IteratorExhausted):
for i in iterable:
yield i
raise to # StopIteration would get ignored because this is generator,
# but custom exception can leave the generator.
def custom_zip(*iterables, reductor=tuple):
iterators = tuple(map(translate_StopIteration, iterables))
while True:
try:
yield reductor(next(i) for i in iterators)
except IteratorExhausted: # when any of iterators get exhausted.
break
然后
def chunker(data, size, reductor=tuple):
return custom_zip(*[iter(data)]*size, reductor=reductor)
用法示例:
>>> for i in chunker('12345', 2):
... print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
... print(repr(i))
...
'12'
'34'
我喜欢这种方法。感觉简单,没那么神奇,支持所有类型的可迭代的,不需要进口。
def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
chunk = tuple(next(it) for _ in range(chunk_size))
if not chunk:
break
yield chunk
我从未想过我的块填充,这样的要求是必不可少的。我发现,对可迭代的工作能力也要求。鉴于此,我决定上公认的答案, https://stackoverflow.com/a/434411/1074659 延伸。
性能发生轻微的命中在这种方法中,如果填充不希望由于需要比较和筛选填充的值。然而,对于大的块大小,此实用工具是非常高性能的。
#!/usr/bin/env python3
from itertools import zip_longest
_UNDEFINED = object()
def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
"""
Collect data into chunks and optionally pad it.
Performance worsens as `chunksize` approaches 1.
Inspired by:
https://docs.python.org/3/library/itertools.html#itertools-recipes
"""
args = [iter(iterable)] * chunksize
chunks = zip_longest(*args, fillvalue=fillvalue)
yield from (
filter(lambda val: val is not _UNDEFINED, chunk)
if chunk[-1] is _UNDEFINED
else chunk
for chunk in chunks
) if fillvalue is _UNDEFINED else chunks
def chunker(iterable, n):
"""Yield iterable in chunk sizes.
>>> chunks = chunker('ABCDEF', n=4)
>>> chunks.next()
['A', 'B', 'C', 'D']
>>> chunks.next()
['E', 'F']
"""
it = iter(iterable)
while True:
chunk = []
for i in range(n):
try:
chunk.append(next(it))
except StopIteration:
yield chunk
raise StopIteration
yield chunk
if __name__ == '__main__':
import doctest
doctest.testmod()
下面是没有进口支持发电机组块一个:
def chunks(seq, size):
it = iter(seq)
while True:
ret = tuple(next(it) for _ in range(size))
if len(ret) == size:
yield ret
else:
raise StopIteration()
使用的实例:
>>> def foo():
... i = 0
... while True:
... i += 1
... yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]
似乎没有要做到这一点相当的方式。 这里是具有多种方法,其中包括一个页:
def split_seq(seq, size):
newseq = []
splitsize = 1.0/size*len(seq)
for i in range(size):
newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
return newseq
如果该列表是相同的大小,可以将它们组合成的四元组zip()
名单。例如:
# Four lists of four elements each.
l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)
for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
...
这里的itertools.izip()
函数产生什么:
>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]
如果该列表是大,你不希望将它们组合成一个更大的列表,请使用<=>,产生一个迭代器,而不是一个列表。
from itertools import izip
for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
...
一衬垫,自组织溶液遍历大小的块x
列表4
-
for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
... do something with a, b, c and d ...
首先,我设计了它拆字符串的子分析字符串中含有六角。
今天我把它变成复杂的,但仍然简单的发电机。
def chunker(iterable, size, reductor, condition):
it = iter(iterable)
def chunk_generator():
return (next(it) for _ in range(size))
chunk = reductor(chunk_generator())
while condition(chunk):
yield chunk
chunk = reductor(chunk_generator())
参数:
显而易见的
iterable
任何可迭代/迭代/发电机提供/产生/迭代过输入数据,size
是,当然,大小的块你想得到的,
更有趣
reductor
是一个可调用,其接收发电机循环的内容的区块。
我希望它返回序列或string,但我不需求。你可以通过为这个参数,例如
list
,tuple
,set
,frozenset
,
或是任何爱好者.我会通过这一功能,返回字符串
(提供,iterable
包含/产生/迭代strings):def concatenate(iterable): return ''.join(iterable)
注意,
reductor
可能会导致关闭发电机,通过提高例外。condition
是一个可调用其收到的任何东西什么reductor
返回。
它决定批准&它产率(通过返回的任何评价来True
),
或下降,它完成发电机的工作(通过返回或任何其他提高例外)。当数量的元素
iterable
是不可分割的,由size
, 时it
得到筋疲力尽,reductor
将收到发电机产生较少的元素比size
.
让我们把这些元素 持续的元素.我邀请两个功能,通过作为这个论点:
lambda x:x
-对 持续的元素 将产生。lambda x: len(x)==<size>
-对 持续的元素 将被拒绝。
替换<size>
使用数量等于size