Python "for in" loop to print the last item in the list

10,759

Solution 1

You are mutating the list whilst iterating over it.

You can use a while loop to do this:

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

while 'c' in list_A:
    print(list_A.pop())

print('job done')

Output:

j
i
h
g
f
e
d
c
job done

A more efficient way would be to determine the index of the first instance of the sentinel character, and remove it and the rest of the list (although the characters are not printed as they are removed):

try:
    pos = list_A.index('c')
    list_A[:] = list_A[:pos]
    # del list_A[pos:]           # more efficient alternative suggested by @ShadowRanger
except ValueError as e:
    pass

Solution 2

When using a for..in loop in Python, you are not supposed to modify the list.

What happened here is the following:

  • The loop goes from the first item to the last, so it starts at a
  • pop() removes the last list entry, so in that first loop iteration you get rid of the last letter j and print it
  • This all continues for the next 5 letters. You iterate over them from the left, but remove the last one on the right at at the same time
  • When meeting e you remove and print f from the list
  • After that the list contains the letters a to e and since you just iterated over e the loop's job is done

It's really hard to say what you wanted to do here, since it's more of playing around rather than getting something done. I would suggest to use a while loop though, whenever you intend to edit the list from within the loop. Your example with proper semantics could look like this:

while list_A:
    print(list_A.pop())
    if "c" not in list_A:
        break

This loop goes for as long as there is items in the list and only stops once there is no c in the list anymore.

Solution 3

I recently answered a similar question and it boils down to: Don't modify the sequence you're iterating over.

Using a custom iterator (from another answer of mine) shows what happened:

class CustomIterator(object):
    def __init__(self, seq):
        self.seq = seq
        self.idx = 0

    def __iter__(self):
        return self

    def __next__(self):
        print('give next element:', self.idx)
        for idx, item in enumerate(self.seq):
            if idx == self.idx:
                print(idx, '--->', item)
            else:
                print(idx, '    ', item)
        try:
            nxtitem = self.seq[self.idx]
        except IndexError:
            raise StopIteration
        self.idx += 1
        return nxtitem

    next = __next__  # py2 compat

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

for i in CustomIterator(list_A):
    print(list_A.pop())
    if 'c' not in list_A:
        break

Which prints:

give next element: 0
0 ---> a
1      b
2      c
3      d
4      e
5      f
6      g
7      h
8      i
9      j
j
give next element: 1
0      a
1 ---> b
2      c
3      d
4      e
5      f
6      g
7      h
8      i
i
give next element: 2
0      a
1      b
2 ---> c
3      d
4      e
5      f
6      g
7      h
h
give next element: 3
0      a
1      b
2      c
3 ---> d
4      e
5      f
6      g
g
give next element: 4
0      a
1      b
2      c
3      d
4 ---> e
5      f
f
give next element: 5
0      a
1      b
2      c
3      d
4      e

So it didn't end because of the break but because it iterated over the whole list (or better: until there were no more items!).

Also the 'c' not in listA is an O(n) operation, so your loop is effectively O(n**2). Why not just find the first index of 'c' and simply iterate until you're there:

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

try:
    c_index = list_A.index('c')
except ValueError:
    # no 'c' in the list, probably should do something more useful here ...
    pass
else:
    for item in reversed(list_A[c_index:]):  # print the items
        print(item)
    del list_A[c_index:]  # remove the items from the list

prints (as expected):

j
i
h
g
f
e
d
c

Solution 4

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']

while list_A: # while list_A has elements, in case 'c' wasn't present
    el = list_A.pop() # save the last element
    print(el)
    if 'c'==el: # if 'c' was popped (reached)
        break
print("job done.")

This way, even if 'c' isn't present, it'll just print everything then exit. This also avoids checking if 'c' is present on every iteration, which takes time.

Based on @MSeifert's comment, if the loop shouldn't stop at the first c popped instead stops whenever the list has no c, a little modification to the code above results in:

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'c', 'h', 'i', 'j']

while list_A:
    print(list_A.pop())
    if 'c' not in list_A:
        break
print("job done.")

We could go faster, but I don't know wether you learned list slicing and comprehesion yet, so here's a better and faster solution:

list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
try:
  p=list_A.index('c')
  r='\n'.join(list_A[list_A.index('c'):][::-1])
except ValueError:
  r='\n'.join(list_A[::-1])
print(r)
print('job done.')
Share:
10,759
jxie0755
Author by

jxie0755

A total beginner here. Owning a Ph.D. degree in other areas, and now decided to learn programming for personal interest. Currently learning Python: https://github.com/jxie0755/Learning_Python The future is uncertain, but I believe that having the computational thinking will help.

Updated on June 10, 2022

Comments

  • jxie0755
    jxie0755 almost 2 years

    Lately I learned about lists and for loops, as well as the command .pop() that indicates and removes the last item in a list.

    So I tried to write a code to remove the last items in a list one by one, until it remains with only one item.

    The code is:

    list_A = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
    
    for i in list_A:
        print(list_A.pop())
        if 'c' not in list_A:
            break
    
    print("job done.")
    

    The output of python 3.6 gives me this:

    /Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6
    j
    i
    h
    g
    f
    job done.
    

    As you can see, it actually worked, but for a half of it?

    I was expecting:

    j
    i
    h
    g
    f
    e
    d
    c
    job done
    

    I mean, I will be more comfortable if it returns some error, that means the code is not right. But why did it work, but not a full way through?