2.10. Arrays and Hashes

As in many programming languages, arrays and hashes are popular structures in Ruby for storing data.

Arrays

An array is a list of objects. Let's play around in irb:
$ irb --simple-prompt
>> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
>> a.class
=> Array
>> exit
$
That is simple and easy to understand.
Let's see if it also works with strings in the array:
$ irb --simple-prompt
>> a = ['Test', 'Banana', 'blue']
=> ["Test", "Banana", "blue"]
>> a.class
=> Array
>> a[1]
=> "Banana"
>> a[1].class
=> String
>> exit
$
That also works.
So all that's missing now is an array with a mixture of both. Obviously that will work, too, because the array stores objects and it does not matter which kind of objects they are (i.e. String, Fixnum, Float, …). But a little test can't hurt:
$ irb --simple-prompt
>> a = [1, 2.2, 'House', nil]
=> [1, 2.2, "House", nil]
>> a.class
=> Array
>> a[0]
=> 1
>> a[0].class
=> Fixnum
>> a[2]
=> "House"
>> a[2].class
=> String
>> exit
$
Next, let's have a look at what the ri help page says for Array:
$ ri -T Array
Array < Object

------------------------------------------------------------------------------
Includes:
Enumerable (from ruby site)

(from ruby site)
------------------------------------------------------------------------------
Arrays are ordered, integer-indexed collections of any object. Array indexing
starts at 0, as in C or Java.  A negative index is assumed to be relative to
the end of the array---that is, an index of -1 indicates the last element of
the array, -2 is the next to last element in the array, and so on.
------------------------------------------------------------------------------
Class methods:

  []
  new
  try_convert

Instance methods:

  &
  *
  +
  -
  <<

[...]

$ 
As you can see, arrays can also be created via the method new (like any class). Individual new elements can then be added at the end of an array via the method <<. Here is the corresponding example:
$ irb --simple-prompt
>> a = Array.new
=> []
>> a << 'first item'
=> ["first item"]
>> a << 'second item'
=> ["first item", "second item"]
>> exit
$

Iterator each

You can work your way through an array piece by piece via the method each. Example:
$ irb --simple-prompt
>> cart = ['eggs', 'butter']
=> ["eggs", "butter"]
>> cart.each do |item|
?>   puts item
>> end
eggs
butter
=> ["eggs", "butter"]
>> exit
$
Once more, ri provides help and an example in case you forget how to use each:
$ ri -T Array.each
Array.each

(from ruby site)
------------------------------------------------------------------------------
  ary.each {|item| block }   -> ary
  ary.each                   -> an_enumerator
   

------------------------------------------------------------------------------

Calls block once for each element in self, passing that element
as a parameter.

If no block is given, an enumerator is returned instead.

  a = [ "a", "b", "c" ]
  a.each {|x| print x, " -- " }

produces:

  a -- b -- c --


$

Hashes

A Hash is a list of key/value pairs. Here is an example with strings as keys:
$ irb --simple-prompt
>> prices = { 'egg' => 0.1, 'butter' => 0.99 }
=> {"egg"=>0.1, "butter"=>0.99}
>> prices['egg']
=> 0.1
>> prices.count
=> 2
>> exit
$
Of course, hashes can store not just strings as objects in the values, but - as with arrays - also classes that you define yourself (see the section called “Arrays”).

Symbols

Symbols are a strange concept and difficult to explain. But they are very useful and used frequently, amongst others with hashes. Normally, variables always create new objects:
$ irb --simple-prompt
>> a = 'Example 1'
=> "Example 1"
>> a.object_id
=> 70260036330560
>> a = 'Example 2'
=> "Example 2"
>> a.object_id
=> 70260036391520
>> exit
$
In both cases, we have the variable a, but object ID is different. We could carry on in this way indefinitely. Each time, it would generate a different object ID and therefore a new object. In principle, this is no big deal and entirely logical in terms of object orientation. But it is also rather a waste of memory space.
A symbol is defined by a colon before the name and cannot store any values itself, but it always has the same object ID, so it is very well suited to be a key:
$ irb --simple-prompt
>> :a.class
=> Symbol
>> :a.object_id
=> 413928
>> exit
$
Let's do another little experiment to make the difference clearer. We use a string object with the content white three times in a row and then the symbol :white three times in a row. For "white", a new object is created each time. For the symbol :white, only the first time:
$ irb --simple-prompt
>> 'white'.object_id
=> 70209583052020
>> 'white'.object_id
=> 70209583082240
>> 'white'.object_id
=> 70209583088920
>> :white.object_id
=> 413928
>> :white.object_id
=> 413928
>> :white.object_id
=> 413928
>> exit
$ 
Using symbols as key for hashes is much more memory efficient:
$ irb --simple-prompt
>> colors = { :black => '#000000', :white => '#FFFFFFFF' }
=> {:black=>"#000000", :white=>"#FFFFFFFF"}
>> puts colors[:white]
#FFFFFFFF
=> nil
>> exit
$ 
You will frequently see symbols in Rails. If you want to find out more about symbols, go to the help page about the class Symbol via ri Symbol.

Iterator each

With the method each you can work your way through a Hash step by step. Example:
$ irb --simple-prompt
>> colors = { :black => '#000000', :white => '#FFFFFFFF' }
=> {:black=>"#000000", :white=>"#FFFFFFFF"}
>> colors.each do |key,value|
?>   puts "#{key} #{value}"
>> end
black #000000
white #FFFFFFFF
=> {:black=>"#000000", :white=>"#FFFFFFFF"}
>> exit
$
Again, ri offers help and an example, in case you cannot remember one day how to use each:
$ ri -T Hash.each
Hash.each

(from ruby site)
------------------------------------------------------------------------------
  hsh.each      {| key, value | block } -> hsh
  hsh.each_pair {| key, value | block } -> hsh
  hsh.each                              -> an_enumerator
  hsh.each_pair                         -> an_enumerator
   

------------------------------------------------------------------------------

Calls block once for each key in hsh, passing the key-value pair
as parameters.

If no block is given, an enumerator is returned instead.

  h = { "a" => 100, "b" => 200 }
  h.each {|key, value| puts "#{key} is #{value}" }

produces:

  a is 100
  b is 200


$

Updates about this book will be published on my Twitter feed.