daisyl
daisyl

Reputation: 441

How to join list in Python but make the last separator different?

I'm trying to turn a list into separated strings joined with an ampersand if there are only two items, or commas and an ampersand between the last two e.g.

Jones & Ben
Jim, Jack & James

I currently have this:

pa = ' & '.join(listauthors[search])

and don't know how to make sort out the comma/ampersand issue. Beginner so a full explanation would be appreciated.

Upvotes: 44

Views: 27026

Answers (12)

Sash Sinha
Sash Sinha

Reputation: 22370

You could use formatted-join1 to handle joining lists with commas and an ampersand before the final element:

>>> from formatted_join import formatted_join
>>> authors = ['Jim', 'Jack', 'James']
>>> formatted_join(
...   items=authors,
...   separator=', ',
...   final_separator=' & ',
...   use_penultimate_separator=False
... )
'Jim, Jack & James'

Installation

pip install formatted-join

More usage examples

By default, formatted_join uses ', ' as the main separator and ' and ' as the final separator, and will include the penultimate separator only if there are more than two items in the sequence:

>>> formatted_join(['Hello', 'World'])
'Hello and World'
>>> formatted_join(('One', 'Two', 'Three'))
'One, Two, and Three'
>>> formatted_join(['Apple'])
'Apple'

Additionally the package also provides a few convenience functions for common joining patterns taking inspiration from the Intl.ListFormat API that is available in Javascript (note localized conjunctions and disjunctions have only been added for english and german currently):

formatted_join_conjunction — Joins items using a comma and localized* “and” before the last item.
>>> from formatted_join import formatted_join_conjunction
>>> formatted_join_conjunction(['One', 'Two', 'Three'])
'One, Two, and Three'
>>> formatted_join_conjunction(['One', 'Two'])
'One and Two'
>>> formatted_join_conjunction(['One'])
'One'
>>> formatted_join_conjunction(['Motorcycle', 'Bus', 'Car'], language='en')
'Motorcycle, Bus, and Car'
>>> formatted_join_conjunction(['Motorcycle', 'Bus', 'Car'], language='de')
'Motorcycle, Bus und Car'
formatted_join_disjunction — Joins items using a comma and localized* “or” before the last item.
>>> from formatted_join import formatted_join_disjunction
>>> formatted_join_disjunction(['X', 'Y', 'Z'])
'X, Y, or Z'
>>> formatted_join_disjunction(['Motorcycle', 'Bus', 'Car'], language='en')
'Motorcycle, Bus, or Car'
>>> formatted_join_disjunction(['Motorcycle', 'Bus', 'Car'], language='de')
'Motorcycle, Bus oder Car'
formatted_join_unit — Joins items with commas only (no distinct final separator).
>>> from formatted_join import formatted_join_unit
>>> formatted_join_unit(['A', 'B', 'C'])
'A, B, C'
formatted_join_narrow — Joins items with a single space separating them.
>>> from formatted_join import formatted_join_narrow
>>> formatted_join_narrow(['A', 'B', 'C'])
'A B C'

* Supported languages for formatted_join_[conjunction|disjunction]:
language and or separator has penultimate separator
en and or ', ' yes
de und oder ', ' no

1 Disclosure: I am the author of formatted-join. The source code is avaliable here.

Upvotes: 1

Dane Iracleous
Dane Iracleous

Reputation: 1759

Here's one that supports the following:

  • Any number of items
  • Any data type for each item
  • Toggling of the oxford comma
  • Specification of the last delimiter
def format_list(items, last_delimiter='and', use_oxford_comma=True):
    if not items:
        return ''
    elif len(items) == 1:
        return str(items[0])
    elif len(items) == 2:
        return f'{items[0]} {last_delimiter} {items[1]}'
    curated_last_delimiter = f', {last_delimiter} ' if use_oxford_comma else f' {last_delimiter} '
    return f'{", ".join(map(str, items[:-1]))}{curated_last_delimiter}{items[-1]}'


assert format_list(['Hi', 12, True]) == 'Hi, 12, and True'

assert format_list(['Hi', 12, True], last_delimiter='&') == 'Hi, 12, & True'

assert format_list(['Hi', 12, True], use_oxford_comma=False) == 'Hi, 12 and True'

assert format_list(['Hi', 12]) == 'Hi and 12'

assert format_list(['Hi']) == 'Hi'

assert format_list([]) == ''

assert format_list(
    ['bleu', 'rouge', 'vert', 'jaune'],
    last_delimiter='et',
    use_oxford_comma=False,
) == 'bleu, rouge, vert et jaune'

Upvotes: 0

404rorre
404rorre

Reputation: 127

My Idea is as following I dug a bit deeper and tried to make it more flexible as requested.

Concept: Use dictionary to create a person with an attached list of friends.

Advantage: Can contain/store multiple persons with regarding friends. :)

How it works:

  1. The "for" loop iterates through the number of keys stored inside the dictionary.
  2. The key (here the entered person) is then put in the first string of the loop with the argument end="" which tells the print function to operate in the same line.
  3. After that a second variable is implemented which is given the length of the key-value by requesting the stored variable of the dictionary behind the key. We need that value later to determine the last entry of the list. ;) (Note: with every for loop it gets a new value)
  4. In the next for loop we will iterate through all friends on the list by pointing to the key-value (here the list) through the key from the outer for loop iteration.
  5. Inside this for loop are three mechanism intertwined:

5.a) The value n_friend decrements by one with each cycle.

5.b) As long as n_friend does not reach zero every friend will be printed with the argument end="" as explained in point 2.

