开发者

How to use Python iterators elegantly

开发者 https://www.devze.com 2023-01-29 16:03 出处:网络
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

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?


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)


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.


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))

0

精彩评论

暂无评论...
验证码 换一张
取 消