前回はハッシュを用いたアンケートの処理を行った。通常アンケートを行う場合、質問項目が多くなる傾向にある。これを配列で処理しようとした場合、質問の数だけ配列を準備する必要が生じる。しかし、ハッシュを用いれば1つで済み、プログラムが煩雑になることを避けることができる。
前回の課題は、アンケートを行ったうえで、1番好きなものを3点、2番目に好きなものを2点、3番目に好きなものを1点として結果を集計するというものであった。このプログラムをそのまま掲載すると前回の課題の答えになってしまうため、若干内容の異なる以下のプログラムを見てみよう。
これは、ある大学で行われたソフトボール大会の結果である。6チームで136試合を行い、最も勝率が高いチームが優勝となる(sotfball.txt)。
チーム 勝 負 引 アンツ 75 56 5 スネイルズ 47 85 4 ドラゴンフライズ 80 54 2 モスキートーズ 65 70 1 クリケッツ 52 81 3 ブックウォームス 82 54 0
チーム名をkey、勝数、負数、引分数をvalueとしてハッシュ変数softに代入し、勝率を求めて結果を表示するプログラムを書いてみると以下のようになる(softball.rb)
#!/usr/koeki/bin/ruby soft = Hash.new while line = gets if /(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/ =~ line # 1個目の() (\S+)→チーム名が入る # 2個目の() (\d+)→勝ち数が入る # 3個目の() (\d+)→負け数が入る # 4個目の() (\d+)→引分数が入る soft[$1] = [$2.to_f,$3.to_f,$4] # 配列を代入 end end print "--チーム名------------+-勝ち-+-負け-+-引分--+-勝率---\n" for team, data in soft # data には、[勝ち数, 負け数, 引分数] という配列が入っている printf("%-20s %6d %6d %6d %9.3f\n", team, data[0], data[1], data[2], data[0]/(data[0]+data[1])) # dataの第0要素が勝ち数、第1要素が負け数、第2要素が引分数 end
irsv{c10xxxx}% ruby soft.rb softball.txt --チーム名------------+-勝ち-+-負け-+-引分--+-勝率--- ブックウォームス 82 54 0 0.603 クリケッツ 52 81 3 0.391 スネイルズ 47 85 4 0.356 ドラゴンフライズ 80 54 2 0.597 モスキートーズ 65 70 1 0.481 アンツ 75 56 5 0.573
結果を表示すると確かに勝率が追加されている。しかし、通常勝敗を示す場合は勝率が高い順に表示したいという気持ちになる。ハッシュの場合、結果を表示する順番は代入した順番にすらならない。値を格納するという点ではハッシュは便利であるが、結果を表示する上では使い勝手が悪いといえる。
ハッシュにおける結果の並び替えを考えるにあたり、まず配列の並び替え方法を確認しよう。
例えば、a=[192,168,0,255]という配列を仮定する。この配列内の4つの値を昇順(小さい順)に並び替えることを考えてみる。この場合、配列処理メソッドのsortを使用すればよい。sortは昇順(小さい順)に並び替えるメソッドである。
b = a.sort
とすることで、配列aの4つの値を小さい順に並び替えた配列bが完成する。すなわち配列bは、b=[0,168,192,255]となっている。
今度は配列内の値を降順(大きい順)に並び替えることを考えよう。この場合はsortメソッドに加えてreverseメソッドを使用する。reverseメソッドは配列内の値を逆順にするメソッドである。
b = a.reverse
とすると、配列aの中の値が逆順に並び替えられ、b=[255,0,168,192]となる。このままでは降順ではないが、sortメソッドと組み合わせればよい。まずsortメソッドで昇順に並び替えてから、reverseメソッドで逆順に並び替えると降順にした結果が得られる。すなわち下記のように書く。
b = a.sort.reverse
これにより、配列bはb=[255,192,168,0]となる。b=a.reverse.sortのように順番を逆にすると昇順になってしまうので気をつけよう。
reverseメソッドと似たような働きをするメソッドとしてreverse!メソッドがある。sortメソッドにも!をつけたsort!メソッドがある。これまで頻繁に使用してきたchomp!メソッドにも!のないchompメソッドがある。!の働きとはいったいなんであろうか。以下のプログラムを実行して、!の働きを想像してみよう。
#!/usr/koeki/bin/ruby a = [9,5,2,6,1] b = [8,4,1,3,7] x = a.sort y = b.sort! p x p y p a p b
制限時間は10分。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
sortメソッドは、ソート基準を決めるブロックを付加することができる。具体的には以下のように書き、{ }内がソート基準ブロックに相当する。
配列.sort{|x,y| xとyの比較式}
ソート基準ブロックを用いて昇順(小さい順)、降順(大きい順)に並び替えを行う場合、それぞれ以下のように書く。
|a, b|は、配列内の値(要素)を順次a、bに代入し左側をa、右側をbとするという意味である。<=>は宇宙船演算子とも呼ばれ、本来の働きは左辺が大きければ1、等しければ0、右辺が大きければ-1を返すというものである。ここでは、右が大きければ順番はそのまま、左が大きければ順番を入れ替えるという働きをする。これを配列内の要素について繰り返し行うことで、最終的に右側が大きくなる。昇順の場合は| |内と後ろのabの順が同じであるが、降順の場合は順番が異なっている。順番を入れ替えることで右側に大きな値を入れると、結果的に大きい順に並ぶことになる。
ちなみに、単にsortとすると昇順ソートになるが、これはソート基準ブロックに {|a, b| a<=>b} を指定したことと同義になる。sortやsort.reverseを使用する場合と比べ、ソート基準ブロックを使用する方法は複雑であるが、ハッシュのソートを行う際は必要不可欠となる。
なお、ソート基準ブロックの中でa、bという2つの変数を使っているが、これはxとyでもjとkでも何でも構わない。
以下はテストの得点を示したものである。これを名前をkey、得点をvalueとするハッシュ変数testに代入し、得点の低い順に並び替えることを考えてみよう。
氏名 得点 一郎 72 二郎 48 三郎 96 五郎 33
まずハッシュ変数testに上記のデータを初期値として代入すると以下のようになる。
test = { "一郎" => 72, "二郎" => 48, "三郎" => 96, "五郎" => 33, }
ハッシュの並び替えを行う場合、keyを並べ替えることを考える。keyの並べ替えができれば、keyと対応するvalueを表示していけばよいことになる。ただし、keyを並べ替える際の基準はvalueとなる。ここではvalueの小さい順にkeyを並べておき、keyとvalueを対で表示するという方策が考えられる。
ハッシュからkeyのみを取り出すためには
ハッシュ.keys
とすればよい。keyではなくkeysであることに注意しよう。参考までにvalueのみを取り出す場合は
ハッシュ.values
となる。ここでもvalueではなくvaluesとなっている。
上記のハッシュ変数testからkeyのみを取り出してpメソッドで中身を確認してみよう。すると以下のように
p test.keys #=>["三郎","二郎","一郎","五郎"]
keyが配列として返される。順番はハッシュへの代入時とは異なっている。これをsortメソッドを使って並び替えよう。この際、並べ替えの基準は得点であるため、sortメソッドのソート基準ブロックではvalueを比較することになる。
p test.keys.sort {|x,y| test[x] <=> test[y]} #=>["五郎", "二郎", "一郎", "三郎"]
test[x]やtest[y]はxやyをkeyとするvalueに相当する。|x,y|とtest[x] <=> test[y]の部分でxとyの順番が同順であるため、valueに基づいて、keyが昇順に並べ替えられる。
これをプログラムとして書いたものが、以下のsort.rbである。for-endでハッシュの値を取り出す際にソート基準ブロックを用いて、valueが小さい順に取り出している。
#!/usr/koeki/bin/ruby test = { "一郎" => 72, "二郎" => 48, "三郎" => 96, "五郎" => 33, } for item in test.keys.sort{|a, b| test[a] <=> test[b]} printf("%s は %d点です。\n", item, test[item]) end
実行結果は下記の通りとなる。
% ruby sort.rb 五郎は33点です。 二郎は48点です。 一郎は72点です。 三郎は96点です。
本日の冒頭で取り上げたソフトボールの結果を、勝ち数の多い順に並べ変えてみよう。今度はsort.rbとは異なりvalueが配列になっている。配列内には勝ち数、負け数、引分数の3つの値が代入され、1つのkeyに結び付けられている。並び替えを行う場合には、sort.rbと同様にソート基準ブロックを用いるが、3つあるvalueのうちの勝ち数に基づいてソートをするということで若干書き方が異なってくる。
具体的には以下のように書く。
soft.keys.sort {|a, b| soft[b][0] <=> soft[a][0]}
ここで変数a、bにはkeyであるチーム名が入る。soft[a]やsoft[b]はkeyに対応するvalueである。valueは3つの値を持ち、第0要素の勝ち数を基準とするため、soft[a][0]やsoft[b][0]として勝ち数を指定している。なお|a, b|と後半のa、bの順番が逆であるため降順に並べ替えが行われる。
これを踏まえてプログラムを書き直すと以下の通りとなる(softball2.rb)。
#!/usr/koeki/bin/ruby soft = Hash.new while line = gets if /(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/ =~ line # 1個目の( ) (\S+)→チーム名が入る # 2個目の( ) (\d+)→勝ち数が入る # 3個目の( ) (\d+)→負け数が入る # 4個目の( ) (\d+)→引分数が入る soft[$1] = [$2.to_f,$3.to_f,$4] # 配列を代入 end end print "--チーム名------------+-勝ち-+-負け-+-引分--+-勝率---\n" for team in soft.keys.sort {|a, b| soft[b][0] <=> soft[a][0]} printf("%-20s %6d %6d %6d %9.3f\n", team, soft[team][0], soft[team][1], soft[team][2], soft[team][0]/(soft[team][0]+soft[team][1])) end
このプログラムを実行すると以下のように、勝ち数の大きい順に結果が出力される。
ruby softball2.rb softball.txt --チーム名------------+-勝ち-+-負け-+-引分--+-勝率--- ブックウォームス 82 54 0 0.603 ドラゴンフライズ 80 54 2 0.597 アンツ 75 56 5 0.573 モスキートーズ 65 70 1 0.481 クリケッツ 52 81 3 0.391 スネイルズ 47 85 4 0.356
以下のうちいずれかを選んで解答する。
問題1(7点満点):softball2.rbを改良し勝率の高い順に結果を表示できるようにする(softball.txtには手を加えない)。
問題2(8点満点):softball2.rbをベースに勝ち3点、引き分け1点、負け0点として、これらを合計して算出した勝ち点の大きい順に結果を表示できるようにする(softball.txtには手を加えない)。例えば5勝3敗2引き分けの場合、勝ち点は5(勝)×3(点)+2(引き分け)×1(点)=17点となる。
問題3(10点満点):前回のレポート課題であるkadai04(の問題2もしくは3)において、1位を3点、2位を2点、3位を1点として結果を集計したが、集計結果を表示する際に得点が大きい順になるようにせよ。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照