In [1]:
from __future__ import print_function
import cStringIO

In [2]:
import inflect
p = inflect.engine()

In [3]:
# This works fine, but I hate typing those numbers.
gifts = [
    'a partridge in a pear tree',
    'two turtle doves',
    'three french hens',
    ]

In [4]:
unnumbered_gifts = [
    'partridge in a pear tree',
    'turtle doves',
    'french hens',
    'calling birds',
    'gold rings',
    'geese-a-laying',
    'swans-a-swimming',
    'maids-a-milking',
    'ladies dancing',
    'lords-a-leaping',
    'pipers piping',
    'drummers drumming',
    ]

In [5]:
# This works, but the if/else is too complex, too ugly.
gifts = [
    ' '.join([(p.number_to_words(i) if i > 1 else 'a'), gift])
    for i, gift in enumerate(unnumbered_gifts, 1)]
gifts


Out[5]:
['a partridge in a pear tree',
 'two turtle doves',
 'three french hens',
 'four calling birds',
 'five gold rings',
 'six geese-a-laying',
 'seven swans-a-swimming',
 'eight maids-a-milking',
 'nine ladies dancing',
 'ten lords-a-leaping',
 'eleven pipers piping',
 'twelve drummers drumming']

In [6]:
# This is more straightforward, handling the idiomatic 'a' separately.
gifts = [
    p.number_to_words(i) + ' ' + gift
    for i, gift in enumerate(unnumbered_gifts, 1)]
gifts[0] = gifts[0].replace('one', 'a', 1)
gifts


Out[6]:
['a partridge in a pear tree',
 'two turtle doves',
 'three french hens',
 'four calling birds',
 'five gold rings',
 'six geese-a-laying',
 'seven swans-a-swimming',
 'eight maids-a-milking',
 'nine ladies dancing',
 'ten lords-a-leaping',
 'eleven pipers piping',
 'twelve drummers drumming']

In [7]:
def combine(items, separator, last_separator):
    terms_before_last = separator.join(items[:-1])
    if terms_before_last:
        return last_separator.join([terms_before_last, items[-1]])
    else:
        return items[-1]
    
if False:
    print(combine(['one', 'two', 'three'], '\n', '\nand '))
    print(combine(['uno', 'dos'], '\n', '\nand '))
    print(combine(['one'], '\n', '\nand '))

In [8]:
if True:
    gifts = gifts[:3]

In [9]:
gifts.reverse()

In [10]:
# The 1s in the range argments are a bit awkward.
for n in range(1, len(gifts)+1):
    nth = p.number_to_words(p.ordinal(n))
    print('On the %s day of Christmas, my true love sent to me' % nth)
    print(combine(gifts[-n:], '\n', '\nand ') + '.')
    print()


On the first day of Christmas, my true love sent to me
a partridge in a pear tree.

On the second day of Christmas, my true love sent to me
two turtle doves
and a partridge in a pear tree.

On the third day of Christmas, my true love sent to me
three french hens
two turtle doves
and a partridge in a pear tree.


In [11]:
# The n += 1 is straightforward now, but the range(len()) is awkward.
for n in range(len(gifts)):
    n += 1
    nth = p.number_to_words(p.ordinal(n))
    print('On the %s day of Christmas, my true love sent to me' % nth)
    print(combine(gifts[-n:], '\n', '\nand ') + '.')
    print()


On the first day of Christmas, my true love sent to me
a partridge in a pear tree.

On the second day of Christmas, my true love sent to me
two turtle doves
and a partridge in a pear tree.

On the third day of Christmas, my true love sent to me
three french hens
two turtle doves
and a partridge in a pear tree.


In [12]:
# That I ignore the iterated gift is awkward.
# The enumeration is nice.
for n, dont_care in enumerate(gifts, 1):
    nth = p.number_to_words(p.ordinal(n))
    print('On the %s day of Christmas, my true love sent to me' % nth)
    print(combine(gifts[-n:], '\n', '\nand ') + '.')
    print()


On the first day of Christmas, my true love sent to me
a partridge in a pear tree.

On the second day of Christmas, my true love sent to me
two turtle doves
and a partridge in a pear tree.

On the third day of Christmas, my true love sent to me
three french hens
two turtle doves
and a partridge in a pear tree.


In [13]:
# For TDD, avoid I/O in function.
def make_lyrics():
    output = cStringIO.StringIO()
    for n, dont_care in enumerate(gifts, 1):
        nth = p.number_to_words(p.ordinal(n))
        print('On the %s day of Christmas, my true love sent to me' % nth, file=output)
        print(combine(gifts[-n:], '\n', '\nand ') + '.', file=output)
        print(file=output)
    return output.getvalue()

In [14]:
# For TDD, avoid I/O in function.
def make_lyrics():
    lyrics = []
    for n, dont_care in enumerate(gifts, 1):
        nth = p.number_to_words(p.ordinal(n))
        lyrics.extend([
            'On the %s day of Christmas, my true love sent to me' % nth,
            combine(gifts[-n:], '\n', '\nand ') + '.',
            '',
            ])
    return '\n'.join(lyrics)

In [15]:
print(make_lyrics())


On the first day of Christmas, my true love sent to me
a partridge in a pear tree.

On the second day of Christmas, my true love sent to me
two turtle doves
and a partridge in a pear tree.

On the third day of Christmas, my true love sent to me
three french hens
two turtle doves
and a partridge in a pear tree.


In [16]:
with open('12doc.lyrics', 'w') as file:
    file.write(make_lyrics())
!! espeak <12doc.lyrics 2>/dev/null


Out[16]:
[]