【Ruby】宇宙船演算子(<=>)でArray同士を比較する方法

Ruby

こんにちは、しきゆらです。
今回は、Rubyの比較演算子(<や>など)の元になっている「宇宙船演算子」やArray同士の比較について調べたのでメモしておきます。

調べたきっかけ

表題の件を調べたきっかけをつらつら書いていきます。
不要であれば、次の節まで移動してください。

ここ最近、体調が悪く何もできていません。
こんな時期が長くなると、頭が全く回らなくなります。(n回目)
そのリハビリとして、競技プログラミングをやってみようと思い数回参加してみました。

一般的にはC++やPythonで解説されていることが多いので、これらの言語を使うのが良いとされているようですが
頭を回すためのリハビリなので、普段使っているRubyで挑戦してみました。
記載時点で直近のABC468のC問題で、辞書順比較をする問題がありました。

この問題について、標準入力から得た値をArrayに変換してArray同士を比較すれば良いのかな、ということで雑にコードを書いてみましたがエラー。
そういえばArray同士の比較って書いたことないかも、ということで調べてみたのが今回の話です。

p = gets.strip.split.map(&:to_i)
q = gets.strip.split.map(&:to_i)
...
p < target && target < q
# undefined method '<' for an instance of Array (NoMethodError)
# Did you mean?  <<

Rubyの比較演算子

Rubyの大抵のクラスでは比較演算子(<や>など)が定義されています。
これらの比較演算子は、個別に定義されているのではなく、宇宙船演算子(<=>)と呼ばれるものが元になっています。
というのも、宇宙船演算子を定義したクラスがComparableモジュールをインクルードすると、<や>のような比較演算子やbetween?などのメソッドが使えるようになっています。

例えば、Numericクラスの場合、宇宙船演算子がインスタンスメソッドとして定義されており、Comparableモジュールから継承しているメソッドとして比較演算子が定義されています。

宇宙船演算子(<=>)の呼び名

この演算子ですが、見た目から宇宙船演算子と呼ばれているようです。
(調べてみたら、Wikipediaのページもありました)

その他、UFO演算子とか三方比較演算子と呼ばれることがあるようですが
Ruby用語集で「宇宙船演算子(spaceship operator)」と記載されているので、ここでは宇宙船演算子と表記します。

Arrayの比較

では、本題のArrayの比較についてみてみます。
メソッド一覧をみてみると、宇宙船演算子は定義されていますがComparableモジュールはインクルードされていないようです。

おそらくですが、Arrayの比較となると要素数や合計値での比較のほか、要素の並び順など複数の方法で比較することが考えられるため、明示的な比較演算子が定義されていないのではないか、と思っています。

それでは、Arrayの宇宙船演算子の定義をみてみます。
https://docs.ruby-lang.org/ja/latest/method/Array/i/=3c=3d=3e.html

自身と other の各要素をそれぞれ順に <=> で比較していき、結果が 0 でなかった場合にその値を返します。各要素が等しく、配列の長さも等しい場合には 0 を返します。各要素が等しいまま一方だけ配列の末尾に達した時、自身の方が短ければ -1 をそうでなければ 1 を返します。 other に配列以外のオブジェクトを指定した場合は nil を返します。

  • 自分と対象の各要素を、それぞれの順番で宇宙船演算子を使って比較する
  • 比較した結果が0でない場合にその値を返す
  • 長さ・各要素が等しければ0を返す
  • 各要素が等しく、一方だけが配列の末尾に達した時、自分が短ければ-1、そうでなければ1を返す

ドキュメントをざっくりまとめてみると、各要素を宇宙船演算子で比較して、結果が0でなければその値を返すとのことです。
自分が小さければ−1、相手が小さい時は1を返します。
サンプルコードは以下の通り。

p [ 1, 2, 3 ] <=> [ 1, 3, 2 ]     #=> -1
p [ 1, 2, 3 ] <=> [ 1, 2, 3 ]     #=> 0
p [ 1, 2, 3 ] <=> [ 1, 2 ]        #=> 1

サンプルでは、要素がNumericクラスのインスタンスなので単純な数値の大きさで比較しています。
1行目の例では、2要素目が2 <=> 3の結果が-1なので全体の結果が-1となっていますね。

irb(main):001> %w[a b c] <=> %w[a c b]
=> -1
irb(main):002> %w[a b c] <=> %w[a b c]
=> 0
irb(main):003> %w[a b c] <=> %w[a b]
=> 1

要素数について、自分の方が小さい場合に-1・相手が小さい場合に1を返す点についてもメモしておきます。
これも辞書順のように考えれば覚えやすいかと思います。

文字列"abc""ab"について考えてみると、"abc"の方が後ろにあるので1を返し
文字列"ab""aba"についても同様に、"ab"の方が前にあるので-1を返しています。

irb(main):001> %w[a b c] <=> %w[a b]
=> 1
irb(main):002> %w[a b] <=> %w[a b a]
=> -1

他のクラスで定義されている宇宙船演算子も、基本的には想定されているような大小関係を表す形で定義されていることを覚えておけば、Array同士の比較については理解できるかと思います。

まとめ

今回は、Rubyの比較演算子のもとになっている宇宙船演算子(<=>)とArray同士の比較について調べてまとめてみました。
個人的な理解なので、ミス等があれば指摘いただければ幸いです。

今回は、ここまで。
おわり

Posted by しきゆら