views:

80

answers:

4

A question of particular interest about python for loops. Engineering programs often require values at previous or future indexes, such as:

for i in range(0,n):
    value = 0.3*list[i-1] + 0.5*list[i] + 0.2*list[i+1]

etc...

However I rather like the nice clean python syntax:

for item in list:
    #Do stuff with item in list

or for a list of 2d point data:

for [x,y] in list:
    #Process x, y data

I like the concept of looping over a list without explicitly using an index to reference the items in the list. I was wondering if there was a clean way to grab the previous or next item without looping over the index (or without keeping track of the index independently)?

EDIT:

Thanks Andrew Jaffe (and by proxy Mark Byers) and gnibbler for the simple, extendable examples. I wasn't aware of the itertools or nwise modules till now. John Machin - thanks for the very complex example of what NOT to do. You put a lot of effort into this example, obviously the somewhat recursive algorithm I presented cannot produce a list with the same number of elements as the input list and it presents problems if not using explicit indexes. An algorithm like this would commonly occur in signal processing.

+2  A: 

To have access to an element and the next one you can use the pairwise recipe that is shown in the itertools documentation:

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

This could be adapted to allow access to three neighbouring elements instead of just two.

Mark Byers
+3  A: 

Here's a recipe, based on the itertools pairwise code, which does general n-wise grouping:

import itertools

def nwise(iterable, n=2):
    "s->(s_0,s_1, ..., s_n), (s_1,s_2,..., s_n+1), ... "
    ntup = itertools.tee(iterable, n)
    for i, item in enumerate(ntup):
        for ii in range(i):
            next(item, None)
    return itertools.izip(*ntup)

Which can be used thusly:

>>> import nwise
>>> ll = range(10)
>>> for tup in nwise.nwise(ll,3): print tup
... 
(0, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, 5)
(4, 5, 6)
(5, 6, 7)
(6, 7, 8)
(7, 8, 9)

[Thanks to Mark Byers' answer for the idea]

Andrew Jaffe
+1  A: 
>>> from itertools import islice, izip
>>> seq = range(10)
>>> for x,y,z in izip(*(islice(seq,i,None) for i in range(3))):
...  print x,y,z
... 
0 1 2
1 2 3
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9

this can be trivially extended beyond 3 items.

If you need it to work with any iterable, Andrew's answer is suitable, or you can do it like this

>>> from itertools import izip, islice, tee
>>> seq=(x for x in range(10))
>>> for x,y,z in izip(*(islice(j,i,None) for i,j in enumerate(tee(seq,3)))):
...  print x,y,z
... 
0 1 2
1 2 3
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9
gnibbler
A: 

If you want to use zip, here's how NOT to do it (stuffed, needlessly) and how not to check it (stuffed x 2.5), with those deficiencies corrected.

vector = [2**i for i in range(1,6)]
print "vector", vector
value = []
for i in range(1,len(vector)-1):
    value.append(0.3*vector[i-1] + 0.5*vector[i] + 0.2*vector[i+1])

print "value", len(value), value
value2=[0.3*before + 0.5* this + 0.2 * after
          for before,this,after in zip(vector,vector[1:]+[0], vector[2:]+[0,0])
          ]
# above +[0] and +[0,0] needlessly extend the answer by two items
print "value2", len(value2), value2          
print "bad check bad values", not any([x-y for x,y in zip(value,value2) if x-y > 1e-7])
# the bad check doesn't check for length 
# the bad check doesn't use abs(actual - expected)
# the bad check has a unnecessary if test in it
# the bad check uses a list comprehension when a generator would do
print "good check bad values", (
    len(value2) == len(value)
    and
    not any(abs(x-y) > 1e-7 for x,y in zip(value,value2))
    )
value2=[0.3*before + 0.5* this + 0.2 * after
          for before,this,after in zip(vector,vector[1:], vector[2:])
          ]    
print "fixed value2", len(value2), value2  
print "good check good values", (
    len(value2) == len(value)
    and
    not any(abs(x-y) > 1e-7 for x,y in zip(value,value2))
    )
John Machin
This solution would be right if vector is supposed to change length and the calculation only used for those values which have both neighbours. Multipliers in example do seem to sum up 1.0, and 0 is not really neighbour value for vectors first and last numbers. Also the example's wrap around to last as neighbour in case of first value is questionable, how ever
Tony Veijalainen