Marcus Junius Brutus
Marcus Junius Brutus

Reputation: 27276

clojure for sequence comprehnsion adding two elements at a time

The comprehension:

(for [i (range 5])] i)

... yields: (0 1 2 3 4)

Is there an idiomatic way to get (0 0 1 1 2 4 3 9 4 16) (i.e. the numbers and their squares) using mostly the for comprehension?

The only way I've found so far is doing a:

(apply concat (for [i (range 5)] (list i (* i i))))

Upvotes: 2

Views: 226

Answers (3)

Beyamor
Beyamor

Reputation: 3378

Actually, using only for is pretty simple if you consider applying each function (identity and square) for each value.

(for [i (range 5),             ; for every value
      f [identity #(* % %)]]   ; for every function
  (f i))                       ; apply the function to the value

 ; => (0 0 1 1 2 4 3 9 4 16)

Upvotes: 5

NielsK
NielsK

Reputation: 6956

Since for loops x times, it will return a collection of x values. Multiple nested loops (unless limited by while or when) will give x * y * z * ... results. That is why external concatenation will always be necessary.

A similar correlation between input and output exists with map. However, if multiple collections are given in map, the number of values in the returned collection is the size of the smallest collection parameter.

=> (map (juxt identity #(* % %)) (range 5))
([0 0] [1 1] [2 4] [3 9] [4 16])

Concatenating the results of map is so common mapcat was created. Because of that, one might argue mapcat is a more idiomatic way over for loops.

=> (mapcat (juxt identity #(* % %)) (range 5))
(0 0 1 1 2 4 3 9 4 16)

Although this is just shorthand for apply concat (map, and a forcat function or macro could be created just as easily.

However, if an accumulation over a collection is needed, reduce is usually considered the most idiomatic.

=> (reduce (fn [acc i] (conj acc i (* i i))) [] (range 5))
[0 0 1 1 2 4 3 9 4 16]

Both the for and map options would mean traversing a collection twice, once for the range, and once for concatenating the resulting collection. The reduce option only traverses the range.

Care to share why "using mostly the for comprehension" is a requirement ?

Upvotes: 4

HMM
HMM

Reputation: 3013

I think you are doing it right.

A slightly compressed way maybe achieved using flatten

(flatten (for [i (range 5)] [ i (* i i) ] ))

But I would get rid of the for comprehension and just use interleave

(let [x (range 5)
      y (map #(* % %) x)]
  (interleave x y))

Disclaimer: I am just an amateur clojurist ;)

Upvotes: 2

Related Questions