iT邦幫忙

2021 iThome 鐵人賽

DAY 5
1
自我挑戰組

初級紅寶石魔法師心得分享。系列 第 5

D-25. 枚舉(enumerate) && Intersection of Two Arrays II

曾經以為[each == 迭代(Iteration), map == 枚舉(enumerate)],後來發現錯得非常離譜。

迭代(Iterate)

有請:

教育部重編國語辭典修訂本
交換替代。《文選.張衡.東京賦》:「於是春秋改節,四時迭代。」北周.庾信〈哀江南賦序〉:「嗚呼!山岳崩頹,既履危亡之運,春秋迭代,必有去故之悲,天意人事,可以悽愴傷心者矣!」

一個接替一個,做重複的事。與迴圈感覺上很像?

再有請:

自由的百科全書維基百科
迴圈是計算機科學運算領域的用語,也是一種常見的控制流程。迴圈是一段在程式中只出現一次,但可能會連續執行多次的程式碼。迴圈中的程式碼會執行特定的次數,或者是執行到特定條件成立時結束迴圈,或者是針對某一集合中的所有項目都執行一次。

迭代是指一個接著一個交換做,迴圈是指重複執行。

each, map這兩個方法,做出迭代的行為。

會用到each與map的都是"集合資料"類別,例如Array與Hash。

2.7.3 :008 > [1, 2, 3].each
 => #<Enumerator: [1, 2, 3]:each>
2.7.3 :009 > {:a => 1, :b => 2, :c => 2}.each
 => #<Enumerator: {:a=>1, :b=>2, :c=>2}:each>
2.7.3 :010 > (1..5).each
 => #<Enumerator: 1..5:each>
2.7.3 :011 > 5.times.each
 => #<Enumerator: 5:times>
2.7.3 :012 > [1, 2, 3].map
 => #<Enumerator: [1, 2, 3]:map>
#map大同小異。
#Enumerator枚舉器。

配合昨天說的block。

2.7.3 :013 > [1, 2, 3].each {|num| puts num + 1}
2
3
4
 => [1, 2, 3]
2.7.3 :014 > [1, 2, 3].map {|num| num + 1}
 => [2, 3, 4]

#一個一個元素進去操作,這件事情就是迭代。

可以看到有說明each、map為迭代器的資料,但它們是不是枚舉器?

枚舉(enumerate)

教育部重編國語辭典修訂本。
一一列舉。如:「隨著科技進步,全世界每日新發明的產品,真是不勝枚舉。」金.元好問〈故物譜〉:「住在鄉里,常侍諸父及兩兄燕談,每及家所有書,則必枚舉而問之。」元.陶宗儀《南村輟耕錄.卷一九.闌駕上書》:「歌曰:『官吏黑漆皮燈籠,奉使來時添一重。』如此怨謠,未能枚舉,皆萬姓不平之氣,鬱結于懷,而發諸聲者然也。」

在block內做的事情,就是一一列舉。

2.7.3 :008 > [1, 2, 3].each
 => #<Enumerator: [1, 2, 3]:each>
2.7.3 :013 > [1, 2, 3].each {|num| puts num + 1}
2
3
4
 => [1, 2, 3]

When a block given, passes each successive array element to the block。-- form Array
Calls the given block with each key-value pair。-- form Hash

集合資料類別使用each與map,會產生枚舉器Enumerator:,接上black後會有迭代的行為,送進block後進行枚舉

自己做個枚舉器。

2.7.3 :001 > Enumerator.is_a? Class
 => true
2.7.3 :012 > new_enume = Enumerator.new {}
 => #<Enumerator: #<Enumerator::Generator:0x00007ff6ad33e900>:each>

是的,有這個類別,在一開始我是真的嚇到,枚舉也包裝成類別了,但後來越了解Ruby裏面都是物件這件事後,就覺得大驚小怪了,難怪枚舉這麼強大。

這是Ruby API示範的費波......放棄,Fibonacci係數。

fib = Enumerator.new do |y|
  a = b = 1
  loop do
    y << a
    a, b = b, a + b
  end
end

fib.take(10)

說明一下
"<<",感覺上跟很眼熟。

2.7.3 :009 > [] << 2
 => [2]
2.7.3 :010 > [] << [2]
 => [[2]]

還記得前面說過,Ruby有很多相同名稱方法,在Enumerator類別中,變數使用<<成為yielder的對象來取得值,可以當成方法的別名,而其實在Array中,<<push方法的別名。但行為上都很像賦予值對吧,更感覺到duck typing的好處了。

會有別名方法的簡單介紹的....如果時間夠。

冷知識,map是collect的別名,為了跳槽的魔法師們設計,當然是語法裡有map的魔法師們。

繼續回到官網示範。

fib = Enumerator.new do |y|
  a = b = 1
  loop do
    y << a # 循環中y被賦予a值,每個變數被賦予a的值。
    a, b = b, a + b # 但a與b的值,會依造此規則變化。
  end
end

2.7.3 :010 > fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
 => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

還可以這樣玩。

