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

этот вопрос уже есть ответ здесь:

Я ищу функцию, которая принимает iterable 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 и pad конечная группа со значением заполнения, используйте рецепт окунь 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]

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


как насчет этого? Однако у него нет значения заполнения.

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

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

может быть, я должен изменить list() to tuple() поэтому он лучше соответствует вашему выходу.


Это очень распространенный запрос в 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]]

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


Я использую функция 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,)]

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

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

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

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)

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

    def grouper(iterable, n):
        while True:
            yield itertools.chain((next(iterable),), itertools.islice(iterable, n-1))