diff --git a/bin/generate b/bin/generate index c344f40..094e717 100755 --- a/bin/generate +++ b/bin/generate @@ -66,7 +66,7 @@ def indent(n, s): def camelize(s): - return re.sub('[-_](\w)', lambda x: x.group(1).upper(), s) + return re.sub(r'[-_](\w)', lambda x: x.group(1).upper(), s) def sml_type(value, force=False): diff --git a/config.json b/config.json index 05297d2..ebe4a27 100644 --- a/config.json +++ b/config.json @@ -18,7 +18,6 @@ "test_runner": { "average_run_time": 1 }, - "checklist_issue": 8, "files": { "solution": [ "%{kebab_slug}.sml" @@ -34,7 +33,6 @@ ] }, "exercises": { - "concept": [], "practice": [ { "slug": "hello-world", @@ -42,8 +40,7 @@ "uuid": "a00f98a6-98dd-4c8a-b3d1-adc1d56eef29", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "leap", @@ -51,8 +48,7 @@ "uuid": "622644a8-55c3-498e-ac72-ad6d7d31109d", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "two-fer", @@ -60,8 +56,7 @@ "uuid": "a56bad29-684c-47e7-9724-8c7f165a51af", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "scrabble-score", @@ -69,8 +64,7 @@ "uuid": "cc6e7e8d-84f8-4b85-8f5e-7a68b3b34b1f", "practices": [], "prerequisites": [], - "difficulty": 3, - "topics": [] + "difficulty": 3 }, { "slug": "space-age", @@ -100,8 +94,7 @@ "uuid": "d17ab3c6-82a1-413e-9018-8775ec9ea498", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "collatz-conjecture", @@ -120,8 +113,7 @@ "uuid": "38a13f45-9e80-4322-b896-d2cf06add3f9", "practices": [], "prerequisites": [], - "difficulty": 3, - "topics": [] + "difficulty": 3 }, { "slug": "kindergarten-garden", @@ -129,8 +121,7 @@ "uuid": "7d74175b-a2c6-4e77-a908-ebcbcf1e29b6", "practices": [], "prerequisites": [], - "difficulty": 3, - "topics": [] + "difficulty": 3 }, { "slug": "secret-handshake", @@ -138,8 +129,7 @@ "uuid": "55048224-25dc-4c91-b4f4-1ea1bacdd87f", "practices": [], "prerequisites": [], - "difficulty": 3, - "topics": [] + "difficulty": 3 }, { "slug": "matching-brackets", @@ -188,8 +178,7 @@ "uuid": "7be1122c-33cd-40ed-a70f-c71a6713c47a", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "binary", @@ -198,7 +187,6 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": null, "status": "deprecated" }, { @@ -208,7 +196,6 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": [], "status": "deprecated" }, { @@ -228,8 +215,7 @@ "uuid": "216334c2-bdb5-481a-a22b-20ac088f20e9", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "atbash-cipher", @@ -237,8 +223,7 @@ "uuid": "0f488d4b-89da-4d1f-958b-a7b3171f43d5", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "bob", @@ -246,8 +231,7 @@ "uuid": "4fb40e77-727b-41bc-ac15-b909a26bb917", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "diamond", @@ -279,8 +263,7 @@ "uuid": "fb0a030d-33bc-4066-a30a-1b8b02cc42f1", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "wordy", @@ -296,8 +279,7 @@ "uuid": "ce5dd1a4-7c90-48e4-b260-d2b62f29fbaf", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "list-ops", @@ -327,8 +309,7 @@ "uuid": "5184de5a-4772-4389-aa3c-b1117b601450", "practices": [], "prerequisites": [], - "difficulty": 5, - "topics": [] + "difficulty": 5 }, { "slug": "perfect-numbers", @@ -366,8 +347,7 @@ "uuid": "24edacd5-6e6d-4d37-8486-25283dce806f", "practices": [], "prerequisites": [], - "difficulty": 3, - "topics": [] + "difficulty": 3 }, { "slug": "rotational-cipher", @@ -386,8 +366,7 @@ "uuid": "f923bffb-109f-4257-957c-cea8cae2d5c5", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "isogram", @@ -441,8 +420,7 @@ "uuid": "76b1e1d1-2360-487c-89be-37f9780540b7", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "resistor-color", @@ -458,8 +436,7 @@ "uuid": "db3e09ee-6c93-417f-b0a0-196cdccf4984", "practices": [], "prerequisites": [], - "difficulty": 1, - "topics": [] + "difficulty": 1 }, { "slug": "pythagorean-triplet", @@ -478,8 +455,7 @@ "uuid": "b4359037-8457-49ea-ac33-44710ada3b4d", "practices": [], "prerequisites": [], - "difficulty": 5, - "topics": [] + "difficulty": 5 }, { "slug": "sum-of-multiples", @@ -534,8 +510,7 @@ "uuid": "90237a3f-595a-4238-bcef-46d6f84b0945", "practices": [], "prerequisites": [], - "difficulty": 2, - "topics": [] + "difficulty": 2 }, { "slug": "gigasecond", @@ -566,8 +541,7 @@ "uuid": "a503a4e2-b6eb-4ab0-ac67-8b136157f3dd", "practices": [], "prerequisites": [], - "difficulty": 4, - "topics": [] + "difficulty": 4 }, { "slug": "yacht", @@ -575,8 +549,7 @@ "uuid": "c4a24f88-febd-4955-a08d-67895a3d119f", "practices": [], "prerequisites": [], - "difficulty": 4, - "topics": [] + "difficulty": 4 }, { "slug": "triangle", @@ -618,16 +591,22 @@ "uuid": "95b2f13e-e59f-477c-905a-8cfa652607b3", "practices": [], "prerequisites": [], - "difficulty": 6, - "topics": [] + "difficulty": 6 + }, + { + "slug": "knapsack", + "name": "Knapsack", + "uuid": "4aaa8cbf-a48e-475a-a986-bd2cc3468e67", + "practices": [], + "prerequisites": [], + "difficulty": 5 } + ], + "foregone": [ + "bank-account", + "parallel-letter-frequency" ] }, - "foregone": [ - "bank-account", - "parallel-letter-frequency" - ], - "concepts": [], "key_features": [ { "title": "Algebraic data types", @@ -661,15 +640,15 @@ } ], "tags": [ + "execution_mode/interpreted", "paradigm/functional", "paradigm/imperative", - "typing/static", - "typing/strong", - "execution_mode/interpreted", - "platform/windows", - "platform/mac", "platform/linux", + "platform/mac", + "platform/windows", "runtime/language_specific", + "typing/static", + "typing/strong", "used_for/financial_systems", "used_for/scientific_calculations" ] diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md new file mode 100644 index 0000000..3411db9 --- /dev/null +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -0,0 +1,25 @@ +# Instructions + +Your task is to determine which items to take so that the total value of his selection is maximized, taking into account the knapsack's carrying capacity. + +Items will be represented as a list of items. +Each item will have a weight and value. +All values given will be strictly positive. +Bob can take only one of each item. + +For example: + +```text +Items: [ + { "weight": 5, "value": 10 }, + { "weight": 4, "value": 40 }, + { "weight": 6, "value": 30 }, + { "weight": 4, "value": 50 } +] + +Knapsack Maximum Weight: 10 +``` + +For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. +In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. +He cannot get more than 90 as his knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md new file mode 100644 index 0000000..9b2bed8 --- /dev/null +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Bob is a thief. +After months of careful planning, he finally manages to crack the security systems of a fancy store. + +In front of him are many items, each with a value and weight. +Bob would gladly take all of the items, but his knapsack can only hold so much weight. +Bob has to carefully consider which items to take so that the total value of his selection is maximized. diff --git a/exercises/practice/knapsack/.meta/config.json b/exercises/practice/knapsack/.meta/config.json new file mode 100644 index 0000000..b3eaad4 --- /dev/null +++ b/exercises/practice/knapsack/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "kahgoh" + ], + "files": { + "solution": [ + "knapsack.sml" + ], + "test": [ + "test.sml" + ], + "example": [ + ".meta/example.sml" + ] + }, + "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Knapsack_problem" +} diff --git a/exercises/practice/knapsack/.meta/example.sml b/exercises/practice/knapsack/.meta/example.sml new file mode 100644 index 0000000..947fa61 --- /dev/null +++ b/exercises/practice/knapsack/.meta/example.sml @@ -0,0 +1,40 @@ +fun itemMaxValue(i: {value: int, weight: int}, prevValues, cap) = + let + val valueWithout = List.nth(prevValues, cap) + in + if (#weight i > cap) then + valueWithout + else + let + val valueWith = List.nth(prevValues, cap - #weight i) + #value i + in + Int.max(valueWith, valueWithout) + end + end; + +fun nextValues(i: {value: int, weight: int}, prevValues, cap, acc) : int list = + if cap < 0 then + acc + else + let + val itemValue = itemMaxValue(i, prevValues, cap) + in + nextValues(i, prevValues, cap - 1, itemValue::acc) + end; + +fun calculateValues([], values) = values + | calculateValues((i :: next), values) = + let + val valuesForNext = nextValues(i, values, (List.length values) - 1, []) + in + calculateValues(next, valuesForNext) + end; + +fun maximumValue([], _) = 0 + | maximumValue(items, maximumWeight): int = + let + val size : int = maximumWeight + 1 + val initial : int list = List.tabulate(size, fn _ => 0) + in + List.last(calculateValues(items, initial)) + end; \ No newline at end of file diff --git a/exercises/practice/knapsack/.meta/tests.toml b/exercises/practice/knapsack/.meta/tests.toml new file mode 100644 index 0000000..8e013ef --- /dev/null +++ b/exercises/practice/knapsack/.meta/tests.toml @@ -0,0 +1,36 @@ +# 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. + +[a4d7d2f0-ad8a-460c-86f3-88ba709d41a7] +description = "no items" +include = false + +[3993a824-c20e-493d-b3c9-ee8a7753ee59] +description = "no items" +reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7" + +[1d39e98c-6249-4a8b-912f-87cb12e506b0] +description = "one item, too heavy" + +[833ea310-6323-44f2-9d27-a278740ffbd8] +description = "five items (cannot be greedy by weight)" + +[277cdc52-f835-4c7d-872b-bff17bab2456] +description = "five items (cannot be greedy by value)" + +[81d8e679-442b-4f7a-8a59-7278083916c9] +description = "example knapsack" + +[f23a2449-d67c-4c26-bf3e-cde020f27ecc] +description = "8 items" + +[7c682ae9-c385-4241-a197-d2fa02c81a11] +description = "15 items" diff --git a/exercises/practice/knapsack/knapsack.sml b/exercises/practice/knapsack/knapsack.sml new file mode 100644 index 0000000..99f781a --- /dev/null +++ b/exercises/practice/knapsack/knapsack.sml @@ -0,0 +1,2 @@ +fun maximumValue (items: {value: int, weight: int} list, maximumWeight: int): int = + raise Fail "'maximumValue' is not implemented" \ No newline at end of file diff --git a/exercises/practice/knapsack/test.sml b/exercises/practice/knapsack/test.sml new file mode 100644 index 0000000..3896c8f --- /dev/null +++ b/exercises/practice/knapsack/test.sml @@ -0,0 +1,31 @@ +use "testlib.sml"; +use "knapsack.sml"; + +infixr |> +fun x |> f = f x + +val testsuite = + describe "knapsack" [ + test "no items" + (fn _ => maximumValue ([], 100) |> Expect.equalTo 0), + + test "one item, too heavy" + (fn _ => maximumValue ([{weight = 100, value = 1}], 10) |> Expect.equalTo 0), + + test "five items (cannot be greedy by weight)" + (fn _ => maximumValue ([{weight = 2, value = 5}, {weight = 2, value = 5}, {weight = 2, value = 5}, {weight = 2, value = 5}, {weight = 10, value = 21}], 10) |> Expect.equalTo 21), + + test "five items (cannot be greedy by value)" + (fn _ => maximumValue ([{weight = 2, value = 20}, {weight = 2, value = 20}, {weight = 2, value = 20}, {weight = 2, value = 20}, {weight = 10, value = 50}], 10) |> Expect.equalTo 80), + + test "example knapsack" + (fn _ => maximumValue ([{weight = 5, value = 10}, {weight = 4, value = 40}, {weight = 6, value = 30}, {weight = 4, value = 50}], 10) |> Expect.equalTo 90), + + test "8 items" + (fn _ => maximumValue ([{weight = 25, value = 350}, {weight = 35, value = 400}, {weight = 45, value = 450}, {weight = 5, value = 20}, {weight = 25, value = 70}, {weight = 3, value = 8}, {weight = 2, value = 5}, {weight = 2, value = 5}], 104) |> Expect.equalTo 900), + + test "15 items" + (fn _ => maximumValue ([{weight = 70, value = 135}, {weight = 73, value = 139}, {weight = 77, value = 149}, {weight = 80, value = 150}, {weight = 82, value = 156}, {weight = 87, value = 163}, {weight = 90, value = 173}, {weight = 94, value = 184}, {weight = 98, value = 192}, {weight = 106, value = 201}, {weight = 110, value = 210}, {weight = 113, value = 214}, {weight = 115, value = 221}, {weight = 118, value = 229}, {weight = 120, value = 240}], 750) |> Expect.equalTo 1458) + ] + +val _ = Test.run testsuite \ No newline at end of file diff --git a/exercises/practice/knapsack/testlib.sml b/exercises/practice/knapsack/testlib.sml new file mode 100644 index 0000000..0c8370c --- /dev/null +++ b/exercises/practice/knapsack/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)