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

问题描述:

我有以下内容输入文件:如何在不使用递归的情况下将输入分割为可变长度的块?

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中不是惯用的。

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

+0

博尔肯先生有正确的做法。我还建议你看看'recur'和'loop'来实现Clojure的尾递归版本。 – WolfeFan

+0

感谢您指出'recur'和'loop'! – sesm

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? %) (vec %2))

+0

因此,这个解决方案忽略了这样一个事实,即数字指定,我们应该读取多少行,并且只使用数字作为分隔符的行。井井有条。 – sesm

我不喜欢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)))) 
+0

也可以使用edn/read-string,(解析东西“3 a b c \ nstuff21”)也可以解析,就像我的解决方案一样。 –

+0

呃,没有。不,它不。至少不是如果输入结构良好。我认为说这个人正在解决codejam拼图或类似的东西是相当安全的,所以我们看着整个“包含一个inteter的行,N,然后N行包含一堆数字”的东西。看看他的代码,它就是这样做的。 –

+0

关于可扩展性,当我们需要从带有数字的行中提取更多信息时,可以轻松扩展此解决方案。虽然Michiel Borkent的解决方案可以很容易地扩展,但部分之间的间隔是任意的。这两种解决方案都是可扩展的。关于这个解决方案:据我所知,阅读文档时,当需要一次性递归函数时,'loop'和'recur'更具惯用,然后用'let'定义本地函数。 – sesm