All original content is created in Ukrainian. Not all content has been translated yet. Some posts may only be available in Ukrainian.Learn more

How arrays work in Ruby: practical examples of each, map, select, inject, reduce, filter_map

Post cover: How arrays work in Ruby: practical examples of each, map, select, inject, reduce, filter_map
This content has been automatically translated from Ukrainian.
Arrays are one of the most convenient data structures in Ruby. They are flexible, dynamic, and have a huge number of built-in methods that allow you to process data in the most elegant way. Let's go through the main ones with examples.

each - simple iteration over elements

The each method iterates over the array and executes a block of code for each element. It is usually used when you need to do something with the elements but do not want to create a new array.
[1, 2, 3, 4, 5].each do |n|
  puts "Number: #{n}"
end
Result:
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
=> [1, 2, 3, 4, 5]
each always returns the original array, not the result of the block execution. What does this mean? The block executes puts (you see "Number 1" printed and so on). The iterator returns the array => [1, 2, 3, 4, 5]
So if you assign the iterator's value to a variable, you will get the array.
myvar = [1, 2, 3, 4, 5].each do |n|
  puts "Number: #{n}"
end

Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
=> [1, 2, 3, 4, 5]

myvar
=> [1, 2, 3, 4, 5]

map - transforming elements

The map (or collect) method creates a new array where each element is the result of executing the block.
numbers = [1, 2, 3, 4, 5]
squares = numbers.map { |n| n ** 2 }

p squares  # => [1, 4, 9, 16, 25]
Use map when you need to get a new set of data from an existing array.
What is the difference between map and collect?
In Ruby, map is simply a more familiar name for programmers coming from other languages (JavaScript, Python, etc.). collect is a historical name that has remained from early versions of Ruby (influenced by Smalltalk).
If you look at the Ruby code (Enumerable module):
alias collect map
that is, it is an exact synonym, not a wrapper, not a delegate - just a different name for the same method.

select - filtering elements

The select method returns an array of elements for which the block returns true.
numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.select { |n| n.even? }

p even_numbers  # => [2, 4]
There is a reverse method - reject, which returns elements for which the block returns false.
odd_numbers = numbers.reject { |n| n.even? }
p odd_numbers   # => [1, 3, 5]

inject / reduce - accumulating a value

inject (synonym for reduce) is a powerful tool for "folding" an array into a single value. You can calculate a sum, build a hash, etc.
numbers = [1, 2, 3, 4, 5]

sum = numbers.inject(0) { |acc, n| acc + n }
p sum  # => 15
It takes an initial value (in our case 0) and passes it along with each element of the array to the block.
Syntax:
array.inject(initial_value) { |accumulator, element| ... }
  • accumulator - a variable that holds the current result.
  • element - the current element of the array.
  • The block returns a new value for the accumulator.
Another example - joining elements into a string:
words = ["Ruby", "is", "fun"]
sentence = words.reduce("") { |acc, word| acc + word + " " }
p sentence.strip  # => "Ruby is fun"
strip is used to remove the extra space at the end.
Building a hash would look like this:
letters = %w[a b c]
indexed = letters.inject({}) { |acc, l| acc[l] = l.upcase; acc }
# => {"a"=>"A", "b"=>"B", "c"=>"C"}
But here it’s a bit more complicated. The inject (or reduce) method goes through the collection and gradually accumulates the result in the accumulator (acc).
Here we pass {} - that is, the initial value of the accumulator - an empty hash.
{ |acc, l| acc[l] = l.upcase; acc }
This is a block that is called for each element l in the array.
  • acc is the current accumulator (initially {})
  • l is the current letter ("a", then "b", then "c")
Inside the block:
acc[l] = l.upcase
adds a pair to the hash:
  • key: l (for example, "a")
  • value: l.upcase (that is, "A")
After that, we return acc itself to pass it to the next iteration
Of course, this can be done in other ways, but here we are considering the capabilities of these methods.

filter_map - Ruby 2.7+ magic

The filter_map method combines map and select in a single pass over the array. It transforms and filters at the same time.
numbers = [1, 2, 3, 4, 5, 6]
even_squares = numbers.filter_map { |n| n**2 if n.even? }

p even_squares  # => [4, 16, 36]
This is more convenient and efficient than numbers.select { ... }.map { ... }.

This post doesn't have any additions from the author yet.

How self, protected, and private work (Ruby)
28 Oct 13:52

How self, protected, and private work (Ruby)

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Integer division in Ruby: why 6 / 4 equals 1
28 Oct 14:10

Integer division in Ruby: why 6 / 4 equals 1

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
28 Oct 14:42

How &:to_s works in Ruby and what Symbol#to_proc is

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
What are Proc and Lambda in Ruby?
28 Oct 15:57

What are Proc and Lambda in Ruby?

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
What happens if you call [1, 2, 3].map(&Person)
29 Oct 17:54

What happens if you call [1, 2, 3].map(&Person)

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Singleton class (eigenclass) in Ruby: what it is and why it is needed
29 Oct 18:29

Singleton class (eigenclass) in Ruby: what it is and why it is needed

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska