If you're interested in more idiomatic Ruby, my advice would be to thoroughly understand Array, Enumerable, and the functional style enabled by using blocks.
The functional style allows you to use the output of one method directly as input to another method without intermediate variables. This is known as method chaining, and it reduces the number of intermediate variables you need to create.
Let's start with your array creation:
# Initializes array and iterator.
x = []
i = 0
# Stores 4000 random numbers 0..1 in array 'x'
while i < 4000
x << rand(2)
i += 1
end
First, a bit about code comments. These are a bit gratuitous, as these repeat what the code says. Good comments should explain why something was done, not what is being done. If you feel that the code is non-obvious enough that you'll need a what comment, then it's time to think about refactoring so the code is a bit more self-explanatory.
I've been programming in Ruby for years and have never needed an iteration variable or a while loop. Why? Because the iteration methods such as Array's each
or Enumerable's each_with_index
are so powerful. So, as you mentioned, you could use times
and do this:
x = []
4000.times do
x << rand(2)
end
This is an improvement, but in this case we can do better. If you look at the constructor options for Array, you'll notice that you can specify the size and also pass a block for an initial value. Therefore, this can be written as:
Array.new(4000){ rand(2) }
#=> [1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0...]
The output is the same: a 4000-element array consisting of random zeros and ones.
Now let's look at what you want to do next, according to your comment:
# Initializes array for the purpose of storing zeros and ones in separate
# arrays for the sake of counting how many instances of each occur in the sample
So we want to separate the array into two buckets, based on their value. Thinking functionally, we should ask if, instead of iterating over intermediate output and creating a new data structure (which is also intermediate output because it will be thrown away once you count them), is there something we can do directly to the array? It turns out that the Enumerable module contains partition
which can take a block that does exactly that.
Array.new(4000){ rand(2) }.partition{ |digit| digit.zero? }
#=> [[0, 0, 0, 0, 0, 0, 0, 0, ...], [1, 1, 1, 1, 1, 1, 1, 1, ...]]
Partition will separate the array into two sub-arrays: one for which the block returns true; and the other for which the block returns false. Note that I also used the zero?
method which is available for numbers (see Fixnum#zero?) where I could have just said digit == 0
. Either one is fine, but using zero?
allows me to use a shortcut. This is equivalent:
Array.new(4000){ rand(2) }.partition(&:zero?)
#=> [[0, 0, 0, 0, 0, 0, 0, 0, ...], [1, 1, 1, 1, 1, 1, 1, 1, ...]]
This is known as the Symbol#to_proc
trick which I won't go into detail here, but it basically allows you to shorten a block in the form {|x| x.method}
to &:method
. Whenever you want to call the same method on every item in an array, it is useful. You'll see this quite often in Ruby code these days.
Now, you don't really want these sub-arrays, you just want to know how many zeros and ones there are. Again thinking functionally, for each element in the array, you'd like its size. Enumerable's map
is useful for transforming each element of an array.
Array.new(4000){ rand(2) }.partition(&:zero?).map{ |subarray| subarray.size }
#=> [2038, 1962]
Which, using the Symbol#to_proc
trick can be shortened to
Array.new(4000){ rand(2) }.partition(&:zero?).map(&:size)
#=> [1994, 2006]
So there you have it: using idiomatic Ruby and functional style, you can reduce the first 28 lines down to a single short, yet readable line. To answer question #1, any speed difference would be insignificant with arrays of this size. (Thought it would be interesting to benchmark the two approaches with huge arrays)