如何从Clojure的集合中获取n个随机项?

时间:2014-07-03 12:03:37

标签: clojure

我有一个函数,它从一个集合中获取n个随机项,以便永远不会选择相同的项两次。我很容易就能做到这一点:

(defn take-rand [n coll]
  (take n (shuffle coll)))

但我有一个令人讨厌的要求,即当提供相同的种子时,我需要返回相同的随机子集,即

(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)

我有一个解决方案,但它感觉有点笨重,不是很惯用。那里的任何Clojure老兵都可以提出改进(或者采用完全不同的方法)吗?

这就是我的所作所为:

(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)))))))

2 个答案:

答案 0 :(得分:8)

嗯,我不是老练的,但是怎么样:

(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与clojure shuffle类似,只是将Random的实例传递给Java的java.util.Collections.shuffle()

答案 1 :(得分:4)

将第一个解决方案中的shuffle函数替换为可重现的函数。我将在下面向您展示我的解决方案:

(defn- shuffle'
  [seed coll]
  (let [rng (java.util.Random. seed)
        rnd #(do % (.nextInt rng))]
    (sort-by rnd coll)))

(defn take-rand'
  ([n coll]
     (->> coll shuffle (take n)))
  ([n coll seed]
     (->> coll (shuffle' seed) (take n))))

我希望这个解决方案能够达到预期效果:

user> (take-rand' 5 (range 10))
(5 4 7 2 6)

user> (take-rand' 5 (range 10))
(1 9 0 8 5)

user> (take-rand' 5 (range 10))
(5 2 3 1 8)

user> (take-rand' 5 (range 10) 42)
(2 6 4 8 1)

user> (take-rand' 5 (range 10) 42)
(2 6 4 8 1)

user> (take-rand' 5 (range 10) 42)
(2 6 4 8 1)