📝

【Ruby】正規表現のMatchDataをパターンマッチで使う

2024/03/18に公開

RegExp#matchの戻り値であるMatchDataはパターンマッチに対応しており、キャプチャしたグループの文字列を直接取り出すのに使える。ifの条件として自然に使えて、なかなかいい感じだと思う。

if /(\d{4})-(\d{2})-(\d{2})/.match("2024-09-01") in year, month, day
  puts year  #=> 2024
  puts month #=> 09
  puts day   #=> 01
end

キャプチャが1つしかない場合はarray_patternであることを明示する[]が必要になる。

if /(\d+)/.match("aaa999aa") in [number]
  puts number #=> 999
end

名前付きキャプチャも使える。

if /\$(?<dollars>\d+)\.(?<cents>\d+)/.match("$3.67") in dollars:, cents:
  puts dollars #=> 3
  puts cents   #=> 67
end

ただし正規表現内にキャプチャがない場合はうまく動作しない。正規表現マッチが失敗した場合の戻り値nilが常にパターンにマッチしてしまうので、成功・失敗を判定できない。

#これはダメ
if /\d+/.match("aaa999aa") in n
  p n #=> #<MatchData "999">
end

if /\d+/.match("aaabb") in n
  p n #=> nil
end

パターンマッチで、正規表現マッチした文字列全体を簡単に取り出す方法は無い……と思う。

無理矢理な方法はいくつか考えられるが……普通に変数代入使うか、Regexp.last_match経由の方がわかりやすいかな。

#案1: 配列化する。#values_atでも可
if /\d+/.match("aaa999aa").to_a in [number]
  puts number #=> 999
end

#案2: 文字列化する。safe navigation operator必須
if /\d+/.match("aaa999aa")&.to_s in String => number
  puts number #=> 999
end

#案3: 素直にMatchDataを取り出す
if /\d+/.match("aaa999aa") in MatchData => m
  puts m.to_s #=> 999
end

Discussion