roy > naoya > 基礎プログラミングII・情報表現[月1] > (5)ハッシュ[1]

(5) 11/05の授業内容:ハッシュ[1]

ハッシュとは何か

配列ではインデックスを使用して値を順次代入したり、取り出したりすることができた。ハッシュも配列と似たような形態をとるが、ハッシュではインデックスのかわりにkeyを用いる。keyは通し番号ではなく、任意の値を取ることができる。以下は左側が配列の例、右側がハッシュの例である。

menu = Hash.new
menu["カレーライス"]=600
menu["ハンバーグ"]=800
menu["ロールキャベツ"]=750
menu["牛丼"]=400
menu["ラーメン"]=500
menu["田舎煮定食"]=650
menu = []
menu[0]=600
menu[1]=800
menu[2]=750
menu[3]=400
menu[4]=500
menu[5]=650

これはメニューと金額を示したものである。ハッシュの場合、keyを任意の値とすることができるため、カレーライスやハンバーグといった文字列をkeyとして、対応する金額を代入している。keyに対応する金額をvalueと呼ぶ。配列ではインデックスは通し番号となるため、金額のみが代入される形となり何の値段なのかを対応づけることができない。配列を用いて対応づけを考える場合、menuという配列以外に、例えばpriceという配列を設け、menu[0]にカレーライスを代入し、price[0]に600を代入するという作業が必要となる。

まずはハッシュの特徴と用法についてまとめてみよう。

ハッシュの特徴と用法

  1. ハッシュ:任意の値に任意の値を結びつけて保存をすることができる変数。
  2. menu = Hash.newmenuがハッシュ変数であることを宣言。
  3. menu["カレーライス"]=600menuの持つハッシュの"カレーライス"に対応する値として600を代入している。配列のインデックスに相当するものをハッシュではkey(キー)と呼び、keyに対して結び付けられたデータをvalue(バリュー)と呼ぶ。

ハッシュへの初期値の与え方

以下のデータをハッシュの初期値として与えておくことを考えよう。

key

value

チーズラーメン

600

納豆ラーメン

700

キムチラーメン

780

まぐろラーメン

800

牛乳ラーメン

500

配列の場合は、gakushoku=[350,350,250,250,120,450]というように両側を[]でくくり、中のデータはカンマを入れて記述したのに対し、ハッシュではkey => valueというように、keyとvalueのペアを並べて表記する。ペアとペアの区切りにはカンマをつける。また、配列とは異なり両側を{}で囲んで示す。=>は=(イコール)と>(不等号)を組み合わせる。

ハッシュの初期値の与え方(その1)

noodle = {"チーズラーメン" => 600, "納豆ラーメン" => 700, "キムチラーメン" => 780, "まぐろラーメン" => 800, "牛乳ラーメン" => 500}

記述をする際には、下記のように適宜改行を入れて見やすく書いてもよい。

ハッシュの初期値の与え方(その2)-改行を入れて見やすくする

noodle = {
         "チーズラーメン" => 600,
         "納豆ラーメン" => 700,
         "キムチラーメン" => 780,
         "まぐろラーメン" => 800,
         "牛乳ラーメン" => 500
         }

ハッシュからの値の取り出し

ハッシュから値を取り出す方法を、配列と比較しながら見ていこう。いずれもレジでレシートを作成するプログラムを想定している。購入した商品と金額はバーコードにより読取り済みであり、配列もしくはハッシュに代入されているものとする。

配列の場合

配列を使用した場合は下記の通りとなる(receipt-array.rb)。商品名が代入された配列itemと、金額が代入された配列kingakuという2つの配列変数を使用している。配列からの値の取出しにはインデックスを使用するため、初期値を0とするiという変数を使用し、while文で繰り返し表示をしている。

#!/usr/koeki/bin/ruby

item = ["牛乳","醤油","玉葱","納豆","林檎","牛肉",]
kingaku = [98,298,198,128,300,698]
i = 0
sum = 0

while i < kingaku.length
  sum += kingaku[i]
  printf ("%s\t%d円\n",item[i],kingaku[i])
  i += 1
end

printf ("合計\t%d円\n",sum)

実行するとインデックス0から順番に表示される。

irsv{学籍番号}% ruby receipt-hash.rb[Return]
牛乳	98円
醤油	298円
玉葱	198円
納豆	128円
林檎	300円
牛肉	698円
合計	1720円

ハッシュの場合

