diff --git a/config.json b/config.json index 30db813..b04a555 100644 --- a/config.json +++ b/config.json @@ -404,6 +404,17 @@ "logic" ] }, + { + "slug": "grains", + "name": "Grains", + "uuid": "1759636a-eeb0-42e9-98af-01cf4c738700", + "practices": [], + "prerequisites": [], + "difficulty": 2, + "topics": [ + "math" + ] + }, { "slug": "pascals-triangle", "name": "Pascals Triangle", diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md new file mode 100644 index 0000000..df479fc --- /dev/null +++ b/exercises/practice/grains/.docs/instructions.md @@ -0,0 +1,15 @@ +# Instructions + +Calculate the number of grains of wheat on a chessboard given that the number on each square doubles. + +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chess board, with the number of grains doubling on each successive square. + +There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). + +Write code that shows: + +- how many grains were on a given square, and +- the total number of grains on the chessboard diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json new file mode 100644 index 0000000..75ef219 --- /dev/null +++ b/exercises/practice/grains/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "grains.sml" + ], + "test": [ + "test.sml" + ], + "example": [ + ".meta/example.sml" + ] + }, + "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", + "source": "The CodeRanch Cattle Drive, Assignment 6", + "source_url": "https://coderanch.com/wiki/718824/Grains" +} diff --git a/exercises/practice/grains/.meta/example.sml b/exercises/practice/grains/.meta/example.sml new file mode 100644 index 0000000..7509236 --- /dev/null +++ b/exercises/practice/grains/.meta/example.sml @@ -0,0 +1,12 @@ +local + fun power2 (k: int): LargeInt.int = + if k = 0 then 1 + else 2 * power2 (k - 1) +in + fun square (n: int): string = + if n < 1 orelse n > 64 then raise Fail "square must be between 1 and 64" + else LargeInt.toString (power2 (n - 1)) + + fun total (): string = + LargeInt.toString ((power2 64) - 1) +end diff --git a/exercises/practice/grains/.meta/tests.toml b/exercises/practice/grains/.meta/tests.toml new file mode 100644 index 0000000..6ea68bc --- /dev/null +++ b/exercises/practice/grains/.meta/tests.toml @@ -0,0 +1,43 @@ +# 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. + +[9fbde8de-36b2-49de-baf2-cd42d6f28405] +description = "returns the number of grains on the square -> grains on square 1" + +[ee1f30c2-01d8-4298-b25d-c677331b5e6d] +description = "returns the number of grains on the square -> grains on square 2" + +[10f45584-2fc3-4875-8ec6-666065d1163b] +description = "returns the number of grains on the square -> grains on square 3" + +[a7cbe01b-36f4-4601-b053-c5f6ae055170] +description = "returns the number of grains on the square -> grains on square 4" + +[c50acc89-8535-44e4-918f-b848ad2817d4] +description = "returns the number of grains on the square -> grains on square 16" + +[acd81b46-c2ad-4951-b848-80d15ed5a04f] +description = "returns the number of grains on the square -> grains on square 32" + +[c73b470a-5efb-4d53-9ac6-c5f6487f227b] +description = "returns the number of grains on the square -> grains on square 64" + +[1d47d832-3e85-4974-9466-5bd35af484e3] +description = "returns the number of grains on the square -> square 0 is invalid" + +[61974483-eeb2-465e-be54-ca5dde366453] +description = "returns the number of grains on the square -> negative square is invalid" + +[a95e4374-f32c-45a7-a10d-ffec475c012f] +description = "returns the number of grains on the square -> square greater than 64 is invalid" + +[6eb07385-3659-4b45-a6be-9dc474222750] +description = "returns the total number of grains on the board" diff --git a/exercises/practice/grains/grains.sml b/exercises/practice/grains/grains.sml new file mode 100644 index 0000000..429c0df --- /dev/null +++ b/exercises/practice/grains/grains.sml @@ -0,0 +1,5 @@ +fun square (n: int): string = + raise Fail "'square' is not implemented" + +fun total (): string = + raise Fail "'total' is not implemented" diff --git a/exercises/practice/grains/test.sml b/exercises/practice/grains/test.sml new file mode 100644 index 0000000..c818a2f --- /dev/null +++ b/exercises/practice/grains/test.sml @@ -0,0 +1,47 @@ +(* version 1.0.0 *) + +use "testlib.sml"; +use "grains.sml"; + +infixr |> +fun x |> f = f x + +val testsuite = + describe "grains" [ + describe "returns the number of grains on the square" [ + test "grains on square 1" + (fn _ => square 1 |> Expect.equalTo "1"), + + test "grains on square 2" + (fn _ => square 2 |> Expect.equalTo "2"), + + test "grains on square 3" + (fn _ => square 3 |> Expect.equalTo "4"), + + test "grains on square 4" + (fn _ => square 4 |> Expect.equalTo "8"), + + test "grains on square 16" + (fn _ => square 16 |> Expect.equalTo "32768"), + + test "grains on square 32" + (fn _ => square 32 |> Expect.equalTo "2147483648"), + + test "grains on square 64" + (fn _ => square 64 |> Expect.equalTo "9223372036854775808"), + + test "square 0 is invalid" + (fn _ => (fn _ => square 0) |> Expect.error (Fail "square must be between 1 and 64")), + + test "negative square is invalid" + (fn _ => (fn _ => square ~1) |> Expect.error (Fail "square must be between 1 and 64")), + + test "square greater than 64 is invalid" + (fn _ => (fn _ => square 65) |> Expect.error (Fail "square must be between 1 and 64")) + ], + + test "returns the total number of grains on the board" + (fn _ => total () |> Expect.equalTo "18446744073709551615") + ] + +val _ = Test.run testsuite diff --git a/exercises/practice/grains/testlib.sml b/exercises/practice/grains/testlib.sml new file mode 100644 index 0000000..0c8370c --- /dev/null +++ b/exercises/practice/grains/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)