我想编写函数collect,它可以将子列表组合成一个列表,如:
user=> (collect '(a b c d e) 1)
((a)(b)(c)(d)(e))
user=> (collect '(a b c d e) 2)
((a b)(c d)(e))
user=> (collect '(a b c d e) 5)
(a b c d e))
这是我的代码:
(defn collect [lst num]
(loop [l lst res (atom ())]
(if (<= (count l) num) @res
(recur (drop num l) (swap! res conj (take num (drop num l)))))))
但是当我跑步时
user=> (collect '(a b c d e) 1)
我收到了错误:
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IAtom clojure.core/swap!
当我使用“swap”时,为什么我无法获得res? ?谢谢。
答案 0 :(得分:3)
它在第二次通过循环时爆炸了。
swap
返回放入原子的值,而不是它自己的原子。所以第一遍是更新原子,然后将刚刚放入原子的值传递给第二次通过循环。在第二遍中,它试图将该值用作原子,并获得上述异常。
To&#34; fix&#34;这使用do
更新原子,然后一旦包含正确的值,就将原子传递给下一个循环。
user> (defn collect [lst num]
(loop [l lst res (atom ())]
(if (<= (count l) num) @res
(recur (drop num l)
(do (swap! res conj (take num (drop num l)))
res)))))
#'user/collect
user> (collect '(a b c d e) 2)
((e) (c d))
在这种情况下,您也可以完全删除原子并获得完全相同的结果(我通过在初始值中使用[]
代替()
来修复示例中的排序问题RES):
user> (defn collect [lst num]
(loop [l lst res []]
(if (<= (count l) num) res
(recur (drop num l)
(conj res (take num (drop num l)))))))
#'user/collect
user> (collect '(a b c d e) 2)
[(c d) (e)]
当然,您也可以使用上面提到的partition-all
。
答案 1 :(得分:1)
;; this would be a correct way to do it
(defn collect [coll n]
(partition-all n coll))
;; this would be a clumsy way to do it
(defn collect
"using a loop (there is not point to do that but at least you can see the logic working as in your example)"
[coll n]
(lazy-seq
(loop [res []
coll coll]
(if (empty? coll)
res
(recur (conj res (take n coll)) (drop n coll))))))
关于您的错误,在第二个循环中,res
是类似列表的值,而不再是atom
。这将导致我们:
(defn collect [coll n]
(lazy-seq (loop [res (atom [])
coll coll]
(if (empty? coll)
@res
(recur (do (swap! res conj (take n coll))
;; return the atom instead of the value'
res)
(drop n coll))))))
请注意,为了保留解决方案中的顺序,我使用向量(litteral []
)而不是列表(litteral '()
)。这是因为conj
described here的行为。