Ruby面試精選30題 - Day27 Ruby裡的Hash預設值

第14天的時候,我們曾經提到Hash(雜湊)是一對key(鍵)value(值)的集合。今天的大哉問是,我們要如何設定預設值呢?


重點摘要:


Ruby經典面試題目 #27

  • 如何設定Ruby的Hash預設值?
    How to set default value in hash?

首先,起手式就是來翻閱Ruby API手冊看看裡面的例子,並依照我自己的喜好,改寫成容易被我記憶學習的變數。

Hash 與 Array

記得我在第14天使用過銀行帳戶的例子,在這裡繼續沿用類似的情境。假設我創建一個hash記錄我的銀行帳戶名稱和存款金額:

hbank = Hash.new("Bank Name")
hbank["CAN"] = 100
hbank["NAB"] = 200

p hbank.keys #=> 銀行名["CAN", "NAB"]
p hbank.values #=> 存款金額[100, 200]

p hbank["CAN"]  #=> 100
p hbank["NAB"] #=> 200

如果我要查閱某個並沒有在此Hash的銀行名稱呢?

p hbank["WEST"]           #=> "Bank Name"

它會出現預設值`Bank Name。

如果你以為這題面試題這麼簡單,那就大錯特錯了!(不要來~污辱我的美)我們必須深入探討另一個常見實務場景:如何改動Hash裡的值呢?

你或許會想到:使用method!趕快搬出常見的字串方法使一使:

p hbank["WEST"]           #=> "Bank Name"
p hbank["WEST"].upcase!   #=> "BANK NAME"
p hbank["WEST"]           #=> "BANK NAME"

第14天講過:

加上!驚嘆號的方法,通常代表小心!注意!的意思。Array#.map!方法會讓原本的物件陣列值被改變!

因此這裡的 string#.upcase!就讓Hash裡的Bank Name硬生生地變成BANK NAME了。因此,我們了解到陣列是個可變的物件。(Array in Ruby is mutable object)

Hash 與 Block

要預設Hash,我們還必須複習第11天block(區塊),因為Hash和block有密不可分的關係!

hbank_key = Hash.new { |hash,name| hash[name] = "Bank Name: #{name}" }

p hbank_key["ANZ"]           #=> "Bank Name: ANZ"
p hbank_key["ANZ"].upcase!   #=> "BANK NAME: ANZ"
p hbank_key["ANZ"]
#=> "BANK NAME: ANZ" 變大寫了
p hbank_key["CBA"]           #=> "Bank Name: CBA" 還是小寫
p hbank_key.keys           #=> ["ANZ", "CBA"]

hbank_key這個雜湊的例子比第一個版本hbank稍微進步,讓hbank_key["CBA"]的‘值並不會因為hbank_key["ANZ"].upcase!作怪,而受到影響。

Stackovrflow 原文這句話解釋的很清楚:

It is the block’s responsibility to store the value in the hash if required.

Hash <<+=結合比較

如果我們硬要唱反調,讓Hash設定default value時不使用block,看會發生什麼情況?

1. hash: 用 « 指定值

我們在第17天已了解到:<< 會改變常數,因此hsh_array[:z]被指定了新值。

hsh_array = Hash.new([])
p hsh_array # => {} 預設是空hash
hsh_array[:a] << 'This is symbol a'
hsh_array[:b] << 'This is symblo b'

p hsh_array[:z]
# => ["This is symbol a", "This is symblo b"]

p hsh_array # => {} hash不變,仍是空hash

2. hash, block 與 «

例子1 在Hash內使用<<指定值時(the assignment way),hsh_array[:z]原本預設為[],卻受到[:a]與[:b]的改變而影響:那改成<<(the mutable way)呢?

hsh_block = Hash.new { |h, k| h[k] = [] }

p hsh_block[:a] << 'This is symbol a'  #=> ["a"]
p hsh_block[:b] << 'This is symblo b'  #=> ["b"]

p hsh_block[:z] #[]
p hsh_block            #=> {:a=>["This is symbol a"], :b=>["This is symblo b"], :z=>[]}

3. hash: 用 += 改變值

關於串接字串,來複習一下第8天+=mutable的方法,其object_id會隨著新串接上的字串,而改變記憶體位置。

hsh_plus = Hash.new([])
p hsh_plus  # => {} 預設是空hash
p hsh_plus[:a] += ['This is symbol a']
p hsh_plus[:b] += ['This is symblo b']

p hsh_plus[:z]
# => [] 空array

p hsh_plus
# {:a=>["This is symbol a"], :b=>["This is symblo b"]}
# hash is mutable< 預設值已被改變

4. hash: 用 += 與 freeze? 固定值

最後,如果要讓hash的+=不透過block的作用也能變成imutable,我們就要來複習Day17freeze?方法:

hsh_freeze = Hash.new([].freeze)
p hsh_freeze[:a] += ['This is symbol a']  #=> ["This is symbol a"]
p hsh_freeze[:b] += ['This is symbol b']   #=> ["This is symbol b"]
p hsh_freeze[:z]           #=> [] #=> hash值不受更動,仍為空
p hsh_freeze              #=> {:a=>["This is symbol a"], :b=>["This is symbol b"]}

結論:

看完幾篇文章,發現有寫工程師偏好不要變動預設變數、但也有些人偏好能夠更彈性的變動變數,端看個人開發習慣。不過寫完落落長的比較,這個結論你一定要熟記喔!

We must store the default value in the hash from within the block if we wish to use << instead of <<=

Ref: