我目前正在Clojure中实现一个小解析器,它接受一个输入字符串,如:
aaa(bbb(ccc)ddd(eee))fff(ggg)hhh
并返回不带括号内字符的字符串,即
(BBB(CCC)DDD(EEE))(GGG)
我写了以下函数:
(defn- parse-str [input]
(let [bracket (atom 0)
output (atom [])]
(doseq [ch (seq input)]
(case ch
\( (swap! bracket inc)
\) (swap! bracket dec)
nil)
(if (or (> @bracket 0) (= ch \)))
(swap! output conj ch)))
(apply str @output)))
对我有用:
然而,我担心我的方法过于面向对象,因为它使用原子作为某种局部变量来保持解析器的当前状态。(parse-str“aaa(bbb(ccc)ddd(eee))fff(ggg)hhh”)
“(BBB(CCC)DDD(EEE))(GGG)”
是否可以从更具功能性的编程角度编写相同的函数? (避免原子?)
我们也欢迎任何改进我的代码的评论。
答案 0 :(得分:3)
两种方式:您可以使用显式递归或减少。
(defn parse-str [input]
(letfn [(parse [input bracket result]
(if (seq input)
(let [[ch & rest] input]
(case ch
\( (recur rest (inc bracket) (conj result ch))
\) (recur rest (dec bracket) (conj result ch))
(recur rest bracket (if (> bracket 0)
(conj result ch)
result))))
result))]
(clojure.string/join (parse input 0 []))))
(defn parse-str [input]
(clojure.string/join
(second (reduce (fn [acc ch]
(let [[bracket result] acc]
(case ch
\( [(inc bracket) (conj result ch)]
\) [(dec bracket) (conj result ch)]
[bracket (if (> bracket 0)
(conj result ch)
result)])))
[0 []]
input))))
答案 1 :(得分:1)
在很多情况下你会使用局部变量,你只需将任何变化的变量作为参数循环,从而使用递归而不是变异。
(defn- parse-str [input]
;; Instead of using atoms to hold the state, use parameters in loop
(loop [output []
bracket 0
;; The [ch & tail] syntax is called destructuring,
;; it means let ch be the first element of (seq input),
;; and tail the rest of the elements
[ch & tail] (seq input)]
;; If there's no elements left, ch will be nil, which is logical false
(if ch
(let [bracket* (case ch
\( (inc bracket)
\) (dec bracket)
bracket)
output* (if (or (> bracket* 0) (= ch \)))
(conj output ch)
output)]
;; Recurse with the updated values
(recur output* bracket* tail))
;; If there's no characters left, apply str to the output
(apply str output))))
答案 2 :(得分:1)
这是你的函数的迭代版本;但它仍然功能纯净。我发现这样的代码布局使得它易于阅读。请记住,在使用递归时,请务必先检查终止条件。
(defn parse-str [s]
(loop [[x & xs] (seq s), acc [], depth 0]
(cond
(not x) (clojure.string/join acc)
(= x \() (recur xs (conj acc x) (inc depth))
(= x \)) (recur xs (conj acc x) (dec depth))
(<= depth 0) (recur xs acc depth)
:else (recur xs (conj acc x) depth))))