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

Jos*_*ver 5 clojure

我有一个函数,它从一个集合中获取n个随机项,使得相同的项永远不会被选中两次.我很容易完成这件事:

(defn take-rand [n coll]
  (take n (shuffle coll)))
Run Code Online (Sandbox Code Playgroud)

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

(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)
Run Code Online (Sandbox Code Playgroud)

我有一个解决方案,但它感觉有点笨重,而不是非常惯用.任何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)))))))
Run Code Online (Sandbox Code Playgroud)

slo*_*oth 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))))
Run Code Online (Sandbox Code Playgroud)

shuffle-with-seed与clojure类似shuffle,它只是将一个实例传递Random给Java java.util.Collections.shuffle().

  • +1不重新发明轮子!:) (2认同)

tno*_*oda 5

用可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))))
Run Code Online (Sandbox Code Playgroud)

我希望这个解决方案能产生你预期的结果:

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)
Run Code Online (Sandbox Code Playgroud)