Clojure的惰性与递归

Clojure可以很轻松的生成无穷列表1,如下代码:

(println (take 10 (range)))
;;-> (0 1 2 3 4 5 6 7 8 9)

通过打印函数结果可知,我们使用take取的了range返回的前10个数。range返回的一组序列被为惰性序列。

惰性序列

在调用range时我们给传递了一个参数来限定序列的范围,若我们不提供参数,默认会是无穷的。直接运行会导致电脑卡机,repl奔溃。要注意,不用轻易的尝试(试过,就知道)。

我们也可以使用(range 10)来生成前十个数序列:

(range 10)

count函数,用来统计序列的长度,我们可以取1000个序列数,并计算长度:

(println (count (take 1000 (range))))
;;-> 1000

注意range需要用()括起来,因为range可以是无参数的调用,在clojure中括号就是一个执行单位,若不加()则会不识别range这个函数的操作,上述的例子我们也可以这样写:

(println (count (range 1000)))
;;-> 1000

repeat,可以生成重复项的无穷序列,可以指定一个参数表示重复的次数,也可以无参数,此时生成的序列是无穷序列,与range一样是无穷的,但repeat生成的是重复项:

(println (repeat "aa"))
;;-> 生成"aa"无穷序列,不要轻易尝试
(println (take 10 (repeat "aa")))
;;-> (aa aa aa aa aa aa aa aa aa aa)
;;-> 取出前十个

rand-int,生成一个随机数无穷序列,
随机生成一个0-10以内的随机整数:

(println (rand-int 10))

加上repeat:

(println (repeat 5 (rand-int 10)))
;;-> (8 8 8 8 8)

生成的数一样,可见这只是将数重复了5次,并不是将后面的函数执行五次。
若想重复执行五次函数,需要使用repeatedly参数而不是repeat:

(println (repeatedly 5 #(rand-int 10)))
;;-> (9 9 5 0 7)

注意repeatedly的参数为repeatedly[f] ,[n f],两种参数模式,一种是只传入一个函数,另一种是传入一个数值和函数,我们输入的(rand-int 10)是一个结果,通过加#将其变成一个匿名函数,该函数无参数,作为repeatedly的参数使用,结果就变成了生成5个0-10的随机数。
当然我们也可以使用(repeatedly #(rand-int 10))生成一个无穷序列,通过take来获取我们想要的个数。

(println (take 10 (repeatedly #(rand-int 10))))
;;-> (3 6 5 2 1 3 0 5 8 1)

cycle,接受一个collection作为参数,返回其无限循环的序列。

(println (take 10 (cycle ["aa" "bb" "vv"])))
;;-> (aa bb vv aa bb vv aa bb vv aa)

递归

递归应该使用过很多,归根结底就是调用自身的函数,先看一个递归的小例子2

(defn Example []
  (loop [i 0]
    (when (< i 5)
      (println i)
      (recur (inc i)))))
(Example)

loop为声明一个参数[i 0],绑定i为0,当i小于5的时候打印i,并且通过recur执行inc函数将i加1,输出应该为:0 1 2 3 4。其中recur跳回了递归点,也即是loop的开头,当i大于等于5时,此时不再执行recur操作,程序也就结束了。

loop-recur与for循环类似,不过它是递归的形式,通过recur来代替函数名称,返回到递归点loop,完成类似循环的操作。此外,recur是Clojure避免栈消耗的办法,它一次只调用一个栈。


  1. Living Clojure(中文版):中国电力出版社 ↩︎

  2. Clojure递归:https://www.w3cschool.cn/clojure/clojure_recursion.html ↩︎

你可能感兴趣的:(一起学clojure)