LoginSignup
1
1

正規表現 - 量指定子(繰り返し)について

Posted at

はじめに

正規表現で、直前の文字の出現回数を指定する、量指定子の紹介と簡単な用法を例示する。
検証は Ruby で行うため、Onigmo 正規表現ライブラリに従う。

量指定子(Quantifier)

「」とそれに囲まれた領域にマッチするパターンを検討する。

例文1
aaa「bbb」ccc

こういった文字列の 「bbb」 にマッチする表現を考える。

その中で量指定子の種類と効果を確認する。

用法

「」 の中に含まれるテキストが固定内容なら、以下の様に簡単な正規表現を記述できる。

基本1
re = Regexp.new(/「bbb」/)

p 'aaa「bbb」ccc'.scan(re)
# => ["「bbb」"]

# scan なのでマッチしたすべてが配列で出てくる

しかし、入力された文字列を対象とする場合
「」 に含まれる内容は文字種も長さも定まったものではないだろう。

この不定な内容にマッチするように正規表現を書く際に、量指定子が有効である。

量指定子にはいくつかのタイプがある。

greedy(最大、貪欲、欲張り)

恐らく最も利用されている基本的なもの。

? => 0回 or 1回
* => 0回以上
+ => 1回以上

{} を使った回数指定は今回は対象としない。

使用例

これらを使って、「」 内がどのような内容でもマッチする正規表現を考える。

貪欲1
re = Regexp.new(/「.*」/)

p 'aaa「bbb」ccc'.scan(re)
# => ["「bbb」"]

p '外側「内側」外側'.scan(re)
# => ["「内側」"]

量指定子以外に使用したメタ文字は以下

. => 任意の文字

うまくいかない例

今度は、テキスト中に 「」 が複数出てくるような例を考える。

例文2
aaa「bbb」ccc「ddd」eee

これに対して、先ほどと同じ正規表現でマッチをはかると以下のような結果になる。

貪欲2
re = Regexp.new(/「.*」/)

p 'aaa「bbb」ccc「ddd」eee'.scan(re)
# => ["「bbb」ccc「ddd」"]

気持ちとしては 「bbb」「ddd」 を取得してほしいが、「bbb」ccc「ddd」 を1塊として取ってしまった。

  1. . にマッチしてしまうため、1つ目の も貪欲に .* へとマッチさせる。
  2. 2つ目の もマッチするが、これ以降 が登場しないため、戻って直前までを .* のマッチとして終了する。

こんな感じの判定になってしまいました。

reluctant(最小、最短、非貪欲、無欲)

greedy でうまくいかなかったような例で、それぞれの 「」 を望み通り取得するためにはこれが使えます。

?? => 0回 or 1回
*? => 0回以上
+? => 1回以上

後ろに ? が付いた形をしている。

使用例

非貪欲
re = Regexp.new(/「.*?」/)

p 'aaa「bbb」ccc「ddd」eee'.scan(re)
# => ["「bbb」", "「ddd」"]

非貪欲と呼称される *? を使用したところ、次の条件を満たせる1つ目の と遭遇した時点で文字の取得をやめてマッチを終了して 「bbb」 が取得できました。
その後、続いて登場した2つ目の から再びマッチを始めて、「ddd」 も取得できました。

余談1: 貪欲マッチで同じことをする

個人的にあんまり非貪欲マッチを利用した記憶が無いのは、貪欲マッチでも簡単なレベルなら同じことをできるからだろうか。

要は、貪欲マッチが終了の識別子としている にマッチしなければよいのです。
. の代わりに文字クラスの否定を使って、 以外の文字を0回以上期待するようにします。

余談1-1
re = Regexp.new(/「[^」]*」/)

p 'aaa「bbb」ccc「ddd」eee'.scan(re)
# => ["「bbb」", "「ddd」"]

また、「」 両方とも否定文字クラスに入れると 「」 が入れ子になっているときに内側の結果だけを取ることもできる。

