• 20 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 10/4/17

Enumerables

Log in or subscribe for free to enjoy all this course has to offer!

 

As we mentioned at the end of last chapter, Ruby blocks are really useful for doing things to collections of objects. Imagine that you had a list of dollar amounts, and you wanted to convert it into a list of euro amounts. You could do that with theArray#mapmethod in Ruby:

dollar_amounts = [2347, 5834, 89, 89533]
EXCHANGE_RATE = 0.865448692
euro_amounts = dollar_amounts.map do |amount|
  (amount * EXCHANGE_RATE).round(2)
end
=> [2031.21, 5049.03, 77.02, 77486.22]

The block that we pass in our call tomapis called one time for each element in the array. Each time the block is called, the value of the current array element is passed in as the block parameter. The results of running each block are stored as new elements in a new array. After this code is run, we have two separate arrays:

dollar_amounts = [2347, 5834, 89, 89533]
euro_amounts = [2031.21, 5049.03, 77.02, 77486.22]

 

Hashes

Arrays aren't the only class of object that you can iterate over.

Hashes are like arrays, but different. They're a collection of objects that aren't really in any particular order, but that each has a name you can use to find it in the collection. If an array is like a group of people waiting in a line to be served at a takeout counter, a hash is like a group waiting to be seated at a restaurant, where each person gives their name and then just waits wherever outside. When the host is ready to seat them, they go outside and call them by name.

my_hash = {
  alessi: 8,
  hanh: 2,
  donnelly: 2,
  shaw: 5,
  stein: 2,
  ngabe: 4
}

my_hash[:alessi]
=> 8
my_hash[:ngabe]
=> 4

You can iterate over hashes as well. Their order isn't guaranteed like that of Arrays. It's like taking each person out of the crowd, one-at-a-time, until you've gotten to everyone. Here's how we can map a hash to create an array of arrays:

my_hash.map do |last_name, number_in_party|
  [
    last_name,
    number_in_party >= 4 ? :big_table : :small_table
  ]
end
=> [[:alessi, :big_table], [:hanh, :small_table], [:donnelly, :small_table], [:shaw, :big_table], [:stein, :small_table], [:ngabe, :big_table]]

In a previous chapter, we also looked at how different classes of numbers in Ruby shared behaviors through the Numeric class. In the same way, collections of data likeArrayandHashshare behaviors through the Enumerable module.

Let's take a look at the documentation for Enumerable, for the 2.4.1 version of Ruby:

https://ruby-doc.org/core-2.4.1/Enumerable.html

These methods provide great examples of the usefulness of blocks. They're also some of the most-used tools for the day-to-day work of a Ruby developer.

This is also a good chance to get familiar with the Ruby docs, which are the standard reference for how the Ruby language works.

On the left side of this page you see a list of instance methods that come with including the Enumerable module. You can click on any of those methods to visit its documentation in the page.

all?

Like many enumerable methods, the block passed in to *all? is expected to be a kind of custom test for some condition that you devise. Specifically, *all? tells you if every element in the collection passes your test:

[1,2,3,4,5].all? { |i| i < 100 }
=> true
[1,2,3,4,5].all? { |i| i < 5 }
=> false

Truthiness

Remember, just as with a method, the result of evaluating the last line in your block (in this case, the only line), becomes the value that is returned by the block. Methods likeall?will try to interpret the value you return as atrueorfalse. In the example above, the<method does return a boolean value, so that works perfectly for our test. But you can also return any kind of value you like, and Ruby will make a guess as to whether it should be considered truthy or falsy. Objects of most types are considered truthy. This goes for all numbers and strings, even0and"". The objectnil– and of course the objectfalseitself – are considered falsy.

any?

any?works much likeall?, except that it tells you if any element passes your test:

[1,2,3,4,5].any? { |i| i == 6 }
=> false
[1,2,3,4,5].any? { |i| i == 5 }
=> true

count

*count is different depending on whether or not you pass a block. If you don't pass a block, it just tells you how many elements are in the collection:

[1,2,3,4,5].count
=> 5

