If any of you have written code in the last year that had an explicit loop […], you don’t understand collections.
Professor David West – OOP is Dead! Long Live OODD!
You may be baffled by this quote if you come from a C-flavored language like
C++ and Java, where explicit loops like for
and foreach
are part of your
everyday life.
Luckily, collection methods come built-in with Ruby. Once you realize how powerful they are compared to explicit loops, there is no going back!
each
Let’s begin with the collection method that has the least added value. each
is the equivalent of a for
loop. Use it when you need to iterate on a
collection with side effects.
['a', 'b', 'c'].each do |e|
# do side effects, such as printing to the console, writing to a file,
# persisting in database, etc.
end
FYI Ruby also has a for
statement, but nobody really uses
it.
map
(alias: collect)
Whenever you need to transform some values into some other values, map
is
your friend.
[1, 2, 3].map { |e| e * 2 } # returns [2, 4, 6]
['a', 'b', 'c'].map { |e| e.upcase } # returns ['A', 'B', 'C']
['a', 'b', 'c'].map(&:upcase) # same result as before, for experienced
# lazy Ruby programmers
map
is your bread and butter. I probably use it more than each
.
select
(alias: find_all)
Very useful when you need to filter (i.e. “select”) multiple values.
[1, 2, 3, 4].select { |e| e % 2 == 0 } # returns [2, 4]
reject
The contrary of select
.
[1, 2, 3, 4].reject { |e| e % 2 == 0 } # returns [1, 3]
partition
select
+ reject
.
[2, 3, 4, 5].partition { |e| e.even? } # returns [[2, 4], [3, 5]]
find
(alias: detect)
Very useful when you need to find a single value.
[4, 6, 8, 13].find { |e| e > 7 } # Returns 8 (the first found element)
reduce
(alias: inject)
reduce
is very important in functionnal programming, where it is also known
as “fold”. Indeed, it can be used to build everything else: map
, select
,
find
, min
, max
, sums, etc.
The idea is to use an accumulator that will contain the final result. This accumulator can be anything: a number, an array, a hash, etc.
[1, 2, 3].reduce(0) { |acc, e| acc + e } # returns 6 (0 + 1 + 2 + 3).
# 0 is the initial value of the
# accumulator.
[1, 2, 3].reduce { |acc, e| acc + e } # same result as before. When we omit the
# accumulator initial value, the first element
# of the array is chosen. So in our case,
# 1 is the initial value of the accumulator.
# What is computed is 1 + 2 + 3.
[1, 2, 3].reduce(:+) # same result as before, for experienced lazy Ruby programmers.
# Reduce accepts :+, but you can also use &:+
[2, 3, 4].reduce { |acc, e| acc * e } # returns 24 (2 * 3 * 4)
[0, 2, 3, 4].reduce { |acc, e| acc * e } # returns 0 (0 * 2 * 3 * 4)
I rarely use reduce
in practice, but it is fun and good to know for your
computer science knowledge.
all?
[2, 4, 6].all? { |e| e.even? } # returns true
Self-explanatory.
any?
[3, 8, 42].any? { |e| e > 10 } # returns true
Self-explanatory.
By the way, I like using the “no block” form of any?
to ask if something “has
any” significant element:
[3, 4].any? # returns true
[].any? # returns false
[nil].any? # returns false
[false].any? # returns false
I find it more expressive than !some_array.empty?
.
times
This is a fun one!
3.times { puts 'Hello world!' } # prints 'Hello world!' 3 times
3.times { |i| puts i } # prints '0', '1' and '2'
In practice I don’t use it very often though.
Sorting methods
[7, 2, 5].sort # returns [2, 5, 7]
['c', 'b', 'a'].sort # returns ['a', 'b', 'c']
employees.sort_by {|e| e.last_name} # sort your employees by last name
But… I want indexes!
On top of the current element, you also need the current index? Worry not, Ruby has it all.
['a', 'b', 'c'].each_with_index do |e, i|
# do stuff
end
['a', 'b', 'c'].map.with_index do |e, i|
# do stuff
end
In practice I don’t use each_with_index
very often.
I never had to use map.with_index
, but I included it for comprehensiveness.
Explicit loops
Collection methods will probably cover 90% of your needs.
What is certain is that you won’t need to use a for
loop ever again.
But there are cases when the number of iterations in not known in advance, for
example if you work on an algorithm or low-level code. This is a typical job
for the while
and until
explicit loops, which are ok to use in this case.
finished = false
until finished
# do stuff
end
x = 100
while x > 0
# do stuff
end
You may also want to use an infinite loop, for example the main infinite loop of a video game. Here is the syntax:
loop do
# Do stuff.
# You can still exit the loop with break.
end
Same as while true
, but terser.
Going further
Ruby collection methods become even more powerful if you combine them:
['coconut', 'lemon', 'banana', 'apple'].select { |e| e.size > 5 }
.map { |e| e.upcase }
.sort
# returns ['BANANA', 'COCONUT']
Don’t do this to excess though!
This article covers the main collection methods: feel free to dive into the Ruby Core documentation for more.