Getting started with Ruby – Part 3 – Collections, Ranges & Arrays

In my previous article Getting started with Ruby – Part 2 I introduced you all to Ruby & its philosophy and I started out with data type classes in Ruby. I was able to give you an overview about Strings & Numbers in ruby. Today I will start with Collections.

The Range #

As the name suggests, ranges hold a sequential collection of numbers. Defining a range in Ruby is extremely simple, instead of explaining, i’ll show it to you. Don’t be afraid to start an IRB shell to try the code out.

full_range = 1..10
loop_range = 0...10
test_range = 1..10

puts full_range == loop_range    # => false
puts full_range == test_range    # => true
puts loop_range.eql?(loop_range) # => false

puts full_range.include?(1)      # => true
puts test_range.include?(4)      # => true
puts full_range.include?(10)     # => true
puts full_range.include?(12)     # => false

puts loop_range.include?(0)      # => true
puts loop_range.include?(4)      # => true
puts loop_range.include?(10)     # => false

From the examples above, it should be clear what the syntax to define a range is, although you might be confused regarding the difference in line 1 & line 2, I can assure you it’s not a typo. Ruby provides 2 ways to create ranges, one using two periods .. and one using triple periods ..., the difference being full_range = 1..10 is a range which starts from value 1 and ends at value 10, whereas loop_range = 0...10 is a range that starts from value 0 and ends at value 9.

Here I have also introduced you to 3 methods of the range ==, eql? & include?, I am sure that the methods names are quite self explanatory. Although I would like to bring your attention towards a convention that is followed in ruby for method names. Method names in Ruby may end in =, ? or !. As a convention we end a method name in ? to indicate that the method will be returning a boolean value, i.e. true or false, we end a method name in = to indicate that it is a setter method used for assignment operations, and we end a method name in ! to indicate that the execution of the method will alter the state / value of the caller object. We will get into details of methods later again when we would be digging into classes.

Array #

These are simple indexed or ordered collection of elements or objects. If you have worked in C/C++/Java, Arrays are similar in concept except for the fact that the various elements within the Array need not be of the same type, they could be of any type. Let me give you some examples to make it more clear:

empty_array = [] #=> []

another_empty_array = Array.new # => []
number_array = (1..10).to_a # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# to_a is a method that the Range class implements which converts the range to
# an array and returns the array

random_array = [1, 'hello', (100/3).to_s, Array.new, (1..10).to_a]
# => [1, "hello", "33", [], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
# As you can see, the array is dynamic in nature and the individual element
# can be just about any object.

preinitialized_array = Array.new(10) # => [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]
preinitialized_array_with_values = Array.new(10) {0} # => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

short_hand_array = %w(this is an array) # => ["this", "is", "an", "array"]
short_hand_with_interpolation = %W(this is an array of length #{1*7})
# => ["this", "is", "an", "array", "of", "length", "7"]
# %w works similar to single quoted string creation except that it creates an
# array out of the words, whereas %W works similar to double quoted string
# creation and allows for interpolation by embedding results within and creates
# an array from the words.

short_hand_with_string = 'this is an array'.split # => ["this", "is", "an", "array"]
short_hand_with_string = 'this is an array'.split(' ') # => ["this", "is", "an", "array"]

short_hand_with_string[0] # => 'this'
short_hand_with_string[-1] # => 'array'
short_hand_with_string[-2] # => 'an'
# you can access the array from reverse by using negative numbers, awesome
# isn't it ? :)

short_hand_with_string[0..2] # => ["this", "is", "an"]
# you can use a range to reference multiple elements within an array and it
# will return an array

short_hand_with_string.values_at(0, 1, 2) # => ["this", "is", "an"]

short_hand_with_string.at(-1) # => 'array'
short_hand_with_string.at(100) # => nil
short_hand_with_string.fetch(100, 'Not Found!') # => 'Not Found!'

The above are various ways in which one can create & access Arrays in Ruby, I think most of them are pretty self explanatory, there may be some minor constructs here that you may not understand, but I will be going through them more in detail later on, for now lets concentrate on just Arrays.

Let us now take a look at various ways to manipulate arrays :

array = (1..10).to_a # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array[0] = 11 # => [11, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array[12] = 12 # => [11, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12]
array.push(13, 14) # => [11, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14]
# pushes / appends / inserts multiple comma separate values to the end of the array

array.insert(-1, 16) # => [11, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14, 16]
array.insert(1, 100, 101) # => [11, 100, 101, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14, 16]
# `insert` method takes as input multiple comma separate values, however the
# first argument is the index of the position where you wish to insert th
# multiple values. So the values after the first argument are the values tha
# will be inserted into the array started from the index identified by the first
# argument.

array << 20 # => [11, 100, 101, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14, 16, 20]
array << 21 << 22 << 23 # => [11, 100, 101, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14, 16, 20, 21, 22, 23]
array.pop # => 23
array # => [11, 100, 101, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14, 16, 20, 21, 22]

array.pop(2) # => [21, 22]
array # => [11, 100, 101, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14, 16, 20]
# As you can see above `pop` method can optionally take as input an integer
# representing the number of elements that should be popped from the array.

array.shift # => 11
array # => [100, 101, 2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14, 16, 20, 21, 22]
array.shift(2) # => [100, 101]
array # => [2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 14, 16, 20]

array.index(14) # => 13
# This returns the index of the input element in the array, it returns nil if
# the element doesn't exist

array.include?(5) # => true

array.delete(14) # => 14
array # => [2, 3, 4, 5, 6, 7, 8, 9, 10, nil, nil, 12, 13, 16, 20]
# `delete` method takes as input an element which is then deleted from the
# caller array. In case the element does not exist within the array, it returns
# with `nil`

array.delete(1000) { 'Doesn\'t Exist!' } # => Doesn't Exist
#The `delete` method also accepts a block which it uses in case the input element does not exist

array.delete_at(3) # => 5
array # => [2, 3, 4, 6, 7, 8, 9, 10, nil, nil, 12, 13, 16, 20]
# `delete_at` method takes the index number as input instead of the element
# like the delete method and deletes the element at that index within the caller
# array and returns it.

The above should give you a very good idea on how to create & manipulate arrays, the examples should be explanatory, I have also added helpful comments along.

I had forgotten to demonstrate some really powerful features of arrays before hand. Let me do that now :

# While assigning, its not necessary to have the square brackets []
array1 = 1,2,3,4
array2 = 4,5,6,7

# union
array1 + array2 # => [1, 2, 3, 4, 4, 5, 6, 7]

# difference
array1 - array2 # => [1, 2, 3]
array2 - array1 # => [5, 6, 7]

# intersection
array1 & array2 # => [4]

Since the article is already very long, I will cut it short here and take up the last collection data type Hash in my next article. After that I will take you through various mechanisms for looping over collections in Ruby, which will apply to all collection data types.

In Ruby there are always several ways to do one thing, the reason for that is because there are always more than one intuitive way to do the same thing. Different developers use different ways to do what they wish to as per what suits them best. I am sure you will get there too soon enough.

Please do feel free to launch the IRB and try out various things that I have shown here, experiment & play around with it and I am sure you will have a nice time :). After I finish with covering the looping mechanisms for collections I will also write about how you can use IRB & Ruby’s reflective nature to inspect objects.

I hope you find the series of articles easy to understand and that it helps you getting started with Ruby. In case you have any questions or suggestions, please do let me know by adding it in the comments below.

comments powered by Disqus