反復可能オブジェクトを一定サイズのチャンクに分割する方法


87

重複の可能性:
Pythonでリストを均等なサイズのチャンクに分割するにはどうすればよいですか?

iterableを入力として受け取り、iterableのiterableを返す「バッチ」関数が見つからなかったことに驚いています。

例えば:

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]

だから、私は何かを逃しました、そしてこれはおそらくPythonジェネレーターの私の完全な理解の欠如を示しています。誰かが私を正しい方向に向けることを気にかけますか?

[編集:上記の動作は、Python自体ではなくipython内で実行した場合にのみ発生することに最終的に気付きました]


良い質問です。よく書かれていますが、すでに存在していて、問題を解決します。
Josh Smeaton 2011年

7
IMOこれは実際には重複していません。もう1つの質問は、イテレーターではなくリストに焦点を当てており、それらの回答のほとんどは、イテレーターにとって望ましくないlen()を必要とします。しかし、
ええと

7
これは明らかに重複ではありません。他のQ&Aはリストに対してのみ機能します。この質問は、すべての反復可能オブジェクトに一般化することに関するものです。これは、私がここに来たときに念頭に置いていた質問です。
マークE.ハース2017年

1
@JoshSmeaton @casperOneこれは重複ではなく、受け入れられた回答は正しくありません。リンクされた重複する質問はリスト用であり、これは反復可能です。リスト()メソッドをlenを提供しますが、反復可能がlen()メソッドを提供していないと答えは(LENを使用せずに異なるだろう)これが正しい答えである: batch = (tuple(filterfalse(lambda x: x is None, group)) for group in zip_longest(fillvalue=None, *[iter(iterable)] * n))
Trideepラース

@TrideepRathうん、私は再開することに投票した。
JoshSmeaton19年

回答:


126

これはおそらくより効率的(より高速)です

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
記録として、これは私が見つけた最速のソリューションです:mine = 4.5s、yours = 0.43s、Donkopotamus = 14.8s
mathieu

77
実際、バッチはリスト(len()あり)を受け入れますが、反復可能ではありません(len()なし)
tdihp 2014年

31
これは問題の解決策ではないため、より高速です。Raymond Hettingerによるグルーパーレシピ(現在これより下)は、入力オブジェクトにlenメソッドを必要としない一般的なソリューションを探しているものです。
Robert E Mealey 2014年

7
なぜmin()を使用するのですか?min()コードなしで完全に正しいです!
Pavel Patrin 2017年

21
Iterablesにはありませんlen()シーケンスにはありますlen()
Kos

64

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
置き換え@mathieuizip_longestizip、そのうではないパッドの最後のエントリを、代わりに要素の一部が不足して起動したときにエントリを遮断。
googieK 2016

3
Python3ではzip_longest / zipである必要があります
Peter Gerdes

5
@GoogieKfor x, y in enumerate(grouper(3, xrange(10))): print(x,y)は実際には値を入力せず、不完全なセグメントを完全に削除するだけです。
kadrach 2017年

3
不完全な場合に最後の要素を削除するワンライナーとして:list(zip(*[iter(iterable)] * n))。これは私が今まで見た中で最も近いPythonコードでなければなりません。
LeFrite19年

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()StopIteration1回sourceiterが使い果たされるようにし、イテレータを終了します。nextそれを呼び出さないと、空のイテレータが無期限に返され続けます。
donkopotamus 2017

7
私は交換していたbatchiter.next()next(batchiter)はPython 3に上記のコードを動作させるために
マーティンWiebusch

2
リンクされた記事からのコメントを指摘します。「次のバッチに進む前に、バッチを完全に消費する必要があるという警告を追加する必要があります。」これの出力は、次のようなもので消費する必要がありますmap(list, batch(xrange(10), 3))。実行list(batch(xrange(10), 3)すると、予期しない結果が発生します。
Nathan Buesgens 2017

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おり、その中にあります。


これは確かに最も適切な答えであり(もう1つのパッケージをインストールする必要がありますが)、ichunked反復可能であるということもあります。
viddik 1320年

10

奇妙なことに、Python2.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

8

これは私が知っている非常に短いコードスニペットlenであり、Python 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)))

7

len関数を定義しない反復可能オブジェクトを使用していて、疲れ果てている場合のPython3.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]

もちろん、セイウチの演算子なしでも実装できます。


3
現在のバージョンでbatcherは、イテレータではなくイテレータを受け入れます。たとえば、リストを含む無限ループが発生します。ループをiterator = iter(iterable)開始する前に、おそらく行があるはずwhileです。
ダニエルペレス

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

ベストの答えこれまでに、すべてのデータ構造を持つ作品
クレマンPREVOST

1

これは、どの反復可能でも機能します。

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

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

1

@Atra Azamiの回答を基にした、Python3.8の新機能のない実行可能なバージョン。

import itertools    

def batch_generator(iterable, batch_size=1):
    iterable = iter(iterable)

    while True:
        batch = list(itertools.islice(iterable, batch_size))
        if len(batch) > 0:
            yield batch
        else:
            break

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

出力:

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

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.