接下来Day4-6
的用法,都是由Ruby
的Enumerable
。Enumerable
是Ruby
相当强大的库,专门处理集合资料的递回处理。
今天我们要介绍的是Array
,Array
最基本的用法为map
, each
,map
, each
与javascript
的map
, forEach
概念相同
each
回传的值仍是原值map
回传的值为处理过後的值[1, 2, 3].each {|_| _ + 1} #=> [1, 2, 3]
[1, 2, 3].map {|_| _ + 1} #=> [2, 3, 4]
那如果懂了each
, map
以後,其他的我们继续看下去。
Ruby
可以宣告重复值的阵列
Array.new(10)
#=> [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]
Array.new(10, 'a')
#=> ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a"]
此外,还可以使用下列的表示法宣告
[nil]*10 #=> [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]
%w[a]*10 #=> ["a", "a", "a", "a", "a", "a", "a", "a", "a", "a"]
除了%Q
以外,与阵列相关的方法有%w
, %i
。%w
, %i
是Ruby
相当好用的方法。用%w/%i
包覆的方法可以不用双引号
# 可以使用中括号,也可以使用小括号
%i[a b c d e] #=> [:a, :b, :c, :d, :e]
# 可以使用中括号,也可以使用小括号
%w[a b c d e] #=> ["a", "b", "c", "d", "e"]
compact 可以滤除空值。
[1, 2, nil, ""].compact #=> [1, 2, ""]
map
搭配next
会回传nil
,所以我们可以搭配 compact
做变化
(1..10).map do |_|
next if _.even?
_*100
end .compact
#=> [100, 300, 500, 700, 900]
今天文章的最尾端会讲到reducer
,看完reducer
再回头看compact
,就可以想像得到如果自己用reducer
来刻画 compact
⭐️ 上面的compact
,可以用select(&:itself)
代替
[1, 2, nil, ""].compact #=> [1, 2, ""]
[1, 2, nil, ""].select(&:itself) #=> [1, 2, ""]
:itself
回传的值为物件本身,而select
会筛选 nil
,因此select(&:itself)
会将空值去除
1.itself #=> 1
[1, 2, nil, ""].map(&:itself) #=> [1, 2, nil, ""]
⭐️ 若我们要取得第一个非空值,可以使用find(&:itself)
[nil, nil, 1, 2, nil, ""].find(&:itself) #=> 1
当我们要在阵列取得特定值,除了写成array[2]
取单值以外,还可以使用逗点或者范围的方式取多值。
%w[q w e r t][1, 3]
#=> ["w", "e", "r"]
%w[q w e r t][1..3]
#=> ["w", "e", "r"]
%w[q w e r t][1...3]
#=> ["w", "e"]
splat
意即展开 array
,可以作为浅拷贝用
include_models = [:shipments, :return_order, order: :customer]
current_brand.sub_orders.includes(*include_models)
# 等同於
current_brand.sub_orders.includes(:shipments, :return_order, order: :customer)
Rails 的 ActiveRecord_Relation
类别,也可以参与Array
的splat
IgImage.order(id: :asc).limit(10).class
#=> IgImage::ActiveRecord_Relation
[*IgImage.order(id: :asc).limit(10), 1,2,3]
#=> [#<IgImage:0x00007ff78b9e65a8...>, #<IgImage:0x00007ff78b9e6468...>, 1, 2, 3]
Ruby
对於Array
的解构子
x, y = [1, 2, 3]
x # => 1
y # => 2
first, *rest = [1, 2, 3]
first # => 1
rest # => [2, 3]
以下为列出关於 Array
的逻辑操作
x = [1, 1, 2, 4]
y = [1, 2, 2, 2]
# intersection
x & y # => [1, 2]
# union
x | y # => [1, 2, 4]
# difference
x - y # => [4]
# intersection
[ 1, 1, 3, 5 ] & [ 3, 2, 1 ] #=> [ 1, 3 ]
[ 'a', 'b', 'b', 'z' ] & [ 'a', 'b', 'c' ] #=> [ 'a', 'b' ]
# difference
[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
使用 Array
将阵列凑成堆,可以做转换 Hash
用
students = ["Steve", "John", "Kim", "Gloria", "Sam"]
#=> ["Steve", "John", "Kim", "Gloria", "Sam"]
ages = [14, 12, 2, 23, 4]
#=> [14, 12, 2, 23, 4]
students.zip(ages)
#=> [["Steve", 14], ["John", 12], ["Kim", 2], ["Gloria", 23], ["Sam", 4]]
flatten
用来摊平 array
,若不给数字会全瘫。
num = [1, [2, 3], [4, [5, 6]]]
num.flatten #=> [1, 2, 3, 4, 5, 6]
num.flatten(1) #=> [1, 2, 3, 4, [5, 6]]
Array
可以做搜寻用
⭐️ 找开头为 70
的值
response = {}
response[:routes] = [
{:opcode=>"54", :remark=>"收取快件", :occurred_at=>"2021-07-12 16:32:59"},
{:opcode=>"30", :remark=>"寄到驿站了", :occurred_at=>"2021-07-12 18:57:46" },
{:opcode=>"31", :remark=>"到达台湾桃园机场】", :occurred_at=>"2021-07-12 20:42:02"},
{:opcode=>"204", :remark=>"正在派送途中", :occurred_at=>"2021-07-13 08:34:09"},
{:opcode=>"80", :remark=>"您的快件代签收", :occurred_at=>"2021-07-13 11:21:47"},
{:opcode=>"70-55", :remark=>"您的快件代签收", :occurred_at=>"2021-07-13 11:21:47"},
{:opcode=>"8000", :remark=>"结单", :occurred_at=>"2021-07-13 11:21:48"}
]
# 失败时间
failed_at = response[:routes].find {|r| r[:opcode].start_with? '70'}.try(:[], :occurred_at)
#=> "2021-07-13 11:21:47"
⭐️ 找寻不是nil
的第一个元素
group_list.find { |x| !x["list"].blank? }
group_list.find(&:itself)
group_list.find{|x|!x.nil?}
group_list.compact.first
Array
可以做筛选用
⭐️ select
为正向的 filter
[1, 2, 3, 4, 5].select(&:even?) # => [2, 4]
⭐️ reject
为负向的 filter
[1, 2, 3, 4, 5].reject { |v| v.even? } #=> [1, 3, 5]
⭐️ 可以处理 Array of Strings
grep_v
fruit = ["apple", "orange", "banana"]
#===== a 开头
fruit.grep(/^a/)
#=> ["apple"]
#===== e 结尾
fruit.grep(/ap$/)
#=> ["apple", "orange"]
#===== 含有dis字眼的值
Order.column_names.grep(/dis/)
#=> ["district",
# "discount_detail",
# "distribution_bonus",
# "vip_discount",
# "diamond_discount_price",
# "diamond_discount",
# "customer_vip_disc_pct"]
objects = ["a", "b", "c", 1, 2, 3, nil]
objects.grep(String)
# ["a", "b", "c"]
objects.grep(Integer)
# [1, 2, 3]
objects.grep(NilClass)
# [nil]
objects.grep_v(NilClass)
# ["a", "b", "c", 1, 2, 3]
[1, :a, 2, :b].grep(Symbol) # => [:a, :b]
[1, :a, 2, :b].grep(Numeric) { |v| v + 1 } # => [2, 3]
uniq
可以将重复的值滤除掉。
[1, 2, 3, 1, 1, 2].uniq
# => [1, 2, 3]
(1..10).uniq { |v| v % 5 }
# => [1, 2, 3, 4, 5]
# 除後的结果为 [1, 2, 3, 4, 5, 1, 2, 3, 4, 5],再进行 uniq
对集合做询问的动作
all?
,any?
为Ruby
常见的两种方法
# all?
[true, true, true].all? #=> true
[true, false, true].all? #=> false
# any?
[true, false, true].any? #=> true
[1, 2, 3].any? {|_| _.even?} #=> true
[1, 2, 3].all? {|_| _.even?} #=> false
⭐️ 该笔子订单的母订单底下的所有子订单,是否状态为 完成
或 退货
# sub_order 为某张子订单,为instance (object)
sub_order.order.sub_orders.pluck(:status).all?{ |_| _.in? [Status::DONE.to_s, Status::RETURNED.to_s] }
询问动作还有 #none?
, #include?
等用法,#include?
在Day3已经介绍过了,而#none?
就为字面上的意思,即为全部都没有回传true
排序也是Enum很实用的方法
Order.all.sort_by(&:price) #=> 订单价格由小排大
以上这些方法在算最佳解、最便宜单价等地方可以用,为Ruby
相当实用的方法之一
#===== #max
[1, 2, 3].max
# => 3
[1, 2, 3].max { |a, b| b <=> a }
# => 1
[1, 2, 3].max(2)
# => [3, 2]
#===== #min
[1, 2, 3].min
# => 1
#===== #minmax
[1, 2, 3, 4, 5].minmax
# => [1, 5]
[1, 2, 3].count #=> 6
使用tally
,可以省却非常多的程序码。
%w(a b b c c c d).group_by(&:itself).map { |k, vs| [k, vs.size] }.to_h
#=> {"a"=>1, "b"=>2, "c"=>3, "d"=>1}
%w(a b b c c c d).each_with_object(Hash.new(0)) { |key, hash| hash[key] += 1 }
#=> {"a"=>1, "b"=>2, "c"=>3, "d"=>1}
%w(a b b c c c d).each_with_object(Hash.new(100)) { |key, hash| hash[key] += 1 }
#=> {"a"=>101, "b"=>102, "c"=>103, "d"=>101}
#======= 用tally可以简单做到
%w(a b b c c c d).tally
#=> {"a"=>1, "b"=>2, "c"=>3, "d"=>1}
至於each_with_object
的用法,接下来就会开始介绍
将判断结果为true
放一边,false
放在另外一边
[1, 2, 3, 4].partition(&:even?)
# => [[2, 4], [1, 3]]
⭐️ 可以应用在分主/附图、主信用卡/其他信用卡,是蛮好用的功能
将同一群的放在一边。以下又使用:itself
做为例子,是为了让读者更明白:itself
不只可以当作回传本身物件使用这麽没用,还可以与Enumable
, ActiveRecord
做为搭配。
%w(a b b c c c d).group_by(&:itself)
#=> {"a"=>["a"], "b"=>["b", "b"], "c"=>["c", "c", "c"], "d"=>["d"]}
#===== group_by 第一个字元
%w(apple banana bear cat car cap dude).group_by{ |_| _.first }
#=> {"a"=>["apple"], "b"=>["banana", "bear"], "c"=>["cat", "car", "cap"], "d"=>["dude"]}
Javascript 的reduce
是对阵列处理中最难理解的,同样的,ruby 的inject
, reduce
也不好理解。
# 数字使用 reduce
(5..10).reduce(:+) #=> 45
# 数字使用 inject
(5..10).inject { |sum, n| sum + n } #=> 45
# 从1开始乘
(1..5).reduce(1, :*) #=> 120
# 从2开始乘
(1..5).reduce(2, :*) #=> 240
# 从1开始乘
[2, 3, 4].inject(1) {|product, i| product*i } # => 24
# 从10开始乘
[2, 3, 4].inject(10) {|product, i| product*i } # => 240
# inject 可以使用 block
(1..5).inject(1) { |product, n| product * n } #=> 120
# 找最长的文字 #=> "sheep"
longest = %w{ cat sheep bear }.inject do |memo, word|
memo.length > word.length ? memo : word
end
汉汉老师列了很多reduce
, inject
的用法,有没有发现用法很像吗? 其实reduce,
inject`是一样的东西,并没有任何差别。
请问读者看到这里有被耍的感觉吗? 汉汉老师只是想要让读者印象比较深刻。
reduce
, inject
两个词本来就比较不直觉,尤其是reduce
这个词,根本跟「集合」单词打不上关系。
reduce
的词是源自於 functional programming
的 reducer
,维基百科定义的 reducer
是:
A reducer is the component in a pipeline that reduces the pipe size from a larger to a smaller bore (inner diameter).
我们搭配下列的例子来更了解reduce/inject
的用法
# 递减处理
(1..5).inject { |sum, n| sum + n } #=> 45
(1..5).inject(0) { |sum, n| sum + n } #=> 45
假设我的身体没有任何药物0
,第一次打1剂、第二次打2剂、...、第五次打5剂,共打了45剂,这就是inject
的意思,而reducer
的意思为逐步的缩小阵列的处理范围,动词称为reduce
(1..5).inject(0) { |sum, n| sum + n } #=> 45
ruby
有很多方法是用reduce
实作的,下列为常见使用reduce`实作的用法
[1, 2, 3].sum # => 6
[1, 2, 3].min # => 1
[1, 2, 3].max # => 2
[1, 2, 3].count # => 3
each_with_object
的用法与前面提到的reduce/inject
用法很像,接着我们来比较 each_with_object
, inject
的用法
#======= inject =======#
%w{foo bar blah}.inject({}) do |hash, string|
hash[string] = "something"
hash # 需要回传运算结果
end
#=> {"foo"=>"something" "bar"=>"something" "blah"=>"something"}
#======= each_with_object =======#
%w{foo bar blah}.each_with_object({}){|string, hash| hash[string] = "something"}
#=> {"foo"=>"something", "bar"=>"something", "blah"=>"something"}
#======= inject =======#
MainOrder.payment_types
.inject({}) {|h, d| h[d.last.to_s] = I18n.t("main_orders.payment_type.#{d.first}"); h }
#=> {"1"=>"信用卡", "2"=>"小品点", "3"=>"ATM", "4"=>"PayEasy", "5"=>"超商付款", "6"=>"7-11付款", "7"=>"银联卡", "8"=>"Eslite Pay"}
#======= each_with_object =======#
MainOrder.payment_types
.each_with_object({}) {|h, d| h[d.last.to_s] = I18n.t("main_orders.payment_type.#{d.first}")}
#=> {"1"=>"信用卡", "2"=>"小品点", "3"=>"ATM", "4"=>"PayEasy", "5"=>"超商付款", "6"=>"7-11付款", "7"=>"银联卡", "8"=>"Eslite Pay"}
each_with_object
会顺道回传hash
值,不用像hash
特地回传值
注意:inject
, each_with_object
的block
带入值
h
, hash
)虽然inject
, each_with_object
万用,但两者还是有用法上的适应性
Hash
, Array
⭐️ zip
与 each_with_object
的组合技
students = ["Steve", "John", "Kim", "Gloria", "Sam"]
ages = [14, 12, 2, 23, 4]
students.zip(ages).each_with_object({}) { |pair, hsh| hsh[pair[0]] = pair[1] }
#=> {"Steve"=>14, "John"=>12, "Kim"=>2, "Gloria"=>23, "Sam"=>4}
集合引入索引值
也很简单,只要用each.with_index
, map.with_index
即可。
[11, 22, 31].each_with_index { |val,index| puts "index: #{index} for #{val}" if val < 30}
# index: 0 for 11
# index: 1 for 22
#=> [11, 22, 31]
[11,22,31].each.with_index { |val,index| puts "index: #{index} for #{val}" if val < 30}
# index: 0 for 11
# index: 1 for 22
#=> [11, 22, 31]
下列为 map
搭配 index
的用法
[11,22,31].map.with_index { |val, index| [val, index]}
#=> [[11, 0], [22, 1], [31, 2]]
我们可以使用下列方法来range
和#map
。
(1..rand(10)).map(&:itself)
#
#=> [1]
#=> [1, 2]
#=> ...
#=> ...
#=> [1, 2, 3, 4, 5, 6, 7, 8]
取出随机值,并将该值从原本的阵列拿出来。
variant_ids = Variant.limit(10).pluck(:id)
#=> [1, 2, 131, 132, 133, 134, 135, 136, 137, 138]
variant_id = variant_ids.sample #=> 134
variant_ids = variant_ids.reject {|id| id == variant_id }
#=> [1, 2, 131, 132, 133, 135, 136, 137, 138]
variant_id = variant_ids.sample #=> 2
variant_ids = variant_ids.reject {|id| id == variant_id }
#=> [1, 131, 132, 133, 135, 136, 137, 138]
今天介绍了除了map
, each
以外,很多不一样的用法。这些方法都很好用,但其中几个概念更为重要。
:itself
不只可以用来回传本身Enumable
方法的原型each_with_object({})
, each_with_object([])
的用法明天会开始讲Hash
addEventListener 事件监听 JavaScript 是一个事件驱动 (Event-dr...
这次使用的元件是1.54inch_e-paper_b (黑白红显示) Pin Layout VCC ...
随着科技进步与智能设备的普及,越来越多人使用智能手机或者手提电脑搜寻网络上的资料。2019年Goog...
还记得第2天在做专案规划时,有提到一个目标「加密敏感资料实现资安管理」吗? 目前我们的密码还是一样...
view 的渲染 在这几天小光认识了dotnetcore的网页开发相关知识,从请求流水线、路由到过滤...