HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyMinor

Most efficient way to iterate through arrays/can I use .times method in place of while method?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
canarrayswhilemethodplaceefficientwayiteratethroughtimes

Problem

I created a quick program to test the precision of randomness generated by the .rand method. The primary question is whether I can use the .times method in place of the while code blocks to increase efficiency and:

  • Whether such a practice would reduce the amount of processing required/if this would be at all significant.



  • Whether it's a more common approach, or alternatively is it an infeasible/awkward approach.



-
Even if it is a less acceptable approach than the one I've taken, how would I use the .times method to execute the same task. If it is inappropriate when should I use .times?

# 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

# 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
count_one = []
count_zero = []

# Resets iterator to 0
i = 0

# Stores instances of zero in `count_zero` and instances of one in `count_one`
while i < x.length
  if x[i] == 1
    count_zero << x[i]
    i += 1
  else
    count_one << x[i]
    i += 1
  end
end

# Calculates final averages
zero_average = count_zero.length.to_f/x.length.to_f * 100.0    
one_average = count_one.length.to_f/x.length.to_f * 100.0


Additionally, I am curious as to:

  • How I could possibly have coded this for flexibility to better anticipate future needs. Example: If I later needed to perform the same operations on a larger range of numbers.



  • If I am using extraneous/obvious facts in my commenting or if my commenting is otherwise not in good practice.



  • What more insight I might be able to gain in general regarding my current coding practices. Thank you.

Solution

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)

Code Snippets

# 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
x = []
4000.times do
  x << rand(2)
end
Array.new(4000){ rand(2) }

#=> [1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0...]
# 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
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, ...]]

Context

StackExchange Code Review Q#21080, answer score: 7

Revisions (0)

No revisions yet.