通过使用Clojure中的循环来连接要列出的元素?

时间:2018-07-12 06:34:19

标签: list clojure functional-programming lisp

我正在尝试通过解决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")
  )

3 个答案:

答案 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这样的低级功能的解决方案。而是尝试结合更高级别的功能,例如takerepeatrepeatedly。如果您喜欢冒险,也可能会感到懒惰。 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)