webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

【Ruby】UFO演算子を定義して、sortやminメソッドを使えるようにする

こんにちは、さもです。

UFO演算子というのがRubyにあるのはご存知でしょうか?

<=> こういうやつです。比較演算子をまとめたような「<=>」が一つの演算子になっています。

RubyではこのUFO演算子を任意のクラスで定義しておくと、そのクラスのインスタンスからなる配列に対して、minやsortなどのメソッドが使えるようになります。

今回はこのUFO演算子を定義してみます。また、Comparableモジュールをインクルードしてインスタンス同士を比較可能にしてみます。

スポンサーリンク

目次

UFO演算子を定義していない場合

例えば以下のようなStudentクラスがあったとします

class Student
  attr_accessor :class_num, :num

  def initialize(class_num, num)
    @class_num = class_num
    @num = num
  end
end

小学校の生徒をイメージしてみてください。

class_numはクラスの番号で、numは出席番号だとします。

このとき、Studentのインスタンスの配列をクラス番号優先で並べ替えを行いたいと思います。

どういうことかというと、クラス番号が1、出席番号が10の生徒Aと、クラス番号が2、出席番号が3の生徒Bと、クラス番号が1、出席番号が1の生徒Cがいる場合には、まずクラス番号で並べ替えてから、次にクラスの中で出席番号順に並べ替えます。

結果は、C,A,Bの順になります。

a = Student.new(1,10)
b = Student.new(2,3)
c = Student.new(1,1)
students = [a,b,c]

実装は例えば、クラス内に高々50人しかいないとすると、

students.sort_by{|student| student.class_num * 100 + student.num}

こんな感じでしょうか。ブロックの戻り値を比較可能なオブジェクト(今回は整数)にするとブロックの戻り値でソートしてくれます。

でも、いたるところでソートを行いたいときは毎回書いていられないですし、できれば、students.sortと書けた方がかっこいいですよね。

残念ながら現状ではstudents.sortとするとArgumentError: comparison of Student with Student failedというエラーが返されます。

こんなときはUFO演算子を定義しましょう

UFO演算子を定義する

先ほどのクラスにメソッドを追加します

class Student
  attr_accessor :class_num, :num

  def initialize(class_num, num)
    @class_num = class_num
    @num = num
  end

  def <=>(other)
    return -1 if self.class_num < other.class_num
    return 1  if self.class_num > other.class_num
    return -1 if self.num < other.num
    return 1  if self.num > other.num
    0
  end
end

少し冗長な書き方ですが、先ほどのように上限が50人みたいな仮定を取って実装してみました。

UFO演算子は、自分(self)と相手(other)とを比較したときに、

  • self<other(並べ替えたときにselfの方が前に来る)であれば-1
  • self>other(並べ替えたときにselfが後ろに来る)であれば1
  • self==other(selfとotherは同順)であれば0を返す

のように実装するというルールがあります。

<,=,>と-1,0,1が同じ順番なので覚えやすいと思います。

そうすると、なんと、

a = Student.new(1,10)
b = Student.new(2,3)
c = Student.new(1,1)
students = [a,b,c]

のとき、students.sortが並べ替えられた生徒の配列を返してくれます。

students.sort
#=> [#<Student:0x007f7d99dbb758 @class_num=1, @num=1>, #<Student:0x007f7d9a00a270 @class_num=1, @num=10>, #<Student:0x007f7d99ed4978 @class_num=2, @num=3>]

さらに、students.maxとすると、並べ替えたときの一番後ろの生徒、students.minとすると、並べ替えたときの一番前の子を返してくれます。

Comparableモジュールをインクルードする

UFO演算子が定義されているクラスにComparableモジュールをインクルードすると、インスタンス同士の比較が行えるようになります。

インクルードする前では、

a < bとしても、< が未定義だというエラーが返されます。

そこで、StudentクラスにComparableモジュールをインクルードします。

class Student
  include Comparable

  attr_accessor :class_num, :num

  def initialize(class_num, num)
    @class_num = class_num
    @num = num
  end

  def <=>(other)
    return -1 if self.class_num < other.class_num
    return 1  if self.class_num > other.class_num
    return -1 if self.num < other.num
    return 1  if self.num > other.num
    0
  end
end

すると、a < bが自分で定義したUFO演算子に応じてture,falseを返してくれるようになります。

 a < b
#=> true

< の他にも >やbetween?メソッドなど使えるようになります。

詳しくはこちら

最後に

いかがでしたか?

自分でメソッドを定義したり、既存のクラスをより使いやすくしたりするのもオブジェクト指向、Rubyの醍醐味だと思います。

実際の仕事でUFO演算子を定義したことはないのですが、覚えておくといざと言うとき使えそうですね。

以上、UFO演算子のご紹介でした。

読者登録はこちらからお願いします。