循环与递归 #

一、循环与递归简介 #

Clojure作为函数式语言,没有传统意义上的循环语句(如for、while)。取而代之的是递归和序列操作。

1.1 函数式迭代 #

clojure
(map inc [1 2 3 4 5])

(filter even? (range 10))

(reduce + [1 2 3 4 5])

二、loop/recur #

2.1 基本语法 #

clojure
(loop [bindings]
  body
  (recur new-values))

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

2.2 累加器模式 #

clojure
(defn sum-to [n]
  (loop [i 1
         sum 0]
    (if (> i n)
      sum
      (recur (inc i) (+ sum i)))))

(sum-to 10)

2.3 尾递归优化 #

recur 实现尾递归优化,不会栈溢出:

clojure
(defn factorial [n]
  (loop [n n
         acc 1]
    (if (<= n 1)
      acc
      (recur (dec n) (* acc n)))))

(factorial 10)

(factorial 1000)

2.4 多变量循环 #

clojure
(defn fibonacci [n]
  (loop [a 0
         b 1
         count 0]
    (if (= count n)
      a
      (recur b (+ a b) (inc count)))))

(map fibonacci (range 10))

2.5 循环终止条件 #

clojure
(defn find-first [pred coll]
  (loop [coll coll]
    (when (seq coll)
      (if (pred (first coll))
        (first coll)
        (recur (rest coll))))))

(find-first even? [1 3 5 4 7 8])

(find-first pos? [-1 -2 3])

三、函数内递归 #

3.1 普通递归 #

clojure
(defn sum [n]
  (if (<= n 0)
    0
    (+ n (sum (dec n)))))

(sum 10)

(sum 10000)

3.2 使用recur #

clojure
(defn sum-tail [n]
  (letfn [(helper [n acc]
            (if (<= n 0)
              acc
              (recur (dec n) (+ acc n))))]
    (helper n 0)))

(sum-tail 10000)

3.3 内部辅助函数 #

