【问题标题】:Python generator that groups another iterable into groups of NPython 生成器,将另一个可迭代对象分组为 N 组
【发布时间】:2011-04-28 21:49:16
【问题描述】:

我正在寻找一个函数,它采用可迭代的 i 和大小 n 并产生长度为 n 的元组,它们是来自 i 的顺序值:

x = [1,2,3,4,5,6,7,8,9,0]
[z for z in TheFunc(x,3)]

给予

[(1,2,3),(4,5,6),(7,8,9),(0)]

标准库中是否存在这样的函数?

如果它作为标准库的一部分存在,我似乎找不到它,而且我已经用完了要搜索的术语。我可以自己写,但我宁愿不写。

【问题讨论】:

标签: python generator std


【解决方案1】:

查看docs for the itertools package 中的grouper 配方

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

(但是,这是 quite a few questions 的副本。)

【讨论】:

  • 如果我知道要搜索“grouper”,我根本不需要问。但我不知道这个词。
  • +1。完全忘记了文档中的方便食谱。
  • 我最终使用了它,但不得不在稍后阶段过滤掉填充值。
  • 很高兴知道但不回答这个问题,因为 OP 想要最后一个没有填充的块
【解决方案2】:

这个怎么样?但它没有填充值。

>>> def partition(itr, n):
...     i = iter(itr)
...     res = None
...     while True:
...             res = list(itertools.islice(i, 0, n))
...             if res == []:
...                     break
...             yield res
...
>>> list(partition([1, 2, 3, 4, 5, 6, 7, 8, 9], 3))
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>>

它使用原始可迭代对象的副本,每次连续拼接都会耗尽该副本。我疲惫的大脑能想到的唯一其他方法是生成具有范围的拼接端点。

也许我应该将 list() 更改为 tuple() 以便更好地对应您的输出。

【讨论】:

  • 哈哈。你一定是在开玩笑吧。答案中有一个错误,我对它的编辑被拒绝了?我对 SO 社区的尊重大大降低了。
  • btw, itertools.islice(i, 0, 3) -> itertools.islice(i, 0, n) 还是不敢相信 SO 社区。​​span>
  • 我没有拒绝,其他人拒绝了。但你是对的。 3 是硬编码的,否定了 n 作为参数的目的。如果你愿意,我可以编辑它,但你不会得到任何代表,这取决于你:)
  • 是的......我现在已经有点克服了。继续自己编辑吧:)
【解决方案3】:
    def grouper(iterable, n):
        while True:
            yield itertools.chain((next(iterable),), itertools.islice(iterable, n-1))

【讨论】:

  • 如果您在该代码中包含一个简短的解释,您的答案会更好。
【解决方案4】:

如果您想将迭代器分组为 n 的块不填充最后一个组具有填充值,请使用 iter(lambda: list(IT.islice(iterable, n)), [])

import itertools as IT

def grouper(n, iterable):
    """
    >>> list(grouper(3, 'ABCDEFG'))
    [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']]
    """
    iterable = iter(iterable)
    return iter(lambda: list(IT.islice(iterable, n)), [])

seq = [1,2,3,4,5,6,7]
print(list(grouper(3, seq)))

产量

[[1, 2, 3], [4, 5, 6], [7]]

this answer的后半部分有对其工作原理的解释。


如果您想将迭代器分组为 n 的块并用填充值填充最终组,请使用 grouper recipe zip_longest(*[iterator]*n)

例如,在 Python2 中:

>>> list(IT.izip_longest(*[iter(seq)]*3, fillvalue='x'))
[(1, 2, 3), (4, 5, 6), (7, 'x', 'x')]

在 Python3 中,以前的 izip_longest 现在重命名为 zip_longest

>>> list(IT.zip_longest(*[iter(seq)]*3, fillvalue='x'))
[(1, 2, 3), (4, 5, 6), (7, 'x', 'x')]

当您想将 序列 分组为 n 的块时,您可以使用 chunks 配方

def chunks(seq, n):
    # https://stackoverflow.com/a/312464/190597 (Ned Batchelder)
    """ Yield successive n-sized chunks from seq."""
    for i in xrange(0, len(seq), n):
        yield seq[i:i + n]

请注意,与一般的迭代器不同,sequences by definition 有一个长度(即定义了 __len__)。

【讨论】:

  • 很棒的答案,这对我帮助很大。谢谢!
  • 这个答案对我遇到的问题有很大帮助,非常感谢 6 多年前发布它。
【解决方案5】:

我知道这已经回答了好几次了,但我添加了我的解决方案,与 grouper 配方相比,它应该在序列和迭代器的一般适用性、可读性(StopIteration 异常没有不可见的循环退出条件)和性能方面都有所提高.它与 Svein 的最后一个答案最相似。

def chunkify(iterable, n):
    iterable = iter(iterable)
    n_rest = n - 1

    for item in iterable:
        rest = itertools.islice(iterable, n_rest)
        yield itertools.chain((item,), rest)

【讨论】:

    【解决方案6】:

    这是一个不同的解决方案,它不使用 itertools,即使它有几行,但当块比可迭代长度短很多时,它显然优于给定的答案。 但是,对于大块,其他答案要快得多。

    def batchiter(iterable, batch_size):
        """
        >>> list(batchiter('ABCDEFG', 3))
        [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']]
        """
        next_batch = []
        for element in iterable:
            next_batch.append(element)
            if len(next_batch) == batch_size:
                batch, next_batch = next_batch, []
                yield batch
        if next_batch:
            yield next_batch
    
    
    In [19]: %timeit [b for b in batchiter(range(1000), 3)]
    1000 loops, best of 3: 644 µs per loop
    
    In [20]: %timeit [b for b in grouper(3, range(1000))]
    1000 loops, best of 3: 897 µs per loop
    
    In [21]: %timeit [b for b in partition(range(1000), 3)]
    1000 loops, best of 3: 890 µs per loop
    
    In [22]: %timeit [b for b in batchiter(range(1000), 333)]
    1000 loops, best of 3: 540 µs per loop
    
    In [23]: %timeit [b for b in grouper(333, range(1000))]
    10000 loops, best of 3: 81.7 µs per loop
    
    In [24]: %timeit [b for b in partition(range(1000), 333)]
    10000 loops, best of 3: 80.1 µs per loop
    

    【讨论】:

      【解决方案7】:

      这是 Python 中非常常见的请求。足够常见,以至于它进入了boltons 统一实用程序包。首先,there are extensive docs here。此外,the module 的设计和测试仅依赖于标准库(兼容 Python 2 和 3),这意味着您可以使用just download the file directly into your project

      # if you downloaded/embedded, try:
      # from iterutils import chunked
      
      # with `pip install boltons` use:
      
      from boltons.iterutils import chunked 
      
      print(chunked(range(10), 3))
      # [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
      

      对于不定/长序列也有一个迭代器/生成器形式:

      print(list(chunked_iter(range(10), 3, fill=None)))
      # [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, None, None]]
      

      如您所见,您也可以用您选择的值填充序列。最后,作为维护者,我可以向您保证,虽然代码已经被成千上万的开发人员下载/测试,但如果您遇到任何问题,您将通过boltons GitHub Issues page 获得最快的支持。希望这(和/或任何其他 150 多个 Boltons 食谱)有所帮助!

      【讨论】:

        【解决方案8】:

        我使用chunked function from the more_itertools package

        $ pip install more_itertools
        $ python
        >>> x = [1,2,3,4,5,6,7,8,9,0]
        >>> [tuple(z) for z in more_itertools.more.chunked(x, 3)]
        [(1, 2, 3), (4, 5, 6), (7, 8, 9), (0,)]
        

        【讨论】:

          【解决方案9】:

          这是一个非常古老的问题,但我认为在一般情况下提及以下方法很有用。它的主要优点是它只需要对数据进行一次迭代,因此它可以与数据库游标或其他只能使用一次的序列一起使用。我也觉得它更具可读性。

          def chunks(n, iterator):
              out = []
              for elem in iterator:
                  out.append(elem)
                  if len(out) == n:
                      yield out
                      out = []
              if out:
                  yield out
          

          【讨论】:

          • 这是最优雅的答案。唯一的问题是它可以返回一个空列表作为最后一个块。在最后一行之前添加if len(out) > 0: 来解决这个问题。
          猜你喜欢
          • 2010-12-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-18
          • 1970-01-01
          • 2021-12-01
          • 2018-05-12
          • 1970-01-01
          相关资源
          最近更新 更多