How to generate repeatable random sequences with rand-int

|▌冷眼眸甩不掉的悲伤 提交于 2020-01-12 07:20:46

问题


I want to be able to generate repeatable numbers using rand in Clojure. (Specifically, I want results of calls to rand-nth or Incanter's sample to be repeatable, and these call rand-int which in turn calls rand).

I figured out from this question that if I use clojure.data.generators, I can reset the random state:

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

However, that method only seems to affect functions in clojure.data.generators, which isn't surprising:

(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

How can I restore the random state in such as way as to get repeatable results from rand? So far I haven't found any documentation about this.

(Another question sounds as if it might be the same issue, but it's asking about something completely different.)


回答1:


Probably not the cleanest way, but you can make it work by redefining 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)))

This produces (5 8 7 3 4 4 4 6 8 8) every time I run it.




回答2:


Clojure's rand relies on the random method in 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))))

According to the Oracle Java 7 documentation at https://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#random()

public static double random()

Returns a double value with a positive sign, greater than or equal to 0.0 and less than 1.0. Returned values are chosen pseudorandomly with (approximately) uniform distribution from that range.

When this method is first called, it creates a single new pseudorandom-number generator, exactly as if by the expression

new java.util.Random()

This new pseudorandom-number generator is used thereafter for all calls to this method and is used nowhere else.

This method is properly synchronized to allow correct use by more than one thread. However, if many threads need to generate pseudorandom numbers at a great rate, it may reduce contention for each thread to have its own pseudorandom-number generator.

Returns:

a pseudorandom double greater than or equal to 0.0 and less than 1.0.

If you browse the java.lang.Math documentation, you will see that it does not have an API to allow setting of a random number generator seed. Code using the random() API does not get the ability to set the seed or hold onto different copies of the random number generator.

The original question was:

I want to be able to generate repeatable numbers using rand in Clojure. [e.g. clojure.core/rand]

This is not possible, unless you use your own rand function. So, instead of clojure.core/rand, I suggesting using clojure.data.generators which exposes the random number generator as a dynamic var *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))

Use gen/*rnd* as shown in this example:

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

This always returns -593551136.




回答3:


Here's how you might define the function generator that you can seed that I described in the comments.

If you don't want doubles, see the 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

Here's how you might create it with a seeded Random number generator.

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

As a bonus, you could also use the newish ThreadLocalRandom or the not very new at all SecureRandom as your random number generators.

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



回答4:


A clean way:

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

It redefines rand within the scope. Example:

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

BTW. Beware of lazy seqs: http://kotka.de/blog/2009/11/Taming_the_Bound_Seq.html (Apparently, this link redirects to https without a trusted certificate so here's a link to a version at Web Archive: https://web.archive.org/web/20120505012701/http://kotka.de/blog/2009/11/Taming_the_Bound_Seq.html).




回答5:


In the original question, two things are misunderstood:

  • How to use dynamic vars
  • How to use clojure.data.generators

First, dynamic vars should be managed via the binding macro. Second rand is not a function of clojure.data.generators, but of clojure.core itself and thus resetting the *rnd* var doesn't have effect. So here is how you should do it:

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



回答6:


From what I understand, none of the above are functional approaches. If you want that, you can use a lazy-seq:

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

To get the first random number you use:

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

To get the rest of the sequence you use:

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

To get the first element and a reference to the rest you can use:

(defn pop [x (create-random 0)]
     (n, rest) `(~(first x) ~(rest x)))


来源:https://stackoverflow.com/questions/24007063/how-to-generate-repeatable-random-sequences-with-rand-int

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!