【问题标题】:Simplifying a Racket function简化球拍功能
【发布时间】:2020-03-14 01:01:15
【问题描述】:

我有以下函数“change”,它需要支付一定数量的钱,用于支付的钞票/硬币的大小,并返回一个包含 “硬币”数量的​​列表 ($50, $20 $10 $5 $2 and $1) 完成交易后会收到:

(define (change total payment)
  (let [(x (- payment total))]
    (change-aux x '(50 20 10 5 2 1))))

(define (change-aux change coins)
  (cond
    [(empty? coins) empty]
    [else (let [(num-coins (floor (/ change (car coins))))]
            (append (list num-coins)
                    (change-aux (- change (* (car coins) num-coins)) (cdr coins))))]))

所以,如果我输入这些参数:

> (change 44 200)

它返回输出:

'(3 0 0 1 0 1)

即 200-44 = 156,对应于 3 个价值 50 美元的硬币、1 个价值 5 美元的硬币和 1 个价值 1 美元的硬币。

我的问题是,是否有一种更优雅、更简化的方式来编写类似程序而不依赖辅助函数,而是使用 lambda、filter、map、foldr、foldl 等?

提前致谢。

【问题讨论】:

  • 欢迎来到 StackOverflow!该站点是针对有关编程的具体、客观的问题。一般批评或审查请求被视为离题,应转至CodeReview.SE
  • 谢谢布赖恩,从现在开始会牢记在心。有没有办法手动将此问题移至 CodeReview? (完整的新手在这里)
  • 函数接口与描述不符。功能是取“一定要支付的金额”(单次输入)。代码引入了两个参数,函数立即将它们相减,不再使用。
  • 注意这个问题和base转换很相似。例如,用十进制表示 1234 与使用“硬币”(1000 100 10 1) 兑换零钱是同一个问题。

标签: lisp racket


【解决方案1】:

这是另一个 Lisp dialect 中的一个解决方案,它展示了如何在没有累加器变量任何突变的情况下使用左折叠(减少)来做到这一点,作为现有解决方案的一种功能对立面。

(defun change (amount coins)
  (reduce-left (tb ((counts . rem) next-coin)
                 (let* ((coin-count (floor rem next-coin))
                        (coin-value (* coin-count next-coin)))
                   (cons (cons coin-count counts)
                         (- rem coin-value))))
               coins
               (cons '() amount)))


3> (change 156 '(50 20 10 5 2 1))
((1 0 1 0 0 3) . 0)

4> (change 88 '(50 20 10 5 2 1))
((1 1 1 1 1 1) . 0)

请注意,这些值最终以相反的顺序报告并包装在一个额外的cons 单元格中;可以围绕这个“管道”使用“瓷器”函数来以预期的形式报告结果。

我们的想法是我们有一个如下所示的累加器:(counts . remainder)。存储在car 中的累加器的counts 部分是迄今为止累积的硬币列表。当 reduce 完成时,它保存了最终列表。 cdr 字段保存待处理的剩余金额;因为最后一个硬币是 1,所以它总是出现为零。

使用这个累加器结构,我们处理硬币列表。

在每次调用我们的 reduce 内核函数时,左侧参数是累加器,右侧参数 next-coin 是下一个硬币面额值。

我使用了一个名为tb(“树绑定”)宏的宏,它是一种lambda,提供内置解构,使它看起来像我们有三个参数。

reduce 作业的初始值是起始累加器,它有一个空的硬币列表和完整的原始数量:(cons nil amount)(为了更好的方案兼容性,我将其重写为(cons '() amount))。

reduce 函数非常简单:贪婪地计算下一个硬币值需要多少个来表示余数,然后计算新的余数,将它们打包成一个新的累加器cons 单元格,然后返回,即传递给函数的下一次调用,或在处理完硬币值列表时返回。

希望这指明了“一种更优雅、更简化的方式来编写类似程序而不依赖辅助函数,而是使用 lambda、filter、map、foldr、foldl 等”可以在 Racket 中锻炼。祝你好运!

【讨论】:

  • 这也是一个非常聪明的解决方案。感谢您的分享!这将帮助我了解如何在接下来的任务中简化我的代码。
【解决方案2】:

当然可以。

最终解决方案

(define (change total payment (coins '(50 20 10 5 2 1)))
  (let ((rest-diff (- payment total)))
    (map (lambda (coin)
           (let ((num-coins (floor (/ rest-diff coin))))
             (set! rest-diff (- rest-diff (* num-coins coin)))
             num-coins))
         coins)))

一步一步

首先,使用内部define,可以从全局命名空间中去掉辅助函数。

(define (change total payment)
  (define (change-aux change coins)
    (cond
      [(empty? coins) empty]
      [else (let [(num-coins (floor (/ change (car coins))))]
              (append (list num-coins)
                      (change-aux (- change (* (car coins) num-coins)) (cdr coins))))]))
  (let [(x (- payment total))]
    (change-aux x '(50 20 10 5 2 1))))

然后,您可以将辅助函数的一些变量拉到全局函数的 lambda 列表中。

(define (change total payment (coins '(50 20 10 5 2 1)))
  (define (change-aux change) ;; eliminate coins in the inner lambda list
    (cond
      [(empty? coins) empty] ;; coins in function body looked up from outer arguments
      [else (let [(num-coins (floor (/ change (car coins))))]
              (append (list num-coins)
                      (change-aux (- change (* (car coins) num-coins)) (cdr coins))))]))
  (let [(x (- payment total))]
    (change-aux x))) ;; eliminate coins in the call

然后,看change-aux的代码,就明白这其实是 循环并尝试拟合当前值的最大倍数 剩下的差异 - 并收集这些结果。可以使用map 循环并使用set! 改变其余部分。

(define (change total payment (coins '(50 20 10 5 2 1)))
  (let ((rest-diff (- payment total)))
    (map (lambda (coin)
           (let ((num-coins (floor (/ rest-diff coin))))
             (set! rest-diff (- rest-diff (* num-coins coin)))
             num-coins))
         coins)))

然后,你像上面这样调用:

(change 44 200)
;; '(3 0 0 1 0 1)

【讨论】:

  • 感谢您如此清楚地解释您的解决方案!这真的会帮助我更好地理解 lambdas/mapping。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-16
  • 2019-07-31
  • 2014-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多