Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #17280 > unrolled thread

How to generate "a, b, c, and d"?

Started byRoy Smith <roy@panix.com>
First post2011-12-15 08:48 -0800
Last post2011-12-16 00:26 -0500
Articles 12 — 7 participants

Back to article view | Back to comp.lang.python


Contents

  How to generate "a, b, c, and d"? Roy Smith <roy@panix.com> - 2011-12-15 08:48 -0800
    Re: How to generate "a, b, c, and d"? MRAB <python@mrabarnett.plus.com> - 2011-12-15 17:27 +0000
    Re: How to generate "a, b, c, and d"? Tim Chase <python.list@tim.thechases.com> - 2011-12-15 12:01 -0600
    Re: How to generate "a, b, c, and d"? Ethan Furman <ethan@stoneleaf.us> - 2011-12-15 10:19 -0800
    Re: How to generate "a, b, c, and d"? Tim Chase <python.list@tim.thechases.com> - 2011-12-15 12:51 -0600
      Re: How to generate "a, b, c, and d"? Roy Smith <roy@panix.com> - 2011-12-15 11:01 -0800
      Re: How to generate "a, b, c, and d"? Roy Smith <roy@panix.com> - 2011-12-15 11:01 -0800
    Re: How to generate "a, b, c, and d"? MRAB <python@mrabarnett.plus.com> - 2011-12-15 19:27 +0000
    Re: How to generate "a, b, c, and d"? Ian Kelly <ian.g.kelly@gmail.com> - 2011-12-15 14:22 -0700
    Re: How to generate "a, b, c, and d"? Terry Reedy <tjreedy@udel.edu> - 2011-12-15 19:57 -0500
    Re: How to generate "a, b, c, and d"? Chris Angelico <rosuav@gmail.com> - 2011-12-16 13:42 +1100
    Re: How to generate "a, b, c, and d"? Terry Reedy <tjreedy@udel.edu> - 2011-12-16 00:26 -0500

#17280 — How to generate "a, b, c, and d"?

FromRoy Smith <roy@panix.com>
Date2011-12-15 08:48 -0800
SubjectHow to generate "a, b, c, and d"?
Message-ID<9393353.282.1323967703697.JavaMail.geo-discussion-forums@vbyc2>
I've got a list, ['a', 'b', 'c', 'd'].  I want to generate the string, "a, b, c, and d" (I'll settle for no comma after 'c').  Is there some standard way to do this, handling all the special cases?

[] ==> ''
['a'] ==> 'a'
['a', 'b'] ==> 'a and b'
['a', 'b', 'c', 'd'] ==> 'a, b, and c'

It seems like the kind of thing django.contrib.humanize would handle, but alas, it doesn't.

[toc] | [next] | [standalone]


#17283

FromMRAB <python@mrabarnett.plus.com>
Date2011-12-15 17:27 +0000
Message-ID<mailman.3677.1323970009.27778.python-list@python.org>
In reply to#17280
On 15/12/2011 16:48, Roy Smith wrote:
> I've got a list, ['a', 'b', 'c', 'd'].  I want to generate the string, "a, b, c, and d" (I'll settle for no comma after 'c').  Is there some standard way to do this, handling all the special cases?
>
> [] ==>  ''
> ['a'] ==>  'a'
> ['a', 'b'] ==>  'a and b'
> ['a', 'b', 'c', 'd'] ==>  'a, b, and c'
>
> It seems like the kind of thing django.contrib.humanize would handle, but alas, it doesn't.

How about this:

def and_list(items):
     if len(items) <= 2:
         return " and ".join(items)

     return ", ".join(items[ : -1]) + ", and " + items[-1]

print(and_list([]))
print(and_list(['a']))
print(and_list(['a', 'b']))
print(and_list(['a', 'b', 'c']))
print(and_list(['a', 'b', 'c', 'd']))

[toc] | [prev] | [next] | [standalone]


#17290

FromTim Chase <python.list@tim.thechases.com>
Date2011-12-15 12:01 -0600
Message-ID<mailman.3680.1323972100.27778.python-list@python.org>
In reply to#17280
On 12/15/11 10:48, Roy Smith wrote:
> I've got a list, ['a', 'b', 'c', 'd'].  I want to generate the string, "a, b, c, and d" (I'll settle for no comma after 'c').  Is there some standard way to do this, handling all the special cases?
>
> [] ==>  ''
> ['a'] ==>  'a'
> ['a', 'b'] ==>  'a and b'
> ['a', 'b', 'c', 'd'] ==>  'a, b, and c'
>
> It seems like the kind of thing django.contrib.humanize would handle, but alas, it doesn't.

If you have a list, it's pretty easy as MRAB suggests.  For 
arbitrary iterators, it's a bit more complex.  Especially with 
the odd edge-case of 2 items where there's no comma before the 
conjunction (where >2 has the comma before the conjunction).  If 
you were willing to forgo the Oxford comma, it would tidy up the 
code a bit.  Sample code below

-tkc

