Thursday, August 26th, 2010 03:32 pm
A couple of days ago, Chris Yocum asked me to help him out with some OCaml code. He's learning OCaml at the moment, and thought he'd practice it by solving a problem he'd encountered in his research on early Irish law.

In early Irish law, murder was punishable by a fine dependent on the status of the victim. Ten-and-a-half cows per king murdered (no, really!), five for a major nobleman, and so on. At one particular event, we know the total fine levied, but not the number of victims. Chris wanted to find out the range of possibilities. This is an example of the change-making problem: given small change of various denominations, how can you give your customer 73p? I most often encounter this problem with rugby scores: if Wales beat England 67 - 3 (I can dream...), then at seven points for a converted try, five for an unconverted try and three for a penalty, what might have happened?

Chris had already written a Perl program that looked very much like this:
#!/usr/bin/perl                                                                 

use warnings;
use strict;

my $target = 23; # or whatever it really was
my $w_val = 10;
my $x_val = 5;
my $y_val = 2;
my $z_val = 1;

for my $w (0 .. $target / $w_val) {
    for my $x (0 .. $target/$x_val) {
        for my $y (0 .. $target / $y_val) {
            for my $z (0 .. $target / $z_val) {
                if ($w*$w_val + $x*$x_val + $y*$y_val + $z*$z_val == $target) {
                    print "$w, $x, $y, $z\n";
                }
            }
        }
    }
}

