Matthew Moss wrote: > -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- > > The three rules of Ruby Quiz 2: > > 1. Please do not post any solutions or spoiler discussion for this > quiz until 48 hours have passed from the time on this message. > > 2. Support Ruby Quiz 2 by submitting ideas as often as you can! (A > permanent, new website is in the works for Ruby Quiz 2. Until then, > please visit the temporary website at > > <http://splatbang.com/rubyquiz/>. > > 3. Enjoy! > > Suggestion: A [QUIZ] in the subject of emails about the problem > helps everyone on Ruby Talk follow the discussion. Please reply to > the original quiz message, if you can. > > -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- > > ## Not So Random (#173) > > As part of Ruby's standard library, we have access to a good > pseudorandom number generator (PRNG), the [Mersenne twister][1]. (In > particular, the MT19937 variant.) Ruby provides two kernel functions > to make use of this generator: `srand` and `rand`. This week's quiz > involves generating random numbers in a predictable manner. > > The first part is to create a wrapper around the random number > generator `rand`, supporting the appropriate parameter (i.e. the > parameter intrinsic to `rand`). Your wrapper should implement two > additional functions: `next` which returns the next random number, and > `reset` which restarts the sequence. So, for example: > > irb> x = Random.new(100) > => #<Random:...> > > irb> Array.new(5) { x.next } > => [51, 92, 14, 71, 60] > > irb> Array.new(5) { x.next } > => [20, 82, 86, 74, 74] > > irb> x.reset > => nil > > irb> Array.new(5) { x.next } > => [51, 92, 14, 71, 60] # after reset, sequence restarts > > You may do this as a class, as depicted here, or as a function, > lambda, code block... whatever you like. > > The second part is a little trickier: creating multiple, concurrent, > _reproducible_ sequences of pseudorandom numbers isn't so easy. As > convenient as the built-in generator is, there is only one seed. For > example, assume we have two `Random` objects, created as shown above, > `x` and `y`. > > irb> x = Random.new(100) > => #<Random:...> > > irb> Array.new(6) { x.next } > => [51, 92, 14, 71, 60, 20] > > irb> y = Random.new(100) > => #<random:...> > > irb> Array.new(6) { y.next } > => [66, 92, 98, 17, 83, 57] > > irb> x.reset > => nil > > irb> Array.new(2) { x.next } > => [51, 92] # ok: sequence restarted as requested > > irb> Array.new(2) { y.next } > => [14, 71] # fail: this is part of _x_ sequence > > irb> Array.new(2) { x.next } > => [60, 20] # more fail: _x_ is now [51, 92, 60, 20]? wrong... > > > The reason for the failure should be obvious: my current > implementation of `Random` just blindly uses `srand` and `rand` > without considering that there may be multiple instances of `Random`. > > So, for this second part, expand your wrapper to support concurrent > use. Please note that you are required to make use of the built-in > generator: you should not implement your own PRNG. > > One final note... It is up to you whether the seed for each wrapper > will be user-settable or hidden (as in my examples above). However, if > hidden, each wrapper should have a different seed. (Generated > randomly, perhaps?) > > > > [1]: http://en.wikipedia.org/wiki/Mersenne_twister > > > Without knowing how the PRNG is implemented (even if in C or Ruby), this solution solves the problem. It'll be slow if you're switching between generators deep in the sequence, but it works. It also accounts for Kernel#rand and Kernel#srand screwing up your Random objects. It passes the tests posted by brabuhr / gmail.com. module Kernel alias_method :old_rand, :rand alias_method :old_srand, :srand def rand(*a) Random.dirty old_rand(*a) end def srand(*a) Random.dirty old_srand(*a) end end class Random @@seed = nil def self.dirty @@seed = nil end def initialize(range, seed = Time.now.to_i) @range = range @seed = seed reset end def reset @sequence = 0 reinitialize end def reinitialize old_srand(@seed) @@seed = @seed @sequence.times { old_rand(@range) } end def next reinitialize if @@seed.nil? or @@seed != @seed @sequence += 1 old_rand(@range) end end -- Michael Morin Guide to Ruby http://ruby.about.com/ Become an About.com Guide: beaguide.about.com About.com is part of the New York Times Company