Подтвердить что ты не робот

Итерировать итератор кусками (из n) в Python?

Можете ли вы придумать хороший способ (возможно, с itertools) разделить итератор на куски заданного размера?

Поэтому l=[1,2,3,4,5,6,7] с chunks(l,3) становится итератором [1,2,3], [4,5,6], [7]

Я могу придумать небольшую программу, чтобы сделать это, но не очень хорошо с помощью itertools.

4b9b3361

Ответ 1

Рецепт grouper() из документации itertools recipes близок к тому, что вы хотите:

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

Он заполнит последний кусок значением заполнения, однако.

Менее общее решение, которое работает только с последовательностями, но обрабатывает последний фрагмент по желанию,

[my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)]

Наконец, решение, которое работает на общих итераторах, ведет себя по желанию,

def grouper(n, iterable):
    it = iter(iterable)
    while True:
       chunk = tuple(itertools.islice(it, n))
       if not chunk:
           return
       yield chunk

Ответ 2

Хотя OP запрашивает функцию, чтобы возвращать куски в виде списка или кортежа, в случае, если вам нужно вернуть итераторы, тогда можно решение Sven Marnach:

def grouper_it(n, iterable):
    it = iter(iterable)
    while True:
        chunk_it = itertools.islice(it, n)
        try:
            first_el = next(chunk_it)
        except StopIteration:
            return
        yield itertools.chain((first_el,), chunk_it)

Некоторые ориентиры: http://pastebin.com/YkKFvm8b

Он будет немного более эффективным, только если ваша функция выполняет итерацию через элементы в каждом фрагменте.

Ответ 3

Это будет работать с любым итерабельным. Он возвращает генератор генераторов (для полной гибкости). Теперь я понимаю, что это в основном то же самое, что и решение @reclosedevs, но без пуха. Нет необходимости в try...except по мере распространения StopIteration, чего мы хотим.

Вызов iterable.next() необходим, чтобы поднять StopIteration, когда итерабельность пуста, так как islice будет продолжать порождать пустые генераторы навсегда, если вы позволите.

Это лучше, потому что это всего лишь две строки, но их легко понять.

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

Обратите внимание, что iterable.next() помещается в список. Если iterable.next() можно повторить и не помещать в список, то itertools.chain сгладит этот объект. Спасибо Джереми Брауну за то, что он указал на эту проблему.

Ответ 4

Я работал над чем-то сегодня и придумал то, что я считаю простым решением. Он похож на ответ jsbueno, но я считаю, что он даст пустой group, когда длина iterable делится на n. Мой ответ делает простую проверку, когда iterable исчерпан.

def chunk(iterable, chunk_size):
    """Generate sequences of `chunk_size` elements from `iterable`."""
    iterable = iter(iterable)
    while True:
        chunk = []
        try:
            for _ in range(chunk_size):
                chunk.append(iterable.next())
            yield chunk
        except StopIteration:
            if chunk:
                yield chunk
            break

Ответ 5

Здесь один, который возвращает ленивые куски; используйте map(list, chunks(...)), если вы хотите списки.

from itertools import islice, chain
from collections import deque

def chunks(items, n):
    items = iter(items)
    for first in items:
        chunk = chain((first,), islice(items, n-1))
        yield chunk
        deque(chunk, 0)

if __name__ == "__main__":
    for chunk in map(list, chunks(range(10), 3)):
        print chunk

    for i, chunk in enumerate(chunks(range(10), 3)):
        if i % 2 == 1:
            print "chunk #%d: %s" % (i, list(chunk))
        else:
            print "skipping #%d" % i

Ответ 6

"Упрощение лучше, чем сложное" - простой генератор длиной несколько строк может выполнить эту работу. Просто поместите его в некоторый модуль утилит или так:

def grouper (iterable, n):
    iterable = iter(iterable)
    count = 0
    group = []
    while True:
        try:
            group.append(next(iterable))
            count += 1
            if count % n == 0:
                yield group
                group = []
        except StopIteration:
            yield group
            break

Ответ 7

Краткая реализация:

