Skip to content

Commit

Permalink
Add 'change' exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
ageron committed Oct 8, 2024
1 parent 112108b commit 22b60a9
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,14 @@
"prerequisites": [],
"difficulty": 6
},
{
"slug": "change",
"name": "Change",
"uuid": "2660ff89-57d1-453e-b616-b76ac01c7eb9",
"practices": [],
"prerequisites": [],
"difficulty": 6
},
{
"slug": "connect",
"name": "Connect",
Expand Down
14 changes: 14 additions & 0 deletions exercises/practice/change/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Instructions

Correctly determine the fewest number of coins to be given to a customer such that the sum of the coins' value would equal the correct amount of change.

## For example

- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) or [5, 10]
- An input of 40 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) and one quarter (25) or [5, 10, 25]

## Edge cases

- Does your algorithm work for any given set of coins?
- Can you ask for negative change?
- Can you ask for a change value smaller than the smallest coin value?
32 changes: 32 additions & 0 deletions exercises/practice/change/.meta/Example.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module [findFewestCoins]

findFewestCoins : List U64, U64 -> Result (List U64) [NotFound]
findFewestCoins = \coins, target ->
help = \sortedCoins, subTarget, maxLength ->
if subTarget == 0 then
Ok []
else if maxLength == 0 then
Err NotFound
else

when sortedCoins is
[] -> Err NotFound
[largestCoin, .. as otherCoins] ->
if largestCoin == subTarget then
Ok [largestCoin]
else if largestCoin < subTarget then
when help sortedCoins (subTarget - largestCoin) (maxLength - 1) is
Ok otherCoinsWith ->
coinsWith = otherCoinsWith |> List.append largestCoin
when help otherCoins subTarget (List.len coinsWith - 1) is
Ok coinsWithout -> Ok coinsWithout
Err NotFound -> Ok coinsWith

Err NotFound -> help otherCoins subTarget maxLength
else
help otherCoins subTarget maxLength

help? (coins |> List.sortDesc) target Num.maxU64
|> List.sortAsc
|> Ok

19 changes: 19 additions & 0 deletions exercises/practice/change/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"ageron"
],
"files": {
"solution": [
"Change.roc"
],
"test": [
"change-test.roc"
],
"example": [
".meta/Example.roc"
]
},
"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"
}
18 changes: 18 additions & 0 deletions exercises/practice/change/.meta/template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{%- import "generator_macros.j2" as macros with context -%}
{{ macros.canonical_ref() }}
{{ macros.header() }}

import {{ exercise | to_pascal }} exposing [{{ cases[0]["property"] | to_camel }}]

{% for case in cases -%}
# {{ case["description"] }}
expect
coins = {{ case["input"]["coins"] | to_roc }}
result = coins |> {{ case["property"] | to_camel }} {{ case["input"]["target"] }}
{%- if case["expected"]["error"] %}
result |> Result.isErr
{%- else %}
result == Ok {{ case["expected"] | to_roc }}
{%- endif %}

{% endfor %}
50 changes: 50 additions & 0 deletions exercises/practice/change/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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"
include = false
5 changes: 5 additions & 0 deletions exercises/practice/change/Change.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module [findFewestCoins]

findFewestCoins : List U64, U64 -> Result (List U64) _
findFewestCoins = \coins, target ->
crash "Please implement the 'findFewestCoins' function"
84 changes: 84 additions & 0 deletions exercises/practice/change/change-test.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# These tests are auto-generated with test data from:
# https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json
# File last updated on 2024-10-08
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}

main =
Task.ok {}

import Change exposing [findFewestCoins]

# change for 1 cent
expect
coins = [1, 5, 10, 25]
result = coins |> findFewestCoins 1
result == Ok [1]

# single coin change
expect
coins = [1, 5, 10, 25, 100]
result = coins |> findFewestCoins 25
result == Ok [25]

# multiple coin change
expect
coins = [1, 5, 10, 25, 100]
result = coins |> findFewestCoins 15
result == Ok [5, 10]

# change with Lilliputian Coins
expect
coins = [1, 4, 15, 20, 50]
result = coins |> findFewestCoins 23
result == Ok [4, 4, 15]

# change with Lower Elbonia Coins
expect
coins = [1, 5, 10, 21, 25]
result = coins |> findFewestCoins 63
result == Ok [21, 21, 21]

# large target values
expect
coins = [1, 2, 5, 10, 20, 50, 100]
result = coins |> findFewestCoins 999
result == Ok [2, 2, 5, 20, 20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100]

# possible change without unit coins available
expect
coins = [2, 5, 10, 20, 50]
result = coins |> findFewestCoins 21
result == Ok [2, 2, 2, 5, 10]

# another possible change without unit coins available
expect
coins = [4, 5]
result = coins |> findFewestCoins 27
result == Ok [4, 4, 4, 5, 5, 5]

# a greedy approach is not optimal
expect
coins = [1, 10, 11]
result = coins |> findFewestCoins 20
result == Ok [10, 10]

# no coins make 0 change
expect
coins = [1, 5, 10, 21, 25]
result = coins |> findFewestCoins 0
result == Ok []

# error testing for change smaller than the smallest of coins
expect
coins = [5, 10]
result = coins |> findFewestCoins 3
result |> Result.isErr

# error if no combination can add up to target
expect
coins = [5, 10]
result = coins |> findFewestCoins 94
result |> Result.isErr

0 comments on commit 22b60a9

Please sign in to comment.