【问题标题】:split a generator/iterable every n items in python (splitEvery)在python中每n个项目拆分一个生成器/可迭代(splitEvery)
【发布时间】:2010-12-27 06:36:15
【问题描述】:

我正在尝试用 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.

它的基本版本可以正常工作,但我想要一个可以与生成器表达式、列表和迭代器一起使用的版本。 而且,如果有一个生成器作为输入,它应该返回一个生成器作为输出!

测试

# 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]]

当前实施

这是我目前拥有的代码,但它不适用于简单的列表。

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 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,但我不希望它填充额外的值。

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

它确实提到了一种截断最后一个值的方法。这也不是我想要的。

保证迭代的从左到右的评估顺序。这使得使用 izip(*[iter(s)]*n) 将数据系列聚类为 n 长度组成为可能。

list(izip(*[iter(range(9))]*5)) == [[0, 1, 2, 3, 4]]
# should be [[0, 1, 2, 3, 4], [5, 6, 7, 8]]

【问题讨论】:

标签: python iterator split


【解决方案1】:
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))

一些测试:

>>> 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, []))
[]

【讨论】:

  • 查看我的答案,了解基于此版本的无状态单行版本。
  • 查看我对 python 2 中更简单的单线 (python 3) 和 2 线的答案
  • 查看我的也可以内联的单线解决方案。
  • +1 如果传递了一个序列,则上述 cmets 中的所有“单行”都会进行无限循环,例如range(),或者如果问题已经解决,它们就不再是单行了。这似乎仍然是最好的答案。
  • 一个值得注意的解决方案是 Ashley Waite,对巨大的 n 很重要。她是唯一接受这个要求的人:“如果有一个生成器作为输入,它应该返回一个生成器作为输出!”
【解决方案2】:

这是一个快速的单行版本。和 Haskell 一样,它是懒惰的。

from itertools import islice, takewhile, repeat
split_every = (lambda n, it:
    takewhile(bool, (list(islice(it, n)) for _ in repeat(None))))

这要求您在调用split_every之前使用iter

例子:

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 的改进。)

【讨论】:

  • 在此我给你[复兴]徽章! =p
  • 为什么使用 lambda 而不仅仅是 def split_every(n, it):
  • 目标是成为“单线”,但我最终将它放在 SO 上的两个以防止滚动。
  • 这会产生一个带有序列的无限循环,例如使用范围()/xrange()。
  • @hynekcer 我上传了一个没有更新的解决方案
【解决方案3】:

基于已接受的答案并采用鲜为人知的 iter 用法(即,当传递第二个 arg 时,它会调用第一个直到收到第二个),您可以很容易地做到这一点:

python3:

from itertools import islice

def split_every(n, iterable):
    iterable = iter(iterable)
    yield from iter(lambda: list(islice(iterable, n)), [])

python2:

def split_every(n, iterable):
    iterable = iter(iterable)
    for chunk in iter(lambda: list(islice(iterable, n)), []):
        yield chunk

【讨论】:

  • 它不适用于一般的可迭代对象。它适用于生成器,但会在序列上产生无限循环。
  • @hynekcer 好电话。对其进行了编辑,使其不会在非生成器上无限循环。
  • 好的。写一个可读的单行字并不容易,但iter的用法很有趣。
【解决方案4】:

more_itertools 有一个chunked 函数:

import more_itertools as mit


list(mit.chunked(range(9), 5))
# [[0, 1, 2, 3, 4], [5, 6, 7, 8]]

