Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to efficiently join a list with commas and add "and" before the last element

Tags:

python

list

I've been going through Automatetheboringstuff and came across a challenge called Comma Code (end of chapter 4). You have to write a function that takes a list and print out a string, joining the elements with a comma and adding "and" before the last element.

Keeping in mind that I am fairly new to python, or programing for that matter, it was still a manageable task, but the output had a comma before the inserted "and". So I revised the code to clean that up. This is my code:

def comma_separator(someList):
    """The function comma_separator takes a list and joins it
       into a string with (", ") and adds " and " before the last value."""

    if type(someList) is list and bool(someList) is True:
        return ", ".join(someList[:-1]) + " and " + someList[-1]
    else:
        print("Pass a non-empty list as the argument.")

Is there a better way to do it? Is there a module that can do this?

like image 227
dasdachs Avatar asked Aug 14 '15 11:08

dasdachs


2 Answers

You'll have to account for the case where you have just the one element:

def comma_separator(sequence):
    if not sequence:
        return ''
    if len(sequence) == 1:
        return sequence[0]
    return '{} and {}'.format(', '.join(sequence[:-1]), sequence[-1])

Note that bool(sequence) is True is a very elaborate way of testing for a non-empty list; simply using if sequence: is enough as the if statement looks for boolean truth already.

Arguably, calling the function with anything other than a sequence (something that can be indexed and has a length) should just result in an exception. You generally do not test for types in functions like these. If you did have to test for a type, use isinstance(sequence, list) to at least allow for subclasses.

I'd also make it an error to pass in an empty list. You could turn that exception into a ValueError:

def comma_separator(sequence):
    if len(sequence) > 1:
        return '{} and {}'.format(', '.join(sequence[:-1]), sequence[-1])
    try:
        return sequence[0]
    except IndexError:
        raise ValueError('Must pass in at least one element')

Demo of the latter:

>>> def comma_separator(sequence):
...     if len(sequence) > 1:
...         return '{} and {}'.format(', '.join(sequence[:-1]), sequence[-1])
...     try:
...         return sequence[0]
...     except IndexError:
...         raise ValueError('Must pass in at least one element')
... 
>>> comma_separator(['foo', 'bar', 'baz'])
'foo, bar and baz'
>>> comma_separator(['foo', 'bar'])
'foo and bar'
>>> comma_separator(['foo'])
'foo'
>>> comma_separator([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in comma_separator
ValueError: Must pass in at least one element
like image 127
Martijn Pieters Avatar answered Oct 22 '22 13:10

Martijn Pieters


As an alternative to Martijn's readable answer, you can use two str.join(); an inner join that joins with commas all but the last item in the given sequence, and an outer join that joins with and the result of the inner join with the last item. It's a one-liner:

def comma_separator(seq):
    return ' and '.join([', '.join(seq[:-1]), seq[-1]] if len(seq) > 2 else seq)

>>> comma_separator([])
''
>>> comma_separator(['a'])
'a'
>>> comma_separator(['a', 'b'])
'a and b'
>>> comma_separator(['a', 'b', 'c'])
'a, b and c'
>>> comma_separator(['a', 'b', 'c', 'd'])
'a, b, c and d'

This does not treat an empty sequence as an error but instead returns an empty string.

like image 39
mhawke Avatar answered Oct 22 '22 13:10

mhawke