python中每n项拆分一个生成器/iterable(splitEvery)
我正在尝试用Python编写Haskell函数“splitEvery”。以下是它的定义:python中每n项拆分一个生成器/iterable(splitEvery),python,iterator,split,Python,Iterator,Split,我正在尝试用Python编写Haskell函数“splitEvery”。以下是它的定义: 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. 这个的基本版本很好用,但是
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.
这个的基本版本很好用,但是我想要一个可以使用生成器表达式、列表和迭代器的版本。而且,如果有一个发电机作为输入,它应该返回一个发电机作为输出
测验
当前实施
下面是我目前拥有的代码,但它不能用于简单的列表
def splitEvery_1(n, iterable):
res = list(itertools.islice(iterable, n))
while len(res) != 0:
yield res
res = list(itertools.islice(iterable, n))
这一个不适用于生成器表达式(感谢jellybean修复了它):
必须有一段简单的代码来进行拆分。我知道我可以有不同的功能,但似乎这应该是一件简单的事情。我可能被一个不重要的问题缠住了,但它真的让我心烦
它类似于来自的石斑鱼,但我不希望它填充额外的值
def grouper(n, iterable, fillvalue=None):
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)
它确实提到了截断最后一个值的方法。这也不是我想要的
iterables从左到右的求值顺序是有保证的。这使得使用izip(*[iter(s)]*n将数据序列聚类为n个长度组成为可能
为什么不这样做呢?看起来很像您的
splitEvery_2
函数
def splitEveryN(n, it):
return [it[i:i+n] for i in range(0, len(it), n)]
实际上,它只会从解决方案中的切片中删除不必要的步长间隔。:) 下面是如何处理列表与迭代器的比较:
def isList(L): # Implement it somehow - returns True or false
...
return (list, lambda x:x)[int(islist(L))](result)
一些测试:
>>> 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, []))
[]
我认为他们几乎是平等的
稍作修改以裁剪最后一个,我认为发电机外壳的一个好解决方案是:
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)
对于支持切片(列表、字符串、元组)的对象,我们可以执行以下操作:
def slice_grouper(n, sequence):
return [sequence[i:i+n] for i in range(0, len(sequence), n)]
现在只需要分派正确的方法:
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)
我想您可以再润色一下:-)def chunks(iterable,n):
“”“假定n是大于0的整数
"""
iterable=iter(iterable)
尽管如此:
结果=[]
对于范围(n)中的i:
尝试:
a=下一个(可编辑)
除停止迭代外:
打破
其他:
结果.追加(a)
如果结果为:
产量结果
其他:
打破
g1=(范围(10)内i的i*i)
g2=块(g1,3)
打印g2
''
打印列表(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'
这样就可以了
from itertools import izip_longest
izip_longest(it[::2], it[1::2])
在那里,它是一个很好的地方
例如:
izip_longest('abcdef'[::2], 'abcdef'[1::2]) -> ('a', 'b'), ('c', 'd'), ('e', 'f')
list(split_every(5, iter(xrange(9))))
[[0, 1, 2, 3, 4], [5, 6, 7, 8]]
我们来分析一下
'abcdef'[::2] -> 'ace'
'abcdef'[1::2] -> 'bdf'
正如您所看到的,切片中的最后一个数字指定了用于拾取项目的间隔。您可以阅读有关使用扩展切片的更多信息
函数从第一个iterable中获取第一个iterable,并将其与第一个iterable和第二个iterable组合。然后,zip函数对第二个和第三个项执行相同的操作,直到其中一个iterables的值用完
结果是一个迭代器。如果需要列表,请对结果使用list()函数。这里是一个简单的单行程序版本。就像哈斯克尔的一样,它是懒惰的
from itertools import islice, takewhile, repeat
split_every = (lambda n, it:
takewhile(bool, (list(islice(it, n)) for _ in repeat(None))))
这要求您在每次调用split\u之前使用iter
例如:
izip_longest('abcdef'[::2], 'abcdef'[1::2]) -> ('a', 'b'), ('c', 'd'), ('e', 'f')
list(split_every(5, iter(xrange(9))))
[[0, 1, 2, 3, 4], [5, 6, 7, 8]]
虽然不是一行程序,但下面的版本不要求调用iter
,这可能是一个常见的陷阱
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的改进。)这是一个对列表和生成器都有效的答案:
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
一个单行程序、可内联的解决方案(支持v2/v3、迭代器,使用标准库和单个生成器):
如果你想要一个解决方案
- 仅使用生成器(无中间列表或元组)
- 适用于非常长(或无限)的迭代器
- 适用于非常大的批量
这就是诀窍:
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)
它通过查看迭代器中的下一个值并将该值作为第一个值传递给生成器(one_batch()
)来工作,生成器将生成该值以及批处理的其余部分
peek步骤将在输入迭代器耗尽且不再有批处理时准确地引发StopIteration
。由于这是在batch\u iterator()
方法中引发StopIteration
的正确时间,因此无需捕获异常
这将成批处理来自stdin的管线:
for input_batch in batch_iterator(sys.stdin, 10000):
for line in input_batch:
process(line)
finalise()
我发现这对于处理大量数据和将结果批量上传到外部存储非常有用。基于公认的答案,使用一种鲜为人知的iter
(当传递第二个arg时,它调用第一个arg,直到收到第二个arg),您可以非常轻松地做到这一点:
蟒蛇3:
from itertools import islice
def split_every(n, iterable):
iterable = iter(iterable)
yield from iter(lambda: list(islice(iterable, n)), [])
蟒蛇2:
def split_every(n, iterable):
iterable = iter(iterable)
for chunk in iter(lambda: list(islice(iterable, n)), []):
yield chunk
具有以下功能:
import more_itertools as mit
list(mit.chunked(range(9), 5))
# [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
我在尝试切碎批处理时也遇到了这个问题,但是在一个流的生成器上进行,所以这里的大多数解决方案都不适用,或者在python 3中不起作用
对于仍在这方面遇到困难的人,这里有一个使用itertools的通用解决方案:
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)
这实际上就是我的splitEvery_2
函数的意思。如果输入生成器表达式,则它不起作用。我想我可能只是将生成器转换成一个列表来简化事情,但答案仍然会困扰我。迭代器不支持len
函数,尽管列表或元组可以。例如len(itertools.imap(lambda x:x*2,范围(3))
将失败。这与此类似,我仍然需要最后一个块。我只想让它与生成器和列表一起工作。哦,对不起,我误解了那部分。。。我会解决的我确实考虑过这个问题,但我认为必须有一个比hasattr更简单的方法。Roberto Bonvallet张贴了它,以便得到答案。也就是说,你的工作似乎是+1。相关的“什么是最“pythonic”的方式来在一个列表上分块迭代?”OP已经知道zip了。但这不适用于生成器,也不包括OP所希望的奇数大小iterables的最后一个元素。任意iterables不支持切片(例如:xrange(10)[::2]
是一个
from itertools import islice
def split_every(n, iterable):
iterable = iter(iterable)
yield from iter(lambda: list(islice(iterable, n)), [])
def split_every(n, iterable):
iterable = iter(iterable)
for chunk in iter(lambda: list(islice(iterable, n)), []):
yield chunk
import more_itertools as mit
list(mit.chunked(range(9), 5))
# [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
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)