;;; -*- Mode:LISP; Readtable:CL; Base:10 -*-

;;; There are often several ways to write a simple function in LISP. No,
;;; I don't mean the right and wrong ways -- I mean, the "normal" way
;;; the function might be written in a procedural language, versus the
;;; "LISP" ways, which just sometimes, might be "better" if more
;;; legible, or more easily modified, etc.

;;; Here's a standard implementation of the COINS algorithm.  Given an
;;; input coin value, what combination of coins -- quarters, dimes,
;;; nickels, and pennies -- yields the desired value with the minimum
;;; number of coins?  [Ref.: HARDCOPY magazine, 4/87, p. 136]

;;; The procedural algorithm is very short; it's a brute force method
;;; that isn't hard to follow.

(defun coins-proc()
  (let((amount (floor (prompt-and-read :number "Enter amount: "))))
    (format t "~& Quarters: ~d" (quotient amount 25))
    (setq amount (mod amount 25))
    (format t "~& Dimes: ~d"  (quotient amount 10))
    (setq amount (mod amount 10))
    (format t "~& Nickels: ~d" (quotient amount 5))
    (setq amount (mod amount 5))
    (format t "~& Pennies: ~d" amount)))

;;; But notice that accumulating the results, e.g. to return the results
;;; to a calling program, is messier to add in.

(defun coins-proc2()
  (let((amount (floor (prompt-and-read :number "Enter amount: ")))
       (vals))
    (format t "~& Quarters: ~d" (quotient amount 25))
    (push (cons 25 (quotient amount 25)) vals)
    (setq amount (mod amount 25))
    (format t "~& Dimes: ~d"  (quotient amount 10))
    (push (cons 10 (quotient amount 25)) vals)
    (setq amount (mod amount 10))
    (format t "~& Nickels: ~d" (quotient amount 5))
    (push (cons 5 (quotient amount 25)) vals)
    (setq amount (mod amount 5))
    (format t "~& Pennies: ~d" amount)
    (push (cons 1 (quotient amount 25)) vals)
    ))

;;; Here's a lispified version that does a few nice things:
;;;  1. you can pass in the amount, so it does type error checking;
;;;  2. uses an accumulator driver (see below), so the 
;;;     algorithm is more centralized;
;;;  3. nicer output format;
;;;  3. returns the accumulated results.

(defun coins-lisp(&optional amount)
  (declare(values got-list left-over))
  (if (null amount)
     (setq amount (prompt-and-read :number "How many cents? ")))
  ;;check it's positive
  (etypecase amount ((integer 0 *)))
  ;;accumulate and print
  (let*((got-amounts (accum amount))
	(got-list (car got-amounts))
	(left-over (cadr got-amounts))
	(coin-list '((25 quarter "~p")
		     (10 dime "~p")
		     (5 nickel "~p")
		     (1 penn "~@p"))))
    (format t "~%Need:~%")
    (dolist (amts got-list)
      (let((coin (first amts))
	   (num (rest amts)))
	(when (and (numberp num)
		   (plusp num))
	  (let*((coin-stuff(rest(assoc coin coin-list)))
		(coin-name (string-downcase (first coin-stuff)))
		(coin-fmt (second coin-stuff)))
	    (format t "~& ~d ~1(~a~@?~)"
		    num coin-name coin-fmt num)))))
    (unless (zerop left-over)
      (format t "~% ...is ~d cents short."))
    (format t "~%")
    (values got-list left-over)))

(defun accum(amount)
  (accum2
    (accum2
      (accum2
	(accum2 (list nil			;accumulator
		      amount)
		25)				;quarters
	10)					;dimes
      5)					;nickels
    1)						;pennies
  )

(defun accum2 (got-amount increment)
  ;;accumulator function
  (let((got (first got-amount))
       (amount (second got-amount)))
    (if (and (numberp amount)
	       (> amount 0))
      (multiple-value-bind (used left)
	  (floor amount increment)
	(if (> used 0)
	  (list 
	    ;;GOT
	    (append got
		    (ncons (cons increment used)))
	    ;;AMOUNT
	    left)
	  got-amount))
      got-amount)))
