ぶれすとつーる

だいたいjavascript

rubyのArray#sortの挙動が怪しい件(あやしくなかった)

この挙動は別段問題なくて単純にコードブロックの評価順序によるものでした。
末尾に詳細を説明してますのでそちらを参照ください。

元記事

Array#sortをブロック渡しで実行したときの挙動が変

# case 1
ary = ["1", "2", "10", "11", "3", "4", "23"]
p ary.sort
# result: ["1", "10", "11", "2", "23", "3", "4"]


#case 2
ary = ["1", "2", "10", "11", "3", "4", "23"]
p ary.sort do |a, b|
  a.to_i <=> b.to_i
end
# result: ["1", "10", "11", "2", "23", "3", "4"]


#case 3
ary = ["1", "2", "10", "11", "3", "4", "23"]
p ary.sort! do |a, b|
  a.to_i <=> b.to_i
end
# result: ["1", "10", "11", "2", "23", "3", "4"]


#case 4
ary = ["1", "2", "10", "11", "3", "4", "23"]
ary.sort! do |a, b|
  a.to_i <=> b.to_i
end
p ary
# result: ["1", "2", "3", "4", "10", "11", "23"]


#case 5
ary = ["1", "2", "10", "11", "3", "4", "23"]
ary2 = ary.sort do |a, b|
  a.to_i <=> b.to_i
end
p ary2
# result: ["1", "2", "3", "4", "10", "11", "23"]

case1は多分文字コードの出現順(だと思う)だから先頭一文字目の評価が優先されて

["1", "10", "11", "2", "23", "3", "4"]

となるのは正しいと思う。


さらにcase 4,5も評価方法をintにキャストして比較するように命令してるので

["1", "2", "3", "4", "10", "11", "23"]

となるのは正しいと思う。


問題はcase 2,3
こいつらもcase 4,5と同じように

["1", "2", "3", "4", "10", "11", "23"]

となることを期待してたんだけど得られた期待値は違った。

バグ? 僕の知らない仕様?


僕はjserで、rubyは今日からはじめたので正直よくわかってないけどjsとかでも文字列型の数値を要素にもった配列のsortは注意しないといけないところなのでrubyではどうなってるかなとおもってやってみたら闇にはいりこんでしまった。


ちなみにこの結果を確認した環境
ruby 1.9.2p180 (rev 30909) [x86_64-linux]
ruby 1.8.7 (2009-06-12 patchlevel 174) [universal-darwin10.0]

冒頭で追記したとおり、この現象はコードブロックの優先度の問題でした。

do..endの評価時の優先度が低くpの引数として扱われているようです。

コード的には以下のように解釈されている。

p(ary.sort) do |a, b|
  a.to_i <=> b.to_i
end

ただしくコードブロックを適用させたsortの結果をdo..endを用いて評価させるのであればcase5のように一旦評価結果を変数に代入し、その後にpで評価する、もしくは以下のように優先度を考慮して書く必要があるようです。

p(ary.sort do |a, b|
  a.to_i <=> b.to_i
end)

あと、do..endよりも{}ブロックのほうが優先度が高いので、そもそもdo..endを使わずに{}ブロックを用いる事でも解決できるよです。 っていうか普通はそっち使えって話らしい。

p ary.sort {|a, b|
  a.to_i <=> b.to_i
}

skypeでいろいろ調査してくださったまだいさんとredmineのほうで手厚くご指導してくださったno6vさんに感謝。

http://bugs.ruby-lang.org/issues/7182