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


488

入力として整数のリストを受け取るPythonスクリプトがあり、一度に4つの整数を処理する必要があります。残念ながら、私は入力を制御できません。または、4要素のタプルのリストとして渡されます。現在、私はそれをこのように繰り返しています:

for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

それは「C-think」によく似ていますが、この状況に対処するためのよりパイソン的な方法があるのではないかと思います。リストは反復後に破棄されるため、保持する必要はありません。おそらく、このようなものが良いでしょうか?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

ただし、それでも「気分」はよくありません。:-/

関連質問:Pythonではどのようにリストを均等なサイズのチャンクに分割しますか?


3
リストのサイズが4の倍数でない場合、コードは機能しません。
Pedro Henriques、

5
私はリストをextend()して、長さが4の倍数になるようにしています。
ベンブランク

4
@ΤΖΩΤΖΙΟΥ—質問はよく似ていますが、完全に重複しているわけではありません。これは、「サイズNの任意の数のチャンクに分割」対「任意のサイズのNチャンクに分割」です。:-)
ベンブランク


回答:


340

Pythonのitertoolsドキュメントのレシピセクションから変更:

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)


では、例えば、簡潔を維持するために擬似コード。

grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'

注: Python 2 izip_longestではの代わりにを使用してくださいzip_longest


67
ついにpythonセッションでこれをいじる機会を得ました。私と同じように混乱している人のために、これは同じイテレータをizip_longestに複数回フィードしているため、個別のシーケンスからストライプされた値ではなく、同じシーケンスの連続した値を消費します。大好きです!
Ben Blank

6
fillvalueをフィルターで除外する最良の方法は何ですか?([grouper(iterable)のアイテムの[itemがfillvalueでない場合のアイテムのアイテムのアイテム])?
gotgenes 2009

14
256kサイズのチャンクに対するこのハタレシピのパフォーマンスは、izip_longest256kの引数が渡されるため、非常に悪いと思います。
anatoly techtonik 2013

13
いくつかの場所で、コメンターは、「これが最終的にどのように機能するかを最終的に理解したとき...」と言います。多分少し説明が必要です。特にイテレータの側面のリスト。
LondonRob

6
これを使用する方法はありますがNone、最後のチャンクをいっぱいにすることはありませんか?
CMCDragonkai 2018

420
def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)

シンプル。簡単です。速い。任意のシーケンスで機能します:

text = "I am a very, very helpful text"

for group in chunker(text, 7):
   print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'

print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text

animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']

for group in chunker(animals, 3):
    print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']

16
@Carlos Crasbornのバージョンは、(上記のコードのようなシーケンスだけでなく)イテラブルで機能します。それは簡潔で、おそらく同じくらい速く、さらにはもっと速いです。ただし、itertoolsモジュールに不慣れな人にとっては、少しあいまい(不明確)かもしれません。
jfs

1
同意した。これは最も一般的でpythonicな方法です。明確で簡潔。(およびApp Engineで動作します)
マットウィリアムソン

3
はをchunker返すことに注意してくださいgeneratorreturn [...]リストを取得するには、return to:を置き換えます。
Dror 2015

11
関数を作成してジェネレータを返す代わりに、yield:を使用してジェネレータを直接作成することもできますfor pos in xrange(0, len(seq), size): yield seq[pos:pos + size]。内部的にこれが関連する側面で異なる方法で処理されるかどうかはわかりませんが、少しだけ明確になるかもしれません。
Alfe

3
これは、インデックスによる項目アクセスをサポートするシーケンスでのみ機能し、__getitem__メソッドをサポートしていない可能性があるため、ジェネリックイテレーターでは機能しないことに注意してください。
apollov

135

私はのファンです

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size

len(ints)がchunkSizeの倍数でない場合、どのように動作しますか?
PlsWork

3
@AnnaVopureta chunkには、要素の最後のバッチに対して1、2、または3つの要素があります。スライスインデックスが範囲外になる可能性がある理由については、この質問を参照してください。
ボリス

