itertools.islice与列表切片的比较

14

我一直在尝试应用一种算法,根据某些标准将Python列表缩小到更小的规模。由于原始列表的数量很大,约为10万个元素,所以我尝试使用itertools来避免多次内存分配,因此我想出了以下解决方案:

reducedVec = [ 'F' if sum( 1 for x in islice(vec, i, i+ratio) if x == 'F' ) 
                         > ratio / 3.0 else 'T'
                for i in xrange(0, len(vec), ratio) ]

当vec包含大约100k个元素时,执行此操作的时间会非常长,令人担忧,需要几分钟的时间。当我尝试使用以下方法时:

reducedVec = [ 'F' if sum( 1 for x in vec[i:i+ratio] if x == 'F' ) 
                         > ratio / 3.0 else 'T'
                for i in xrange(0, len(vec), ratio) ]

实质上,用切片替换islice执行是瞬间完成的。

你能想到一个合理的解释吗?我原以为避免重复分配具有大量元素的新列表,实际上会节省一些计算周期,而不是使整个执行崩溃。


1
使用vec.count("F", i, i+ratio)代替sum( 1 for x in vec[i:i+ratio] if x == 'F' )怎么样?在我看来,这样更易读,而且可能更快。 - Elmar Zander
2个回答

14
< p > < code > islice 可以与任意可迭代对象一起使用。为了实现这一点,它不是直接跳转到第n个元素,而是必须遍历前n-1个元素并将它们丢弃,然后生成您想要的元素。

请查看itertools文档中的纯Python实现:

def islice(iterable, *args):
    # islice('ABCDEFG', 2) --> A B
    # islice('ABCDEFG', 2, 4) --> C D
    # islice('ABCDEFG', 2, None) --> C D E F G
    # islice('ABCDEFG', 0, None, 2) --> A C E G
    s = slice(*args)
    it = iter(xrange(s.start or 0, s.stop or sys.maxint, s.step or 1))
    nexti = next(it)
    for i, element in enumerate(iterable):
        if i == nexti:
            yield element
            nexti = next(it)

说到itertools文档,如果我要执行这个操作,我可能会使用 grouper 的方法。它实际上并不会节省任何内存,但如果你将其重写为更懒的方式,那么就可以节省内存,而这并不难。

from __future__ import division

from itertools import izip_longest
def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

reducedVec = []
for chunk in grouper(ratio, vec):
    if sum(1 for x in chunk if x == 'F') > ratio / 3:
        reducedVec.append('F')
    else:
        reducedVec.append('T')

我喜欢使用grouper来抽象连续的切片,这段代码比原始代码更易于阅读。

分组器确实是一个方便的函数,使得事情更易读。 - Themis

1

我猜测使用islice()会涉及到对vec中每个元素进行Python函数调用,而扩展切片符号则被解析器理解并直接转换为CPython调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接