Avatar of MetalKid

MetalKid's solution

to Twelve Days in the C# Track

Published at Jul 13 2018 · 13 comments
Instructions
Test suite
Solution

Note:

This solution was written on an old version of Exercism. The tests below might not correspond to the solution code, and the exercise may have changed since this code was written.

Output the lyrics to 'The Twelve Days of Christmas'.

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.

Hints

  • Try to capture the structure of the song in your code, where you build up the song by composing its parts.

Source

Wikipedia http://en.wikipedia.org/wiki/The_Twelve_Days_of_Christmas_(song)

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

TwelveDaysTest.cs

// This file was auto-generated based on version 1.1.0 of the canonical data.

using Xunit;

public class TwelveDaysTest
{
    [Fact]
    public void First_day_a_partridge_in_a_pear_tree()
    {
        var expected = "On the first day of Christmas my true love gave to me, a Partridge in a Pear Tree.";
        Assert.Equal(expected, TwelveDays.Recite(1));
    }

    [Fact(Skip = "Remove to run test")]
    public void Second_day_two_turtle_doves()
    {
        var expected = "On the second day of Christmas my true love gave to me, two Turtle Doves, and a Partridge in a Pear Tree.";
        Assert.Equal(expected, TwelveDays.Recite(2));
    }

    [Fact(Skip = "Remove to run test")]
    public void Third_day_three_french_hens()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(3));
    }

    [Fact(Skip = "Remove to run test")]
    public void Fourth_day_four_calling_birds()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(4));
    }

    [Fact(Skip = "Remove to run test")]
    public void Fifth_day_five_gold_rings()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(5));
    }

    [Fact(Skip = "Remove to run test")]
    public void Sixth_day_six_geese_a_laying()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(6));
    }

    [Fact(Skip = "Remove to run test")]
    public void Seventh_day_seven_swans_a_swimming()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(7));
    }

    [Fact(Skip = "Remove to run test")]
    public void Eighth_day_eight_maids_a_milking()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(8));
    }

    [Fact(Skip = "Remove to run test")]
    public void Ninth_day_nine_ladies_dancing()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(9));
    }

    [Fact(Skip = "Remove to run test")]
    public void Tenth_day_ten_lords_a_leaping()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(10));
    }

    [Fact(Skip = "Remove to run test")]
    public void Eleventh_day_eleven_pipers_piping()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(11));
    }

    [Fact(Skip = "Remove to run test")]
    public void Twelfth_day_twelve_drummers_drumming()
    {
        var expected = "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.";
        Assert.Equal(expected, TwelveDays.Recite(12));
    }

    [Fact(Skip = "Remove to run test")]
    public void Recites_first_three_verses_of_the_song()
    {
        var expected = 
            "On the first day of Christmas my true love gave to me, a Partridge in a Pear Tree.\n" +
            "On the second day of Christmas my true love gave to me, two Turtle Doves, and a Partridge in a Pear Tree.\n" +
            "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.";
        Assert.Equal(expected, TwelveDays.Recite(1, 3));
    }

    [Fact(Skip = "Remove to run test")]
    public void Recites_three_verses_from_the_middle_of_the_song()
    {
        var expected = 
            "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.\n" +
            "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.\n" +
            "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.";
        Assert.Equal(expected, TwelveDays.Recite(4, 6));
    }

    [Fact(Skip = "Remove to run test")]
    public void Recites_the_whole_song()
    {
        var expected = 
            "On the first day of Christmas my true love gave to me, a Partridge in a Pear Tree.\n" +
            "On the second day of Christmas my true love gave to me, two Turtle Doves, and a Partridge in a Pear Tree.\n" +
            "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.\n" +
            "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.\n" +
            "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.\n" +
            "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.\n" +
            "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.\n" +
            "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.\n" +
            "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.\n" +
            "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.\n" +
            "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.\n" +
            "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.";
        Assert.Equal(expected, TwelveDays.Recite(1, 12));
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class TwelveDaysSong
{

    private const int MAX_VERSE = 12;
    private const string VERSE_PLACEHOLDER = "On the {0} day of Christmas my true love gave to me, {1}.\n";
    private readonly IDictionary<string, string> _verses = new Dictionary<string, string>() {
        { "first", "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" }
    };

    public string Verse(int day)
    {
        if (day < 1 || day > MAX_VERSE) throw new ArgumentOutOfRangeException("The 'day' can only be between 1 and 12, inclusive.");
        
        var gifts = _verses.Values.TakeWhile((verse, index) => index < day).Reverse().ToList();
        
        // If more than one gift, the last gift needs 'and ' added to it
        if (gifts.Count > 1) 
        {
            gifts[gifts.Count - 1] = "and " + gifts[gifts.Count - 1];
        }

        return string.Format(VERSE_PLACEHOLDER, _verses.ElementAt((day - 1)).Key, string.Join(", ", gifts));
    }

    public string Verses(int from, int to)
    {
        StringBuilder result = new StringBuilder();
        for (int verse = from; verse <= to; verse++)
        {
            result.Append(Verse(verse)).Append("\n");
        }
        return result.ToString();
    }

    public string Sing()
    {
        return Verses(1, MAX_VERSE);
    }
}

Community comments

Find this solution interesting? Ask the author a question to learn more.
Avatar of MetalKid
MetalKid
Solution Author
commented over 5 years ago

Just cleaned up wrong comments (was gift, not a verse) and removed a using statement.

Avatar of rprouse

I like the use of the dictionary. It shorten's the code a bit.

Rather than TakeWhile then Reverse, you could have also done a Skip then Take. I think the meaning would be clearer then.

Avatar of MetalKid
MetalKid
Solution Author
commented over 5 years ago

How could skip and Take work?

Avatar of rprouse

I didn't notice that you ordered the gifts in the opposite order than I did. I ordered the gifts in the order they would be in the song, so twelve drummers first, partridge in a pear tree last. When I am constructing the list of gifts, I use a GIFTS.Skip(12 - verse).Take(verse) to skip over the gifts that aren't used, then start taking the rest in the order they appear.

I think I prefer the way I did it. With yours, taking gifts then reversing them doesn't read well with me.

Of course, I am being very nit-picky :smile: I've been enjoying comparing my solutions to yours which are always well done. I see these exercises as an attempt to distill pure thought into code as succinctly and clearly as possible and it is always interesting seeing how many ways we can solve the same problem.

Avatar of MetalKid
MetalKid
Solution Author
commented over 5 years ago

Heh, I just wanted to use TakeWhile for the first time in my life. :) I also like reading 1 thru 12 in the dictionary, so I end up having to reverse it this way. You are right, it isn't ideal, but I just wanted a different example out there. :)

