Ryan C. Thompson
Ryan C. Thompson

Reputation: 42060

Can I "splice" in the contents of an iterator into my own iterator in Python?

Suppose I have an iterator, and I want to add some elements before or after it. The only way I can think of to do this is to use an explicit loop:

def myiter(other_iter):
    yield "First element"
    for item in other_iter:
        yield item
    yield "Last element"

Is there a better or more efficient way to do this? Is there a function with a name like yield_items_from that can be used like this?

def myiter(other_iter):
    yield "First element"
    yield_items_from(other_iter)
    yield "Last element"

Edit:

Ok, I oversimplified my example. Here's a better one:

Suppose I have an iterator other_iter that returns an ascending sequence of nonnegative integers. I want to return an iterator that counts up from zero, returning 1 for numbers returned by other_iter and 0 otherwise. For example, if other_iter yields [1,4,5,7], I want to yield [0,1,0,0,1,1,0,1]. Is there an efficient and readable way to do this?

Upvotes: 2

Views: 499

Answers (5)

Schism
Schism

Reputation: 275

As of Python 3.3, you can use the yield from syntactic form.

def myiter(other_iter):
    yield "First element"
    yield from other_iter
    yield "Last element"

See PEP 380 -- Syntax for Delegating to a Subgenerator for details, or the What's New in Python 3.3 post for a brief tutorial.

Upvotes: 2

dawg
dawg

Reputation: 104014

Itertools works, but if you want an alternate:

def ch(*iters):    
    for it in iters:
        for elem in it:
            yield elem    

s1=["First element"] 
s2=['middle 1', 'middle 2', 'middle 3']
s3=["last element"]

for i in ch(s1,s2,s3):
    print i

Update from your Edit

Your specific request can be met with:

def in_l(l):
    count = 0
    while count<=max(l):
        if count in l: 
            count += 1
            yield 1
        else: 
            count += 1
            yield 0   

print list(in_l([1,4,5,7]))

Be aware that l has to be a sequence, not a generator object. Sequences have a length and a max. Generators may be infinite and therefor no length, subscript, of calculable max value.

I think there is more to this puzzle? What you are describing sounds like there are many other design patterns that better implement it. Have you looked at a simple state machine?

Upvotes: 0

unutbu
unutbu

Reputation: 880269

Use itertools.chain:

import itertools

for item in itertools.chain(["First element"],other_iter,["Last element"]):
    ...

Regarding the edited question, how about:

import itertools as it

def bincounter(iterable):
    counter=it.count()
    for elt in iterable:
        for c in counter:
            if c < elt:
                yield 0
            else:
                yield 1
                break

other_iter=iter([1,4,5,7])    
print(list(bincounter(other_iter)))
# [0, 1, 0, 0, 1, 1, 0, 1]

Upvotes: 2

Jochen Ritzel
Jochen Ritzel

Reputation: 107666

To answer your new question:

other_iter = [1,4,5,7]

def flags( items ):
    c = 0
    for this in items:
        while c < this:
            yield 0
            c += 1
        yield 1

print list(flags(other_iter))

I'm pretty sure there is no builtin or helper that does this.

Upvotes: 0

shang
shang

Reputation: 24802

No, there is nothing like yield_items_from, although there is a draft proposal for adding one to Python 3.X in PEP 380.

For current Python, the explicit loop is the only way to yield from a sub-iterator. However, it's reasonably efficient, and it is the idiomatic way to do it, so you shouldn't be too bothered by the verbosity.

If all you need to do is add new items to the front or back of the iterator, then you can simply use itertools.chain to create a new iterator. You can use it to chain several iterators together, or append/prepend individual items by wrapping them in a list.

new_iter = itertools.chain(['prefix item'], old_iter, appended_iter)

Upvotes: 3

Related Questions