[ruby] Ruby中的模式匹配

321 阅读3分钟

Ruby中的模式匹配从2.7引入,通过in操作符实现,匹配成功可赋值。匹配成功,整个模式匹配返回true;匹配失败,整个模式匹配返回false

格式

Ruby中的模式匹配有2种格式:

  1. 单独使用in
  2. case in

单独使用in

值与值的匹配、值与类型的匹配、常量格式匹配、数组格式匹配、hash格式匹配

可做变量的解构赋值,失败会抛异常,如:

0 in 0 # 成功,值与值的匹配
[2, 3] in [2, 3] # 成功, 值与值的匹配
{one: 1, two: 2} in {one: 1, two: 2} # 成功,值与值的匹配
0 in Integer # 成功, 值与类型的匹配
[2, 3] in Array # 成功,值与类型的匹配
{one: 1, two: 2} in {one: 1, two: Integer} # 成功,值与类型的匹配
0 in a # 成功,常量格式匹配,a的值为0
[1, 2] in b # 成功,常量格式匹配,b的值为[1, 2]
[1, 2, 3, 4] in [one, two, *three] #成功,数组格式匹配,one值为1, two值为2,three值为[3, 4]
[0, [1, 2, 3]] in [one, [two, *three]] #成功,数组格式,one值为0, two值为1,three值为[2, 3]
{one:1, two:2, three:3} in {one:o, three:t} # 成功,hash格式匹配,o值为1,t值为3
{one: 1, two:2, three:3} in {one:, three:} # 成功,注意,key和value相同的时候,可以省略value
{one: 1, two:2, three:3} in {one:, **rest} # 成功,使用**KEY表示剩余的匹配,rest值为{two:2, three:3}

0 in 1 # 失败
[2, 3] in [1, 2] # 失败

匹配时检测类型

0 in Float => a  # 匹配失败
0 in Integer => a # 成功

langs = %w[perl shell ruby]
langs in [String => a, String => b, c]
langs in [Integer => a,*other] # 匹配失败
# hash格式时可使用key: Type方式
hs = {one: 1, two: 2, three: 3}
hs in {one: Integer => a, two: Integer => b, **rest}
# a的值为1
# b的值为2
# rest的值为{three: 3}

## 注意,下面仅仅只是对value的类型进行了匹配,没有为value绑定变量
hs in {one: Integer, three: Integer} 

丢弃不要的值

模式匹配时,可使用_(下划线)表示不需要的值,如:

[0, [1, 2]] in [0, [1, _] => a] # 成功,a的值为[1, 2]
[2,3,4] in [a,b,_] # 成功,a的值2b的值为3 

数组匹配与hash匹配的时候,可以使用*和**表示不需要的值,如:

[1,2,3] in [Integer, *]
{a: 1, b: 2, c: 3} in {a:, **}

case in 匹配

语法格式:

case <value>
in <pattern 1>
  ...
in <pattern 2>
  ...
else
  ...
end

匹配成功,执行对应的分支;匹配失败,如果有else分支,执行else分支,否则抛错误NoMatchingPatternError

case {name: 'John', friends: [{name: 'Jane'}, {name: 'Rajesh'}]}
in name:, friends: [{name: first_friend}, *]
  "matched: #{first_friend}"
else
  "not matched"
end
# 匹配成功,返回 "matched: Jane"

参照上述例子,如果pattern是数组或hash的格式,那么最外层的括号可以省略。

如果pattern只包含常量,则可使用|符号表示逻辑或:只要匹配其中一个皆可成功,如:

case 0
in 0 | 1 | 2
  "0 or 1 or 2"
in Hash | Array
  "Hash or Array"
else
  "not matched"
end 
# 返回 "0 or 1 or 2"

如果使用了|符号,但是pattern中包括了变量,会抛异常SyntaxError,如:

case {a: 1, b: 2}
in {a: } | Array
  "matched: #{a}"
else
  "not matched"
end
# 返回 SyntaxError (illegal variable in alternative pattern (a))

case in中的pattern支持if/unless判断。

自定义对象的模式匹配

如果类实现了deconstruct()实例方法,并返回数组,则实例支持数组格式的模式匹配。

如果类实现了deconstruct_keys(keys)实例方法,并返回hash,则实例支持hash格式的模式匹配,keys表示要匹配的key数组。

class Point
  def initialize(x, y)
    @x, @y = x, y
  end

  def deconstruct
    [@x, @y]
  end

  def deconstruct_keys(keys)
    {x: @x, y: @y}
  end
end

case Point.new(1, -2)
in px, Integer
  "matched: #{px}"
else
  "not matched"
end
# 返回 "matched: 1"

case Point.new(1, -2)
in x: 0.. => px
  "matched: #{px}"
else
  "not matched"
end
# 返回 "matched: 1"

Reference

Ruby 2.7的模式匹配和in操作符