正規表現基礎
*? +? ?? { , }? 繰り返し
カテゴリー:量指定子
欲を抑えて...
さて、今回は量指定子の繰り返し方に着目して解説します。
今まで説明してきた量指定子( * + ? { , } ) は、正規表現を繰り返したとき、可能な限り何度も繰り返します。
貪欲 (greedy) マッチと呼ばれる繰り返し方です。
例えば、文字列パターンが be+ なら e を出来るだけ多く繰り返したものにマッチするので be や beeeeeee にもマッチします。
ここで、e の繰り返し回数を最少にしたい場合に用いるのが ? です。
+ の後に ? を追加して +? とすると、+ の最少繰り返し回数分のみマッチします。
所謂、非貪欲 (non-greedy)あるいは 最小 (minimal)のマッチになります。
それゆえ be+? であるならば、e の1回分の繰り返しのみにマッチするので beeeeeee については、be までの箇所でのマッチになります。
+ 以外の量指定子と ? の組み合わせについても、まとめておきます。
*?
パターンが be*? の場合
+?
パターンが be+? の場合
??
パターンが be?? の場合
{1,3}?
パターンが be{1,3}? の場合
{1,}?
パターンが be{1,}? の場合
{,3}?
パターンが be{,3}? の場合
では、以下で簡単な例を実行して慣れていきましょう。
この記事の難度は、基礎 Cクラスです。
(A: やさしい → E: 難しい)
事前知識として、pythonから正規表現を扱う方法が必要になります。
また、正規表現における文字クラスの知識や、グループを表す正規表現である ( ) を理解している事が望ましいです。
(不安な人でも、【Pythonから使う】【基礎1 文字クラス】 【( ) グループを指定】【( ) キャプチャを使う】、で詳しい解説があるので安心です。)
それぞれの量指定子につき、貪欲 (greedy) マッチと、非貪欲 (non-greedy)マッチとの違いを把握しましょう。
難度 : | |
事前知識: | Pythonの基礎文法(reモジュールを含む)。正規表現の文字クラスやグループ等。 |
学習効果: | *? +? ?? { , }? が非貪欲 (non-greedy) マッチになる事を理解できる。 |
Contens | 目次
Chapter1 | Pythonで実行 |
Chapter1 Pythonで実行
a+?
先ず文字列パターンを、アルファベット1文字の a と +? を組み合わせて a+? とします。
これは a につき最小の繰り返しを表します。
対象文字列が map なら a にマッチするはずです。
re_meta29_1.py import re pattern = re.compile("a+?") st = "map" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
予想通り、a にマッチしました。
次にパターンを ma+?p にします。
m と p の間で a が最少で繰り返されている場合にのみ一致するはずです。
re_meta29_2.py import re pattern = re.compile("ma+?p") st = "mp map maap" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
この場合は、? が無い場合と等しい結果になりました。
? があっても、a を2回繰り返している maap に一致しました。
これに対して、対象文字列を mop としてしまうと、a の代わりに o が存在するのでマッチしません。
re_meta29_3.py import re pattern = re.compile("ma+?p") st = "mop" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
mop ではマッチしない事が確認出来ました。
e*? e+? e?? e{1,3}* e{1,}? e{,3}?
さて、ここで冒頭で例示した bee について実行してみましょう。
先ず *? です。
パターンを be*? と構成する事で、e に最小マッチさせます。
対象文字列が beeeeeee のように、e が多く繰り返されていてる場合でも、b までしかヒットしないはずです。
re_meta29_4.py import re pattern = re.compile("be*?") st = "b be bee beeeeeee" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
実行結果(続き)
想定通り、e が多く繰り返されていても b までしかヒットしていません。
次にパターンを be+? と構成して、+? を試してみます。
re_meta29_5.py import re pattern = re.compile("be+?") st = "b be bee beeeeeee" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
この場合は、be までのマッチになりました。
今度は ?? を実行してみましょう。
文字列パターンは be?? です。
re_meta29_6.py import re pattern = re.compile("be??") st = "b be bee beeeeeee" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
実行結果(続き)
b までのマッチになりました。
これは be*? と同様の結果です。
では、be{1,3}? も試してみます。
re_meta29_7.py import re pattern = re.compile("be{1,3}?") st = "b be bee beeeeeee" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
e の1回分の繰り返しである be までマッチしました。
続いて、be{1,}? を実行します。
re_meta29_8.py import re pattern = re.compile("be{1,}?") st = "b be bee beeeeeee" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
結果は be{1,3}? のときと等しく、be までの一致です。
be{,3}? も確認します。
re_meta29_9.py import re pattern = re.compile("be{,3}?") st = "b be bee beeeeeee" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
実行結果(続き)
b までのマッチになりました。
be?? や be*? と同様、b までのマッチです。
p タグ1個分を取得する
ここでは、もう少し実用的な例で検証します。
HTMLの pタグ(<p> </p>) 1個分を各々取得したいと思います。
対象文字列のpタグは2つ並んでいて、<p> </p><p> </p> のようになっている状態です。
このうち1個分のpタグを取得するには ? を用いて、<p>.*?</p>などと記述します。
(. は任意の1文字を表します。詳細は、【. 改行以外の任意の1文字】で詳しく説明しています。)
re_meta29_10.py import re pattern = re.compile("<p>.*?</p>") st = "<p>title</p><p>sub</p>" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
狙い通り、各々pタグを一つ分取得出来ました。
ちなみに、これを ? を付けないで実行するとどうなるかも確認しましょう。
re_meta29_11.py import re pattern = re.compile("<p>.*</p>") st = "<p>title</p><p>sub</p>" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
今度は、2つがまとまってマッチしました。
* が強欲にマッチする為です。
(es)+?
今度は、グループ化されたものを繰り返しの対象とします。
グループを表す正規表現は ( ) です。
(グループ については【( ) グループを指定】で詳しく説明しています。)
パターンを (es) について最少回数の繰り返しにする場合、(es)+? のように記述します。
re_meta29_12.py import re pattern = re.compile("gen(es)+?") st = "gen genes geneses" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
それぞれ、es の一つ分の繰り返しである genes に一致しています。
さらに、次の例では (es) の後方参照を試しています。
(後方参照 については【( ) キャプチャを使う】で詳しく説明しています。)
パターンは、シンプルに (es)+?\1 にしましょう。
対象文字列は、先程と同様なものに geneseseses 加えた gen genes geneses geneseseses です。
re_meta29_13.py import re pattern = re.compile(r"gen(es)+?\1") st = "gen genes geneses geneseseses" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("----- match -----") for i in range(result.lastindex + 1): print('group{num};'.format(num = i),result.group(i)) print("位置",result.span(i))
実行結果
実行結果(続き)
geneses , geneseseses の geneses の箇所 にはマッチしました。
グループに量指定子を組み合わせてから、後方参照を (es)+?\1 のように行っても、あくまで es と、その参照が必要になる事は変わらないようです。
((es)+?)
次の例では、(es)+? 自体を ( ) で括ってグループ化してみましょう。
パターン以外は、一つ前に実行した meta29_13.py と同様にして試します。
re_meta29_14.py import re pattern = re.compile(r"gen((es)+?)\1") st = "gen genes geneses geneseseses" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("----- match -----") for i in range(result.lastindex + 1): print('group{num};'.format(num = i),result.group(i)) print("位置",result.span(i))
実行結果
実行結果(続き)
geneses , geneseseses につき、各々最少回数の繰り返しに該当する後方参照が行われたようです。
なお、gen((es)+)\1 とした場合は、geneseseses につき2回分の繰り返しに対する後方参照が行われます。
\d+?
続けて例を出します。
数字を表す文字クラスである \d と +? を組み合わせて、数字の繰り返しを狙います。
(文字クラス については【\d 数字を指定する】で詳しく説明します。)
re_meta29_15.py import re pattern = re.compile("¥\d+?") st = "¥ ¥7 ¥57 ¥287" print("↓ 対象文字列\n"+st) result_iter = pattern.finditer(st) for result in result_iter: print("match:",result.group()) print("位置",result.span())
実行結果
¥ 表示の金額を左から1桁分取得出来ました。
以上で ? の説明は終了にします。
*? +? ?? { , }? が、非貪欲 (non-greedy) あるいは 最小 (minimal) のマッチになる事を理解できました。
これは、HTMLのPタグの例のように、繰り返しパターン1個分のみをマッチの対象にしたい場合などで役に立つでしょう。
さて、ここで6回にわたり解説した量指定子の解説は終了になります。
量指定子は先読みなどに比べると、比較的イメージが持ちやすかったと思います。
但し、非貪欲のマッチである ? など部分的に慣れが必要な箇所もあります。
一読しただけで分かった人は、優れた理解力を持っています。
関連記事
* 繰り返し
正規表現: | 量指定子 |
難度 : | 基礎 |
事前知識: | Pythonの基礎。文字クラスやグループ等。 |
学習効果: | * が直前の正規表現を 0 回以上、できるだけ多く繰り返す事を理解できる。 |
+ 繰り返し
正規表現: | 量指定子 |
難度 : | 基礎 |
事前知識: | Pythonの基礎。文字クラスやグループ等。 |
学習効果: | + が直前の正規表現を 1 回以上、できるだけ多く繰り返す事を理解できる。 |
? 繰り返し
正規表現: | 量指定子 |
難度 : | 基礎 |
事前知識: | Pythonの基礎。文字クラスやグループ等。 |
学習効果: | ? が、直前の正規表現を0回か、1回繰り返したものにマッチさせる事を理解できる。 |
{ } 繰り返し
正規表現: | 量指定子 |
難度 : | 基礎 |
事前知識: | Pythonの基礎。文字クラスやグループ等。 |
学習効果: | { } が、直前の正規表現を指定した回数、ちょうど繰り返したものにマッチさせる事を理解できる。 |
{ , } 繰り返し
正規表現: | 量指定子 |
難度 : | 基礎 |
事前知識: | Pythonの基礎。文字クラスやグループ等。 |
学習効果: | {m,n} が、直前の正規表現を m 回から n 回、できるだけ多くの繰り返しにマッチする事が分かる。 |
*? +? ?? { , }? 繰り返し
正規表現: | 量指定子 |
難度 : | 基礎 |
事前知識: | Pythonの基礎。文字クラスやグループ等。 |
学習効果: | *? +? ?? { , }? が、非貪欲 (non-greedy)のマッチになる事を理解できる。 |
正規表現をPythonから使うには ?
正規表現: | Pythonから使う |
難度 : | 入門 |
事前知識: | Pythonの基礎文法 |
学習効果: | pythonから正規表現を使う一連の流れを掴む |
ハロー ! メタキャラクタ
正規表現: | メタキャラクタの概要 |
難度 : | 入門 |
事前知識: | 不要 |
学習効果: | メタキャラクタの概要を掴む |
正規表現とは?
正規表現: | 概要 |
難度 : | 入門 |
事前知識: | 不要 |
学習効果: | 正規表現の概要を知る |
PR