Enumerableモジュール
CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 30/57。
Enumerableは、配列やハッシュのコレクションメソッドを支えるモジュールです。eachを定義して要素を1つずつyieldし、Enumerableをincludeするクラスは、map、select、count、sort_by、min_by、sum、include?など、さらに数十個のメソッドを使えるようになります。
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")出力:
INTRO, DUNE, OUTRO
["Intro", "Outro"]
3
trueyield は、呼び出し元が each に渡したブロックに値を渡します。クラスが1つの要素を何とするかを決めます。たとえば、テキストの1行、値のペア、または独自のオブジェクトのいずれかです。
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 }出力:
["apple", "plum"]
["plum", 7]要素を保存しておく必要はありません。eachは順次要素を計算でき、firstなどのメソッドはループを早めに終了します。
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出力:
[1, 4, 9, 16, 25, 36, 49]
[1, 4, 9]
385要素が <=> を定義している場合、sort、min、max などの Enumerable のソートメソッドはブロックなしで動作します。そうでない場合は、sort_by や min_by のようにブロックを受け取ります:
class Team
include Enumerable
def each(&block)
@players.each(&block) # ブロックをそのまま渡す
end
end
team.sort_by(&:age)
team.min_by(&:score)チャレンジ
簡単Playlistクラスを完成させてください。これは、addで追加されたtitleとsecondsを持つSongオブジェクトを保持し、Enumerableをインクルードして、追加された順に各曲をyieldするeachを定義します。
提供されたコードは入力行ごとに1曲(title,seconds)を追加し、その後プレイリストに対してEnumerableのメソッドのみを使用します。曲数、カンマでつないだタイトル、合計時間(m:ss)、最も長い曲のタイトル、そして3分を超える曲のタイトルをアルファベット順(該当する曲がなければnone)で出力します。
コードはsong.rbとplaylist.rbに記述します。main.rbには提供された入出力コードがあり、編集できません。
自分で試してみよう
require_relative 'song'
require_relative 'playlist'
# 提供された入出力コード:そのままにしてください
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(", ")}"
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rubyオンラインコンパイラ