从循环的最后一次迭代中访问值的最佳方法

3

在for循环中,如果要访问前一次迭代的值,最好和最快的方法是什么?假设对象非常大(例如,拥有100,000个以上记录的游标对象)。

下面使用一个简单的例子:

tmp = [
         ['xyz', 335], ['zzz', 338], ['yyy', 339], ['yyy', 442], 
         ['abc', 443], ['efg', 444], ['ttt', 446], ['fff', 447]
      ]

for x in tmp:
   if not prev:
     prev = x[1]
   print 'seq: ', x[1], 'prev seq:', prev, 'variance: ', x[1]-prev
   prev = x[1]

这是处理这个问题的最优方式吗?

根据下面的回复,我进行了一些测试:tmp创建了500个列表,运行20次的平均值如下所示。

结果:

Mines: 0.623
Dave片段1:0.605
Dave片段2:0.586
Catchmeifyoutry(编辑代码):0.707


1
另外一点需要注意的是,这种类型的循环可能会受益于使用非常易于使用的 psyco JIT 编译器来优化 Python 代码:http://psyco.sourceforge.net/ - catchmeifyoutry
有趣的是,在WinXP Netbook上使用Python 2.5(耶),如果没有Psyco,我的izip解决方案比你的慢但比Dave的快(使用range(100000)),但是有了Psyco,你的解决方案比Dave的和我的都要快得多。 - catchmeifyoutry
6个回答

4

只需使用zip()迭代对,这样更易读。

更新:对于Python 2.x,请使用itertools.izip,因为它更有效率!

from itertools import izip
for prev, next in izip(tmp, tmp[1:]):
    print 'seq: ', next[1], 'prev seq:', prev[1], 'variance: ', next[1]-prev[1]

也可以使用值解包来避免索引:

for (_, prev), (_, next) in izip(tmp, tmp[1:]):
    print 'seq: ', next, 'prev seq:', prev, 'variance: ', next-prev

或者如果你确实需要第一次迭代,您可以尝试以下代码:
for prev, next in izip(tmp, tmp[:1] + tmp):
    print 'seq: ', next[1], 'prev seq:', prev[1], 'variance: ', next[1]-prev[1]

编辑

如果你想避免在第二个参数中创建一个列表,你也可以使用显式迭代器:

itr = iter(tmp)
itr.next() # here I assume tmp is not empty, otherwise an exception will be thrown
for prev, next in izip(tmp, itr):
    print 'seq: ', next[1], 'prev seq:', prev[1], 'variance: ', next[1]-prev[1]

注意:这个zip模式在类似的问题中也很有用。 例如,从列表中提取连续的三元组:

xs = range(9)
triplets = zip(xs[::3], xs[1::3], xs[2::3]) # python 2.x, zip returns a list

print xs       # [0, 1, 2, 3, 4, 5, 6, 7, 8]
print triplets # [(0, 1, 2), (3, 4, 5), (6, 7, 8)]

另外请注意,在Python 3中,zip返回一个迭代器,类似于itertools.izip


感谢回复,我刚测试了一下(你修改的代码),似乎是所有选项中最慢的(甚至比我上面的原始代码还要慢)。 - ismail
正如所述,这可能是因为在Python 2.x中zip会在内存中构建完整的列表。无论如何,在这种情况下,您应该使用显式循环。不幸的是,依我之见,这是最佳解决方案(在“期望的Pythonic方式”意义上最佳)。祝好运! - catchmeifyoutry
算了吧,Python 2.x 有 itertools.izip :p,请重新计时 - catchmeifyoutry

3
你的代码会在每次循环中执行“if not prev”测试,即使它只适用于第一个元素。此外,我认为你的代码有问题-在循环的第一次中,prev和current的值是相同的。
如果至少有一个元素,我会这样做:
tmp_iter = iter(tmp)
prev = tmp_iter.next()

for x in tmp_iter: 
   print 'seq: ', x[1], 'prev seq:', prev[1], 'variance: ', x[1]-prev[1]
   prev = x

这可以通过去掉索引进一步优化:
tmp_iter = iter(tmp)
[_, prev] = tmp_iter.next()

for [_, x] in tmp_iter: 
   print 'seq: ', x, 'prev seq:', prev, 'variance: ', x-prev
   prev = x

我使用赋值操作将列表分解为其组成部分,并将第一个元素分配给_,因为它没有被使用。


Dave,感谢你的快速回应。实际上代码是正确的,在第一次迭代中方差确实应该为0,即在它之前没有任何内容。还有其他的优化措施吗? - ismail
这里的第二个选项似乎是最快的,我将更新我的问题并附上平均结果。 - ismail

2

使用 itertools

from itertools import izip, islice
for prev, cur in izip(l, islice(l, 1, None)):
    print 'seq:', cur[1], 'prev seq:', prev[1], 'delta:', cur[1]-prev[1]

针对问题中给出的具体示例,需要注意的是,如果这些数字可以用32位整数表示,并且数字列表可以放入内存中,则使用numpy是计算差异最快的方法之一:

import numpy
a = numpy.array([x[1] for x in tmp])
delta = numpy.diff(a)

1

Guido的时间机器来拯救了!

来自itertools recipes页面:

import itertools
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.izip(a, b)

这应该是最合适的方法(考虑到可迭代对象为(random.randint(100) for x in xrange(1000));在这里,将iter(iterable); next(iterable)作为辅助迭代器可能无法提供正确的功能。

在循环中使用它:

for prev_item, item in pairwise(iterable):
    …

0

这段代码会生成NameError,因为在if not prev处,prev未被定义。在循环之前将其设置为False或None。另外,您可以使用不同的循环:

for i in xrange(1, len(tmp)):
    print 'seq: {0}, prev seq: {1}, variance: {2}'.format(tmp[i][1], tmp[i - 1][1], tmp[i] - tmp[i - 1][1])

如果您使用100,000+条记录,瓶颈将不是循环,而是应用程序使用的内存。不要以这种格式存储所有数据:每对值(列表)将占用100+字节。如果它们在文件中,则最好迭代其行:
(假设数据是以制表符分隔的)
def reader(filename):
    with open(filename) as f:
        prev = f.next()
        for l in f:
            l = l.split('\t')
            yield (prev, l)
            prev = l

for (prev, curr) in reader(myfile):
    print 'seq: {0}, prev seq: {1}, variance: {2}'.format(curr[1], prev[1], curr[1] - prev[1])

reader 是一个生成器,它可以多次从序列中返回值。这样,内存中只会同时存储两行数据,即使有数百万行数据,您的应用程序也能够持续运行。

为了使代码易读,我将其放在一边,这样在程序主体中我们处理数据序列时,不必关心它是如何组成的。


嗨,culebron,是的,在我的代码中,我实际上设置了 prev = None(但这里没有包括)。 - ismail

0
it = imap(operator.itemgetter(1), tmp) # get all 2nd items
prev = next(it, None) # get 1st element (doesn't throw exception for empty `tmp`)
for x in it:
    print 'seq: %s prev seq: %s variance: %s' % (x, prev, x-prev)
    prev = x

如果我可以回报你的恩情:现在一般会使用next(it, None)代替for prev in it: break - tzot
@ΤΖΩΤΖΙΟΥ:谢谢。自从Python 2.6以来,next(it, None)就是正确的方法。 - jfs

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