ハッシュ


ハッシュとは

ハッシュは別名連想配列とも言われ、任意の値に任意の値を結び付けることができるデータ型である。 分かりやすい利用例としては、辞書が挙げられる。たとえば、

このようなデータをRubyプログラムで表現するときに使われるのがハッシュである。


ハッシュの特性

ハッシュは、配列と同じ感覚で利用できる。配列の添字が整数に限られていたのに対し、ハッシュはどんな種類の値でも添字に利用できる。 たとえば、つぎの値段表を変数に格納する場合を考えよう。

  1. りんご・・・150円
  2. みかん・・・ 30円
  3. いちご・・・ 20円
  4. 梨・・・・・150円

これまでの知識では、配列変数に格納する。

配列 fruits
インデックス0123
りんごみかんいちご

配列 prices
インデックス0123
1503020150

2つの変数(fruits, prices)を用意し、fruitsには品名を、pricesには単価をそれぞれ配列として格納している。

ハッシュを利用すると、品名に対して直接価格を結び付けることができる。

ハッシュ fruit_prices
キーりんごみかんいちご
1503020150

このような関係をRubyプログラムにしてみよう。ハッシュを利用した簡単なプログラムは以下のようになる。

fruit_prices = Hash.new(0)
fruit_prices["りんご"] = 150
fruit_prices["みかん"] = 30
fruit_prices["いちご"] = 20
fruit_prices["梨"]  = 150

各行の働きは以下のとおり。

ハッシュの初期化は以下のように書くこともできる。

fruit_prices = {"りんご" => 150, "みかん" => 30,
         "いちご" => 20, "梨" => 150}

このように、プログラムを書く段階でハッシュの初期値を入れる場合は 中括弧 { } で括り、

「キー」 => 「値」

という組み合わせをカンマ (,) で区切って列挙する。

fruit_prices = {
  "りんご" => 150,
  "みかん" => 30,
  "いちご" => 20,
  "梨" => 150
}

のように見やすいように適当に改行を入れてもよい。

このようにして fruit_prices 変数に入ったハッシュの値は、配列変数と同じように [] で取り出すことができる。

fruit_prices["みかん"] # => 30
fruit_prices["梨"] # => 150

ハッシュの初期値を囲む括弧は中括弧 {} だが、 ハッシュの値を取り出す時に使うのは大括弧 [] であることに注意せよ。

実際にプログラムを動かしてみよう。

 練習問題  fruits.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

fruit_prices = Hash.new(0)
fruit_prices["りんご"] = 150
fruit_prices["みかん"] = 30
fruit_prices["いちご"] = 20
fruit_prices["梨"]  = 150

puts "りんご、みかん、いちご、梨、のどの値段を知りたいですか?"
product = gets.chomp
price = fruit_prices[product]

if price > 0
  printf("%sは1個%d円です\n", product, price)
else
  printf("%sは売っていません\n", product)
end

上記プログラムを fruits.rb という名前で保存し、実行してみること。りんご、みかん、いちご、梨、を入れたり、それ以外の名前を入れて何が返るか実験すること。

% ./fruits.rb
りんご、みかん、いちご、梨、のどの値段を知りたいですか?
りんご
りんごは1個150円です
「りんご、みかん」などを入れるとき、 Shift+SPCで日本語をON/OFFして入力してもよいが、 マウスでコピー&ペーストすると楽である。 KTermに表示されたメッセージにマウスを合わせて左ボタンを ダブルクリックすると単語をコピーでき、真中ボタンをクリックすると ペーストできる。

ハッシュの key は重複できない。以下のように、同一の key で複数回代入を行うと、最後に代入をした key と value のペアの値が保持される。

 練習問題  (黄色で書かれたコードをirbで入力し実行すること)
irb(main):001:0> fruit_prices = Hash.new(0)
irb(main):002:0> fruit_prices["みかん"] = 30
irb(main):003:0> fruit_prices["みかん"] = 40
irb(main):004:0> fruit_prices["みかん"]
=> 40

ここまでのまとめ。


ハッシュへの値の追加手順

キーと値を別々に入力する例

