Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Python iterators elegantly

I am trying to use iterators more for looping since I heard it is faster than index looping. One thing I am not sure is about how to treat the end of the sequence nicely. The way I can think of is to use try and except StopIteration, which looks ugly to me.

To be more concrete, suppose we are asked to print the merged sorted list of two sorted lists a and b. I would write the following

aNull = False
I = iter(a)
try:
    tmp = I.next()
except StopIteration:
    aNull = True

for x in b:
    if aNull:
        print x
    else:
        if x < tmp:
            print x
        else:
            print tmp,x
            try:
                tmp = I.next()
            except StopIteration:
                aNull = True

while not aNull:
    print tmp
    try:
        tmp = I.next()
    except StopIteration:
        aNull = True

How would you code it to make it neater?

like image 628
nos Avatar asked Dec 08 '22 00:12

nos


1 Answers

I think handling a and b more symmetrically would make it easier to read. Also, using the built-in next function in Python 2.6 with a default value avoids the need to handle StopIteration:

def merge(a, b):
    """Merges two iterators a and b, returning a single iterator that yields
    the elements of a and b in non-decreasing order.  a and b are assumed to each
    yield their elements in non-decreasing order."""

    done = object()
    aNext = next(a, done)
    bNext = next(b, done)

    while (aNext is not done) or (bNext is not done):
        if (bNext is done) or ((aNext is not done) and (aNext < bNext)):
            yield aNext
            aNext = next(a, done)
        else:
            yield bNext
            bNext = next(b, done)

for i in merge(iter(a), iter(b)):
    print i

The following function generalizes the approach to work for arbitrarily many iterators.

def merge(*iterators):
    """Merges a collection of iterators, returning a single iterator that yields
    the elements of the original iterators in non-decreasing order.  Each of
    the original iterators is assumed to yield its elements in non-decreasing
    order."""

    done = object()
    n = [next(it, done) for it in iterators]

    while any(v is not done for v in n):
        v, i = min((v, i) for (i, v) in enumerate(n) if v is not done)
        yield v
        n[i] = next(iterators[i], done)
like image 85
jchl Avatar answered Dec 29 '22 00:12

jchl