Rubyで文字列処理を実装するうえで便利なのが、正規表現と呼ばれる文字列の表現方法です。正規表現は、元の文字列から該当する部分的な文字列を検索するのに用いられ、match()メソッドと呼ばれる文字列検索メソッドと併用することが多いです。
今回は、正規表現を使った文字列検索処理を実装するのにうってつけな、match()メソッドの使い方を詳しく解説。類似した機能を持つメソッドとの違いについても、ソースコードを交えて解説しています。文字列検索処理について理解したい方は、ぜひ参考にしてください。
なお、Rubyの魅力や特徴については以下の記事で詳しく解説していますので、こちらもぜひご覧くださいね。
みなさんこんにちは! 今回の記事は、 悩みを抱えた人 ・Rubyってどんなプログラミング言語なの?・Rubyを勉強すると将来役に立つかな?・Rubyのおすすめの学習方法が知りたい! というお悩みを解決する記事にな[…]
フリーランス案件を探すならこのエージェント!
転職を考えているならこのエージェント!
match()メソッドの基本
match()メソッドは、RubyのStringクラスとRegExpクラスで用意されているインスタンスメソッドです。
Stringクラスのmatch()メソッド
Stringクラスで用意されているmatch()メソッドは、文字列オブジェクトをレシーバとし、正規表現を引数とすることで文字列の検索処理を実行します。文字列がヒットした場合は、ヒットした文字列や関連情報を保有するオブジェクトが返され、逆にヒットする文字列がない場合は、nilが返されます。match()メソッドのサンプルコードを以下に示します。
sample_str_1 = "This is a pen. Do you use this pen?"
sample_str_2 = "This is an apple. Do you eat this apple?"
# 文字列をレシーバとしてmatch()メソッドを呼び出す
sample_match_data_1 = sample_str_1.match(/pen/)
sample_match_data_2 = sample_str_2.match(/pen/)
p sample_match_data_1 # #<MatchData "pen">
p sample_match_data_2 # nil
2つの文字列を対象として、文字列中に「pen」が存在するかどうかをmatch()メソッドで検索するプログラムを実装しています。今回は「pen」を検索したいので、対象の単語をスラッシュで囲んだ正規表現「/pen/」を引数として渡します。
単語がヒットした場合、戻り値はMatchDataと呼ばれるオブジェクトです。一方、単語がヒットしなかった場合は、nilが返されます。戻り値の違いによって、マッチする文字列が含まれているか否かを判断することができます。
文字列がヒットした場合に返されるMatchDataオブジェクトは、正規表現でグループを定義した個数によってサイズが変動します。以下のサンプルコードをご覧ください。
sample_product_key = "0E3R-1W2F-4H6T"
# 丸括弧でグループ化した正規表現でmatch()メソッドを呼び出す
sample_match_data_3 = sample_product_key.match(/(\w+)-(\w+)-(\w+)/)
# ヒットした文字列がグループごとに格納されている
p sample_match_data_3 # #<MatchData "0E3R-1W2F-4H6T" 1:"0E3R" 2:"1W2F" 3:"4H6T">
# captures()メソッドでヒットした文字列の配列を生成できる
p sample_match_data_3.captures # ["0E3R", "1W2F", "4H6T"]
プロダクトキーから英数字の部分をグループごとに抜き出すプログラムです。正規表現で丸括弧を用いることでグループを定義できます(4行目)。グループが定義された正規表現でmatc()メソッドを呼び出した場合、グループの個数分MatchDataオブジェクトにヒットした文字列が格納されます。
ヒットした文字列に対して何らかの処理を施したい場合は、captures()メソッドで配列を生成するのがおすすめです。
RegExpクラスのmatch()メソッド
RegExpクラスで用意されているmatch()メソッドは、Stringクラスのものとは違い、正規表現オブジェクトをレシーバとして、検索対象の文字列オブジェクトを引数にとります。戻り値は、ヒットした場合、しなかった場合ともにStringクラスのものと同様です。サンプルコードは以下の通りです。
sample_str_1 = "This is a pen. Do you use this pen?"
sample_str_2 = "This is an apple. Do you eat this apple?"
# 正規表現をレシーバとしてmatch()メソッドを呼び出す
sample_match_data_1 = /pen/.match(sample_str_1)
sample_match_data_2 = /pen/.match(sample_str_2)
# 戻り値はStringクラスのmetch()メソッドの場合と同様
p sample_match_data_1 # #<MatchData "pen">
p sample_match_data_2 # nil
サンプルコードを実行しても分かるように、レシーバと引数を入れ替えたとしても、実行結果に違いはありません。
match()メソッドの応用テクニック
ここからは、match()メソッドを利用した応用テクニックを紹介しましょう。
検索開始位置を設定する
match()メソッドによる文字列の検索は通常、文字列の先頭から実行されます。この実行開始する文字列の位置を、match()メソッドの第二引数で設定可能です。以下のサンプルコードをご覧ください。
sample_text = "0123 abcdefg 4567 hijklmn"
pattern = /(\d+)/
# 先頭から検索
sample_match_data_1 = pattern.match(sample_text)
# 2番目から検索
sample_match_data_2 = pattern.match(sample_text, 2)
# 4番目から検索
sample_match_data_3 = pattern.match(sample_text, 4)
p sample_match_data_1.captures # ["0123"]
p sample_match_data_2.captures # ["23"]
p sample_match_data_3.captures # ["4567"]
サンプル文字列の中から、数字が連続する部分のみを抽出するプログラムです。第二引数を指定しない場合は、通常通り先頭から検索を開始するため、「0123」がヒットします。次に、第二引数に2を設定した場合です。Rubyにおいては、0から数える決まりとなっているので、3文字目の「2」から検索を開始するように動作します。したがって、「23」がヒットします。同様に、第二引数に4を設定した場合、戻り値は「4567」です。
ヒットした文字列を加工する
match()メソッドにブロックを渡すことで、ヒットした文字列に対し何らかの処理を実施することができます。以下のサンプルコードをご覧ください。
sample_text_1 = "This product price is 1800 yen."
# 文字列がヒットしたらブロック内の処理が実行される
tax_included_price = sample_text_1.match(/\d+ yen/) { |match|
p "Price excluding tax: #{match}" # "Price excluding tax: 1800 yen"
price = match.to_a[0].to_i
price = (price * 1.1).floor
}
p "Price including tax: #{tax_included_price} yen" # "Price including tax: 1980 yen"
sample_text_2 = "Do you buy the product?"
# 文字列がヒットしないならブロック内の処理は実行されない
tax_included_price = sample_text_2.match(/\d+ yen/) { |match|
p "Price excluding tax: #{match}"
price = match.to_a[0].to_i
price = (price * 1.1).floor
}
p tax_included_price # nil
価格が含まれる文字列から価格部分のみ抜き出すプログラムです。また、match()メソッドに渡しているブロックには、抜き出した価格から税込価格を計算するプログラムが定義されています。
文字列がヒットした場合は、ブロック内の処理が実行されます。ブロック引数はヒットした文字列です。つまり、サンプルコード4行目の引数matchに「1800 yen」が格納されています。ブロック内の処理が終了すると、最終的にブロックの最後で評価された値がmatch()メソッドの戻り値として返されます。つまり、サンプルコード4行目のmatch()メソッド呼び出し時は、戻り値は変数priceの値の中身です。
一方、文字列がヒットしない場合はブロック内の処理が一切実行されません。サンプルコード16行目におけるmatch()メソッド呼び出しでは、レシーバの文章中に価格に関わる部分が含まれていないため、ブロック内の処理は実行されずnilが返されます。
match?()メソッドとの違い
match()メソッドと似た機能を持つメソッドとして、match?()メソッドがあります。match?()メソッドは、RegExpクラスにて用意されているインスタンスメソッドの1つです。正規表現オブジェクトをレシーバとし、文字列を引数として検索処理を実行する点では、match()メソッドと同様の機能を持ちます。
一方、メソッドの戻り値は両者で挙動が異なります。match()メソッドの戻り値は、文字列がヒットしたときはMatchDataオブジェクト、ヒットしなかったときはnilを返すのに対し、match?()メソッドは、検索結果に対応する論理値(trueまたはfalse)を返します。以下のサンプルコードで違いを確認してみましょう。
sample_text = "Ruby is a programming language born in Japan."
pattern = /Ruby/
# match()メソッドで文字列検索
result_1 = pattern.match(sample_text)
result_2 = pattern.match(sample_text, 1)
# match?()メソッドで文字列検索
result_3 = pattern.match?(sample_text)
result_4 = pattern.match?(sample_text, 1)
p result_1 # #<MatchData "Ruby">
p result_2 # nil
p result_3 # true
p result_4 # false
文章中に「Ruby」が含まれているかを検索する処理を、match()メソッドとmatch?()メソッドをそれぞれ利用して実装しています。サンプルコードを見ても分かるように、match?()メソッドで文字列検索をしヒットした場合は、論理値trueを返します。一方、ヒットしなかった場合はnilではなくfalseを返します。単に文字列が存在するか否かを判定するのが目的であれば、match?()メソッドを呼び出すことで、論理値による判断ができます。
scan()メソッドとの違い
もう一つ、似た機能を有するscan()メソッドについても紹介しましょう。scan()メソッドは、Stringクラスのインスタンスメソッドの1つで、正規表現オブジェクトを基にして文字列検索処理を実行します。この点については、match()メソッドと変わりありません。しかし、scan()メソッドの戻り値は配列であることが、match()メソッドと大きく異なります。以下のサンプルコードで、実行結果の違いを見てみましょう。
sample_text = "apache apart application apollo"
pattern_1 = /ap[a-z]+/
pattern_2 = /ab[a-z]+/
# match()メソッドで文字列検索
result_1 = sample_text.match(pattern_1)
result_2 = sample_text.match(pattern_2)
# scan()メソッドで文字列検索
result_3 = sample_text.scan(pattern_1)
result_4 = sample_text.scan(pattern_2)
p result_1 # #<MatchData "apache">
p result_2 # nil
p result_3 # ["apache", "apart", "application", "apollo"]
p result_4 # []
サンプルコードの変数sample_textには、apから始まる単語が4つ含まれています。文字列がヒットした場合、match()メソッドは最初にヒットした文字列が格納された、MatchDataオブジェクトが返されます。つまり、サンプルコードの変数result_1には、「apache」のみが格納されます。一方、scan()メソッドの戻り値は、ヒットするすべての文字列が格納された配列です。したがって、要素数4の配列が格納されます。
文字列がヒットしない場合も両者で戻り値が異なり、match()メソッドはnilが返されるのに対し、scan()メソッドは空の配列が返されます。scan()メソッドを利用する場合は、配列の要素数が0であるか否かで、文字列が含まれているか否かの判断が可能です。
お仕事の途中ですが、少し一休みして、転職や独立について考えてみませんか🙌?
現役エンジニアが選ぶおすすめの転職エージェント11選【成功談・失敗談もあります】
レバテックフリーランスの評判ってどう?【現役エンジニアが徹底解説します】
MidWorks(ミッドワークス)の評判ってどう?【現役エンジニアが徹底解説します】
日々の業務に追われて自分を見失わないよう、
定期的にキャリアを振り返るようにしておきましょう🤲
最後に
さて、ここまでRubyのRangeクラスの取り扱い方について解説してきましたがいかがでしたか?
今回ご紹介した通り、Rangeクラスには便利なメソッドが多数準備されており、これらを使いこなすことで、コードや処理の簡素化/効率化が図れます。
実際の開発でも頻繁に登場するものばかりですので、ぜひこの機会にマスターしておいてくださいね。
このブログを通じて少しでも「傍(はた)を楽(らく)にする」ことができていれば嬉しく思います。
最後まで読んで頂きありがとうございました。