iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 14
1
Modern Web

30天修煉Ruby面試精選30題系列 第 14

Day14 - Ruby比一比: #each #map 和 #collect method

今天要講的是迭代器(iterator)。什麼是迭代器呢?它的好處就是會一個一個地傳回集合裡的元素,讓我們可以利用迭代方法做重複的事。

在Ruby裡的collection集合裡有Array陣列和Hash雜湊。

今天要介紹三種用在collection集合的迭代器分別叫eachmapcollect,這也是常見的Ruby面試考題呢!


Ruby經典面試題目 #14

Day14 each, map 和 collect 比較? What's the difference between each, map and collect?

我們來使用這三種迭代器,在Array和Hash兩種集合中各舉幾個生活化的例子,相信大家就比較容易暸解囉!

Array

Array#each

最近我在進行旅行存錢計劃。我有三個銀行帳戶,NAB, CAN, 和WESTPAC,開戶金額分別為100,200,300。所以我寫一個陣列集合放入初始金額[100,200,300]。

假設我打算開始從本週開始在每個帳戶存入50元(+50),可以在陣列後加上.each方法:

[100,200,300].each {|n| puts n+50}

結果列出各個帳戶的金額(注意:puts寫在block大括號裡):

150
250
350

經過.each方法作用之後,只會分別印出同一陣列中各個元素的值,不會產生新陣列。

Array#collect

以上案例,換成.collect試試看:(注意!puts寫在前面)

puts [100,200,300].collect {|n| n+50}

.collect會幫我們把結果放入新的陣列。結果印出:

[150, 250, 350]

Array#map

同樣的,.map方法也會幫我們產生新的陣列。

puts [100,200,300].map {|n| n+50}

結果印出:

[150, 250, 350]

.collect.map又有什麼不同?以及分別用在什麼情況呢?這時候就要翻查Ruby手冊裡,關於.collect.map的介紹了:

collect { |item| block } → new_ary Invokes the given block once for each element of self. Creates a new array containing the values returned by the block.

hmm...好像看不出有什麼差異呢!

map { |obj| block } → array Returns a new array with the results of running block once for every element in enum.

近一步查詢stackoverflow,果然有其他網友問過類似的問題。我們可以把.map當作是.collect的別名 (map is an alias for collect),實務上比較常使用.map喔!

更多Array#map用法

現在把我的銀行帳戶陣列存進account變數裡,再用.inspect檢查陣列的值:

account = [100, 200, 300]
account.map {|n| n+50}
p account.inspect

結果印出

"[100, 200, 300]"

如果在.map後加上驚嘆號.map!呢?

account.map! {|n| n+50}
p account.inspect

存進去原本的陣列了。錢錢變多了!開心~~(加上!驚嘆號的方法,通常代表小心!注意!的意思)

在這裡的.map方法會讓原本的物件陣列被改變呢!

"[150, 250, 350]"

Hash

Hash雜湊是一對keyvalue的集合。在剛剛的銀行帳戶例子裡,我們可以把銀行名稱當作索引存款數目當作

account = {"NAB" => 100, "CAN" => 200, "WEST" => 300}

利用雜湊來展現目前的資訊(帳戶名稱、存款金額),這樣就可讀性更加清楚了。

Hash#each

現在我想要計算三個帳戶加總共有多少錢,以.each的方式可寫為:

mymoney = 0
account.each {|bankname, saving| mymoney += saving} #把索引和值列出
print "My Money: $ " + mymoney.to_s

或是

mymoney = 0 #設定初始值
account.each{|bank| mymoney += bank[1]} #依序加總bank集合裡第二個元素bank[1]
print "My Money: $ " + mymoney.to_s

結果都會印出:

My Money: $ 600  

Hash#map

在Hash裡,把.each換成.map或是.collect

mymoney = 0 #設定初始值
account.collect{|bank| mymoney += bank[1]} #依序加總bank集合裡第二個元素bank[1]
print "My Money: $ " + mymoney.to_s

結果都是一樣的:

My Money: $ 600  

Hash#map 結合 Array#each 與 Array#map

現在要進階到一個較為複雜的例子:hash裡包含索引兩部份,那我們可不可以把陣列當作一種,放在裡面呢?

當然可以!

假設我的NAB銀行下有2個子帳戶,CAN銀行下有3個子帳戶,分別放入這些資產:

hash = { "NAB" => ["Cash", "Gold"], "CAN" => ["Bitcoin", "Litecoin", "Ethereum"] }

利用hash.map會產生一個新的陣列:(進一步了解說明,看stackoverflow的說明)

p hash.map {|n| n}

結果顯示:

[["NAB", ["Cash", "Gold"]], ["CAN", ["Bitcoin", "Litecoin", "Ethereum"]]] #我有好多帳戶!NAB下有2個,CAN下有3個

我想分別提取出銀行:帳戶名稱的這一對資訊,並且用逗號.join(", ")隔開。

為了程式可讀性,hash索引命名為bank(銀行名),account_arry(放了不同數目的子帳戶陣列)。在走account_arry.each展開陣列迭代器時,每在集合裡走完一個元素,就印出#{bank}: #{sub_account}

p hash.map {
            |bank, account_arry| account_arry.each{
                |sub_account| "#{bank}: #{sub_account}"}
        }.join(", ")

結果僅印出:

"Cash, Gold, Bitcoin, Litecoin, Ethereum"

奇怪,這不是我要的結果呀!我很希望帳戶前面能顯示出銀行名稱呢!

這是因為剛剛說過,arry.each會回傳陣列本身,在這個例子裡,分別回傳的是["Cash", "Gold"]["Bitcoin", "Litecoin", "Ethereum"]

改成.map試試看:

p hash.map {
            |bank, account_arry| account_arry.map{
                |sub_account| "#{bank}: #{sub_account}"}
        }.join(", ")

結果顯示為:

"NAB: Cash, NAB: Gold, CAN: Bitcoin, CAN: Litecoin, CAN: Ethereum"

這是因為account_arry.map自動幫我們產生新的陣列,放進bank與對應的sub_account並回傳。

最後放個小小的比一比表格作為總結:

each map / collect
Array方法 Enumerable(列舉)方法
回傳Array本身 產生新的Array並回傳

祝福大家能順利collect不同的資產,不管是有形的財富、還是無形的知識,最後都可以達成錢多多的心願喔!:D

===

Ref:


上一篇
Day13 - Ruby比一比: instance_eval 和 class_eval方法
下一篇
Day15 - Ruby比一比: 別名的使用 alias 和 alias_method
系列文
30天修煉Ruby面試精選30題31

尚未有邦友留言

立即登入留言