22
import itertools
def chunks(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
    foo += x1 + x2 + x3 + x4

for chunk in chunks(ints,4):
    foo += sum(chunk)

別の方法:

import itertools
def chunks2(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
    foo += x1 + x2 + x3 + x4

2
ジェネレーターを使用する場合の+1、推奨されるすべてのソリューションの中で最も「pythonic」のような継ぎ目
セルゲイ

7
それは、とても簡単なものに対してはかなり長くて不器用ですが、それは非常にpythonicではありません。私はS.ロットのバージョンを好む
zenazn 2009年

4
@zenazn:これはジェネレーターインスタンスでは機能しますが、スライスは機能しません
Janus Troelsen

ジェネレータやその他のスライス不可能なイテレータを適切に処理することに加えて、最初のソリューションでは、最終チャンクがより小さい場合に「フィラー」値を必要としないためsize、これが望ましい場合があります。
dano 2014

1
ジェネレーターの場合も+1。他のソリューションはlen呼び出しを必要とするため、他のジェネレーターでは機能しません。
Cuadue


11

この問題の理想的な解決策は、(シーケンスだけでなく)イテレーターで機能します。また、高速でなければなりません。

これはitertoolsのドキュメントによって提供されるソリューションです:

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

ipythonの使用 %timeitMacブックエアですると、ループごとに47.5 usが得られます。

ただし、結果は同じサイズのグループになるように埋め込まれるため、これは実際には機能しません。パディングなしのソリューションは少し複雑です。最も単純な解決策は次のとおりです。

def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

シンプルですがかなり遅いです:ループあたり693 us

用途を考え出せる最高のソリューション islice内側のループのを:

def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

同じデータセットで、ループごとに305 usが得られます。

それよりも速く純粋なソリューションを取得できない場合、私は次のソリューションに重要な警告を提供しますfilldata

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

私はこの答えが本当に好きではありませんが、それはかなり高速です。ループあたり124 us


Cレイヤーに移動することで、レシピ#3のランタイムを約10〜15%削減できます(itertoolsインポートを省略map。Py3 mapまたはである必要がありますimapdef grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n)))))。センチネルを使用すると、最終的な関数の脆弱性を減らすことができfillvalueます。引数を取り除きます。最初の行を追加してからfillvalue = object()ifチェックを変更し、チェックする行を変更if i[-1] is fillvalue:yield tuple(v for v in i if v is not fillvalue)ます。の値がiterableフィラーの値と間違われることがないことを保証します。
ShadowRanger 2016

ところで、#4の大きな親指。私は#3の最適化をこれまでに投稿されたものよりも(パフォーマンスに関して)より良い答えとして投稿しようとしましたが、信頼性と復元力を高めるための調整により、最適化された#3の2倍の速さで#4が実行されます。私は、Pythonレベルのループ(および理論的なアルゴリズムの違いAFAICT)を備えたソリューションが勝つことを期待していませんでした。私はisliceオブジェクトの構築/反復の費用のために#3が負けると思います(nたとえば、グループの数が少ないなど、比較的大きい場合は#3が勝ちますが、それはまれなケースに対して最適化されます)。極端。
ShadowRanger 2016

#4の場合、条件の最初の分岐は、最後の反復(最後のタプル)でのみ行われます。最後のタプルを何度も再構成する代わりに、元の反復可能オブジェクトの長さのモジュロを上部にキャッシュし、それを使用してizip_longest、最終的なタプルから不要なパディングを切り取りますyield i[:modulo]。また、args変数の場合は、リストの代わりにタプルします:args = (iter(iterable),) * n。クロックサイクルをさらにいくつか削ります。最後に、fillvalueを無視してと仮定するNoneと、条件はif None in iさらに多くのクロックサイクルになる可能性があります。
クンバ2017

1
@Kumba:最初の提案では、入力の長さが既知であると想定しています。既知の長さのコレクションではなく、イテレーター/ジェネレーターの場合、キャッシュするものはありません。とにかく、そのような最適化を使用する本当の理由はありません。まれなケース(最後のケース)を最適化していますがyield、一般的なケースは影響を受けていません。
ShadowRanger 2017年

10

セットやジェネレーターでも機能するソリューションが必要でした。短くてきれいなものは思いつきませんでしたが、少なくともかなり読みやすいです。

def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

リスト:

>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

セットする:

>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

発生器:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

8

他の提案と同様ですが、完全に同一ではありませんが、シンプルで読みやすいので、このようにするのが好きです。

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

この方法では、最後の部分チャンクを取得しません。(9, None, None, None)最後のチャンクとして取得したい場合は、izip_longestfrom fromitertoolsます。


で改善することができるzip(*([it]*4))
ジーン・フランソワ・ファーブル

@Jean-FrançoisFabre:読みやすさの観点からは、改善とは言えません。そして、それはわずかに遅いです。あなたがゴルフをしているならそれは改善です、私はそうではありません。
クリス、

私はゴルフをしていませんが、10の引数がある場合はどうでしょうか。私は:)今それを見つけるように見えることはできませんもちろん、いくつかの公式page.butでその構造を読んで
ジャン=フランソワ・ファーブル

@Jean-FrançoisFabre:引数が10個の場合、または引数の数が可変の場合、それはオプションですが、次のように記述します:zip(*(it、)* 10)
kriss

正しい!それは私が読んだものです。私は:)作ったことないリストのもの
ジャン・フランソワ・ファーブル

8

外部パッケージの使用を気にしない場合はiteration_utilities.grouper1から使用できます。(シーケンスだけでなく)すべての反復可能オブジェクトをサポートします。iteration_utilties

from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
    print(group)

印刷する:

(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)

長さがグループサイズの倍数ではない場合、最後のグループを埋める(不完全な最後のグループ)または切り捨てる(不完全な最後のグループを破棄する)もサポートします。

from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)

for group in grouper(seq, 4, fillvalue=None):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)

for group in grouper(seq, 4, truncate=True):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)

ベンチマーク

また、前述のアプローチのいくつかの実行時間を比較することも決定しました。これは、さまざまなサイズのリストに基づいて「10」要素のグループにグループ化された対数-対数プロットです。定性的な結果の場合:低いほど速くなります:

ここに画像の説明を入力してください

少なくともこのベンチマークでは、iteration_utilities.grouperパフォーマンスが最高です。続いてクレイズのアプローチ

ベンチマークは1で作成されました。このベンチマークを実行するために使用したコードは次のとおりです。simple_benchmark

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

def Craz_inner(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

def nosklo_inner(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1免責事項:私は、ライブラリの作者だiteration_utilitiessimple_benchmark


7

まだ誰も言及していないので、ここにzip()解決策があります:

>>> def chunker(iterable, chunksize):
...     return zip(*[iter(iterable)]*chunksize)

シーケンスの長さが常にチャンクサイズで割り切れるか、そうでない場合は後続のチャンクを気にしない場合にのみ機能します。

例:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

または、itertools.izipを使用して、リストの代わりにイテレータを返します。

>>> from itertools import izip
>>> def chunker(iterable, chunksize):
...     return izip(*[iter(iterable)]*chunksize)

パディングは@ΤΖΩΤΖΙΟΥの答えを使用して修正できます:

>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
...     args = [it] * chunksize
...     return izip(*args)

5

zip()の代わりにmap()を使用すると、JFセバスチャンの回答のパディングの問題が修正されます。

>>> def chunker(iterable, chunksize):
...   return map(None,*[iter(iterable)]*chunksize)

例:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

2
これはitertools.izip_longest(Py2)/ itertools.zip_longest(Py3)でよりよく処理されます。この使用mapは二重に非推奨になり、Py3では使用できません(Noneマッパー関数として渡すことはできません。最長の反復可能オブジェクトが使い果たされたときに停止します。最長ではなく、パディングされません)。
ShadowRanger 2016年

4

別のアプローチは、次の2つの引数の形式を使用することですiter

from itertools import islice

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

これはパディングを使用するように簡単に調整できます(これはMarkus Jarderotに似ています)の回答にます):

from itertools import islice, chain, repeat

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

これらはオプションのパディングのために組み合わせることができます:

_no_pad = object()
def group(it, size, pad=_no_pad):
    if pad == _no_pad:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(pad))
        sentinel = (pad,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

1
パディングを省略するオプションがあるため、好ましいです!
n611x007 14

3

リストが大きい場合、これを行う最もパフォーマンスの高い方法は、ジェネレータを使用することです。

def get_chunk(iterable, chunk_size):
    result = []
    for item in iterable:
        result.append(item)
        if len(result) == chunk_size:
            yield tuple(result)
            result = []
    if len(result) > 0:
        yield tuple(result)

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

(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)

(MizardXのitertoolsの提案は、これと機能的に同等だと思います。)
Robert Rossney 09/12/01

1
(実際には、リフレクションではありません。itertools.isliceはイテレータを返しますが、既存のイテレータは使用しません。)
Robert Rossney 09/12 / 01、4:

それは4-7倍遅くに受け入れられハタ方法よりもいいとシンプルですが、いくつかの理由のためにも、タプルへの変換なしiterable = range(100000000)chunksize10000まで
Valentas

ただし、一般的にこの方法をお勧めします。最後のアイテムのチェックが遅い場合、受け入れられるものは非常に遅くなる可能性があるためです。docs.python.org
/

3

小さな関数や物事を使用することは本当に私には魅力的ではありません。私はスライスを使用することを好みます:

data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...

わかりませんが、既知のストリームがない無限ストリームには適していませんlenitertools.repeatまたはでテストを行うことができますitertools.cycle
n611x007 2014

1
また、次の要素と予備のメモリを気にするジェネレータ式を使用する代わりに、[...for...] リスト内包を使用して物理的にリストを作成するため、メモリを(...for...)
消費し

2

リストへのすべての変換を回避するにはimport itertools

>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
...     list(g)

生成する:

... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>> 

チェックgroupbyしたところ、リストに変換したり、使用したりできませんlenので、実際に使用されるまで各値の解決が遅れると思います。悲しいことに、(現時点では)利用可能な回答のどれもこのバリエーションを提供していないようです。

明らかに、各アイテムを順番に処理する必要がある場合は、gでforループをネストします。

for k,g in itertools.groupby(xrange(35), lambda x: x/10):
    for i in g:
       # do what you need to do with individual items
    # now do what you need to do with the whole group

これに対する私の特別な関心は、最大1000のバッチで変更をGmail APIに送信するためにジェネレーターを使用する必要性でした:

    messages = a_generator_which_would_not_be_smart_as_a_list
    for idx, batch in groupby(messages, lambda x: x/1000):
        batch_request = BatchHttpRequest()
        for message in batch:
            batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
        http = httplib2.Http()
        self.credentials.authorize(http)
        batch_request.execute(http=http)

チャンクしているリストが一連の昇順整数以外の場合はどうなりますか?
PaulMcG、2015年

参照@PaulMcGuire GROUPBYを。順序を記述する関数を指定すると、イテラブルの要素は何でもかまいません。
John Mee、

1
はい、私はgroupbyに精通しています。ただし、メッセージが「ABCDEFG」の文字のgroupby(messages, lambda x: x/3)場合、3文字のグループではなく、TypeError(文字列をintで除算しようとするため)が返されます。今あなたがしたなら、あなたはgroupby(enumerate(messages), lambda x: x[0]/3)何かを持っているかもしれません。しかし、あなたはあなたの投稿でそれを言っていませんでした。
PaulMcG 2015年

2

NumPyを使用すると簡単です。

ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
    print(int1, int2)

出力:

1 2
3 4
5 6
7 8

2
def chunker(iterable, n):
    """Yield iterable in chunk sizes.

    >>> chunks = chunker('ABCDEF', n=4)
    >>> chunks.next()
    ['A', 'B', 'C', 'D']
    >>> chunks.next()
    ['E', 'F']
    """
    it = iter(iterable)
    while True:
        chunk = []
        for i in range(n):
            try:
                chunk.append(next(it))
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()

2

私が何かを見逃していない限り、ジェネレータ式を使用した次の簡単な解決策は言及されていません。チャンクのサイズと数の両方がわかっている(多くの場合そうである)こと、およびパディングが不要であることを前提としています。

def chunks(it, n, m):
    """Make an iterator over m first chunks of size n.
    """
    it = iter(it)
    # Chunks are presented as tuples.
    return (tuple(next(it) for _ in range(n)) for _ in range(m))

1

2番目の方法では、次の4つのグループに進みます。

ints = ints[4:]

ただし、パフォーマンスの測定はまだ行っていないため、どちらがより効率的かはわかりません。

そうは言っても、私は通常最初の方法を選びます。それはきれいではありませんが、それは多くの場合、外界とのインターフェースの結果です。


1

さらに別の答えは、その利点は次のとおりです。

1)簡単に理解できる
2)シーケンスだけでなく反復可能で機能する(上記の回答の一部はファイルハンドルで詰まる)
3)チャンクを一度にメモリにロードしない
4)への参照のチャンク長リストを作成しないメモリ内の同じイテレータ
5)リストの最後にフィル値のパディングなし

