LoginSignup
6
5

More than 5 years have passed since last update.

Ruby で MatchData#named_captures が欲しい

Last updated at Posted at 2015-02-04

$1 は分かりにくい

正規表現検索でキャプチャーした文字列を取り出すのにこんな書き方をしていた。

def parse_isbn(isbn)
  /(\d+)-(\d+)-(\d+)-(\d+)-(\d)/ =~ isbn
  {prefix: $1, group: $2, publisher: $3, title: $4, check_digit: $5}
end

p parse_isbn("978-4-7561-3254-3")
  #=> {:prefix=>"978", :group=>"4", :publisher=>"7561", :title=>"3254", :check_digit=>"3"}

なんか馬鹿らしい。名前と番号の対応を考えるのがめどい。

ISBN のような,単純かつ一度作ったら変更の無いようなものならいいけど,開発中に正規表現の形がコロコロ変わるようだと,$nn を正しく変更するのが大変だ。

※話を簡単にするため,与えられる ISBN は常に正しいと仮定している。

そうだ名前付きキャプチャーを使おう

Ruby 1.9 になって正規表現エンジンが Oniguruma(鬼車)になり,名前付きキャプチャーが使えるようになった。キャプチャーに名前を付けておいて,対応する部分文字列を番号でなく名前で参照できるというアレだ。ちなみに Ruby 2.0 からは Oniguruma のフォークである Onigmo(鬼雲;u は入らない)に代わっている。

キャプチャーについての詳細はるりまの「正規表現」をどうぞ:

これを使おうではないか。

ただ,名前付きキャプチャーを使うと正規表現が長くなりすぎるきらいがあるので,x オプションでフリーフォーマットにして書くことにする。

def parse_isbn(isbn)
  /(?<prefix> \d+ ) # 接頭記号(978 か 979)
   -
   (?<group> \d+ ) # グループ記号(国,地域,言語圏)
   -
   (?<publisher> \d+ )
   -
   (?<title> \d+ )
   -
   (?<check_digit> \d )
  /x =~ isbn
  Regexp.last_match
end

p parse_isbn("978-4-7561-3254-3")
  #=> #<MatchData "978-4-7561-3254-3" prefix:"978" group:"4" publisher:"7561" title:"3254" check_digit:"3">
p parse_isbn("978-4-7561-3254-3")[:publisher] #=> "7561"

x オプションというのは,正規表現リテラル中の空白文字を無視してくれるもの。ご覧の通り # 以降がコメントとみなされる。

でもさ,MatchData じゃなくてキャプチャー文字列のハッシュが欲しいわけよ。

あ,ちなみに MatchData#[] の引数に名前を与えるとき,名前は String でも Symbol でもいい。

名前付きキャプチャーのハッシュを返すメソッドは無い

絶対そういう目的のメソッドがあるはずと思って調べたけど,どうやら無い。

キャプチャー文字列を配列で返す MatchData#captures ならある。

名前付きキャプチャーの名前のリストを返す MatchData#names もある。(シンボルの配列じゃなくて文字列の配列を返すことに注意)

無ければ作れ

こんなでいいかな。

class MatchData
  def named_captures
    Hash[ names.map{ |name| [name.to_sym, self[name]] } ]
  end
end

(2016-08-03 追記)Ruby 2.4.0 でキター!

Ruby 2.4 で MathData#named_captures が入るようです。

> ruby -v
ruby 2.4.0preview1 (2016-06-20 trunk 55466) [x86_64-linux]

で試しました。

def parse_isbn(isbn)
  /(?<prefix> \d+ ) # 接頭記号(978 か 979)
   -
   (?<group> \d+ ) # グループ記号(国,地域,言語圏)
   -
   (?<publisher> \d+ )
   -
   (?<title> \d+ )
   -
   (?<check_digit> \d )
  /x =~ isbn
  Regexp.last_match.named_captures
end

parse_isbn "978-4-7561-3254-3"
#=> {"prefix"=>"978", "group"=>"4", "publisher"=>"7561", "title"=>"3254", "check_digit"=>"3"}

やたー!

6
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5