Monday, March 30, 2009

make-change and make-best-change

This is one of the challenges presented on this page.
1. make-change
Find denominations of a given amount in given coins. Define your function to take the number of cents and an optional list of the available coins, largest to smallest, e.g., (make-change 72 '(25 10 1)). The default list should be (25 10 5 1).You can assume that the coins are such that the simple greedy algorithm will get the answer with the fewest coins.
Solution:
(defun make-change (amount &optional (coins '(25 10 5 1)))
(labels ((make-change-iter (amount coins result)
(cond ((null coins)
(apply #'values (nreverse result)))
((= amount 0)
(make-change-iter amount
(cdr coins) (cons 0 result)))
(t
(make-change-iter
(mod amount (car coins))
(cdr coins)
(push (floor amount (car coins)) result))))))
(make-change-iter amount coins nil)))

2. make-best-change
make-change may fail to find the best answer for certain sets of coins, where best means "uses the fewest coins." For example, if there are no nickels, then (make-change 30 '(25 10 1)) will return 1 quarter and 5 pennies, which is 6 coins, but 3 dimes would be better.
Define make-best-change to take the same arguments as make-change. make-best-change should return an answer that leaves the least amount of cents unaccounted for. If there's more than such answer, it should return a solution that uses the fewest coins.
Solution:
(defun make-best-change (amount &optional (coins '(25 10 5 1)))
(apply #'values
(butlast
(reduce #'(lambda (x y)
(cond ((< (car (last x)) (car (last y))) x)
((> (car (last x)) (car (last y))) y)
((< (sum-list (butlast x))
(sum-list (butlast y))) x)
(t y)))
(make-all-change amount coins)))))
;sum elements of a list
(defun sum-list (nums)
(reduce #'+ nums))
;this returns a list of all the possible changes with
;remaining amount appended at last of each list.
(defun make-all-change (amount coins)
(cond ((= amount 0) `(,(make-list
(1+ (length coins)) :initial-element 0)))
((null coins) `((,amount)))
((< amount (car coins))
(mapcar #'(lambda (x) (cons 0 x))
(make-all-change amount (cdr coins))))
(t (append
(mapcar
#'(lambda (x) (incf (car x)) x)
(make-all-change (- amount (car coins)) coins))
(mapcar #'(lambda (x) (cons 0 x))
(make-all-change amount (cdr coins)))))))

2 comments:

  1. a much shorter solution for make-change

    (defun make-change (amount &optional (coin-list '(25 10 5 1)))
    (let ((placeholder nil))
    (values-list (mapcar #'(lambda (x) (multiple-value-setq (placeholder amount)
    (floor amount x))) coin-list))))

    ReplyDelete
  2. yes it is and more "functional" also :)

    ReplyDelete