iterable을 일정한 크기의 청크로 분할하는 방법


85

중복 가능성 :
Python에서 목록을 균등 한 크기의 청크로 어떻게 분할합니까?

이터 러블을 입력으로 받아 이터 러블의 이터 러블을 반환하는 "배치"함수를 찾을 수 없다는 것에 놀랐습니다.

예를 들면 :

for i in batch(range(0,10), 1): print i
[0]
[1]
...
[9]

또는:

for i in batch(range(0,10), 3): print i
[0,1,2]
[3,4,5]
[6,7,8]
[9]

이제 저는 매우 간단한 생성기라고 생각한 것을 작성했습니다.

def batch(iterable, n = 1):
   current_batch = []
   for item in iterable:
       current_batch.append(item)
       if len(current_batch) == n:
           yield current_batch
           current_batch = []
   if current_batch:
       yield current_batch

그러나 위의 내용은 내가 기대했던 것을 제공하지 않습니다.

for x in   batch(range(0,10),3): print x
[0]
[0, 1]
[0, 1, 2]
[3]
[3, 4]
[3, 4, 5]
[6]
[6, 7]
[6, 7, 8]
[9]

그래서 나는 무언가를 놓 쳤고 이것은 아마도 파이썬 생성기에 대한 나의 완전한 이해 부족을 보여줄 것입니다. 누구든지 나를 올바른 방향으로 안내해 줄 수 있습니까?

[편집 : 결국 위의 동작은 파이썬 자체가 아닌 ipython 내에서 실행할 때만 발생한다는 것을 깨달았습니다.]


잘 쓰여진 좋은 질문이지만 이미 존재하며 문제를 해결할 것입니다.
Josh Smeaton 2011

7
IMO 이것은 실제로 중복이 아닙니다. 다른 질문은 반복자 대신 목록에 초점을 맞추고 있으며 대부분의 답변에는 반복기에 바람직하지 않은 len ()이 필요합니다. 그러나 어, 현재 허용 대답은 여기에 또한 () 렌이 필요합니다, 그래서 ...
dequis

7
이것은 분명히 중복이 아닙니다. 다른 Q & A 는 목록에서만 작동 하며이 질문은 모든 반복 가능 항목에 대한 일반화에 관한 것입니다.이 질문은 제가 여기 왔을 때 염두에 두었던 질문입니다.
마크 E. 하세

1
@JoshSmeaton @casperOne 이것은 중복이 아니며 수락 된 답변이 올바르지 않습니다. 연결된 중복 질문은 목록 용이며 반복 가능한 질문입니다. list는 len () 메소드를 제공하지만 iterable은 len () 메소드를 제공하지 않으며 len ()을 사용하지 않으면 대답이 다를 수 있습니다. 이것은 정답입니다. batch = (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *[iter(iterable)] * n))
Trideep Rath

@TrideepRath 그래, 나는 재개하기로 투표했다.
Josh Smeaton

답변:


117

이것은 아마도 더 효율적일 것입니다 (더 빠름).

def batch(iterable, n=1):
    l = len(iterable)
    for ndx in range(0, l, n):
        yield iterable[ndx:min(ndx + n, l)]

for x in batch(range(0, 10), 3):
    print x

목록을 사용한 예

data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # list of data 

for x in batch(data, 3):
    print(x)

# Output

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

새 목록 작성을 방지합니다.


4
기록을 위해, 이것이 내가 찾은 가장 빠른 해결책입니다 : 내 = 4.5s, 당신의 = .43s, Donkopotamus = 14.8s
mathieu

74
사실에 배치, 반복 가능하지 (렌없이 () () () 렌 포함) 목록을 받아
tdihp

28
이것은 문제에 대한 해결책이 아니기 때문에 더 빠릅니다. Raymond Hettinger의 그룹화 레시피 (현재이 아래에 있음)는 입력 개체에 len 메서드 가 필요하지 않은 일반적인 솔루션을 찾고있는 것입니다 .
Robert E Mealey 2014 년

7
min ()을 사용하는 이유는 무엇입니까? min()코드 없이는 완전히 정확합니다!
Pavel Patrin

20
Iterable 에는 len(), 시퀀스 가 없습니다len()
Kos

60

FWIW, itertools 모듈레시피 는 다음 예제를 제공합니다.

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

다음과 같이 작동합니다.

>>> list(grouper(3, range(10)))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]

13
이것은 None 집합으로 마지막 요소를 채웠기 때문에 정확히 필요한 것은 아닙니다. 즉, None은 실제로 내 함수와 함께 사용하는 데이터의 유효한 값이므로 대신 필요한 것은 마지막 항목을 채우지 않는 것입니다.
mathieu 2011

12
@mathieu은 교체 izip_longest와 함께 izip하는 패드 마지막 항목되지 않지만 일부 요소가 밖으로 실행 시작할 때 대신 항목 잘라.
GoogieK

3
zip_longest해야 / 파이썬 3에서 압축
피터 GERDES

5
@GoogieK for x, y in enumerate(grouper(3, xrange(10))): print(x,y)는 실제로 값을 채우지 않고 불완전한 세그먼트를 모두 삭제합니다.
kadrach

3
불완전한 경우 마지막 요소를 드롭하는 단일 라이너로 : list(zip(*[iter(iterable)] * n)). 이것은 내가 본 것 중 가장 간결한 파이썬 코드 여야한다.
Le Frite

31

다른 사람들이 언급했듯이 귀하가 제공 한 코드는 귀하가 원하는 것을 정확히 수행합니다. 사용하는 다른 접근 방식 의 경우 다음 레시피 itertools.islice 를 볼 수 있습니다 .

from itertools import islice, chain

def batch(iterable, size):
    sourceiter = iter(iterable)
    while True:
        batchiter = islice(sourceiter, size)
        yield chain([batchiter.next()], batchiter)

1
@abhilash 아니 ...이 코드에 대한 호출을 사용하여 next()원인을 StopIteration한 번을 sourceiter따라서 반복자를 종료, 소진된다. 이에 대한 호출이 없으면 next계속해서 빈 반복기를 무기한으로 반환합니다.
donkopotamus 2017

7
나는 대체했다 batchiter.next()next(batchiter)파이썬 3 위의 코드가 작동하도록
마틴 Wiebusch

2
링크 된 기사에서 "다음 작업으로 진행하기 전에 배치를 완전히 소비해야한다는 경고를 추가해야합니다." 이것의 출력은 다음과 같이 소비되어야합니다 : map(list, batch(xrange(10), 3)). 수행 : list(batch(xrange(10), 3)예기치 않은 결과가 생성됩니다.
Nathan Buesgens

2
py3에서 작동하지 않습니다. .next()로 변경해야 next(..)하고, list(batch(range(0,10),3))슬로우RuntimeError: generator raised StopIteration
마티유

1
@mathieu : while루프를 try:/except StopIteration: return 로 후자의 문제를 해결합니다.
ShadowRanger

13

나는 단지 하나의 대답을 주었다. 그러나 이제는 새로운 기능을 작성하지 않는 것이 최선의 해결책이라고 생각합니다. More-itertools 에는 많은 추가 도구가 포함되어 chunked있으며 그 중 하나입니다.


이것은 실제로 가장 적합한 대답입니다 (패키지가 하나 더 설치되어야하지만), ichunkediterable 도 생성합니다.
viddik13

10

이상하다, Python 2.x에서 잘 작동하는 것 같습니다.

>>> def batch(iterable, n = 1):
...    current_batch = []
...    for item in iterable:
...        current_batch.append(item)
...        if len(current_batch) == n:
...            yield current_batch
...            current_batch = []
...    if current_batch:
...        yield current_batch
...
>>> for x in batch(range(0, 10), 3):
...     print x
...
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]

아무것도 가져올 필요가없고 읽기가 직관적이기 때문에 훌륭한 대답입니다.
ojunk 19

8

이것은 lenPython 2와 3 모두에서 사용하지 않고 작동 하는 매우 짧은 코드 스 니펫입니다 (내 생성이 아님).

def chunks(iterable, size):
    from itertools import chain, islice
    iterator = iter(iterable)
    for first in iterator:
        yield list(chain([first], islice(iterator, size - 1)))

4

len함수를 정의하지 않는 이터 러블을 사용하고 지쳐 있는 경우 Python 3.8에 대한 솔루션 :

def batcher(iterable, batch_size):
    while batch := list(islice(iterable, batch_size)):
        yield batch

사용 예 :

def my_gen():
    yield from range(10)
 
for batch in batcher(my_gen(), 3):
    print(batch)

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

물론 해마 운영자 없이도 구현할 수 있습니다.


1
현재 버전에서는 batcher이터 러블이 아닌 이터레이터를 허용합니다. 예를 들어 목록이있는 무한 루프가 발생합니다. 루프 iterator = iter(iterable)를 시작하기 전에 줄이있을 것입니다 while.
Daniel Perez

2

이것은 내 프로젝트에서 사용하는 것입니다. 가능한 한 효율적으로 반복 가능 또는 목록을 처리합니다.

def chunker(iterable, size):
    if not hasattr(iterable, "__len__"):
        # generators don't have len, so fall back to slower
        # method that works with generators
        for chunk in chunker_gen(iterable, size):
            yield chunk
        return

    it = iter(iterable)
    for i in range(0, len(iterable), size):
        yield [k for k in islice(it, size)]


