From d4824d62c2585ae81ad128e3061c83492b06bf10 Mon Sep 17 00:00:00 2001 From: Hans Roman Date: Thu, 24 Aug 2017 00:38:28 -0500 Subject: [PATCH] Add pangram exercise --- config.json | 10 +++ exercises/pangram/README.md | 45 ++++++++++ exercises/pangram/example.sml | 18 ++++ exercises/pangram/pangram.sml | 2 + exercises/pangram/test.sml | 41 +++++++++ exercises/pangram/testlib.sml | 159 ++++++++++++++++++++++++++++++++++ 6 files changed, 275 insertions(+) create mode 100644 exercises/pangram/README.md create mode 100644 exercises/pangram/example.sml create mode 100644 exercises/pangram/pangram.sml create mode 100644 exercises/pangram/test.sml create mode 100644 exercises/pangram/testlib.sml diff --git a/config.json b/config.json index a465aa6..f920bc6 100644 --- a/config.json +++ b/config.json @@ -124,6 +124,16 @@ ] }, + { + "uuid": "42adb6ed-09f0-6980-2d4f-75ac90cc2bf1f59fb3a", + "slug": "pangram", + "core": false, + "unlocked_by": null, + "difficulty": 1, + "topics": [ + + ] + }, { "uuid": "225cfd7d-81a3-4a58-b1aa-1de2e40e7a93", "slug": "binary", diff --git a/exercises/pangram/README.md b/exercises/pangram/README.md new file mode 100644 index 0000000..54c58ee --- /dev/null +++ b/exercises/pangram/README.md @@ -0,0 +1,45 @@ +# Pangram + +Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma, +"every letter") is a sentence using every letter of the alphabet at least once. +The best known English pangram is: +> The quick brown fox jumps over the lazy dog. + +The alphabet used consists of ASCII letters `a` to `z`, inclusive, and is case +insensitive. Input will not contain non-ASCII symbols. + +## Loading your exercise implementation in PolyML + +``` +$ poly --use {exercise}.sml +``` + +Or: + +``` +$ poly +> use "{exercise}.sml"; +``` + +**Note:** You have to replace {exercise}. + +## Running the tests + +``` +$ poly -q --use test.sml +``` + +## Feedback, Issues, Pull Requests + +The [exercism/sml](https://github.com/exercism/sml) repository on +GitHub is the home for all of the Standard ML exercises. + +If you have feedback about an exercise, or want to help implementing a new +one, head over there and create an issue. We'll do our best to help you! + +## Source + +Wikipedia [https://en.wikipedia.org/wiki/Pangram](https://en.wikipedia.org/wiki/Pangram) + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/exercises/pangram/example.sml b/exercises/pangram/example.sml new file mode 100644 index 0000000..395c76e --- /dev/null +++ b/exercises/pangram/example.sml @@ -0,0 +1,18 @@ +fun isPangram (input: string): bool = + let + val counter = Array.tabulate (26, fn _ => 0) + val chars = map Char.toLower (String.explode input) + val aCode = ord #"a" + + fun updateCounter c = + let + val index = ord c - aCode + in + if index < 0 + then () + else Array.update (counter, index, (Array.sub (counter, index)) + 1) + end + in + List.app updateCounter chars; + Array.all (fn x => x > 0) counter + end diff --git a/exercises/pangram/pangram.sml b/exercises/pangram/pangram.sml new file mode 100644 index 0000000..6925a42 --- /dev/null +++ b/exercises/pangram/pangram.sml @@ -0,0 +1,2 @@ +fun isPangram (input: string): bool = + raise Fail "'isPangram' is not implemented" \ No newline at end of file diff --git a/exercises/pangram/test.sml b/exercises/pangram/test.sml new file mode 100644 index 0000000..8a1bddb --- /dev/null +++ b/exercises/pangram/test.sml @@ -0,0 +1,41 @@ +(* version 1.1.0 *) + +use "pangram.sml"; +use "testlib.sml"; + +infixr |> +fun x |> f = f x + +val testsuite = + describe "pangram" [ + describe "Check if the given string is an pangram" [ + test "sentence empty" + (fn _ => isPangram ("") |> Expect.falsy), + + test "pangram with only lower case" + (fn _ => isPangram ("the quick brown fox jumps over the lazy dog") |> Expect.truthy), + + test "missing character 'x'" + (fn _ => isPangram ("a quick movement of the enemy will jeopardize five gunboats") |> Expect.falsy), + + test "another missing character 'x'" + (fn _ => isPangram ("the quick brown fish jumps over the lazy dog") |> Expect.falsy), + + test "pangram with underscores" + (fn _ => isPangram ("the_quick_brown_fox_jumps_over_the_lazy_dog") |> Expect.truthy), + + test "pangram with numbers" + (fn _ => isPangram ("the 1 quick brown fox jumps over the 2 lazy dogs") |> Expect.truthy), + + test "missing letters replaced by numbers" + (fn _ => isPangram ("7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog") |> Expect.falsy), + + test "pangram with mixed case and punctuation" + (fn _ => isPangram ("\"Five quacking Zephyrs jolt my wax bed.\"") |> Expect.truthy), + + test "upper and lower case versions of the same character should not be counted separately" + (fn _ => isPangram ("the quick brown fox jumps over with lazy FX") |> Expect.falsy) + ] + ] + +val _ = Test.run testsuite \ No newline at end of file diff --git a/exercises/pangram/testlib.sml b/exercises/pangram/testlib.sml new file mode 100644 index 0000000..b9291bc --- /dev/null +++ b/exercises/pangram/testlib.sml @@ -0,0 +1,159 @@ +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 b a = + if Real.== (a, b) + then Pass + else failEq (Real.toString b) (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)