iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 7
0
Modern Web

關於 Ruby on Rails,我想說的是系列 第 7

[Day 7] Enumable 迭代方法

Enumable 又叫做枚舉,可以想像城一串資料一個個丟進機器去操作,不同的目的用不同的機器。

以前聽龍哥說過,有人遇到迭代就只會使用#each方法,戲稱他們是each工程師。像我在工作中常需要對一串資料做操作,如果每次遇到矩陣只會使用each,程式碼會看起來很繁瑣。

今天要介紹一些不錯的迭代方法,脫離 Ruby 菜比八的行列。

大綱

1.給矩陣元素加上序號(index)

  • each_with_index
  • map也可以知道序號

2.Array變成Hash的方法

  • each
  • inject
  • merge
  • each_with_object

3.同場加映

  • index
  • find

給矩陣元素加上序號(index)

each_with_index

這方法可以知道現在迭代到第幾個元素了

  detective=['柯南', '小哀', '光彥']
  detective.each_with_index do |element, index| 
    p "第#{index + 1}位偵探: #{element}"
  end

輸出結果

"第1位偵探: 柯南"
"第2位偵探: 小哀"
"第3位偵探: 光彥"

map也可以知道序號

map是對矩陣元素做計算後再放回原位,想知道現在是迭代到第幾個元素,該怎麼做?

直接在.each_with_index後使用.map

  detective.each_with_index.map do |element, index| 
    "第#{index + 1}位偵探: #{element}"
  end
  #=> ["第1位偵探: 柯南", "第2位偵探: 小哀", "第3位偵探: 光彥"] 

或是.map搭配.with_index也有一樣的效果

  detective.map.with_index do |element, index| 
    "第#{index + 1}位偵探: #{element}"
  end
  #=> ["第1位偵探: 柯南", "第2位偵探: 小哀", "第3位偵探: 光彥"] 

Array變成Hash的方法

假設Rails app 有個Customer model,有namephone欄位,想要把name當key去匹配phone value
目前資料庫有這兩筆資料

Customer.all 
#=> <ActiveRecord::Relation [#<Customer id: 1, name: "龍小台", phone: 919891989, created_at: "2019-09-22 15:07:40", updated_at: "2019-09-22 15:07:40">, 
   #<Customer id: 2, name: "英小采", phone: 914501450, created_at: "2019-09-22 15:08:19", updated_at: "2019-09-22 15:16:07">]> 

法一:each

先從菜菜each工程師開始

customers = {}
Customer.all.each do |customer|
  customers[customer.name] = customer.phone
end
customers #=> {"龍小台"=>919891989, "英小采"=>914501450} 

第一行還要先宣告一個空的Hash,有點繁瑣

法二:inject

Customer.all.inject({}) do |result, customer|
  result[customer.name] = customer.phone
  result
end
# => {"龍小台"=>919891989, "英小采"=>914501450} 

使用inject先給定初始值(這裏用{}),每次計算的結果result會作為下次計算的參數,直到全部迭代完,回傳最後一次的result

但在這裡有一個很不符合DRY的地方,就是在第3行的地方,因為inject每一次重跑時會取用前一次的回傳值當做result,因此最後需要使用result變數回傳,否則依照Ruby回傳值的邏輯會產生錯誤。例如第一次迭代回傳919891989,是Integer。下一次迭代變成919891989[customer.name]就噴錯了。

法三:merge

Customer.all.inject({}) do |result, customer|
  result.merge({customer.name => customer.phone})
end
# => {"龍小台"=>919891989, "英小采"=>914501450} 

每次迭代產生的新hash都合併進原本的result,就可以一行回傳最新的result

法四:each_with_object

Customer.all.each_with_object({}) do |customer, result|
	result[customer[:name]] = customer[:phone]
end
# => {"龍小台"=>919891989, "英小采"=>914501450} 

一樣要給原始值,但是變數順序與inject相反,每次迭代後的result放在後方

每一次跑完不取回傳值,而是取原始值被修改後的樣子,不需擔心回傳值的問題

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

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

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


同場加映其他有趣的方法

.index (等同 .find_index)

回傳第一個符合元素的index(第一個元素inex為0),若沒有則回傳nil
如果有給block,則回傳第一個讓block回傳true的元素index

a = [ "a", "b", "c" ] 
a.index("b") #=> 1 
a.index("z") #=> nil 
a.index { |x| x == "b" } #=> 1    

.find (等同 .detect)

回傳第一個讓block回傳true的元素,沒有則回傳nil
沒給block就回傳Enumerator

[1,2].find    #=> #<Enumerator: [1, 2]:find> 
(1..100).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> 35 
(1..100).find { |i| i % 5 == 0 and i % 7 == 0 } #=> 35

今天是壓線,可以體會漫畫家被催稿的壓力了w(゚Д゚)w


上一篇
[Day 6] 猴子補丁 Monkey Patch
下一篇
[Day 8] 檔案操作 File,Dir,FileUtils(上)
系列文
關於 Ruby on Rails,我想說的是23
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言