0

I am trying to get into Lisps and FP by trying out the 99 problems.

Here is the problem statement (Problem 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")
  )
3
  • this line: (conj returnList head) doesn't modify returnlist, rather , the result is just dropped in your case. 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. Like (defn replicate-list [data times] (apply concat (repeat times data))) Commented Jul 12, 2018 at 8:04
  • or (defn replicate-list [data times] (take (* (count data) times) (cycle data))) Commented Jul 12, 2018 at 8:12
  • If I call (replicateList [1 2 3] 2), should I expect the result [1 2 3 1 2 3] or [1 1 2 2 3 3]? Commented Jul 12, 2018 at 8:29

3 Answers 3

3

copying my comment to answer:

this line: (conj returnList head) doesn't modify returnlist, rather it just drops the result in your case. 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. Like (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)
;;=> []
Sign up to request clarification or add additional context in comments.

1 Comment

... and I'd swap the arguments to conform to core repeat.
2

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. So, as pointed out in the comment, the line (conj returnList head) will not have an effect, because it's return value is ignored.

The above version works, but does not really take advantage of Clojure's sequence processing facilities. 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, e.g.

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.

1 Comment

Yeah I was attempting to produce the first result. I added a test case to clarify that
2

If you want to learn idiomatic Clojure you should try to find a solution without such low level facilities as loop. Rather try to combine higher level functions like 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.

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:

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.