19

一般に、Pythonでリストの一部を反復処理する場合、最も簡単な方法はリストをスライスすることです。

# Iterate over everything except the first item in a list
#
items = [1,2,3,4]
iterrange = (x for x in items[1:])

ただし、スライス演算子は新しいリストを作成します。これは、多くの場合、実行する必要はありません。理想的には、新しいリストオブジェクトではなく、ジェネレーターを作成するある種のスライス関数が必要です。これに似た何かrangeは、リストの特定の部分のみを返すためにを使用するジェネレータ式を作成することによって達成できます。

# Create a generator expression that returns everything except 
# the first item in the list
#
iterrange = (x for x, idx in zip(items, range(0, len(items))) if idx != 0)

しかし、これはちょっと面倒です。これを行うためのより良い、よりエレガントな方法があるかどうか疑問に思います。では、新しいリストオブジェクトの代わりにジェネレータ式が作成されるように、リストをスライスする最も簡単な方法は何でしょうか。

4

3 に答える 3

21

itertools.isliceを使用してください:

import itertools

l = range(20)

for i in itertools.islice(l,10,15):
    print i

10
11
12
13
14

ドキュメントから:

イテレータから選択した要素を返すイテレータを作成します

于 2012-07-08T13:31:13.950 に答える
2

始める前に、明確にするために、スライスアプローチを選択する正しい順序は通常次のとおりです。

  1. 通常のスライスを使用します(最も長い入力を除くすべてをコピーするコストは通常​​意味がなく、コードははるかに単純です)。入力がスライス可能なシーケンスタイプでない可能性がある場合は、それを1つに変換してから、スライスしますallbutone = list(someiterable)[1:]。これは、他のどのアプローチよりも簡単で、ほとんどの場合、通常は高速です。
  2. 通常のスライスが実行可能でない場合(入力がシーケンスであることが保証されておらず、スライスする前にシーケンスに変換するとメモリの問題が発生する可能性があります。または、サイズが大きく、スライスがそのほとんどをカバーします。たとえば、最初の1000要素と最後の1000要素をスキップします。 10Mエレメントlistの場合、メモリが問題になる可能性があります)は、十分に単純であり、パフォーマンスコストは通常​​重要ではないため、itertools.islice通常は正しいソリューションです。
  3. isliceのパフォーマンスが許容できないほど遅く(確かに非常に少量ですが、すべてのアイテムの作成にオーバーヘッドが追加されます)スキップされるデータの量が少なく、含まれるデータが膨大である場合に限ります(たとえば、単一の要素をスキップして残りを保持するというOPのシナリオ)、読み続けます

ケース#3に気付いた場合は、islice最初の要素を(比較的)すばやくバイパスする機能だけでは、残りの要素を生成するための増分コストを補うのに十分ではないというシナリオになります。その場合、問題を元に戻して、にすべての要素を選択することから、前にすべての要素を破棄することで、パフォーマンスを向上させることができます。 n n

このアプローチでは、入力を手動でイテレーターに変換し、値を明示的に引き出して破棄nし、イテレーターに残っているものを繰り返します(ただし、要素ごとのオーバーヘッドはありませんislice)。たとえば、の入力のmyinput = list(range(1, 10000))場合、要素1から最後までを選択するためのオプションは次のとおりです。

# Approach 1, OP's approach, simple slice:
for x in myinput[1:]:

# Approach 2, Sebastian's approach, using itertools.islice:
for x in islice(myinput, 1, None):

# Approach 3 (my approach)
myiter = iter(myinput)  # Explicitly create iterator from input (looping does this already)
next(myiter, None) # Throw away one element, providing None default to avoid StopIteration error
for x in myiter:  # Iterate unwrapped iterator

破棄する要素の数が多い場合は、ドキュメントからレシピを借りるのがconsumeitertoolsおそらく最善です。

def consume(iterator, n=None):
    "Advance the iterator n-steps ahead. If n is None, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

nこれにより、要素をスキップするためのアプローチが一般化され、次のようになります。