【讨论】:

    【解决方案5】:

    我在尝试拆分批次时遇到了这个问题,但是在流中的生成器上执行此操作,因此这里的大多数解决方案都不适用,或者在 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)
    

    【讨论】:

    • +1:当结果应该是生成器的迭代器时,这确实是巨大的n 的最佳解决方案。问题中需要它,除了你之外没有人接受它:“如果有一个生成器作为输入,它应该返回一个生成器作为输出!”:
    【解决方案6】:

    对此的单行、可内联解决方案(支持 v2/v3、迭代器、使用标准库和单个生成器理解):

    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))
    

    【讨论】:

    • 此解决方案使用序列创建无限循环,例如使用范围()/xrange()。
    【解决方案7】:

    我认为those questions 几乎相等

    稍微改变一下以裁剪最后一个,我认为生成器案例的一个很好的解决方案是:

    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)
    

    我认为你可以再润色一点:-)

    【讨论】:

    • 类似,我确实仍然想要最后一块。我只想让它与生成器和列表一起使用。
    • 哦,对不起,我误解了那部分......我会修复它
    • 我确实想过这个,但我认为必须有比hasattr 更简单的方法。 Roberto Bonvallet 发布了它,以便他得到答案。那就是说你的似乎工作+1。
    • 请注意,第一个代码示例永远不会终止
    【解决方案8】:

    为什么不这样做呢?看起来几乎像您的 splitEvery_2 函数。

    def splitEveryN(n, it):
        return [it[i:i+n] for i in range(0, len(it), n)]
    

    实际上,它只是消除了解决方案中切片中不必要的步长间隔。 :)

    【讨论】:

    • 这实际上是我的 splitEvery_2 函数的意思。如果您输入生成器表达式,它将不起作用。我想我可能只是将我的生成器转换为列表以使事情变得简单,但答案仍然会困扰我。
    • 迭代器不支持len 函数,尽管列表或元组可以。例如len(itertools.imap(lambda x:x*2, range(3))) 将失败。
    【解决方案9】:

    这是一个适用于列表和生成器的答案:

    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
    

    【讨论】:

      【解决方案10】:

      以下是您如何处理列表与迭代器:

      def isList(L): # Implement it somehow - returns True or false
      ...
      return (list, lambda x:x)[int(islist(L))](result)
      

      【讨论】:

        【解决方案11】:
        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]]'
        

        【讨论】:

          【解决方案12】:

          这样就可以了

          from itertools import izip_longest
          izip_longest(it[::2], it[1::2])
          

          其中 *it* 是一些可迭代的


          例子:

          izip_longest('abcdef'[::2], 'abcdef'[1::2]) -> ('a', 'b'), ('c', 'd'), ('e', 'f')
          

          让我们分解一下

          'abcdef'[::2] -> 'ace'
          'abcdef'[1::2] -> 'bdf'
          

          如您所见,切片中的最后一个数字指定了用于拾取物品的间隔。您可以阅读有关使用扩展切片的更多信息here

          zip 函数从第一个可迭代对象中获取第一项,并将其与具有第二个可迭代对象的第一项结合起来。然后 zip 函数对第二项和第三项执行相同的操作,直到其中一个可迭代项用完值。

          结果是一个迭代器。如果你想要一个列表,请在结果上使用 list() 函数。

          【讨论】:

          • OP 已经知道 zip。但这不适用于生成器,并且不包括 OP 说他想要的奇数大小的迭代的最后一个元素。
          • 任意迭代不支持切片(例如:xrange(10)[::2] 是一个错误)。
          【解决方案13】:

          如果你想要一个解决方案

          • 仅使用生成器(无中间列表或元组),
          • 适用于非常长(或无限)的迭代器,
          • 适用于非常大的批量,

          这就是诀窍:

          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_iterator() 方法中引发StopIteration 的正确时间,因此无需捕获异常。

          这将批量处理来自标准输入的行:

          for input_batch in batch_iterator(sys.stdin, 10000):
              for line in input_batch:
                  process(line)
              finalise()
          

          我发现这对于处理大量数据并将结果批量上传到外部存储非常有用。

          【讨论】:

            猜你喜欢
            • 2011-04-28
            • 2021-03-07
            • 2011-02-11
            • 2022-12-16
            • 2018-04-04
            • 1970-01-01
            • 1970-01-01
            • 2021-12-01
            • 1970-01-01
            相关资源
            最近更新 更多