Skip to content

Commit bd739c2

Browse files
sync change (#571)
1 parent a9e40fa commit bd739c2

File tree

6 files changed

+144
-38
lines changed

6 files changed

+144
-38
lines changed
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
# Instructions
22

3-
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.
3+
Determine the fewest number of coins to give a customer so that the sum of their values equals the correct amount of change.
44

5-
## For example
5+
## Examples
66

7-
- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) or [5, 10]
8-
- 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]
9-
10-
## Edge cases
11-
12-
- Does your algorithm work for any given set of coins?
13-
- Can you ask for negative change?
14-
- Can you ask for a change value smaller than the smallest coin value?
7+
- 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].
8+
- 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].
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Introduction
2+
3+
In the mystical village of Coinholt, you stand behind the counter of your bakery, arranging a fresh batch of pastries.
4+
The door creaks open, and in walks Denara, a skilled merchant with a keen eye for quality goods.
5+
After a quick meal, she slides a shimmering coin across the counter, representing a value of 100 units.
6+
7+
You smile, taking the coin, and glance at the total cost of the meal: 88 units.
8+
That means you need to return 12 units in change.
9+
10+
Denara holds out her hand expectantly.
11+
"Just give me the fewest coins," she says with a smile.
12+
"My pouch is already full, and I don't want to risk losing them on the road."
13+
14+
You know you have a few options.
15+
"We have Lumis (worth 10 units), Viras (worth 5 units), and Zenth (worth 2 units) available for change."
16+
17+
You quickly calculate the possibilities in your head:
18+
19+
- one Lumis (1 × 10 units) + one Zenth (1 × 2 units) = 2 coins total
20+
- two Viras (2 × 5 units) + one Zenth (1 × 2 units) = 3 coins total
21+
- six Zenth (6 × 2 units) = 6 coins total
22+
23+
"The best choice is two coins: one Lumis and one Zenth," you say, handing her the change.
24+
25+
Denara smiles, clearly impressed.
26+
"As always, you've got it right."
Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
11
return function(amount, values)
2-
local index, sorted_values, result = {}, {}, {}
2+
assert(amount >= 0, "target can't be negative")
3+
if amount == 0 then
4+
return {}
5+
end
36

4-
for i, v in ipairs(values) do
5-
index[v] = i
6-
sorted_values[i] = v
7+
local counts = {}
8+
local coins = {}
9+
for total = 1, amount do
10+
local best_count = amount + 1
11+
local best_coin = 0
12+
for _, value in ipairs(values) do
13+
if value == total then
14+
best_count = 1
15+
best_coin = value
16+
elseif total > value then
17+
local count = counts[total - value] + 1
18+
if count < best_count then
19+
best_count = count
20+
best_coin = value
21+
end
22+
end
23+
end
24+
table.insert(counts, best_count)
25+
table.insert(coins, best_coin)
726
end
827

9-
table.sort(sorted_values, function(a, b)
10-
return b < a
11-
end)
28+
assert(counts[amount] <= amount, "can't make target with given coins")
1229

13-
for i, value in ipairs(sorted_values) do
14-
result[index[value]] = amount // value
15-
amount = amount % value
30+
local result = {}
31+
local remaining = amount
32+
while remaining > 0 do
33+
table.insert(result, coins[remaining])
34+
remaining = remaining - coins[remaining]
1635
end
17-
18-
return amount == 0 and result or nil
36+
return result
1937
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
local function render_coins(coins)
2+
return table.concat(coins, ', ')
3+
end
4+
5+
return {
6+
module_name = 'change',
7+
8+
generate_test = function(case)
9+
if case.expected.error then
10+
local template = [[
11+
assert.has.error(function()
12+
change(%s, { %s })
13+
end, "%s")]]
14+
return template:format(case.input.target, render_coins(case.input.coins), case.expected.error)
15+
else
16+
local template = [[
17+
assert.same({ %s }, change(%s, { %s }))]]
18+
return template:format(render_coins(case.expected), case.input.target, render_coins(case.input.coins))
19+
end
20+
end
21+
}