There are a few things to criticise in this program: it searches a lot of possibilities it can rule out (once $w and $x are too big, it doesn't matter what $y and $z are), it repeats a lot of calculations in the inner loops (more on that later), and the number of different social classes is baked into the structure of the program - if the early Irish lawmakers ever decide to institute a new class of noble, we'd have to modify our code rather than just changing the values of some parameters. But it's simple, easy to read, obviously correct and easy to write.

Chris wanted to re-write this in OCaml, which is a (mostly) functional language, and iteration is considered poor style by functional devotees. Could I help him re-write it to use recursion instead?

At this point I'm afraid I seized on the word "recursion", because, you see, there's an elegant recursive solution to the change-making problem and I thought he was asking about that. Somewhere between my wanting to entirely re-write the program using a new algorithm, it being trickier to write than I'd expected, and my not actually knowing OCaml very well, I managed to completely confuse him. Sorry, Chris. Anyway, we'll come back to the first program we wrote in a bit; here's what I think he was actually asking for:
let rec range i j = if i > j then [] else i :: (range (i+1) j)

let poss_range target value = range 0 (target/value)

let concat_map f xs = List.concat (List.map f xs)

let make_quad w x y z = (w,x,y,z)

let is_good target quad =
  match quad with
  (w,x,y,z) -> (w * 10 + x * 5 + y * 2 + z * 1) = target

let ways target = List.filter (is_good target)
  (concat_map
    (fun w -> concat_map
      (fun x -> concat_map
        (fun y -> List.map
          (make_quad w x y)
          (poss_range target 1))
        (poss_range target 2))
      (poss_range target 5))
    (poss_range target 10))

I think that's pretty clear: the only slightly tricksy bit is the use of the partially-applied function make_quad w x y. It's a couple of lines shorter than the original Perl version, but suffers from all of its flaws. It would be fairly easy to reduce the size of the search space - change the first argument passed to poss_range - but it's more interesting to fix the restriction to a fixed set of denominations. Here's an attempt to do that:
(* General utilities *)

let rec range i j = if i > j then [] else i :: (range (i+1) j)

let cons x xs = x::xs

let singleton x = [x]

let sum = List.fold_left (fun x y -> x + y) 0


(* dot product - x1*y1 + x2*y2 + x3*y3 + ... *)
let dot xs ys = sum (List.map2 (fun x y -> x * y) xs ys)

(* finite choice functions - ways of choosing one elt from each list *)
let rec choices xss =
  match xss with
    [] -> []
  | [xs] -> List.map singleton xs
  | xs::rest -> List.concat (List.map (fun x -> List.map (cons x) (choices rest)) xs)

(* functions specific to the problem at hand *)

let ranges target values = List.map (fun x -> range 0 (target / x)) values

let candidates target values = choices (ranges target values)

let isgood target values xs = (dot values xs) = target

let ways target values = List.filter (isgood target values) (candidates target values)

It doesn't look much like the previous version, but it works in exactly the same way: we iterate over all possible combinations of victim numbers, and check each one (using List.filter) to see if it gives the desired result. The meat of the program is the function choices, which generates finite choice functions. Given a list of lists [xs1;xs2;xs3;xs4;...] it will produce all lists whose first element is chosen from xs1, whose second element is chosen from xs2, etc. I believe it's considered poor style to use naked recursion like that (as opposed to using a higher-order function like List.map or List.fold_left), but frankly that function was the hardest thing I had to write in this whole exercise, and I was happy just to get something that worked.

Now, let's look at a more efficient approach.

The key to solving this problem efficiently is to realise that choosing a "first move" leaves us with a smaller example of the same type of problem. Let's suppose, as above, that Wales scored 67 points, and let's suppose that we've heard that they scored four converted tries. To work out the full space of possibilities, we just need to calculate all the ways of making 67 - 4*7 = 39 points with unconverted tries and penalties. But that's also a change-making problem! This means we can solve the problem recursively, and that's what I thought Chris was asking about. If we only have one denomination left to play with, then there's at most one way to solve the problem: return the list containing only target/value if value divides target evenly or the empty list (to indicate "no solutions") if it doesn't. If we have more than one denomination, we make all possible choices of "first move", and then recursively solve all the new, smaller problems that each choice presents us with.

Here's the code that we wrote to do this:
(* things that are probably in a standard library, if only I knew where to look
for them *)
let rec range i j = if i > j then [] else i :: (range (i+1) j)

let cons x xs = x::xs

let fuzz = 0.00001

(* from http://www.podval.org/~sds/ocaml-sucks.html *)
let round x = int_of_float (floor (x +. 0.5))

let float_eq x y = abs_float (x -. y) <= fuzz

let divides n d = let q = n /. d in float_eq q (float_of_int (truncate q))

(* the actual meat of the code *)
let rec ways ps tgt =
  match ps with
    [] -> []
  | [p] -> if (divides tgt p && tgt >= 0.) then [[round (tgt /. p)]] else []
  | p::rest -> List.concat (List.map (helper p rest tgt)
                  (range 0 (round (tgt /.p))))
and
  helper p ps tgt n =
    let ws = ways ps (tgt -. (float_of_int n) *. p) in
      List.map (cons n) ws

(* test code *)
let values = [10.;5.;2.;1.]

let target = 23.

let answers = ways values target

let add x y = x +. y

let dot xs ys = List.fold_left add 0.
  (List.map2 (fun x y -> x *. (float_of_int y)) xs ys)

let sums = List.map (dot values) answers

let errors = List.length (List.find_all (fun x -> not (float_eq x target)) sums)

let _ = print_endline ((string_of_int errors) ^ " errors.")

[One thing I didn't mention - though I can't remember the exact values in Chris' original problem, the death-price for a king really was 10.5 cows. Hence our program had to handle fractional denominations, which makes it a lot uglier than it would otherwise need to be - OCaml's lack of polymorphism means that it needs separate operators (called +., *. etc.) for floating-point numbers, and we need to strew conversion functions about the place.]

A refinement of this technique is to memoize the recursive function - this typically helps a lot, because you need to solve many of the same inner subproblems again and again. If you use the memoized version, the technique's called dynamic programming (a name chosen purely because it sounded impressive), which is a useful technique in all kinds of search applications. Some quick experiments indicate that it's not a win in this case, though - the memory requirements grow too rapidly.

Could we have written the recursive version in Perl? Sure:
#!/usr/bin/perl

use strict;
use warnings;

sub ways {
    my $target = shift;
    my $first = shift;
    if (scalar(@_) == 0 && $target >= 0 && ($target % $first == 0)) {
        return ([$target/$first]);
    }
    my @ways;
    for my $move (0 .. ($target/$first)) {
        push @ways, [$move, @$_] foreach ways($target - $move * $first, @_);
    }
    return @ways;
}
foreach (ways(23, 10,5,2,1)) { print join ",", @$_)."\n" };

[This version, by the way, was easily the most fun to write.]

I think it's interesting how different the equivalent Perl and OCaml versions look in this case: they're about the same length, but the Perl version is one subroutine, and the Caml version's divided into several smaller routines. This is because I find long subroutines in functional languages to be devilishly hard to write correctly - splitting expressions out into their own top-level functions makes it easier to ask the compiler what types it's inferring for them.

All suggestions for improvement, as ever, gratefully accepted :-)
Thursday, August 26th, 2010 02:43 pm (UTC)
I haven't had time to really read through and think about this but thank you for the post, this is fascinating! OCaml is next on my list to learn, so seeing the same algorithm in it and Perl is really useful!
Thursday, August 26th, 2010 02:48 pm (UTC)
I haven't had time to really read through and think about this

