:pぴー.sendせんど(:pぴー, :pぴー)

mokoaki
mokoriso@gmail.com

2017/07/22

Hash ハッシュ

Ruby技術者認定試験の書籍写経メモ

ハッシュの生成

ハッシュのキーには任意のオブジェクトが使えるが、 シンボルがよく使われる

リテラル

旧記法

{:a => 1}
=> {:a=>1}

{:a => 1}.class
=> Hash

1.9からの新記法

{a: 1}
=> {:a=>1}

{a: 1}.class
=> Hash

2.2からの新記法(現在のRuby資格試験の対象はVer2.1の為、この書き方は出来ない、という考えで臨むことになる)

{'a': 1, 'b-c': 2}
=> {:a=>1, :"b-c"=>2}

{'a': 1, 'b-c': 2}.class
=> Hash

Hash.[] メソッド

この方法は積極的に使う理由がない限り使う必要は無いでしょう

Hash[1, 2, 3, 4]
=> {1=>2, 3=>4}

Hash.new メソッド

newメソッドを使用した場合、キーが存在しない場合のデフォルト値を設定できます がぶっちゃけ使いません

h = Hash.new('test')

h.class
=> Hash

h[:hage]
=> '"test"'

配列からハッシュへ変換

Hash[[[:c, 1], [:a, 2], [:b, 3]]]
=> {:c=>1, :a=>2, :b=>3}

[[:c, 1], [:a, 2], [:b, 3]].to_h
=> {:c=>1, :a=>2, :b=>3}

初期値はハッシュ作成後でも変更できる

h = Hash.new('test')

h.default
=> "test"
h = {}

h[:hage]
=> nil

h.default
=> nil

h.default = 'test'

h.default
=> 'test'

h[:hage]
=> 'test'
h = Hash.new do |hash, key|
  p '= access new key'
  p hash, key

  hash[key] = (key == :hage1 ? 'a' : 'b')
end

h.class
=> Hash

h[:hage1]
"= access new key"
{}
:hage1
=> "a"

h[:hage1]
=> "a"

h[:hage2]
"= access new key"
{:hage1=>"a"}
:hage2
=> "b"

h[:hage2]
=> "b"

h[:hage3]
"= access new key"
{:hage1=>"a", :hage2=>"b"}
:hage3
=> "b"

h[:hage3]
=> "b"

参照ではなく、キーと値の新規設定時には動作しない。動作する必要もないし

h = Hash.new do |hash, key|
  p '= access new key'
  p hash, key

  hash[key] = (key == :hage1 ? 'a' : 'b')
end

h.class
=> Hash

h[:hage1] = 'hage-!'
=> "hage-!"
h = Hash.new do |hash, key|
  hash[key] = (key == :hage1 ? 'a' : 'b')
end

h.class
=> Hash

h.default_proc
=> #<Proc:0x007fcaa10af730@(irb):77>

Array#to_h

ぶっちゃけ使わないけど

[[:a, 1], ['b', 2]].to_h
=> {:a=>1, "b"=>2}

ハッシュのキーや値を参照する

[]
keys
values
values_at
fetch
select

[] メソッド

そのまんま

h = {a: 1}

h[:a]
=> 1

keys メソッド

ハッシュの全てのキーの配列を生成する

{a: 1, b: 2, c: 3, d: 4}.keys
=> [:a, :b, :c]

values メソッド

ハッシュの全ての値の配列を生成する

{a: 1, b: 2, c: 3, d: 4}.values
=> [1, 2, 3]

values_at メソッド

複数の引数を取り、指定されたキーに対応する値による配列を生成する

{a: 1, b: 2, c: 3, d: 4}.values_at(:a, :c)
=> [1, 3]

fetch メソッド

キーが存在する場合は値を返すのみ、下記以降の例でもそれは変わらない

{a: 1, b: 2, c: 3, d: 4}.fetch(:a)
=> 1
{a: 1, b: 2, c: 3, d: 4}.fetch(:z)
KeyError: key not found: :z
{a: 1, b: 2, c: 3, d: 4}.fetch(:z, 'test')
=> "test"
{a: 1, b: 2, c: 3, d: 4}.fetch(:z) do |key|
  key.to_s
