Rubyの0は真になる

beanz (2008年11月17日 00:55) | コメント(0) | トラックバック(0)

前回のエントリーではPythonを使い、辞書の値でソートする方法について書いたが同様の事を他の言語でやりたい時はどうすればいいか?
例によって名前と年齢のハッシュがあったとする。例えばPerlの場合、

my %smap = ('nakai' => 36, 'kimura' => 35, 'katori' => 31, 'kusanagi' => 34, 'inagaki' => 34);

こんなデータがあり、これを年齢でソートしてみる。
ざっとこんな感じだ。

for (sort {$smap{$a} <=> $smap{$b}} keys %smap) {
    print "$_ => $smap{$_}\n";
}

これで、

katori => 31
inagaki => 34
kusanagi => 34
kimura => 35
nakai => 36

という出力が得られる。
Perlのsort関数は比較用の関数やブロックを引数として渡す事が出来るがこれを省力してもデフォルトの動作が用意されている。
省略した場合は文字列としての比較となる。なのでたとえ数値だけからなる配列があったとしても、

print $_."\n" for (sort (1,2,6,3,7,9,4,33,5,55));
1
2
3
33
4
5
55
6
7
9

こう出力されてしまう。数値でソートしたければ先ほどの比較関数の部分に単純なブロックを追加してやればいい

print $_."\n" for (sort {$a <=> $b} (1,2,6,3,7,9,4,33,5,55));

こうすれば

1
2
3
4
5
6
7
9
33
55

と、こうなる。配列の要素一つ一つがローカル化された変数$aと$bに格納されそれぞれ比較されていくという感じだ。
上の例で

for (sort {$a <=> $b} keys %smap)

単純にこうしていたらハッシュ"%smap"の各エントリのキーでソートされる事になる、しかも上の場合は数値でソートしようとしているので多分警告文が出力されてしまうだろう(use warningsしていた場合)。

for (sort {$smap{$a} <=> $smap{$b}} keys %smap)

とすればキーではなく値でソートしてくれる。
さらに比較用のブロックはもっと複雑な事も書ける。
例えば年齢でソートした後同じ年だった場合は名前の長さでソートするみたいな事だ。
これはこう書ける。

for (sort {$smap{$a} <=> $smap{$b} || length($a) <=> length($b)} keys %smap)

ここではデータの数が少ないのでいまいち分かりにくいが、

length($a) <=> length($b)

この部分を

length($b) <=> length($a)

こう書くだけで'kusanagi'と'inagaki'の順番が入れ替わるはずだ。
Perlのこの比較用関数を使って処理する考え方はそのままPythonでも使える。
前回は辞書のキーと値を一時的に入れ替えるような形を使ったりsorted関数のkeyオプションを使ったりしたが今回は以下のような書き方になる。

smap = {'nakai': 36, 'kimura': 35, 'katori': 31, 'kusanagi': 34, 'inagaki': 34}
for i in sorted(smap, lambda x,y: cmp(smap[x], smap[y])): print "%s => %d" % (i, smap[i])

Pythonの場合は数値だろうと文字列だろうcmp関数を使えばいい。
同じく年齢が一緒だった場合名前の長さでソートするには

for i in sorted(smap, lambda x,y: cmp(smap[x], smap[y]) or cmp(len(y), len(x))): print "%s => %d" % (i, smap[i])

こうすればいい。ここではx,yという変数にしているが別にここをa,bにしても全然問題ない。ちなみにPerlの場合は$aと$bは決まってるみたい。
Perlに比べるとPythonは(誰が書いても同じようなコードになりやすい)というが、あながちそうも言えないような気がしてきた。
もちろんPerlほど自由ではないにしろ。

Rubyのハッシュを値でソートするにはどうすればいいか?
Rubyのハッシュクラスにはsortメソッドが用意されている。が実際ソートを行うのはArrayクラスのsortメソッドだ。
ハッシュの各エントリをそれぞれのキーと値を要素にした配列に変換し入れ子になった配列を作り、その配列に対してsortメソッドを呼び出す。
例えば

smap = {'nakai' => 36, 'kimura' => 35, 'katori' => 31, 'kusanagi' => 34, 'inagaki' => 34}

このハッシュに

smap.sort

とすると

[["inagaki", 34], ["katori", 31], ["kimura", 35], ["kusanagi", 34], ["nakai", 36]]

こんな二重配列になって帰ってくる。
この場合はハッシュのキー(入れ子になった配列の最初の要素)でソートが行われている。
この動作をかえたい場合(値でソートしたい)はさらにブロックを指定して

smap.sort {|x, y| x[1] <=> y[1]}

とすればいいだけだ。
これで

[["katori", 31], ["kusanagi", 34], ["inagaki", 34], ["kimura", 35], ["nakai", 36]]

こうなって帰ってくる。出力したければ

smap.sort {|x, y| x[1] <=> y[1]}.each {|k, v|puts "#{k} => #{v}"}

一行で書いてしまうとこうなる。
ただしRubyの場合、PerlやPythonの時のような||やorを使ったやり方はうまくいかない。

smap.sort {|x, y| x[1] <=> y[1] || y[0].length <=> x[0].length}

上の文は例え右側の式のxとyを入れ替えても同様の結果になる。要は右の式は一切機能していない。
比較関数が返す値は最初の要素が小さければ-1、大きければ1、等しい時は0を返すようになっているが、PerlやPythonの時は0を偽と判断して右側の式を実行するようになっている。Rubyは0も真として扱うため右の式が実行される事はないという事だ。

smap.sort {|x, y|(x[1] <=> y[1]) != 0 ? x[1] <=> y[1] : x[0].length <=> y[0].length}

こう書くと同様の結果が得られる。まああんまりきれいじゃないが。

カテゴリ:

トラックバック(0)

トラックバックURL: http://blog.beanz-net.jp/beanz_mtos/mt-tb.cgi/22

コメントする