2.7.3 :042 > fib = Enumerator.new do |y|
2.7.3 :043 >   i = 0
2.7.3 :044 >   a, b = 2, 3
2.7.3 :045 >   loop do
2.7.3 :046 >     y << a
2.7.3 :047 >     a, b = b, a+b
2.7.3 :048 >     i += 1
2.7.3 :049 >     puts "某題:#{i}年後,動物有#{a}隻的問題"
2.7.3 :050 >   end
2.7.3 :051 > end
 => #<Enumerator: #<Enumerator::Generator:0x00007f9b6e469fb8>:each>
2.7.3 :052 > fib.take(10)
某題:1年後,動物有3隻的問題
某題:2年後,動物有5隻的問題
某題:3年後,動物有8隻的問題
某題:4年後,動物有13隻的問題
某題:5年後,動物有21隻的問題
某題:6年後,動物有34隻的問題
某題:7年後,動物有55隻的問題
某題:8年後,動物有89隻的問題
某題:9年後,動物有144隻的問題
 => [2, 3, 5, 8, 13, 21, 34, 55, 89, 144]

雖然明明也是跟定義方法很像,但怎麼感覺這樣處理,比較帥?

Enumerator類別博大精深,它還有子系列是Enumerator::,有興趣可以先看看Enumerator::Lazy,簡單一點的說法是lazy這個子系列避免枚舉元素過多,屬於優化的一種,Rails內也會有所謂的Lazy load喔。
高階魔法....

Enumerable(可枚舉)

Ruby API https://ruby-doc.org/core-3.0.2/Enumerable.html

Public Instance Methods

手冊上可以看到有被標注Enumerable的方法們,說是Ruby的精髓可能太誇張,但可以說是處理各種資料的專屬Ruby利器,例如昨日提到的sort_by

以下是騙篇幅用的整理資料,不討論用法,可以自行google翻譯看看,看到中文看會不會順利想到正確用法,可以更發現Ruby有多口語化。

`all?`、`any?`、`chain`、`chunk`、`chunk_while`、`collect`、`collect_concat`、`count`、`cycle`、`deect`、`drop`、`drop_while`、`each_cons`、`each_entry`、`each_slice`、`each_with_index`、`each_with_object`、`entries`、`filter`、`filter_map`、`find`、`find_all`、`find_index`、`first`、 `flat_map`、`grep`、`grep_v`、`group_by`、`include?`、`inject`、`lazy`、`map`、`max`、`max_by`、`member?`、`min`、`min_by`、`minmax`、`minmax_by`、`none?`、`one?`、`partition`、`reduce`、`reject`、`reverse_each`、`select`、`slice_after`、`slice_before`、`slice_when`、`sort`、`sort_by`、`sum`、`take`、`take_while`、`tally`、`to_a`、`to_h`、`uniq`、`zip`

用時沒發現,回頭整理才發現,原來用過的很多方法,都是有枚舉這個行為存在。

2.7.3 :054 > ruby_chain = [1, 2].chain(3..9)
 => #<Enumerator::Chain: [[1, 2], 3..9]>
2.7.3 :056 > ruby_chain.to_a
 => [1, 2, 3, 4, 5, 6, 7, 8, 9]

當初還以為這方法只是簡單連接而已,結果也可以枚舉。

補充一下next

2.7.3 :090 > arr = %w(惡魔靈魂 黑暗靈魂 黑暗靈魂2 血源詛咒 黑暗靈魂3)
 => ["惡魔靈魂", "黑暗靈魂", "黑暗靈魂2", "血源詛咒", "黑暗靈魂3"]
2.7.3 :091 > list = arr.to_enum
 => #<Enumerator: ["惡魔靈魂", "黑暗靈魂", "黑暗靈魂2", "血源詛咒", "黑暗靈魂3"]:each>
2.7.3 :092 > list.next
 => "惡魔靈魂"
2.7.3 :093 > list.next
 => "黑暗靈魂"
2.7.3 :094 > list.next
 => "黑暗靈魂2"
2.7.3 :095 > list.next
 => "血源詛咒"
2.7.3 :096 > list.next
 => "黑暗靈魂3"
2.7.3 :097 > list.next
StopIteration (iteration reached an end)

可以利用next方法進行分次枚舉,也是利用到昨天的yield原理。
一樣看程式碼比較有感覺。

2.7.3 :098 > games = Object.new
 => #<Object:0x00007f9b6e3f07a8>

2.7.3 :099 > def games.each
2.7.3 :100 >   p "惡魔靈魂先出在PS3"
2.7.3 :101 >   yield
2.7.3 :102 >   p "黑暗靈魂也出在PS3"
2.7.3 :103 >   yield
2.7.3 :104 >   p "黑暗靈魂2也先出在PS3"
2.7.3 :105 >   yield
2.7.3 :106 >   p "血源詛咒出在PS4"
2.7.3 :107 >   yield
2.7.3 :108 >   p "黑暗靈魂3出在PS4"
2.7.3 :109 >   yield
2.7.3 :110 >   p "下一款叫做Elden Ring"
2.7.3 :111 > end
 => :each

