Books / Ruby for Beginners / Chapter 18

Arrays in Ruby

What is an Array?

Array is just a set of data. For example, array of tenants living in apartment building. Or array of numbers where each number has some meaning (for example, employee salary). Or array of objects: each object represents employee with multiple properties like salary, age, name, and so on.

In Ruby each element in array can be of different type. In other words, arrays look like a bucket where we can put apples, pears, some tape recordings, and a couple of aircraft ships. But usually arrays are homogeneous: all items (or “elements”) are of the same type.

But the question is why do we need arrays? Why would we need to put something into array? The question is quite simple: arrays are useful, because they represent a set of some data and we can perform bulk operations over this data. Let’s say we have a list of visited cities:

arr = ['San Francisco', 'Moscow', 'London', 'New York']

We initialized array with four elements inside of type String. Ruby knows it’s array because we used square brackets for array definition. We can perform variety of different useful operations over this data. For example, get the number of elements (visited cities):

$ irb
...
> arr.size
=> 4

Or just sort array in alphabetical order:

$ irb
...
> arr.sort
=> ["London", "Moscow", "New York", "San Francisco"]

Or we can iterate over array (walk over each element):

arr = ['San Francisco', 'Moscow', 'London', 'New York']
arr.each do |word|
  puts "Word #{word} has #{word.size} letters"
end

Output:

Word San Francisco has 13 letters
Word Moscow has 6 letters
Word London has 6 letters
Word New York has 8 letters

And nothing prevents developer from defining empty array:

arr = []

But why would we need an empty array? For the same purpose we need empty bucket – to put something inside on later steps. There are many ways to put something into array, but there are two standard ways:

  • arr.push(123) - method push also implemented in some other languages (like JavaScript), and some programmers prefer this way.
  • arr << 123 - double arrow shows to where we want to put something.

And trivial phonebook program could look like this:

arr = []

loop do
  print 'Type name and number (empty string to finish): '
  entry = gets.chomp
  break if entry.empty?
  arr << entry
end

puts 'Your phone book:'

arr.each do |element|
  puts element
end

Result:

Type name and number (empty string to finish): Saul 12345
Type name and number (empty string to finish): Mummy (555) 111-22-33
Type name and number (empty string to finish): Honey (555) 12345
Type name and number (empty string to finish): Honey 2 (555) 98765
Type name and number (empty string to finish):
Your phone book:
Saul 12345
Mummy (555) 111-22-33
Honey (555) 12345
Honey 2 (555) 98765

Of course, this phone book application is minimalistic and lacks lots of features, program doesn’t save any data to disk, no search by name or phone number, but it works! We were able to save data temporarily and show the list of contacts on the screen. We can invoke arr.sort to sort all elements in our phonebook, so result looks much better!

Exercise Run this program and think about adding arr.sort line. Where would you put it?

“Array.Each” Explained

Array is represented by Array type and this type implements method each. You are already familiar with this method when we were performing action over each element. Technically, method each accepts block. As we mentioned before, you can pass block to any method, and further behavior of a program depends on what’s “under the hood” of this method.

So that method each runs what’s inside the block for each element of array. In other words, it runs (usually tiny) sub-program for every item:

arr.each do |item|
  # here we have sub-program
  # it may have multiple lines
end

Or it can be done with one line:

arr.each { |item| ...here we have 1-line sub-program... }

Examples above show that blocks accept parameters (item in our case). This parameter comes from each method, and it’s just next element of array. Everything between do and end (or between { and }) is block. This way ruby implements iteration over array (or “array iteration”, “iteration over each element of array”, “array traversal”).

We can specify any name as parameter. In example from previous chapter (iteration over cities) we used word as parameter name. Phone book application has element as parameter name. And in this chapter we have item. The only rule here is parameter name should be understandable for you and your team (ideally, tools like Rubocop must be also okay with that).

Method each looks like calling your own method:

def my_method(word)
  puts "Word #{word} has #{word.size} letters"
end

Compare it with each:

arr.each do |word|
  puts "Word #{word} has #{word.size} letters"
end

It’s sad that in Ruby you can’t pass the name of a method to each:

arr.each my_method

It would be a nice language feature. Note: actually, you can write the similar code, but 1) not exactly this way 2) with some limitations 3) nobody doesn’t do it anyway.

Method each is also implemented in some other types. Iteration over something is operation which is pretty often used by programmers here and there, and you’ll see iteration in some not very obvious places. For example, iteration over string (object of String type):

$ irb
> 'hello'.each_char { |x| puts x }
h
e
l
l
o

 Method each for Range type:

(1001..1005).each do |x|
  puts "I'm robot #{x}"
end

Result:

I'm robot 1001
I'm robot 1002
I'm robot 1003
I'm robot 1004
I'm robot 1005

