From 580a7b2aa8e6e2286292ff503e3f3b3b8832c6c7 Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Wed, 24 Dec 2025 19:15:54 -0500 Subject: [PATCH] change --- config.json | 8 +++ exercises/practice/change/.busted | 5 ++ .../practice/change/.docs/instructions.md | 8 +++ .../practice/change/.docs/introduction.md | 26 ++++++++ exercises/practice/change/.meta/config.json | 19 ++++++ exercises/practice/change/.meta/example.moon | 51 +++++++++++++++ .../practice/change/.meta/spec_generator.moon | 20 ++++++ exercises/practice/change/.meta/tests.toml | 49 ++++++++++++++ exercises/practice/change/change.moon | 4 ++ exercises/practice/change/change_spec.moon | 64 +++++++++++++++++++ 10 files changed, 254 insertions(+) create mode 100644 exercises/practice/change/.busted create mode 100644 exercises/practice/change/.docs/instructions.md create mode 100644 exercises/practice/change/.docs/introduction.md create mode 100644 exercises/practice/change/.meta/config.json create mode 100644 exercises/practice/change/.meta/example.moon create mode 100644 exercises/practice/change/.meta/spec_generator.moon create mode 100644 exercises/practice/change/.meta/tests.toml create mode 100644 exercises/practice/change/change.moon create mode 100644 exercises/practice/change/change_spec.moon diff --git a/config.json b/config.json index 379e8e3..007cf86 100644 --- a/config.json +++ b/config.json @@ -462,6 +462,14 @@ "practices": [], "prerequisites": [], "difficulty": 5 + }, + { + "slug": "change", + "name": "Change", + "uuid": "756a79f1-6149-4eef-a365-e3cfcaf9c999", + "practices": [], + "prerequisites": [], + "difficulty": 6 } ] }, diff --git a/exercises/practice/change/.busted b/exercises/practice/change/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/change/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/change/.docs/instructions.md b/exercises/practice/change/.docs/instructions.md new file mode 100644 index 0000000..5887f4c --- /dev/null +++ b/exercises/practice/change/.docs/instructions.md @@ -0,0 +1,8 @@ +# Instructions + +Determine the fewest number of coins to give a customer so that the sum of their values equals the correct amount of change. + +## Examples + +- An amount of 15 with available coin values [1, 5, 10, 25, 100] should return one coin of value 5 and one coin of value 10, or [5, 10]. +- An amount of 40 with available coin values [1, 5, 10, 25, 100] should return one coin of value 5, one coin of value 10, and one coin of value 25, or [5, 10, 25]. diff --git a/exercises/practice/change/.docs/introduction.md b/exercises/practice/change/.docs/introduction.md new file mode 100644 index 0000000..b4f8308 --- /dev/null +++ b/exercises/practice/change/.docs/introduction.md @@ -0,0 +1,26 @@ +# Introduction + +In the mystical village of Coinholt, you stand behind the counter of your bakery, arranging a fresh batch of pastries. +The door creaks open, and in walks Denara, a skilled merchant with a keen eye for quality goods. +After a quick meal, she slides a shimmering coin across the counter, representing a value of 100 units. + +You smile, taking the coin, and glance at the total cost of the meal: 88 units. +That means you need to return 12 units in change. + +Denara holds out her hand expectantly. +"Just give me the fewest coins," she says with a smile. +"My pouch is already full, and I don't want to risk losing them on the road." + +You know you have a few options. +"We have Lumis (worth 10 units), Viras (worth 5 units), and Zenth (worth 2 units) available for change." + +You quickly calculate the possibilities in your head: + +- one Lumis (1 × 10 units) + one Zenth (1 × 2 units) = 2 coins total +- two Viras (2 × 5 units) + one Zenth (1 × 2 units) = 3 coins total +- six Zenth (6 × 2 units) = 6 coins total + +"The best choice is two coins: one Lumis and one Zenth," you say, handing her the change. + +Denara smiles, clearly impressed. +"As always, you've got it right." diff --git a/exercises/practice/change/.meta/config.json b/exercises/practice/change/.meta/config.json new file mode 100644 index 0000000..e0942d9 --- /dev/null +++ b/exercises/practice/change/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "change.moon" + ], + "test": [ + "change_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "blurb": "Correctly determine change to be given using the least number of coins.", + "source": "Software Craftsmanship - Coin Change Kata", + "source_url": "https://web.archive.org/web/20130115115225/http://craftsmanship.sv.cmu.edu:80/exercises/coin-change-kata" +} diff --git a/exercises/practice/change/.meta/example.moon b/exercises/practice/change/.meta/example.moon new file mode 100644 index 0000000..825b933 --- /dev/null +++ b/exercises/practice/change/.meta/example.moon @@ -0,0 +1,51 @@ +-- Change making algorithm from +-- http://www.ccs.neu.edu/home/jaa/CSG713.04F/Information/Handouts/dyn_prog.pdf + +-- This function generates two arrays: +-- +-- C = maps the minimum number of coins required to make +-- change for each n from 1 to amount. +-- It is returned but only used internally in this +-- application. +-- +-- S = the _first_ coin used to make change for amount n +-- (actually stores the coin _index_ into the +-- denominations array) +-- +change = (amount, denominations) -> + C = {[0]: 0} + S = {} + + for p = 1, amount + min = math.maxinteger + coin_idx = nil + + for i = 1, #denominations + if denominations[i] <= p + if C[p - denominations[i]] < min + min = 1 + C[p - denominations[i]] + coin_idx = i + + C[p] = min + S[p] = coin_idx + + C, S + + +change_maker = (S, d, n) -> + result = {} + while n > 0 + coin = d[S[n]] + assert coin, "can't make target with given coins" + + table.insert result, 1, coin + n -= coin + result + + +{ + make_change: (amount, coins) -> + assert amount >= 0, "target can't be negative" + _, first_coin = change amount, coins + change_maker first_coin, coins, amount +} diff --git a/exercises/practice/change/.meta/spec_generator.moon b/exercises/practice/change/.meta/spec_generator.moon new file mode 100644 index 0000000..4f12a36 --- /dev/null +++ b/exercises/practice/change/.meta/spec_generator.moon @@ -0,0 +1,20 @@ +int_list = (list) -> "{#{table.concat list, ', '}}" + +{ + module_imports: {'make_change'}, + + generate_test: (case, level) -> + local lines + if case.expected.error + lines = { + "f = -> make_change #{case.input.target}, #{int_list case.input.coins}", + "assert.has.error f, #{quote case.expected.error}" + } + else + lines = { + "result = make_change #{case.input.target}, #{int_list case.input.coins}", + "expected = #{int_list case.expected}", + "assert.are.same expected, result" + } + table.concat [indent line, level for line in *lines], '\n' +} diff --git a/exercises/practice/change/.meta/tests.toml b/exercises/practice/change/.meta/tests.toml new file mode 100644 index 0000000..2d2f44b --- /dev/null +++ b/exercises/practice/change/.meta/tests.toml @@ -0,0 +1,49 @@ +# 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. + +[d0ebd0e1-9d27-4609-a654-df5c0ba1d83a] +description = "change for 1 cent" + +[36887bea-7f92-4a9c-b0cc-c0e886b3ecc8] +description = "single coin change" + +[cef21ccc-0811-4e6e-af44-f011e7eab6c6] +description = "multiple coin change" + +[d60952bc-0c1a-4571-bf0c-41be72690cb3] +description = "change with Lilliputian Coins" + +[408390b9-fafa-4bb9-b608-ffe6036edb6c] +description = "change with Lower Elbonia Coins" + +[7421a4cb-1c48-4bf9-99c7-7f049689132f] +description = "large target values" + +[f79d2e9b-0ae3-4d6a-bb58-dc978b0dba28] +description = "possible change without unit coins available" + +[9a166411-d35d-4f7f-a007-6724ac266178] +description = "another possible change without unit coins available" + +[ce0f80d5-51c3-469d-818c-3e69dbd25f75] +description = "a greedy approach is not optimal" + +[bbbcc154-e9e9-4209-a4db-dd6d81ec26bb] +description = "no coins make 0 change" + +[c8b81d5a-49bd-4b61-af73-8ee5383a2ce1] +description = "error testing for change smaller than the smallest of coins" + +[3c43e3e4-63f9-46ac-9476-a67516e98f68] +description = "error if no combination can add up to target" + +[8fe1f076-9b2d-4f44-89fe-8a6ccd63c8f3] +description = "cannot find negative change values" diff --git a/exercises/practice/change/change.moon b/exercises/practice/change/change.moon new file mode 100644 index 0000000..8d22b6c --- /dev/null +++ b/exercises/practice/change/change.moon @@ -0,0 +1,4 @@ +{ + make_change: (target_amount, denominations) -> + error 'Implement me' +} diff --git a/exercises/practice/change/change_spec.moon b/exercises/practice/change/change_spec.moon new file mode 100644 index 0000000..968227f --- /dev/null +++ b/exercises/practice/change/change_spec.moon @@ -0,0 +1,64 @@ +import make_change from require 'change' + +describe 'change', -> + it 'change for 1 cent', -> + result = make_change 1, {1, 5, 10, 25} + expected = {1} + assert.are.same expected, result + + pending 'single coin change', -> + result = make_change 25, {1, 5, 10, 25, 100} + expected = {25} + assert.are.same expected, result + + pending 'multiple coin change', -> + result = make_change 15, {1, 5, 10, 25, 100} + expected = {5, 10} + assert.are.same expected, result + + pending 'change with Lilliputian Coins', -> + result = make_change 23, {1, 4, 15, 20, 50} + expected = {4, 4, 15} + assert.are.same expected, result + + pending 'change with Lower Elbonia Coins', -> + result = make_change 63, {1, 5, 10, 21, 25} + expected = {21, 21, 21} + assert.are.same expected, result + + pending 'large target values', -> + result = make_change 999, {1, 2, 5, 10, 20, 50, 100} + expected = {2, 2, 5, 20, 20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100} + assert.are.same expected, result + + pending 'possible change without unit coins available', -> + result = make_change 21, {2, 5, 10, 20, 50} + expected = {2, 2, 2, 5, 10} + assert.are.same expected, result + + pending 'another possible change without unit coins available', -> + result = make_change 27, {4, 5} + expected = {4, 4, 4, 5, 5, 5} + assert.are.same expected, result + + pending 'a greedy approach is not optimal', -> + result = make_change 20, {1, 10, 11} + expected = {10, 10} + assert.are.same expected, result + + pending 'no coins make 0 change', -> + result = make_change 0, {1, 5, 10, 21, 25} + expected = {} + assert.are.same expected, result + + pending 'error testing for change smaller than the smallest of coins', -> + f = -> make_change 3, {5, 10} + assert.has.error f, "can't make target with given coins" + + pending 'error if no combination can add up to target', -> + f = -> make_change 94, {5, 10} + assert.has.error f, "can't make target with given coins" + + pending 'cannot find negative change values', -> + f = -> make_change -5, {1, 2, 5} + assert.has.error f, "target can't be negative"