3

What is the idiomatic way to get something like that?

((fn [coll] (function-body)) [:a :b :c :d]) 
-> [[:a :b][:a :c][:a :d][:b :c][:b :d][:c :d]]

I can only do this in this way.

#(for [i (range (count %))
       j (range (inc i) (count %))]
   [(nth % i) (nth % j)])

But this is ugly, and at bigger collections very slow. I would like to avoid a loop/recur

5
  • I'd approach it with recursion, taking each key with (first) and calling some inner function over the rest with (rest ) until (= 0 (count (rest ))) (but I'm having a heck of time trying this in tryclj.com, maybe this can help some else who knows more than I do. Commented May 16, 2014 at 23:54
  • What kind of collections is it being slow for, specifically? I'd expect reasonable performance with vectors, awful performance with lists. Commented May 17, 2014 at 1:38
  • vectors are fast. "nth" is slow, very slow. Commented May 17, 2014 at 9:00
  • @HuxleySource Running some timings on a million element vector v, I found that (nth v 999999) is as fast as (v 999999). Commented May 17, 2014 at 22:34
  • @Thumbnail i agree, but iterating over collection is faster, than get each value by index. (for [elem (range 1000)] elem) is faster than (let [r (range 1000)] (for [idx r] (nth r idx))) Commented May 19, 2014 at 19:22

5 Answers 5

3

Using recursion explicitly ...

(defn pairs [coll]
  (if-let [[x & xs] (seq coll)]
    (lazy-cat (map #(vector x %) xs) (pairs xs))))

For example,

(pairs [:a :b :c :d])
; ([:a :b] [:a :c] [:a :d] [:b :c] [:b :d] [:c :d])

This is a lazy sequence. You can use vec to pour it into a vector if need be: a decision better left to the client, I think.

Sign up to request clarification or add additional context in comments.

Comments

0

Here's a lazy solution.

user> (def data [:a :b :c :d]) 
#'user/data
user> (defn loopy [data] 
         (lazy-seq (if (seq data) 
                       (cons (map #(vector (first data) %) (rest data)) 
                             (loopy (rest data))) 
                        nil))) 
#'user/loopy   

user> (apply concat (loopy data)) 
([:a :b] [:a :c] [:a :d] [:b :c] [:b :d] [:c :d])

this produces a lazy sequence where each element it a lazy sequence of creating a vector of the current element into a vector with each of the rest of the elements. The next segment is generated lazily when this segment is empty by the call to (apply concat ).

Comments

0

This is along the lines of Juan Manuel's answer, I find it a bit more readable using list comprehension for the last part. The anonymous function generates the pairs [(first s) x]l for all the elements in (rest s).

(defn pairs [xs]
   (->> xs
       (iterate rest)
       (take-while seq)
       (mapcat #(for [x (rest %)]
                  [(first %) x]))))

Comments

0

I've found an ugly pipeline that solves the problem. I don't find it much elegant but I haven't been able to beautify it.

(->> [:a :b :c :d] 
     (iterate rest) 
     (take-while not-empty) 
     (mapcat (fn [[f & r]] (map #(vector f %) r)))
     vec)

Let's analyze the function in the mapcat when given list [:a :b :c :d]. It computes all vectors with first element :a and second element succesive elements taken from [:b :c :d], that is([:a :b] [:a :c] [:a :d])`.

The first part of the pipeline, constructs the sequence of all the rests od the original vector ([:a :b :c :d](:b :c :d)(:c :d)(:d) () () () ...) which is stopped at the first empty rest.

Then the mapcat plugs this list in the combining function described before, and the vec at the end, converts the sequence into a vector.

This can be more readable by introducing some auxiliar functions:

(defn rests [coll] 
  (take-while not-empty (iterate rest coll)))

(defn pairfirst [[f & r]] 
  (map #(vector f %) r))

(defn pairs [coll]
  (mapcat pairfirst (rests coll)))

Comments

0

for is already lazy, and filter is too. So one way to do this, if the elements have a natural order, is to produce the cartesian product, and then filter on sort order.

(defn pairs [c]
  (->> (for [x c, y c] [x y])
       (filter #(apply <= %))))

(def c (range 0 1e7))

(time (nth (pairs c) 0))    ;; ~ 0.07 ms
(time (nth (pairs c) 1e4))  ;; ~ 6 ms
(time (nth (pairs c) 1e7))  ;; ~ 2600 ms

I think your use of nth might be slowing the collection iteration down:

(def pairs2
  #(for [i (range (count %))
         j (range (inc i) (count %))]
     [(nth % i) (nth % j)]))

(def c (range 0 1e7))

(time (nth (pairs2 c) 0))    ;; ~ 960 ms
(time (nth (pairs2 c) 1e4))  ;; ~ 1514 ms
(time (nth (pairs2 c) 1e7))  ;; ~ ¯\_(ツ)_/¯

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.