I'm not surprised - I only posted it a minute ago!
Thursday, August 26th, 2010 02:50 pm (UTC)
OCaml is next on my list to learn, so seeing the same algorithm in it and Perl is really useful!

I'm far from being an expert on the language, and I'm probably violating all kinds of style guidelines and missing out on helpful features and libraries that would have made my life easier (for instance, there must be an easier way of passing operators as arguments to higher-order functions!). Hopefully some real experts will drop in and enlighten us all.
Thursday, August 26th, 2010 02:57 pm (UTC)
Last time I looked one could use just (+) instead of fun x y -> x + y.
Thursday, August 26th, 2010 03:02 pm (UTC)
Cheers! But that doesn't seem to work in general - (::) gives me a syntax error. Still, I could clean up the definition of sum.
Thursday, August 26th, 2010 03:02 pm (UTC)
There's a simple and recursive way of counting coins described in the SICP book which, loosely translated to ocaml, would go something like this:

let rec intlist_repr = function
  | [] -> ""
  | h::t -> printf.sprintf "%d %s" h ( intlist_repr t )


let rec ways_of total choices accum =

  if total = 0 then
    printf.printf "%s\n%!" (intlist_repr accum)

  else if total > 0 then
    match choices with
    | x::xs ->
        ways_of (total - x) choices (x::accum);
        ways_of (total) xs accum;
    | _ -> ()


let _ =
  ways_of 23 [10; 5; 2; 1] []

Thursday, August 26th, 2010 03:13 pm (UTC)
Very neat!

[For those following along at home: it doesn't compile as [livejournal.com profile] elfz has written it. You need to change printf.sprintf to Printf.sprintf and printf.printf to Printf.printf.]
Thursday, August 26th, 2010 03:19 pm (UTC)
Aaargh, obviously, I had an "open Printf" as the first line, and while posting I minified and inlined that without checking.
Thursday, August 26th, 2010 03:25 pm (UTC)
:-)
[identity profile] http://www.google.com/profiles/cyocum (from livejournal.com)
Thursday, August 26th, 2010 03:26 pm (UTC)

The fine amount is given in cumals which is Old Irish for "female slave" so if you want to calculate the amount in bó mlicht "milch cow" then it is 3 milch cows for 1 cumal (female slave) so a king is 7 cumals or 21 milch cows.

Like I said, a nit-pick and a rather interesting look in to the early Irish monetary system.

Thursday, August 26th, 2010 03:29 pm (UTC)
Curse my fallible memory :-)
Thursday, August 26th, 2010 03:41 pm (UTC)
If we only have one denomination left to play with, then there's at most one way to solve the problem

This is an optimization: for clarity, you might omit it, and let "no denominations left" be your own only base case.

I don't have OCaml lying around, but here's a first pass at the problem in Lisp, which should be similar, if slightly more verbose. Note also how I've avoided the need to do a "map" by building up a partial solution in the arguments to the recursive calls.
(defun ways (amount coins &optional prepend (count 0))
  "Return all ways of making change for AMOUNT using COINS.
AMOUNT is an integer and COINS a list of integers.
The list PREPEND is prepended to all solutions.
COUNT is added to the number of times the first coin is used."
  (if (endp coins)
      (if (= amount 0) (list prepend) nil)
    (let* ((coin (first coins))
           (unused (ways amount (rest coins) (append prepend (list count)))))
      (if (< amount coin)
          unused
        (append unused (ways (- amount coin) coins prepend (1+ count)))))))

