4

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?

3
  • 2
    That code is almost illegible. Describe what it's supposed to do. Commented Dec 10, 2010 at 9:09
  • a and b are two sorted lists. the task is to print the elements of these two lists in nondecreasing order
    – nos
    Commented Dec 10, 2010 at 9:25
  • So you need to merge two sorted lists.
    – khachik
    Commented Dec 10, 2010 at 9:27

3 Answers 3

9

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)
3
  • 1
    Of course, if you actually want to merge two lists you should use the standard library function heapq.merge.
    – jchl
    Commented Dec 10, 2010 at 9:37
  • This would be even better if done as a generator - pass a and b into it and replace the print statements with yield. Then you could do whatever you want with the result and it wouldstill be an iterator.
    – neil
    Commented Dec 10, 2010 at 11:41
  • @neil Agreed. I thought of that but didn't think it was worth the extra complexity for this example. But since you've mentioned it too, I think I'll rewrite it as you suggest.
    – jchl
    Commented Dec 10, 2010 at 11:48
6

You're missing the whole point of iterators. You don't manually call I.next(), you just iterate through I.

for tmp in I:
    print tmp

Edited

To merge two iterators, use the very handy functions in the itertools module. The one you want is probably izip:

merged = []
for x, y in itertools.izip(a, b):
    if x < y:
        merged.append(x)
        merged.append(y)
    else:
        merged.append(y)
        merged.append(x)

Edit again

As pointed out in the comments, this won't actually work, because there could be multiple items from list a smaller than the next item in list b. However, I realised that there is another built-in funciton that deals with this: heapq.merge.

3
  • 1
    I don't see how it's possible to merge two iterators using for.
    – jchl
    Commented Dec 10, 2010 at 9:37
  • That won't work - there might be multiple items from one iterator between two from the other.
    – neil
    Commented Dec 10, 2010 at 11:29
  • @neil yes, I just realised that. Will have to think some more. Commented Dec 10, 2010 at 11:31
0

The function sorted works with lists and iterators. Maybe it is not what you desire, but the following code works.


a.expand(b)
print sorted(iter(a))

1
  • sorted transform iter(a) into a list and then sorts it, so you're not using generators...
    – Ant
    Commented Dec 10, 2010 at 9:54

Not the answer you're looking for? Browse other questions tagged or ask your own question.