Rubyの<=>演算子とsortメソッド
概要
player1 = Player.new("moe")
player2 = Player.new("larry",60)
player3 = Player.new("curly", 125)
@players = [player1, player2, player3]
上記では、いくつかのプレーヤー オブジェクトを作成し、それらを以前は空だった配列 @players に追加しました。
次に、 <=> を次のように再定義しました。
def <=>(other)
other.score <=> score
end
その後、このコードを実行できます
@players.sort
@players 内のプレーヤー オブジェクトの配列は、高スコアから低スコアの順に並べ替えられます。 これは私には少しブラックボックスのように見えると思います。ここで何が起こっているのか少しわかりません。舞台裏で何が起こっているかを知るにはどうすればよいですか?
私が知っているのは、2 つの値をとり、宇宙船演算子/一般比較演算子を使用すると次のようになることだけです。
2 <=> 1
=> 1
1 <=> 2
=> -1
1 <=> 1
=>0
時々、Ruby では、私がプログラミングしている高レベルでは見ることができない、多くの下位レベルの処理が行われているように見えます。これは自然なことのように思えます…しかし、このケースは特に、下位レベルの処理からは切り離されているように見えます。ソート方法。ソートは宇宙船オペレーターをどのように使用しますか?なぜ今までと同じように宇宙船のオペレーターを再定義することで、オブジェクトを分類できるようになったのでしょうか?
解決策
オブジェクトの並べ替えを理解する前に。 Ruby の .sort メソッドを理解する必要があります。数字が書かれた 5 枚のカードを並べ替える場合、すべてのカードを見て、最も低いカードを簡単に見つけて、そのカードを最初のカードとして選ぶことができます (最も低いカードから最も高いカードへ並べ替えていると仮定すると、どれがルビーになります)します)。脳は分類するとき、あらゆるものを見てそこから分類することができます。
ここには、めったに取り上げられない、混乱を招く主な要素が 2 つあります。
宇宙船の操縦者を見てください。これは、<、=、> という 3 つの個別の演算子を 1 つにまとめたものです。 Ruby が 2 つの変数を比較すると、結果は次のいずれかの数値になります。
[宇宙船オペレーター][1]
では、「結果」とは何を意味するのでしょうか?これは、変数の 1 つに 0、1、-1 が割り当てられるという意味ではありません。これは単に、Ruby が 2 つの変数を受け取り、それらを使って何かを実行できる方法です。ここで、次のように実行すると、
puts 4 <=> 5
比較演算子 (宇宙船) の「部分」 (<、=、> など) が true であれば、それに割り当てられた番号が取得されるため、結果は -1 になります (上の図を参照)。 。ただし、Ruby が配列でこの <=> を認識した場合、配列に対してのみ 2 つのことを行います。配列をそのままにするか、配列の要素を交換します。
Ruby が <=> を使用して 1 を取得すると、配列の 2 つの要素が交換されます。 Ruby が -1 または 0 の結果を取得した場合、配列はそのままになります。
例としては、Ruby が配列 [2,1] を認識した場合です。ソート方法では、2<=>1 のようにこれらの数値を取得します。宇宙船の部分 (そのように考えたい場合) が真であるのは > であるため (つまり、2>1 が真)、Ruby からの結果は「1」になります。 Ruby は宇宙船から 1 の結果を確認すると、配列の 2 つの要素を交換します。現在の配列は [1,2] です。
この時点で、Ruby は <=> 演算子とのみ比較し、比較する配列内の 2 つの要素を交換する (またはそのままにする) ことがお分かりいただけたと思います。
.sort メソッドは反復メソッドであり、コードのブロックを何度も実行するメソッドであることを理解してください。ほとんどの人は、.each や .upto などのメソッドを見た後でのみ .sort メソッドを紹介します (聞いたことがない場合は、これらのメソッドが何をするのか知る必要はありません)。しかし、これらのメソッドは最後まで実行されます。配列は 1 回のみ。 .sort メソッドは、必要な回数だけ配列を実行して並べ替えられる (並べ替えとは、比較して交換するという意味です) という点が異なります。
Ruby の構文を確実に理解するには:
foo = [4, 5, 6]
puts foo.sort {|a,b| a <=> b}
コードのブロック ({} で囲まれた部分) は、Ruby が最低位から最高位にソートするときに実行するものです。ただし、.sort メソッドの最初の反復では、パイプ (a、b) 間の変数を配列の最初の 2 つの要素に割り当てると言うだけで十分です。したがって、最初の反復では a=4 および b=5 であり、4<5 であるため、結果は -1 になります。Ruby は、これを配列を交換しないことを意味すると解釈します。これを 2 回目の反復で実行します (a=5 および b=6 を意味します)。5<6 であることが確認され、結果は -1 となり、配列はそのままになります。 <=> の結果はすべて -1 だったので、Ruby はループを停止し、配列が [4,5,6] でソートされたと感じます。
変数の順序を入れ替えるだけで、高い順から低い順に並べ替えることができます。
bar = [5, 1, 9]
puts bar.sort {|a,b| b <=> a}
Ruby がやっていることは次のとおりです。
反復 1: 配列 [5,1,9]。 a=5、b=1。 Ruby は b<=>a を見て、1 < 5? と言いました。はい。その結果は -1 になります。そのまま。
反復 2: 配列 [5,1,9]。 a=1、b=9。 Ruby は b<=>a を見て、9 < 1? と言いました。いいえ。結果は 1 になります。2 つの配列要素を交換します。配列は [5,9,1] になりました。
反復 3: 配列 [5,9,1]。すべてを実行する前に、配列に +1 の結果があったことを最初からやり直します。 a=5、b=9。 Ruby は b<=>a を見て、9<5 ですか?いいえ。その結果は 1. スワップになります。 [9、5、1]
はい。その結果は -1 になります。反復 4: 配列 [9,5,1]。 したがって、スワップは実行されません。a=5、b=1。 終わり。 Ruby は b<=>a を見て、1<5 ですか?[9,5,1]。
最初の 999 要素が 50、要素 1000 が 1 である配列を想像してください。Ruby が同じ単純な比較と交換ルーチンを実行してこの配列を何千回も調べてシフトする必要があることがわかれば、ソート方法を完全に理解できるでしょう。それは [1] 配列の先頭までです。
これで、オブジェクトに関してついに .sort を確認できるようになりました。
def <=>(other)
other.score <=> score
end
これでもう少し意味がわかるはずです。以下を実行したときなど、オブジェクトに対して .sort メソッドが呼び出されたとき。
@players.sort
@players からの現在のオブジェクトを持つパラメーター (例: 「other」) を使用して「def <=>」メソッドを呼び出します (例: 「現在のインスタンス オブジェクトが ‘@players’ のものであれば何でも、これはソート メソッドであるため)」 、最終的には ‘@players’ 配列のすべての要素を通過することになります)。これは、クラスに対して put メソッドを実行しようとすると、そのクラス内の to_s メソッドが自動的に呼び出されるのと同じです。 .sort メソッドでも同じことが自動的に <=> メソッドを探します。
<=> メソッド内のコードを見ると、そのクラスには .score インスタンス変数 (アクセサー メソッドを含む) または単に .score メソッドが存在する必要があります。そして、その .score メソッドの結果は (できれば) 文字列または数値である必要があります。Ruby が「ソート」できる 2 つのものです。それが数値の場合、Ruby は <=> ‘sort’ 操作を使用して、これらのオブジェクトのすべての部分を並べ替えます。これで、オブジェクトのどの部分を並べ替えるかがわかります (この場合、それは .score メソッドまたはインスタンス変数の結果です)。 )。
最後に、Ruby は数値に変換することによってアルファベット順にソートします。あらゆる文字に ASCII のコードが割り当てられていると見なすだけです (つまり、ASCII コード表では大文字の数値が小さいため、デフォルトでは大文字が先頭にソートされます)。
お役に立てれば! [1]: https://i.sstatic.net/CpwOf.jpg