Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is an idiomatic way to implement double loop over a vector in Clojure?

Tags:

clojure

I am new to Clojure and it's hard for me to idiomatically implement basic manipulations with data structures.

What would be an idiomatic way to implement the following code in Clojure?

l = [...]
for i in range(len(l)):
  for j in range(i + 1, len(l)):
    print l[i], l[j]
like image 514
Ivan Mushketyk Avatar asked Dec 24 '15 13:12

Ivan Mushketyk


1 Answers

the simplest (but not the most FP-ish) is almost identical to your example:

(let [v [1 2 3 4 5 6 7]]
  (doseq [i (range (count v))
          j (range (inc i) (count v))]
    (println (v i) (v j))))

and here is more functional variant to generate all these pairs (it isn't based on length or indices, but rather on the tail iteration):

(let [v [1 2 3 4 5 6 7]]
  (mapcat #(map (partial vector (first %)) (rest %))
          (take-while not-empty (iterate rest v))))

output:

 ([1 2] [1 3] [1 4] [1 5] [1 6] [1 7] [2 3] [2 4] 
  [2 5] [2 6] [2 7] [3 4] [3 5] [3 6] [3 7] [4 5] 
  [4 6] [4 7] [5 6] [5 7] [6 7])

then just use these pairs in doseq for any side effect:

(let [v [1 2 3 4 5 6 7]
      pairs (fn [items-seq]
              (mapcat #(map (partial vector (first %)) (rest %))
                      (take-while not-empty (iterate rest items-seq))))]
  (doseq [[i1 i2] (pairs v)] (println i1 i2)))

update: following @dg123's answer. it is nice, but you can make it even better, using doseq's and for's features like destructuring and guards:

(let [v [1 2 3 4 5 6 7]]
  (doseq [[x & xs] (iterate rest v)
          :while xs
          y xs]
    (println "x:" x "y:" y)))

you iterate through the tails of a collection, but remember, that iterate produces an infinite coll:

user> (take 10 (iterate rest [1 2 3 4 5 6 7]))
([1 2 3 4 5 6 7] (2 3 4 5 6 7) (3 4 5 6 7) 
 (4 5 6 7) (5 6 7) (6 7) (7) () () ())

so you have to limit it somehow to include just not empty collections. the destructuring form [x & xs] splits the argument to a first param and the sequence of the rest params:

user> (let [[x & xs] [1 2 3 4 5 6]]
        (println x xs))
1 (2 3 4 5 6)
nil

and when the binded collection is empty, or have a single element, the xs would be nil:

user> (let [[x & xs] [1]]
        (println x xs))
1 nil
nil

so you just make use of this feature, using :while guard in a list comprehension.

in the end you just construct pairs (or do some side effect in this case) for x and every item in xs

like image 102
leetwinski Avatar answered Sep 24 '22 22:09

leetwinski