0

I'm looking for a pythonic way to iterate through a list and do something on the last (and only the last) element. There are two ways I can see to do this, of which I would guess the second is best:

for item in a_list:
    #do something to every element
    if a_list.index(item) == len(a_list) - 1:
        # do something to the last one

and

for n, item in enumerate(a_list):
   #do something to every element
   if n == len(a_list) - 1 :
       # do something to the last one

However, I wonder if there is a way of doing it without calling len() on a list I'm already iterating over. I'm quite happy, by the way, to be told that this isn't something I should worry about.

FarmerGedden
  • 1,162
  • 10
  • 23
  • 1
    Can you explain the difference between 'iterate through a list and do something on the last (and only the last) element' and just doing something with `a_list[-1]`? – dawg Jun 26 '14 at 16:06
  • It simply seemed a shame to go through a list, and then go back and do something to the last element, instead of doing them both on the same iteration. As above, this may be founded in emotion; but it's good to learn when that's true. – FarmerGedden Jun 26 '14 at 16:10
  • 1
    You could also do `enumerate(a_list, 1)` and then you don't need to do a subtraction at every step checking `n == len(a_list)`. – mgilson Jun 26 '14 at 16:15

6 Answers6

10
for item in lst:
   do_something_to(item)
else:
   do_something_extra_special_to_last(item)

Here I just assume that you want to do something extra to the last item (the normal action will still be taken on it beforehand). I also assume you aren't hitting any break statements (in that case else won't execute). Of course, you don't really need else:

for item in lst:
    do_something_to(item)
do_something_extra_special_to_last(item)

should work too since the loop variable "leaks" into the enclosing scope and if there are breaks that you're worried about and you really are looping over a sequence, why not:

for item in lst:
   do_something_to(item)
do_something_extra_special_to_last(lst[-1])
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • Using the `else` block though might be the better option. If we have explicitly broken the loop with `break`, the last element should not be processed IMO. The loop is ended and we are done with `lst`. –  Jun 26 '14 at 16:11
  • 1
    @iCodez -- Possibly. It really depends on the problem that OP is trying to solve (which hasn't been defined very well) which I why I tried to provide a solution for a number of different variations on the problem. :-) – mgilson Jun 26 '14 at 16:13
4

You're making up problems :) There really isn't any with your approach.

If you want to loop, you can find the length. And then access the last thing. Or just do the loop, then do something with a_list[-1]. Fancy way, use for-else - you can google it. But then again, really, there is nothing wrong with your code.

aIKid
  • 26,968
  • 4
  • 39
  • 65
2

You can use the else block of a for-loop:

>>> for i in [1, 2, 3, 4, 5]:
...     print(i)
... else:
...     print(i**2)
...
1
2
3
4
5
25
>>>

As you can see, an operation is performed on each element in the list but the last one undergoes an extra operation.

Note too that the else block will only be run if the loop exits normally without encountering a break statement. This behavior seems proper because, if a break statement was encountered, then the loop was explicitly exited and we are done with the list.

1

You can use this:

a_list[-1]

to access last element

vminof
  • 121
  • 7
  • `a_list[:-1]` will give me the list up to the last element. I think you're thinking of `a_list[-1]` – FarmerGedden Jun 26 '14 at 16:02
  • 2
    @FarmerGedden, did you just answer your own question? – Ben Jun 26 '14 at 16:03
  • Nope. I know how to get the last element of a list. What I'd like is a simple way, while iterating through the list, to do something on only the last element. – FarmerGedden Jun 26 '14 at 16:04
  • 1
    @FarmerGedden it's probably easiest just to tack this on after the loop, rather than playing with indices. – jonrsharpe Jun 26 '14 at 16:05
1

I would certainly prefer the second version of the two you present; index could cause problems if there are duplicates in the list and is an O(n) operation on every iteration, whereas len is O(1).

Generally, though, as you want to do something additional (not different) to the last item, I would just make it a separate step after the for loop:

for item in lst:
    # do something to every element
# do something to lst[-1]

This will work even if there is a break (unlike using else) and affects the last item in the list not the last item iterated over - this may or may not be desired behaviour.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
0

Consider:

li[:]=[do_somthing(item) for item in li]  # something to every item in place
li[-1]=something_extra(li[-1])            # additional to last item

vs

for i, item in enumerate(li):
    li[i]=do_somthing(item)
    if i==len(li)-1:
        li[i]=something_extra(item)

If you time these, you can see this is the fastest way:

def do_something(e):
    return e*2

def something_extra(e):
    return e/2

def f1(li):
    for i, item in enumerate(li):
        li[i]=do_something(item)
        if i==len(li)-1:
            li[i]=something_extra(item)

def f2(li):
    li[:]=[do_something(item) for item in li]
    li[-1]=something_extra(li[-1])

def f3(li):
    for i, item in enumerate(li):
        li[i]=do_something(item)
    li[i]=something_extra(item)

if __name__ == '__main__':
    import timeit
    for f in (f1,f2,f3):
        t=timeit.timeit("f(range(1000))", 
                         setup="from __main__ import f,do_something,something_extra", 
                         number=10000)
        print '{}: {:6.3} seconds'.format(f.__name__, t)

On my (iMac) machine:

f1:   2.95 seconds
f2:   1.45 seconds
f3:   1.97 seconds
dawg
  • 98,345
  • 23
  • 131
  • 206