Take Full Advantage of Ruby's Array API and Blocks

As someone who has programmed in Pascal, C, C++, Java, JavaScript, and Scheme, I'm proud to say that Ruby is by far the most enjoyable language that I've used for everyday use. (Although if I'm honest, Scheme programming was probably the most fun.)

Ruby's syntax is concise, expressive, and avoids much of the "noise" and ceremony that exists in many programming languages. I mean seriously, how many programming languages allow you to write something as clear and elegant as this with an if-statement:

puts "I love programming." if programmer.happy?

or this with an unless-statement:

puts "I love programming." unless programmer.sad?

But I digress . . .

Arrays and Code Blocks

Beyond Ruby's syntax, it comes with an incredibly rich core library that takes full advantage of the language's facilities for passing around blocks of code that can be executed.

Take for example the following code snippet that iterates over an array and prints each value:

[1, 2, 3, 4, 5].each do |number|
  puts number
end

each is a method defined by Ruby's Array class. It accepts a code block as a parameter and executes the block for each value in the array. The block syntax uses the do/end keywords, but blocks can also be defined using curly braces:

[1, 2, 3, 4, 5].each { |number| puts number }

Dive a Little Deeper into the Array API

Developers coming from programming languages that don't support passing blocks of code around often become too comfortable with Array's each method once they discover it — to the point where they don't explore other methods provided by the Array class or the Enumerable module which is included by Array.

Let's have a look at several examples of methods available to arrays that should be used as an alternative to relying solely on the each method.

Multiply All Values by Two

The following snippet multiplies each value in an array by two and collects the new values in the numbers_times_two array:

numbers_times_two = []

[1, 2, 3, 4, 5].each do |number|
  numbers_times_two << number * 2
end

numbers_times_two is set to [2, 4, 6, 8, 10].

This certainly works, but this type of use of each it's hardly more than a gloried loop statement. It doesn't take full advantage of the features offered by the Array class.

Here's a more idiomatic solution:

numbers_times_two = [1, 2, 3, 4, 5].map do |number|
  number * 2
end

Similar to each, the map method accepts a code block that is executed for each value in the array. map returns an new array containing all the values returned by the code block; in this case, each of the numbers multiplied by two.

Calculate Sum of All Values

What about calculating the sum of all of the values in an array? As opposed to resorting to some like this:

total = 0

[1, 2, 3, 4, 5].each do |number|
  total += number
end

we can used the reduce method:

total = [1, 2, 3, 4, 5].reduce(0) do |sum, number|
  sum + number
end

0 is passed as an argument to reduce. Therefore, the value of sum for the first iteration of the array is 0. The result of sum + number (1) is passed as the value of sum for the next iteration. And again, the value of sum + number (3) is passed as the value of sum for the following iteration. This goes on until all values in the array are processed, and total is set to 15.

The use of reduce in this example is equivalent to the following:

sum = 0
sum = sum + 1  # sum is now set to 1
sum = sum + 2  # sum is now set to 3
sum = sum + 3  # sum is now set to 6
sum = sum + 4  # sum is now set to 10
sum = sum + 5  # sum is now set to 15

total = sum

If we don't pass 0 as an argument to reduce, it will set the initial value of sum to 0 by default:

total = [1, 2, 3, 4, 5].reduce do |sum, number|
  sum + number
end

But wait a minute. According to the the Ruby API documentation, we can do even better:

total = [1, 2, 3, 4, 5].reduce(0, :+)

reduce applies the + (the plus sign) method to the first argument, which is 0 in this case. An array value is passed as an argument to +, during each iteration, keeping track of the accumulated total.

It's essentially the equivalent of this:

sum = 0
sum = sum.+(1)  # sum is now set to 1
sum = sum.+(2)  # sum is now set to 3
sum = sum.+(3)  # sum is now set to 6
sum = sum.+(4)  # sum is now set to 10
sum = sum.+(5)  # sum is now set to 15

total = sum

(If you're confused by seeing 1.+(2), see the bonus tip below.)

And as we learned earlier, when 0 is passed as an argument to reduce, we can remove it:

total = [1, 2, 3, 4, 5].reduce(:+)

Find Values that Meet Specific Criteria

How can we find the even numbers in an array? As opposed to relying on each:

even_numbers = []

[1, 2, 3, 4, 5].each do |number|
  if number.even?
    even_numbers << number
  end
end

we can use find_all:

even_numbers = [1, 2, 3, 4, 5].find_all do |number|
  number.even?
end

find_all returns an array of values where the code block returns true. even_numbers is set to [2, 4].

(In case you didn't know, yes, numbers in Ruby have a method call even?. Awesome, right?!)

This solution can be further shortened to:

even_numbers = [1, 2, 3, 4, 5].find_all(&:even?)

Collecting Unique Values

Let's say we'd like a list of unique values in an array. Ruby's Array class has us covered with it's uniq method:

unique_numbers = [1, 1, 2, 2, 3, 3].uniq

uniq returns a new array without the duplicate values. unique_numbers is set to [1, 2, 3].

uniq works well with values such as numbers and strings, but what a about more complex data types? Let's consider a custom Person class (in an admittedly contrived example):

class Person
  attr_reader :ssn, :first_name, :last_name

  def initialize(ssn, first_name, last_name)
    @ssn = ssn
    @first_name = first_name
    @last_name = last_name
  end
end

person1 = Person.new("111-11-1111", "Bruce", "Wayne")
person2 = Person.new("222-22-2222", "Clark", "Kent")
person3 = Person.new("222-22-2222", "Clark", "Kent")

unique_people = [person1, person2, person3].uniq

In this example, uniq doesn't eliminate any of the person objects even though person2 and person3 hold the same data. This is because uniq uses object equality as the means to determine if any of the objects in the array are the same. person2 and person3 have the same data, but they reference different objects in memory.

Let's define uniqueness by a person's social security number (SSN) — the person object's ssn property. In other words, if two person objects have the same SSN, they are not unique.

uniq accepts an optional code block that it can use to calculate uniqueness. In this example we use the ssn property to evaluate a person object's uniqueness:

unique_people = [person1, person2, person3].uniq do |person|
  person.ssn
end

As a result, unique_people only includes person1 and person2.

Bonus Tip

Many of the symbols that appear as special operators in most popular programming languages are actually defined as methods in Ruby. For instance, the plus sign (+) that is used to add numbers is a method defined by the Fixnum class.

The following:

1 + 2

is nothing more than syntactic sugar for:

1.+(2)

The same is true for the symbols used for subtraction (-), multiplication (*), and division (/). Even the comparison "operators" are defined as methods.

This:

1 > 2

is syntactic sugar for:

1.>(2)

Additional Reading

Don't be afraid to explore the APIs provided by programming languages. See what the language and its libraries offer you before you reinvent the wheel. Read the source code of developers more experienced than you in a language and adopt their habits.

Take a look at the all the methods provided by Ruby's Array class and Enumerable module. Explore other core Ruby classes. I barely touched the tip of the iceberg of all the features they have to offer.