そうは言っても、私は時間を計っていないので、より賢い方法のいくつかよりも遅くなるかもしれません。

def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

更新:
内部ループと外部ループが同じイテレーターから値を取得しているという事実によるいくつかの欠点:
1)継続が外部ループで期待どおりに機能しない-チャンクをスキップするのではなく、次のアイテムに進む。ただし、外側のループでテストするものがないため、これは問題のようには見えません。
2)内側のループでブレークが期待どおりに機能しない-コントロールは、反復子の次のアイテムで内側のループに再び巻き上げられます。チャンク全体をスキップするには、内部イテレータ(上記のii)をタプルなどでラップするかfor c in tuple(ii)、フラグを設定してイテレータを使い果たします。


1
def group_by(iterable, size):
    """Group an iterable into lists that don't exceed the size given.

    >>> group_by([1,2,3,4,5], 2)
    [[1, 2], [3, 4], [5]]

    """
    sublist = []

    for index, item in enumerate(iterable):
        if index > 0 and index % size == 0:
            yield sublist
            sublist = []

        sublist.append(item)

    if sublist:
        yield sublist

+1パディングが省略されます。あなたとbcoughlan のは非常に似ている
n611x007


1

J.F. Sebastian ここで与えられ解決策について:

def chunker(iterable, chunksize):
    return zip(*[iter(iterable)]*chunksize)

