プログラムを書いていると、ときどき「過去・現在・未来」とか「赤字・とんとん・黒字」のような3方向への分岐が必要になる場面があります。Rubyでスッキリ書くにはどうすればいいのでしょうか。
素直に書いてみる
何も考えずに書くと、次のようになります。
if foo == bar
# 等しい時
elsif foo > bar
# fooのほうが大きい時
else
# fooのほうが小さい時
end
もちろんこれでも動くのですが、同じ値での比較を2度も書いていて、あまりきれいではありません(この例ではfoo
やbar
だからどうということもないのですが、オブジェクトのプロパティなどややこしい式を二度も書くのは、正直めんどうです)。
UFO演算子
ここで、Rubyには「UFO演算子」とも呼ばれる<=>
という演算子があります。これは、両辺の値を比較して「左が大きい」「等しい」「右が大きい」というごとに違う値を返します。で、よくそれぞれ「1」「0」「-1」を返すと言われるのですが、この演算子の最大の利用者であるComparable
のマニュアルによれば「正の整数」「0」「負の整数」であればいいということで、決まった値は保証されていません。というわけで、UFO演算子を使って書き直すと
comp = (foo <=> bar)
if comp == 0
# 等しい時
elsif comp > 0
# fooのほうが大きい時
else
# fooのほうが小さい時
end
多少はすっきりしましたが、一時変数を使わずにできないものでしょうか。
無限の彼方へ
Rubyのcase
は、単純な一致判定だけではなく、===
演算子を使った比較をしてくれるので、いろんな操作をできます。「正か0か負か」ということで、こんなこともできなくはないです。
case foo <=> bar
when 0
#等しい時
when 1.0..Float::INFINITY
# fooのほうが大きい時
else
# fooのほうが小さい時
end
UFO2機
ここで改めてマニュアルを見てみると、Numeric#<=>
は(正常に比較できる場合)0、±1を返してくれるとのことでした。ということで、UFOを2つ連ねてしまえば、強制的に値を0、±1のどれかにしてしまうこともできます。
case (foo <=> bar) <=> 0
when 0
#等しい時
when 1
# fooのほうが大きい時
else
# fooのほうが小さい時
end
その他
今までのコードでは省略していましたが、比較不能な場合には<=>
がnil
を返すこともあります。数値や文字列、日付といった値で問題となることはありませんが、nil
が混入するなど型が揃わない場合や、Module
などのように全順序が決まらない場合もあるので気をつけましょう。