exercises/practice/change/.meta/tests.toml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
# This is an auto-generated file. Regular comments will be removed when this
2-
# file is regenerated. Regenerating will not touch any manually added keys,
3-
# so comments can be added in a "comment" key.
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[d0ebd0e1-9d27-4609-a654-df5c0ba1d83a]
13+
description = "change for 1 cent"
414

515
[36887bea-7f92-4a9c-b0cc-c0e886b3ecc8]
616
description = "single coin change"
@@ -23,6 +33,9 @@ description = "possible change without unit coins available"
2333
[9a166411-d35d-4f7f-a007-6724ac266178]
2434
description = "another possible change without unit coins available"
2535

36+
[ce0f80d5-51c3-469d-818c-3e69dbd25f75]
37+
description = "a greedy approach is not optimal"
38+
2639
[bbbcc154-e9e9-4209-a4db-dd6d81ec26bb]
2740
description = "no coins make 0 change"
2841

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,62 @@
11
local change = require('change')
22

33
describe('change', function()
4-
it('should generate the correct change when there is only one type of coin', function()
5-
assert.same({ 5 }, change(5, { 1 }))
4+
it('change for 1 cent', function()
5+
assert.same({ 1 }, change(1, { 1, 5, 10, 25 }))
66
end)
77

8-
it('should generate the correct change when there are multiple coin types', function()
9-
assert.same({ 5, 0 }, change(5, { 1, 10 }))
8+
it('single coin change', function()
9+
assert.same({ 25 }, change(25, { 1, 5, 10, 25, 100 }))
1010
end)
1111

12-
it('should generate the correct change when multiple types of coins are needed', function()
13-
assert.same({ 3, 1, 1 }, change(18, { 1, 5, 10 }))
12+
it('multiple coin change', function()
13+
assert.same({ 5, 10 }, change(15, { 1, 5, 10, 25, 100 }))
1414
end)
1515

16-
it('should return nil if it is not possible to make change', function()
17-
assert.is_nil(change(3, { 5, 10, 25 }))
16+
it('change with lilliputian coins', function()
17+
assert.same({ 4, 4, 15 }, change(23, { 1, 4, 15, 20, 50 }))
1818
end)
1919

20-
it('should generate the correct change given any coin order', function()
21-
assert.same({ 3, 1, 1 }, change(18, { 1, 5, 10 }))
22-
assert.same({ 1, 1, 3 }, change(18, { 10, 5, 1 }))
20+
it('change with lower elbonia coins', function()
21+
assert.same({ 21, 21, 21 }, change(63, { 1, 5, 10, 21, 25 }))
2322
end)
2423

25-
it('should generate the correct change for large values with many coins', function()
26-
assert.same({ 3, 1, 0, 1, 1 }, change(133, { 1, 5, 10, 25, 100 }))
24+
it('large target values', function()
25+
assert.same({ 2, 2, 5, 20, 20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100 },
26+
change(999, { 1, 2, 5, 10, 20, 50, 100 }))
27+
end)
28+
29+
it('possible change without unit coins available', function()
30+
assert.same({ 2, 2, 2, 5, 10 }, change(21, { 2, 5, 10, 20, 50 }))
31+
end)
32+
33+
it('another possible change without unit coins available', function()
34+
assert.same({ 4, 4, 4, 5, 5, 5 }, change(27, { 4, 5 }))
35+
end)
36+
37+
it('a greedy approach is not optimal', function()
38+
assert.same({ 10, 10 }, change(20, { 1, 10, 11 }))
39+
end)
40+
41+
it('no coins make 0 change', function()
42+
assert.same({}, change(0, { 1, 5, 10, 21, 25 }))
43+
end)
44+
45+
it('error testing for change smaller than the smallest of coins', function()
46+
assert.has.error(function()
47+
change(3, { 5, 10 })
48+
end, "can't make target with given coins")
49+
end)
50+
51+
it('error if no combination can add up to target', function()
52+
assert.has.error(function()
53+
change(94, { 5, 10 })
54+
end, "can't make target with given coins")
55+
end)
56+
57+
it('cannot find negative change values', function()
58+
assert.has.error(function()
59+
change(-5, { 1, 2, 5 })
60+
end, "target can't be negative")
2761
end)
2862
end)

0 commit comments

Comments
 (0)