From: "Ara.T.Howard" Date: 2005-07-20T09:53:50+09:00 Subject: Re: Regarding rand --8323328-752396638-1121820827=:10750 Content-Type: MULTIPART/MIXED; BOUNDARY="8323328-752396638-1121820827=:10750" This message is in MIME format. The first part should be readable text, while the remaining parts are likely unreadable without MIME-aware tools. --8323328-752396638-1121820827=:10750 Content-Type: TEXT/PLAIN; charset=X-UNKNOWN; format=flowed Content-Transfer-Encoding: QUOTED-PRINTABLE On Wed, 20 Jul 2005, Daniel Brockman wrote: > Hi Ben, > >> Daniel, I started out with something similar to that, >> although yours is alot better, but then thought it would >> be nice to have something like random 'a'..'z' aswell > > That could be useful, at least for single-character strings. > > Luckily, there was a just quiz about random choosing, so we > can steal the winning algorithm from there. :-) > > Here's a more extensive implementation: > > def random(object) > if object.kind_of? Numeric > if object.integer? > rand(object) > else > rand * object > end > else > object.random > end > end > > # Inefficient fallback implementation. > module Enumerable > def random(n=3Dnil) to_a.random(n) end > end > > class Range > def bounds ; [self.begin, self.end] end > def lower_bound ; bounds.sort.first end > def upper_bound ; bounds.sort.last end > def numeric? ; bounds.all? { |x| x.kind_of? Numeric } end > def integral? ; numeric? and bounds.all? { |x| x.integer? } end > def include_end? ; not exclude_end? end > > def size > d =3D upper_bound - lower_bound > if include_end? and d.respond_to? :succ > then d.succ else d end > end > > def random(n=3Dnil) > if integral? > if n.nil? > rand(size) + lower_bound > else > n.integer? or raise ArgumentError, > "can't choose a non-integral (#{n}) number of elements" > n <=3D size or raise ArgumentError, > "can't choose #{n} out of #{size} elements" > n >=3D 0 or raise ArgumentError, > "can't choose a negative (#{n}) number of elements" > hash =3D {} > hash[random] =3D true while hash.size < n > hash.keys.sort_by { rand } > end > elsif numeric? > if n.nil? > rand * size + lower_bound > else > values =3D [] > n.times { values << random } > values > end > else > super > end > end > end > > class Array > def random(n=3Dnil) > if n.nil? > self[rand(size)] > else > values_at(*(0...size).random(n)).sort_by { rand } > end > end > end > > There's some overkill randomization going on in Array#random > where we first grab n random numbers in random order, and > then select the values at those indices and randomize > *their* order. It would be sufficient to grab n random > numbers in whatever order (not necessarily random), select > the values at those indices, and then randomize the result. > > If you populate a hash with random float keys, I'm not sure > in what order the keys come out when you call Hash#keys. > Could the order be random? I assume not, but if that's so, > we could get rid of the =E2=80=98sort_by { rand }=E2=80=99 in Range#rando= m. > > Actually, I'm not even sure whether Array#random needs to > return the elements in random order. Maybe it could return > them in whatever order is most convenient, and leave it to > the user to randomize or sort the result according to need. > > Oh, and we could add this convenience method: > > module Enumerable > def shuffle > sort_by { rand } > end > end > > So you'd do one of these: > > * foo.random(4) # Order doesn't matter. > > * foo.random(4).sort # Increasing order. > > * foo.random(4).shuffle # Random order. > > Okay, I'm convinced =E2=80=94 let's change Array#random accordingly: > > class Array > def random(n=3Dnil) > if n.nil? > self[rand(size)] > else > values_at(*(0...size).random(n)) > end > end > end > > I think my implementation of Hash#random is pretty much as > good as it gets, but if anyone can think of a better way, > please share it. I don't particularly like the way I'm > first building an array, then flattening it, splatting it, > and finally turning it back into a hash. :-) > > Here it is, anyway: > > class Hash > def pair_at(key) [key, self[key]] end > def pairs_at(*keys) keys.collect { |k| pair_at k } end > def random(n=3Dnil) > if n.nil? > pair_at(keys.random) > else > Hash[*pairs_at(*keys.random(n)).flatten] > end > end > end > > I think returning a subhash (except when n is nil) seems the > most useful, since if you just want some random values or > keys from the hash, that's easy: > > hash.values.random(123) hash.keys.random(123) > > It's easy to adapt other collections to this API: > > class Set > def random(n=3Dnil) Set[*super] end > end > >> Which i'll probably not need anyway so i'll 'borrow' yours >> if I may. > > Of course, help yourself. You may want to put all this in a > separate file =E2=80=94 say, =E2=80=98random.rb=E2=80=99. :-) > > The above code will let you do all these: > > random(42) #=3D> 13 random(2.0) #=3D> 1.9656... > > random(1..9) #=3D> 5 random(2.3..5.8) #=3D> 3.1246... > > random("a".."z") #=3D> "f" random("a".."zz") #=3D> "ge" > > [1, 1, 2, 3, 5, 8].random #=3D> 2 > > [1, 1, 2, 3, 5, 8].random(3) #=3D> [1, 1, 5] > > {:a=3D>1, :b=3D>2, :c=3D>3}.random #=3D> [:b, 2] > > {:a=3D>1, :b=3D>2, :c=3D>3}.random(2) #=3D> {:a=3D>1, :c=3D>3} > > Set[1, 2, 3, "x", "y", "z"].random(3) #=3D> Set[2, 3, "y"] why not bundle this up and post on the raa? i'd like to require 'random' and have some nice things added. cheers. -a --=20 =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D | email :: ara [dot] t [dot] howard [at] noaa [dot] gov | phone :: 303.497.6469 | My religion is very simple. My religion is kindness. | --Tenzin Gyatso =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D --8323328-752396638-1121820827=:10750-- --8323328-752396638-1121820827=:10750--