Grammatically correct human readable string from list (with Oxford comma)

Viewed 866

I want a grammatically correct human-readable string representation of a list. For example, the list ['A', 2, None, 'B,B', 'C,C,C'] should return the string A, 2, None, B,B, and C,C,C. This contrived example is somewhat necessary. Note that the Oxford comma is relevant for this question.

I tried ', '.join(seq) but this doesn't produce the expected result for the aforementioned example.

Note the preexisting similar questions:

4 Answers

This function works by handling small lists differently than larger lists.

from typing import Any, List

def readable_list(seq: List[Any]) -> str:
    """Return a grammatically correct human readable string (with an Oxford comma)."""
    # Ref: https://stackoverflow.com/a/53981846/
    seq = [str(s) for s in seq]
    if len(seq) < 3:
        return ' and '.join(seq)
    return ', '.join(seq[:-1]) + ', and ' + seq[-1]

Usage examples:

readable_list([])
''

readable_list(['A'])
'A'

readable_list(['A', 2])
'A and 2'

readable_list(['A', None, 'C'])
'A, None, and C'

readable_list(['A', 'B,B', 'C,C,C'])
'A, B,B, and C,C,C'

readable_list(['A', 'B', 'C', 'D'])
'A, B, C, and D'

You can also use unpacking for a slightly cleaner solution:

def readable_list(_s):
  if len(_s) < 3:
    return ' and '.join(map(str, _s))
  *a, b = _s
  return f"{', '.join(map(str, a))}, and {b}"

vals = [[], ['A'], ['A', 2], ['A', None, 'C'], ['A', 'B,B', 'C,C,C'], ['A', 'B', 'C', 'D']]
print([readable_list(i) for i in vals])

Output:

['', 'A', 'A and 2', 'A, None, and C', 'A, B,B, and C,C,C', 'A, B, C, and D']

I got really stubborn and I really wanted to figure out a one-liner solution.

"{} and {}".format(seq[0], seq[1]) if len(seq)==2 else ', '.join([str(x) if (y < len(seq)-1 or len(seq)<=1) else "and {}".format(str(x)) for x, y in zip(seq, range(len(seq)))])

I think this one does the trick. And I think the problem is also more complicated than I thought to be solved with a non-ugly one-liner.

Based on the accepted answer for the thread you linked to, here's a one-liner that takes an optional argument for whether to use an Oxford comma or not.

from typing import List

def list_items_in_english(l: List[str], oxford_comma: bool = True) -> str:
    """
    Produce a list of the items formatted as they would be in an English sentence.
    So one item returns just the item, passing two items returns "item1 and item2" and
    three returns "item1, item2, and item3" with an optional Oxford comma.
    """
    return ", ".join(l[:-2] + [((oxford_comma and len(l) != 2) * ',' + " and ").join(l[-2:])])
Related