简化球拍功能
Simplifying a Racket function
我有以下功能 "change" 需要支付一定数量的钱,用于支付的 bill/coin 的大小,以及 returns 带有数字的列表"coins"($50、$20、$10、$5、$2 和 $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)
它returns输出:
'(3 0 0 1 0 1)
即 200-44 = 156,对应 3 个硬币价值 50 美元,1 个硬币价值 5 美元,1 个硬币价值 1 美元。
我的问题是,是否有一种更优雅、更简化的方法来编写类似的过程而不依赖辅助函数,而是使用 lambda、filter、map、foldr、foldl 等?
提前致谢。
当然可以。
最终解决方案
(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)))
循序渐进
首先,使用innerdefine
,可以从全局命名空间中去掉辅助函数
(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)
这是另一个 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
单元格中;可以围绕此 "plumbing" 使用 "porcelain" 函数以预期的形式报告结果。
想法是我们有一个累加器,如下所示:(counts . remainder)
。存储在 car
中的 accumulator 的 counts
部分是到目前为止累积的硬币列表。当 reduce 完成后,这将保存最终列表。 cdr
字段保存了待处理的剩余金额;因为最后一枚硬币是 1,所以它总是出现为零。
使用这个累加器结构,我们处理硬币列表。
每次调用我们的 reduce 内核函数时,左边的参数是累加器,右边的参数 next-coin
是下一个硬币面额值。
我使用了一个叫做 tb
("tree bind") 宏的宏,它是一种 lambda
提供内置解构的宏,使它看起来像我们有三个参数.
reduce 作业的初始值是起始累加器,它有一个空的硬币列表,以及完整的原始数量:(cons nil amount)
(为了更好的方案兼容性,我将其重写为 (cons '() amount)
).
reduce函数很简单:贪婪地计算下一个硬币值需要多少来表示余数,然后计算新的余数,将它们打包到一个新的累加器cons
单元格中,即返回,并传递给函数的下一次调用,或者在处理硬币值列表时返回。
希望这指出了通往 "a more elegant, simplified way to write a similar procedure without relying on auxiliary functions, and rather use lambda, filter, map, foldr, foldl etc" 的道路,您可以在 Racket 中锻炼。祝你好运!
我有以下功能 "change" 需要支付一定数量的钱,用于支付的 bill/coin 的大小,以及 returns 带有数字的列表"coins"($50、$20、$10、$5、$2 和 $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)
它returns输出:
'(3 0 0 1 0 1)
即 200-44 = 156,对应 3 个硬币价值 50 美元,1 个硬币价值 5 美元,1 个硬币价值 1 美元。
我的问题是,是否有一种更优雅、更简化的方法来编写类似的过程而不依赖辅助函数,而是使用 lambda、filter、map、foldr、foldl 等?
提前致谢。
当然可以。
最终解决方案
(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)))
循序渐进
首先,使用innerdefine
,可以从全局命名空间中去掉辅助函数
(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)
这是另一个 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
单元格中;可以围绕此 "plumbing" 使用 "porcelain" 函数以预期的形式报告结果。
想法是我们有一个累加器,如下所示:(counts . remainder)
。存储在 car
中的 accumulator 的 counts
部分是到目前为止累积的硬币列表。当 reduce 完成后,这将保存最终列表。 cdr
字段保存了待处理的剩余金额;因为最后一枚硬币是 1,所以它总是出现为零。
使用这个累加器结构,我们处理硬币列表。
每次调用我们的 reduce 内核函数时,左边的参数是累加器,右边的参数 next-coin
是下一个硬币面额值。
我使用了一个叫做 tb
("tree bind") 宏的宏,它是一种 lambda
提供内置解构的宏,使它看起来像我们有三个参数.
reduce 作业的初始值是起始累加器,它有一个空的硬币列表,以及完整的原始数量:(cons nil amount)
(为了更好的方案兼容性,我将其重写为 (cons '() amount)
).
reduce函数很简单:贪婪地计算下一个硬币值需要多少来表示余数,然后计算新的余数,将它们打包到一个新的累加器cons
单元格中,即返回,并传递给函数的下一次调用,或者在处理硬币值列表时返回。
希望这指出了通往 "a more elegant, simplified way to write a similar procedure without relying on auxiliary functions, and rather use lambda, filter, map, foldr, foldl etc" 的道路,您可以在 Racket 中锻炼。祝你好运!