If you do pass a block, it treats it like a test, and tells how many elements pass:

[1,2,3,4,5].count { |i| i.even? }
=> 2

find, akadetect

like any?, but returns the first element that passes your test

[1,2,3,4,5].find { |i| i.even? }
=> 2
[1,2,3,4,5].detect { |i| i.even? }
=> 2

drop

returns a new array without the first n elements

[1,2,3,4,5].drop(3)
=> [4,5]

drop_while

like drop, but drops everything up until but not including the first element that fails your test

[1,2,3,4,5].drop_while { |i| i == 3 }
=> [3,4,5]

 

find_all

returns a new array with all the elements that pass your test

[1,2,3,4,5].find_all { |i| i.odd? }
=> [1,3,5]

selectdoes the same thing

reject

like find_all and select, except it returns a new array with all the elements that _don't pass your test:

[1,2,3,4,5].reject { |i| i.odd? }
=> [2,4]

 

sort

Sorting puts an array, or other enumerable, in order according its default sorting logic, or, if you wish to override that, a test that you define:

%w{dog cat pig fish}.sort => ["cat", "dog", "fish", "pig"]
# alphabetical

%w{dog cat pig fish}.sort { |a, b| a <=> b } => ["cat", "dog", "fish", "pig"]
# same thing. This is the default behavior spelled out in code.

%w{dog cat pig fish}.sort { |a, b| b <=> a } => ["pig", "fish", "dog", "cat"]
# here we've reversed the default behavior by switching the order of the *a and *b arguments in the comparison operation

%w{dog cat piglet fish}.sort { |a, b| a.length <=> b.length } => ["dog", "cat", "fish", "piglet"]
# here we're changing comparison to be based on the string's length

 

sort_by

Similar to sort, but instead of requiring you to use the comparison operator *<=>, sort_by sorts based on what each element in your enumerable would produce when passed to your block:

We could rewrite the last example above like:

%w{dog cat piglet fish}.sort_by { |word| word.length } => ["dog", "cat", "fish", "piglet"]

Note this comment in the docs:

> The result is not guaranteed to be stable. When two keys are equal, the order of the corresponding elements is unpredictable.

In the example abovedogandcatare the same length. In this case, it left them in the order they were in in the original array, but we can't rely on this behavior. In another circumstance,sort_bymight change the order of "equal" comparisons.

take

Sort of the opposite ofdrop, returns the first n elements of the enumerable, where n is the argument passed totake. If n is more than the length of the enumerable,takejust returns the original enumerable:

[1,2,3,4,5].take(3) => [1,2,3]
[1,2,3,4,5].take(10) => [1,2,3,4,5]

 

take_while

Sort of the opposite of *drop_while, returns everything up until but not including the first element that fails your test

[1, 2, 3, 4, 5].take_while { |i| i < 3 } #=> [1, 2]

 

reduce

The last thing we want to look at in this chapter is the reduce method. Reduce is one of those things that can be hard to grasp, but which can change the way you look at programming.

Like a lot of the methods here, *reduce executes your block one time for each element in the enumerable. The difference between reduce and many other iterators, though, is that it doesn't try to produce a new enumerable with a one-to-one correspondence with the old enumerable. Instead, as it iterates over the enumerable, *reduce accumulates the output of the block each time, passing the result of each calculation to the next block, along with the value of the current element. As a result, the block in *reduce has _two_ arguments.

[1,2,3,4,5].reduce { |accumulated_value, i| accumulated_value + i }
=> 15

you can specify what you want "accumulated_value" to be for the first iteration:

[1,2,3,4,5].reduce(10) { |accumulated_value, i| accumulated_value + i }
=> 25

If we were to spell out the calculations that happen in the last line, they would look like this:

accumulated_value = 10
accumulated_value = accumulated_value + 1
accumulated_value = accumulated_value + 2
accumulated_value = accumulated_value + 3
accumulated_value = accumulated_value + 4
accumulated_value = accumulated_value + 5
=> 25

 

Example of certificate of achievement
Example of certificate of achievement