我想在Racket中编写一个功能,它需要一定数量的金钱和一个特定的账单值列表,然后返回一个列表,其中包含每种类型的账单金额,以总计给定的金额。例如,(calc 415 (list 100 10 5 2 1))
应返回'(4 1 1 0 0)
。
我试过这种方式,但这并不起作用:/我认为我还没有完全理解你在Racket中对set!
能做什么/不能做什么,说实话
(define (calc n xs)
(cond ((null? xs) (list))
((not (pair? xs))
(define y n)
(begin (set! n (- n (* xs (floor (/ n xs)))))
(list (floor (/ y xs))) ))
(else (append (calc n (car xs))
(calc n (cdr xs))))))
答案 0 :(得分:2)
这个问题需要一些简单的递归非确定性编程。
此处的代码将遵循我的another answer,它会找出解决方案的总数( 多于一个,对于您的示例也是如此)。我们也必须自己维护解决方案,而上面提到的代码只计算它们。
我们可以将这个编码为递归回溯过程,并使用每个成功找到的解决方案调用回调:
(define (change sum bills callback)
(let loop ((sum sum) (sol '()) (bills bills)) ; "sol" for "solution"
(cond
((zero? sum) (callback sol))
((< sum 0) #f)
((null? bills) #f)
(else (apply
(lambda (b . bs)
(loop (- sum b) (cons b sol) bills) ; either use the first denomination,
(loop sum sol bs)) ; or backtrack, and don't!
bills)))))
通过
之一调用(define (first-solution proc . params)
(call/cc (lambda (return)
(apply proc (append params
(list (lambda (sol)
(return sol))))))))
(define (n-solutions n proc . params) ; n assumed an integer
(let ([res '()]) ; n <= 0 gets ALL solutions
(call/cc (lambda (break)
(apply proc (append params
(list (lambda (sol)
(set! res (cons sol res))
(set! n (- n 1))
(cond ((zero? n) (break)))))))))
(reverse res)))
测试,
> (first-solution change 406 (list 100 10 5 2))
'(2 2 2 100 100 100 100)
> (n-solutions 7 change 415 (list 100 10 5 2 1))
'((5 10 100 100 100 100)
(1 2 2 10 100 100 100 100)
(1 1 1 2 10 100 100 100 100)
(1 1 1 1 1 10 100 100 100 100)
(5 5 5 100 100 100 100)
(1 2 2 5 5 100 100 100 100)
(1 1 1 2 5 5 100 100 100 100))
关于这段代码的结构,参见How to generate all the permutations of elements in a list one at a time in Lisp?它创建了嵌套循环,解决方案可以在最里面的循环体中访问。
关于如何以正确的功能方式编写非确定性算法(一次性做出所有可能的选择),请参阅How to do a powerset in DrRacket?和How to find partitions of a list in Scheme。
答案 1 :(得分:2)
你的程序做得太多了,你使用了不必要的突变。如果你把问题分开了。
(define (calc-one-bill n bill)
...)
;; test
(calc-one-bill 450 100) ; ==> 4
(calc-one-bill 450 50) ; ==> 9
然后你可以:
(define (calc-new-n n bill amount)
...)
(calc-new-n 450 100 4) ; ==> 50
(calc-new-n 450 50 9) ; ==> 0
然后你可以减少你原来的实现:
(define (calc n bills)
(if (null? bills)
(if (zero? n)
'()
(error "The unit needs to be the last element in the bills list"))
(let* ((bill (car bills))
(amount (calc-one-bill n bill)))
(cons amount
(calc (calc-new-n n bill amount)
(cdr bills))))))
这将始终选择费用最少的解决方案,就像您的版本似乎一样。两个版本都要求传递的bill
中的最后一个元素是单位1
。对于更复杂的方法,该方法适用于(calc 406 (list 100 10 5 2))
,并且可能找到解决方案的所有组合,请参阅Will's answer。
答案 2 :(得分:1)
我现在这样解决了:)
(define (calc n xs)
(define (calcAssist n xs usedBills)
(cond ((null? xs) usedBills)
((pair? xs)
(calcAssist (- n (* (car xs) (floor (/ n (car xs)))))
(cdr xs)
(append usedBills
(list (floor (/ n (car xs)))))))
(else
(if ((= (- n (* xs (floor (/ n xs)))) 0))
(append usedBills (list (floor (/ n xs))))
(display "No solution")))))
(calcAssist n xs (list)))
测试:
> (calc 415 (list 100 10 5 2 1))
'(4 1 1 0 0)
答案 3 :(得分:1)
我认为这是我在学习FORTRAN时编写的第一个程序!这是一个关于使用Racket所提供的一切(或者至少是我所知道的一切)的骨头的版本。因此,它可能是一个糟糕的家庭作业解决方案,它肯定比我在1984年写的FORTRAN更漂亮。
请注意,此版本不会搜索,因此即使不需要,也会获得余数。当然,如果最低面额为1,它永远不会得到余数。
(define/contract (denominations-of amount denominations)
;; split amount into units of denominations, returning the split
;; in descending order of denomination, and any remainder (if there is
;; no 1 denomination there will generally be a remainder).
(-> natural-number/c (listof (integer-in 1 #f))
(values (listof natural-number/c) natural-number/c))
(let handle-one-denomination ([current amount]
[remaining-denominations (sort denominations >)]
[so-far '()])
;; handle a single denomination: current is the balance,
;; remaining-denominations is the denominations left (descending order)
;; so-far is the list of amounts of each denomination we've accumulated
;; so far, which is in ascending order of denomination
(if (null? remaining-denominations)
;; we are done: return the reversed accumulator and anything left over
(values (reverse so-far) current)
(match-let ([(cons first-denomination rest-of-the-denominations)
remaining-denominations])
(if (> first-denomination current)
;; if the first denomination is more than the balance, just
;; accumulate a 0 for it and loop on the rest
(handle-one-denomination current rest-of-the-denominations
(cons 0 so-far))
;; otherwise work out how much of it we need and how much is left
(let-values ([(q r)
(quotient/remainder current first-denomination)])
;; and loop on the remainder accumulating the number of bills
;; we needed
(handle-one-denomination r rest-of-the-denominations
(cons q so-far))))))))