Python循环:惯用方法比较列表中相邻的项

6
我需要循环遍历一个对象列表,并进行如下比较:0 vs. 1, 1 vs. 2, 2 vs. 3 等等。(我使用pysvn提取差异列表。)我最终只是循环了一个索引,但我一直在想是否有更贴近惯用语的方法来实现它。这是Python;我应该以某种聪明的方式使用迭代器吗?简单地循环索引似乎非常清晰,但我想知道是否有更具表现力或更简洁的方法来实现它。
for revindex in xrange(len(dm_revisions) - 1):
    summary = \
        svn.diff_summarize(svn_path,
                          revision1=dm_revisions[revindex],
                          revision2 = dm_revisions[revindex+1])

1
就个人而言,我认为可能有更聪明的方法来完成这个任务,但通过循环索引是最清晰的方式。 - Nikwin
如果您提供了一个好的描述,帮助我找到解决方案,我会+1。 - xtian
5个回答

13

这被称为滑动窗口。在itertools文档中有一个示例可以执行此操作。下面是代码:

from itertools import islice

def window(seq, n=2):
    "Returns a sliding window (of width n) over data from the iterable"
    "   s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...                   "
    it = iter(seq)
    result = tuple(islice(it, n))
    if len(result) == n:
        yield result    
    for elem in it:
        result = result[1:] + (elem,)
        yield result

你可以这样说:

for r1, r2 in window(dm_revisions):
    summary = svn.diff_summarize(svn_path, revision1=r1, revision2=r2)

当然,你只关心n=2的情况,所以你可以使用一些更简单的方法:

def adjacent_pairs(seq):
    it = iter(seq)
    a = it.next()
    for b in it:
        yield a, b
        a = b

for r1, r2 in adjacent_pairs(dm_revisions):
    summary = svn.diff_summarize(svn_path, revision1=r1, revision2=r2)

1
我看到新的itertools文档在Recipes部分有一个'pairwise'函数(http://docs.python.org/library/itertools.html)。那似乎会做同样的事情,对吗? - Allan Anderson
1
是的。*(谢天谢地,我们有这个15个字符的限制。否则,我可以直接回答一个是或否的问题说“是”。)* - Jason Orendorff
太好了。这样做很有效,而且我认为更加清晰。我可以给修改起一个有信息量的名称,这样人们就知道在脚本中下一步要使用什么了。我也很感激你把所有东西都详细列出来,即使我最终使用了“tee”和“izip”。 - Allan Anderson

4
我可能会这样做:

import itertools
for rev1, rev2 in zip(dm_revisions, itertools.islice(dm_revisions, 1, None)):
    summary = svn.diff_sumeraize(svn_python, revision1=rev, revision2=rev2)

可以使用类似的更聪明的方法,而不必触及迭代器本身,可能需要使用


这是我脑海中首先想到的事情,因为它是更加实用的方法。实际上,你将列表与“其余部分”一起压缩(得到v1,v2,v2,v3,v3...),然后从结果列表中取出两个一对(v1,v2)(v2,v3)(v3,v4)... - RHSeeger
听起来很合理,而且似乎相当简洁。使用izip怎么样,就像这里描述的一样:http://docs.python.org/library/itertools.html? - Allan Anderson

3

很多复杂的解决方案被发布了,为什么不保持简单呢?

myList = range(5)

for idx, item1 in enumerate(myList[:-1]):
    item2 = L[idx + 1]
    print item1, item2

>>> 
0 1
1 2
2 3
3 4

0

将先前的值存储在一个变量中。使用一个不太可能在处理的序列中找到的值初始化该变量,以便您可以知道是否在第一个元素。将旧值与当前值进行比较。


啊,那听起来是一个有趣的替代方法——虽然不像创建一个花哨的成对迭代器那样Pythonic :) - Allan Anderson
实际上,一个花哨的成对迭代器更符合 Haskell/Lisp 的风格,尽管它也可以在 Python 中使用。 - Ignacio Vazquez-Abrams
有趣;看来我还需要学习所有三种表达式的知识。 - Allan Anderson

-1

如果你在减少函数的结果中保留当前项的副本,那么可以使用Reduce来实现这个目的。

def diff_summarize(revisionList, nextRevision):
    '''helper function (adaptor) for using svn.diff_summarize with reduce'''
    if revisionList:
        # remove the previously tacked on item
        r1 = revisionList.pop()
        revisionList.append(svn.diff_summarize(
            svn_path, revision1=r1, revision2=nextRevision))
    # tack the current item onto the end of the list for use in next iteration
    revisionList.append(nextRevision)
    return revisionList

summaries = reduce(diff_summarize, dm_revisions, [])

编辑:是的,但没有人说reduce函数中的结果必须是标量。我改变了我的示例以使用列表。基本上,最后一个元素始终是先前修订版(除了第一次通过),所有前面的元素都是svn.diff_summarize调用的结果。这样,您将获得一个结果列表作为最终输出...

编辑2:是的,代码确实有问题。我这里有一个可行的虚拟代码:

>>> def compare(lst, nxt):
...    if lst:
...       prev = lst.pop()
...       lst.append((prev, nxt))
...    lst.append(nxt)
...    return lst
...
>>> reduce(compare, "abcdefg", [])
[('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'e'), ('e', 'f'), ('f', 'g'), 'g']

这在shell中进行了测试,正如您所看到的。您需要将comparelst.append调用中的(prev, nxt)替换为实际将调用摘要附加到svn.diff_summarize的内容。

>>> help(reduce)
Help on built-in function reduce in module __builtin__:

reduce(...)
    reduce(function, sequence[, initial]) -> value

    Apply a function of two arguments cumulatively to the items of a sequence,
    from left to right, so as to reduce the sequence to a single value.
    For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
    ((((1+2)+3)+4)+5).  If initial is present, it is placed before the items
    of the sequence in the calculation, and serves as a default when the
    sequence is empty.

1
不,reduce函数将函数应用于序列中的每个元素和到目前为止累积的减少值,而不是将其应用于每个元素及其前一个元素。 - Ian Clelland
我相信OP只是想要比较连续的元素。reduce函数的作用是对前两个元素进行操作,然后将结果与下一个元素进行操作,并重复此过程,直到没有元素剩余。 - Nikwin
当然可以,但那只是微不足道的不同 - 您仍在将一次迭代的数据与下一次迭代的数据进行比较。请参阅更新的代码。 - Daren Thomas
那段代码看起来相当糟糕。你可以使用reduce来完成:http://pastie.org/798394,但我不建议这样做。它似乎过于复杂了。 - Jason Orendorff

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