Avatar of jkedgar

Good job.

I assumed that the second parameter to Verses() was the number of verses to return rather than the last verse to return. Since the test cases always use verse 1 for the starting verse in Verses() there is no effective difference, but yours will work with Verses(11, 12) whereas I would only get that behavior with Verses(11, 1). Anyone know how to get the test case updated?

One comment on your code. You have a separate 'if' statement to add the 'and' in. Couldn't that be done in the previous line? var gifts = _verses .Values .TakeWhile((verse, index) => index < day) .Select((verse. index) => (index == 0 && day > 1) ? "and " + verse : verse) .Reverse() .ToList();

Note: I haven't tried to compile that code.

Avatar of MetalKid
MetalKid
Solution Author
commented over 5 years ago

Ah, good catch on the Verses() thing. We really don't know the right answer, do we?! :)

While I could do the .Select() there and do that, I think it wouldn't be all that clear compared to my if check there. I know trying to do everything in LINQ is the fad now, but I'm old school enough to do my own if check and update every now and then. :) Plus, that Select is going to run on every single verse it finds instead of just doing the check once afterwards and updating one verse. That select will be doing 2 if checks and a tertiary call on each one. :)

Avatar of jkedgar

Good points. When I did mine, I built the entire string as I went along rather than generating a list of strings to later combine. Thus I had to put the 'and' in at the time I processed the "a Partridge..." phrase. That is probably why I was thinking that inlining it would be good. You are correct that in your system having a single check would be faster.

Avatar of CodeMonkey25

I'm surprised that this works, considering you are enumerating a dictionary and using the index to build the verses.

From the docs: For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair<tkey tvalue> structure representing a value and its key. The order in which the items are returned is undefined. </tkey> I have to say, the fact that it passes all the tests is completely wigging me out. :-D Can you change it to an OrderedDictionary so I can sleep at night?

Avatar of MetalKid
MetalKid
Solution Author
commented over 5 years ago

Ah, true. I guess I didn't think about it. Again, I should have used a List<tupple>&gt; and not a dictionary (or List of some class).</tupple>

Avatar of BadAsstronaut

I had 2 lists, and I changed it to use the Dictionary. I like combining the ordinal and the verse phrase.

I haven't encountered an ordered dictionary yet, but looking at it I have a question: Does this mean that a ordered dictionary cannot be indexed by int datatypes? To clarify, MSDN specifies: Item[Int32] Gets or sets the value at the specified index. Item[Object] Gets or sets the value with the specified key.

So, if I have an int key = 3, and it's the 6th entry in the OrderedDictionary, when I enter OrderedDictionary.Item[3], what do I get??

Avatar of CodeMonkey25

@BadAsstronaut I don't know, I haven't tried it out. I personally did this exercise by using a two dimensional array, so the ordinal and verse phrase were stored together. Feel free to check out my submission.

Avatar of BadAsstronaut

@CodeMonkey25 Looks good! I think it's way cleaner to store the data together rather than have 2 separate arrays or lists.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?