end
=> "z"

select メソッド

{a: 1, b: 2, c: 3, d: 4}.select do |key, value|
  value % 2 == 0
end
=> {:b=>2, :d=>4}

ハッシュを変更する

[]=    
delete    
reject reject! delete_if
replace    
shift    
merge merge! update
invert    
clear    

[]= メソッド

h = {}

h[:a]
=> nil

h[:a] = 'hage-!'

h[:a]
=> "hage-!"

delete メソッド

指定されたキーと値を取り除く

h = {a: 1, b: 2, c: 3, d: 4}

h
=> {:a=>1, :b=>2, :c=>3, :d=>4}

h.delete(:b)
=> 2

h
=> {:a=>1, :c=>3, :d=>4}

指定したキーが存在しなければ戻り値はnil

h = {a: 1, b: 2, c: 3, d: 4}

h
=> {:a=>1, :b=>2, :c=>3, :d=>4}

h.delete(:z)
=> nil

h
=> {:a=>1, :b=>2, :c=>3, :d=>4}

ブロックを取った場合、キーが存在しない場合にブロックの評価結果を返す

h = {a: 1, b: 2, c: 3, d: 4}

h
=> {:a=>1, :b=>2, :c=>3, :d=>4}

h.delete(:z) do |key|
  p "Not found #{key}"
  key
end
"Not found z"
=> :z

reject reject! delete_if メソッド

キーと値毎にブロックを評価し、結果がtrueとなったキーと値を取り除いたハッシュを生成する

{a: 1, b: 2, c: 3, d: 4}.reject do |key, value|
  value % 2 == 0
end
=> {:a=>1, :c=>3}

reject!とdelete_ifは同じ動作となり、自己を破壊的に変更する

h = {a: 1, b: 2, c: 3, d: 4}

h.reject! do |key, value|
  value % 2 == 0
end
=> {:a=>1, :c=>3}

h
=> {:a=>1, :c=>3}

replace メソッド

自分自身のオブジェクトIDを変えることなく、引数で指定されたハッシュで自分自身の内容を置き換えます

a = {a: 1, b: 2}
b = {c: 3, d: 4}

a
=> {:a=>1, :b=>2}

a.object_id
=> 70254130865000

a.replace(b)
=> {:c=>3, :d=>4}

a
=> {:c=>3, :d=>4}

a.object_id
=> 70254130865000

shift メソッド

h = {}

h[:a] = 1
h[:b] = 2
h[:c] = 3

h.shift
=> [:a, 1]

h.shift
=> [:b, 2]

h.shift
=> [:c, 3]

merge merge! update メソッド

{a: 1, b: 1}.merge({a: 2, c: 2})
=> {:a=>2, :b=>1, :c=>2}

defaultは自分自身のものが引き継がれる

a = {}
b = {}

a.default = 'a'
b.default = 'b'

a.merge(b).default
=> "a"
a = {a: '1-a', b: '1-b', d: '1-d'}
b = {a: '2-a', b: '2-b', c: '2-c'}

a.merge(b) do |key, value1, value2|
  p "Duplicate #{key}"
  p key, value1, value2
  value2
end
"Duplicate a"
:a
"1-a"
"2-a"
"Duplicate b"
:b
"1-b"
"2-b"
=> {:a=>"2-a", :b=>"2-b", :d=>"1-d", :c=>"2-c"}

invert メソッド

キーと値を逆にしたハッシュを返す

{a: 1, b: 2}.invert
=> {1=>:a, 2=>:b}

同じ値が存在していた場合、キーが被る為 結果は不定となる とこの本に書いてあるが、どう見ても後からハッシュに追加されたキーと値が優先される

h = {}

h[:a] = 1
h[:b] = 1
h[:c] = 1

h.invert
=> {1=>:c}
{a: 1, b: 1, c: 1}.invert
=> {1=>:c}

clear メソッド

