27

I'm working through 'Automate the Boring Stuff with Python'. I can't figure out how to remove the final output comma from the program below. The goal is to keep prompting the user to input values, which are then printed out in a list, with "and" inserted before the end. The output should look something like this:

apples, bananas, tofu, and cats

Mine looks like this:

apples, bananas, tofu, and cats,

That last comma is driving me NUTS.

def lister():
    listed = []
    while True:
        print('type what you want to be listed or type nothing to exit')
        inputted = input()
        if inputted == '':
            break
        else:
            listed.append(inputted+',')
    listed.insert(-1, 'and')
    for i in listed:
        print(i, end=' ')
lister()
cs95
  • 379,657
  • 97
  • 704
  • 746
Admin_Who
  • 305
  • 3
  • 12
  • 1
    Does this answer your question? [How to join list in Python but make the last separator different?](https://stackoverflow.com/questions/30083949/how-to-join-list-in-python-but-make-the-last-separator-different) – Georgy Feb 18 '20 at 14:55

9 Answers9

46

You can avoid adding commas to each string in the list by deferring the formatting to print time. Join all the items excluding the last on ', ', then use formatting to insert the joined string with the last item conjuncted by and:

listed.append(inputed)
...
print('{}, and {}'.format(', '.join(listed[:-1]), listed[-1]))

Demo:

>>> listed = ['a', 'b', 'c', 'd']
>>> print('{}, and {}'.format(', '.join(listed[:-1]), listed[-1]))
a, b, c, and d
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • 5
    Improvement suggestion: `str.format` is no longer recommended, and 3.6 added support for a new inline formatting syntax that makes the code even more readable if you want to use a formatting approach: `f"{', '.join(listed[:-1])}, and {listed[-1]}"`. However, I would say no formatting at all is the most readable: `', '.join(listed[:-1]) + ", and" + listed[-1]` – gntskn Jun 16 '17 at 03:24
  • 1
    @gntskn Is `str.format` really "no longer recommended"? I would argue that the format string itself is easier to read as is, could be extracted to a `fmt` variable, etc. I'm sure there are use cases for `f"..."` strings, especially when just pasting local variables into a format string, but to say that `str.format` is "no longer recommended" at all seems a bit overzealous. – Zac Crites Jun 16 '17 at 14:24
  • @ZacCrites You know what, you're right; I was thinking of the `%` operator. This is what I get for posting from my phone right before midnight :p – gntskn Jun 16 '17 at 15:26
  • 1
    @gntskn Also note that *f-strings* are not available in Python versions < 3.6 – Moses Koledoye Jun 17 '17 at 18:02
  • 1
    This code doesn't handle the list having less than 3 items. – Boris Verkhovskiy Feb 17 '20 at 10:11
19

The accepted answer is good, but it might be better to move this functionality into a separate function that takes a list, and also handle the edge cases of 0, 1, or 2 items in the list:

def oxfordcomma(listed):
    if len(listed) == 0:
        return ''
    if len(listed) == 1:
        return listed[0]
    if len(listed) == 2:
        return listed[0] + ' and ' + listed[1]
    return ', '.join(listed[:-1]) + ', and ' + listed[-1]

Test cases:

>>> oxfordcomma([])
''
>>> oxfordcomma(['apples'])
'apples'
>>> oxfordcomma(['apples', 'pears'])
'apples and pears'
>>> oxfordcomma(['apples', 'pears', 'grapes'])
'apples, pears, and grapes'
David Conrad
  • 15,432
  • 2
  • 42
  • 54
  • 1
    Good modularization. For the general case I think I prefer passing the list to join with the last item having `and ` prefixed, so the comma gets added by `join` along with the rest of them: `', '.join(listed[:-1] + [f'and {listed[-1]}'])`... but that seems like a simple aesthetic choice. – Mark Reed Aug 27 '20 at 12:42
8

This will remove the comma from the last word.

listed[-1] = listed[-1][:-1]

The way it works is listed[-1] gets the last value from the list. We use = to assign this value to listed[-1][:-1], which is a slice of the last word from the list with everything before the last character.

Implemented as shown below:

def lister():
    listed = []
    while True:
        print('type what you want to be listed or type nothing to exit')
        inputted = input()
        if inputted == '':
            break
        else:
            listed.append(inputted+',')
    listed.insert(-1, 'and')
    listed[-1] = listed[-1][:-1]
    for i in listed:
        print(i, end=' ')
lister()
Will Da Silva
  • 6,386
  • 2
  • 27
  • 52
4

Modifying your code a little bit...

def lister():
    listed = []
    while True:
        print('type what you want to be listed or type nothing to exit')
        inputted = input()
        if inputted == '':
            break
        else:
            listed.append(inputted) # removed the comma here

    print(', '.join(listed[:-2]) + ' and ' + listed[-1])  #using the join operator, and appending and xxx at the end
lister()
cs95
  • 379,657
  • 97
  • 704
  • 746
3
listed[-1] = listed[-1][:-1]

This will truncate the final character of the final string in listed.

Prune
  • 76,765
  • 14
  • 60
  • 81
2

Lots of ways to do it, but how about this?

# listed[-1] is the last element of the list
# rstrip removes matching characters from the end of the string
listed[-1] = listed[-1].rstrip(',')
listed.insert(-1, 'and')
for i in listed:
    print(i, end=' ')

You'll still be printing a space at the end of the line, but I guess you won't see it and thus won't care. :-)

user94559
  • 59,196
  • 6
  • 103
  • 103
2

I would do it using an f-string (a formatted string literal, available in Python 3.6+):

def grammatically_join(words, oxford_comma=False):
    if len(words) == 0:
        return ""
    if len(words) == 1:
        return listed[0]
    if len(words) == 2:
        return f"{listed[0]} and {listed[1]}"
    return f'{", ".join(words[:-1])}{"," if oxford_comma else ""} and {words[-1]}'

If you don't need the Oxford comma, then you can simplify the code and remove the extra edge case for len(words) == 2:

def grammatically_join(words):
    if len(words) == 0:
        return ""
    if len(words) == 1:
        return listed[0]
    return f'{", ".join(words[:-1])} and {words[-1]}'
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
0

Assuming you're okay with a comma if there are only two items, this is fairly compact:

def commaize(items):
    return ', and'.join(', '.join(items).rsplit(',', 1))

Which behaves like this:

>>> commaize([])
''
>>> commaize(['apples'])
'apples'
>>> commaize(['apples', 'bananas'])
'apples, and bananas'
>>> commaize(['apples', 'bananas', 'tofu', 'cats'])
'apples, bananas, tofu, and cats'
Dan
  • 1
0

I solve this problem by using inflect package and p.join()

import inflect
p = inflect.engine()
nameList = []
while True:
    try:
        names = input("Name: ")
        nameList.append(names)

    except EOFError:
        break


lastStr = p.join(nameList , ",")
print("\r", lastStr)