How to randomly sort (scramble) an array in Ruby?
Built in now:
[1,2,3,4].shuffle => [2, 1, 3, 4]
[1,2,3,4].shuffle => [1, 3, 2, 4]
Randomizing array elements
Use the shuffle
method ...
irb(main):001:0> [1,2,3,4,5].shuffle
=> [3, 4, 2, 5, 1]
How to shuffle an array the same way every time?
Array#shuffle
can take a seeded Random
instance.
a = [1,2,3,4]
seed = 1
a.shuffle(random: Random.new(seed))
# => [4, 1, 3, 2]
a.shuffle(random: Random.new(seed))
# => [4, 1, 3, 2]
Just replace seed = 1
with whatever random seed you want to use.
Ruby - return an array in random order
If you don't have [].shuffle, [].sort_by{rand} works as pointed out by sepp2k. .sort_by temporarily replaces each element by something for the purpose of sorting, in this case, a random number.
[].sort{rand-0.5} however, won't properly shuffle. Some languages (e.g. some Javascript implementations) don't properly shuffle arrays if you do a random sort on the array, with sometimes rather public consequences.
JS Analysis (with graphs!): http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Ruby is no different! It has the same problem. :)
#sort a bunch of small arrays by rand-0.5
a=[]
100000.times{a << [0,1,2,3,4].sort{rand-0.5}}
#count how many times each number occurs in each position
b=[]
a.each do |x|
x.each_index do |i|
b[i] ||=[]
b[i][x[i]] ||= 0
b[i][x[i]] += 1
end
end
p b
=>
[[22336, 18872, 14814, 21645, 22333],
[17827, 25005, 20418, 18932, 17818],
[19665, 15726, 29575, 15522, 19512],
[18075, 18785, 20283, 24931, 17926],
[22097, 21612, 14910, 18970, 22411]]
Each element should occur in each position about 20000 times. [].sort_by(rand) gives much better results.
#sort with elements first mapped to random numbers
a=[]
100000.times{a << [0,1,2,3,4].sort_by{rand}}
#count how many times each number occurs in each position
...
=>
[[19913, 20074, 20148, 19974, 19891],
[19975, 19918, 20024, 20030, 20053],
[20028, 20061, 19914, 20088, 19909],
[20099, 19882, 19871, 19965, 20183],
[19985, 20065, 20043, 19943, 19964]]
Similarly for [].shuffle (which is probably fastest)
[[20011, 19881, 20222, 19961, 19925],
[19966, 20199, 20015, 19880, 19940],
[20062, 19894, 20065, 19965, 20014],
[19970, 20064, 19851, 20043, 20072],
[19991, 19962, 19847, 20151, 20049]]
Writing a shuffle method in Ruby
Your idea is to pick elements at random until you have picked all elements.
Other than verbosity, another issue that you have is that you use #delete
. Hence if there are two repeating elements in the array, you will get only one in the shuffled result.
Also you mutate the array passed, which is generally undesirable. Here is an implementation of the same algorithm:
def recursive_shuffle(to_shuffle, shuffled = [])
return shuffled if to_shuffle.empty?
to_shuffle = to_shuffle.dup if shuffled.empty?
element = to_shuffle.sample
to_shuffle.delete_at(to_shuffle.index(element))
recursive_shuffle(to_shuffle, shuffled + [element])
end
As for a simpler solution:
array.sort_by { rand }
Ruby, how to shuffle one array into another
(0..a1.length).to_a.sample(a2.length).sort
.zip(a2)
.reverse
.each{|i, e| a1.insert(i, e)}
Shuffling an array with a condition
Juksefantomet sent me this solution in discord, since he can't post here due to an account lock.
The below codeblock contains an alternative approach on how to tackle the problem at hand. This contains a fragmented solution to understand the steps on how you would normally approach a complex problem like the one presented above.
going through the various steps you can see how each condition has to be known in advance and specified to the point where your final array is not "illegal".
@illegal_order = ['1','2','3','4','5','6','7','8','9']
puts @illegal_order.count
puts "#{@illegal_order}"
@foo = []
# traverse the original array and append a random value from that into foo array
# this can of course and should be done in a loop where you check for duplicates
# this is done below in this example, fragmented to see the individual action
(0..@illegal_order.count).each do |add_value|
@temp = @illegal_order[rand(@illegal_order.count)]
unless @foo.count == @illegal_order.count
@foo.push(@temp)
end
end
# making sure it contains everything in the original array
@foo.each do |bar|
unless @illegal_order.include?(bar)
@approve = false
puts "Errored generation!"
end
end
# defining patterns, this can of course be extracted by the original array and be done alot cleaner
# but printing it out to better break it down
@pattern1 = [@illegal_order[0],@illegal_order[1],@illegal_order[2]]
@pattern2 = [@illegal_order[3],@illegal_order[4],@illegal_order[5]]
@pattern3 = [@illegal_order[6],@illegal_order[7],@illegal_order[8]]
# Let us step through the new array and use case to check for various conditions that would flag the new array as invalid
@foo.each do |step|
# setting a temp pattern based on current state
@temp_pattern = [@foo[step.to_i-1],@illegal_order[step.to_i],@illegal_order[step.to_i+1]]
case step
when @temp_pattern == @pattern1
@illegalpatterns = true
when @temp_pattern == @pattern2
@illegalpatterns = true
when @temp_pattern == @pattern3
@illegalpatterns = true
end
# checking the foo array if it contains duplicates, if yes, set conditional to true
@illegal_order.each do |count|
if @foo.count(count) > 1
@duplicates = true
end
end
end
# printing out feedback based on duplicates or not, this is where you rescramble the array if duplicate found
(@duplicates == true) ? (puts "dupes found") : (puts "looks clear. no duplicates")
# printing out feedback based on illegal patterns or not, this is where you rescramble the array if duplicate found
(@illegalpatterns == true) ? (puts "illegal patterns found") : (puts "looks clear, no illegal patterns")
puts "#{@foo}"
Related Topics
How to Validate Exits and Aborts in Rspec
Using a String as a Variable at Run Time
How to Cleanly Initialize Attributes in Ruby with New
Typeerror: Superclass Mismatch for Class Word in Ruby
How to Enable C Extension Support in Jruby
How to Alter the Timezone of a Datetime in Ruby
How to Fix Rubygems Recent Deprecation Warning
Flattening Nested Hash to a Single Hash with Ruby/Rails
How to Install the Ruby Ri Documentation
How to Install Jekyll on Osx 10.11
Escaping Single and Double Quotes in a String in Ruby
Why Did Matz Choose to Make Strings Mutable by Default in Ruby
How to Make Part of a Regular Expression Optional in Ruby
Split String into a List, But Keeping the Split Pattern
Why Does My Ruby 'Ri' Tool Not Return Results in Command Prompt