# Approach 1, OP's approach, simple slice:
for x in myinput[n:]:

# Approach 2, Sebastian's approach, using itertools.islice:
for x in islice(myinput, n, None):

# Approach 3 (my approach)
myiter = iter(myinput)  # Explicitly create iterator from input (looping does this already)
consume(myiter, n)      # Throw away n elements
# Or inlined consume as next(islice(myiter, n, n), None)
for x in myiter:        # Iterate unwrapped iterator

パフォーマンスの面では、これはほとんどの大規模な入力に対して意味のある量で勝ちます(例外:rangePython 3のそれ自体は、プレーンスライス用にすでに最適化されています。プレーンスライスは、実際のrangeオブジェクトでは打ち負かされません)。ipython3マイクロベンチマーク(CPython 3.6、64ビットLinuxビルド)はこれを示しています(slurpセットアップでの定義は、反復可能を実行するための最小のオーバーヘッドアプローチを作成する方法であり、関心のないものの影響を最小限に抑えます) :

>>> from itertools import islice
>>> from collections import deque
>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... slurp(r[1:])
...
65.8 μs ± 109 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... slurp(islice(r, 1, None))
...
70.7 μs ± 104 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10000))
... ir = iter(r)
... next(islice(ir, 1, 1), None)  # Inlined consume for simplicity, but with islice wrapping to show generalized usage
... slurp(ir)
...
30.3 μs ± 64.1 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)

明らかに、私のソリューションの余分な複雑さは通常それだけの価値はありませんが、中程度のサイズの入力(この場合は10K要素)の場合、パフォーマンス上の利点は明らかです。isliceパフォーマンスが(少しだけ)最悪で、プレーンスライスの方がわずかに優れていました(実際のシーケンスがある場合は、プレーンスライスがほとんどの場合最良の解決策であるという私の主張を補強します)、「イテレータに変換し、初期値を破棄し、残りを使用します「アプローチは、比較的言えば、莫大な量で勝ちました(いずれかのアンダーソリューションの半分以下の時間)。

iter読み込み/呼び出し/の固定オーバーヘッドnext、特にislice、の固定オーバーヘッドが節約を上回るため、この利点は小さな入力には現れません。

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... slurp(r[1:])
...
207 ns ± 1.86 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... slurp(islice(r, 1, None))
...
307 ns ± 1.71 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... ir = iter(r)
... next(islice(ir, 1, 1), None)  # Inlined consume for simplicity, but with islice wrapping to show generalized usage
... slurp(ir)
...
518 ns ± 4.5 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

>>> %%timeit -r5 slurp = deque(maxlen=0).extend; r = list(range(10))
... ir = iter(r)
... next(ir, None)  # To show fixed overhead of islice, use next without it
... slurp(ir)
...
341 ns ± 0.947 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

しかし、ご覧のとおり、10個の要素であっても、isliceフリーアプローチはそれほど悪くはありません。100要素で、islice-freeアプローチはすべての競合他社よりも速く、200要素で、一般化されたnext+isliceはすべての競合他社を打ち負かします(islice180 nsのオーバーヘッドを考えると明らかに-freeに勝るものはありませんisliceが、一般化してスキップすることで補われます複数の要素をスキップするためnに繰り返し呼び出す必要はなく、単一のステップとして要素)。nextプレーンisliceラッパーが正確な要素ごとのオーバーヘッドのため、「少数をスキップし、多くを維持する」ケースで勝つことはめったにありません(マイクロベンチマークでの熱心なスライスは、約10万要素まで明確に打ち負かされませんでした。メモリ効率は高くなりますが、CPU効率は低くなります)。 「多くスキップし、少数を維持する」場合は、(熱心なスライスに比べて)さらに悪化します。

于 2018-11-07T18:13:59.957 に答える
0

itertools.isliceを試してください:

http://docs.python.org/library/itertools.html#itertools.islice

iterrange = itertools.islice(items, 1, None)
于 2012-07-08T13:33:35.017 に答える