5.c) After n_friend reaches zero the if/else conditional statment will switch to else and will print the last iteration of the for loop with the requested "and...[enter friend here].". This is without a end="" so the next print will switch into a new line.

  1. Continue the cycle by 1 again.

.break for code.

friends_list={
    "person_1":['Jack', 'John','Jane'],
    "person_2":['Luke', 'Darth','Han','Wednesday']
}

for person in friends_list:
    print(f"{person} likes following persons: ",end="")
    n_friend = len(friends_list[person])
    for friend in friends_list[person]:
        n_friend -= 1
        if n_friend > 0:
            print(friend, end=" ")
        else:
            print(f"and {friend}.")

Upvotes: 0

maxandron
maxandron

Reputation: 1660

Just a more grammatically correct example :)

def list_of_items_to_grammatical_text(items):
    if len(items) <= 1:
        return ''.join(items)
    if len(items) == 2:
        return ' and '.join(items)
    return '{}, and {}'.format(', '.join(items[:-1]), items[-1])

Output example:

l1 = []
l2 = ["one"]
l3 = ["one", "two"]
l4 = ["one", "two", "three"]

list_of_items_to_grammatical_text(l1)
Out: ''

list_of_items_to_grammatical_text(l2)
Out: 'one'

list_of_items_to_grammatical_text(l3)
Out: 'one and two'

list_of_items_to_grammatical_text(l4)
Out: 'one, two, and three'

Upvotes: 3

Saur
Saur

Reputation: 39

Here is a one line example that handles all the edge cases (empty list, one entry, two entries):

' & '.join(filter(None, [', '.join(my_list[:-1])] + my_list[-1:]))

The filter() function is used to filter out the empty entries that happens when my_list is empty or only has one entry.

Upvotes: 3

Levan Gharibashvili
Levan Gharibashvili

Reputation: 1

Here's a simple one that also works for empty or 1 element lists:

' and '.join([', '.join(mylist[:-1])]+mylist[-1:])

The reason it works is that for empty lists both [:-1] and [-1:] give us an empty list again

Upvotes: 0

Kasravnd
Kasravnd

Reputation: 107297

You can simply use Indexng and F-strings (Python-3.6+):

In [1]: l=['Jim','Dave','James','Laura','Kasra']                                                                                                                                                            

In [3]: ', '.join(l[:-1]) + f' & {l[-1]}'                                                                                                                                                                      

Out[3]: 'Jim, Dave, James, Laura & Kasra'

Upvotes: 3

Shashank
Shashank

Reputation: 13869

('{}, '*(len(authors)-2) + '{} & '*(len(authors)>1) + '{}').format(*authors)

This solution can handle a list of authors of length > 0, though it can be modified to handle 0-length lists as well. The idea is to first create a format string that we can format by unpacking list. This solution avoids slicing the list so it should be fairly efficient for large lists of authors.

First we concatenate '{}, ' for every additional author beyond two authors. Then we concatenate '{} & ' if there are two or more authors. Finally we append '{}' for the last author, but this subexpression can be '{}'*(len(authors)>0) instead if we wish to be able to handle an empty list of authors. Finally, we format our completed string by unpacking the elements of the list using the * unpacking syntax.

If you don't need a one-liner, here is the code in an efficient function form.

def format_authors(authors):
    n = len(authors)
    if n > 1:
        return ('{}, '*(n-2) + '{} & {}').format(*authors)
    elif n > 0:
        return authors[0]
    else:
        return ''

This can handle a list of authors of any length.

Upvotes: 4

Deacon
Deacon

Reputation: 3803

It looks like while I was working on my answer, someone may have beaten me to the punch with a similar one. Here's mine for comparison. Note that this also handles cases of 0, 1, or 2 members in the list.

# Python 3.x, should also work with Python 2.x.
def my_join(my_list):
    x = len(my_list)
    if x > 2:
        s = ', & '.join([', '.join(my_list[:-1]), my_list[-1]])
    elif x == 2:
        s = ' & '.join(my_list)
    elif x == 1:
        s = my_list[0]
    else:
        s = ''
    return s

assert my_join(['Jim', 'Jack', 'John']) == 'Jim, Jack, & John'
assert my_join(['Jim', 'Jack']) == 'Jim & Jack'
assert my_join(['Jim',]) == 'Jim'
assert my_join([]) == ''

Upvotes: 1

Saksham Varma
Saksham Varma

Reputation: 2130

One liner. Just concatenate all but the last element with , as the delimiter. Then just append & and then the last element finally to the end.

print ', '.join(lst[:-1]) + ' & ' + lst[-1]

If you wish to handle empty lists or such:

if len(lst) > 1:
    print ', '.join(lst[:-1]) + ' & ' + lst[-1]
elif len(lst) == 1:
    print lst[0]

Upvotes: 13

John Kugelman
John Kugelman

Reputation: 361730

You could break this up into two joins. Join all but the last item with ", ". Then join this string and the last item with " & ".

all_but_last = ', '.join(authors[:-1])
last = authors[-1]

' & '.join([all_but_last, last])

Note: This doesn't deal with edge cases, such as when authors is empty or has only one element.

Upvotes: 16

Joran Beasley
Joran Beasley

Reputation: 113988

"&".join([",".join(my_list[:-1]),my_list[-1]])

I would think would work

or maybe just

",".join(my_list[:-1]) +"&"+my_list[-1]

to handle edge cases where only 2 items you could

"&".join([",".join(my_list[:-1]),my_list[-1]] if len(my_list) > 2 else my_list)

Upvotes: 46

Related Questions