![](/img/trans.png)
[英]Returning collection of random elements from ArrayList using functional programming
[英]Avoid using set! into functional programming style algorithm
我不确定我是否可以在这里提出这个问题。 如果这不是正确的地方,请告诉我,我将删除它。
我正在学习球拍,有人告诉我一些关于避免使用set!
事情set!
在函数式编程风格。 但我很困惑,我不明白“函数式编程风格”的含义。 只是为了学习,我想问这个问题。
我有以下代码:
(define lst1 '())
(do ([i n (- i 1)])
((zero? i))
; Get an item.
(set! item (random-chooser original-list))
; Insert it into an auxiliary list, lst1
(set! lst1 (cons item lst1))
; Remove the item from the original list
(set! original-list (remove item original-list)))
(append (list lst1) (list original-list))))))
这段代码完美无缺。 我必须随机选择original-list
中的n
项目而不重复。 然后创建一个列表,其中包含两个子列表,其中包含在lst1
选择的n
项目以及作为第二个子列表,其余项目在original-list
。
如果不使用set!
有没有更好的方法set!
?
为了删除使用set!
和显式循环,你需要使用递归,“更新”值作为参数传入。
Scheme在letrec
周围有一些很好的语法糖(递归let
,用于可以引用自己的绑定),它允许你一次创建和调用一个函数: named let
。 这通常用于循环,因此该函数通常称为loop
,但可以调用其他任何东西。 重要的是在循环时用修改的参数调用它,而不是在循环结束时调用它。
(define (take-random n lst)
; Syntactic sugar that creates a 3-parameter function called `loop` and calls it
; with the values `n`, `'()` and `lst`
(let loop ((n n)
(picked '())
(lst lst))
; If the loop counter reaches zero or if there are no more items to take
(if (or (zero? n) (null? lst))
; Then combine the picked items and the remaining unpicked items
(list picked lst)
; Otherwise, pick a random item from the list
(let ((item (random-chooser lst)))
; And loop again, decreasing the loop counter to be nearer zero,
; adding the picked item to the list of picked items, and removing
; it from the list of remaining items to pick from
(loop (- n 1)
(cons item picked)
(remove item lst))))))
;; Examples
(take-random 3 '(1 2 3 4 5 6))
; => ((4 1 6) (2 3 5))
(take-random 3 '(1 2))
; => ((2 1) ())
ÓscarLópez的答案很好地介绍了这种更实用的方法。
在函数式编程中,我们尽可能通过编写现有过程来编写代码。 您正在执行的所有操作都可以使用列表过程表达:
(define n 5)
(define lst '(0 1 2 3 4 5 6 7 8 9))
(let ((shuffled (take (shuffle lst) n)))
(list shuffled (remove* shuffled lst)))
=> '((5 8 9 6 2) (0 1 3 4 7))
最后,您将有两个列表,第一个包含从原始列表中随机选择的n
元素,第二个包含其余元素。
在函数式编程中,我们很难避免显式循环(我们在其位置使用递归)并set!
。 我们使用现有的过程,组合和创建新数据(比如列表),而不是修改现有数据。
我可以告诉你,你来自程序背景,放弃循环和分配有点困难,但这就是函数式编程的美妙之处:它让你以不同的方式思考如何解决问题。 变异数据是许多困难的根源,特别是在进行并发编程时,这就是FP不惜一切代价避免它的原因。
请注意,尽可能避免变异数据,但Scheme是一种不纯的函数式编程语言。 例如,当选择随机值时, shuffle
过程必须在某个时刻改变状态。 但它全部封装起来,而且在正常的日常编程中,您可以完成大部分工作而无需使用set!
。
解
shuffle
确实是一个很好的解决方案,非常实用。 功能样式禁止突变/改变现有/定义的变量值(因此没有set!
)。 而是创建/复制现有对象并使其变异或以变异形式创建它。 shuffle
以变异顺序创建/复制原始列表。
您可以使用take
和drop
来获取或离开任何列表的前n
位置。 或者使用split-at
功能。
但是,由于这会将结果作为值返回,但任务是返回列表,因此使用let-values
绑定两个返回结果并将它们绑定到单个列表中。
(define (choose-n-and-rest lst n)
(let-values ([(chosen rest) (split-at (shuffle lst) n)])
(list chosen rest)))
要么:
(define (choose-n-and-rest lst n)
(let ((new-lst (shuffle lst)) ; so that `take` & `drop` use the same shuffled list
(list (take new-list n) (drop new-list n))))
但是你可以在这里阅读, split-at
可能比take
和drop
的组合稍微有效。
尝试一下:
(choose-n-and-rest '(a b c d e 1 2 3 4 5) 3)
;; e.g. '((a 4 2) (1 b 3 5 c e d))
顺便说一句, set!
或者更好:在lisp风格的函数式编程中,变异函数不是完全被禁止的。 原因是表现。 复制每个冗长的列表都很昂贵。 一个例子是common-lisp的nreverse
,它改变原始列表的顺序(颠倒顺序)。 非变异reverse
创建一个新列表,其中元素的顺序相反。 但为此它必须复制。 如果你改变局部变量(由let
定义),它可以导致性能的提高 - 当然,非常谨慎,因为变异是一个危险的事情。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.