パターンマッチングで特定の文字列を抽出する(match、scan)
Rubyである文字列から特定のパターンにマッチする文字列を抽出する方法です。
似たようなメソッドでmatchとscanがあったのですが、どちらのメソッドがどのように動作するのか、違いがよく分かっていなかったのでメモとして残しておきます。
目次
- match()
- scan()
- 2つのメソッドの違いと使いどころ
- 参考リンク
match()
まずはmatchメソッドで実行した場合のコードと結果です。
str = 'aaatestaaatestaaa'
if m = str.match(/test/)
puts m[0]
puts m[1]
end
# 結果: m[0] => test
# 結果: m[1] => nil
if m = str.match(/test(aaa)/)
puts m[0]
puts m[1]
puts m[2]
end
# 結果: m[0] => testaaa
# 結果: m[1] => aaa
# 結果: m[2] => nil
データの取得方法が配列と同じなので、ループ処理(each)もできるだろうと思ってやったら NoMethodError で怒られました。
調べるとmatchメソッドで戻り値として得られるデータは配列ではなくMatchDataオブジェクトらしく、ループ処理はできないようです。
さらに上記のコードと結果を見てわかる通り、パターンマッチする文字列(testまたはtestaaa)は2件あるはずなのに、1件分しか格納されていません。
scan()
scanメソッドで実行した場合のコードと結果は以下の通りです。
str = 'aaatestaaatestaaa'
if m = str.scan(/test/)
puts m
end
# 結果: m[0] => test
# 結果: m[1] => test
if m = str.scan(/test(aaa)/)
puts m
end
# 結果: m[0] => aaa
# 結果: m[1] => aaa
if m = str.scan(/test[a]{3}/)
puts m
end
# 結果: m[0] => testaaa
# 結果: m[1] => testaaa
結果を見てわかる通り、パターンにマッチするすべての文字列が取得できています。
注意点としては、通常は戻り値の一番目に全体でマッチした文字列、続いて()内でマッチした文字列が格納されますが、scanメソッドで()を指定したら全体でマッチした文字列が結果として取得できませんでした。(上記の2番目のコード)
2つのメソッドの違いと使いどころ
この二つのメソッドの大きな違いは、matchは戻り値としてMatchDataオブジェクト(マッチなしの場合はnil)を返すのに対して、scanは配列(マッチなしの場合は空の配列)を返すところです。
私はmatchしか知らずに取得してきたデータをeachでループ処理しようとして失敗して、matchメソッドで取得できるデータが単純な配列ではないことに気が付きました。
Rubyではパターンにマッチしたデータすべてを抽出して件数の取得やループでなにかしらの処理を行いたいといった場合はscanメソッドを利用して、それ以外の場合はmatchメソッドを使うようです。