require 'facets/hash/zip' require 'facets/string/shatter' require 'facets/kernel/maybe' # = Randomization Extensions # # This library extends Array, String, Hash and other classes with randomization # methods. Most of the methods are of one of two kinds. Either they "pick" a # random element from the reciever or they randomly "shuffle" the reciever. # # The most common example is Array#shuffle, which simply randmomizes the # order of an array's elements. # # [1,2,3].shuffle #~> [2,3,1] # # The other methods do similar things for their respective classes. # # The classes are all extended via mixins which have been created within # Ruby's Random class. # # Credit for this work is due to: # # * Ilmari Heikkinen # * Christian Neukirchen # * Thomas Sawyer # # NOTE: Inline QED tests are almost pointless here b/c... yea, it's random. # So indtead of the usual `#=>` we use `#~>` which means approx. equal and # prevens QED from making a hard assertion. class Random class << self # Alias for Kernel#rand. alias_method :number, :rand public :number end ##def self.number(*args) ## ::Kernel.rand(*args) ##end # Module method to generate a random letter. # # Random.letter #~> "q" # Random.letter #~> "r" # Random.letter #~> "a" # def self.letter (Random.number(26) + (Random.number(2) == 0 ? 65 : 97) ).chr end # Random extensions for Range class. # module RangeExtensions # Return a random element from the range. # # (1..4).at_rand #~> 2 # (1..4).at_rand #~> 4 # # (1.5..2.5).at_rand #~> 2.06309842754533 # (1.5..2.5).at_rand #~> 1.74976944931541 # # ('a'..'z').at_rand #~> 'q' # ('a'..'z').at_rand #~> 'f' # # CREDIT: Lavir the Whiolet, Thomas Sawyer def at_rand first, last = first(), last() if first.respond_to?(:random_delta) # TODO: work on this!!! begin first.random_delta(last, exclude_end?) rescue to_a.at_rand end else to_a.at_rand end ##elsif first.respond_to?(:succ) ## # optimized algorithm ## if (Fixnum === first || Bignum === first) && ## (Fixnum === last || Bignum === last) ## last -= 1 if exclude_end? ## return nil if last < first ## return Random.number(last - first + 1) + first ## end ## # standard algorithm ## return to_a.at_rand ##elsif Numeric === first && Numeric === last ## return nil if last < first ## return nil if exclude_end? && last == first ## return (last - first) * Random.number + first ##else ## return nil ##end end end # Random extensions fo Integer class. # module IntegerExtensions # def random_delta(last, exclude_end) first = self last -= 1 if exclude_end return nil if last < first return Random.number(last - first + 1) + first end end # Random extensions for Numeric class. # module NumericExtensions # def random_delta(last, exclude_end) first = self return nil if last < first return nil if exclude_end && last == first return (last - first) * Random.number + first end end # Random extensions for Array class. # module ArrayExtensions # Return a random element from the array. # # [1, 2, 3, 4].at_rand #~> 2 # [1, 2, 3, 4].at_rand #~> 4 # def at_rand at(Random.number(size)) end # Same as #at_rand, but acts in place removing a # random element from the array. # # a = [1,2,3,4] # a.at_rand! #~> 2 # a #~> [1,3,4] # def at_rand! return delete_at( Random.number( size ) ) end # Similar to #at_rand, but will return an array of randomly # picked exclusive elements if given a number. def pick(n=nil) if n a = self.dup a.pick!(n) else at(Random.number(size)) end end # Similar to #at_rand!, but given a number will return # an array of exclusive elements. def pick!(n=nil) if n if n > self.size r = self.dup self.replace([]) r else r = [] n.times { r << delete_at(Random.number(size)) } r end else delete_at(Random.number(size)) end end # Random index. # def rand_index Random.number(size) end # Returns a random subset of an Array. If a _number_ # of elements is specified then returns that number of # elements, otherwise returns a random number of elements # upto the size of the Array. # # By defualt the returned values are exclusive of # each other, but if _exclusive_ is set to false, # the same values can be choosen more than once. # # When _exclusive_ is true (the default) and the # _number_ given is greater than the size of the array, # then all values are returned. # # [1, 2, 3, 4].rand_subset(1) #~> [2] # [1, 2, 3, 4].rand_subset(4) #~> [2, 1, 3, 4] # [1, 2, 3, 4].rand_subset #~> [1, 3, 4] # [1, 2, 3, 4].rand_subset #~> [2, 3] # def rand_subset(number=nil, exclusive=true) number = Random.number(size) unless number number = number.to_int #return self.dup if (number >= size and exlusive) return sort_by{rand}.slice(0,number) if exclusive ri =[]; number.times { |n| ri << Random.number(size) } return values_at(*ri) end # Generates random subarrays. Uses random numbers and bit- # fiddling to assure performant uniform distributions even # for large arrays. # # a = *1..5 # a.rand_subarrays(2) #=> [[3, 4, 5], []] # a.rand_subarrays(3) #=> [[1], [1, 4, 5], [2, 3]] # # CREDIT: Michael Kohl def rand_subarrays(n=1) raise ArgumentError, "negative argument" if n < 0 (1..n).map do r = rand(2**self.size) self.select.with_index { |_, i| r[i] == 1 } end end # Randomize the order of an array. # # [1,2,3,4].shuffle #~> [2,4,1,3] # def shuffle dup.shuffle! #sort_by{Random.number} end # As with #shuffle but modifies the array in place. # The algorithm used here is known as a Fisher-Yates shuffle. # # a = [1,2,3,4] # a.shuffle! # # a #~> [2,4,1,3] # # CREDIT Niel Spring def shuffle! s = size each_index do |j| i = Random.number(s-j) #self[j], self[j+i] = self[j+i], self[j] tmp = self[j] self[j] = self[j+i] self[j+i] = tmp end self end end # Random extensions for Hash class. # module HashExtensions # Returns a random key. # # {:one => 1, :two => 2, :three => 3}.pick_key #~> :three # def rand_key keys.at(Random.number(keys.size)) end # Delete a random key-value pair, returning the key. # # a = {:one => 1, :two => 2, :three => 3} # a.rand_key! #~> :two # a #~> {:one => 1, :three => 3} # def rand_key! k,v = rand_pair delete(k) return k end alias_method( :pick_key, :rand_key! ) # Returns a random key-value pair. # # {:one => 1, :two => 2, :three => 3}.pick #~> [:one, 1] # def rand_pair k = rand_key return k, fetch(k) end # Deletes a random key-value pair and returns that pair. # # a = {:one => 1, :two => 2, :three => 3} # a.rand_pair! #~> [:two, 2] # a #~> {:one => 1, :three => 3} # def rand_pair! k,v = rand_pair delete( k ) return k,v end alias_method( :pick_pair, :rand_pair! ) # Returns a random hash value. # # {:one => 1, :two => 2, :three => 3}.rand_value #~> 2 # {:one => 1, :two => 2, :three => 3}.rand_value #~> 1 # def rand_value fetch(rand_key) end # Deletes a random key-value pair and returns the value. # # a = {:one => 1, :two => 2, :three => 3} # a.at_rand! #~> 2 # a #~> {:one => 1, :three => 3} # def rand_value! k,v = rand_pair delete( k ) return v end alias_method( :pick, :rand_value! ) alias_method( :at_rand, :rand_value ) alias_method( :at_rand!, :rand_value! ) # Returns a copy of the hash with _values_ arranged # in new random order. # # h = {:a=>1, :b=>2, :c=>3} # h.shuffle #~> {:b=>2, :c=>1, :a>3} # def shuffle ::Hash.zip( keys.sort_by{Random.number}, values.sort_by{Random.number} ) end # Destructive shuffle_hash. Arrange the values in # a new random order. # # h = {:a => 1, :b => 2, :c => 3} # h.shuffle! # h #~> {:b=>2, :c=>1, :a=>3} # def shuffle! self.replace(shuffle) end end # Random extensions for String class. # module StringExtensions # def self.included(base) base.extend(Self) end # Class-level methods. module Self # Returns a randomly generated string. One possible use is # password initialization. Takes a max legnth of characters # (default 8) and an optional valid char Regexp (default /\w\d/). # # String.random #~> 'dd4qed4r' # # CREDIT George Moschovitis #-- # TODO: This is not very efficient. Better way? #++ def random(max_length = 8, char_re = /[\w\d]/) raise ArgumentError.new('second argument must be a regular expression') unless char_re.is_a?(Regexp) string = "" while string.length < max_length ch = Random.number(255).chr string << ch if ch =~ char_re end return string end # Generate a random binary string of +n_bytes+ size. # # CREDIT: Guido De Rosa def random_binary(n_bytes) ( Array.new(n_bytes){ rand(0x100) } ).pack('c*') end end # Return a random separation of the string. # Default separation is by charaacter. # # "Ruby rules".at_rand(' ') #~> ["Ruby"] # def at_rand( separator=// ) #separator = self.class.patterns( separator ) self.split(separator,-1).at_rand end # Return a random separation while removing it # from the string. Default separation is by character. # # s = "Ruby rules" # s.at_rand!(' ') #~> "Ruby" # s #~> "rules" # def at_rand!( separator=// ) #separator = self.class.patterns( separator ) a = self.shatter( separator ) w = []; a.each_with_index { |s,i| i % 2 == 0 ? w << s : w.last << s } i = Random.number(w.size) r = w.delete_at( i ) self.replace( w.join('') ) return r end # Return a random byte of _self_. # # "Ruby rules".rand_byte #~> 121 # def rand_byte self[Random.number(size)] end # Destructive rand_byte. Delete a random byte of _self_ and return it. # # s = "Ruby rules" # s.rand_byte! #~> 121 # s #~> "Rub rules" # def rand_byte! i = Random.number(size) rv = self[i,1] self[i,1] = '' rv end # Return a random string index. # # "Ruby rules".rand_index #~> 3 # def rand_index Random.number(size) end # Return the string with seperated sections arranged # in a random order. The default seperation is by character. # # "Ruby rules".shuffle #~> "e lybRsuur" # def shuffle(separator=//) split(separator).shuffle.join('') end # In place version of shuffle. # def shuffle!(separator=//) self.replace( shuffle(separator) ) end end end class Range ; include Random::RangeExtensions ; end class Array ; include Random::ArrayExtensions ; end class Hash ; include Random::HashExtensions ; end class String ; include Random::StringExtensions ; end class Integer ; include Random::IntegerExtensions ; end class Numeric ; include Random::NumericExtensions ; end # Copyright (c) 2005 Ilmari Heikkinen, Christian Neukirchen, Thomas Sawyer