賢い方法ですが、デメリットが1つあります。常にタプルを返すことです。代わりに文字列を取得する方法は?
もちろん書くことができます''.join(chunker(...))が、一時的なタプルはとにかく構築されます。

次のzipようにownを記述することで、一時的なタプルを取り除くことができます。

class IteratorExhausted(Exception):
    pass

def translate_StopIteration(iterable, to=IteratorExhausted):
    for i in iterable:
        yield i
    raise to # StopIteration would get ignored because this is generator,
             # but custom exception can leave the generator.

def custom_zip(*iterables, reductor=tuple):
    iterators = tuple(map(translate_StopIteration, iterables))
    while True:
        try:
            yield reductor(next(i) for i in iterators)
        except IteratorExhausted: # when any of iterators get exhausted.
            break

その後

def chunker(data, size, reductor=tuple):
    return custom_zip(*[iter(data)]*size, reductor=reductor)

使用例:

>>> for i in chunker('12345', 2):
...     print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
...     print(repr(i))
...
'12'
'34'

2
あなたがあなたの答えを変えることを意図した批評ではなく、むしろコメント:コードは責任です。記述するコードが多いほど、バグを隠すために作成するスペースが多くなります。この観点からzip、既存のものを使用する代わりに書き換えを行うことは最良の考えではないようです。
Alfe

1

私はこのアプローチが好きです。それはシンプルで不思議ではなく、反復可能なすべての型をサポートし、インポートを必要としません。

def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
    chunk = tuple(next(it) for _ in range(chunk_size))
    if not chunk:
        break
    yield chunk

1

私はチャンクにパッドを入れたくないので、その要件は不可欠です。イテラブルで作業できる能力も必要だと思います。それを踏まえ、私は受け入れられた回答https://stackoverflow.com/a/434411/1074659を拡張することにしました。

パディングされた値を比較およびフィルタリングする必要があるためにパディングが不要な場合、このアプローチではパフォーマンスがわずかに低下します。ただし、チャンクサイズが大きい場合、このユーティリティは非常に高性能です。

#!/usr/bin/env python3
from itertools import zip_longest


_UNDEFINED = object()


def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
    """
    Collect data into chunks and optionally pad it.

    Performance worsens as `chunksize` approaches 1.

    Inspired by:
        https://docs.python.org/3/library/itertools.html#itertools-recipes

    """
    args = [iter(iterable)] * chunksize
    chunks = zip_longest(*args, fillvalue=fillvalue)
    yield from (
        filter(lambda val: val is not _UNDEFINED, chunk)
        if chunk[-1] is _UNDEFINED
        else chunk
        for chunk in chunks
    ) if fillvalue is _UNDEFINED else chunks

1

以下は、ジェネレーターをサポートするインポートなしのチャンカーです。

def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(next(it) for _ in range(size))
        if len(ret) == size:
            yield ret
        else:
            raise StopIteration()

使用例:

>>> def foo():
...     i = 0
...     while True:
...         i += 1
...         yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]

1

Python 3.8では、walrus演算子とを使用できますitertools.islice

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 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, 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]

0

これを行うためのきれいな方法はないようです。 以下は、いくつかのメソッドがあるページです。

def split_seq(seq, size):
    newseq = []
    splitsize = 1.0/size*len(seq)
    for i in range(size):
        newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
    return newseq

0

リストが同じサイズの場合は、を使用してそれらを4タプルのリストに結合できますzip()。例えば:

# Four lists of four elements each.

l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)

for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
    ...

zip()関数が生成するものは次のとおりです。

>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]

リストが大きく、それらを組み合わせて大きなリストにしたくない場合は、リストではitertools.izip()なくイテレータを生成するを使用します。

from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...

0

xサイズのチャンクでリストを反復処理するワンライナーのアドホックソリューション4-

for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
    ... do something with a, b, c and d ...
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.