Coding challenge
Ben Rubinstein
benr_mc at cogapp.com
Fri Feb 1 14:45:47 EST 2013
I'm interested that nobody went recursive. My first solution (I read Mark's
email and stopped reading until I'd done a version) was
command testChange
local t
put empty into t
repeat 10 times
get random(99)
put format("to make %d:%s\n", it, makeChange(it)) after t
end repeat
put t
end testChange
function makeChange iAmount, tChangeSoFar
constant kCoins = "50 20 10 5 2 1"
if iAmount < 1 then return tChangeSoFar
repeat for each word c in kCoins
if iAmount >= c then
return makeChange(iAmount - c, tChangeSoFar && c)
end if
end repeat
return "error"
end makeChange
(note the especially crafty thing here: that by citing my test function, I've
justified leaving a leading space in my output...).
So far, effectively like the rest - I don't think the recursion is better, I
was just surprised that nobody else naturally went that way.
When I replaced the coins list with the unusual denominations of Craig$, 6
came out as 4+1+1, as you'd expect. Here I think is where recursion makes
things better, because it's easy to adjust:
function makeChange iAmount, tChangeSoFar
local tCandidates
constant kCoins = "40 30 10 4 3 1" -- 50 20 10 5 2 1"
if iAmount < 1 then return tChangeSoFar
put empty into tCandidates
repeat for each word c in kCoins
if iAmount < c then next repeat
put makeChange(iAmount - c, c) & return after tCandidates
end repeat
sort lines of tCandidates ascending numeric by the number of words in each
return tChangeSoFar && first line of tCandidates
end makeChange
With these small changes this works, and I'm confident returns the optimal
solution - but it gets shockingly slow for values above about 20.
The first reason it was so slow is because I'm being lazy above - allowing the
code to try options in the wrong order, as each call tries the full set of
coins again. I tried speeding it up by making it having found a coin it can
use, try with that and the next one down, but not all possibles. I'm
reasonably confident that this produced the right result for all cases with
Craig's currency - but I wasn't absolutely sure, and it's certainly not a
general solution, eg asked to make 27 out of "40 30 23 20 9 1" - admittedly a
really crazy currency - it would offer "23 1 1 1 1" rather than the optimal "9
9 9".
So in the end I had to rewrite it more substantially:
command testChange
constant kCoins = "40 30 10 4 3 1" -- "50 20 10 5 2 1"
local t
put empty into t
repeat 10 times
get random(99)
put format("to make %d:%s\n", it, makeChange(it, kCoins)) after t
end repeat
put t
end testChange
function makeChange iAmount, tCoins, tChangeSoFar
local tCandidates, i, c, j
if iAmount < 1 then return tChangeSoFar
-- trim coins that couldn't possibly be used
repeat with i = number of words in tCoins down to 1
if word i of tCoins > iAmount then delete word i of tCoins
end repeat
-- find possible combinations
put empty into tCandidates
repeat with i = 1 to number of words in tCoins
put word i of tCoins into c
put makeChange(iAmount - c, word i to -1 of tCoins, c) \
& return after tCandidates
end repeat
-- return shortest combination
sort lines of tCandidates ascending numeric by the number of words in each
return tChangeSoFar && first line of tCandidates
end makeChange
This definitely returns the optimal combination. It's quite slow (fractions
of a second, but noticeable fractions). But I feel entirely justified in
playing LiveCode's joker:
local saAmountCoins2Change
function makeChange iAmount, tCoins, tChangeSoFar
local tCandidates, i, c, j
if iAmount < 1 then return tChangeSoFar
-- have we done this before?
get saAmountCoins2Change[iAmount, tCoins]
if it <> empty then return tChangeSoFar && it
-- trim coins that couldn't possibly be used
repeat with i = number of words in tCoins down to 1
if word i of tCoins > iAmount then delete word i of tCoins
end repeat
-- find possible combinations
put empty into tCandidates
repeat with i = 1 to number of words in tCoins
put word i of tCoins into c
put makeChange(iAmount - c, word i to -1 of tCoins, c) \
& return after tCandidates
end repeat
-- return shortest combination
sort lines of tCandidates ascending numeric by the number of words in each
put first line of tCandidates into saAmountCoins2Change[iAmount, tCoins]
return tChangeSoFar && first line of tCandidates
end makeChange
Interestingly, even if the array is emptied before each run of tests, this
changes the typical time for a run of tests from approx 1 second for the ten,
to approx 7 milliseconds for the ten. Of course, if the array isn't emptied,
after a few runs it approaches zero. (If the array is emptied before each
single test, which could be described as fairer, it averages around 20
milliseconds for ten tests).
But of course, all of that is only necessary if we move to Craig's Crazy
Currency zone.
More information about the use-livecode
mailing list