iT邦幫忙

DAY 28
0

Ruby on Rails 花招百出系列 第 30

Ruby 用 inject 和 each_with_object 來組 hash

本期Ruby Weekly上有一篇很有趣的討論,關於在Ruby當中組成Hash的方法。

inject是用來處理陣列的方法,例如數列加總:

array = [1,2,3,4,5]
array.inject(0) {|result, number| result + number}
# => 15

首先,inject會帶入我們設定的變數(0)當做初始值,而這個初始值就是block中的第一個變數(result),接下來帶入數列中的數值到第二個變數(number),最後將回傳值儲存到result,重新再跑一遍。最後回傳result給使用者。

針對整理Hash的三種解法

整理資料庫時,也會使用inject整理,一般來說是為了方便查找資料,例如在Rails當中先把user的名字和電話先對起來:

User.all.inject({}) do |result, user|
	result[user[:name]] = user[:phone]
	result
end

這樣一來,我們就有一個hash可以直接用名字來查詢電話號碼。

但在這樣的組合方法中,有一個很不符合Ruby風格的地方,就是在第3行的地方,因為inject每一次重跑時會取用前一次的回傳值當做result,因此最後需要使用result變數回傳,否則依照Ruby回傳值的邏輯會產生錯誤。

如果要把User這段code用一行解決,可以選擇用merge:

User.all.inject({}) do |result, user|
	result.merge(user[:name] => user[:phone])
end

不過對於原本文章作者Chris Mar而言,這依然不夠直觀,因為他喜歡原本直接用等號(=)的方式,因此他想到了另一個解法,使用each_with_object

User.all.each_with_object({}) |user, result|
	result[user[:name]] = user[:phone]
end

這裡和inject有幾個不同的地方:

  1. 所帶的變數順序與inject相反,要累積的result會放在後方

  2. 每一次跑完不取回傳值,而是取原本的result值,不需擔心回傳值的問題

  3. 只能使用於object、hash、array等型態,無法用於數字和字串。例如:

    [1,2,3].each_with_object(0) {|number, result| result + number}

    => 0

    => 永遠都是原始值,並不會改變

以上總共提到三種解法。

三種解法的效能問題

不過另一位網友Andy Croll從效能的角度來討論,merge的速度是最快的。他使用了Benchmark ips這個gem,可以將不同的code丟進去檢查效能,並分別在幾個方法上丟入1000筆資料,得出以下結果。

第一種:inject方法 => 3136.7 i/s
第二種:merge方法 => 5.9 i/s
第三種:each_with_object => 3220.8 i/s

這...也太打臉的吧,前一篇文章整理出來最好的方法是each_with_object,但效能卻是最差的,反而是中途使用的merge方法最簡單。筆者認為其實測量單位非常的小,即便以上幾個方法有著600多倍的差距,但實際在程式或網站上執行的速度,應該跟前端讀取和資料庫讀取比起來,還是快如閃電。各位覺得呢?

延伸閱讀

三種解法
三種解法的效能


上一篇
認識Rails ActiveRecord系列:文章導讀
下一篇
Rails使用include和join避免 N+1 queries
系列文
Ruby on Rails 花招百出32

尚未有邦友留言

立即登入留言