如何在不使用递归的情况下将输入分割为可变长度的块?
我有以下内容输入文件:如何在不使用递归的情况下将输入分割为可变长度的块?
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中不是惯用的。
有没有更好的方法来解决这个问题?
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))
。
因此,这个解决方案忽略了这样一个事实,即数字指定,我们应该读取多少行,并且只使用数字作为分隔符的行。井井有条。 – 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))))
也可以使用edn/read-string,(解析东西“3 a b c \ nstuff21”)也可以解析,就像我的解决方案一样。 –
呃,没有。不,它不。至少不是如果输入结构良好。我认为说这个人正在解决codejam拼图或类似的东西是相当安全的,所以我们看着整个“包含一个inteter的行,N,然后N行包含一堆数字”的东西。看看他的代码,它就是这样做的。 –
关于可扩展性,当我们需要从带有数字的行中提取更多信息时,可以轻松扩展此解决方案。虽然Michiel Borkent的解决方案可以很容易地扩展,但部分之间的间隔是任意的。这两种解决方案都是可扩展的。关于这个解决方案:据我所知,阅读文档时,当需要一次性递归函数时,'loop'和'recur'更具惯用,然后用'let'定义本地函数。 – sesm
博尔肯先生有正确的做法。我还建议你看看'recur'和'loop'来实现Clojure的尾递归版本。 – WolfeFan
感谢您指出'recur'和'loop'! – sesm