From 9285c4bbbdab6ae87d8cb71230c57b34436ff5fe Mon Sep 17 00:00:00 2001 From: keiravillekode Date: Mon, 17 Feb 2025 08:06:39 +1100 Subject: [PATCH] Add twelve-days exercise (#331) --- config.json | 8 + .../twelve-days/.docs/instructions.md | 36 ++++ .../practice/twelve-days/.meta/config.json | 19 +++ .../practice/twelve-days/.meta/example.sml | 18 ++ .../practice/twelve-days/.meta/tests.toml | 55 ++++++ exercises/practice/twelve-days/test.sml | 151 +++++++++++++++++ exercises/practice/twelve-days/testlib.sml | 160 ++++++++++++++++++ .../practice/twelve-days/twelve-days.sml | 2 + 8 files changed, 449 insertions(+) create mode 100644 exercises/practice/twelve-days/.docs/instructions.md create mode 100644 exercises/practice/twelve-days/.meta/config.json create mode 100644 exercises/practice/twelve-days/.meta/example.sml create mode 100644 exercises/practice/twelve-days/.meta/tests.toml create mode 100644 exercises/practice/twelve-days/test.sml create mode 100644 exercises/practice/twelve-days/testlib.sml create mode 100644 exercises/practice/twelve-days/twelve-days.sml diff --git a/config.json b/config.json index c79eff8a..ea518d29 100644 --- a/config.json +++ b/config.json @@ -440,6 +440,14 @@ "lists" ] }, + { + "slug": "twelve-days", + "name": "Twelve Days", + "uuid": "d3799ff2-5cf4-4148-ba2a-e40137cfe2ce", + "practices": [], + "prerequisites": [], + "difficulty": 3 + }, { "slug": "all-your-base", "name": "All Your Base", diff --git a/exercises/practice/twelve-days/.docs/instructions.md b/exercises/practice/twelve-days/.docs/instructions.md new file mode 100644 index 00000000..83bb6e19 --- /dev/null +++ b/exercises/practice/twelve-days/.docs/instructions.md @@ -0,0 +1,36 @@ +# Instructions + +Your task in this exercise is to write code that returns the lyrics of the song: "The Twelve Days of Christmas." + +"The Twelve Days of Christmas" is a common English Christmas carol. +Each subsequent verse of the song builds on the previous verse. + +The lyrics your code returns should _exactly_ match the full song text shown below. + +## Lyrics + +```text +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. +``` diff --git a/exercises/practice/twelve-days/.meta/config.json b/exercises/practice/twelve-days/.meta/config.json new file mode 100644 index 00000000..9f0018f9 --- /dev/null +++ b/exercises/practice/twelve-days/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "twelve-days.sml" + ], + "test": [ + "test.sml" + ], + "example": [ + ".meta/example.sml" + ] + }, + "blurb": "Output the lyrics to 'The Twelve Days of Christmas'.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/The_Twelve_Days_of_Christmas_(song)" +} diff --git a/exercises/practice/twelve-days/.meta/example.sml b/exercises/practice/twelve-days/.meta/example.sml new file mode 100644 index 00000000..c7b277ae --- /dev/null +++ b/exercises/practice/twelve-days/.meta/example.sml @@ -0,0 +1,18 @@ +fun recite (startVerse: int, endVerse: int): string = + let + val ordinals = [ "", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth" ] + + val ordinal = List.nth (ordinals, startVerse) + + val offsets = [ 0, 235, 213, 194, 174, 157, 137, 113, 90, 69, 48, 26, 0 ] + + val offset = List.nth (offsets, startVerse) + + val gifts = "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." + + val gift = String.substring (gifts, offset, String.size gifts - offset) + + val suffix = if startVerse = endVerse then "" else "\n" ^ recite (startVerse + 1, endVerse) + in + "On the " ^ ordinal ^ " day of Christmas my true love gave to me: " ^ gift ^ suffix + end diff --git a/exercises/practice/twelve-days/.meta/tests.toml b/exercises/practice/twelve-days/.meta/tests.toml new file mode 100644 index 00000000..01fbc03f --- /dev/null +++ b/exercises/practice/twelve-days/.meta/tests.toml @@ -0,0 +1,55 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[c0b5a5e6-c89d-49b1-a6b2-9f523bff33f7] +description = "verse -> first day a partridge in a pear tree" + +[1c64508a-df3d-420a-b8e1-fe408847854a] +description = "verse -> second day two turtle doves" + +[a919e09c-75b2-4e64-bb23-de4a692060a8] +description = "verse -> third day three french hens" + +[9bed8631-ec60-4894-a3bb-4f0ec9fbe68d] +description = "verse -> fourth day four calling birds" + +[cf1024f0-73b6-4545-be57-e9cea565289a] +description = "verse -> fifth day five gold rings" + +[50bd3393-868a-4f24-a618-68df3d02ff04] +description = "verse -> sixth day six geese-a-laying" + +[8f29638c-9bf1-4680-94be-e8b84e4ade83] +description = "verse -> seventh day seven swans-a-swimming" + +[7038d6e1-e377-47ad-8c37-10670a05bc05] +description = "verse -> eighth day eight maids-a-milking" + +[37a800a6-7a56-4352-8d72-0f51eb37cfe8] +description = "verse -> ninth day nine ladies dancing" + +[10b158aa-49ff-4b2d-afc3-13af9133510d] +description = "verse -> tenth day ten lords-a-leaping" + +[08d7d453-f2ba-478d-8df0-d39ea6a4f457] +description = "verse -> eleventh day eleven pipers piping" + +[0620fea7-1704-4e48-b557-c05bf43967f0] +description = "verse -> twelfth day twelve drummers drumming" + +[da8b9013-b1e8-49df-b6ef-ddec0219e398] +description = "lyrics -> recites first three verses of the song" + +[c095af0d-3137-4653-ad32-bfb899eda24c] +description = "lyrics -> recites three verses from the middle of the song" + +[20921bc9-cc52-4627-80b3-198cbbfcf9b7] +description = "lyrics -> recites the whole song" diff --git a/exercises/practice/twelve-days/test.sml b/exercises/practice/twelve-days/test.sml new file mode 100644 index 00000000..6bb26466 --- /dev/null +++ b/exercises/practice/twelve-days/test.sml @@ -0,0 +1,151 @@ +(* version 1.0.0 *) + +use "testlib.sml"; +use "twelve-days.sml"; + +infixr |> +fun x |> f = f x + +val testsuite = + describe "twelve-days" [ + describe "verse" [ + test "first day a partridge in a pear tree" + (fn _ => let + val expected = + "On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree." + in + recite (1, 1) |> Expect.equalTo expected + end), + + test "second day two turtle doves" + (fn _ => let + val expected = + "On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree." + in + recite (2, 2) |> Expect.equalTo expected + end), + + test "third day three french hens" + (fn _ => let + val 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." + in + recite (3, 3) |> Expect.equalTo expected + end), + + test "fourth day four calling birds" + (fn _ => let + val 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." + in + recite (4, 4) |> Expect.equalTo expected + end), + + test "fifth day five gold rings" + (fn _ => let + val 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." + in + recite (5, 5) |> Expect.equalTo expected + end), + + test "sixth day six geese-a-laying" + (fn _ => let + val 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." + in + recite (6, 6) |> Expect.equalTo expected + end), + + test "seventh day seven swans-a-swimming" + (fn _ => let + val 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." + in + recite (7, 7) |> Expect.equalTo expected + end), + + test "eighth day eight maids-a-milking" + (fn _ => let + val 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." + in + recite (8, 8) |> Expect.equalTo expected + end), + + test "ninth day nine ladies dancing" + (fn _ => let + val 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." + in + recite (9, 9) |> Expect.equalTo expected + end), + + test "tenth day ten lords-a-leaping" + (fn _ => let + val 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." + in + recite (10, 10) |> Expect.equalTo expected + end), + + test "eleventh day eleven pipers piping" + (fn _ => let + val 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." + in + recite (11, 11) |> Expect.equalTo expected + end), + + test "twelfth day twelve drummers drumming" + (fn _ => let + val 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." + in + recite (12, 12) |> Expect.equalTo expected + end) + ], + + describe "lyrics" [ + test "recites first three verses of the song" + (fn _ => let + val 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." + in + recite (1, 3) |> Expect.equalTo expected + end), + + test "recites three verses from the middle of the song" + (fn _ => let + val 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." + in + recite (4, 6) |> Expect.equalTo expected + end), + + test "recites the whole song" + (fn _ => let + val 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." + in + recite (1, 12) |> Expect.equalTo expected + end) + ] + ] + +val _ = Test.run testsuite diff --git a/exercises/practice/twelve-days/testlib.sml b/exercises/practice/twelve-days/testlib.sml new file mode 100644 index 00000000..0c8370c0 --- /dev/null +++ b/exercises/practice/twelve-days/testlib.sml @@ -0,0 +1,160 @@ +structure Expect = +struct + datatype expectation = Pass | Fail of string * string + + local + fun failEq b a = + Fail ("Expected: " ^ b, "Got: " ^ a) + + fun failExn b a = + Fail ("Expected: " ^ b, "Raised: " ^ a) + + fun exnName (e: exn): string = General.exnName e + in + fun truthy a = + if a + then Pass + else failEq "true" "false" + + fun falsy a = + if a + then failEq "false" "true" + else Pass + + fun equalTo b a = + if a = b + then Pass + else failEq (PolyML.makestring b) (PolyML.makestring a) + + fun nearTo delta b a = + if Real.abs (a - b) <= delta * Real.abs a orelse + Real.abs (a - b) <= delta * Real.abs b + then Pass + else failEq (Real.toString b ^ " +/- " ^ Real.toString delta) (Real.toString a) + + fun anyError f = + ( + f (); + failExn "an exception" "Nothing" + ) handle _ => Pass + + fun error e f = + ( + f (); + failExn (exnName e) "Nothing" + ) handle e' => if exnMessage e' = exnMessage e + then Pass + else failExn (exnMessage e) (exnMessage e') + end +end + +structure TermColor = +struct + datatype color = Red | Green | Yellow | Normal + + fun f Red = "\027[31m" + | f Green = "\027[32m" + | f Yellow = "\027[33m" + | f Normal = "\027[0m" + + fun colorize color s = (f color) ^ s ^ (f Normal) + + val redit = colorize Red + + val greenit = colorize Green + + val yellowit = colorize Yellow +end + +structure Test = +struct + datatype testnode = TestGroup of string * testnode list + | Test of string * (unit -> Expect.expectation) + + local + datatype evaluation = Success of string + | Failure of string * string * string + | Error of string * string + + fun indent n s = (implode (List.tabulate (n, fn _ => #" "))) ^ s + + fun fmt indentlvl ev = + let + val check = TermColor.greenit "\226\156\148 " (* ✔ *) + val cross = TermColor.redit "\226\156\150 " (* ✖ *) + val indentlvl = indentlvl * 2 + in + case ev of + Success descr => indent indentlvl (check ^ descr) + | Failure (descr, exp, got) => + String.concatWith "\n" [indent indentlvl (cross ^ descr), + indent (indentlvl + 2) exp, + indent (indentlvl + 2) got] + | Error (descr, reason) => + String.concatWith "\n" [indent indentlvl (cross ^ descr), + indent (indentlvl + 2) (TermColor.redit reason)] + end + + fun eval (TestGroup _) = raise Fail "Only a 'Test' can be evaluated" + | eval (Test (descr, thunk)) = + ( + case thunk () of + Expect.Pass => ((1, 0, 0), Success descr) + | Expect.Fail (s, s') => ((0, 1, 0), Failure (descr, s, s')) + ) + handle e => ((0, 0, 1), Error (descr, "Unexpected error: " ^ exnMessage e)) + + fun flatten depth testnode = + let + fun sum (x, y, z) (a, b, c) = (x + a, y + b, z + c) + + fun aux (t, (counter, acc)) = + let + val (counter', texts) = flatten (depth + 1) t + in + (sum counter' counter, texts :: acc) + end + in + case testnode of + TestGroup (descr, ts) => + let + val (counter, texts) = foldr aux ((0, 0, 0), []) ts + in + (counter, (indent (depth * 2) descr) :: List.concat texts) + end + | Test _ => + let + val (counter, evaluation) = eval testnode + in + (counter, [fmt depth evaluation]) + end + end + + fun println s = print (s ^ "\n") + in + fun run suite = + let + val ((succeeded, failed, errored), texts) = flatten 0 suite + + val summary = String.concatWith ", " [ + TermColor.greenit ((Int.toString succeeded) ^ " passed"), + TermColor.redit ((Int.toString failed) ^ " failed"), + TermColor.redit ((Int.toString errored) ^ " errored"), + (Int.toString (succeeded + failed + errored)) ^ " total" + ] + + val status = if failed = 0 andalso errored = 0 + then OS.Process.success + else OS.Process.failure + + in + List.app println texts; + println ""; + println ("Tests: " ^ summary); + OS.Process.exit status + end + end +end + +fun describe description tests = Test.TestGroup (description, tests) +fun test description thunk = Test.Test (description, thunk) diff --git a/exercises/practice/twelve-days/twelve-days.sml b/exercises/practice/twelve-days/twelve-days.sml new file mode 100644 index 00000000..7dfed990 --- /dev/null +++ b/exercises/practice/twelve-days/twelve-days.sml @@ -0,0 +1,2 @@ +fun recite (startVerse: int, endVerse: int): string = + raise Fail "'recite' is not implemented"