chunker = lambda iterable, n: (ifilterfalse(lambda x: x == (), chunk) for chunk in (izip_longest(*[iter(iterable)]*n, fillvalue=())))

Это работает, потому что [iter(iterable)]*n - это список, содержащий один и тот же итератор n раз; zipping over, который берет один элемент из каждого итератора в списке, который является тем же самым итератором, в результате каждый zip-элемент содержит группу элементов n.

izip_longest необходим, чтобы полностью использовать базовый итерируемый, а не итерацию, останавливающуюся при достижении первого исчерпанного итератора, который отрубает остаток от iterable. Это приводит к необходимости отфильтровать значение заполнения. Таким образом, немного более надежная реализация:

def chunker(iterable, n):
    class Filler(object): pass
    return (ifilterfalse(lambda x: x is Filler, chunk) for chunk in (izip_longest(*[iter(iterable)]*n, fillvalue=Filler)))

Это гарантирует, что значение заливки никогда не является элементом в исходном итерабельном. Используя вышеприведенное определение:

iterable = range(1,11)

map(tuple,chunker(iterable, 3))
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10,)]

map(tuple,chunker(iterable, 2))
[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]

map(tuple,chunker(iterable, 4))
[(1, 2, 3, 4), (5, 6, 7, 8), (9, 10)]

Эта реализация почти делает то, что вы хотите, но она имеет проблемы:

def chunks(it, step):
  start = 0
  while True:
    end = start+step
    yield islice(it, start, end)
    start = end

(Разница заключается в том, что, поскольку islice не вызывает StopIteration или что-либо еще при вызовах, выходящих за пределы it, это будет навсегда, а также немного сложная проблема, что результаты islice должны быть потребляется до повторения этого генератора).

Функциональное создание движущегося окна:

izip(count(0, step), count(step, step))

Итак, это будет:

(it[start:end] for (start,end) in izip(count(0, step), count(step, step)))

Но это все равно создает бесконечный итератор. Итак, вам нужно takewhile (или, возможно, что-то еще может быть лучше), чтобы ограничить его:

chunk = lambda it, step: takewhile((lambda x: len(x) > 0), (it[start:end] for (start,end) in izip(count(0, step), count(step, step))))

g = chunk(range(1,11), 3)

tuple(g)
([1, 2, 3], [4, 5, 6], [7, 8, 9], [10])

Ответ 8

Я забыл, где я нашел вдохновение для этого. Я немного изменил его для работы с MSI GUID в реестре Windows:

def nslice(s, n, truncate=False, reverse=False):
    """Splits s into n-sized chunks, optionally reversing the chunks."""
    assert n > 0
    while len(s) >= n:
        if reverse: yield s[:n][::-1]
        else: yield s[:n]
        s = s[n:]
    if len(s) and not truncate:
        yield s

reverse не относится к вашему вопросу, но это то, что я широко использую с этой функцией.

>>> [i for i in nslice([1,2,3,4,5,6,7], 3)]
[[1, 2, 3], [4, 5, 6], [7]]
>>> [i for i in nslice([1,2,3,4,5,6,7], 3, truncate=True)]
[[1, 2, 3], [4, 5, 6]]
>>> [i for i in nslice([1,2,3,4,5,6,7], 3, truncate=True, reverse=True)]
[[3, 2, 1], [6, 5, 4]]

Ответ 9

Здесь вы идете.

def chunksiter(l, chunks):
    i,j,n = 0,0,0
    rl = []
    while n < len(l)/chunks:        
        rl.append(l[i:j+chunks])        
        i+=chunks
        j+=j+chunks        
        n+=1
    return iter(rl)


def chunksiter2(l, chunks):
    i,j,n = 0,0,0
    while n < len(l)/chunks:        
        yield l[i:j+chunks]
        i+=chunks
        j+=j+chunks        
        n+=1

Примеры:

for l in chunksiter([1,2,3,4,5,6,7,8],3):
    print(l)

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

for l in chunksiter2([1,2,3,4,5,6,7,8],3):
    print(l)

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


for l in chunksiter2([1,2,3,4,5,6,7,8],5):
    print(l)

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