Menu
Coddy logo textTech

Comparableモジュール

CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 29/57。

Rubyの組み込みモジュールComparableを使うと、クラスに演算子<、<=、==、>、>=と、メソッドbetween?およびclampが与えられます。クラスに必要なのは、オブジェクトが小さい場合、等しい場合、大きい場合に、それぞれ負の数、0、正の数を返す1つのメソッド<=>です:

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 は値を他の2つの値の間に保ちます。

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 は、わかりにくい NoMethodError で失敗する代わりに、< に対して ArgumentError を発生させ、== に対して false を返します。

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

  grams <=> other.grams
end

Weight.new(1) == 5     # false
Weight.new(1) < 5      # ArgumentError: comparison of Weight with 5 failed
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オンラインコンパイラ