11
votes

How can I create a lazy sequence of random numbers?

My current code:

(import '(java.util Random))

(def r (new Random))
(defn rnd [_] 
    (.nextInt r 10))

(defn random-numbers [max] 
    (iterate #(.nextInt r max) (.nextInt r max)))

(println (take 5 (random-numbers 10)))

executing it throws an exception:

(Exception in thread "main" clojure.lang.ArityException: Wrong number of args (1) passed to: user$random-numbers$fn at clojure.lang.AFn.throwArity(AFn.java:437) at clojure.lang.AFn.invoke(AFn.java:39) at clojure.core$iterate$fn__3870.invoke(core.clj:2596) at clojure.lang.LazySeq.sval(LazySeq.java:42) at clojure.lang.LazySeq.seq(LazySeq.java:60) at clojure.lang.RT.seq(RT.java:466) at clojure.core$seq.invoke(core.clj:133) at clojure.core$take$fn__3836.invoke(core.clj:2499) at clojure.lang.LazySeq.sval(LazySeq.java:42) at clojure.lang.LazySeq.seq(LazySeq.java:60) at clojure.lang.Cons.next(Cons.java:39) at clojure.lang.RT.next(RT.java:580) at clojure.core$next.invoke(core.clj:64) at clojure.core$nthnext.invoke(core.clj:2752) at clojure.core$print_sequential.invoke(core_print.clj:57) at clojure.core$fn__4990.invoke(core_print.clj:140) at clojure.lang.MultiFn.invoke(MultiFn.java:167) at clojure.core$pr_on.invoke(core.clj:3264) at clojure.core$pr.invoke(core.clj:3276) at clojure.lang.AFn.applyToHelper(AFn.java:161) at clojure.lang.RestFn.applyTo(RestFn.java:132) at clojure.core$apply.invoke(core.clj:600) at clojure.core$prn.doInvoke(core.clj:3309) at clojure.lang.RestFn.applyTo(RestFn.java:137) at clojure.core$apply.invoke(core.clj:600) at clojure.core$println.doInvoke(core.clj:3329) at clojure.lang.RestFn.invoke(RestFn.java:408) at user$eval7.invoke(testing.clj:12) at clojure.lang.Compiler.eval(Compiler.java:6465) at clojure.lang.Compiler.load(Compiler.java:6902) at clojure.lang.Compiler.loadFile(Compiler.java:6863) at clojure.main$load_script.invoke(main.clj:282) at clojure.main$script_opt.invoke(main.clj:342) at clojure.main$main.doInvoke(main.clj:426) at clojure.lang.RestFn.invoke(RestFn.java:408) at clojure.lang.Var.invoke(Var.java:401) at clojure.lang.AFn.applyToHelper(AFn.java:161) at clojure.lang.Var.applyTo(Var.java:518) at clojure.main.main(main.java:37) [Finished in 3.8s with exit code 1]

Is this a completey wrong approach, because I am using state, namely r is an instance of java.util.Random, or is it just a nooby syntax error?

I just studing clojure on myself, so please bear with me :) .

4
Thanks for including the whole stacktrace. Here it's not really necessary, but it can help immensely and often people don't bother.amalloy

4 Answers

31
votes

repeatedly is great for repeatedly running a function and gathering the results in a seq

user> (take 10 (repeatedly #(rand-int 42)))
(14 0 38 14 37 6 37 32 38 22)

as for your original approach: iterate takes an argument, feeds it to a function and then takes the result of that and passes it back to the same function. I't not quite what you want here because the function you are using doesn't need any arguments. You can of course give it a placeholder for that argument and get it working, though repeatedly is likely a better fit.

(defn random-numbers [max]
  (iterate (fn [ignored-arg] (.nextInt r max)) (.nextInt r max)))
#'user/random-numbers

user> (println (take 5 (random-numbers 10)))
(3 0 0 2 0)
6
votes

As a general guide, do not start with a class/function from Java. Look first at Clojure's core functions and namespaces in clojure.* (and then at the contributed namespaces which are now in modular repos: see http://dev.clojure.org/display/doc/Clojure+Contrib); rand-int itself is readily available in clojure-core. So, how would one start the search for a random number helper?

With Clojure 1.3 onwards, you can "use" the clojure-repl namespace to have access to the handy apropos function (used in the same spirit as the apropos command in Unix/linux); apropos returns all matching defs in namespaces loaded so far.

user> (use 'clojure.repl)
nil
user> (apropos "rand")
(rand rand-int rand-nth)

The find-doc function in clojure.repl is also another alternative.

The other pointer is to search at www.clojuredocs.org which includes usage-examples for the funcs in clojure core and clojure.*.

0
votes

For testing purposes at least, it's good to be able to repeat a "random" sequence by seeding the generator. The new spec library reports the results of its tests this way.

The native Clojure functions don't let you seed a random sequence, so we have to resort to the underlying Java functions:

(defn random-int-seq
  "Generates a reproducible sequence of 'random' integers (actually longs)
  from an integer (long) seed. Supplies its own random seed if need be."
  ([] (random-int-seq (rand-int Integer/MAX_VALUE)))
  ([seed]
   (let [gen (java.util.Random. seed)]
     (repeatedly #(.nextLong gen))))) 
0
votes

Lets use a transducer shall we?

 (def xf (map (fn [x] (* 10 (rand)))))

We can also use rand-int as:

(def xf (map (fn [x] (* 10 (rand-int 10)))))

To use this to generate lazy sequence, we'll use sequence

(sequence xf (range))

This returns a lazy sequence of random numbers. To get a complete sequence of n numbers we can use take as:

(take n (sequence xf (range)))