Ruby each_with_object vs reduce

Posted by Noel on 2015-06-03

又發現一個好用的方法each_with_object,是屬於 Enumerable 的方法之一,最近看前輩的 code 才發現的,算是 each 家族中的一員,不得不說 ruby 內建的許多列舉方法實在是非常方便啊!它與前陣子介紹的reduce目的上有點類似,但似乎更為易懂,直接來看範例吧!

each_with_object 範例一
1
2
3
4
%w(red blue yello black).each_with_object({}) do |value, hash|
hash.store(value, value.capitalize + '!')
end
# => {"red"=>"Red!", "blue"=>"Blue!", "yello"=>"Yello!", "black"=>"Black!"}

介紹

範例一其實就只是把一個陣列迭代做些處理後存入hash裡,然後依序下去直到迭代完成,是不是很熟悉呢?沒錯,類似的功能reduct/inject也做得到,甚至你單用each也是可以,但是each_with_object更方便簡單,你必須先指定一個參數當做一個容器的初始值,此例就是 hash ,然後每次迭代之後他會自動記住容器的狀態,並自動代入下次迭代,這裡與reduce不太一樣,寫法上是更簡當方便,剛剛的範例如果用reduce寫的話則是:

reduce 範例一
1
2
3
4
5
%w(red blue yello black).reduce({}) do |result, value|
result.store(value, value.capitalize + '!')
result
end
# => {"red"=>"Red!", "blue"=>"Blue!", "yello"=>"Yello!", "black"=>"Black!"}

最後輸出是一樣的,但reduce我們必須自己把結果擺在最後一行 (return),reduce才會把它當作下次繼續迭代的結果值,這點對某些人可能比較不是那麼地直覺。

來看看另一個each_with_object的應用範例,讓我們來使用該函式做出類似 select 的效果

each_with_object 範例二:模仿 select
1
2
3
4
(1..9).each_with_object([]) do |value, array|
array << value unless (value % 2 == 0)
end
# => [1, 3, 5, 7, 9]

基本上你可以依需求或你傳入的參數類型來自由使用該方法進而達到任何可能的功能,而可傳入的參數可以是 array、hash、甚至是 openstruct,所以 object 或 hashie 當然應該也可以,有興趣的人可以自己玩玩看。

與 reduce 差別

雖然這個方法看似很潮、很精簡,但還是有些使用上的限制與和reduce的差別,像是:

  1. each_with_object 必須傳入一個參數,reduce 則不然
  2. each_with_object 接受的參數必須是容器型,不能是純值
  3. each_with_object 程式區塊的參數順序與 reduce 相反,each 家族都是以個別值為第一個參數

第一與第三點應該非常直覺好懂,第二點則需要說明一下,因為 each_with_object 可以自己記住迭代回傳結果,而該結果也必須在使用該方法時就給定,如果此時你給他的參數是純值像是 int、str、boolean 等型態的物件,則會因為屬於純值而無法改變或操縱物件本身,所以永遠都會回傳自己,如以下範例:

each_with_object 範例三:參數給數字
1
2
3
4
(1..5).each_with_object(1) do |value, array|
value * array
end
# => 1 而非 120

以上就是該方法的介紹與分享,如果有問題或是發現小弟有哪邊有錯誤都請歡迎指教唷,感謝各位