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


明日利用&做一个很阳春的名字检验器,以及分享几题考古题。


<<:  Day5. 活用Hash,掌握资料处理的诀窍

>>:  @Day5 | C# WixToolset + WPF 帅到不行的安装包 [变更UI预设介面]

[学习笔记] element ui upload 限制与提示

参考文章:Element UI 上传档案 el-upload —— 手动上传档案,限制上传档案数量,...

【Day 22】 Swift 5.5 Async/await 新特性

由於今天比较晚才到家,所以可能内容会比较少一点。 另外今天在火车上看到github上面有几个开源的 ...

[Day10] TS:什麽!Conditional Types 中还能建立型别?使用 infer 来实作 ReturnType 和 Parameters

今天会来说明 TypeScript 中内建 ReturnType 和 Parameters 的原始...

Day 2 - 如何运用sail快速建置Laravel 8.0

观看Laravel 8.0的官方文件教学,可以看到一个新的东西就是我们这次要介绍的Sail,用起来非...

机器学习:模型训练架构

分散式运算架构 MapReduce 利用函式语言程序设计的概念,将分散运算分为映射(Map)和归纳...