こんにちは、さもです。
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演算子のご紹介でした。
読者登録はこちらからお願いします。