def chunker_gen(generator, size):
    iterator = iter(generator)
    for first in iterator:

        def chunk():
            yield first
            for more in islice(iterator, size - 1):
                yield more

        yield [k for k in chunk()]

2
def batch(iterable, n):
    iterable=iter(iterable)
    while True:
        chunk=[]
        for i in range(n):
            try:
                chunk.append(next(iterable))
            except StopIteration:
                yield chunk
                return
        yield chunk

list(batch(range(10), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

지금까지 최고의 답변, 모든 데이터 구조에서 작동
Clément Prévost

1

이것은 iterable에 대해 작동합니다.

from itertools import zip_longest, filterfalse

def batch_iterable(iterable, batch_size=2): 
    args = [iter(iterable)] * batch_size 
    return (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *args))

다음과 같이 작동합니다.

>>>list(batch_iterable(range(0,5)), 2)
[(0, 1), (2, 3), (4,)]

추신 : iterable에 None 값이 있으면 작동하지 않습니다.


1

다음은 reduce함수를 사용하는 접근 방식 입니다.

짧막 한 농담:

from functools import reduce
reduce(lambda cumulator,item: cumulator[-1].append(item) or cumulator if len(cumulator[-1]) < batch_size else cumulator + [[item]], input_array, [[]])

또는 더 읽기 쉬운 버전 :

from functools import reduce
def batch(input_list, batch_size):
  def reducer(cumulator, item):
    if len(cumulator[-1]) < batch_size:
      cumulator[-1].append(item)
      return cumulator
    else:
      cumulator.append([item])
    return cumulator
  return reduce(reducer, input_list, [[]])

테스트:

>>> batch([1,2,3,4,5,6,7], 3)
[[1, 2, 3], [4, 5, 6], [7]]
>>> batch(a, 8)
[[1, 2, 3, 4, 5, 6, 7]]
>>> batch([1,2,3,None,4], 3)
[[1, 2, 3], [None, 4]]

0

반복 가능한 항목을 일괄 색인별로 그룹화 할 수 있습니다.

def batch(items: Iterable, batch_size: int) -> Iterable[Iterable]:
    # enumerate items and group them by batch index
    enumerated_item_groups = itertools.groupby(enumerate(items), lambda t: t[0] // batch_size)
    # extract items from enumeration tuples
    item_batches = ((t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    return item_batches

내부 반복 가능 항목을 수집하려는 경우가 많으므로 여기에 더 고급 버전이 있습니다.

def batch_advanced(items: Iterable, batch_size: int, batches_mapper: Callable[[Iterable], Any] = None) -> Iterable[Iterable]:
    enumerated_item_groups = itertools.groupby(enumerate(items), lambda t: t[0] // batch_size)
    if batches_mapper:
        item_batches = (batches_mapper(t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    else:
        item_batches = ((t[1] for t in enumerated_items) for key, enumerated_items in enumerated_item_groups)
    return item_batches

예 :

print(list(batch_advanced([1, 9, 3, 5, 2, 4, 2], 4, tuple)))
# [(1, 9, 3, 5), (2, 4, 2)]
print(list(batch_advanced([1, 9, 3, 5, 2, 4, 2], 4, list)))
# [[1, 9, 3, 5], [2, 4, 2]]

0

필요한 관련 기능 :

def batch(size, i):
    """ Get the i'th batch of the given size """
    return slice(size* i, size* i + size)

용법:

>>> [1,2,3,4,5,6,7,8,9,10][batch(3, 1)]
>>> [4, 5, 6]

시퀀스에서 i 번째 배치를 가져 오며 pandas 데이터 프레임 ( df.iloc[batch(100,0)]) 또는 numpy 배열 ( array[batch(100,0)]) 과 같은 다른 데이터 구조에서도 작동 할 수 있습니다 .


0
from itertools import *

class SENTINEL: pass

def batch(iterable, n):
    return (tuple(filterfalse(lambda x: x is SENTINEL, group)) for group in zip_longest(fillvalue=SENTINEL, *[iter(iterable)] * n))

print(list(range(10), 3)))
# outputs: [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)]
print(list(batch([None]*10, 3)))
# outputs: [(None, None, None), (None, None, None), (None, None, None), (None,)]

0

나는 사용한다

def batchify(arr, batch_size):
  num_batches = math.ceil(len(arr) / batch_size)
  return [arr[i*batch_size:(i+1)*batch_size] for i in range(num_batches)]
  

0

다 떨어질 때까지 (최대) n 개의 요소를 계속 취하십시오.

def chop(n, iterable):
    iterator = iter(iterable)
    while chunk := list(take(n, iterator)):
        yield chunk


def take(n, iterable):
    iterator = iter(iterable)
    for i in range(n):
        try:
            yield next(iterator)
        except StopIteration:
            return
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.