Menu
CoddyTech

自定义集合

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

集合类会组合多个特殊方法:使用 Enumerable 实现 each,使用 << 添加元素,使用 size 获取大小,并使用 to_s 和 inspect 打印内容。each 也应该能在不传入代码块时工作,通过 to_enum 返回一个枚举器,就像数组一样:

class TodoList
  include Enumerable

  def initialize
    @items = []
  end

  def <<(item)
    @items << item
    self
  end

  def each
    return to_enum(:each) unless block_given?

    @items.each { |item| yield item }
    self
  end
end

list = TodoList.new << "wash" << "shop" << "cook"
list.each.with_index(1) { |item, n| puts "#{n}. #{item}" }
p list.select { |i| i.include?("o") }

输出:

1. wash
2. shop
3. cook
["shop", "cook"]

block_given? 用于判断调用者是否传入了代码块。没有代码块时返回的枚举器支持链式调用,例如 each.with_index、each_slice 或 next:

class Countdown
  include Enumerable

  def initialize(from)
    @from = from
  end

  def each
    return to_enum(:each) unless block_given?

    @from.downto(1) { |n| yield n }
  end
end

e = Countdown.new(3).each
puts e.next
puts e.next
p Countdown.new(5).each_slice(2).to_a

输出:

3
2
[[5, 4], [3, 2], [1]]

to_s 是面向用户的友好文本,而 inspect 是给程序员看的简短摘要。对于集合,inspect 通常会显示类和大小,而不是每个元素:

class TodoList
  include Enumerable

  def initialize(*items)
    @items = items
  end

  def each(&block)
    @items.each(&block)
  end

  def size
    @items.size
  end

  def to_s
    map.with_index(1) { |item, n| "#{n}. #{item}" }.join("\n")
  end

  def inspect
    "#<TodoList #{size} items>"
  end
end

list = TodoList.new("wash", "shop")
puts list
p list

输出:

1. wash
2. shop
#<TodoList 2 items>

Enumerable 提供了 count,但不提供 size、empty?、<< 或 []:这些属于类,因为只有类才知道元素是如何存储的。定义这些方法后,调用者就会觉得这个集合像数组一样:

def size
  @items.size
end

def empty?
  @items.empty?
end

def [](index)
  @items[index]
end
challenge icon

挑战

简单

完成 TodoList 类。<< 添加任务文本并返回列表。each 逐个生成任务并返回 to_enum(:each)(不带代码块时)。包含 Enumerable。size 返回任务数量。to_s 将任务作为带编号的行返回,例如 1. wash 或 nothing to do,而 inspect 返回 #<TodoList 3 items>。

提供的代码会通过链式调用 << 添加每一行输入,然后依次输出列表、列表的 p 输出、通过 each.with_index(1) 为任务编号并加上 ! 的结果(例如 1! wash)、包含 o 的任务数量,以及按字母顺序排列的第一个任务。

你的代码写在 todo_list.rb 中。main.rb 包含提供的输入/输出代码,不能编辑。

亲手试试

require_relative 'todo_list'

# 提供的输入输出代码:保持原样。
input = []
while (line = gets)
  input << line.chomp
end
list = input.reduce(TodoList.new) { |l, task| l << task }
puts list
p list
list.each.with_index(1) { |task, n| puts "#{n}! #{task}" }
puts "with o: #{list.count { |t| t.include?("o") }}"
puts "first: #{list.min}"
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Ruby 在线编译器