全てのキーと値を削除する もちろん object_id は変わらない

a = {a: 1}

a.object_id
=> 70254131321800

a.clear
=> {}

a
=> {}

a.object_id
=> 70254131321800

ハッシュを調べる

lengrh size    
empty?      
has_key? incluce? key? member?
has_value? value?    

length size メソッド

キーと値のペアの数を返す

{a: 1, b: 2}.length
=> 2

empty?

空のハッシュの場合、trueを返す

{a: 1}.empty?
=> false

{}.empty?
=> true

has_key? incluce? key? member? メソッド

みんな同じ動作、キーが存在している場合はtrueを返す

{}.has_key?(:a)
=> false

{}.include?(:a)
=> false

{}.key?(:a)
=> false

{}.member?(:a)
=> false
{a: 1}.has_key?(:a)
=> true

{a: 1}.include?(:a)
=> true

{a: 1}.key?(:a)
=> true

{a: 1}.member?(:a)
=> true

has_value? value? メソッド

両方とも同じ動作、値が存在している場合はtrueを返す

{}.has_value?(1)
=> false

{}.value?(1)
=> false
{a: 1}.has_value?(1)
=> true

{a: 1}.value?(1)
=> true

ハッシュを使った繰り返し

each each_pair
each_key  
each_value  

この書籍には 繰り返しの順番は不定である と書いてあるが、恐らく1.8の頃の文章をそのまま使っていると思われる。1.9からは順序が保持される事を考慮すべきかと思う

each each_pair メソッド

ブロックにキーと値のペアを繰り返し渡し、評価させる

{a: 1, b: 2, c: 3}.each do |key, value|
  p key, value
end
:a
1
:b
2
:c
3
=> {:a=>1, :b=>2, :c=>3}

each_key メソッド

ブロックにキーを繰り返し渡し、評価させる

{a: 1, b: 2, c: 3}.each_key do |key|
  p key
end
:a
:b
:c
=> {:a=>1, :b=>2, :c=>3}

each_value メソッド

ブロックに値を繰り返し渡し、評価させる

{a: 1, b: 2, c: 3}.each_value do |value|
  p value
end
1
2
3
=> {:a=>1, :b=>2, :c=>3}

ハッシュをソートする

「Ruby 1.9では、Hashクラスのsortメソッドは廃止されました。Ruby 1.9ではEnumerableモジュールのsortメソッドを実行します」

キー優先、辞書順、そんな感じの動きをする

{b: 99, a: 1, c: 50, az: 10, ba:50}.sort
=> [[:a, 1], [:az, 10], [:b, 99], [:ba, 50], [:c, 50]]

戻り値が配列になることに注意

{b: 99, a: 1, c: 50, az: 10, ba:50}.sort
=> [[:a, 1], [:az, 10], [:b, 99], [:ba, 50], [:c, 50]]

{b: 99, a: 1, c: 50, az: 10, ba:50}.sort {|a, b| a[1] <=> b[1]}
=> [[:a, 1], [:az, 10], [:c, 50], [:ba, 50], [:b, 99]]

ハッシュに変換するにはこんな感じ

Hash[{b: 99, a: 1, c: 50, az: 10, ba:50}.sort]
=> {:a=>1, :az=>10, :b=>99, :ba=>50, :c=>50}

{b: 99, a: 1, c: 50, az: 10, ba:50}.sort {|a, b| a[1] <=> b[1]}.to_h
=> {:a=>1, :az=>10, :c=>50, :ba=>50, :b=>99}

メモ

キーワード引数 も併せて理解しておくべし

メソッド呼び出し時の実引数の{}波カッコの省略

def moko(a, b, c)
  p a
  p b
  p c
end

moko(1, 2, a: 1, 'b' => 2)
1
2
{:a=>1, "b"=>2}
=> {:a=>1, "b"=>2}

# 引数の数的に考えて、残りの部分がハッシュとして解釈できそうなら、
moko(1, 2, { a: 1, 'b' => 2 })
# こう書いたかの如く動作するよ!って事だわな