今天我们会介绍Hash
,Hash
中文为杂凑,不过汉汉老师还是习惯念英文。
看完这篇文章,读者即将会学到
Struct
and OpenStruct
hash
的赋值很简单。如下所示,只要给key
, value
即可。
data = {}
data[:a] = 1
data
#=> { :a=>1 }
阵列也可以用Hash的写法来写,但不实用
data = []
data[2] = 'qwer'
data
#=> [nil, nil, "qwer"]
若宣告深层的hash
的话,就会报错
data = {}
data[:a][:b] = 1
# Traceback (most recent call last):
# NoMethodError (undefined method `[]=' for nil:NilClass)
可以用比较更难的notation
来达成宣告深层Hash
的目的
data = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
data[:a][:b] = 1
#=> {:a=>{:b=>1}}
接着我们用实际的例子,说明宣告深层Hash
的原理
# 可以宣告2阶
hash = Hash.new { |h,k| h[k] = {} }
# 可以宣告3阶
hash = Hash.new { |h,k| h[k] = Hash.new { |h,k| h[k] = {} } }
# 可以宣告n阶
hash = Hash.new{ |h,k| h[k] = Hash.new(&h.default_proc) }
除了Array以外,Hash可以做到展开。Hash的展开以两个星号**
表示。
a = {a: 1}
b = {b: 2}
c = {c: { c: 1 }}
{**a, **b} #=> {a: 1, b: 2}
a.merge(b) #=> {a: 1, b: 2}
{**a, **c} #=> {:a=>1, :c=>{:c=>1}}
a.merge(c) #=> {:a=>1, :c=>{:c=>1}}
**
在某种意义上跟 merge
很像,就像在Array中*
之余concat
一边写Javascript
的读者们,不知道会不会不小心写成这样 ?
{...a, ...b}
我们举实际的例子,我们对data_attr
做Hash层面的展开,而这里可以会有两个不懂的地方
.()
到底是什麽 ➡️ Day8-10
会揭开谜底Day26-27
揭开谜底def tab_list(list)
# Day9 会讲到 block 与 Procedure
data_attr = -> (content) { content.try(:[], :data).presence || {} }
content_tag :ul, class: 'nav nav-tabs', role: 'tablist' do
list.each_with_index.map do |content, index|
if index.zero?
content_tag(:li,
content_tag(:a, content[:wording], href: "##{content[:id]}-tab",
class: 'nav-link active', data: { toggle: 'tab', **data_attr.(content) },
aria: { controls: "#{content[:id]}-tab", selected: 'true' }),
class: 'nav-item', role: 'presentation')
else
content_tag(:li,
content_tag(:a, content[:wording], href: "##{content[:id]}-tab",
class: 'nav-link', data: { toggle: 'tab', **data_attr.(content) },
aria: { controls: "#{content[:id]}-tab", selected: 'false' }),
class: 'nav-item', role: 'presentation')
end
end .join.html_safe
end
end
# helper
module Admin::UnshippedOrdersHelper
def unshipped_tab
[
{ id: 'unshipped', wording: '待出货', data: {a: 1, b: 2, c: 3} },
{ id: 'not_arrived', wording: '未取/未送达,需重新出货' },
]
end
end
Javascript
有对object
的解构,当然Ruby
对 Hashes
也会有!
hash = {:a => 1, :b => 2, :c => 3}
a, b = hash.values_at(:a, :b)
a # => 1
b # => 2
Javascript
有对object
的解构,当然Ruby
对 Hashes
也会有!
hash = {:a => 1, :b => 2, :c => 3}
a, b = hash.values_at(:a, :b)
a # => 1
b # => 2
如果不能确定hash
是否有可能为空值的话,可以写成下列形式
a, b = (hash || {}).values_at(:a, :b) #=> [nil, nil]
a # => nil
b # => nil
Day4 的篇章结尾已讲过,记得要复习。很重要!
Day2 提到 save navigator
➡️ &
。hash
的话不能使用&
,但可以使用try
的方式救回
{a: 1, key: 3}.try(:[], :key) #=> 3
{a: 1}.try(:[], :key) #=> nil
[{a:1}, {b:2}][2].try(:[], :qwer) #=> nil
[{a:1}, {b:2}][2].try(:[], :qwer) #=> nil
[{a:1}, {b:2}][1].try(:[], :b) #=> 2
⭐️ 取运货单好时使用的方法
if sub_order.sf_taken_at.nil?
# 用白话文比对: response[:routes][0][:occured_at]
response[:routes][0].try(:[], :occurred_at)
end
相比於Javascript
,Ruby
的hash
并没有dot notation
,不觉得这种事情很让人在意吗?尤其是在两个语言中间切换的时候,在Ruby
写却会常常报错。
const a = {animal: 'cat'}
a['animal'] // cat
a.animal // cat
在Ruby
程序语言中,Hash
没有办法使用dot notation
的形式。
a = {animal: 'cat'}
#=> {:animal=>"cat"}
a.animal
# Traceback (most recent call last):
# NoMethodError (undefined method `animal' for {:animal=>"cat"}:Hash)
a[:animal]
#=> "cat"
其实Ruby
有个介於Hash
和自定义class
中间的型别,叫做Struct
。Struct
可以模拟一个 class
物件,至於class
的话会在Day11介绍。
Animal = Struct.new(:species)
animal = Animal.new('cat')
animal.species # cat
另一个用法为OpenStruct
require 'ostruct'
cat = OpenStruct.new(species: "cat")
# 读取
cat.species # => "cat"
cat[:species] # => "cat"
cat["species"] # => "cat"
# 存入
cat.species = "Dog" # => "Dog"
cat[:species] = "Dog" # => "Dog"
cat["species"] = "Dog" # => "Dog"
# 像hash一样,可以新增属性
cat.foot = 4
cat.foot
# hash 转 OpenStruct 的应用
cat = {species: "cat"}
cat = OpenStruct.new(cat)
OpenStruct
的使用方式,几乎就和使用 Javascript
一样,不过OpenStruct
一直有会拖慢速度的诟病。如果是大型专案,能避免就避免,但若为快速接案,要用真的可以。
⭐️ 若OpenStruct
作为Api使用会踩到一些雷。目前在回传值上,遇到会被多包一层:table
的状况
table: {
return_amount: 5
return_cash_amount: 5
return_rebate_amount: 0
}
我将原本的结果加上
original_openstruct_instance&.as_json.try(:[], 'table')
可以深挖hash
,若不存在回传nil
。虽然用法有点丑,不过这方法很好用
a = {a: {a: {a: {a: {a: {a: 1}}}}}}
a.dig(*%i(a a a a a a))
#=> 1
a.dig(*%i(a a))
#=> {:a=>{:a=>{:a=>{:a=>1}}}}
a.dig(*%i(a a b))
#=> nil
fetch 可以用在回应不到目标 key 时回传预设 key
h = {
'a' => :a_value,
'b' => nil,
'c' => false
}
h.fetch('a', :default_value) #=> :a_value
h.fetch('b', :default_value) #=> nil
h.fetch('c', :default_value) #=> false
h.fetch('d', :default_value) #=> :default_value
slice
跟 slice!
是rails
提供的方法。顺带一提,惊叹号在Ruby
的程序语言中称为bang!
,代表会破坏原本的结构。
{ a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
# => {:a=>1, :b=>2}
option = [:a, :b]
{ a: 1, b: 2, c: 3, d: 4 }.slice(*option)
注意slice
, slice!
回传的结果不一样。
> {a: 1, b: 2, c: 3}.slice(:a)
=> {:a=>1}
> {a: 1, b: 2, c: 3}.slice!(:a)
=> {:b=>2, :c=>3}
without
就是except
的别称,不过 except
比较常拿来被使用。
h = { :a => 1, :b => 2, :c => 3 }
h.without(:a) #=> { :b => 2, :c => 3 }
h #=> { :a => 1, :b => 2, :c => 3 }
h.without(:a, :c) #=> { :b => 2 }
h.without!(:a, :c) # { :b => 2 }
h #=> { :b => 2 }
merge
同样有bang
跟没有的版本!
# merge!
h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }
h1.merge!(h2) #=> {"a"=>100, "b"=>254, "c"=>300}
h1 #=> {"a"=>100, "b"=>254, "c"=>300}
# merge!
h1 = { "a" => 100, "b" => 200 }
h2 = { "b" => 254, "c" => 300 }
h1.merge!(h2) { |key, v1, v2| v1 }
#=> {"a"=>100, "b"=>200, "c"=>300}
h1 #=> {"a"=>100, "b"=>200, "c"=>300}
# merge!
h = {}
h.merge!(key: "bar") # => {:key=>"bar"}
题外话,Rails Controller 里面的ActionController::Parameters
物件,也可以被视为Hash
操作,所以也可以使用merge
_params
#=> <ActionController::Parameters {"status_eq"=>"", "payment_status_eq"=>"", "shipping_type_eq"=>"", "created_at_gteq"=>"2020-10-05", "created_at_lt"=>"2021-10-02", "stores_id_eq"=>"", "number_or_receiver_phone_or_receiver_name_or_customer_phone_or_customer_name_cont"=>"", "sync_pos_at_not_null"=>"", "invoice_status_eq"=>""}
_params.merge(status_eq: "unpaid")
#=> <ActionController::Parameters {"status_eq"=>"unpaid", "payment_status_eq"=>"", "shipping_type_eq"=>"", "created_at_gteq"=>"2020-10-05", "created_at_lt"=>"2021-10-02", "stores_id_eq"=>"", "number_or_receiver_phone_or_receiver_name_or_customer_phone_or_customer_name_cont"=>"", "sync_pos_at_not_null"=>"", "invoice_status_eq"=>""}
merge! 会改变Hash
键的值,我们可以用reverse_merge
防止改变已经存在的key
值
hash_one = { a: 1, b:2 }
hash_one.merge({ a:2, b:3 }) # => { a:2, b:3 }
hash_one = { a: 1, b:2 }
hash_one.reverse_merge({ a:2, b:3, c:3 }) # => { a:1, b:2, c:3 }
检查 hash 是否有 nil
{a: 1, b: 2}.all? {|k,v| !v.nil?} #=> "true"
{a: 1, b: nil}.all? {|k,v| !v.nil?} #=> "false"
将hash
所有的key
转为符号
# key 转为符号 (Ruby 2.5 以上可以用)
my_hash.transform_keys(&:to_sym)
# key 转为字串 (Ruby 2.5 以上可以用)
my_hash.transform_keys(&:to_s)
# 旧写法
my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
# Rails可以使用
my_hash.symbolize_keys
my_hash.deep_symbolize_keys
Hash 也可以拿来做递回运算,只要用 each
或map
就行!
Invoice.statuses
#=> {"unissued"=>0, "issued"=>1, "allowance"=>2, "allowance_failed"=>3}
Invoice.statuses.map {1}
#=> [1, 1, 1, 1]
Invoice.statuses.map { |k, v| [k,v] }
#=> [["unissued", 0], ["issued", 1], ["allowance", 2], ["allowance_failed", 3]]
下列为Rails 6
提供的方法,可以将hash
包含深层的value
做转换。下列的情境是要将以下hash
的资料做省略符号跟大写开头
hash = {a: {b: "rewyeryewry", c: "weryewrewry"}, d: {e: "saelouertewryteryewrttwerytrewyn"}, f: ["reaergergdieweqrtqwteng", "ergehrerheerhehherdheherherhewrhewhrehrhehehrerehherhrehreng"]}
hash.deep_transform_values(&:capitalize).deep_transform_values{ |attribute| attribute.truncate(6) }
#=> {:a=>{:b=>"Rew...", :c=>"Wer..."}, :d=>{:e=>"Sae..."}, :f=>["Rea...", "Erg..."]}
实际上,当我收到了资料内容比较多的Array of Hash
,这时候就可以使用对每一笔的value
加省略符号的动作
layout_params.map { |data| data.deep_transform_values {|attribute| attribute.is_a?(String) ? attribute.truncate(3) : attribute} }
#=> [
# {"layout_type"=>"...", "store_landing_elements"=>[{"element_type"=>"...", "panel_type"=>"...", "photo"=>{"url"=>"..."}}]},
# {"layout_type"=>"...", "store_landing_elements"=>[{"element_type"=>"...", "panel_type"=>"...", "photo"=>{"url"=>"..."}}]},
# {"layout_type"=>"...", "store_landing_elements"=>[{"element_type"=>"...", "panel_type"=>"...", "id"=>"2", "content"=>"..."}]},
# {"...}]
deep_transform_values
可以广泛的应用在专案当中,因此一并介绍给读者
大部分的重点,在Day4便已经讲完,因此今天的篇幅比较少,但这里还是整理一些重点
each
, map
明天会介绍Array
, Hash
之间的关系。
>>: D-25. 枚举(enumerate) && Intersection of Two Arrays II
Arrow Function 这个从 ES6 开始新增的一种写法,叫做 Arrow Function...
Actual Microsoft DA-100 Dumps – Quickest Way to Ge...
Iteration forEach()、every()、some() 三者的差异在於:他们会对我们...
在昨天建置vue-cli插件时我们有新增vuex和vue-router,所以今天要先来介绍vue-r...
Hi Da Gei Ho~ 初次见面,我是Winnie~ 我是一位刚转职六个月的菜鸟前端(前身是网页...