在Clojure中思考:避免使用OOP进行简单的字符串解析

时间:2015-11-18 14:34:22

标签: parsing clojure functional-programming atomic

我目前正在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)”

然而,我担心我的方法过于面向对象,因为它使用原子作为某种局部变量来保持解析器的当前状态。

是否可以从更具功能性的编程角度编写相同的函数? (避免原子?)

我们也欢迎任何改进我的代码的评论。

3 个答案:

答案 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))))