Welcome, guest | Sign In | My Account | Store | Cart

zip() stops zipping at the shortest of its sequence-arguments and thus also allows unbounded-sequence arguments. This affords, for example, a very spare idiom for the frequent need of a parallel loop on index and sequence-item.

Python, 26 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
indices = xrange(sys.maxint)

for item, index in zip(sequence, indices):
    something(item, index)

# same semantics as:
for index in range(len(sequence)):
    something(sequence[index], index)

# but the change-of-emphasis allows greater
# clarity in some usage contexts.

# Further alternatives exist of course:

class Indexed:
    def __init__(self, seq):
        self.seq = seq
    def __getitem__(self, i):
        return self.seq[i], i

for item, index in Indexed(sequence):
    something(item, index)

# or equivalently:
def Indexed(sequence):
    return zip(sequence, indices)

We often want to loop on a sequence, but also need the current index in the loop body. Canonical is "for i in range(len(sequence)):", using sequence[i] as the item-reference in the body. But in some contexts it's clearer to emphasise the loop on the sequence-item rather than on the index. zip gives an easy alternative, looping on both index and item in parallel, since it truncates at the _shortest_ of its arguments -- thus, it's OK for some arguments to be unbounded sequences. An unbounded sequence of indices is trivial to write (xrange is handy for this!) and a (reusable...) instance of that can be passed to zip, in parallel to the sequence being indexed.

The same zip usage also affords a client-code-transparent alternative to the use of a wrapper-class 'Indexed' -- see the Indexed class and function in the above example code.

4 comments

Sami Hangaslammi 20 years, 5 months ago  # | flag

zip() not semantically equivalent. Sometimes using zip() will provide different results (with some lazily evaluted sequences) or fail (with unbounded sequences).

Since we already have xrange and xreadlines, maybe someone should do a PEP on xfilter, xmap and xzip too. ;-)

Phillip Ruggera 16 years, 11 months ago  # | flag

so just use the Class. map is also going to be depreciated in 3.0. So use the Class:

>>> class Indexed(object):
...     def __init__(self, seq):
...         self.seq = seq
...     def __getitem__(self, i):
...         return self.seq[i], i

>>> sequence = ['a','b','c','d','e','f']
>>> for item, index in Indexed(sequence):
...     print item, str(index)
a 0
b 1
c 2
d 3
e 4
f 5
Phillip Ruggera 16 years, 11 months ago  # | flag

An iterable version. Since almost all objects are iterable now we can enhance the class to work with them:

class Indexed(object):
    """provide index while iterating over an object

    >>> sequence = ['a','b','c','d','e','f']
    >>> for item, index in Indexed(sequence):
    ...     print item, str(index)
    a 0
    b 1
    c 2
    d 3
    e 4
    f 5

    """
    def __init__(self, object):
        self.object = object.__iter__()
    def __getitem__(self, i):
        return self.object.next(), i
Phillip Ruggera 16 years, 11 months ago  # | flag

Builtin with 2.3. There is a builtin that provides the same functionality as the class:

>>> sequence = ['a','b','c','d','e','f']
>>> for index, item in enumerate(sequence):
...     print index, item
...
0 a
1 b
2 c
3 d
4 e
5 f

I need to read the fine manuals more often.

Created by Alex Martelli on Thu, 15 Mar 2001 (PSF)
Python recipes (4591)
Alex Martelli's recipes (27)
Python Cookbook Edition 1 (103)

Required Modules

  • (none specified)

Other Information and Tasks