繁体   English   中英

避免使用套装!进入函数式编程风格算法

[英]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以变​​异顺序创建/复制原始列表。

您可以使用takedrop来获取或离开任何列表的前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可能比takedrop的组合稍微有效。

尝试一下:

(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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM