Menu
Coddy logo textTech

The Enumerable Module

Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 30 of 57.

Enumerable is the module behind the collection methods of arrays and hashes. A class that defines each, yielding its elements one by one, and includes Enumerable gets map, select, count, sort_by, min_by, sum, include? and dozens more:

class Playlist
  include Enumerable

  def initialize(*songs)
    @songs = songs
  end

  def each
    @songs.each { |song| yield song }
  end
end

list = Playlist.new("Intro", "Dune", "Outro")
puts list.map(&:upcase).join(", ")
puts list.select { |s| s.size > 4 }.inspect
puts list.count
puts list.include?("Dune")

Output:

INTRO, DUNE, OUTRO
["Intro", "Outro"]
3
true

yield passes a value to the block the caller gave to each. The class decides what one element is, for example a line of text, a pair of values or one of its own objects:

class Inventory
  include Enumerable

  def initialize
    @stock = { "apple" => 3, "pear" => 0, "plum" => 7 }
  end

  def each
    @stock.each { |name, qty| yield name, qty }
  end
end

inv = Inventory.new
p inv.select { |_, qty| qty.positive? }.map(&:first)
p inv.max_by { |_, qty| qty }

Output:

["apple", "plum"]
["plum", 7]

The elements do not have to be stored. each can compute them as it goes, and methods such as first stop the loop early:

class Squares
  include Enumerable

  def initialize(limit)
    @limit = limit
  end

  def each
    n = 1
    while n * n <= @limit
      yield n * n
      n += 1
    end
  end
end

p Squares.new(50).to_a
p Squares.new(1000).first(3)
puts Squares.new(100).sum

Output:

[1, 4, 9, 16, 25, 36, 49]
[1, 4, 9]
385

When the elements define <=>, the sorting methods of Enumerable, such as sort, min and max, work without a block. Otherwise they take one, as in sort_by and min_by:

class Team
  include Enumerable

  def each(&block)
    @players.each(&block)     # pass the block straight on
  end
end

team.sort_by(&:age)
team.min_by(&:score)
challenge icon

Challenge

Easy

Complete the class Playlist. It holds Song objects (with title and seconds) added with add, includes Enumerable and defines each, which yields every song in the order added.

The supplied code adds one song per input line (title,seconds) and then uses only Enumerable methods on the playlist: it prints the number of songs, the titles joined by commas, the total length in m:ss, the longest title, and the titles of songs over 3 minutes, sorted alphabetically (or none).

Your code goes in song.rb and playlist.rb. main.rb holds the supplied input/output code and cannot be edited.

Try it yourself

require_relative 'song'
require_relative 'playlist'

# Supplied input/output code: keep it as it is
input = []
while (line = gets)
  input << line.chomp
end
list = Playlist.new
input.each do |line|
  title, seconds = line.split(",")
  list.add(Song.new(title, seconds.to_i))
end
total = list.sum(&:seconds)
long = list.select { |s| s.seconds > 180 }.map(&:title).sort
puts "songs: #{list.count}"
puts "titles: #{list.map(&:title).join(", ")}"
puts format("total: %d:%02d", total / 60, total % 60)
puts "longest: #{list.max_by(&:seconds).title}"
puts "over 3 min: #{long.empty? ? "none" : long.join(", ")}"
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online Ruby compiler