Генератор Python, который группирует другую итерацию в группы из N

Я ищу функцию, которая принимает итеративный 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)]

Существует ли такая функция в стандартной библиотеке?

Если он существует как часть стандартной библиотеки, я не могу найти его, и у меня закончились условия для поиска. Я мог бы написать свой собственный, но я бы не хотел.

9 ответов

Решение

Увидеть grouper рецепт в документах для itertools пакет

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

(Тем не менее, это дубликат довольно многих вопросов.)

Когда вы хотите сгруппировать итератор по частям 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]]

Существует объяснение того, как это работает во второй половине этого ответа.


Когда вы хотите сгруппировать итератор по частям n и дополните последнюю группу значением заполнения, используйте рецепт группировщика 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://stackru.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]

Обратите внимание, что, в отличие от итераторов в целом, последовательности по определению имеют длину (т.е. __len__ определено).

Как насчет этого? Это не имеет значения заполнения, хотя.

>>> 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() так что это лучше соответствует вашему выводу.

Я использую функцию chunked из пакета more_itertools.

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

Это очень распространенный запрос в Python. Достаточно общего, что он превратил его в единый пакет утилит. Во-первых, здесь есть обширная документация. Кроме того, модуль спроектирован и протестирован так, чтобы полагаться только на стандартную библиотеку (совместимую с Python 2 и 3), что означает, что вы можете просто загрузить файл прямо в ваш проект.

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

Как видите, вы также можете заполнить последовательность значением по вашему выбору. Наконец, как сопровождающий, я могу заверить вас, что, несмотря на то, что код был загружен / протестирован тысячами разработчиков, если вы столкнетесь с какими-либо проблемами, вы получите самую быструю поддержку на странице болтов GitHub Issues. Надеюсь, что это (и / или любой другой из более чем 150 рецептов болтонов) помогло!

Это очень старый вопрос, но я думаю, что полезно упомянуть следующий подход для общего случая. Его главное достоинство заключается в том, что ему нужно только итерировать данные один раз, поэтому он будет работать с курсорами базы данных или другими последовательностями, которые можно использовать только один раз. Я также нахожу это более читабельным.

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

Вот другое решение, которое не использует 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

Я знаю, что на этот вопрос уже отвечали несколько раз, но я добавляю свое решение, которое должно улучшить как общую применимость к последовательностям и итераторам, так и удобочитаемость (отсутствие условия выхода из невидимого цикла, исключение StopIteration) и производительность по сравнению с рецептом группы. Это наиболее похоже на последний ответ Свейна.

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)
    def grouper(iterable, n):
        while True:
            yield itertools.chain((next(iterable),), itertools.islice(iterable, n-1))
Другие вопросы по тегам