views:

314

answers:

5

I need to loop over a list of objects, comparing them like this: 0 vs. 1, 1 vs. 2, 2 vs. 3, etc. (I'm using pysvn to extract a list of diffs.) I wound up just looping over an index, but I keep wondering if there's some way to do it which is more closely idiomatic. It's Python; shouldn't I be using iterators in some clever way? Simply looping over the index seems pretty clear, but I wonder if there's a more expressive or concise way to do it.

for revindex in xrange(len(dm_revisions) - 1):
    summary = \
        svn.diff_summarize(svn_path,
                          revision1=dm_revisions[revindex],
                          revision2 = dm_revisions[revindex+1])
A: 

Store the previous value in a variable. Initialize the variable with a value you're not likely to find in the sequence you're handling, so you can know if you're at the first element. Compare the old value to the current value.

Ignacio Vazquez-Abrams
Ah, that sounds like an interesting alternative way to do it--not quite as pythonic as creating a fancy pairwise iterator, though :)
Allan Anderson
Actually, a fancy pairwise iterator would be more Haskellish/Lispish, though it would work in Python.
Ignacio Vazquez-Abrams
Interesting; guess I've got more to learn about all three types of expression.
Allan Anderson
+2  A: 

I'd probably do:

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)

Something similarly cleverer and not touching the iterators themselves could probably be done using

Alex Gaynor
This was the first thing that popped into my mind, it being the more functional approach. Effectively, you're zipping the list with "the rest of" itself (resulting in v1,v2,v2,v3,v3...) and then taking pairs of two out of the resulting list (v1,v2)(v2,v3)(v3,v4)...
RHSeeger
Makes sense, and seems quite concise. How about using izip, as described here: http://docs.python.org/library/itertools.html ?
Allan Anderson
A: 

Reduce can be used for this purpose, if you take care to leave a copy of the current item in the result of the reducing function.

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, [])

EDIT: Yes, but nobody said the result of the function in reduce has to be scalar. I changed my example to use a list. Basically, the last element is allways the previous revision (except on first pass), with all preceding elements being the results of the svn.diff_summarize call. This way, you get a list of results as your final output...

EDIT2: Yep, the code really was broken. I have here a workable dummy:

>>> 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']

This was tested in the shell, as you can see. You will want to replace (prev, nxt) in the lst.append call of compare to actually append the summary of the call to 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.
Daren Thomas
No, reduce applies the function to each element in the sequence and *the accumulated reduced value so far*, rather than applying it to each element and its predecessor.
Ian Clelland
I believe that the OP simply wants to compare successive elements. What reduce does is operate on the first two elements, takes the result of that, and performs the operation with the result and the next element, and repeats this until no elements are left.
Nikwin
Sure, but that is only marginally different - you are still comparing data from one iteration to the data from the next iteration. See updated code.
Daren Thomas
That code is pretty broken, looks like. You *can* use reduce for this: http://pastie.org/798394 but I wouldn't recommend it. It seems unnecessarily opaque.
Jason Orendorff
+5  A: 

This is called a sliding window. There's an example in the itertools documentation that does it. Here's the code:

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

What that, you can say this:

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

Of course you only care about the case where n=2, so you can get away with something much simpler:

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)
Jason Orendorff
I see that the newer itertools documentation has a 'pairwise' function in the Recipes section ( http://docs.python.org/library/itertools.html ). That seems like it would do the same thing, yes?
Allan Anderson
Yes. *(Thank goodness we have this 15-character limit. Otherwise I could just say "yes" in response to a yes-or-no question.)*
Jason Orendorff
Great. That works and is, I think, more clear. I can give the revisions informative names so people know what's being used further down in the script. I appreciate seeing it all spelled out, too, even if I did wind up using "tee" and "izip".
Allan Anderson
A: 

So many complex solutions posted, why not keep it simple?

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
truppo