(ways 23 '(10 5 2 1))
  ==> ((0 0 0 23) (0 0 1 21) (0 0 2 19) (0 0 3 17) (0 0 4 15) (0 0 5 13) (0 0 6 11) ...)
To an experienced Lisper, the presence of append in there is a bit of a red flag that there may be unnecessary allocation and list scanning going on. It's straightforward to eliminate one of the calls to append by putting even more of the solution into the arguments:
(defun ways (amount coins &optional prepend (count 0) more)
  "Return all ways of making change for AMOUNT using COINS.
AMOUNT is an integer and COINS a list of integers.
The list PREPEND is prepended to solutions.
COUNT is added to the number of times the first coin is used.
MORE is appended to the list of solutions."
  (if (endp coins)
      (if (= amount 0) (cons prepend more) more)
    (let* ((coin (first coins)))
      (ways amount (rest coins) (append prepend (list count)) 0
            (if (< amount coin)
                more
              (ways (- amount coin) coins prepend (1+ count) more))))))
This also has the benefit of putting one of the recursive calls into tail position, which will help the optimizer. Your mileage may differ as to whether the increase in speed is worth the decrease in readability.

We could eliminate the other call to append if we were willing to get reversed solutions (i.e. with the counts in opposite order to the coins list). I guess this depends on what the output is going to be used for.
Thursday, August 26th, 2010 04:22 pm (UTC)
Adding auxiliary arguments is a pretty standard technique in functional programming. The classic example is the factorial function (here, in Standard ML):
fun factorial 0 = 1
  | factorial n = n * factorial (n - 1)
which can be made tail-recursive by transforming it to
fun factorial n = 
    let fun factorial* 0 x = x
          | factorial* n x = factorial* (n-1) (n * x)
    in factorial* n 1
    end
Incidentally, when you have a fixed set of coins, and you're only interested in the number of ways to make change, there's a mathematical closed form for the answer. See, for example, Concrete Mathematics pages 344–346 ("the number of ways to change $1,000,000 is ... 66666793333412666685000001").
Thursday, August 26th, 2010 04:45 pm (UTC)
I'd encountered the general technique, but your use of it with count is IMHO rather elegant.

Thanks for the reference - I'll try to dig up a copy.
Thursday, August 26th, 2010 06:48 pm (UTC)
Ooh, thank you for this post, [livejournal.com profile] gareth_rees! I'm trying to learn to Lisp at the moment, and your code's really helpful!
[identity profile] http://www.google.com/profiles/cyocum (from livejournal.com)
Thursday, August 26th, 2010 03:52 pm (UTC)

One last thing. If anyone wants to have a look at the original article, I have a copy on my Informatics website (http://homepages.inf.ed.ac.uk/v1cyocum/) under the title of "Early Irish Law, Annals, and Computer Science". It is not finished but most of the information is there. I need to clean up the actual computer science section due to finding the actual algorithm but the code is correct and the output is correct.

Thursday, August 26th, 2010 04:05 pm (UTC)
I don't actually know any OCaml, and thus won't offer any improvements in that language. However, the following was reasonably easy to cobble together - and returns values in the format of > List of List of Number, Coinsize Pairs.

module Coinage where

import Control.Monad
import Data.List (nub,sort,(\\))

makeCoinage :: Int -> [Int] -> [[(Int,Int)]]
makeCoinage 0 _ = [[]]
makeCoinage _ [] = []
makeCoinage n denoms = nub . sort $ do
  d <- denoms
  guard $ d <= n
  k <- [1..n `div` d]
  c <- makeCoinage (n-k*d) (denoms \\ [d])
  return $ (k,d):c
Thursday, August 26th, 2010 04:06 pm (UTC)
Oh, and of course I miss mentioning it. This is Haskell, and uses the »list monad action gives a BFS« idiom extensively.
Thursday, August 26th, 2010 04:23 pm (UTC)
Neat! I was thinking this morning that the List monad would probably provide an elegant approach, but my Haskell's too rusty for me to have done it myself. Your solution creates some duplicates, though: it considers [(1,1),(1,2),(1,10),(2,5)] and [(1,1),(1,2),(2,5),(1,10)] to be separate solutions. I think you meant nub $ map sort instead of nub . sort.

[BTW, I frequently found myself missing the $ operator when writing OCaml. It reduces the profusion of brackets so effectively. Does anyone know if there's an OCaml operator that does the same?]
Thursday, August 26th, 2010 06:51 pm (UTC)
One could probably cheat for some cases and emulate the $ by using definitions in reverse:

# let ($) a b = b a;;
...

# 1 + 2 $ Printf.printf "%d";;
3

# [1; 2; 3; 4] $ List.rev $ List.map float_of_int $ List.map sin ;;
[...weird floats, stupid example...]
Thursday, August 26th, 2010 08:01 pm (UTC)
I did miss that. Also, I suspect that there should be a
filter (\lst -> n == foldr (\s (k,d) -> s+k*d) lst)
in that pipeline too - I'm realizing that I probably don't guarantee summability to full ...

actually ...

wait ...

DAMN! Yes, I deal with the case "This doesn't sum to the right number" with my case handling the parameters _ [].
Impressed by Haskell yet again.
Thursday, August 26th, 2010 11:04 pm (UTC)
Huh, so it does. That's rather neat :-)
Thursday, August 26th, 2010 05:07 pm (UTC)
Here's a slight modification, which doesn't produce and discard duplicates:

module Coinage where

import Control.Monad
import Data.List (nub,sort,(\\))

makeCoinage :: Int -> [Int] -> [[(Int,Int)]]
makeCoinage 0 _ = [[]]
makeCoinage _ [] = []
makeCoinage n (d:denoms) = do
  k <- [0..n `div` d]
  c <- makeCoinage (n-k*d) denoms
  return $ (k,d):c
Thursday, August 26th, 2010 08:02 pm (UTC)
Pretty. Now let's golf it. ;-)
Thursday, August 26th, 2010 04:11 pm (UTC)

This doesn’t pay any attention to the algorithmic problems with the original, but it does treat the denominations as data rather than code; and while it’s not quite as easy to read as Chris’s version, it was easy enough to write that it worked first time.

use List::Util qw<sum>;

sub make_loop {
    my ($denomination, $inner) = @_;
    return sub {
        my ($target, @coins) = @_;
        for my $n (0 .. $target / $denomination) {
            if ($n != 0) {
                push @coins, $denomination;
                print "@coins\n" if sum(0, @coins) == $target;
            }
            $inner->($target, @coins) if $inner;
        }
    };
}

sub find_ways {
    my ($target, @denominations) = @_;
    my $loop;
    $loop = make_loop($_, $loop) for sort { $a <=> $b } @denominations;
    $loop->($target) if $loop;
}

find_ways(23,  10, 5, 2, 1);

To take the other problems with the original:

  1. Searching a lot of possibilities that can be ruled out. If the coin denominations are sorted (as they here and in the original), then the function returned by make_loop merely needs to bail out if the running total has gone over the limit.
  2. Repeating a lot of calculations in the inner loop. The simplistic approach to this is memoisation, as you suggest. An alternative would be to replace the simple list of coin values with a better data structure that keeps a running total; that would also make it easy to improve the output.

I confess I’ve never been very interested in purity questions relating to iteration versus recursion, or functional versus imperative algorithms. This approach is clearly imperative, in the sense that it relies on updatable variables. But the variables in question are always local to a particular function (so, except for emitting output, the functions seem pure when examined from the outside); and this sorted of nested-generated-closure approach is fairly FP-ish anyway.

Thursday, August 26th, 2010 04:31 pm (UTC)
The way I see it, there's very little difference between updatable local state and accumulator parameters. The evil thing is global state.

And that implementation is messing with my head :-)
Thursday, August 26th, 2010 04:43 pm (UTC)

The way I see it, there's very little difference between updatable local state and accumulator parameters. The evil thing is global state.

Quite.

And that implementation is messing with my head :-)

It’s perhaps easier to grok if you look at this first:

loop(10, loop(5, loop(2, loop(1, undef))));

Or at least, that was the first thing I wrote down; the rest was just making it work. It really is the same algorithm as Chris’s, but using programmatically-generated nested closures (with a single loop per closure) in place of nested loops.

Thursday, August 26th, 2010 05:01 pm (UTC)

And apologies for spamming your LJ with my code, but this version is an improvement in two ways:

  • It avoids searching possibilities that can be ruled out as soon as the runnning total exceeds the target
  • It returns a list of the solutions, rather than just printing them out
sub find_ways {
    my ($target, @denominations) = @_;
    my ($loop, @solutions);
    for my $denomination (sort { $a <=> $b } @denominations) {
        my $inner = $loop;
        $loop = sub {
            my ($target, @coins) = @_;
            for my $n (0 .. $target / $denomination) {
                if ($n != 0) {
                    push @coins, $denomination;
                    my $sum = sum(0, @coins);
                    return                    if $sum >  $target;
                    push @solutions, [@coins] if $sum == $target;
                }
                $inner->($target, @coins) if $inner;
            }
        };
    }
    $loop->($target) if $loop;
    return @solutions;
}

print "@$_\n" for find_ways(23,  10, 5, 2, 1);
Thursday, August 26th, 2010 05:10 pm (UTC)
No apologies necessary - this is exactly the sort of thing I was hoping for.
Thursday, August 26th, 2010 04:42 pm (UTC)
OK, I've got it now. I was wondering where $inner got set, because I somehow hadn't noticed that make_loop is called once for every element of @denominations, with the previously-generated loop as its second parameter.

*smacks forehead*
Friday, August 27th, 2010 04:39 pm (UTC)

So, I was thinking more about this problem. If you consider a much smaller variant, with a target of 2, and just a 1-unit coin, then you end up considering the possibilities {[0], [1], [2]}. If you have the same target, and denominations 1 and 2, you consider {[0,0], [0,1], [0,2], [1,0], [1,1], [1,2]} (modulo the possibility that some candidates get optimised out early). This starts to look a lot like a counting problem, but with mixed-unit bases.

So, to go back to the target=23, denominations={1, 2, 5, 10} problem: we know that any answer will have a maximum of 2 tens, 4 fives, 11 twos, and 23 ones; (2+1)×(4+1)×(11+1)×(23+1) = 4320, so we need to consider no more than 4320 cases. Let’s assign a non-negative integer to each of those cases, so that [0 tens, 0 fives, 0 twos, 0 ones] is 0; [1 ten, 2 fives, 3 twos, 4 ones] is 4×1 + 3×24 + 2×12×24 + 1×5×12×24; and so on up to the maximum of 23×1 + 11×24 + 4×12×24 + 2×5×12×24 = 4319. Now iterating over the candidates can be as simple as counting from 0 to 4319; no recursion needed at all.

In practice, however, implementing the arithmetic manually over a list of “digits” is probably easier; at the least, it simplifies implementation of the optimisation that avoids adding more of the current denomination of coin when we’ve already exceeded the target. So the code then looks like this:

use List::Util qw<sum>;
use List::MoreUtils qw<pairwise>;

sub find_ways {
    my ($target, @denominations) = @_;
    @denominations = sort { $a <=> $b } @denominations;
    my @limit = map { int $target / $_ } @denominations;
    my @curr = map { 0 } @denominations;
    my @solutions;
  Candidate: for (;;) {
        my $place = -1;
        for (;;) {
            last Candidate if ++$place >= @curr;
            if ($curr[$place] < $limit[$place]) {
                $curr[$place]++;
                last;
            }
            else {
                $curr[$place] = 0;
            }
        }

        my $sum = sum(0, pairwise { $a * $b } @curr, @denominations);
        if ($sum > $target) {
            $curr[$place] = $limit[$place]; # skip impossible candidates
        }
        elsif ($sum == $target) {
            push @solutions,
                [map { ($denominations[$_]) x $curr[$_] } reverse 0 .. $#curr];
        }
    }
    return @solutions;
}

It’s also really obvious how you’d refactor this to use an explicit iterator in languages without generators or lazy lists or the like, in case generating all the solutions at once seems like the wrong thing.

Sunday, August 29th, 2010 02:29 pm (UTC)

Just one more version. The problem with that as written is that it actually runs substantially slower the nested-closure version. Profiling says that the biggest problem is the sum-of-pairwise-multiplication step; that seems plausible, because Perl’s subroutine calls are relatively slow. Refactoring to keep a running total fixes that. For small problems, the nested-closure implementation wins, but for big enough problems, the running-total version has the edge (and that edge increases with the size of the problem). For context, on my laptop, “small” problems (with denominations 1,2,5,10) are those with a target up to about 41 or 42.

sub find_ways {
    my ($target, @denominations) = @_;
    @denominations = sort { $a <=> $b } @denominations;
    my @limit = map { int $target / $_ } @denominations;
    my @curr = (0) x @denominations;
    my $sum = 0;
    my @solutions;
  Candidate: for (;;) {
        my $place = -1;
        for (;;) {
            last Candidate if ++$place >= @curr;
            if ($curr[$place] < $limit[$place]) {
                $curr[$place]++;
                $sum += $denominations[$place];
                last;
            }
            else {
                $sum -= $denominations[$place] * $curr[$place];
                $curr[$place] = 0;
            }
        }

        if ($sum > $target) {
            $sum += $denominations[$place] * ($limit[$place] - $curr[$place]);
            $curr[$place] = $limit[$place]; # skip impossible candidates
        }
        elsif ($sum == $target) {
            push @solutions,
                [map { ($denominations[$_]) x $curr[$_] } reverse 0 .. $#curr];
        }
    }
    return @solutions;
}