31

リストを使用していた一部のコードを両端キューを使用するように変更しました。エラーが発生するため、スライスできなくなりました。

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 で両端キューへのスライスをサポートする回避策はありますか?

4

2 に答える 2

37

試してみてください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。これを回避するようにコーディングすることは可能であり、サブクラス アプローチを採用する場合はそうする価値があるかもしれません。負の開始または停止の場合は、両端キューの長さを追加するだけです。負のステップの場合は、そこに a を投げる必要があります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()メソッドなどを追加する必要があります。

于 2012-04-04T00:04:46.637 に答える
9

パフォーマンスが懸念される場合は、この回答で提案されている直接アクセス/理解方法を検討してください。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 アイテムの deque をスライスするタイミングは次のとおりです。

オフセット長 islice compr
 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 490.904 948.501
 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
 6000 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
 6000 810 545.001 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 590.706 2117.491

理解は最初のいくつかのスライスを非常に高速に実行しますが、長さが大きくなるにつれてパフォーマンスが劇的に低下します。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)
于 2012-04-04T00:27:07.920 に答える