clojure中的列表处理,需要尾递归
List processing in clojure, tail recursion needed
给定间隔的排序列表,例如
(def lst (list [7 10] [32 35]))
我需要实现一个向列表添加新间隔的函数。如果新区间与列表中的任何区间相邻,则应合并它们:
(= (add-range [1 3] lst) (list [1 3] [7 10] [32 35])) ;; prepend left
(= (add-range [1 6] lst) (list [1 10] [32 35])) ;; merge left
(= (add-range [11 20] lst) (list [7 20] [32 35])) ;; merge right
(= (add-range [11 31] lst) (list [7 35])) ;; merge left and right
这是我的实现:
(defn add-range
[range range-list]
(if (empty? range-list)
(list range)
(let
[lo (first range)
hi (second range)
head (first range-list)
head-lo (dec (first head))
head-hi (inc (second head))]
(if (< hi head-lo)
(cons range range-list)
(if (= hi head-lo)
(cons [lo (second head)] (rest range-list))
(if (= lo head-hi)
(recur [(first head) hi] (rest range-list))
(cons head (add-range range (rest range-list)))))))))
它可以工作并且看起来也很优雅,但是最后一行包含一个递归调用 add-range
,不能用 recur
替换,因为它不是最后一次调用。我计划在我的应用程序中使用长范围列表,我不想破坏堆栈。
如何使用尾递归重写?
有没有另一种方法来解决这个问题?可能是惰性序列?
更新
实际上不需要排序列表。这可以是一个集合,甚至是一个未排序的列表,但一次完成它会非常好。
根据我的经验,制作尾递归涉及将所有本地状态作为参数传递。查看算法,看起来已经处理的范围项是本地状态。即,final result = (ranges ignored + merged-range + ranges not required to be considered)
.
考虑以下版本,它明确传递了一系列已处理的项目。
(defn add-range
[range-obj ranges]
(loop [processed []
range-obj range-obj
remaining (list* ranges)]
(if (empty? remaining)
(conj processed range-obj)
(let [[lo hi] range-obj
[h-lo h-hi :as head] (first remaining)
upper-merge-threshold (dec h-lo)
lower-merge-threshold (inc h-hi)]
(cond
(< hi upper-merge-threshold) (into processed
(conj remaining range-obj))
(= hi upper-merge-threshold) (into processed
(conj (rest remaining) [lo h-hi]))
(= lo lower-merge-threshold) (recur processed
[h-lo hi]
(rest remaining))
:else (recur (conj processed head)
range-obj
(rest remaining)))))))
我的版本接受向量,return接受向量。您可以修改相关代码以使其接受列表和 return 列表。
至于有没有更好的算法,不知道。我只是将您的算法转换为尾递归。
使用排序集,您可以将其实现为:
;; first the constructor
(defn ranges [& rs]
(apply sorted-set-by
(fn [[from-a to-a] [from-b to-b]]
(< to-a (dec from-b))) rs))
;; then add-range itself
(defn add-range [ranges [from to :as r]]
(let [rs (subseq ranges <= [from from] <= [to to])
ranges (reduce disj ranges rs)]
(conj ranges
(let [[from'] (or (first rs) r)
[_ to'] (or (last rs) r)]
[(min from from') (max to to')]))))
让我们试试你的测试:
=> (def lst (ranges [7 10] [32 35]))
#'user/lst
=> (add-range lst [1 3])
#{[1 3] [7 10] [32 35]}
=> (add-range lst [1 6])
#{[7 10] [32 35]}
=> (add-range lst [11 20])
#{[7 20] [32 35]}
=> (add-range lst [11 35])
#{[7 35]}
附录 #1:add-range
是 O((m + 1) log n),其中 n 是范围集的大小,m 是合并间隔的数量。
给定间隔的排序列表,例如
(def lst (list [7 10] [32 35]))
我需要实现一个向列表添加新间隔的函数。如果新区间与列表中的任何区间相邻,则应合并它们:
(= (add-range [1 3] lst) (list [1 3] [7 10] [32 35])) ;; prepend left
(= (add-range [1 6] lst) (list [1 10] [32 35])) ;; merge left
(= (add-range [11 20] lst) (list [7 20] [32 35])) ;; merge right
(= (add-range [11 31] lst) (list [7 35])) ;; merge left and right
这是我的实现:
(defn add-range
[range range-list]
(if (empty? range-list)
(list range)
(let
[lo (first range)
hi (second range)
head (first range-list)
head-lo (dec (first head))
head-hi (inc (second head))]
(if (< hi head-lo)
(cons range range-list)
(if (= hi head-lo)
(cons [lo (second head)] (rest range-list))
(if (= lo head-hi)
(recur [(first head) hi] (rest range-list))
(cons head (add-range range (rest range-list)))))))))
它可以工作并且看起来也很优雅,但是最后一行包含一个递归调用 add-range
,不能用 recur
替换,因为它不是最后一次调用。我计划在我的应用程序中使用长范围列表,我不想破坏堆栈。
如何使用尾递归重写? 有没有另一种方法来解决这个问题?可能是惰性序列?
更新 实际上不需要排序列表。这可以是一个集合,甚至是一个未排序的列表,但一次完成它会非常好。
根据我的经验,制作尾递归涉及将所有本地状态作为参数传递。查看算法,看起来已经处理的范围项是本地状态。即,final result = (ranges ignored + merged-range + ranges not required to be considered)
.
考虑以下版本,它明确传递了一系列已处理的项目。
(defn add-range
[range-obj ranges]
(loop [processed []
range-obj range-obj
remaining (list* ranges)]
(if (empty? remaining)
(conj processed range-obj)
(let [[lo hi] range-obj
[h-lo h-hi :as head] (first remaining)
upper-merge-threshold (dec h-lo)
lower-merge-threshold (inc h-hi)]
(cond
(< hi upper-merge-threshold) (into processed
(conj remaining range-obj))
(= hi upper-merge-threshold) (into processed
(conj (rest remaining) [lo h-hi]))
(= lo lower-merge-threshold) (recur processed
[h-lo hi]
(rest remaining))
:else (recur (conj processed head)
range-obj
(rest remaining)))))))
我的版本接受向量,return接受向量。您可以修改相关代码以使其接受列表和 return 列表。
至于有没有更好的算法,不知道。我只是将您的算法转换为尾递归。
使用排序集,您可以将其实现为:
;; first the constructor
(defn ranges [& rs]
(apply sorted-set-by
(fn [[from-a to-a] [from-b to-b]]
(< to-a (dec from-b))) rs))
;; then add-range itself
(defn add-range [ranges [from to :as r]]
(let [rs (subseq ranges <= [from from] <= [to to])
ranges (reduce disj ranges rs)]
(conj ranges
(let [[from'] (or (first rs) r)
[_ to'] (or (last rs) r)]
[(min from from') (max to to')]))))
让我们试试你的测试:
=> (def lst (ranges [7 10] [32 35]))
#'user/lst
=> (add-range lst [1 3])
#{[1 3] [7 10] [32 35]}
=> (add-range lst [1 6])
#{[7 10] [32 35]}
=> (add-range lst [11 20])
#{[7 20] [32 35]}
=> (add-range lst [11 35])
#{[7 35]}
附录 #1:add-range
是 O((m + 1) log n),其中 n 是范围集的大小,m 是合并间隔的数量。