def gen_list(i, conjunction="and"):
     i = iter(i)
     first = i.next()
     try:
         prev = i.next()
     except StopIteration:
         yield first
     else:
         more_than_two = False
         for item in i:
             if not more_than_two: yield first
             yield prev
             prev = item
             more_than_two = True
         if more_than_two:
             yield "%s %s" % (conjunction, prev)
         else:
             yield "%s %s %s" % (first, conjunction, prev)

def listify(lst, conjunction="and"):
     return ', '.join(gen_list(lst, conjunction))

for test, expected in (
         ([], ''),
         (['a'], 'a'),
         (['a', 'b'], 'a and b'),
         (['a', 'b', 'c'], 'a, b, and c'),
         (['a', 'b', 'c', 'd'], 'a, b, c, and d'),
         ):
     result = listify(test)
     print "%r -> %r (got %r) %s" % (
         test, expected, result,
         result == expected and "PASSED" or "FAILED"
         )

[toc] | [prev] | [next] | [standalone]


#17302

FromEthan Furman <ethan@stoneleaf.us>
Date2011-12-15 10:19 -0800
Message-ID<mailman.3689.1323974099.27778.python-list@python.org>
In reply to#17280
Tim Chase wrote:
> On 12/15/11 10:48, Roy Smith wrote:
>> I've got a list, ['a', 'b', 'c', 'd'].  I want to generate the string, 
>> "a, b, c, and d" (I'll settle for no comma after 'c').  Is there some 
>> standard way to do this, handling all the special cases?
>>
>> [] ==>  ''
>> ['a'] ==>  'a'
>> ['a', 'b'] ==>  'a and b'
>> ['a', 'b', 'c', 'd'] ==>  'a, b, and c'
>>
>> It seems like the kind of thing django.contrib.humanize would handle, 
>> but alas, it doesn't.
> 
> If you have a list, it's pretty easy as MRAB suggests.  For arbitrary 
> iterators, it's a bit more complex.  Especially with the odd edge-case 
> of 2 items where there's no comma before the conjunction (where >2 has 
> the comma before the conjunction).  If you were willing to forgo the 
> Oxford comma, it would tidy up the code a bit.  Sample code below

<snip>

Why go through all that instead of just converting the iterator into a 
list at the beginning of MRAB's solution and then running with it?

~Ethan~

[toc] | [prev] | [next] | [standalone]


#17305

FromTim Chase <python.list@tim.thechases.com>
Date2011-12-15 12:51 -0600
Message-ID<mailman.3690.1323975113.27778.python-list@python.org>
In reply to#17280
On 12/15/11 12:19, Ethan Furman wrote:
> Tim Chase wrote:
>> On 12/15/11 10:48, Roy Smith wrote:
>>> I've got a list, ['a', 'b', 'c', 'd'].  I want to generate the string,
>>> "a, b, c, and d" (I'll settle for no comma after 'c').  Is there some
>>> standard way to do this, handling all the special cases?
>>
>> If you have a list, it's pretty easy as MRAB suggests.  For arbitrary
>> iterators, it's a bit more complex.  Especially with the odd edge-case
>> of 2 items where there's no comma before the conjunction (where>2 has
>> the comma before the conjunction).  If you were willing to forgo the
>> Oxford comma, it would tidy up the code a bit.
>
> Why go through all that instead of just converting the iterator into a
> list at the beginning of MRAB's solution and then running with it?

For the fun/challenge?  Because you have a REALLY big data source 
that you don't want to keep in memory (in addition the resulting 
string)?

Yeah, for most non-pathological cases, it would make more sense 
to just make it a list and then deal with the 4 cases (no 
elements, one element, 2 elements, and >2 elements) individually.

-tkc


[toc] | [prev] | [next] | [standalone]


#17306

FromRoy Smith <roy@panix.com>
Date2011-12-15 11:01 -0800
Message-ID<mailman.3691.1323975683.27778.python-list@python.org>
In reply to#17305
FWIW, I ended up with:

        n = len(names)
        if n == 0:
            return ''
        if n == 1:
            return names[0]
        pre = ', '.join(names[:-1])
        post = names[-1]
        return '%s, and %s' (pre, post)

the slice-and-join() takes care of both the 2 and >2 element cases at the same time :)

It would be nice if there were some standard way to do this.  I'm sure I've seen something that was essentially a join() that took two delimiters; one for most elements, the other a special-case for the last one.  I can't remember where I saw it.  I'm guessing in some web framework.

[toc] | [prev] | [next] | [standalone]


#17307

FromRoy Smith <roy@panix.com>
Date2011-12-15 11:01 -0800
Message-ID<19419800.394.1323975674835.JavaMail.geo-discussion-forums@vbyc2>
In reply to#17305
FWIW, I ended up with:

        n = len(names)
        if n == 0:
            return ''
        if n == 1:
            return names[0]
        pre = ', '.join(names[:-1])
        post = names[-1]
        return '%s, and %s' (pre, post)

the slice-and-join() takes care of both the 2 and >2 element cases at the same time :)

It would be nice if there were some standard way to do this.  I'm sure I've seen something that was essentially a join() that took two delimiters; one for most elements, the other a special-case for the last one.  I can't remember where I saw it.  I'm guessing in some web framework.

