Итерировать итератор кусками (из n) в Python? [дубликат]


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

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

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

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

9 87

9 ответов:

The grouper() рецепт itertools документации!--6-->рецепты приближается к тому, что вы хотите:

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

хотя OP просит функцию возвращать куски в виде списка или кортежа, если вам нужно вернуть итераторы, то Свен Марнах решение может быть изменен:

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

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

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

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

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

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

отметим, что next(iterable) - это поместить в кортеж. В противном случае, если были Iterable, тогда itertools.chain сгладил бы его. Спасибо Джереми Брауну за указание на эту проблему.

я работал над чем-то сегодня и придумал то, что я думаю, это простое решение. Это похоже на jsbueno это ответь, но я считаю его пустымgroups, когда длина 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

вот тот, который возвращает ленивые куски; используйте 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

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

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

это работает, потому что [iter(iterable)]*n - это список, содержащий один и тот же итератор n раз; проскальзывание, которое берет один элемент из каждого итератора в списке,который является тем же итератором, в результате чего каждый 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])

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

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

Я забыл, где я нашел вдохновение для этого. Я немного изменил его для работы с 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]]

вот так.

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]