How to take n random items from a collection in Clojure?
clojure
Solution
Well, I'm no clojure veteran, but how about:
(defn shuffle-with-seed
"Return a random permutation of coll with a seed"
[coll seed]
(let [al (java.util.ArrayList. coll)
rnd (java.util.Random. seed)]
(java.util.Collections/shuffle al rnd)
(clojure.lang.RT/vector (.toArray al))))
(defn take-rand [n coll & [seed]]
(take n (if seed
(shuffle-with-seed coll seed)
(shuffle coll))))
`shuffle-with-seed` is similiar to clojure's `shuffle`, it just passes an instance of `Random` to Java's `java.util.Collections.shuffle()`.
Problem
I have a function that takes n random items from a collection such that the same item is never picked twice. I could accomplish this quite easily: ``` (defn take-rand [n coll] (take n (shuffle coll))) ``` But I have a pesky requirement that I need to return the same random subset when the same seed is provided, i.e. ``` (defn take-rand [n coll & [seed]] ) (take-rand 5 (range 10) 42L) ;=> (2 5 8 6 7) (take-rand 5 (range 10) 42L) ;=> (2 5 8 6 7) (take-rand 5 (range 10) 27L) ;=> (7 6 9 1 3) (take-rand 5 (range 10)) ;=> (9 7 8 5 0) ``` I have a solution for this, but it feels a bit clunky and not very idiomatic. Can any Clojure veterans out there propose improvements (or a completely different approach)? Here's what I did: ``` (defn take-rand "Returns n randomly selected items from coll, or all items if there are fewer than n. No item will be picked more than once." [n coll & [seed]] (let [n (min n (count coll)) rng (if seed (java.util.Random. seed) (java.util.Random.))] (loop [out [], in coll, n n] (if (or (empty? in) (= n 0)) out (let [i (.nextInt rng n)] (recur (conj out (nth in i)) (concat (take i in) (nthrest in (inc i))) (dec n))))))) ```