如何切一个双端队列?

Dre*_*kes 24 python deque slice

我已经改变了一些使用列表来使用双端队列的代码.当我收到错误时,我无法再切入它:

TypeError:序列索引必须是整数,而不是'slice'

这是一个显示问题的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'
Run Code Online (Sandbox Code Playgroud)

那么,有没有一种解决方法来支持在Python中切割成deques?

kin*_*all 27

试试itertools.islice().

 deque_slice = collections.deque(itertools.islice(my_deque, 10, 20))
Run Code Online (Sandbox Code Playgroud)

索引到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)
Run Code Online (Sandbox Code Playgroud)

请注意,您不能使用负索引或步长值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))
Run Code Online (Sandbox Code Playgroud)

当然,与常规函数相比,还有一个额外的函数调用deque,所以如果你真的关心性能,你真的想添加一个单独的slice()方法等.

  • 我一直惊讶于itertools模块是多么神奇.似乎每次我审查SO时,我都会越来越多地学习它. (4认同)
  • 我的主要模块是itertools,functools和集合.这么大的力量. (4认同)

geo*_*org 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
Run Code Online (Sandbox Code Playgroud)

根据下面的@RaymondHettinger评论,理解方法只有在切片很短时才会更好.在更长的切片上,islice令人信服地获胜.例如,以下是从偏移量6000中切割10,000个项目deque的时间:

offset  length      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)
Run Code Online (Sandbox Code Playgroud)

  • 这个答案实际上是*误导性的,并从时间上得出错误的结论.使用d [i]进行索引通常很快,因为查找在时间上跨越了deque 62元素,并且因为当"i> len(d)// 2"时它足够聪明地从右侧遍历.但是,当您需要更长的切片时,则一次一个索引是一个坏主意.例如,如果你在30000个元素的副本中尝试这个时间片``9000:10000``,你会得到一个非常不同的答案. (6认同)
  • 嗨@RaymondHettinger,感谢您的评论!我编辑了帖子以反映您的观点。 (2认同)