ハッシュを使用した場合は下記の通りとなる(receipt-hash.rb)。ハッシュの場合、インデックスではなくkeyとvalueを用いているため、iの値を1ずつ増やしながら繰り返し表示をするwhileが使用できない。ここではfor文を使用している。shinamoneyにはそれぞれkey、valueの値が代入される。

#!/usr/koeki/bin/ruby

kingaku = { "牛乳" => 98,
            "醤油" => 298,
            "玉葱" => 198,
            "納豆" => 128,
            "林檎" => 300,
            "牛肉" => 698
            }
sum = 0

for shina, money in kingaku
  printf ("%-10s\t%5d円\n",shina, money)
  sum += money
end

printf ("合計\t%d円\n",sum)

for文について若干説明する。

for shina, money in kingaku

の行で、ハッシュ変数のkingakuのkeyとvalueを対で取り出し、それぞれshinamoneyに代入している。そして、printfの行とsum+=moneyの行を実施する。kingakuのkeyとvalueを対で1つずつ取り出しながら全てのデータを読み出すまでfor-end間を繰り返し実施する。

receipt-hash.rbの実行結果は以下の通りである。ハッシュの場合、代入した順番と結果の順番が必ずしも一致しないことがわかる。

irsv{学籍番号}% ruby receipt-hash.rb[Return]
牛乳	98円
牛肉	698円
林檎	300円
納豆	128円
玉葱	198円
醤油	298円
合計	1720円

ハッシュへからの値の取り出し方法

  • hogeをハッシュ変数とする場合、for foo, bar in hogeというfor文を用いる。hogeのkeyをfoo、valueをbarに代入し、繰り返し処理を行う。
  • 配列とは異なりハッシュでは代入した順番と取り出された順番が異なる。

ハッシュへの代入方法

ハッシュへの代入を行う場合、keyとvalueをペアで指定する必要がある。ハッシュの初期値を記載する場合は{}を用いたが、ハッシュへの代入および、ハッシュからの値の取り出しは[]を使用する(team.rb)。

#!/usr/koeki/bin/ruby

ball = Hash.new	# 新しいハッシュの生成
while true
  print "親会社(登録終了は q): "
  company = gets.chomp!
  if company == "q"
    break
  end
  print "チーム名: "
  team = gets.chomp!
  ball[company] = team
end
print "読込終了\n"

$KCODE = 'e'	# 漢字コードをEUCに指定

p ball.keys
p ball.values
p ball

このプログラムでは、親会社とチーム名を入力しハッシュに代入している。まず冒頭でball = Hash.newと宣言し、ballをハッシュ変数としている。そして、親会社とチーム名について入力を求め、それぞれcompanyteamに代入している。その後で、

ball[company] = team

の行で、companyをkey、teamをvalueとしてballに代入している。以下が実行結果である。

irsv{学籍番号}% ruby team.rb[Return]
親会社(登録終了は q):スイート[Return]
チーム名:メイプルス[Return]
親会社(登録終了は q):公益[Return]
チーム名:サンダース[Return]
親会社(登録終了は q):サンタナ[Return]
チーム名:カーズ[Return]
親会社(登録終了は q):q[Return]
["スイート","公益","サンタナ"]
["メイプルス","サンダース","カーズ"]
{"スイート"=>"メイプルス", "公益"=>"サンダース", "サンタナ"=>"カーズ"}

このプログラムでは最後にpメソッドを使ってハッシュに代入された値を表示している。ここで、

p ball.keys

とするとkeyのみが表示される。

p ball.values

とするとvalueのみが表示される。

p ball

とするとkey=>valueの形式で表示される。

なお、pメソッドの上の行に、$KCODE = 'e'とある。pメソッドが表示しようとする変数内の値に日本語文字列が含まれる場合、そのまま表示すると文字化けをおこしてしまう。これを防ぐために、$KCODE = 'e'を上の行に挿入し、日本語文字コードがEUCであると指定している。

ハッシュへの代入方法

  • 冒頭でhoge=Hash.newとする。
  • その後、hoge[key]=valueの形で、keyとvalueをペアにしてhogeに代入する。

keyを指定してvalueを取り出す

ハッシュに代入されているkeyを指定してvalueを取り出す方法について見てみよう。まずは実際のプログラムを見ながら確認しよう(capital.rb)。

#!/usr/koeki/bin/ruby

place = Hash.new(0)
place["エリトリア"] = "アスマラ"
place["セントルシア"] = "カストリーズ"
place["ツバル"] = "フナフティ"
place["モンテネグロ"] = "ポドゴリツァ"

