Menu
CoddyTech

Comparable 模块

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

Ruby 内置的模块 Comparable 会为一个类提供运算符 <、<=、==、>、>=,以及方法 between? 和 clamp。它需要该类提供一个方法:<=>。当对象较小、相等或较大时,这个方法分别返回负数、0 或正数:

class Weight
  include Comparable
  attr_reader :grams

  def initialize(grams)
    @grams = grams
  end

  def <=>(other)
    grams <=> other.grams
  end
end

a = Weight.new(500)
b = Weight.new(1200)
puts a < b
puts a == Weight.new(500)
puts b.between?(a, Weight.new(2000))

输出:

true
true
true

数字和字符串已经支持 <=>,所以类通常会委托给其中一个值。数组会逐个元素进行比较,因此可以同时按多个字段排序:

class Person
  include Comparable
  attr_reader :last, :first

  def initialize(first, last)
    @first = first
    @last = last
  end

  def <=>(other)
    [last, first] <=> [other.last, other.first]
  end
end

a = Person.new("Ada", "Lovelace")
b = Person.new("Alan", "Turing")
c = Person.new("Byron", "Lovelace")
puts a < c
puts b > c

输出:

true
true

sort、min 和 max 也会使用 <=>,因此无需代码块就能对这个类的对象进行排序。clamp 会将一个值限制在另外两个值之间:

class Weight
  include Comparable
  attr_reader :grams

  def initialize(grams)
    @grams = grams
  end

  def <=>(other)
    grams <=> other.grams
  end

  def to_s
    "#{grams}g"
  end
end

list = [Weight.new(900), Weight.new(150), Weight.new(400)]
puts list.sort.join(", ")
puts list.max
puts Weight.new(5000).clamp(Weight.new(0), Weight.new(1000))

输出:

150g, 400g, 900g
900g
1000g

当另一个对象无法比较时,<=> 应返回 nil。然后,Comparable 会对 < 引发 ArgumentError,并对 == 返回 false,而不是因令人困惑的 NoMethodError 而失败:

def <=>(other)
  return nil unless other.is_a?(Weight)

  grams <=> other.grams
end

Weight.new(1) == 5     # false
Weight.new(1) < 5      # ArgumentError:Weight 与 5 比较失败
challenge icon

挑战

简单

补全 Weight 类。Weight.parse(text) 可读取 500g、1.2kg 或 2lb(1 lb 是 453.6 g),并存储克数。包含 Comparable,并根据克数定义 <=>。to_s 返回四舍五入到整数的克数,例如 1200g。

提供的代码会解析每一行输入,然后输出排序后的重量、最重的重量、最轻的重量、第一项重量是否介于 1 kg 和 2 kg 之间,以及限制为最多 1 kg 的第一项重量。

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

亲手试试

require_relative 'weight'

# 提供的输入输出代码:保持原样。
input = []
while (line = gets)
  input << line.chomp
end
weights = input.map { |line| Weight.parse(line) }
puts "sorted: #{weights.sort.join(", ")}"
puts "heaviest: #{weights.max}"
puts "lightest: #{weights.min}"
puts "first between 1kg and 2kg: #{weights[0].between?(Weight.parse("1kg"), Weight.parse("2kg"))}"
puts "first clamped: #{weights[0].clamp(Weight.parse("0g"), Weight.parse("1kg"))}"
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Ruby 在线编译器