23
for elt in itertools.chain.from_iterable(node):

if elt is the last element:
  do statement

How do I achieve this

Bruce
  • 33,927
  • 76
  • 174
  • 262

6 Answers6

25

You can do this by manually advancing the iterator in a while loop using iter.next(), then catching the StopIteration exception:

>>> from itertools import chain
>>> it = chain([1,2,3],[4,5,6],[7,8,9])
>>> while True:
...     try:
...         elem = it.next()
...     except StopIteration:
...         print "Last element was:", elem, "... do something special now"
...         break
...     print "Got element:", elem
...     
... 
Got element: 1
Got element: 2
Got element: 3
Got element: 4
Got element: 5
Got element: 6
Got element: 7
Got element: 8
Got element: 9
Last element was: 9 ... do something special now
>>> 
Steven Kryskalla
  • 14,179
  • 2
  • 40
  • 42
  • This is the way to do it for a more general solution (i.e. last two, last three, etc.) but for the last element, my solution is (IMHO) cleaner. – Chris Lutz Jan 13 '10 at 17:52
  • 6
    Your solution has less code, but IMO it's less explicit since it relies on a non-obvious scoping rule. I prefer the more explicit approach. – Steven Kryskalla Jan 13 '10 at 18:29
12

When the loop ends, the elt variable doesn't go out of scope, and still holds the last value given to it by the loop. So you could just put the code at the end of the loop and operate on the elt variable. It's not terribly pretty, but Python's scoping rules aren't pretty either.

The only problem with this (thanks, cvondrick) is that the loop might never execute, which would mean that elt doesn't exist - we'd get a NameError. So the full way to do it would be roughly:

del elt # not necessary if we haven't use elt before, but just in case
for elt in itertools.chain.from_iterable(node):
    do_stuff_to_each(elt)
try:
    do_stuff_to_last(elt)
except NameError: # no last elt to do stuff to
    pass
Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • 4
    Just be careful in the case when the loop never executes as this could lead to some wild execution path. – carl Jan 13 '10 at 17:47
  • 2
    Just drop the code to apply to the last element in the `else` clause. – Ignacio Vazquez-Abrams Jan 13 '10 at 17:52
  • @Ignacio - `for x in (): print x else: print x` gives a `NameError`. We need to catch the exception to cover the case where it never executes. (And putting it in an `else` wouldn't execute if we broke out of the loop. Which we might want to do.) – Chris Lutz Jan 13 '10 at 17:54
2

I do something like this:

rng = len(mlist)
for i in range(rng):
    foo = mlist[i]
    foo.do_something_for_every_item_regardless()
    if i == rng - 1: #since we go from 0 to rng-1
        foo.last_item_only_operation()
ether_joe
  • 1,068
  • 1
  • 13
  • 32
2

You can't per se. You'd need to store the current item, advance the iterator, and catch the StopIteration exception. And then you'd need to somehow signal that you have the last item.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • This wouldn't be hard to do, and is a more general solution, but isn't entirely necessary for the current task. – Chris Lutz Jan 13 '10 at 17:41
0

while True:
    try:
      print(next(myIterator))
    except StopIteration:
        break
0

Here's a utility function that works like enumerate() except returns a bool if this was the last element.

Now you don't have to add any additional lines, and this works on iterables besides lists.

def endify(itr):
    '''
    Like enumerate() except returns a bool if this is the last item instead of the number
    '''
    itr = iter(itr)
    has_item = False
    ended = False
    next_item = None
    while not ended:
        try:
            next_item = next(itr)
        except StopIteration:
            ended = True
        if has_item:
            yield ended, item
        has_item = True
        item = next_item

Look how clean this can be used now:

for end, i in endify(range(3)):
    print(f'Item: {i}, End: {end}')
Item: 0, End: False
Item: 1, End: False
Item: 2, End: True
Dane Bouchie
  • 421
  • 5
  • 11