Leon's Blogging

Coding blogging for hackers.

好用的 Hash Method

| Comments

在 rails 當中經常會使用 hash,rails 也提供很多方便的 methods

Hash

1
2
3
Hash["a", 100, "b", 200]             #=> {"a"=>100, "b"=>200}
Hash[ [ ["a", 100], ["b", 200] ] ]   #=> {"a"=>100, "b"=>200}
Hash["a" => 100, "b" => 200]         #=> {"a"=>100, "b"=>200}

merge & merge!

回傳新的 hash,後面一樣的 key 則會覆蓋前面的

以後面的為優先

1
2
3
4
5
6
7
8
h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }
h1.merge(h2)   #=> {"a"=>100, "b"=>254, "c"=>300}

#將重複的做其他處理
h1.merge(h2){|key, oldval, newval| newval - oldval}
#=> {"a"=>100, "b"=>54,  "c"=>300}
h1             #=> {"a"=>100, "b"=>200}

merge! 直接改變原先的 hash

1
2
3
4
h1.merge!(h2)
#=> {"a"=>100, "b"=>254, "c"=>300}
h1
#=> {"a"=>100, "b"=>254, "c"=>300}

reverse_merge & reverse_merge!

回傳新的 hash,前面一樣的 key 則會覆蓋後面的

通常用在指定hash的預設值

以前面的為優先

1
2
3
4
5
6
7
h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }

h1.merge(h2)
#=> {"a"=>100, "b"=>254, "c"=>300}
h1.reverse_merge(h2)
#=> {"b"=>200, "c"=>300, "a"=>100}

deep_merge & deep_merge!

在兩個hash的鍵值相同,而值也是個hash的情況下

1
2
3
4
5
6
7
h1 = {:a => {:b => 1}}
h2 = {:a => {:c => 2}}

h1.merge(h2)
#=> {:a=>{:c=>2}} error
h1.deep_merge(h2)
#=> {:a=>{:b=>1, :c=>2}}

fetch

即使是 nil, false 也會回傳,只有在空值的時候回傳預設值

1
2
3
4
5
6
7
8
9
10
h = { "a" => 100, "b" => false , 'c' => nil}
#=> {"a"=>100, "b"=>false, "c"=>nil}
h.fetch('a', 8)
#=> 100
h.fetch('b', 8)
#=> false
h.fetch('c', 8)
#=> nil
h.fetch('d', 8)
#=> 8

在處理應該存在的哈希鍵時,使用 fetch。

1
2
3
4
5
6
7
heroes = { batman: 'Bruce Wayne', superman: 'Clark Kent' }
# bad - if we make a mistake we might not spot it right away
heroes[:batman] # => "Bruce Wayne"
heroes[:supermann] # => nil

# good - fetch raises a KeyError making the problem obvious
heroes.fetch(:supermann)

在使用 fetch 時,使用第二個參數設置默認值而不是使用自定義的邏輯。

1
2
3
4
5
6
7
batman = { name: 'Bruce Wayne', is_evil: false }

# bad - if we just use || operator with falsy value we won't get the expected result
batman[:is_evil] || true # => true

# good - fetch work correctly with falsy values
batman.fetch(:is_evil, true) # => false

盡量用 fetch 加區塊而不是直接設定默認值。

1
2
3
4
5
6
7
8
batman = { name: 'Bruce Wayne' }

# bad - if we use the default value, we eager evaluate it
# so it can slow the program down if done multiple times
batman.fetch(:powers, get_batman_powers) # get_batman_powers is an expensive call

# good - blocks are lazy evaluated, so only triggered in case of KeyError exception
batman.fetch(:powers) { get_batman_powers }

except & except!

通常用在確保某些欄位不要被傳進來的參數修改到

1
2
h1 = { a:100, b:200 }
h1.except(:a)

symbolize_keys & symbolize_keys!

回傳新的 hash,key 值轉成 symbol

通常用在確保 key 的一致性

1
2
3
4
hash = { 'name' => 'Rob', 'age' => '28' }

hash.symbolize_keys
# => {:name=>"Rob", :age=>"28"}

stringify_keys & stringify_keys!

回傳新的 hash,key 值轉成 string

alias to_options & to_options!

通常用在確保 key 的一致性

1
2
3
4
5
6
7
8
hash = { name: 'Rob', age: '28' }

hash.stringify_keys
#=> { "name" => "Rob", "age" => "28" }

#若有衝突已後面為優先
{"a" => 1, :a => 2}.stringify_keys
#=> {"a"=>2}

slice & slice!

! 行為會不太一樣,要注意

1
2
3
4
5
6
7
8
9
10
11
h1 = { "a" => 100, "b" => 200 }

h1.slice("a")
#=> {"a"=>100}
h1
#=> {"a"=>100, "b"=>200}

h1.slice!("a")
#=> {"b"=>200}
h1
#=> {"a"=>100}

extract!

將需要的值提取出來,成為新的 hash

1
2
3
4
5
h1 = { "a" => 100, "b" => 200 }
h1.extract!("a")
#=> {"a"=>100}
h1
#=> {"b"=>200}

to_query

Alias for Hash#to_query

1
2
3
4
5
{name: 'David', nationality: 'Danish'}.to_query
#=> "name=David&nationality=Danish"

{name: 'David', nationality: 'Danish'}.to_query('user')
# => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"

values_at

1
2
3
4
5
6
# bad
email = data['email']
nickname = data['nickname']

# good
email, username = data.values_at('email', 'nickname')

other

attributes

將物件轉成 hash

1
2
3
4
5
foo = Book.first
#=> #<Book id: 22, name: "book", desc: "desc", created_at: "xxx", updated_at: "xxx">

foo.attributes
#=> {"id"=>22, "name"=>"book", "desc"=>"desc", "star"=>1, "created_at"=>xxx, "updated_at"=>xxx}

HashWithIndifferentAccess

最後是在 rails 中有這個 method 可以很方便地讓你不管是用 string 或是 symbol 都可以拿到值,在 params 中也是因為這個方法,因此兩種都取得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a = Hash.new
# => {}
a['hi'] = 123
# => 123
a['hi']
# => 123
a[:hi]
# => nil

b = HashWithIndifferentAccess.new
# => {}
b['hello'] = 321
# => 321
 b['hello']
#=> 321
b[:hello]
# => 321

aa = a.with_indifferent_access
# => {"hi"=>123}
aa['hi']
# => 123
aa[:hi]
# => 123

官方文件:
Ruby Doc Hash
Hash apidock

參考文件:
ActiveSupport - 工具函式庫

Comments