用特定账单代表一定数量的钱

时间:2018-04-29 12:07:20

标签: recursion scheme racket mutability coin-change

我想在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))))))

4 个答案:

答案 0 :(得分:2)

这个问题需要一些简单的递归非确定性编程。

  • 我们从一个给定的金额和一个给定的账单衡量单位开始,每个账单的金额无限制,显然(否则,这将是一个不同的问题)。
  • 在每个时间点,我们都可以使用最大的账单。
  • 如果我们使用它,总和会减去帐单值的
  • 如果总数为0,我们就有了解决方案!
  • 如果总数是负数,则无效,所以我们应该放弃这条路径。

此处的代码将遵循我的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))))))))