STDERR.print "どこの国の首都が知りたいですか\n
(エリトリア、セントルシア、ツバル、モンテネグロ):"
where = gets.chomp!
capital = place[where]

if capital == 0
  printf ("%sのデータはありません\n", where)
else
  printf ("%sの首都は%sです\n", where, capital)
end

以下に実行例を示す。ktermでの日本語入力のON/OFFはShift+SPACEである(シフトを押しながらスペースキーを押す)。

irsv{学籍番号}% ruby capital.rb[Return]
どこの国の首都が知りたいですか
(エリトリア、セントルシア、ツバル、モンテネグロ):エリトリア
エリトリアの首都はアスマラです

実行結果の例では、ユーザはエリトリアと入力している。入力データの受け取りは

where = gets.chomp!

の行で行われており、whereに"エリトリア"が代入される。そして

capital = place[where]

の行ではplaceというハッシュ変数において、whereをkeyとするvalueを調べ、これをcapitalに代入している。これによりcapitalにはエリトリアに対応するvalueであるアスマラが代入されることになる。

冒頭のplace = Hash.new(0)placeが空っぽのハッシュ変数であることを宣言するものであるが、Hash.newのかわりにHash.new(0)としている。Hash.new(値)にすると、定義されていないkeyが代入された場合に、この値が返されるようになる。

ハッシュ変数に最初から初期値を与える場合は、冒頭のplace = Hash.new(0)は不要である。このプログラムでは冒頭で空っぽのハッシュを作り、その後で値を代入しているが、代入するkeyとvalueのペアがプログラム内に書かれており、実質的には初期値を与えているのと同じような印象を受けるかもしれない。そこで、次はキーボードからkeyとvalueを入力し、ハッシュ変数に代入する方法を確認しよう。

keyを指定したvalueの取り出し

  • hoge = piyo[key]でハッシュ変数piyoにおけるkeyに対応するvalueをhogeに代入する。
  • 冒頭でpiyo=Hash.new(値)とすると、valueを取り出すにあたり存在しないkeyを指定した場合にこの値が返される。

出席課題

capital.rbにおいてplace = Hash.new(0)の(0)を取り除いてplace = Hash.newとした場合に、未定義のkeyを入力するとどのようになるだろうか。実行して確かめてみよう。

余裕がある人は、Hash.new(0)の(0)を取り除いてplace = Hash.newとしても、「○○のデータはありません」というメッセージが出るようにプログラムを書き換えてみよう。

制限時間は10分。出席点は2点。提出要領は下記の通り。

  • 提出先:naoya@e.koeki-u.ac.jp
  • メールのSubject:attend05
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降に実行結果を貼り付け、何が発生したのかを簡潔に説明する。追加の問題も解いた場合は、プログラムの変更箇所を示す。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:Mewによるメールの送り方はMewコマンドを参照

ハッシュのvalueを配列にする

ハッシュはkeyとvalueを使い、任意の値に任意の値を結びつけ、ペアでデータを保存することができるものであった。では、次のようなデータ(name.txt)をハッシュ変数に代入する場合はどうしたらよいだろうか。

氏  名    年齢 血液型
山田一郎         21      A
佐藤二郎         22      B
加藤三郎         23      B
藤田四郎         24      O
木村五郎         25      A
鈴木六郎         26      O

これをハッシュに代入しようとすると戸惑うことになる。key「山田一郎」のvalueが「21とA」という2つの値になっているためである。

実はこのような場合でもハッシュへの代入は可能である。keyとvalueは1対1の対応をしなければならないというわけではなく、1つのkeyに対して複数のvalueを結びつけることができる。つまりvalueを配列にすることができる。上記の例は次のように書くことができる。

name["山田一郎"] = [21, "A"]

Aは文字列なので""をつけており、21は数字なので""をつけていない。name.txtを読み込んでハッシュに代入し、結果を表示するプログラムを書くと以下の通りとなる(onamae.rb)。

#!/usr/koeki/bin/ruby

raw = Hash.new

while line = gets
  if /(\S+)\s+(\d+)\s+(\S+)/ =~ line
    # 1個目の() (\S+)→氏名が入る
    # 2個目の() (\d+)→年齢が入る
    # 3個目の() (\S+)→血液型が入る
    raw[$1] = [$2, $3] # 配列を代入
  end
end

