diff --git a/config.json b/config.json index 15d55ed..3b01b71 100644 --- a/config.json +++ b/config.json @@ -443,6 +443,17 @@ "strings" ] }, + { + "slug": "sublist", + "name": "Sublist", + "uuid": "ab525174-18aa-4458-bc86-d669a6679c5a", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [ + "lists" + ] + }, { "slug": "queen-attack", "name": "Queen Attack", diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md new file mode 100644 index 0000000..7535931 --- /dev/null +++ b/exercises/practice/sublist/.docs/instructions.md @@ -0,0 +1,25 @@ +# Instructions + +Given any two lists `A` and `B`, determine if: + +- List `A` is equal to list `B`; or +- List `A` contains list `B` (`A` is a superlist of `B`); or +- List `A` is contained by list `B` (`A` is a sublist of `B`); or +- None of the above is true, thus lists `A` and `B` are unequal + +Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. +List `A` is a superlist of `B` if `A` contains a sub-sequence of values equal to `B`. +List `A` is a sublist of `B` if `B` contains a sub-sequence of values equal to `A`. + +Examples: + +- If `A = []` and `B = []` (both lists are empty), then `A` and `B` are equal +- If `A = [1, 2, 3]` and `B = []`, then `A` is a superlist of `B` +- If `A = []` and `B = [1, 2, 3]`, then `A` is a sublist of `B` +- If `A = [1, 2, 3]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [3, 4, 5]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [3, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [1, 2, 3]` and `B = [1, 2, 3]`, then `A` and `B` are equal +- If `A = [1, 2, 3, 4, 5]` and `B = [2, 3, 4]`, then `A` is a superlist of `B` +- If `A = [1, 2, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` and `B` are unequal +- If `A = [1, 2, 3]` and `B = [1, 3, 2]`, then `A` and `B` are unequal diff --git a/exercises/practice/sublist/.meta/config.json b/exercises/practice/sublist/.meta/config.json new file mode 100644 index 0000000..5b47149 --- /dev/null +++ b/exercises/practice/sublist/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "sublist.sml" + ], + "test": [ + "test.sml" + ], + "example": [ + ".meta/example.sml" + ] + }, + "blurb": "Write a function to determine if a list is a sublist of another list." +} diff --git a/exercises/practice/sublist/.meta/example.sml b/exercises/practice/sublist/.meta/example.sml new file mode 100644 index 0000000..f80beb8 --- /dev/null +++ b/exercises/practice/sublist/.meta/example.sml @@ -0,0 +1,22 @@ +datatype relation = + Equal + | Superlist + | Sublist + | Unequal + +local + fun isPrefix (listOne: int list, listTwo: int list): bool = + case (listOne, listTwo) of + (nil, _) => true + | (_, nil) => false + | (firstOne :: restOne, firstTwo :: restTwo) => firstOne = firstTwo andalso isPrefix (restOne, restTwo) + + fun isSublist (listOne: int list, listTwo: int list): bool = + isPrefix (listOne, listTwo) orelse (listTwo <> nil andalso isSublist (listOne, tl listTwo)) +in + fun sublist (listOne: int list, listTwo: int list): relation = + if listOne = listTwo then Equal + else if isSublist(listOne, listTwo) then Sublist + else if isSublist(listTwo, listOne) then Superlist + else Unequal +end diff --git a/exercises/practice/sublist/.meta/tests.toml b/exercises/practice/sublist/.meta/tests.toml new file mode 100644 index 0000000..de5020a --- /dev/null +++ b/exercises/practice/sublist/.meta/tests.toml @@ -0,0 +1,64 @@ +# 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. + +[97319c93-ebc5-47ab-a022-02a1980e1d29] +description = "empty lists" + +[de27dbd4-df52-46fe-a336-30be58457382] +description = "empty list within non empty list" + +[5487cfd1-bc7d-429f-ac6f-1177b857d4fb] +description = "non empty list contains empty list" + +[1f390b47-f6b2-4a93-bc23-858ba5dda9a6] +description = "list equals itself" + +[7ed2bfb2-922b-4363-ae75-f3a05e8274f5] +description = "different lists" + +[3b8a2568-6144-4f06-b0a1-9d266b365341] +description = "false start" + +[dc39ed58-6311-4814-be30-05a64bc8d9b1] +description = "consecutive" + +[d1270dab-a1ce-41aa-b29d-b3257241ac26] +description = "sublist at start" + +[81f3d3f7-4f25-4ada-bcdc-897c403de1b6] +description = "sublist in middle" + +[43bcae1e-a9cf-470e-923e-0946e04d8fdd] +description = "sublist at end" + +[76cf99ed-0ff0-4b00-94af-4dfb43fe5caa] +description = "at start of superlist" + +[b83989ec-8bdf-4655-95aa-9f38f3e357fd] +description = "in middle of superlist" + +[26f9f7c3-6cf6-4610-984a-662f71f8689b] +description = "at end of superlist" + +[0a6db763-3588-416a-8f47-76b1cedde31e] +description = "first list missing element from second list" + +[83ffe6d8-a445-4a3c-8795-1e51a95e65c3] +description = "second list missing element from first list" + +[7bc76cb8-5003-49ca-bc47-cdfbe6c2bb89] +description = "first list missing additional digits from second list" + +[0d7ee7c1-0347-45c8-9ef5-b88db152b30b] +description = "order matters to a list" + +[5f47ce86-944e-40f9-9f31-6368aad70aa6] +description = "same digits but different numbers" diff --git a/exercises/practice/sublist/sublist.sml b/exercises/practice/sublist/sublist.sml new file mode 100644 index 0000000..0196dfb --- /dev/null +++ b/exercises/practice/sublist/sublist.sml @@ -0,0 +1,8 @@ +datatype relation = + Equal + | Superlist + | Sublist + | Unequal + +fun sublist (listOne: int list, listTwo: int list): relation = + raise Fail "'sublist' is not implemented" diff --git a/exercises/practice/sublist/test.sml b/exercises/practice/sublist/test.sml new file mode 100644 index 0000000..d8331f0 --- /dev/null +++ b/exercises/practice/sublist/test.sml @@ -0,0 +1,156 @@ +(* version 1.0.0 *) + +use "testlib.sml"; +use "sublist.sml"; + +infixr |> +fun x |> f = f x + +val testsuite = + describe "sublist" [ + test "empty lists" + (fn _ => let + val listOne = [] + val listTwo = [] + in + sublist (listOne, listTwo) |> Expect.equalTo Equal + end), + + test "empty list within non empty list" + (fn _ => let + val listOne = [] + val listTwo = [1, 2, 3] + in + sublist (listOne, listTwo) |> Expect.equalTo Sublist + end), + + test "non empty list contains empty list" + (fn _ => let + val listOne = [1, 2, 3] + val listTwo = [] + in + sublist (listOne, listTwo) |> Expect.equalTo Superlist + end), + + test "list equals itself" + (fn _ => let + val listOne = [1, 2, 3] + val listTwo = [1, 2, 3] + in + sublist (listOne, listTwo) |> Expect.equalTo Equal + end), + + test "different lists" + (fn _ => let + val listOne = [1, 2, 3] + val listTwo = [2, 3, 4] + in + sublist (listOne, listTwo) |> Expect.equalTo Unequal + end), + + test "false start" + (fn _ => let + val listOne = [1, 2, 5] + val listTwo = [0, 1, 2, 3, 1, 2, 5, 6] + in + sublist (listOne, listTwo) |> Expect.equalTo Sublist + end), + + test "consecutive" + (fn _ => let + val listOne = [1, 1, 2] + val listTwo = [0, 1, 1, 1, 2, 1, 2] + in + sublist (listOne, listTwo) |> Expect.equalTo Sublist + end), + + test "sublist at start" + (fn _ => let + val listOne = [0, 1, 2] + val listTwo = [0, 1, 2, 3, 4, 5] + in + sublist (listOne, listTwo) |> Expect.equalTo Sublist + end), + + test "sublist in middle" + (fn _ => let + val listOne = [2, 3, 4] + val listTwo = [0, 1, 2, 3, 4, 5] + in + sublist (listOne, listTwo) |> Expect.equalTo Sublist + end), + + test "sublist at end" + (fn _ => let + val listOne = [3, 4, 5] + val listTwo = [0, 1, 2, 3, 4, 5] + in + sublist (listOne, listTwo) |> Expect.equalTo Sublist + end), + + test "at start of superlist" + (fn _ => let + val listOne = [0, 1, 2, 3, 4, 5] + val listTwo = [0, 1, 2] + in + sublist (listOne, listTwo) |> Expect.equalTo Superlist + end), + + test "in middle of superlist" + (fn _ => let + val listOne = [0, 1, 2, 3, 4, 5] + val listTwo = [2, 3] + in + sublist (listOne, listTwo) |> Expect.equalTo Superlist + end), + + test "at end of superlist" + (fn _ => let + val listOne = [0, 1, 2, 3, 4, 5] + val listTwo = [3, 4, 5] + in + sublist (listOne, listTwo) |> Expect.equalTo Superlist + end), + + test "first list missing element from second list" + (fn _ => let + val listOne = [1, 3] + val listTwo = [1, 2, 3] + in + sublist (listOne, listTwo) |> Expect.equalTo Unequal + end), + + test "second list missing element from first list" + (fn _ => let + val listOne = [1, 2, 3] + val listTwo = [1, 3] + in + sublist (listOne, listTwo) |> Expect.equalTo Unequal + end), + + test "first list missing additional digits from second list" + (fn _ => let + val listOne = [1, 2] + val listTwo = [1, 22] + in + sublist (listOne, listTwo) |> Expect.equalTo Unequal + end), + + test "order matters to a list" + (fn _ => let + val listOne = [1, 2, 3] + val listTwo = [3, 2, 1] + in + sublist (listOne, listTwo) |> Expect.equalTo Unequal + end), + + test "same digits but different numbers" + (fn _ => let + val listOne = [1, 0, 1] + val listTwo = [10, 1] + in + sublist (listOne, listTwo) |> Expect.equalTo Unequal + end) + ] + +val _ = Test.run testsuite diff --git a/exercises/practice/sublist/testlib.sml b/exercises/practice/sublist/testlib.sml new file mode 100644 index 0000000..0c8370c --- /dev/null +++ b/exercises/practice/sublist/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)