tags:

views:

100

answers:

4

I asked some similar questions [1, 2] yesterday and got great answers, but I am not yet technically skilled enough to write a generator of such sophistication myself.

How could I write a generator that would raise StopIteration if it's the last item, instead of yielding it?

I am thinking I should somehow ask two values at a time, and see if the 2nd value is StopIteration. If it is, then instead of yielding the first value, I should raise this StopIteration. But somehow I should also remember the 2nd value that I asked if it wasn't StopIteration.

I don't know how to write it myself. Please help.

For example, if the iterable is [1, 2, 3], then the generator should return 1 and 2.

Thanks, Boda Cydo.

[1] http://stackoverflow.com/questions/2421355/how-do-i-modify-a-generator-in-python

[2] http://stackoverflow.com/questions/2421368/how-to-determine-if-the-value-is-one-but-last-in-a-python-generator

+2  A: 

First off, is a generator really needed? This sounds like the perfect job for Python’s slices syntax:

result = my_range[ : -1]

I.e.: take a range form the first item to the one before the last.

Konrad Rudolph
Thanks for the friendly response, but a generator is absolutely needed, not a slice.
bodacydo
+9  A: 

This should do the trick:

def allbutlast(iterable):
    it = iter(iterable)
    current = it.next()
    for i in it:
        yield current
        current = i


>>> list(allbutlast([1,2,3]))
[1, 2]

This will iterate through the entire list, and return the previous item so the last item is never returned.
Note that calling the above on both [] and [1] will return an empty list.

Dave Kirby
It works. Thanks! :)
bodacydo
A: 
gen = (x for x in iterable[:-1])
jellybean
Two problems: a) not all iterables can be sliced, b) if `iterable` is a large list, `iterable[:-1]` will create a large copy of most of that list, which will then be thrown away in a generator. There's got to be a better way to make this one-liner generator work. (But not `itertools.islice`, which doesn't support negative arguments. My bad.)
Chris Lutz
The argument with the large list may be applicable in general, but as the OP wants a generator that will stop at the last-but-one element, the list will presumably be traversed entirely, so that's not too much wasted space.
jellybean
Hm, on second thought, it is wasted space. :) The iterable contains all we need, no need to copy it. Thx for the hint, Chris.
jellybean
A: 

the itertools module shows a pairwise() method in its recipes. adapting from this recipe, you can get your generator:

from itertools import *

def n_apart(iterable, n):
    a,b = tee(iterable)
    for count in range(n):
        next(b)
    return zip(a,b)

def all_but_n_last(iterable, n):
    return (value for value,dummy in n_apart(iterable, n))

the n_apart() function return pairs of values which are n elements apart in the input iterable, ignoring all pairs . all_but_b_last() returns the first value of all pairs, which incidentally ignores the n last elements of the list.

>>> data = range(10)
>>> list(data)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(n_apart(data,3))
[(0, 3), (1, 4), (2, 5), (3, 6), (4, 7), (5, 8), (6, 9)]
>>> list(all_but_n_last(data,3))
[0, 1, 2, 3, 4, 5, 6]
>>> 
>>> list(all_but_n_last(data,1))
[0, 1, 2, 3, 4, 5, 6, 7, 8]
Adrien Plisson