for name, data in raw
  # data には、[年齢, 血液型] という配列が入っている
  printf("%-10sさんは%2d歳で、血液型は%s型です。\n", 
  name, data[0], data[1])
  # dataの第0要素が年齢、第1要素が血液型
end
irsv{学籍番号}% ruby onamae.rb name.txt[Return]
鈴木六郎  さんは26歳で、血液型はO型です。
木村五郎  さんは25歳で、血液型はA型です。
藤田四郎  さんは24歳で、血液型はO型です。
加藤三郎  さんは23歳で、血液型はB型です。
佐藤二郎  さんは22歳で、血液型はB型です。
山田一郎  さんは21歳で、血液型はA型です。

配列とは異なり、ハッシュでは値を取り出すときの順番は、読み込んだ順番とは無関係になる。ハッシュはmd5(Message Digest 5)という方法でデータを暗号化して格納する。暗号化されたデータの大きさによって取り出される順番が決まるため、読み込んだ順番と異なることになる。しかし、ハッシュは単一のkeyに対して複数のvalueを結びつけて代入することができることから、データベースの用途に利用する上で都合がよい記法であるといえる。

参考までにこのプログラムを配列で書くと次のようになる(onamae-array.rb)。

#!/usr/koeki/bin/ruby

name = [] #氏名用の配列
age = []  #年齢用の配列
blood = [] #血液型用の配列
i = j = 0 #配列のインデックス

while line = gets
  if /(\S+)\s+(\d+)\s+(\S+)/ =~ line
    # 1個目の() (\S+)→氏名が入る
    # 2個目の() (\d+)→年齢が入る
    # 3個目の() (\S+)→血液型が入る
    name[i] = $1
    age[i] = $2
    blood[i] = $3
    i += 1
  end
end

while j < i
  printf("%-10sさんは%2d歳で、血液型は%s型です。\n", 
  name[j], age[j], blood[j])
  j += 1
end

レポート課題

以下のうちいずれかを選んで実施する。

問題1(7点満点):授業中に取り上げたcapital.rbを参考に、自分の好きなデータをハッシュに代入しておき、あとから検索できるプログラムを作成する。検索できるデータは、誕生月と誕生石、花と花言葉、Rubyメソッドとその働き、都道府県と県庁所在地など好きなものを選んでよい。他の人とは違うもの、楽しいものが望ましい。最低でもデータは10個作ること。

問題2(9点満点):問題1と同じだが、1つのkeyに対して2つ以上のvalueを結びつけて代入しておき、あとから任意のvalueを検索できるプログラムを作成する。例えば、野球選手名をkeyとして、打率、打点、本塁打をvalueとしておく。ユーザが選手名を指定した後で何を知りたいかを選択すると、それに対応したvalueを表示できるようにせよ。最低でもデータは10個作ること。


  • 提出先:naoya@e.koeki-u.ac.jp
  • 提出期限:11/11(日)23:59
  • メールのSubject:課題3
  • 本文の構成:1行目で学籍番号、氏名を記載する。2行目以降は下記の構成とする
  1. 作成したプログラム
  2. プログラムの実行結果
  3. プログラムの説明
  4. 感想

  • 採点基準(問題1):期限内提出点(2点)、メールの体裁(1点)、プログラム(1点)、独自性(1点)、プログラムの説明(2点)
  • 採点基準(問題2):期限内提出点(2点)、メールの体裁(1点)、プログラム(2点)、独自性(1点)、プログラムの説明(3点)
  • プログラムの説明:使用したデータと、ハッシュへのデータの代入方法および取り出し方法について説明する。
  • 独自性:今回は自分で自由に作成するプログラムであるため、使用するデータや説明が他の人と偶然に一致する可能性は低いと考えられる。ほかの人と同じプログラムの場合は0点(変数名だけ違う場合も0点)、ほかに同じプログラムを書いた人がいない場合は1点とする。
  • わかりにくい説明や、Webページを単にコピー&ペーストしただけの説明は減点することがある。一度読み直してから提出すること。
  • 驚異的に良くできているレポートについては満点を超える得点をつけることがある。
  • よくできていたレポートは、他の人の参考になるよう、本人が特定できないような形で掲載する。掲載してほしくない場合はメールでの課題提出時にその旨記載すること。

Tips:emacsでの日本語入力のオンオフはCtrl-oです

Tips:ktermでのプログラムの実行結果をメールに貼り付けるには、コピーしたい箇所をマウスで選択し、emacs(Mew)上でマウスの真ん中ボタンをクリックする

Tips:Mewによるメールの送り方はMewコマンドを参照