如何在不使用递归的情况下将输入拆分为可变长度的片段?

时间:2013-04-14 20:36:16

标签: parsing clojure

我有一个包含以下内容的输入文件:

2
stuff-11
stuff-12
3
stuff-21
stuff-22
stuff-23
1
stuff-31

我想得到以下结果:

([stuff-11 stuff-12] [stuff-21 stuff-22 stuff-23] [stuff-31])

我最初的解决方案是使用累加器递归,如下所示:

(defn parse-input [lines accum]
   (if (= 0 (count lines))
       accum
       (let [[line-num (Integer. (first lines))]
             [head tail] (split-at (+ 1 line-num) lines)]
             [stuff (vec (drop 1 head))]]
            (parse-input tail (concat accum [stuff]))))
(def result (parse-input input []))

但是,据我所知,由于JVM上缺少TCO,递归函数在Clojure中并不惯用。

有没有更好的方法来解决这个问题?

2 个答案:

答案 0 :(得分:1)

user=> (require '[clojure.string :as s])
nil
user=> (require '[clojure.edn :as edn])
nil
user=> (keep-indexed #(if (odd? %) %2) 
                     (partition-by (comp number? edn/read-string) 
                     (s/split-lines (slurp "/tmp/input.txt"))))
(("stuff-11" "stuff-12") ("stuff-21" "stuff-22" "stuff-23") ("stuff-31"))

其中/tmp/input.txt包含您提供的文字。

如果您希望将一系列向量作为结果,请将#(if (odd? %) %2)替换为#(if (odd? %) (vec %2))

答案 1 :(得分:1)

我不喜欢Michiel Borkent的答案有几个原因,其中一个原因是((comp number? read-string) "3 blah blahb stuff and etc")返回true。此外,虽然它可能简洁,但它不是非常直观或可扩展。

我认为你有正确的直觉来使用递归,但是懒惰的seq更习惯。

(defn parse-stuff [text]
  (let [step (fn step [[head & tail]]
               (when-let [n (clojure.edn/read-string head)] 
                 (cons (vec (take n tail))
                   (lazy-seq (step (drop n tail))))))]
     (step (clojure.string/split-lines text))))