配列にデータを格納するときと同様、ハッシュに格納するときも最初からデータの個数が決まっているわけではない。 最初に空のハッシュを作成し、データを入力するごとに値を格納していく。 以下の例は国名と首都を順番に読み込んでハッシュに格納するものである。

 練習問題  capitals.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

capitals = Hash.new	# 新しいハッシュの生成
while true
  print "国名(読込終了は q): "
  country = gets.chomp
  if country == "q"
    break
  end
  print "首都: "
  cap_city = gets.chomp
  capitals[country] = cap_city
end
print "読込終了\n"

p capitals # pを呼んでハッシュの値を全表示

実際に動かしてみよう。入力の終わりでは q をタイプする。

% ./capitals.rb
国名(読込終了は q): 日本
首都: 東京
国名(読込終了は q): ポーランド
首都: ワルシャワ
国名(読込終了は q): カナダ
首都: オタワ
国名(読込終了は q): ジョージア
首都: トビリシ
国名(読込終了は q): スリランカ
首都: スリ・ジャヤワルダナプラ・コッテ
国名(読込終了は q): q
読込終了
{"日本"=>"東京", "ポーランド"=>"ワルシャワ", "カナダ"=>"オタワ",
 "ジョージア"=>"トビリシ", "スリランカ"=>"スリ・ジャヤワルダナプラ・コッテ"}

キーと値をファイルから読み取る例

最初に示した果物の価格の例ではプログラム中に埋め込んでいた価格ハッシュをCSVファイルから読み取るようにする。読み取るデータファイルは以下の fruits.csv とする。

fruits.csv

りんご,150
みかん,30
いちご,20
梨,150

プログラム:

 練習問題  fruits_from_file.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

fruit_prices = Hash.new(0)
open("fruits.csv", "r") do |f|
  while line = f.gets
    if /(.*),(\d+)/ =~ line then
    #$1=品名 $2=価格 (いずれも最初は文字列)
    fruit_prices[$1] = $2.to_i # 価格を整数化
    end
  end
end

puts "りんご、みかん、いちご、梨、のどの値段を知りたいですか?"
product = gets.chomp
price = fruit_prices[product]

if price > 0
  printf("%sは1個%d円です\n", product, price)
else
  printf("%sは売っていません\n", product)
end

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

ハッシュには、key, value がペアで入っている。これを全て取り出すには for ループで2つの変数を使って順次取り出す。

たとえば、

 練習問題  all_fruit_prices.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

fruit_prices = {"りんご" => 150, "みかん" => 30,
"いちご" => 20, "梨" => 150}

for fruit, price in fruit_prices do
  printf("当店の%sは%d円です\n", fruit, price)
end

結果は以下のようになる。

当店のりんごは150円です
当店のいちごは20円です
当店のみかんは30円です
当店の梨は150円です

ハッシュからのキーの取り出し

ハッシュに格納されているキーだけを取り出すには keys メソッドを利用する。たとえば上記の果物リストのある fruit_prices 変数の値から、キーだけを取り出すと以下のようになる。

fruit_prices.keys
=> ["りんご", "みかん", "いちご", "梨"]

これを利用して、ハッシュに備わっているキーの一覧を表示したい場合は以下のようにすればよい。

 練習問題  fruit-keys.rb
#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-

fruit_prices = {"りんご" => 150, "みかん" => 30, "いちご" => 20, "梨" => 150}
fruit_prices.keys.each do |key| # または: for key in fruit_prices.keys
  puts(key)
end
print("以上のうちどの値段を調べますか: ")
fruit = gets.chomp
if fruit_prices[fruit] then
  printf("%sは%d円です。\n", fruit, fruit_prices[fruit])
else # デフォルト値設定なしなので見つからなければ nil
  printf("残念ながら「%s」は取り扱っていません。\n", fruit)
end

以下のような実行結果となる。

% ./fruit-keys.rb
りんご
みかん
いちご
梨
以上のうちどの値段を調べますか: いちご
いちごは20円です。
% ./fruit-keys.rb
りんご
みかん
いちご
梨
以上のうちどの値段を調べますか: 
残念ながら「柿」は取り扱っていません。

