Menu
CoddyTech

Enumerable 模块

本课属于 Coddy Ruby 学习旅程的“面向对象编程”单元。第 30 课,共 57 课。

Enumerable 是数组和哈希集合方法背后的模块。定义了 each,并逐个生成其元素的类,只要包含 Enumerable,就能获得 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
true

yield 会将一个值传递给调用者提供给 each 的代码块。类决定一个元素是什么,例如一行文本、一对值,或者它自己的一个对象:

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

当元素定义了 <=> 时,Enumerable 的排序方法(例如 sort、min 和 max)可以不带块运行。否则,这些方法需要一个块,例如 sort_by 和 min_by:

class Team
  include Enumerable

  def each(&block)
    @players.each(&block)     # 直接传递代码块
  end
end

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

挑战

简单

补全 Playlist 类。它保存通过 add 添加的 Song 对象(包含 title 和 seconds),引入 Enumerable 并定义 each,按添加顺序逐个 yield 每首歌曲。

提供的代码会为每一行输入添加一首歌曲(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(", ")}"
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Ruby 在线编译器