roy > naoya > 基礎プログラミングII・情報表現[月1] > (6)ハッシュ[2]
ハッシュは配列のインデックスとは異なり、keyに任意の名称を与えることができる。また、単一のkeyに対して複数のvalueを結びつけて保存することが可能であり、配列の場合とは異なり取り扱う変数の数を減らすことができるという利点がある。
一方で問題点もあった。前回の授業でも多少触れたが、次のプログラムを見ながらもう一度確認してみよう。
これは、ある大学で行われたソフトボール大会の結果である。6チームで136試合を行い、最も勝率が高いチームが優勝となる(softball.txt)。
チーム 勝 負 引 アンツ 73 53 10 スネイルズ 52 82 2 ドラゴンフライズ 78 51 7 モスキートーズ 65 65 6 クリケッツ 50 78 8 ブックウォームス 75 48 13
チーム名を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("%s %d %d %d %f\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[Return] --チーム名------------+-勝ち-+-負け-+-引分--+-勝率--- ブックウォームス 75 48 13 0.610 クリケッツ 50 78 8 0.391 スネイルズ 52 82 2 0.388 ドラゴンフライズ 78 51 7 0.605 モスキートーズ 65 65 6 0.500 アンツ 73 53 10 0.579
結果を表示すると確かに勝率が追加されている。しかし、通常勝敗を示す場合は勝率が高い順に表示したい。ハッシュの場合、結果を表示する順番は代入した順番にすらならない。値を格納するという点ではハッシュは便利であるが、結果を表示する上では使い勝手が悪いという問題がある。
今日は、ハッシュを用いた際に結果を昇順(小さい順)や降順(大きい順)に並び替えて表示する方法について確認していこう。
softball.txtには6チームの結果が記載されているが、ここに2チーム追加しよう。チーム名・勝・負・引分数は自由に決めてよい。
その後、
printf("%s %d %d %d %f\n", team, data[0], data[1], data[2], data[0]/(data[0]+data[1]))
の行の%sや%dに幅指定の数字を追加し、上の実行結果と同じように結果をそろえて表示できるようにしてみよう。
制限時間は10分程度。出席点は2点。提出要領は下記の通り。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:Mewによるメールの送り方はMewコマンドを参照
ハッシュにおける結果の並び替えを考えるにあたり、まず配列の並び替え方法を確認しよう。
例えば、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のように順番を逆にすると昇順になってしまうので気をつけよう。
sortメソッドには、ソート基準を決めるブロックを付加することができる。具体的には次のように書く。{ }内がソート基準ブロックに相当する。
配列.sort{|x,y| xとyの比較式}
ソート基準ブロックを用いて昇順(小さい順)、降順(大きい順)に並び替えを行う場合、それぞれ以下のように書く。
|a, b|は、配列内の値(要素)を順次a、bに代入し左側をa、右側をbとするという意味である。<=>は宇宙船演算子とも呼ばれ、本来の働きは左辺が大きければ1、等しければ0、右辺が大きければ-1を返すというものである。ここでは、右が大きければ順番はそのまま、左が大きければ順番を入れ替えるという働きをする。これを配列内の要素について繰り返し行うことで、最終的に右側が大きくなる。昇順の場合は| |内と後ろのabの順が同じであるが、降順の場合は順番が異なっている。abの順番を入れ替えて右側に大きな値を入れると、結果的に大きい順に並ぶことになる。
ちなみに、単にsortとすると昇順ソートになるが、これはソート基準ブロックに {|a, b| a<=>b} を指定したことと同義になる。sortやsort.reverseを使用する場合と比べ、ソート基準ブロックを使用する方法は複雑であるが、ハッシュのソートを行う際は必要不可欠となる。
なお、ソート基準ブロックの中でa、bという2つの変数を使っているが、これはxとyでもjとkでも何でも構わない。
ソート基準ブロックの基本形
ここではa、bという2つの変数を使用しているが変数は任意の名称でよい。例えばxとyでも良いし、hogeとpiyoでもよい。
以下はテストの得点を示したものである。名前を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に名前、valueに得点が代入されているのでpメソッドでkeyを表示すると
p test.keys #=>["三郎","二郎","一郎","五郎"]
となる。順番はハッシュへの代入時とは異なっていることを確認しよう。次にkeyをvalueに基づいて昇順に並び替える。このためsortメソッドのソート基準ブロックではvalueを比較する。
p test.keys.sort {|x,y| test[x] <=> test[y]}
#=>["五郎", "二郎", "一郎", "三郎"]
test[x]やtest[y]はxやyをkeyとするvalueに相当する。test.keys.sortでハッシュ変数testのkeyを取り出して並べ替えを行おうとしている。一方、ソート基準ブロックの比較式ではtest[x]、test[y]というようにvalueが使用されている。こうすることでvalueに基づいてkeyを並び替えることが可能になる。なお、|x,y|とtest[x] <=> test[y]の部分でx、yの順番が同じであるため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
実行結果は下記の通りとなる。
irsv{c10xxxx}% ruby sort.rb[Return] 五郎は33点です。 二郎は48点です。 一郎は72点です。 三郎は96点です。
valueに基づいてkeyを並び替える
ただしhogeはハッシュ変数とする。
本日の冒頭で取り上げたソフトボールの結果を、勝ち数の多い順に並べ変えてみよう。今度は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("%s%d%d%d%f\n", team, soft[team][0], soft[team][1], soft[team][2], soft[team][0]/(soft[team][0]+soft[team][1])) end
このプログラムを実行すると以下のように、勝ち数の大きい順に結果が出力される。
irsv{c10xxxx}% ruby softball2.rb softball.txt[Return] --チーム名------------+-勝ち-+-負け-+-引分--+-勝率--- ドラゴンフライズ 78 51 7 0.605 ブックウォームス 75 48 13 0.610 アンツ 73 53 10 0.579 モスキートーズ 65 65 6 0.500 スネイルズ 52 82 2 0.388 クリケッツ 50 78 8 0.391
以下のうちいずれかを選んで解答する。
問題1(7点満点):softball2.rbをベースに勝ち3点、引き分け1点、負け0点として、これらを合計して算出した勝ち点の大きい順に結果を表示できるようにする(softball.txtは出席課題を行ったことにより8チームに増加している)。例えば5勝3敗2引き分けの場合、勝ち点は5(勝)×3(点)+2(引き分け)×1(点)=17点となる。結果を表示する際、printfの書式制御文字に数字をいれ、桁揃えして表示されるようにすること。
問題2(9点満点):課題2の問題2をハッシュを用いて書きなさい。
Tips:emacsでの日本語入力のオンオフはCtrl-oです
Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする
Tips:Mewによるメールの送り方はMewコマンドを参照