リストを均等なサイズのチャンクにどのように分割しますか?


2268

任意の長さのリストがあり、同じサイズのチャンクに分割して操作する必要があります。カウンタと2つのリストを保持するなど、これを行ういくつかの明白な方法があります。2番目のリストがいっぱいになったときに、最初のリストに追加し、次のデータラウンドのために2番目のリストを空にしますが、これは潜在的に非常にコストがかかります。

ジェネレーターを使用するなど、長さのリストに対してこれに対する良い解決策があるかどうか疑問に思いました。

に役立つものを探していましたitertoolsが、明らかに役立つものは見つかりませんでした。しかし、見逃したかもしれません。

関連質問:チャンクのリストを反復処理する最も「パイソン的な」方法は何ですか?


1
新しい回答を投稿する前に、この質問にはすでに60以上の回答があることを考慮してください。回答が既存の回答に含まれない情報を提供していることを確認してください。
janniks

任意に小さく、最終的なチャンクを避けたいユーザーのために、時を超える見てほぼ等しい長さのN個の部分に分割リストを
WIM

回答:


3151

以下は、必要なチャンクを生成するジェネレータです。

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

import pprint
pprint.pprint(list(chunks(range(10, 75), 10)))
[[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
 [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
 [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
 [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
 [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
 [70, 71, 72, 73, 74]]

Python 2を使用している場合は、次のxrange()代わりに使用する必要がありますrange()

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in xrange(0, len(lst), n):
        yield lst[i:i + n]

また、関数を記述する代わりにリスト内包表記を使用することもできますが、コードを理解しやすくするために、このような操作を名前付き関数にカプセル化することをお勧めします。Python 3:

[lst[i:i + n] for i in range(0, len(lst), n)]

Python 2バージョン:

[lst[i:i + n] for i in xrange(0, len(lst), n)]

72
リストの長さがわからない場合はどうなりますか?itertools.repeatでこれを試してみてください([1、2、3])、例えば
jespern

47
これは質問の興味深い拡張ですが、元の質問はリストの操作について明確に尋ねられました。
Ned Batchelder

33
この関数は、いまいましい標準ライブラリにある必要があります
dgan

6
@カリモ:何を提案しますか?47要素のリストを渡します。どのように「均等なサイズのチャンク」に分割しますか?OPは答えを受け入れたので、最後の異なるサイズのチャンクで明らかにOKです。おそらく英語のフレーズは不正確ですか?
Ned Batchelder 2018年

8
変数にlという名前を付けないでください。1のように見え、混乱を招きます。人々はあなたのコードをコピーしていて、これは大丈夫だと思っています。
Yasen

555

あなたが何か超シンプルなものが欲しいなら:

def chunks(l, n):
    n = max(1, n)
    return (l[i:i+n] for i in range(0, len(l), n))

Python 2.xの場合のxrange()代わりに使用range()


6
または(この特定の関数のさまざまな表現をしている場合)次の方法でラムダ関数を定義できます:lambda x、y:[x [i:i + y] for i for range(0、len(x)、y) ]。私はこのリスト理解方法が大好きです!
JP

4
戻った後は、[でなければならない(
alwbtc

2
「超シンプル」とは、無限ループをデバッグする必要がないことを意味しmax()ます。
ボブスタイン

このソリューションについて単純なことは何もありません
mit

1
@Nhoj_Gonkおっと、それは無限ループではありませんが、chunks(L、0)はmax()なしでValueErrorを発生させます。代わりに、最大()は1に何も1未満になります
ボブ・スタイン

295

(古い)Pythonドキュメントから直接(itertoolsのレシピ):

from itertools import izip, chain, repeat

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

JFSebastianによって提案された現在のバージョン:

#from itertools import izip_longest as zip_longest # for Python 2.x
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

グイドのタイムマシンの動作、つまり動作した-動作する-動作するだろう-が再び動作したと思います。

これらのソリューションが機能するのは、[iter(iterable)]*n(または以前のバージョンの同等の)イテレータが1つ作成さnれ、リスト内で繰り返されるためです。izip_longest次に、「各」イテレータのラウンドロビンを効果的に実行します。これは同じイテレーターであるため、このような呼び出しごとに進められ、そのような各zip-roundrobinは1つのタプルのn項目を生成します。


@ninjagecko:をlist(grouper(3, range(10)))返します[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]。タプルはすべて長さが3です。コメントは理解できません。詳しく説明してください。何をあなたは呼んでないことを、どのようにあなたはそれがあることを定義します3の倍数「あなたのことが3の倍数であることを期待して」で?前もって感謝します。
tzot

14
ジェネレーター(lenなし)で動作し、一般的に高速なitertoolsモジュールを使用するため、これに賛成しました。
Michael Dillon

88
itertoolsシンプルで素朴な純粋なpythonの実装と比較した場合、読みにくいスラッジを明らかにする、ファンシーな機能的アプローチの古典的な例
wim

15
@wimこの回答がPythonのドキュメントからの抜粋として始まったことを考えると、bugs.python.orgで問題を開くことをお勧めします。
tzot 2013

1
@pedrosaurio if if l==[1, 2, 3]then if is equal f(*l)to f(1, 2, 3)その質問公式ドキュメントを参照しください。
tzot

226

私はこれが古いものであることを知っていますが、まだ誰も言及していませんnumpy.array_split

import numpy as np

lst = range(50)
np.array_split(lst, 5)
# [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
#  array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
#  array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
#  array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]),
#  array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]

12
これにより、チャンクあたりの要素数ではなく、チャンクの総数を設定できます。
FizxMike

6
あなたは自分で数学をすることができます。あなたは10個の要素を持っている場合は、2、5つの要素の塊または5つの2要素のチャンクにグループにそれらをすることができます
法務省

24
+1これは私のお気に入りのソリューションです。これは、配列を均等なサイズの配列分割する一方で、他のソリューションは分割しないためです(私が調べた他のすべてのソリューションでは、最後の配列は任意に小さい場合があります)。
MiniQuark 2016年

@MiniQuarkしかし、ブロックの数が元の配列サイズの係数ではない場合、これはどうなりますか?
Baldrickk、

1
@Baldrickk N個の要素をK個のチャンクに分割した場合、最初のN%K個のチャンクにはN // K + 1個の要素が含まれ、残りにはN // K個の要素が含まれます。たとえば、108要素を含む配列を5つのチャンクに分割すると、最初の108%5 = 3チャンクには108 // 5 + 1 = 22要素が含まれ、残りのチャンクには108 // 5 = 21が含まれます。要素。
ミニクォーク、

147

iter2つの引数の形式を使用することを誰も考えたことがないことに驚きます

from itertools import islice

def chunk(it, size):
    it = iter(it)
    return iter(lambda: tuple(islice(it, size)), ())

デモ:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]

これはイテラブルで動作し、遅延して出力を生成します。イテレータではなくタプルを返しますが、それでもある程度の優雅さはあると思います。また、パディングしません。パディングが必要な場合は、上記の簡単なバリエーションで十分です。

from itertools import islice, chain, repeat

def chunk_pad(it, size, padval=None):
    it = chain(iter(it), repeat(padval))
    return iter(lambda: tuple(islice(it, size)), (padval,) * size)

デモ:

>>> list(chunk_pad(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk_pad(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

izip_longestベースのソリューションと同様に、上記は常にパディングされます。私の知る限り、オプションでパディングする関数の1行または2行のitertoolsレシピはありません。上記の2つのアプローチを組み合わせると、これはかなり近づきます。

_no_padding = object()

def chunk(it, size, padval=_no_padding):
    if padval == _no_padding:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(padval))
        sentinel = (padval,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

デモ:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
>>> list(chunk(range(14), 3, None))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

これは、オプションのパディングを提供する、提案された最短のチャンカーだと思います。

Tomasz Gandorが観察したように、2つのパディングチャンカーは、長いパッド値のシーケンスに遭遇すると予期せず停止します。次に、その問題を合理的な方法で回避する最後のバリエーションを示します。

_no_padding = object()
def chunk(it, size, padval=_no_padding):
    it = iter(it)
    chunker = iter(lambda: tuple(islice(it, size)), ())
    if padval == _no_padding:
        yield from chunker
    else:
        for ch in chunker:
            yield ch if len(ch) == size else ch + (padval,) * (size - len(ch))

デモ:

>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]

7
素晴らしい、あなたのシンプルなバージョンは私のお気に入りです。他の人も基本的なislice(it, size)式を考え出し、それを(私が行ったように)ループ構造に埋め込みました。iter()(私が完全に気付かなかった)の2つの引数のバージョンを考えたのはあなただけでした。iter歩哨が与えられたときに、最初の引数が0引数の関数に変わるとは思いもしませんでした。チャンクの(pot。infinite)イテレータを返し、入力として(pot。infinite)イテレータを使用できます。len()配列スライスはありません。驚くばかり!
ThomasH 2016

1
これが、上位のカップルだけをスキャンするのではなく、答えを読み通す理由です。私の場合、オプションのパディングが必須でしたが、私は2引数形式のiterについても学びました。
Kerr

私はこれに賛成しましたが、それでも-誇張しすぎないようにしましょう!まず、ラムダはオーバー悪い(遅い閉鎖可能itのチャンクがあれば、あなたが途中で終了します- 。第二に、イテレータ、最もimportanlty padval実際に反復可能に存在し、かつ処理されるべきである。
トマシュGandor

@TomaszGandor、私はあなたの最初のポイントを取ります!私の理解では、ラムダは通常の関数よりも遅いわけではありませんが、もちろん、関数呼び出しとクロージャルックアップによってこれが遅くなるのは当然です。izip_longestたとえば、これに対する相対的なパフォーマンスへの影響がアプローチと比較してどうなるかはわかりません。たとえば、複雑なトレードオフの可能性があります。しかし... パラメータpadvalを提供するすべての回答で問題が共有されていませんpadvalか?
センダーレ

1
@TomaszGandor、かなり公正!しかし、これを修正するバージョンを作成することはそれほど難しくありませんでした。(また、()番兵として使用する最初のバージョン正しく機能することに注意してください。これは、が空のときにがtuple(islice(it, size))生成さ()れるためitです。)
sendle

93

これは、任意のイテラブルで機能するジェネレータです。

def split_seq(iterable, size):
    it = iter(iterable)
    item = list(itertools.islice(it, size))
    while item:
        yield item
        item = list(itertools.islice(it, size))

例:

>>> import pprint
>>> pprint.pprint(list(split_seq(xrange(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
 [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
 [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
 [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
 [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
 [70, 71, 72, 73, 74]]

52
def chunk(input, size):
    return map(None, *([iter(input)] * size))

map(None, iter)と等しいizip_longest(iter)
Thomas Ahle、2012年

1
@TomaszWysocki *イテレータのタプルの前で説明できますか?おそらくあなたの回答テキストにありますが、*以前はPythonでそのように使用されたのを見たことがあると思います。ありがとう!
theJollySin 2013年

1
@theJollySinこのコンテキストでは、スプラット演算子と呼ばれます。その使用法はここで説明されています-stackoverflow.com/questions/5917522/unzipping-and-the-operator
rlms 2013年

2
閉じるが、最後のチャンクにはそれを埋めるNone要素がある。これは欠陥である場合とそうでない場合があります。しかし、本当にクールなパターン。

49

シンプルでありながらエレガント

l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]

またはご希望の場合:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)

18
アラビア数字のように変数をダビングしないでください。一部のフォントでは、1l区別できません。ようです0O。そして、時にはI1
Alfe、2013

14
@Alfe欠陥のあるフォント。人々はそのようなフォントを使うべきではありません。プログラミングではなく、何のためでもありません。
Jerry B

17
ラムダは、名前のない関数として使用するためのものです。そのように使用しても意味がありません。さらに、エラーが発生した場合にトレースバックが「チャンク単位」ではなく「ラムダ単位」で報告するため、デバッグがさらに困難になります。これらがたくさんある場合は、問題を見つけて幸運を祈ります:)
Chris Koston

1
xrange in内では0ではなく1である必要がありますprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta

注: Python 3ユーザーの場合はを使用しますrange
クリスチャンディーン

40

ここでの他の回答の批評:

これらの回答はどれも均等なサイズのチャンクではありません。最後にラントチャンクが残るため、完全にバランスが取れていません。これらの関数を使用して作業を分散している場合、1つが他の関数よりも早く終了する可能性が高いため、他の関数が頑張っている間は何もせずに待機します。

たとえば、現在の上位の回答は次のように終わります。

[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]

最後にそのラントが嫌いです!

その他、などlist(grouper(3, xrange(7)))chunk(xrange(7), 3)両方が返されます:[(0, 1, 2), (3, 4, 5), (6, None, None)]Noneさんはちょうどパディング、そして私の意見ではなく、洗練されています。イテラブルを均等に分割していません。

これらをうまく分割できないのはなぜですか?

私のソリューション

ここではバランスの取れた解決策だ、私は生産に使用していた機能(置き換えるためのPython 3での注意から適応xrangeしてrange):

def baskets_from(items, maxbaskets=25):
    baskets = [[] for _ in xrange(maxbaskets)] # in Python 3 use range
    for i, item in enumerate(items):
        baskets[i % maxbaskets].append(item)
    return filter(None, baskets) 

そして、あなたがそれをリストに入れても同じことをするジェネレータを作成しました:

def iter_baskets_from(items, maxbaskets=3):
    '''generates evenly balanced baskets from indexable iterable'''
    item_count = len(items)
    baskets = min(item_count, maxbaskets)
    for x_i in xrange(baskets):
        yield [items[y_i] for y_i in xrange(x_i, item_count, baskets)]

そして最後に、上記の関数はすべて、要素が(指定されたとおりに)連続した順序で返されることがわかります。

def iter_baskets_contiguous(items, maxbaskets=3, item_count=None):
    '''
    generates balanced baskets from iterable, contiguous contents
    provide item_count if providing a iterator that doesn't support len()
    '''
    item_count = item_count or len(items)
    baskets = min(item_count, maxbaskets)
    items = iter(items)
    floor = item_count // baskets 
    ceiling = floor + 1
    stepdown = item_count % baskets
    for x_i in xrange(baskets):
        length = ceiling if x_i < stepdown else floor
        yield [items.next() for _ in xrange(length)]

出力

それらをテストするには:

print(baskets_from(xrange(6), 8))
print(list(iter_baskets_from(xrange(6), 8)))
print(list(iter_baskets_contiguous(xrange(6), 8)))
print(baskets_from(xrange(22), 8))
print(list(iter_baskets_from(xrange(22), 8)))
print(list(iter_baskets_contiguous(xrange(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(xrange(26), 5))
print(list(iter_baskets_from(xrange(26), 5)))
print(list(iter_baskets_contiguous(xrange(26), 5)))

どれが印刷されるか:

[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19], [20, 21]]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'B', 'C'], ['D', 'E'], ['F', 'G']]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

隣接するジェネレーターは他の2つと同じ長さのパターンでチャンクを提供しますが、項目はすべて順番どおりであり、個別の要素のリストを分割するのと同じように均等に分割されます。


上記のどれもが均等なサイズのチャンクを提供しないと言います。しかし、これもそうで、これもそうです。
センダーレ2014

1
@senderle、最初の1つ、、list(grouper(3, xrange(7)))および2番目の1つはchunk(xrange(7), 3)両方ともを返します[(0, 1, 2), (3, 4, 5), (6, None, None)]Noneさんはちょうどパディング、そして私の意見ではなく、洗練されています。イテラブルを均等に分割していません。投票ありがとうございます!
アーロンホール

4
同じサイズのチャンク(可能でない場合は最後のものを除く)か、バランスの取れた(可能な限り良好な)結果がより頻繁に必要になるかどうかという質問を(明示的に行うことなく、ここでここで行います)提起します。あなたはバランスの取れたソリューションが好むと想定しています。これは、プログラムするものが現実の世界に近い場合に当てはまる可能性があります(たとえば、シミュレートされたカードゲームのカード処理アルゴリズム)。他の場合(行を単語で埋めるなど)は、行をできるだけいっぱいにしたい場合があります。だから私は本当にどちらか一方を優先することはできません。これらは、さまざまなユースケースに対応しています。
Alfe、2014

@ ChristopherBarrington-Leigh良い点、DataFrameの場合、おそらくDataFrameオブジェクトはスライス時にコピーしないと思うので、スライスを使用する必要があります。例:import pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall

1
@AaronHallおっと。自分の批評を2番目に推測したのでコメントを削除しましたが、あなたはすぐに引き分けでした。ありがとう!実際、データフレームでは機能しないという私の主張は真実です。itemsがデータフレームの場合は、最終行としてyield items [range(x_i、item_count、baskets)]を使用してください。私は別の(まだ別の)回答を提供しました。その中で、希望する(最小)グループサイズを指定します。
CPBL

38

私はこの質問の複製で最も素晴らしいPython風の答えを見ました:

from itertools import zip_longest

a = range(1, 16)
i = iter(a)
r = list(zip_longest(i, i, i))
>>> print(r)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

任意のnに対してnタプルを作成できます。の場合a = range(1, 15)、結果は次のようになります。

[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]

リストが均等に分割されている場合は、交換することができるzip_longestzipそうでないトリプレットは、(13, 14, None)失われてしまいます。上記ではPython 3が使用されています。Python 2の場合は、を使用しますizip_longest


これは、リストとチャンクが短い場合に便利ですが、リストを1000のチャンクに分割するにはどうすればよいでしょうか。あなたはzip(i、i、i、i、i、i、i、i、i、i ..... i = 1000)をコード化しません
Tom Smith

9
zip(i, i, i, ... i)zip(*[i]*chunk_size)もちろん、zip()への「chunk_size」引数を使用すると、次のように記述できます。これが良いアイデアかどうかは、もちろん議論の余地があります。
Wilson F

1
これの欠点は、zipが最も短い反復可能なところで停止するため、均等に分割していない場合は要素がドロップされることです。&izip_longestはデフォルトの要素を追加します。
アーロンホール

zip_longest:で行われるように、使用されるべきであるstackoverflow.com/a/434411/1959808
イオアニスFilippidis

答えはrange(1, 15)14個の要素があるため、すでに、要素が欠落しているrange(1, 15)、いない15
イオアニスFilippidisは

35

リストのサイズがわかっている場合:

def SplitList(mylist, chunk_size):
    return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), chunk_size)]

そうでない場合(イテレータ):

def IterChunks(sequence, chunk_size):
    res = []
    for item in sequence:
        res.append(item)
        if len(res) >= chunk_size:
            yield res
            res = []
    if res:
        yield res  # yield the last, incomplete, portion

後者の場合、シーケンスに常に指定されたサイズのチャンクの整数が含まれていることを確認できる場合(つまり、不完全な最後のチャンクがない場合)、より美しい方法で言い換えることができます。


これが今のところずっと埋まっているのが悲しいです。IterChunksはすべてに対して機能し、一般的なソリューションであり、私が知っている警告はありません。
Jason Dunkelberger、2015

18

バーチャルツールのライブラリがありpartition、この機能を:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]

これはすべての提案の中で最も単純なようです。私はそのようなパーティション関数を取得するためにサードパーティのライブラリを使用しなければならないことが本当に本当であり得るのかと思っています。そのパーティション関数と同等のものが組み込み言語として存在することを期待していました。
kasperd

1
itertoolsでパーティションを作成できます。しかし、私はtoolzライブラリが好きです。機能的なスタイルでコレクションに取り組むための、クロジュールにインスパイアされたライブラリ。不変性は得られませんが、単純なコレクションに取り組むための小さな語彙が得られます。さらに、cytoolzはcythonで記述されており、パフォーマンスが大幅に向上します。github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
ザック

:あなたが最後のスラッシュOMMIT場合ザックさんのコメントからのリンクは機能しmatthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
MIT

17

たとえば、チャンクサイズが3の場合、次のようにできます。

zip(*[iterable[i::3] for i in range(3)]) 

ソース:http : //code.activestate.com/recipes/303060-group-a-list-into-sequential-n-tuples/

私がこれを使用するのは、チャンクサイズが「3」などの入力可能な固定数であり、変更されない場合です。


11
これは、len(iterable)%3!= 0の場合は機能しません。最後の(短い)数値のグループは返されません。
シェルバン

16

私はtzotとJFSebastianによって提案されたPythonドキュメントのバージョンが好きですが、次の2つの欠点があります。

  • あまり明確ではない
  • 通常、最後のチャンクの塗りつぶし値は必要ありません

私はこれを私のコードでたくさん使用しています:

from itertools import islice

def chunks(n, iterable):
    iterable = iter(iterable)
    while True:
        yield tuple(islice(iterable, n)) or iterable.next()

更新:遅延チャンクバージョン:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))

while Trueループのブレーク条件は何ですか?
wjandrea

@wjandrea:が空で実行されStopIterationたときに発生します。ただし、発生させるのではなくを使用してジェネレーターを終了する必要がある場合、最新のPythonでは正しく機能しません。ループ全体(および変化の周りにクロスバージョンCOMPATため)少なくとも最小限のオーバーヘッドでこれを固定します。tupleiterable.next()returnStopIterationtry/except StopIteration: returniterable.next()next(iterable)
ShadowRanger

15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

AAが配列の場合、SSはチャンクサイズです。例えば:

>>> AA=range(10,21);SS=3
>>> [AA[i:i+SS] for i in range(len(AA))[::SS]]
[[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]]
# or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3

2
それは最高でシンプルです。
F.タミー

2
短くてシンプル。複雑さよりも単純さ。
dkrynicki

15

さまざまなアプローチのパフォーマンスに興味があったので、ここに示します。

Python 3.5.1でテスト済み

import time
batch_size = 7
arr_len = 298937

#---------slice-------------

print("\r\nslice")
start = time.time()
arr = [i for i in range(0, arr_len)]
while True:
    if not arr:
        break

    tmp = arr[0:batch_size]
    arr = arr[batch_size:-1]
print(time.time() - start)

#-----------index-----------

print("\r\nindex")
arr = [i for i in range(0, arr_len)]
start = time.time()
for i in range(0, round(len(arr) / batch_size + 1)):
    tmp = arr[batch_size * i : batch_size * (i + 1)]
print(time.time() - start)

#----------batches 1------------

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

print("\r\nbatches 1")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#----------batches 2------------

from itertools import islice, chain

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


print("\r\nbatches 2")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#---------chunks-------------
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]
print("\r\nchunks")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in chunks(arr, batch_size):
    tmp = x
print(time.time() - start)

#-----------grouper-----------

from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(iterable, n, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

arr = [i for i in range(0, arr_len)]
print("\r\ngrouper")
start = time.time()
for x in grouper(arr, batch_size):
    tmp = x
print(time.time() - start)

結果:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844

3
モジュールtimeを使用している場合、ライブラリを使用したベンチマークは素晴らしいアイデアではありませんtimeit
Azat Ibrakov

13

コード:

def split_list(the_list, chunk_size):
    result_list = []
    while the_list:
        result_list.append(the_list[:chunk_size])
        the_list = the_list[chunk_size:]
    return result_list

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

print split_list(a_list, 3)

結果:

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

12

ライブラリのget_chunks機能を次のように使用することもできますutilspie

>>> from utilspie import iterutils
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list(iterutils.get_chunks(a, 5))
[[1, 2, 3, 4, 5], [6, 7, 8, 9]]

あなたはutilspiepip経由でインストールできます:

sudo pip install utilspie

免責事項:私はutilspieライブラリの作成者です


11

この時点で、念のために再帰ジェネレータが必要だと思います...

Python 2の場合:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

Python 3の場合:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    yield from chunks(li[n:], n)

また、大規模なエイリアンの侵略の場合、装飾された再帰ジェネレーターが便利になるかもしれません:

def dec(gen):
    def new_gen(li, n):
        for e in gen(li, n):
            if e == []:
                return
            yield e
    return new_gen

@dec
def chunks(li, n):
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

9

代入式のPython 3.8で、それはとてもいいようになります。

import itertools

def batch(iterable, size):
    it = iter(iterable)
    while item := list(itertools.islice(it, size)):
        yield item

これは、単なるリストではなく、任意の反復可能オブジェクトに対して機能します。

>>> import pprint
>>> pprint.pprint(list(batch(range(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
 [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
 [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
 [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
 [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
 [70, 71, 72, 73, 74]]

1
これは、この質問に対する価値のある新しい答えです。私はこれがかなり好きです。私は代入式に懐疑的ですが、それらが機能するときは機能します。
juanpa.arrivillaga

7

えっ、一行バージョン

In [48]: chunk = lambda ulist, step:  map(lambda i: ulist[i:i+step],  xrange(0, len(ulist), step))

In [49]: chunk(range(1,100), 10)
Out[49]: 
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
 [21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
 [31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
 [41, 42, 43, 44, 45, 46, 47, 48, 49, 50],
 [51, 52, 53, 54, 55, 56, 57, 58, 59, 60],
 [61, 62, 63, 64, 65, 66, 67, 68, 69, 70],
 [71, 72, 73, 74, 75, 76, 77, 78, 79, 80],
 [81, 82, 83, 84, 85, 86, 87, 88, 89, 90],
 [91, 92, 93, 94, 95, 96, 97, 98, 99]]

36
「chunk = lambda」の代わりに「def chunk」を使用してください。同じように動作します。1行。同じ機能。n00bzを読んで理解するのがはるかに簡単です。
S.Lott、2008年

4
@ S.Lott:n00bzがスキーム:Pの場合は、これは実際の問題ではありません。グーグルへのキーワードさえあります!n00bzのために避けるべき他の機能は何ですか?収量は、その時もn00bフレンドリーになるのに十分な/ cのようなものではないと思います。
Janus Troelsen、2012年

16
def chunk代わりに生成される関数オブジェクトchunk=lambdaには、「<lambda>」ではなく.__ name__属性「chunk」があります。特定の名前は、トレースバックでより役立ちます。
Terry Jan Reedy

1
@Alfe:主な意味の違いと呼べるかどうかはわかり<lamba>ませんが、トレースバックに有用な名前があるかどうかは、少なくとも顕著な違いです。
martineau 2015年

1
それらの多くをパフォーマンスについてテストした後、これは素晴らしいです!
Sunny Patel、2018年

7
def split_seq(seq, num_pieces):
    start = 0
    for i in xrange(num_pieces):
        stop = start + len(seq[i::num_pieces])
        yield seq[start:stop]
        start = stop

使用法:

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

for seq in split_seq(seq, 3):
    print seq

7

別のより明示的なバージョン。

def chunkList(initialList, chunkSize):
    """
    This function chunks a list into sub lists 
    that have a length equals to chunkSize.

    Example:
    lst = [3, 4, 9, 7, 1, 1, 2, 3]
    print(chunkList(lst, 3)) 
    returns
    [[3, 4, 9], [7, 1, 1], [2, 3]]
    """
    finalList = []
    for i in range(0, len(initialList), chunkSize):
        finalList.append(initialList[i:i+chunkSize])
    return finalList

(2016年9月12日)この答えは、最も言語に依存しない、最も読みやすいものです。
Dアダムス

7

大きなリストに適したlen()を呼び出さずに:

def splitter(l, n):
    i = 0
    chunk = l[:n]
    while chunk:
        yield chunk
        i += n
        chunk = l[i:i+n]

そしてこれはイテラブル用です:

def isplitter(l, n):
    l = iter(l)
    chunk = list(islice(l, n))
    while chunk:
        yield chunk
        chunk = list(islice(l, n))

上記の機能的なフレーバー:

def isplitter2(l, n):
    return takewhile(bool,
                     (tuple(islice(start, n))
                            for start in repeat(iter(l))))

または:

def chunks_gen_sentinel(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return iter(imap(tuple, continuous_slices).next,())

または:

def chunks_gen_filter(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return takewhile(bool,imap(tuple, continuous_slices))

16
len()大きなリストを避ける理由はありません。それは一定時間の操作です。
Thomas Wouters、

7

追加のアプローチのリストは次のとおりです。

与えられた

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

コード

標準ライブラリ

list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

d = {}
for i, x in enumerate(iterable):
    d.setdefault(i//n, []).append(x)

list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
    dd[i//n].append(x)

list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

more_itertools+

list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]

list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

参考文献

+ itertoolsレシピなどを実装するサードパーティのライブラリ。> pip install more_itertools


6

このリファレンスを見る

>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>> 

Python3


3
いいですが、サイズがチャンクの整数と一致しない場合、要素を最後にドロップします。たとえば、入力からをzip(*[iter(range(7))]*3)返し[(0, 1, 2), (3, 4, 5)]、忘れただけ6です。
Alfe、2013

6

ここでみんながイテレータについて話しているから。boltonsそのための完璧な方法がありiterutils.chunked_iterます。

from boltons import iterutils

list(iterutils.chunked_iter(list(range(50)), 11))

出力:

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21],
 [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32],
 [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
 [44, 45, 46, 47, 48, 49]]

しかし、メモリに容赦したくない場合は、old-wayを使用listして、最初にでフルを格納できますiterutils.chunked


そして、これは実際にsubiteratorを見る順序に関係なく動作します!!
Peter Gerdes、

6

もう1つのソリューション

def make_chunks(data, chunk_size): 
    while data:
        chunk, data = data[:chunk_size], data[chunk_size:]
        yield chunk

>>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2):
...     print chunk
... 
[1, 2]
[3, 4]
[5, 6]
[7]
>>> 

5
def chunks(iterable,n):
    """assumes n is an integer>0
    """
    iterable=iter(iterable)
    while True:
        result=[]
        for i in range(n):
            try:
                a=next(iterable)
            except StopIteration:
                break
            else:
                result.append(a)
        if result:
            yield result
        else:
            break

g1=(i*i for i in range(10))
g2=chunks(g1,3)
print g2
'<generator object chunks at 0x0337B9B8>'
print list(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'

1
これは短く、またはitertoolsベースの応答の多くほどきれいに見えないかもしれませんが、最初にアクセスする前に2番目のサブリストを印刷したい場合、つまりi0 = next(g2);を設定できる場合、これは実際に機能します。i1 = next(g2); そして、i0を使用する前にi1を使用し、それは壊れません!!
Peter Gerdes、

5

使用を検討してmatplotlib.cbookの作品を

例えば:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s

誤って2つのアカウントを作成したようです。あなたはチーム連絡してそれらをマージさせることができます。これにより、あなたの貢献に対する直接編集特権を取り戻すことができます。
ジョージー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.