44

I am using Python 3, and trying to detect if an item is the last in a list, but sometimes there will repeats. This is my code:

a = ['hello', 9, 3.14, 9]
for item in a:
    print(item, end='')
    if item != a[-1]:
        print(', ')

And I would like this output:

hello,
9,
3.14,
9

But I get this output:

hello, 
93.14, 
9

I understand why I am getting the output I do not want. I would prefer if I could still use the loop, but I can work around them. (I would like to use this with more complicated code)

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
nedla2004
  • 1,115
  • 3
  • 14
  • 29

6 Answers6

91

While these answers may work for the specific case of the OP, I found they weren't suitable for broader application.

Here are the methods I could think of/saw here and their respective timings.

Negative Slice Method 1

for x in urlist[:-1]:
    pass
# <- handle last item here

Negative Slice Method 2

last_item = urlist[-1]
for x in urlist:
    if x == last_item:
        pass

Enumerate Method

urlist_len = len(urlist)-1
for index, x in enumerate(urlist):
    if index == urlist_len:
        pass

Results:

┌─────────────────────────┬───────┬───────┬───────┐
│        list size        │ 10**2 │ 10**4 │ 10**8 │
├─────────────────────────┼───────┼───────┼───────┤
│ negative slice method 1 │ 2e-6  │ 2e-4  │     3 │
│ negative slice method 2 │ 1e-6  │ 1e-4  │     1 │
│ enumerate               │ 2e-6  │ 2e-4  │     2 │
└─────────────────────────┴───────┴───────┴───────┘

The negative slice method 2 is the fastest in all cases, but if you have duplicate items in your list, negative slice method 2 will give you a false positive. Also, the negative slice method requires that the sequence you are iterating over supports indexing. So, in the case of duplicate items in your list (or not index supporting sequence) use the enumerate method.

Depending on the body your loop, negative slice method 1 or 2 may result in simpler code and they are close in performance.

Mattwmaster58
  • 2,266
  • 3
  • 23
  • 36
  • 3
    Not sure if it will make much difference, but you could do the `len(urlist)-1` calculation outside the loop to improve the performance of both the `index` and `enumerate` approaches – maxedison Mar 21 '19 at 20:13
  • 1
    @maxedison You're absolutely right. I was able to shave off ~35% in the `enumerate` method. – Mattwmaster58 Mar 21 '19 at 23:08
  • 1
    I think the first two could be sped up by using `for x in urlist[:-1]:` for all the elements except the last (with no `if` statement in the loop checking for it), followed by code to handle the last element outside the loop. Something similar could be done for the `enumerate()` approach as well. – martineau Jul 22 '19 at 09:04
  • 1
    Quick note about the "Negative Slice" option, if you are inputing a bunch of similar values, say test scores, and you have 2 identical values in your list, you could prematurely exit your loop. – Vallier Jun 21 '20 at 17:03
  • @Vallier Already noted in the answer :) – Mattwmaster58 Jun 22 '20 at 17:37
  • 1
    This is a textbook example of what a SO answer should be. Outstanding work @Mattwmaster58 – Steve Whitmore Dec 12 '20 at 19:43
  • To fix the Negative Slice method replace `==` with `is`. That will ensure it is the last element in memory, and not just an element with equal value. – young_souvlaki Aug 20 '21 at 15:26
  • @young_souvlaki Not quite, identical objects can be contained in the the same list, eg small integers – Mattwmaster58 Aug 22 '21 at 04:51
  • You're suggesting that in this context python will check object types when using `is` on primitives. Am I following? – young_souvlaki Aug 25 '21 at 18:51
  • @young_souvlaki almost everything in python is an object. Including ints. – Mattwmaster58 Nov 26 '21 at 17:22
30

Rather than try and detect if you are at the last item, print the comma and newline when printing the next (which only requires detecting if you are at the first):

a = ['hello', 9, 3.14, 9]
for i, item in enumerate(a):
    if i:  # print a separator if this isn't the first element
        print(',')
    print(item, end='')
print()  # last newline

The enumerate() function adds a counter to each element (see What does enumerate mean?), and if i: is true for all values of the counter except 0 (the first element).

Or use print() to insert separators:

print(*a, sep=',\n')

The sep value is inserted between each argument (*a applies all values in a as separate arguments, see What does ** (double star) and * (star) do for parameters?). This is more efficient than using print(',n'.join(map(str, a))) as this doesn't need to build a whole new string object first.

Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
10

If you really just want to join your elements into a comma+newline-separated string then it's easier to use str.join method:

elements = ['hello', 9, 3.14, 9]
s = ',\n'.join(str(el) for el in elements)
# And you can do whatever you want with `s` string:
print(s)
skovorodkin
  • 9,394
  • 1
  • 39
  • 30
5

You are not getting the expected output because in your code you say, "Hey python, print a comma if current element is not equal to last element."

Second element 9 is equal to last element, hence the comma is not printed.

The right way to check for the last element is to check the index of element :

a = ['hello', 9, 3.14, 9]
for item in a:
    print(item, end='')
    if a.index(item) != len(a)-1: #<--------- Checking for last element here
        print(', ')

(Also, this might throw a value error. You should check for that too.)

Shivek Khurana
  • 2,056
  • 1
  • 19
  • 16
4

Test if the index value is the length of the list - 1

Like this:

    if item != (len(a) - 1):
        #whatever you wanted to do

    #or, if you want the index to be the last...
    if item == (len(a) - 1):
        #whatever you wanted to do
Smilez
  • 113
  • 7
0

If you are just looking to see if the item is last in the (or any item in any list) you could try using a function to check if an item is last in a list.

a = ['hello', 9, 3.14, 9]
def is_last(alist,choice):
        if choice == alist[-1]:
            print("Item is last in list!")
            return True
        else:
            print("Item is not last")
            return False

if is_last(a,9) == True:
    <do things>

else:
    <do other things>
Malzyhar
  • 17
  • 1
  • Yep, Python uses negative indices to count from right to left in a list. – AgataB Oct 01 '16 at 16:54
  • 2
    `is_last(a, 9)` will return `True` **twice**, for both `a[1]` and `a[3]`. Also, don't use `== True` in an `if` statement, leave testing for truth to `if` directly. – Martijn Pieters Oct 01 '16 at 16:57
  • Martijn is correct, comparing values does NOT necessarily find the end of the list. – kmarsh Aug 05 '20 at 19:03
  • I agree with Martijn and kmarsh. If the list has duplicate values, the "end" will be found earlier and not at the end. – Jax Teller Nov 10 '20 at 09:02