简体   繁体   English

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

[英]Concatenate elements to list by using loop in clojure?

I am trying to get into Lisps and FP by trying out the 99 problems. 我试图通过尝试99个问题来了解Lisps和FP。

Here is the problem statement (Problem 15) 这是问题陈述(问题15)

Replicate the elements of a list a given number of times. 将列表的元素复制给定的次数。

I have come up with the following code which simply returns an empty list [] 我想出了以下代码,它们仅返回一个空列表[]

I am unable to figure out why my code doesn't work and would really appreciate some help. 我无法弄清楚为什么我的代码无法正常工作,并且会非常感谢您的帮助。

(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")
  )

copying my comment to answer: 复制我的评论以回答:

this line: (conj returnList head) doesn't modify returnlist, rather it just drops the result in your case. 这行代码: (conj returnList head)不会修改returnlist,而只是删除结果。 You should restructure your program to pass the accumulated list further to the next iteration. 您应该重组程序,以将累积的列表进一步传递到下一个迭代。 But there are better ways to do this in clojure. 但是在Clojure中有更好的方法可以做到这一点。 Like (defn replicate-list [data times] (apply concat (repeat times data))) (defn replicate-list [data times] (apply concat (repeat times data)))

If you still need the loop/recur version for educational reasons, i would go with this: 如果出于教育原因您仍然需要循环/重复版本,我可以这样做:

(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)
;;=> []

update 更新

based on the clarified question, the simplest way to do this is 根据明确的问题,最简单的方法是

(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)

and the loop/recur variant: 和循环/重复变量:

(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)
;;=> []

Here is a version based on the original post, with minimal modifications: 这是基于原始帖子的版本,仅作了最小的修改:

;; 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)))))))

Please keep in mind that Clojure is mainly a functional language, meaning that most functions produce their results as a new return value instead of updating in place. 请记住,Clojure主要是一种功能语言,这意味着大多数函数将其结果作为新的返回值而不是在原位置进行更新。 So, as pointed out in the comment, the line (conj returnList head) will not have an effect, because it's return value is ignored. 因此,正如注释中指出的那样,该行(conj returnList头)将无效,因为它的返回值将被忽略。

The above version works, but does not really take advantage of Clojure's sequence processing facilities. 上面的版本可以使用,但是并没有真正利用Clojure的序列处理工具。 So here are two other suggestions for solving your problem: 因此,这里有另外两个建议可以解决您的问题:

;; 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))

One thing is not clear about your question though : From your implementation, it looks like you want to create a new list where each element is repeated n times, eg 不过,您的问题尚不清楚 :在您的实现中,您似乎想创建一个新列表,其中每个元素重复n次,例如

playground.replicate> (replicateList [1 2 3] 4)
[1 1 1 1 2 2 2 2 3 3 3 3]

But if you would instead like this result 但是如果您希望得到这个结果

playground.replicate> (replicateList [1 2 3] 4)
[1 2 3 1 2 3 1 2 3 1 2 3]

the answer to your question will be different. 您问题的答案将有所不同。

If you want to learn idiomatic Clojure you should try to find a solution without such low level facilities as loop . 如果您想学习惯用的Clojure,则应尝试找到一种没有loop这样低级功能的解决方案。 Rather try to combine higher level functions like take , repeat , repeatedly . 而是尝试结合更高级别的功能,例如“ take ,“ repeat ,“ repeatedly If you're feeling adventurous you might throw in laziness as well. 如果您喜欢冒险,也可能会感到懒惰。 Clojure's sequences are lazy, that is they get evaluated only when needed. Clojure的序列是惰性的,即仅在需要时才对其进行评估。

One example I came up with would be 我想出的一个例子是

(defn repeat-list-items [l n]
  (lazy-seq
   (when-let [s (seq l)]
     (concat (repeat n (first l))
             (repeat-list-items (next l) n)))))

Please also note the common naming with kebab-case 另请注意烤肉串的常见命名

This seems to do what you want pretty well and works for an unlimited input (see the call (range) below), too: 这似乎可以很好地满足您的要求,并且可以无限输入(请参见下面的调用(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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM