如何使用rand-int生成可重复的随机序列

时间:2014-06-03 04:20:06

标签: clojure

我希望能够在Clojure中使用rand生成可重复的数字。 (具体来说,我希望调用rand-nth或Incanter sample的结果可重复,并调用rand-int,然后调用rand。)

我从this question想出,如果我使用clojure.data.generators,我可以重置随机状态:

(require '[clojure.data.generators :as gen])
(alter-var-root #'gen/*rnd* (constantly (java.util.Random. 437)))
(gen/reservoir-sample 5 (range 1000)) ; => [940 591 636 12 755]
(gen/reservoir-sample 5 (range 1000)) ; => [376 540 827 307 463]
; reset random state:
(alter-var-root #'gen/*rnd* (constantly (java.util.Random. 437)))
; now the same results are generated again:
(gen/reservoir-sample 5 (range 1000)) ; => [940 591 636 12 755]
(gen/reservoir-sample 5 (range 1000)) ; => [376 540 827 307 463]

但是,该方法似乎只影响clojure.data.generators中的函数,这并不奇怪:

(alter-var-root #'gen/*rnd* (constantly (java.util.Random. 437)))
(rand) ; => 0.9372552374760151
(rand) ; => 0.2712729314667742
; reset random state:
(alter-var-root #'gen/*rnd* (constantly (java.util.Random. 437)))
; not same results as before:
(rand) ; => 0.630238593767316
(rand) ; => 0.426744420572015

如何恢复随机状态,以便从rand获得可重复的结果?到目前为止,我还没有找到任何关于此的文档。

Another question听起来似乎可能是同一个问题,但它会询问完全不同的东西。)

7 个答案:

答案 0 :(得分:4)

可能不是最干净的方式,但你可以通过重新定义clojure.core/rand来实现它:

(ns clojure.core)

(def r (java.util.Random. 1))

(defn rand
  ([] (.nextDouble r))
  ([n] (.nextInt r n)))

(take 10 (repeatedly #(rand-int 10)))

每次运行时都会产生(5 8 7 3 4 4 4 6 8 8)。

答案 1 :(得分:2)

以下是如何定义可以在评论中描述的种子的函数生成器。

如果您不想要双打,请参阅Javadoc

user=> (defn randfn
  #_=>   ([] (randfn (java.util.Random.)))
  #_=>   ([r] #(.nextDouble r)))
#'user/randfn
user=> (def source1 (randfn))
#'user/source1
user=> (source1)
0.6270662940925175
user=> (source1)
0.23351789802762046

以下是使用种子随机数生成器创建它的方法。

user=> (def source2 (randfn (java.util.Random. 37)))
#'user/source2
user=> (take 3 (repeatedly #(source2)))
(0.7276532767062343 0.5136790759391296 0.7384220244718898)

user=> (def source3 (randfn (java.util.Random. 37)))
#'user/source3
user=> (take 3 (repeatedly #(source3)))
(0.7276532767062343 0.5136790759391296 0.7384220244718898

作为奖励,您还可以使用新的ThreadLocalRandom或非常新的SecureRandom作为随机数生成器。

user=> (def secure-source (randfn (java.security.SecureRandom.)))
#'user/secure-source
user=> (take 3 (repeatedly #(secure-source)))
(0.9987555822097023 0.48452119609266475 0.443029180668418)

答案 2 :(得分:2)

干净的方式:

(ns designed.ly.rand)

(def ^:dynamic *rand* clojure.core/rand)

(defn rand-1
 ([]
   (*rand* 1))
 ([n]
   (*rand* n)))

(defmacro with-rand-seed
 "Sets seed for calls to random in body. Beware of lazy seqs!"
 [seed & body]
  `(let [g# (java.util.Random. ~seed)]
     (binding [*rand* #(* % (.nextFloat g#))]
      (with-redefs [rand rand-1]
        ~@body))))

它在范围内重新定义了兰特。例如:

(with-rand-seed 9
  (rand 4)       ; => 2.9206461906433105
  (rand-int 10)) ; => 2

顺便说一句。请注意懒惰的序列:http://kotka.de/blog/2009/11/Taming_the_Bound_Seq.html(显然,此链接会重定向到没有受信任证书的https,因此这里是指向Web Archive版本的链接:https://web.archive.org/web/20120505012701/http://kotka.de/blog/2009/11/Taming_the_Bound_Seq.html)。

答案 3 :(得分:2)

Clojure的rand依赖于random中的java.lang.Math方法:

user=> (source rand)
(defn rand
  "Returns a random floating point number between 0 (inclusive) and
  n (default 1) (exclusive)."
  {:added "1.0"
   :static true}
  ([] (. Math (random)))
  ([n] (* n (rand))))

根据https://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#random()

上的Oracle Java 7文档
  

public static double random()

     

返回带有正号的double值,大于或等于0.0且小于1.0。返回值是伪随机选择的,具有来自该范围的(近似)均匀分布。

     

首次调用此方法时,它会创建一个新的伪随机数生成器,就像表达式

一样      

new java.util.Random()

     

此新的伪随机数生成器此后用于对此方法的所有调用,并且在其他任何地方都没有使用。

     

此方法已正确同步,以允许多个线程正确使用。但是,如果许多线程需要以很高的速率生成伪随机数,则可以减少每个线程争用自己的伪随机数生成器的争用。

     

返回:

     

大于或等于0.0且小于1.0的伪随机双。

如果您浏览java.lang.Math文档,您将看到它没有允许设置随机数生成器种子的API。使用random() API的代码无法设置种子或保留随机数生成器的不同副本。

最初的问题是:

  

我希望能够在Clojure中使用rand生成可重复的数字。   [例如。 clojure.core /兰特]

除非您使用自己的rand功能,否则这是不可能的。因此,我建议使用clojure.data.generators而不是clojure.core/rand,将随机数生成器公开为动态变量*rnd*

(def ^:dynamic ^java.util.Random
     *rnd*
     "Random instance for use in generators. By consistently using this
instance you can get a repeatable basis for tests."
     (java.util.Random. 42))

使用gen/*rnd*,如下例所示:

(require '[clojure.data.generators :as gen])
(binding [gen/*rnd* (java.util.Random. 12345)]
  (gen/int))

这总是返回-593551136

答案 4 :(得分:1)

在最初的问题中,有两件事被误解了:

  • 如何使用动态变量
  • 如何使用clojure.data.generators

首先,应通过binding宏管理动态变量。第二个rand不是clojure.data.generators的函数,而是clojure.core本身的函数,因此重置*rnd* var并不起作用。所以这是你应该怎么做的:

(require '[clojure.data.generators :as gen])

(binding [gen/*rnd* (java.util.Random. 437)]
  (println (gen/double)) ;=> 0.7634858067742888
  (println (gen/double)) ;=> 0.6959205688388975
  )

(binding [gen/*rnd* (java.util.Random. 437)]
  (println (gen/double)) ;=> 0.7634858067742888
  (println (gen/double)) ;=> 0.6959205688388975
  )

答案 5 :(得分:1)

只是想补充一下,对于仍在查看此内容的任何人,自2015年以来,clojars上已经有一个库可以做到这一点:

https://github.com/trystan/random-seed

答案 6 :(得分:0)

据我所知,以上都不是功能性方法。如果你想要,你可以使用lazy-seq:

<!DOCTYPE html>
<html>
<head>

</head>
<body>

<h2>Slide in Overlay from the Bottom</h2>
<p>Hover over the image to see the effect.</p>

<div class="container">
  <img src="img_avatar.png" alt="Avatar" class="image">
  <div class="overlay">
    <div class="text">Hello World</div>
  </div>
</div>

</body>
</html>

获取您使用的第一个随机数:

(letfn [(f [randomizer]  (lazy-seq (cons (.nextInt randomizer) (f randomizer))))]
  (defn create-random 
    ([] (f (java.util.Random.)))
    ([seed] (f (java.util.Random. seed)))))

要获得您使用的其余序列:

(first (create-random 0)) ;; using 0 as seed

要获取第一个元素和对其余元素的引用,您可以使用:

(rest (create-random 0)) ;; again 0 as seed