count in 'for ... in ...'

Steven D. Majewski sdm7g at Virginia.EDU
Tue Nov 6 18:28:22 CET 2001

Very readable, but ineffecient ( and also possibly WRONG if there
are duplicates in the list ) is:

>>> xxx = [ "one", 'two', 'three', 'four' , 'five', 'six' ] 
>>> for x in xxx: print xxx.index(x), ':', x  		# or .index(x)+1 ?
0 : one
1 : two
2 : three
3 : four
4 : five
5 : six

A little bit more cryptic, but better is:

>>> for count,item in zip( range(len(xxx)), xxx ): print count,":",item
0 : one
1 : two

But if you really do this frequently, just hide the magic away in 
a function:

>>> def IndexItems( things ):
...     return zip( range(len(things)), things )
>>> for count,item in IndexItems(xxx): print count, ":", item
0 : one
1 : two

In an old thread on the algebra of generators, I posted some 
examples of a class of generators that defined operators for
combining generators. "&" applied a test on a generator and
produced a generator that only output items that  passed
the test and "|" was an 'alternation' operator that combined
two generator streams, alternating one from each. 

So in this example:

for x in Files() & isGif & fileOwner('sdm7g') &  fileLargerThan(512) |range(20):
        print x

Files() generates a recursive list of files ( with no path, current 
  directory is the default, so it's like: unix: 'find .' )
The others following the "&" filter on those attributes, and the 
"|range(20)" causes the output lines to alternate between file names
and counts ( until it hits either 19 or the end of the files that
pass the tests ). 

Using this method, since the two streams are combined into one, 
you can't print count and item with the same print statement --
the .next is getting implicitly called at the top of the loop.

There's probably a better way of using generators for your problem,
but I don't see that it'll be any real improvement over the IndexItems
function above. A generator version of something like IndexItems
would be useful for using to index other generators, as the range
of len of items will fail on an indefinete length sequence. 

Actually, zip() will work for generators as long as at least one
of the sequences or generators you feed to it will terminate. 
It will force evaluation of any iterators until it gets to the
end of one of them. If you don't want them all evaluated (maybe
because of size, or maybe they might all be indefinite length)
you need something like an 'izip()' :

>>> from __future__ import generators
>>> def izip( *args ):
...     its = map( iter, args )
...     while 1: 
...             yield map( lambda x:, its )

>>> zip( xrange(10), range(4) )		# forces evaluation
[(0, 0), (1, 1), (2, 2), (3, 3)]

>>> izip( xrange(10), range(4) )	# returns a generator
<generator object at 0x826440>

>>> for x,y in izip( xrange(10), range(4) ): print x,y
0 0
1 1
2 2
3 3

-- Steve Majewski

More information about the Python-list mailing list