[toc] | [prev] | [next] | [standalone]


#17309

FromMRAB <python@mrabarnett.plus.com>
Date2011-12-15 19:27 +0000
Message-ID<mailman.3694.1323977203.27778.python-list@python.org>
In reply to#17280
On 15/12/2011 18:51, Tim Chase wrote:
> On 12/15/11 12:19, Ethan Furman wrote:
>> Tim Chase wrote:
>>> On 12/15/11 10:48, Roy Smith wrote:
>>>> I've got a list, ['a', 'b', 'c', 'd'].  I want to generate the
>>>> string, "a, b, c, and d" (I'll settle for no comma after 'c').
>>>> Is there some standard way to do this, handling all the special
>>>> cases?
>>>
>>> If you have a list, it's pretty easy as MRAB suggests.  For
>>> arbitrary iterators, it's a bit more complex.  Especially with
>>> the odd edge-case of 2 items where there's no comma before the
>>> conjunction (where>2 has the comma before the conjunction).  If
>>> you were willing to forgo the Oxford comma, it would tidy up the
>>> code a bit.
>>
>> Why go through all that instead of just converting the iterator
>> into a list at the beginning of MRAB's solution and then running
>> with it?
>
> For the fun/challenge?  Because you have a REALLY big data source
> that you don't want to keep in memory (in addition the resulting
> string)?
>
> Yeah, for most non-pathological cases, it would make more sense to
> just make it a list and then deal with the 4 cases (no elements, one
> element, 2 elements, and>2 elements) individually.
>
I was going to question it too, but then I wondered what would happen if
there were a very large number of items and the string would be too big
for memory, for example, writing a list of all the numbers from one to a
billion to a file.

[toc] | [prev] | [next] | [standalone]


#17317

FromIan Kelly <ian.g.kelly@gmail.com>
Date2011-12-15 14:22 -0700
Message-ID<mailman.3704.1323984189.27778.python-list@python.org>
In reply to#17280
On Thu, Dec 15, 2011 at 11:51 AM, Tim Chase
<python.list@tim.thechases.com> wrote:
> For the fun/challenge?  Because you have a REALLY big data source that you
> don't want to keep in memory (in addition the resulting string)?

If you have that much data, then I question why you would want to
build such a large human-readable list in the first place.  Nobody is
going to want to read that no matter how you format it.

[toc] | [prev] | [next] | [standalone]


#17322

FromTerry Reedy <tjreedy@udel.edu>
Date2011-12-15 19:57 -0500
Message-ID<mailman.3708.1323997066.27778.python-list@python.org>
In reply to#17280
On 12/15/2011 12:27 PM, MRAB wrote:
> On 15/12/2011 16:48, Roy Smith wrote:
>> I've got a list, ['a', 'b', 'c', 'd']. I want to generate the string,
>> "a, b, c, and d" (I'll settle for no comma after 'c'). Is there some
>> standard way to do this, handling all the special cases?
>>
>> [] ==> ''
>> ['a'] ==> 'a'
>> ['a', 'b'] ==> 'a and b'
>> ['a', 'b', 'c', 'd'] ==> 'a, b, and c'
>>
>> It seems like the kind of thing django.contrib.humanize would handle,
>> but alas, it doesn't.
>
> How about this:
>
> def and_list(items):
> if len(items) <= 2:
> return " and ".join(items)
>
> return ", ".join(items[ : -1]) + ", and " + items[-1]

To avoid making a slice copy,

   last = items.pop()
   return ", ".join(items) + (", and " + last)

I parenthesized the last two small items to avoid copying the long 
string twice with two appends. Even better is

   items[-1] = "and " + items[-1]
   return ", ".join(items)

so the entire output is created in one operation with no copy.

But I would only mutate the list if I started with
   items = list(iterable)
where iterable was the input, so I was mutating a private copy.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#17325

FromChris Angelico <rosuav@gmail.com>
Date2011-12-16 13:42 +1100
Message-ID<mailman.3710.1324003340.27778.python-list@python.org>
In reply to#17280
On Fri, Dec 16, 2011 at 11:57 AM, Terry Reedy <tjreedy@udel.edu> wrote:
>  items[-1] = "and " + items[-1]
>  return ", ".join(items)

This works only if you're sure there are at least two items, and if
you don't mind two items coming out as "a, and b".

ChrisA

[toc] | [prev] | [next] | [standalone]


#17331

FromTerry Reedy <tjreedy@udel.edu>
Date2011-12-16 00:26 -0500
Message-ID<mailman.3714.1324013192.27778.python-list@python.org>
In reply to#17280
On 12/15/2011 9:42 PM, Chris Angelico wrote:
> On Fri, Dec 16, 2011 at 11:57 AM, Terry Reedy<tjreedy@udel.edu>  wrote:
>>   items[-1] = "and " + items[-1]
>>   return ", ".join(items)
>
> This works only if you're sure there are at least two items, and if
> you don't mind two items coming out as "a, and b".

Please read the context that you removed.
The original second return line and my replacements come after

if len(items) <= 2:
   return " and ".join(items)

-- 
Terry Jan Reedy

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web