Enumerable Module
The enumerable module is a mixin that provides collection classes with many methods for traversing, searching, and sorting data. Some of you may be asking, "what is a mixin?", and that's an excellent question. If you are familiar with Sass and how mixins work then you already understand mixins. If not, I'll explain briefly. They are separate modules that you can "mix in" to your code. Ruby is a single inheritance language, which means each class can inherit from at most one other class. By creating a separate module, we can mix in all of the methods and it would be the same as inheriting from two classes. We can mix in as many modules as we want so despite being a single inheritance language there is no limitation on adding functionality.
Include?
To check a collection for an item, use the "include?" method. It feels as if we are asking ruby a question, does this array include max?
>> %w(Mick, Mike, Manny, Mo).include?("Max")
false
All?
The enumerable module contains very useful methods such as ".sort" and ".include?" and ".count". A full list of public methods can be found at Ruby-Docs. To introduce the enumerable mixin, examine the following line of code taken from Ruby-Docs:
>> %w[ant bear cat].all? { |word| word.length >= 3 }
true
All is a public instance method of enumerable. It returns true if all items passed to the block return true (recall Ruby's boolean logic). The array is created with the %w shortcut and the code is encased in curly brackets { }
instead of a code block. This allows the code to be placed on a single line. A block variable 'word' is created for the purpose of iterating over the collection to check if the length is greater than or equal to 3. The "length" method is provided to us via the string object's public instance methods. All of these concepts of Ruby have been introduced in previous chapters. Here we are putting everything we've learned together. The enumerable mixin is a large part of the Ruby language. It encompasses a wide variety of powerful methods that web/software developers use on a daily basis.
Any?
Unlike the all method, the any method will return true if any of the items return true. Both methods return a boolean as the result.
>> %w[ant bear cat].any? { |word| word.length >= 4 }
true
As we've seen previously, we can use the each method to iterate over a collection. If you aren't a programmer then the word 'iterate' is likely a new addition to your vocabulary. Another way to say it would be 'run through' or 'go over'. As a reminder, use a block variable as a placeholder when you're cycling through a collection with the each method.
In the following examples, the modulo operator is used to find multiples of three. Recall that the modulo operator ( % ) returns the remainder after a division. For example, 9 % 4
will return 1
, the remainder left over.
Find/detect
The method find and it's alias _detect _will run through a collection and return the first item that satisfies the code block.
>> (1...100).find {|x| x % 3 == 0 }
3
If we read the previous example out loud we have the following instruction, "go through the range 1-99 and find where 'x' divided by '3' results in '0'. The ruby interpreter duly responds with the first number that when divided by three equals zero. The detect method is an alias of find, they are interchangeable.
>> (1...100).detect {|x| x % 3 == 0 }
3
Find_all/select
But what if we wanted to return all of the items that satisfy the block, not just the first item. For that we have the 'find_all' method:
>> (1...34).find_all {|x| x % 3 == 0 }
[
[ 0] 3,
[ 1] 6,
[ 2] 9,
[ 3] 12,
[ 4] 15,
[ 5] 18,
[ 6] 21,
[ 7] 24,
[ 8] 27,
[ 9] 30,
[10] 33,
]
In this example, the range has been reduced from 1-99 to 1-33 for brevity. The 'find_all' _method returns all items in the collection that satisfy the block, they are a multiple of three. _The 'select' method is an alias to 'findall'. It will return the same result. Both methods return an array as the result. The output in IRB also provides the index of item, courtesy of the 'pretty print' gem. If you'd like to have a similar print out in your IRB follow the instructions in the Interactive rb chapter.
Find_index
Similar to find, the 'find_index' method returns the index of the item. Most programming languages use zero-based indexes where the first item sits at index zero, the second item sits at index one, and so on. In this example, we will search for a number between 1-100 that is divisible by both seven and five. Practice reading the line of code and saying the English equivalent aloud:
>> (1..100).find_index { |i| i % 5 == 0 and i % 7 == 0 }
34
"In the range 1-100, find the index where 'i' is divisible by 5 and 'i' is divisible by 7". But wait, the first number divisible by 5 and 7 isn't 34?! That's correct, it's 35, which sits at index 34. Keep in mind that in Ruby indexes are zero-based. This is true for most programming languages.
Count
The 'count' method returns the number of items in a collection. It is a simple and useful method. Often times developing with Rails you'll want to display the total number of messages or posts. The 'count' method makes this easy:
>> %w(how much wood would a woodchuck chuck if a woodchuck could chuck wood).count
13
Sort
The sort method is self explanatory. It sorts collections. One nuance that may not be so obvious is that it also can sort words by attributing each letter with a numeric value. In this case, the letter 'a' would come before 'b' and 'b' before 'c' and so on.
>> array = [35, 76, 21, 11, 77, 34, 98, 99, 2, 87, 66, 3, 45, 91, 4, 19, 5]
[
[ 0] 35,
[ 1] 76,
[ 2] 21,
[ 3] 11,
[ 4] 77,
[ 5] 34,
[ 6] 98,
[ 7] 99,
[ 8] 2,
[ 9] 87,
[10] 66,
[11] 3,
[12] 45,
[13] 91,
[14] 4,
[15] 19,
[16] 5
]
>> array.sort
[
[ 0] 2,
[ 1] 3,
[ 2] 4,
[ 3] 5,
[ 4] 11,
[ 5] 19,
[ 6] 21,
[ 7] 34,
[ 8] 35,
[ 9] 45,
[10] 66,
[11] 76,
[12] 77,
[13] 87,
[14] 91,
[15] 98,
[16] 99
]
Sort does not change the array. It prints out a sorted array. To change the array I would need to use the sort bang (sort!) method. This would change the value of 'array' and printed the sorted array. We can also try sorting an array of words:
>> word_array = ["aardvark", "beaver", "woodchuck", "lion", "zebra", "cat", "dog"]
[
[0] "aardvark",
[1] "beaver",
[2] "woodchuck",
[3] "lion",
[4] "zebra",
[5] "cat",
[6] "dog"
]
>> word_array.sort
[
[0] "aardvark",
[1] "beaver",
[2] "cat",
[3] "dog",
[4] "lion",
[5] "woodchuck",
[6] "zebra"
]
Grep
If you're familiar with Linux then you should already know how to use grep. Grep is a unix command that searches for a string using pattern matching. It's often used with regular expressions, a set of self contained pattern matching characters. The grep method is used with a pattern and the returned object will be an array of elements that match that pattern. Let's continue to use our 'array' and 'word_array' that we created in the previous examples:
>> word_array.grep "cat"
[
[0] "cat"
]
>> array.grep 1..20
[
[0] 2,
[1] 3,
[2] 4,
[3] 5,
[4] 11,
[5] 19
]
In the first example, we search (grep) for cat and ruby returns an array of cat. This example isn't particularly useful but as you can see grep allows us to search an array and pull out matching results. We can also search (grep) for a range of numbers in our array and the result is an array of 6 numbers between 1-20. Grep becomes exponentially more powerful by adding regular expressions. This is a topic that will be revisited in a later chapter.
All of the enumerable methods you've been introduced to thus far are important and have various use cases. The final two methods we're going to look at in this chapter are even more important because they allow you to solve problems with increased complexity. You'll see these methods used over and over again. Shying away from them will only produce "holes" in your Ruby knowledge. Although they may be more difficult to master, I encourage you to review and practice them until you're comfortable using both methods. The methods are map and inject. Each method also has an alias that can be used interchangeably.
Map/collect
Map is an powerful method for collections. It "maps" every element in a collection to the code block and returns the result in a new array. The alias "collect" can be used interchangeably. Here is an example of 'map' :
>> array = [1, 2, 3, 4, 5]
[
[0] 1,
[1] 2,
[2] 3,
[3] 4,
[4] 5
]
>> array.map { |n| n + 1 }
[
[0] 2,
[1] 3,
[2] 4,
[3] 5,
[4] 6
]
In this simple example, we create a block variable 'n' that will represent each element in the array. Each element then takes the place of 'n' one at a time. So, the first element, which is located at index zero, has one added to it, then the second, then the third, etc. The result is a new array where each element has been increased by one. Check out the next example using strings:
>> array = ["bob", "john", "sam", "bill"]
[
[0] "bob",
[1] "john",
[2] "sam",
[3] "bill"
]
>> array.map { |name| name.capitalize }
[
[0] "Bob",
[1] "John",
[2] "Sam",
[3] "Bill"
]
Once again, we create a block variable and then we declare what process should be applied to each element in the array. The result is a new array of capitalized names. The block variable was chosen at random. There is nothing special about the word "name". We could have used the letter 'n' once again. For example:
>> array.map { |n| n.capitalize }
[
[0] "Bob",
[1] "John",
[2] "Sam",
[3] "Bill"
]
The variable name isn't important. All that matters is that the receiver of the method call ( what is to the left of the period ) is the same as the block variable.
Inject/reduce
Inject (alias reduce) is an accumulator. If you specify a block, then each element is passed an accumulator value and the element. By default the accumulator value is called "memo". An example is required to illustrate this method.
>> (1..10).inject { |memo, n| memo + n }
55
It may be easier to think of 'inject' as 'reduce' because it is reducing a range of 10 elements to just a single element, the sum of the range. What the inject method is doing is accumulating a value and storing it in the variable memo. In this example, we are adding the currently accumulated value to the next element in the collection. We can specify a starting value by passing it as an argument to inject. If no starting value is specified the first element in the collection is used as the starting point. So, here is what is happening in our example:
memo starts at 1
1 + 2 = memo is now 3
3 + 3 = memo is now 6
6 + 4 = memo is now 10
10 + 5 = memo is now 15
15 + 6 = memo is now 21
21 + 7 = memo is now 28
28 + 8 = memo is now 36
36 + 9 = memo is now 45
45 + 10 = memo is now 55
Based on what you have seen, what is the result of the following code?
>> (1..10).inject(5) { |memo, n| memo + n }
This is almost the same as the previous example. However, now we are passing in a starting value of 5. Type the line above into an IRB session, you should get a result of 60.
The 'inject' method also has a shorthand. If you pass in a symbol then each element in the collection will automatically be passed to the named method.
# shorthand for adding numbers 1-10
>> (1..10).reduce(:+)
55
#shorthand for multiplying numbers 1-5
>> (1..5).inject(:*)
120
# remember, inject and reduce can be used interchangeably
Other Notable methods
You can find the Enumerable documentation in its entirety at Ruby-docs. Here are some other public instance methods and their function:
min - returns the minimum value
max - returns the maximum value
minmax - returns the minimum and maximum values
first - returns the first element in the collection
sum - returns the sum of the elements
take(n) - returns the first (n) elements in the collection
reject - returns array without rejected elements
drop_while - returns an array without dropped elements