2.7.3 :112 > test = games.to_enum
 => #<Enumerator: #<Object:0x00007f9b6e3f07a8>:each>
2.7.3 :113 > test.next
"惡魔靈魂先出在PS3"
 => nil
2.7.3 :114 > test.next
"黑暗靈魂也出在PS3"
 => nil
2.7.3 :115 > test.next
"黑暗靈魂2也先出在PS3"
 => nil
2.7.3 :116 > test.next
"血源詛咒出在PS4"
 => nil
2.7.3 :117 > test.next
"黑暗靈魂3出在PS4"
 => nil
2.7.3 :118 > test.next
"下一款叫做Elden Ring"
StopIteration (iteration reached an end)

看來是很想玩game了


第五天的Leetcode350:Intersection of Two Arrays II
但是先看一下它的前一題Leetcode349:Intersection of Two Arrays

349的題目連結:https://leetcode.com/problems/intersection-of-two-arrays/
題目重點:取兩個陣列交集的元素,必須uniq。

# @param {Integer[]} nums1
# @param {Integer[]} nums2
# @return {Integer[]}
def intersection(nums1, nums2)

end

有時間可以研究迴圈怎麼跑,可以幫助思考怎麼枚舉,沒時間就用Ruby內建方法。

2.7.3 :001 > [2, 5, 6, 3, 8] & [1, 2, 4, 7 ,4]
 => [2]

所以答案當然是

def intersection(nums1, nums2)
  nums1 & nums2
end

可以於Array類別內看到&這方法。

的原理。

  nums1.uniq - ( nums1.uniq - nums2.uniq )

另外補充:intersection

2.7.3 :122 > [1, 2, 4, 6, 7].intersection([4, 6])
 => [4, 6]
2.7.3 :123 > [1, 2, 4, 6, 7].intersection([4, 6], [1,7])
 => []
2.7.3 :124 > [1, 2, 4, 6, 7].intersection([1, 4, 6], [1, 4, 7])
 => [1, 4]

350的題目連結:https://leetcode.com/problems/intersection-of-two-arrays-ii/
題目重點:交集數不再是uniq,例如都有兩個2,就是要出現2次。

# @param {Integer[]} nums1
# @param {Integer[]} nums2
# @return {Integer[]}
def intersect(nums1, nums2)

end

邏輯大概如下

 $    #指針又出來了
[4, 9, 5] #arr1

[9, 4, 9, 8, 4] #arr2

{} #準備一個記錄器

#開跑
 $    #下面的陣列,我有一個4,你有沒有4?
[4, 9, 5]

[9, 4, 9, 8, 4].count(4)  #=>2  有,2個。

{4 => {arr1: 1, arr2:2}} #紀錄下來


#繼續
    $    #下面的陣列,我一個9,你?
[4, 9, 5]

[9, 4, 9, 8, 4].count(9)  #=>2  有,2個

{4 => {arr1: 1, arr2:2}, 9 => {arr1: 1, arr2: 2}} #紀錄下來

#繼續
       $    #下面的陣列,我一個5,你?
[4, 9, 5]

[9, 4, 9, 8, 4].count(5)  #=>0 ,沒有。

{4 => {arr1: 1, arr2:2}, 9 => {arr1: 1, arr2: 2}} #沒有就不必紀錄。

#不用繼續了,跑完了,即使arr2有不同元素,也成為不了交集。

#分析將
{4 => {arr1: 1, arr2:2}, 9 => {arr1: 1, arr2: 2}}
#成為一個陣列

4是交集,arr2出現2次,arr1出現一次,依照規則是取小的。
所以像是

2.7.3 :125 > x = [2 , 1].min
 => 1
2.7.3 :129 > [].push(4)*x
 => [4]
#9以此類推。

349讓我們暸解交集這件事,那我們就省去了檢測這件事了。

def intersect(nums1, nums2)
  arr = []
  (nums1 & nums2).each do |num|
    arr += Array.new([nums1.count(num), nums2.count(num)].min , num)
  end
  arr
end

#原理
2.7.3 :130 > [2, 1].min
 => 1
2.7.3 :131 > Array.new(5, 1)
 => [1, 1, 1, 1, 1]
#提醒下面寫法不行
2.7.3 :144 > [] += Array.new(4) #噴錯
2.7.3 :142 > arr = []
 => []
2.7.3 :143 > arr += arr << 4
 => [4, 4] #資料不正確。

記得回傳arr
用map也可以,一樣還是要回傳arr,所以我用each。


今日有提到的
1.迭代、 枚舉
中間提到的動物問題真的是考古題喔!
2.Leetcode350:Intersection of Two Arrays II


明日利用&做一個很陽春的名字檢驗器,以及分享幾題考古題。


上一篇
D-26.Block、Proc、lambda && Valid Perfect Square
下一篇
D-24. attr_accessor 、類別變數與實體變數差異 && Minimum Moves to Equal Array Elements
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言