2

I'm trying to write a program in Prolog, that does two things:

  1. Takes two arguments, an integer and a list of integers
  2. Is satisfied if change for the first argument can be made from some combination of elements from the second

I have come up with a solution to this problem that allows a change rule to unify to coin facts, as such:

coin(quarter, 25).
coin(dime,10).
coin(nickel,5).
coin(penny,1).

change(0, []).
change(A, [(C, Num)|L]) :- 
    coin(C, V), 
    A >= V, 
    Num is floor(A / V), 
    B is A mod V, 
    change(B, L).

Additionally, if you pass something like change(27,L) to the interpreter, it generates all possible combinations of quarters, dimes, nickels, and pennies that can be used to make change, like such:

?- change(27,L).
L = [(quarter, 1),  (penny, 2)] ;
L = [(dime, 2),  (nickel, 1),  (penny, 2)] ;
L = [(dime, 2),  (penny, 7)] ;
L = [(nickel, 5),  (penny, 2)] ;
L = [(penny, 27)] ;
false.

I'm having trouble, however, with how this can be solved by simply passing a currency list like [25,10,5,1] into change, making the call look something like change(27, [25,10,5,1], L). Is this possible and if so, how can it be done?

false
  • 10,264
  • 13
  • 101
  • 209
Josh
  • 43
  • 6
  • Not the same problem. I'm trying to generate change without a limit on the amount of each coin type available and for any amount. – Josh Apr 15 '19 at 20:37
  • You're right. It should be something like change(27, [25,10,5,1] , L). – Josh Apr 15 '19 at 20:42
  • Yes, change(27,[25,10,5,1], L). is the right one. This is not homework. – Josh Apr 15 '19 at 20:47
  • I'm trying to adapt a program that was originally written in Standard ML, so the instructions aren't exact. – Josh Apr 15 '19 at 20:50
  • Yes, that is also correct. The list does not need the name of the coin, just the coins necessary to make the change, like change(27, [25,10,5,1] , L) could just return [25,1,1] or [10,10,5,1,1]. – Josh Apr 15 '19 at 20:53
  • It would be conventional to use a parameter (let's call it `Coinage`) like `[quarter=25, dime=10, nickel=5, penny=1]` as your argument, and then just change `coin(C, V)` to `member(C=V, Coinage)`. – Daniel Lyons Apr 15 '19 at 20:54
  • so you have `[ (10, 2), (5, 1), (1, 2) ]` among the results, but `[ (10, 1), (5, 3), (1, 2) ]` is also a solution... – Will Ness May 17 '20 at 07:04

1 Answers1

3

So you're actually quite close. This is basically the comment I left, expanded. Let's add another argument for the types of coinage.

change(0, _, []).
change(A, Coinage, [(C, Num)|L]) :- 
    member(C=V, Coinage), 
    A >= V, 
    Num is floor(A / V), 
    B is A mod V, 
    change(B, Coinage, L).

I have replaced coin(C, V) with this member(C=V, Coinage) term. member/2 is a great way to make your Prolog program more dynamic, because rather than consulting the fact store, you can essentially generate solutions out of a list. Take a look at the result:

?- change(27, [quarter=25, dime=10, nickel=5, penny=1], Change).
Change = [(quarter, 1),  (penny, 2)] ;
Change = [(dime, 2),  (nickel, 1),  (penny, 2)] ;
Change = [(dime, 2),  (penny, 7)] ;
Change = [(nickel, 5),  (penny, 2)] ;
Change = [(penny, 27)] ;
false.

Let's see if we should add a 3-cent coin, perhaps with a recent president's face on it:

?- change(27, [quarter=25, dime=10, nickel=5, thripenny=3, penny=1], Change).
Change = [(quarter, 1),  (penny, 2)] ;
Change = [(dime, 2),  (nickel, 1),  (penny, 2)] ;
Change = [(dime, 2),  (thripenny, 2),  (penny, 1)] ;
Change = [(dime, 2),  (penny, 7)] ;
Change = [(nickel, 5),  (penny, 2)] ;
Change = [(thripenny, 9)] ;
Change = [(penny, 27)] ;

Looks like it would be a great idea!

Daniel Lyons
  • 22,421
  • 2
  • 50
  • 77
  • Thanks! This does what I want. As I mentioned, I'm trying to pick up Prolog from Standard ML. Since there is no fact base in this solution, does this mean that Prolog is not "unifying" anything? – Josh Apr 15 '19 at 21:13
  • 1
    @JoshB No, unification is happening whenever Prolog binds a value to a variable. In the sequence `member(C=V, Coinage), A >= V`, Prolog will unify C=quarter, V=25 first. Then it will attempt A >= V. If A is less than V, it will fail, backtrack, and then retry the member call with C=dime, V=10. (Really, it is unifying `C=V` with `quarter=25`, but unification is recursive on the structure of the term.) – Daniel Lyons Apr 15 '19 at 21:19