freeCodeCamp/guide/chinese/clojure/looprecur/index.md

4.9 KiB
Raw Blame History

title localeTitle
Clojure Looprecur Clojure Looprecur

您可能需要了解iflet我们完全掌握Clojure中的递归。

for而且while

Clojure没有for循环或while循环。如果你仔细想想这是有道理的。 for循环更改变量而Clojure中不允许这样做。

for (var i = 0; i < 10; i++) { 
  console.log(i); 
 } 

i++意味着每次循环结束时我们都会向变量i添加一个变量 - 变量变量的一个明显例子。

while循环不太明显依赖于改变变量,但它们与循环一样多。

var i = 0; 
 while (i < 10) { 
  console.log(i); 
  i++; 
 } 

while循环总是有一个条件,比如i < 10 ,并且如果该条件不再为真,它将会中断。这意味着它们必须具有某种副作用(例如向i添加1以使条件最终为假;否则,循环会永远持续下去。

递归

值得庆幸的是Clojure确实有一个循环。这些循环使用递归 - 一个调用自身的函数。最简单的递归算法是找到正数阶乘5阶乘例如等于5 * 4 * 3 * 2 )的算法。

(defn fact [x] 
  (loop [nx prod 1] ;; this works just like a 'let' binding. 
    (if (= 1 n)  ;; this is the base case. 
      prod 
      (recur (dec n) (* prod n))))) 

:rocket: IDEOne吧

你会注意到(loop [nx prod 1] ...)看起来非常类似于let绑定。它实际上以相同的方式工作 - 在这里,我们将n绑定到x ,并将prod绑定到1。

每个递归函数都有一个“基本案例”。这是使循环停止循环的条件。在这种情况下,如果n = 1 ,我们的循环将停止,并返回prod 。如果n不等于1则循环再次出现。

(recur (dec n) (* prod n)) 

recur函数重新启动循环,但具有不同的绑定。这次, n不是绑定到x ,而是绑定到(dec n) (这意味着decrement nn - 1 ),并且prod绑定到(* prod n)

所以当我们调用函数时,会发生这种情况:

(fact 5) 
 ; Loop 1: 5 != 1, so the loop recurs with 4 (5 - 1) and 5 (1 * 5). 
 ; Loop 2: 4 != 1, so the loop recurs with 3 (4 - 1) and 20 (5 * 4). 
 ; Loop 3: 3 != 1, so the loop recurs with 2 (3 - 1) and 60 (20 * 3). 
 ; Loop 4: 2 != 1, so the loop recurs with 1 (2 - 1) and 120 (60 * 2). 
 ; Loop 5: 1 == 1, so the function returns prod, which is now equal to 120. 
 ; => 120 

关于递归的巧妙之处在于变量本身永远不会改变。唯一改变的是nprod 所指的东西 。我们永远不会说, n--n += 2

为什么要使用loop / recur

您可能想知道为什么要使用loop/recur而不是简单地定义一个调用自身的函数。我们的阶乘函数可能是这样编写的:

(defn fact-no-loop [n] 
  (if (= 1 n) 
    1 
    (* n (fact-no-loop (dec n))))) 

这更简洁并以类似的方式工作。为什么你会_永远_使用循环和复发吗

尾调用优化

如果使用loop/recur 则编译器将Clojure代码转换为JVM字节码的软件知道您要创建递归循环。这意味着它最努力地优化代码以进行递归。让我们比较fact的速度和fact-no-loop

(time (fact 20)) 
 ; => "Elapsed time: 0.083927 msecs" 
 ;    2432902008176640000 
 (time (fact-no-loop 20)) 
 ; => "Elapsed time: 0.064937 msecs" 
 ;    2432902008176640000 

:rocket: IDEOne吧

在这种规模下,差异可以忽略不计。实际上,由于计算机内存的不可预测性, fact-no-loop偶尔比fact更快。但是,在更大范围内,这种优化可以使您的代码更快,更快。

在函数中嵌套递归

fact-no-loop在没有loop/recur情况下工作,因为整个函数是递归的。如果我们希望我们的函数的一部分使用递归循环,然后其余部分做一些非递归的东西怎么办?我们必须定义两个完全独立的函数。使用loop/recur让我们使用一些匿名函数代替。

| :point_left:上一页 | :book::book: |下一个:point_right: |
| 让绑定 | 目录 |待补充|