split a generator/iterable every n items in python (splitEvery)
我正在尝试用python编写haskel函数"splitevery"。定义如下:
1 2 3 4 | splitEvery :: Int -> [e] -> [[e]] @'splitEvery' n@ splits a list into length-n pieces. The last piece will be shorter if @n@ does not evenly divide the length of the list. |
这个的基本版本可以正常工作,但是我想要一个能与生成器表达式、列表和迭代器一起工作的版本。如果有一个发电机作为输入,它应该返回一个发电机作为输出!
测验1 2 3 4 5 6 7 8 9 10 | # should not enter infinite loop with generators or lists splitEvery(itertools.count(), 10) splitEvery(range(1000), 10) # last piece must be shorter if n does not evenly divide assert splitEvery(5, range(9)) == [[0, 1, 2, 3, 4], [5, 6, 7, 8]] # should give same correct results with generators tmp = itertools.islice(itertools.count(), 10) assert list(splitEvery(5, tmp)) == [[0, 1, 2, 3, 4], [5, 6, 7, 8]] |
当前实施
这是我目前拥有的代码,但它不适用于简单的列表。
1 2 3 4 5 | def splitEvery_1(n, iterable): res = list(itertools.islice(iterable, n)) while len(res) != 0: yield res res = list(itertools.islice(iterable, n)) |
这个不适用于生成器表达式(感谢Jellybean修复它):
1 2 | def splitEvery_2(n, iterable): return [iterable[i:i+n] for i in range(0, len(iterable), n)] |
必须有一段简单的代码来进行拆分。我知道我可以有不同的功能,但它似乎是应该和容易的事情做。我可能会陷入一个不重要的问题,但这真的让我心烦。
它类似于http://docs.python.org/library/itertools.html itertools.groupby中的grouper,但我不希望它填充额外的值。
1 2 3 4 | def grouper(n, iterable, fillvalue=None): "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx" args = [iter(iterable)] * n return izip_longest(fillvalue=fillvalue, *args) |
它确实提到了截断最后一个值的方法。这也不是我想要的。
The left-to-right evaluation order of the iterables is guaranteed. This makes possible an idiom for clustering a data series into n-length groups using izip(*[iter(s)]*n).
1 2 | list(izip(*[iter(range(9))]*5)) == [[0, 1, 2, 3, 4]] # should be [[0, 1, 2, 3, 4], [5, 6, 7, 8]] |
1 2 3 4 5 6 7 8 | from itertools import islice def split_every(n, iterable): i = iter(iterable) piece = list(islice(i, n)) while piece: yield piece piece = list(islice(i, n)) |
一些测试:
1 2 3 4 5 6 7 8 9 10 11 | >>> list(split_every(5, range(9))) [[0, 1, 2, 3, 4], [5, 6, 7, 8]] >>> list(split_every(3, (x**2 for x in range(20)))) [[0, 1, 4], [9, 16, 25], [36, 49, 64], [81, 100, 121], [144, 169, 196], [225, 256, 289], [324, 361]] >>> [''.join(s) for s in split_every(6, 'Hello world')] ['Hello ', 'world'] >>> list(split_every(100, [])) [] |
这是一个快速的单列版本。和哈斯克尔一样,它也是懒惰的。
1 2 3 | from itertools import islice, takewhile, repeat split_every = (lambda n, it: takewhile(bool, (list(islice(it, n)) for _ in repeat(None)))) |
这要求在调用
例子:
1 2 | list(split_every(5, iter(xrange(9)))) [[0, 1, 2, 3, 4], [5, 6, 7, 8]] |
虽然不是一行程序,但下面的版本并不要求您调用
1 2 3 4 5 6 7 8 9 10 11 | from itertools import islice, takewhile, repeat def split_every(n, iterable): """ Slice an iterable into chunks of n elements :type n: int :type iterable: Iterable :rtype: Iterator """ iterator = iter(iterable) return takewhile(bool, (list(islice(iterator, n)) for _ in repeat(None))) |
(感谢@eli korvigo的改进。)
1 2 3 4 5 | import more_itertools as mit list(mit.chunked(range(9), 5)) # [[0, 1, 2, 3, 4], [5, 6, 7, 8]] |
根据公认的答案,使用不太知名的
Python 3:
1 2 3 4 5 | from itertools import islice def split_every(n, iterable): iterable = iter(iterable) yield from iter(lambda: list(islice(iterable, n)), []) |
Python 2:
1 2 3 4 | def split_every(n, iterable): iterable = iter(iterable) for chunk in iter(lambda: list(islice(iterable, n)), []): yield chunk |
一个一行的、对此的不可线性解决方案(支持v2/v3、迭代器、使用标准库和单个生成器理解):
1 2 3 | import itertools def split_groups(iter_in, group_size): return ((x for _, x in item) for _, item in itertools.groupby(enumerate(iter_in), key=lambda x: x[0] // group_size)) |
我遇到这种情况的时候,我也在尝试拆分批,但是在流的生成器上执行,所以这里的大多数解决方案都不适用,或者在Python3中不工作。
对于仍然在这方面犹豫不决的人们,下面是一个使用ITertools的通用解决方案:
1 2 3 4 5 6 7 8 9 | from itertools import islice, chain def iter_in_slices(iterator, size=None): while True: slice_iter = islice(iterator, size) # If no first object this is how StopIteration is triggered peek = next(slice_iter) # Put the first object back and return slice yield chain([peek], slice_iter) |
我认为这些问题几乎是平等的
稍微改变一下裁剪最后一个,我认为发电机箱的一个好解决方案是:
1 2 3 4 5 6 7 | from itertools import * def iter_grouper(n, iterable): it = iter(iterable) item = itertools.islice(it, n) while item: yield item item = itertools.islice(it, n) |
对于支持切片(列表、字符串、元组)的对象,我们可以执行以下操作:
1 2 | def slice_grouper(n, sequence): return [sequence[i:i+n] for i in range(0, len(sequence), n)] |
现在,问题只在于分配正确的方法:
1 2 3 4 5 | def grouper(n, iter_or_seq): if hasattr(iter_or_seq,"__getslice__"): return slice_grouper(n, iter_or_seq) elif hasattr(iter_or_seq,"__iter__"): return iter_grouper(n, iter_or_seq) |
我想你可以把它再磨光一点。
这是一个同时适用于列表和生成器的答案:
1 2 3 4 5 | from itertools import count, groupby def split_every(size, iterable): c = count() for k, g in groupby(iterable, lambda x: next(c)//size): yield list(g) # or yield g if you want to output a generator |
为什么不这样做?看起来像你的
1 2 | def splitEveryN(n, it): return [it[i:i+n] for i in range(0, len(it), n)] |
实际上,它只会从解决方案的切片中去掉不必要的步骤间隔。:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def chunks(iterable,n): """assumes n is an integer>0 """ iterable=iter(iterable) while True: result=[] for i in range(n): try: a=next(iterable) except StopIteration: break else: result.append(a) if result: yield result else: break g1=(i*i for i in range(10)) g2=chunks(g1,3) print g2 '<generator object chunks at 0x0337B9B8>' print list(g2) '[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]' |
下面是如何处理列表与迭代器:
1 2 3 | def isList(L): # Implement it somehow - returns True or false ... return (list, lambda x:x)[int(islist(L))](result) |
如果你想要一个解决方案
- 仅使用生成器(没有中间列表或元组),
- 适用于非常长(或无限)的迭代器,
- 适用于非常大的批量生产,
这就是诀窍:
1 2 3 4 5 6 7 8 9 10 | def one_batch(first_value, iterator, batch_size): yield first_value for i in xrange(1, batch_size): yield iterator.next() def batch_iterator(iterator, batch_size): iterator = iter(iterator) while True: first_value = iterator.next() # Peek. yield one_batch(first_value, iterator, batch_size) |
它的工作方式是查看迭代器中的下一个值,并将其作为第一个值传递给生成它的生成器(
当输入迭代器用完并且没有更多的批时,peek步骤将提升
这将批量处理来自stdin的行:
1 2 3 4 | for input_batch in batch_iterator(sys.stdin, 10000): for line in input_batch: process(line) finalise() |
我发现这对于处理大量数据和批量将结果上载到外部存储非常有用。
这就行了
1 2 | from itertools import izip_longest izip_longest(it[::2], it[1::2]) |
其中*它*是一些不可更改的
例子:
1 | izip_longest('abcdef'[::2], 'abcdef'[1::2]) -> ('a', 'b'), ('c', 'd'), ('e', 'f') |
让我们把这个拆下来
1 2 | 'abcdef'[::2] -> 'ace' 'abcdef'[1::2] -> 'bdf' |
如您所见,切片中的最后一个数字指定了用于拾取项目的间隔。您可以在这里阅读关于使用扩展片的更多信息。
zip函数从第一个iterable中获取第一个项,并将其与第一个iterable和第二个iterable组合在一起。然后,zip函数对第二和第三项执行相同的操作,直到其中一个iterables的值用完为止。
结果是一个迭代器。如果需要列表,请对结果使用list()函数。