実用的なプログラムでは、実行時にデータベースなどからハッシュを取得するので、キーをプログラム中に列挙したりせず、ハッシュそのものから取り出すことになる。


ハッシュの初期宣言

 練習問題  (黄色で書かれたコードをirbで入力し実行すること)

ハッシュの初期宣言として

irb(main):001:0> x = Hash.new

のように、デフォルト値を指定しなかった場合、存在しない key の value を参照すると nil が返る。

irb(main):002:0> x["hoge"] # "hoge" というkeyは未登録
=> nil

ところがもし、

irb(main):003:0> x = Hash.new(0)

のように、デフォルト値を指定した場合、存在しない key の value を参照すると、指定した値が返る。

irb(main):004:0> x["hero"] # "hero" というkeyは未登録
=> 0

初期宣言として、Hash.new() を利用した場合はデフォルト値が指定できるが、{ } を利用してハッシュの初期値を代入した場合は、デフォルト値は nil となる。つまり、

irb(main):005:1* x = {
irb(main):006:1*   "りんご" => "apple",
irb(main):007:1*   "みかん" => "orange",
irb(main):008:1*   "いちご" => "strawberry"
irb(main):009:0> }

と初期値代入した場合、未登録keyのvalueを参照すると nil となる。

irb(main):010:0> x["梨"] # "梨" というkeyは未登録
=> nil

このようなときに、あとからデフォルト値を設定するには ハッシュに属するメソッドである default= を利用する。

irb(main):011:0> x.default="わかりませーん"

すると、以下のようになる。

irb(main):012:0> x["梨"] # "梨" というkeyは未登録
=> "わかりませーん"
irb(main):013:0> x["りんご"] # "りんご" というkeyは登録されている
=> "apple"

配列の初期値

ちなみに配列の場合も、決まった長さ(要素数)のものを、決められた値で埋めつくした初期配列を作ることができる。これには Array.new を利用する。

 練習問題  (黄色で書かれたコードをirbで入力し実行すること)

たとえば、

irb(main):001:0> Array.new(5, 0)

とすると、

[0, 0, 0, 0, 0]

という配列が作成される。初期値を指定せず、

irb(main):002:0> Array.new(4)

などとした場合は、

[nil, nil, nil, nil]

のように、各要素の初期値が nil の配列が作成される。 ハッシュと違い、配列には存在しない範囲の添字の値を参照したときのデフォルト値は指定できない。範囲外の添字を指定したときの値は nil となる。

irb(main):003:0> arr = Array.new(5, 0)
=> [0, 0, 0, 0, 0]
irb(main):004:0> arr[10]
=> nil

本日の課題

 基本課題 

ハッシュを使って、和英辞典プログラム dictionary.rb を作成せよ。

プログラムを起動すると、以下の選択肢が表示される:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
ユーザーが0を入力しない限り、何度でも1・2を選べるように、無限ループとbreakを使うこと。

辞書データは dictionary.txt というファイルで保管すること。

アリ ant
猫 cat
犬 dog
象 elephant
ナマケモノ sloth
スズメ sparrow

ユーザーが検索したい単語が辞書に登録されていない場合はメッセージを表示すること。また、ハッシュの key は重複できないので追加しようとする単語がすでに辞書にある場合にも、「この単語はすでに登録済みです」というメッセージを表示し新規登録を中止すること。

実行結果の例:

{c11xxxx}% ruby dictionary.rb
OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
1
検索したい単語を入力してください

象 は elephant です

OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
1
検索したい単語を入力してください

この単語は辞書に登録されていません

OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
2
日本語の単語を入力してください

英語の単語を入力してください
bear
単語が登録されました

OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
1
検索したい単語を入力してください

熊 は bear です

OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
2
日本語の単語を入力してください

この単語は既に登録済みです

OPTIONS:
1 - 単語を検索
2 - 新しい単語を登録
0 - 終了
0

 発展課題 

  1. 和英だけじゃなくて英和辞典も検索できるように改良せよ。
  2. 登録済みの項目を訂正(上書き)できるように改良せよ。
  3. 一つの単語に対して複数の訳語を登録することを可能にし、検索結果ですべての訳語が反映されるように改良せよ。

目次