我正在尝试通过解决99个问题来了解Lisps和FP。
这是问题陈述(问题15)
将列表的元素复制给定的次数。
我想出了以下代码,它们仅返回一个空列表[]
我无法弄清楚为什么我的代码无法正常工作,并且会非常感谢您的帮助。
(defn replicateList "Replicates each element of the list n times" [l n]
(loop [initList l returnList []]
(if (empty? initList)
returnList
(let [[head & rest] initList]
(loop [x 0]
(when (< x n)
(conj returnList head)
(recur (inc x))))
(recur rest returnList)))))
(defn -main
"Main" []
(test/is (=
(replicateList [1 2] 2)
[1 1 2 2])
"Failed basic test")
)
答案 0 :(得分:3)
复制我的评论以回答:
此行:(conj returnList head)
不会修改返回列表,而是只会根据您的情况删除结果。您应该重组程序,以将累积的列表进一步传递到下一个迭代。但是在Clojure中有更好的方法可以做到这一点。像(defn replicate-list [data times] (apply concat (repeat times data)))
如果出于教育原因仍需要循环/递归版本,我会这样做:
(defn replicate-list [data times]
(loop [[h & t :as input] data times times result []]
(if-not (pos? times)
result
(if (empty? input)
(recur data (dec times) result)
(recur t times (conj result h))))))
user> (replicate-list [1 2 3] 3)
;;=> [1 2 3 1 2 3 1 2 3]
user> (replicate-list [ ] 2)
;;=> []
user> (replicate-list [1 2 3] -1)
;;=> []
更新
根据明确的问题,最简单的方法是
(defn replicate-list [data times]
(mapcat (partial repeat times) data))
user> (replicate-list [1 2 3] 3)
;;=> (1 1 1 2 2 2 3 3 3)
和循环/重复变量:
(defn replicate-list [data times]
(loop [[h & t :as data] data n 0 res []]
(cond (empty? data) res
(>= n times) (recur t 0 res)
:else (recur data (inc n) (conj res h)))))
user> (replicate-list [1 2 3] 3)
;;=> [1 1 1 2 2 2 3 3 3]
user> (replicate-list [1 2 3] 0)
;;=> []
user> (replicate-list [] 10)
;;=> []
答案 1 :(得分:2)
这是基于原始帖子的版本,仅作了最小的修改:
;; Based on the original version posted
(defn replicateList "Replicates each element of the list n times" [l n]
(loop [initList l returnList []]
(if (empty? initList)
returnList
(let [[head & rest] initList]
(recur
rest
(loop [inner-returnList returnList
x 0]
(if (< x n)
(recur (conj inner-returnList head) (inc x))
inner-returnList)))))))
请记住,Clojure主要是一种函数语言,这意味着大多数函数将其结果作为新的返回值而不是就地更新。因此,正如注释中指出的那样,该行(conj returnList头)将无效,因为它将忽略其返回值。
以上版本有效,但并没有真正利用Clojure的序列处理功能。因此,这里有另外两个解决问题的建议:
;; Using lazy seqs and reduce
(defn replicateList2 [l n]
(reduce into [] (map #(take n (repeat %)) l)))
;; Yet another way using transducers
(defn replicateList3 [l n]
(transduce
(comp (map #(take n (repeat %)))
cat
)
conj
[]
l))
虽然您的问题尚不清楚:在您的实现中,您似乎想创建一个新列表,其中每个元素重复n次,例如
playground.replicate> (replicateList [1 2 3] 4)
[1 1 1 1 2 2 2 2 3 3 3 3]
但是如果您希望得到这个结果
playground.replicate> (replicateList [1 2 3] 4)
[1 2 3 1 2 3 1 2 3 1 2 3]
您问题的答案将有所不同。
答案 2 :(得分:2)
如果您想学习惯用的Clojure,则应尝试找到一种没有loop
这样的低级功能的解决方案。而是尝试结合更高级别的功能,例如take
,repeat
,repeatedly
。如果您喜欢冒险,也可能会感到懒惰。 Clojure的序列是惰性的,即仅在需要时才对其进行评估。
我想到的一个例子是
(defn repeat-list-items [l n]
(lazy-seq
(when-let [s (seq l)]
(concat (repeat n (first l))
(repeat-list-items (next l) n)))))
也请注意kebab-case的常用命名方式
这似乎可以很好地满足您的要求,并且可以无限输入(请参见下面的调用(range)
)
experi.core> (def l [:a :b :c])
#'experi.core/
experi.core> (repeat-list-items l 2)
(:a :a :b :b :c :c)
experi.core> (repeat-list-items l 0)
()
experi.core> (repeat-list-items l 1)
(:a :b :c)
experi.core> (take 10 (drop 10000 (repeat-list-items (range) 4)))
(2500 2500 2500 2500 2501 2501 2501 2501 2502 2502)