clojure
(defn reverse-list [lst]
  (loop [lst lst
         acc '()]
    (if (empty? lst)
      acc
      (recur (rest lst) (conj acc (first lst))))))

(reverse-list [1 2 3 4 5])

四、doseq #

4.1 基本语法 #

clojure
(doseq [x coll]
  body)

(doseq [i [1 2 3 4 5]]
  (println i))

4.2 多重循环 #

clojure
(doseq [x [1 2 3]
        y [:a :b]]
  (println [x y]))

4.3 条件过滤 #

clojure
(doseq [x (range 10)
        :when (even? x)]
  (println x))

(doseq [x (range 10)
        :let [y (* x x)]
        :when (> y 10)]
  (println x "->" y))

4.4 解构绑定 #

clojure
(doseq [[k v] {:a 1 :b 2 :c 3}]
  (println k "=" v))

(doseq [[i x] (map-indexed vector [:a :b :c])]
  (println i ":" x))

4.5 返回值 #

doseq 总是返回 nil

clojure
(doseq [x [1 2 3]]
  (* x 2))

五、dotimes #

5.1 基本语法 #

clojure
(dotimes [i n]
  body)

(dotimes [i 5]
  (println i))

5.2 实际应用 #

clojure
(dotimes [i 3]
  (println "Iteration:" i))

(dotimes [_ 10]
  (print "."))

5.3 返回值 #

dotimes 返回 nil

六、while #

6.1 基本语法 #

clojure
(while condition
  body)

(def counter (atom 5))
(while (pos? @counter)
  (println @counter)
  (swap! counter dec))

6.2 使用场景 #

while 较少使用,通常用 loop/recur 替代:

clojure
(loop [counter 5]
  (when (pos? counter)
    (println counter)
    (recur (dec counter))))

七、惰性序列 #

7.1 惰性求值 #

clojure
(defn naturals []
  (iterate inc 0))

(take 5 (naturals))

(take 10 (naturals))

7.2 无限序列 #

clojure
(take 5 (repeat :x))

(take 5 (cycle [1 2 3]))

(take 10 (iterate #(* % 2) 1))

7.3 自定义惰性序列 #

clojure
(defn fib-seq []
  (map first
       (iterate (fn [[a b]] [b (+ a b)])
                [0 1])))

(take 10 (fib-seq))

(defn powers-of [n]
  (iterate #(* % n) 1))

(take 10 (powers-of 2))

7.4 惰性序列注意事项 #

clojure
(defn print-and-return [x]
  (println "Computing:" x)
  x)

(def lazy-result
  (map print-and-return (range 5)))

(take 2 lazy-result)

八、for #

8.1 基本语法 #

for 是列表推导式,返回惰性序列:

clojure
(for [x [1 2 3 4 5]]
  (* x x))

(for [x [1 2 3]
      y [:a :b]]
  [x y])

8.2 条件过滤 #

clojure
(for [x (range 10)
      :when (even? x)]
  x)

(for [x (range 10)
      :let [y (* x x)]
      :when (> y 10)]
  [x y])

8.3 :while #

clojure
(for [x (range 10)
      :while (< x 5)]
  x)

8.4 实际应用 #

clojure
(defn pairs [coll]
  (for [x coll
        y coll
        :when (< x y)]
    [x y]))

(pairs [1 2 3 4])

(defn cartesian-product [coll1 coll2]
  (for [x coll1
        y coll2]
    [x y]))

(cartesian-product [1 2] [:a :b])

九、实践示例 #

9.1 查找算法 #

clojure
(defn binary-search [coll target]
  (loop [low 0
         high (dec (count coll))]
    (if (> low high)
      nil
      (let [mid (quot (+ low high) 2)
            mid-val (nth coll mid)]
        (cond
          (= mid-val target) mid
          (< mid-val target) (recur (inc mid) high)
          :else (recur low (dec mid)))))))

(binary-search [1 2 3 4 5 6 7 8 9] 5)

(binary-search [1 2 3 4 5 6 7 8 9] 10)

9.2 排序算法 #

clojure
(defn quick-sort [coll]
  (if (empty? coll)
    coll
    (let [pivot (first coll)
          rest-coll (rest coll)]
      (concat
        (quick-sort (filter #(< % pivot) rest-coll))
        [pivot]
        (quick-sort (filter #(>= % pivot) rest-coll))))))

(quick-sort [3 1 4 1 5 9 2 6 5])

9.3 树遍历 #

clojure
(defn tree-seq-custom [branch? children root]
  (lazy-seq
    (if (branch? root)
      (cons root (mapcat #(tree-seq-custom branch? children %)
                         (children root)))
      [root])))

(def tree {:value 1
           :children [{:value 2
                       :children [{:value 4} {:value 5}]}
                      {:value 3
                       :children [{:value 6}]}]})

(tree-seq-custom :children :children tree)

9.4 批处理 #

clojure
(defn process-batch [items batch-size f]
  (loop [items items
         results []]
    (if (empty? items)
      results
      (let [batch (take batch-size items)
            result (f batch)]
        (recur (drop batch-size items)
               (conj results result))))))

(process-batch (range 10) 3
  (fn [batch] (reduce + batch)))

十、性能考虑 #

10.1 尾递归vs普通递归 #

clojure
(defn sum-recursive [n]
  (if (<= n 0)
    0
    (+ n (sum-recursive (dec n)))))

(defn sum-tail [n]
  (loop [n n acc 0]
    (if (<= n 0)
      acc
      (recur (dec n) (+ acc n)))))

(time (sum-tail 100000))

(time (sum-recursive 10000))

10.2 惰性序列内存 #

clojure
(defn sum-range [n]
  (reduce + (range n)))

(defn sum-loop [n]
  (loop [i 0 acc 0]
    (if (>= i n)
      acc
      (recur (inc i) (+ acc i)))))

十一、总结 #

循环与递归速查:

形式 用途 返回值
loop/recur 尾递归循环 自定义
doseq 副作用遍历 nil
dotimes 计数循环 nil
for 列表推导 惰性序列
while 条件循环 nil

关键点:

  1. 使用 recur 实现尾递归优化
  2. doseq 用于副作用,for 用于生成序列
  3. 惰性序列支持无限数据
  4. 优先使用序列函数(map/filter/reduce)
  5. loop/recur 是通用迭代工具

控制流系列完成!接下来学习宏!

最后更新:2026-03-27