Initializing Array

In this chapter we’ll cover how to initialize simple, one-dimensional array. There could be arrays with multiple dimensions, but when it comes to arrays only, for beginner programmers two dimensions is usually enough. Make sure you practice initializing, accessing, and iterating over 1D and 2D arrays, because it’s a very common pitfall on interviews. Sometimes initializing arrays correctly is half the battle.

We are already familiar with the following syntax that creates empty array:

arr = []

If we want to initialize array with pre-defined values, program looks little bit different:

arr = ['one', 'two', 'three']

You can also get the size (length) of array with arr.size. Please note that methods in Ruby language often have the same names. String class has size method, and it returns the size of a string in characters. But size of Array class will return the number of elements in this array. String class has each_char method so one can iterate over the each character in particular string. But arrays have similar “each” method for iterating over each element. In other words, principle of a least surprise helps, and often you can guess the method for this or another class. We’ll be covering other types in this book soon (like Hash), try to guess what would size method return for these types.

Want it or not, there are multiple ways of initializing arrays in Ruby, and result is identical:

arr = []

Alternative syntax:

arr = Array.new

It can be considered as language design flaw, because newbies we’ll need to understand two ways of initializing array. But there are always many opinions.

The advantage of latter approach is ability to pass additional parameters, like:

  • Size of array (optional). Empty array will be initialized if not provided.
  • Block (optional)
$ irb
> arr = Array.new(10)
=> [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]

By default array initialized with nil value. Keep in mind that nil and “empty” are not the same thing. nil is represented by NilClass, and when we say “empty”, we usually refer to an empty string - string with no characters. nil and empty string are different objects and can’t be equal in Ruby.

$ irb
> nil.class
 => NilClass
> "".class
 => String
 > nil == ""
 => false

We can also initialize array with some value. Imagine we’re bulding a computer game, where 0 is empty space, and 1 is soldier. We want to create array of soldiers. It can be done the following way:

Array.new(10, 1)

Code above creates array with the size of 10 where each element equals to 1:

[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Accessing Array

So far we were iterating over array. But let’s look how we can access certain element of array. You can access array by using index. Indexes can be tricky, but at the same time they’re easy. It’s the number of element minus one. In other words, if you’re going to access firth element of array, you’ll need index with the value of 4. Let’s create array with five strings in REPL:

> arr = %w(one two three four five)
=> ["one", "two", "three", "four", "five"] 

And get the size of array:

> arr.size
=> 5

Size of array is 5. We have five elements inside. Let’s actually get the fifth element, we need index with the value of 4:

> arr[4]
=> "five"

In other words:

  • arr[0] returns one
  • arr[1] returns two
  • arr[2] returns three
  • arr[3] returns four
  • arr[4] returns five

And when we know how to evaluate this expression, we can combine it with other methods, like:

puts arr[4]

Or pass the result of expression to your own method as we could do it with variable:

my_own_method(arr[4])

We can also assign (replace) the value of particular cell in array. For example:

# replace "two" with "whatever"
arr[1] = 'whatever'

Here is the demo of how it works:

arr = %w(one two three four five)
arr[1] = 'whatever'
arr.each do |word|
  puts word
end

Result:

one
whatever
three
four
five

We could also write this program another way (it’s correct unless you have too many elements in your array so your program becomes too long to fit on a screen):

arr = %w(one two three four five)
arr[1] = 'whatever'
puts arr[0]
puts arr[1]
puts arr[2]
puts arr[3]
puts arr[4]

Missing Detail

Actually, we’re moving little bit fast. Let’s step back and look at this syntax used to define an array:

arr = %w(one two three four five)

What %w means here? There are a couple of shortcuts in Ruby to define arrays. Here is how we define exactly the same array without any shortcuts:

arr = ["one", "two", "three", "four", "five"]

Or with single quotes (preferred):

arr = ['one', 'two', 'three', 'four', 'five']

But this code looks little bit longer. It’s 45 characters, and version with %w has only 34 characters. Programmers just wanted to simplify their lives, so they introduced this shortcut syntax with %w prefix and parenthesis.

Arrays in all cases are exactly the same, so it’s up to you which syntax you prefer. However, some teams have their standards, and from our experience there is a preference for %w. In a good team you’ll get a warning from a tool like Rubocop if you’re not following naming conventions.

There are also objects similar to strings (explained later in this book) called symbols (represented by Symbol class). Just for the reference, you can also define array of symbols both ways.

Standard way:

arr = [:one, :two, :three]

With shortcut (note the %i shortcut this time):

arr = %i(one two three)

Exercise Open up your REPL and try to define array of string with standard and shortcut way. Do the same for array of symbols.


Licenses and Attributions


Speak Your Mind

-->