4

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:

eyllanesc
  • 190,383
  • 15
  • 87
  • 142
Acumenus
  • 41,481
  • 14
  • 116
  • 107

4 Answers4

12

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'
Acumenus
  • 41,481
  • 14
  • 116
  • 107
2

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']
Ajax1234
  • 58,711
  • 7
  • 46
  • 83
0

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.

Acumenus
  • 41,481
  • 14
  • 116
  • 107
Giacomo Casoni
  • 365
  • 1
  • 12
  • Huh? Why do you say that? I think the command does what I think OP wanted to do – Giacomo Casoni Jun 19 '19 at 13:43
  • Sorry I misunderstood, I retracted the flag. all is fine :) – dippas Jun 19 '19 at 13:44
  • @GiacomoCasoni Given `seq = [1, 2]`, the returned value should grammatically be `'1 and 2'`, but this answer returns `'1, and 2'` instead which is not correct. – Acumenus Jun 19 '19 at 22:48
  • This answer should either be fixed (while maintaining its originality) or be deleted. Being fixed is preferable. – Acumenus Jun 20 '19 at 13:00
  • Oh, I must have misunderstood what the Oxford comma is. Let me have a look if I can change it up – Giacomo Casoni Jun 21 '19 at 08:41
  • You might be curious to take a look at another attempt of a one-liner solution that I wrote based on a different thread: https://stackoverflow.com/a/58692463/1050373 I like that it's shorter but I realize that yours is more explicit. – Christian Nov 04 '19 at 11:33
0

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:])])
Christian
  • 470
  • 6
  • 22