Twelve Days of X-mas in Python Printing the lyrics of "Twelve Days of Christmas" seems to be a fairly popular programming challenge.

As I've successfully attempted it, I was wondering if the code can be simplified even further (say no loops)?

Any feedback in regards to style and/or logic would be greatly appreciated.

#!/usr/bin/env python3


verses = {
        1: ["first", "and a Partridge in a Pear Tree."],
        2: ["second", "two Turtle Doves, "],
        3: ["third", "three French Hens, "],
        4: ["fourth", "four Calling Birds, "],
        5: ["fifth", "five Gold Rings, "],
        6: ["sixth", "six Geese-a-Laying, "],
        7: ["seventh", "seven Swans-a-Swimming, "],
        8: ["eighth", "eight Maids-a-Milking, "],
        9: ["ninth", "nine Ladies Dancing, "],
        10: ["tenth", "ten Lords-a-Leaping, "],
        11: ["eleventh", "eleven Pipers Piping, "],
        12: ["twelfth", "twelve Drummers Drumming, "]
        }


def get_verses(line):
    verse = f"On the {verses[line][0]} day of Christmas " + \
        "my true love gave to me: "
    for i in range(line, 0, -1):
        verse += f"{verses[i][1]}"
    return verse.replace("and ", "") if line == 1 else verse


def recite(start_verse, end_verse):
    return "\n".join([get_verses(line) for line 
            in range(start_verse, end_verse + 1)])


print(recite(1, 12))
You're storing your verses in a format that's a little more complicated than necessary. If you have a dictionary that you're keying by a continuous range of number (and you don't need to handle removals), you likely shouldn't be using a dictionary. Just use a list here:

verses = [["first", "and a Partridge in a Pear Tree."],
          ["second", "two Turtle Doves, "],
          ["third", "three French Hens, "],
          ["fourth", "four Calling Birds, "],
          ["fifth", "five Gold Rings, "],
          ["sixth", "six Geese-a-Laying, "],
          ["seventh", "seven Swans-a-Swimming, "],
          ["eighth", "eight Maids-a-Milking, "],
          ["ninth", "nine Ladies Dancing, "],
          ["tenth", "ten Lords-a-Leaping, "],
          ["eleventh", "eleven Pipers Piping, "],
          ["twelfth", "twelve Drummers Drumming, "]]


If you think about, lists are already indexed by number. Now you don't need to manually increment the number keys if you add a new verse. The change just requires tweaking some numbers to account for 0-based indexing (which I think is more appropriate anyways):

def get_verses(line):
    verse = f"On the {verses[line][0]} day of Christmas, my true love gave to me: "
    for i in range(line, -1, -1):  # -1 stop
        verse += f"{verses[i][1]}"
    return verse.replace("and ", "") if line == 0 else verse  # line 0 now

def recite(start_verse, end_verse):
    return "\n".join([get_verses(line)
                      for line in range(start_verse, end_verse + 1)])


I also changed how you're splitting up the last list comprehension. If you wanted to split it, I would keep the iteration part all on one line, and get_verses on another. I also don't think the line in get_verses needed to be split. It isn't that line.

I'd still change some more things though:


In get_verses, you named the parameter line. It isn't a line though, it's a line number. I'd change it to, at the very least, line_n.
You don't need a strict list comprehension in recite. You can get rid of a pair of brackets by making it a generator expression that uses join's brackets.
The imperative loop in get_verse could be changed to a joined generator expression.


I ended up with this:

verses = [["first", "and a Partridge in a Pear Tree."],
          ["second", "two Turtle Doves, "],
          ["third", "three French Hens, "],
          ["fourth", "four Calling Birds, "],
          ["fifth", "five Gold Rings, "],
          ["sixth", "six Geese-a-Laying, "],
          ["seventh", "seven Swans-a-Swimming, "],
          ["eighth", "eight Maids-a-Milking, "],
          ["ninth", "nine Ladies Dancing, "],
          ["tenth", "ten Lords-a-Leaping, "],
          ["eleventh", "eleven Pipers Piping, "],
          ["twelfth", "twelve Drummers Drumming, "]]

def get_verses(line):
    opener = f"On the {verses[line][0]} day of Christmas, my true love gave to me: "
    verse = "".join(str(verses[i][1]) for i in range(line, -1, -1))
    return opener + (verse.replace("and ", "") if line == 0 else verse)

def recite(start_verse, end_verse):
    return "\n".join(get_verses(line_n)
                     for line_n in range(start_verse, end_verse + 1))

print(recite(0, 11))

On the first day of Christmas, my true love gave to me: a Partridge in a Pear Tree.
On the second day of Christmas, my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.
On the third day of Christmas, my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the fourth day of Christmas, my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the fifth day of Christmas, my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the sixth day of Christmas, my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the seventh day of Christmas, my true love gave to me: seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the eighth day of Christmas, my true love gave to me: eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the ninth day of Christmas, my true love gave to me: nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the tenth day of Christmas, my true love gave to me: ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the eleventh day of Christmas, my true love gave to me: eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.
On the twelfth day of Christmas, my true love gave to me: twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.




Some further things though:


The verse.replace("and ", "") if line == 0 else verse seems like a code smell. It would probably be cleaner to not have "and" in the saved verse, and to add it when needed, instead of removing it in the special case.
Instead of using range(line, -1, -1) to get reversed indices, you could probably use reversed and islice from itertools to get a sliced reversed iterator of the verses. That way you wouldn't need to index the list.


I'd elaborate, but I need to get going. Good luck.