始める前に、明確にするために、スライスアプローチを選択する正しい順序は通常次のとおりです。
- 通常のスライスを使用します(最も長い入力を除くすべてをコピーするコストは通常意味がなく、コードははるかに単純です)。入力がスライス可能なシーケンスタイプでない可能性がある場合は、それを1つに変換してから、スライスします
allbutone = list(someiterable)[1:]
。これは、他のどのアプローチよりも簡単で、ほとんどの場合、通常は高速です。
- 通常のスライスが実行可能でない場合(入力がシーケンスであることが保証されておらず、スライスする前にシーケンスに変換するとメモリの問題が発生する可能性があります。または、サイズが大きく、スライスがそのほとんどをカバーします。たとえば、最初の1000要素と最後の1000要素をスキップします。 10Mエレメント
list
の場合、メモリが問題になる可能性があります)は、十分に単純であり、パフォーマンスコストは通常重要ではないため、itertools.islice
通常は正しいソリューションです。
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
破棄する要素の数が多い場合は、ドキュメントからレシピを借りるのがconsume
itertools
おそらく最善です。
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
パフォーマンスの面では、これはほとんどの大規模な入力に対して意味のある量で勝ちます(例外:range
Python 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
はすべての競合他社を打ち負かします(islice
180 nsのオーバーヘッドを考えると明らかに-freeに勝るものはありませんislice
が、一般化してスキップすることで補われます複数の要素をスキップするためn
に繰り返し呼び出す必要はなく、単一のステップとして要素)。next
プレーンislice
ラッパーが正確な要素ごとのオーバーヘッドのため、「少数をスキップし、多くを維持する」ケースで勝つことはめったにありません(マイクロベンチマークでの熱心なスライスは、約10万要素まで明確に打ち負かされませんでした。メモリ効率は高くなりますが、CPU効率は低くなります)。 「多くスキップし、少数を維持する」場合は、(熱心なスライスに比べて)さらに悪化します。