余談1-2
re = Regexp.new(/「[^」]*」/)

p 'aaa「bbb「ccc」ddd」eee'.scan(re)
# => ["「bbb「ccc」"]
# 1つ目の開始から初めて遭遇する終了まで全部取ってしまう


re = Regexp.new(/「[^「」]*」/)

p 'aaa「bbb「ccc」ddd」eee'.scan(re)
# => ["「ccc」"]

余談2: 非包含オペレーター

余談1 の例では囲い文字が開始・終了ともそれぞれ1文字だったので否定文字クラスで対応できたが、文字列で囲まれた領域を探そうとすると少し面倒になる。

例文3
<div><p>hoge</p><p>fuga</p></div>

ここから <p>hoge</p><p>fuga</p> を取得しようとしてみると </p> を含まないもの にマッチするという条件を表現できなければいけなくなる。
こうなってくると否定文字クラスではなかなか表現が厳しくなる。

そこで、Onigmo 正規表現ライブラリには実験的機能として 非包含オペレーター というものが実装されている。

(?~式) 非包含オペレータ (実験的)
式にマッチする文字列を含まない任意の文字列にマッチする。
より正確には、(?~式) は、.式. がマッチする集合の補集合に
マッチする。これは形式言語理論の意味で正規である。
(?:(?!式).)* と似ているが簡単に書ける。

余談2-1
re = Regexp.new(/<p>(?~<\/p>)<\/p>/)

p '<div><p>hoge</p><p>fuga</p></div>'.scan(re)
# => ["<p>hoge</p>", "<p>fuga</p>"]

文字列にマッチするので量指定子は不要。

まぁこの程度なら非貪欲マッチで実現できるが。

余談2-2
re = Regexp.new(/<p>.*?<\/p>/)

p '<div><p>hoge</p><p>fuga</p></div>'.scan(re)
# => ["<p>hoge</p>", "<p>fuga</p>"]

possessive(絶対最大、強欲、独占的)

?+ => 0回 or 1回
*+ => 0回以上
++ => 1回以上

後ろに + が付いた形をしている。

機能としては Ruby リファレンスマニュアル > 正規表現 の 絶対最大量指定子(possessive quantifier) の項から

以下のメタ文字列は、最大量指定子のように最長のマッチをしますが、一度マッチすると、その後マッチに失敗してもバックトラックしません。つまりマッチ済みの文字列を手放さずにマッチに失敗します。これらの量指定子は絶対最大量指定子と呼ばれます。

私はこの説明文で初見ではピンときませんでしたが、この記事の貪欲マッチの説明で

戻って直前までを .* のマッチとして~

と表現したことから機能がわかってきます。
動作例も見てみましょう。

強欲1
re = Regexp.new(/「.*+」/)

p 'aaa「bbb」ccc'.scan(re)
# => []

なにもマッチしませんでした。
貪欲マッチの時と同じ説明の形を取るなら、

  1. . にマッチしてしまうため、 も強欲に .*+ へとマッチさせる。
  2. 文字列終端まで到達して .*+ の取得が終わるが、当然次に は登場しない。しかしここで戻って の条件を達成しようとせず、マッチなしで終了する。
    上記のような判定になっているでしょう。

使用例

正直なところ、私自身ではこれといった用法を思いつきませんでしたが、以下のような用法があるようです。

Qiita - 強欲な量指定子(絶対最大量指定子)の使いどころ

また、詳細な検証は行っていませんがバックトラックが無いため同じことを表現した際には貪欲マッチより動作が軽いようです。

参考

Ruby リファレンスマニュアル 正規表現
Onigmo ドキュメント
Qiita - [正規表現] .*?は最短マッチではない
Qiita - 絶対最大量指定子(possessive quantifier)について
Qiita - 強欲な量指定子(絶対最大量指定子)の使いどころ

1
1
0

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
1
1