web-dev-qa-db-ja.com

dequeをスライスする方法は?

リストを使用するコードをdequeを使用するように変更しました。エラーが発生したため、スライスできなくなりました。

TypeError:シーケンスインデックスは「スライス」ではなく整数である必要があります

これが問題を示すREPLです。

>>> import collections
>>> d = collections.deque()
>>> for i in range(3):
...     d.append(i)
...
>>> d
deque([0, 1, 2])
>>> d[2:]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sequence index must be integer, not 'slice'

では、Pythonで両端キューへのスライスをサポートするための回避策はありますか?

25
Drew Noakes

itertools.islice()を試してください。

_ deque_slice = collections.deque(itertools.islice(my_deque, 10, 20))
_

dequeにインデックスを付けるには、毎回最初からリンクリストをたどる必要があるため、islice()アプローチでは、アイテムをスキップしてスライスの先頭に到達すると、可能な限り最高のパフォーマンスが得られます(より良い)各要素のインデックス操作としてコーディングするよりも)。

これを自動的に行うdequeサブクラスを簡単に作成できます。

_class sliceable_deque(collections.deque):
    def __getitem__(self, index):
        if isinstance(index, slice):
            return type(self)(itertools.islice(self, index.start,
                                               index.stop, index.step))
        return collections.deque.__getitem__(self, index)
_

isliceで負のインデックスまたはステップ値を使用することはできないことに注意してください。これを回避してコーディングすることは可能であり、サブクラスのアプローチを取る場合はそうする価値があるかもしれません。負の開始または停止の場合は、両端キューの長さを追加するだけです。負のステップの場合は、どこかにreversed()をスローする必要があります。それは演習として残しておきます。 :-)

dequeから個々のアイテムを取得するパフォーマンスは、スライスのifテストによってわずかに低下します。これが問題である場合は、EAFPパターンを使用してこれをいくらか改善できますが、例外を処理する必要があるため、スライスパスのパフォーマンスがわずかに低下します。

_class sliceable_deque(collections.deque):
    def __getitem__(self, index):
        try:
            return collections.deque.__getitem__(self, index)
        except TypeError:
            return type(self)(itertools.islice(self, index.start,
                                               index.stop, index.step))
_

もちろん、通常のdequeと比較して、まだ余分な関数呼び出しがあります。したがって、パフォーマンスを本当に気にする場合は、別のslice()メソッドなどを追加する必要があります。

28
kindall

パフォーマンスが懸念される場合は、 この回答 で提案されている直接アクセス/理解方法を検討してください。大規模なコレクションでは、isliceよりもはるかに高速です。

import timeit

setup = """
import collections, itertools
d = collections.deque(range(10000))
"""

print timeit.timeit('list(itertools.islice(d, 9000, 9010))', setup, number=10000)
## 0.631947040558
print timeit.timeit('[d[i] for i in range(9000, 9010)]', setup, number=10000)
## 0.0292208194733

以下の@RaymondHettingerのコメントによると、理解方法はスライスが短い場合にのみ優れています。より長いスライスでは、isliceが説得力のある勝利を収めます。たとえば、オフセット6000から10,000アイテムの両端キューをスライスするタイミングは次のとおりです。

オフセット長islicecompr 
 6000 10 400.496 46.611 
 6000 50 424.600 183.988 
 6000 90 432.277 237.894 
 6000 130 441.289 352.383 
 6000 170 431.299 404.596 
 6000 210 456.405 546.503 
 6000 250 448.895 575.995 
 6000 290 485.802 778.294 
 6000 330 483.704 781.703 
 6000 370 .____。] 6000 410 500.011 875.807 
 6000 450 508.213 1045.299 
 6000 490 518.894 1010.203 
 6000 530 530.887 1192.784 
 6000 570 534.415 1151.013 [._ 610 530.887 1504.779 
 6000 650 539.279 1486.802 
 6000 690 536.084 1650.810 
 6000 730 549.698 1454.687 
 6000 770 564.909 1576.114 
 1588.297 
 6000 850 564.504 1711.607 
 6000 890 584.197 1760.793 
 6000 930 564.480 1963.091 
 6000 970 586.390 1955.199 [.____._ 6000 1010 ._ 6000 1010 ]

理解度は最初の数スライスを非常に高速に実行しますが、長さが長くなるにつれてパフォーマンスは劇的に低下します。 isliceは小さいスライスでは遅くなりますが、平均速度ははるかに速くなります。

これが私がテストした方法です:

import timeit

size = 10000
repeats = 100

setup = """
import collections, itertools
d = collections.deque(range(%d))
""" % size

print '%5s\t%5s\t%10s\t%10s' % ('offset', 'length', 'islice', 'compr')

for offset in range(0, size - 2000, 2000):
    for length in range(10, 2000, 40):
        t1 = timeit.timeit('list(itertools.islice(d, %d, %d))' % (offset, offset + length), setup, number=repeats)
        t2 = timeit.timeit('[d[i] for i in range(%d, %d)]' % (offset, offset + length), setup, number=repeats)
        print '%5d\t%5d\t%10.3f\t%10.3f' % (offset, length, t1 * 100000, t2  * 100000)
9
georg