正規表現

量指定子 *?, +?, ??, { , }? _key-visual

欲を抑えて...

 さて、今回は量指定子の繰り返し方に着目して解説します。

今まで説明してきた量指定子( * + ? { , } ) は、正規表現を繰り返したとき、可能な限り何度も繰り返します。

貪欲 (greedy) マッチと呼ばれる繰り返し方です。

例えば、文字列パターンが be+ なら e を出来るだけ多く繰り返したものにマッチするので bebeeeeeee にもマッチします。

 ここで、e の繰り返し回数を最少にしたい場合に用いるのが ? です。

+ の後に ? を追加して +? とすると、+最少繰り返し回数分のみマッチします。

所謂、非貪欲 (non-greedy)あるいは 最小 (minimal)のマッチになります。

 それゆえ be+? であるならば、e の1回分の繰り返しのみにマッチするので beeeeeee については、be までの箇所でのマッチになります。

+ 以外の量指定子と ? の組み合わせについても、まとめておきます。

*?

パターンが be*? の場合 パターンが be*? の場合のフローチャート


+?

パターンが be+? の場合 パターンが be+? の場合のフローチャート


??

パターンが be?? の場合 パターンが be?? の場合のフローチャート


{1,3}?

パターンが be{1,3}? の場合 パターンが be{1,3}? の場合のフローチャート


{1,}?

パターンが be{1,}? の場合 パターンが be{1,}? の場合のフローチャート


{,3}?

パターンが be{,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())        
                            


実行結果 量指定子 *? +? ?? { , }?_1

 予想通り、a にマッチしました。

次にパターンを ma+?p にします。

mp の間で 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())        
                            


実行結果 量指定子 *? +? ?? { , }?_2

 この場合は、? が無い場合と等しい結果になりました。

? があっても、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())        
                            


実行結果 量指定子 *? +? ?? { , }?_3

 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())        
                            


実行結果 量指定子 *? +? ?? { , }?_4

実行結果(続き) 量指定子 *? +? ?? { , }?_4_2

 想定通り、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())        
                            


実行結果 量指定子 *? +? ?? { , }?_5

 この場合は、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())        
                            


実行結果 量指定子 *? +? ?? { , }?_6

実行結果(続き) 量指定子 *? +? ?? { , }?_6_2

 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())        
                            


実行結果 量指定子 *? +? ?? { , }?_7

 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())        
                            


実行結果 量指定子 *? +? ?? { , }?_8

 結果は 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())        
                            


実行結果 量指定子 *? +? ?? { , }?_9

実行結果(続き) 量指定子 *? +? ?? { , }?_9_2

 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())        
                            


実行結果 量指定子 *? +? ?? { , }?_10

 狙い通り、各々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())        
                            


実行結果 量指定子 *? +? ?? { , }?_11

 今度は、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())        
                            


実行結果 量指定子 *? +? ?? { , }?_12

 それぞれ、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))
                            


実行結果 量指定子 *? +? ?? { , }?_13

実行結果(続き) 量指定子 *? +? ?? { , }?_13_2

 geneses , genesesesesgeneses の箇所 にはマッチしました。

グループに量指定子を組み合わせてから、後方参照を (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))
                            


実行結果 量指定子 *? +? ?? { , }?_14

実行結果(続き) 量指定子 *? +? ?? { , }?_14_2

 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())        
                            


実行結果 量指定子 *? +? ?? { , }?_15

 ¥ 表示の金額を左から1桁分取得出来ました。



 以上で ? の説明は終了にします。

*?   +?   ??   { , }?   が、非貪欲 (non-greedy) あるいは 最小 (minimal) のマッチになる事を理解できました。

これは、HTMLのPタグの例のように、繰り返しパターン1個分のみをマッチの対象にしたい場合などで役に立つでしょう。


 さて、ここで6回にわたり解説した量指定子の解説は終了になります。

量指定子は先読みなどに比べると、比較的イメージが持ちやすかったと思います。

但し、非貪欲のマッチである ? など部分的に慣れが必要な箇所もあります。

一読しただけで分かった人は、優れた理解力を持っています。

関連記事

基礎5 量指定子 * _key-visual

* 繰り返し

正規表現: 量指定子
難度       : 基礎
事前知識: Pythonの基礎。文字クラスやグループ等。
学習効果: * が直前の正規表現を 0 回以上、できるだけ多く繰り返す事を理解できる。
基礎5 量指定子 + _key-visual

+ 繰り返し

正規表現: 量指定子
難度       : 基礎
事前知識: Pythonの基礎。文字クラスやグループ等。
学習効果: + が直前の正規表現を 1 回以上、できるだけ多く繰り返す事を理解できる。
基礎5 量指定子 ? _key-visual

? 繰り返し

正規表現: 量指定子
難度       : 基礎
事前知識: Pythonの基礎。文字クラスやグループ等。
学習効果: ? が、直前の正規表現を0回か、1回繰り返したものにマッチさせる事を理解できる。
基礎5 量指定子 { } _key-visual

{ } 繰り返し

正規表現: 量指定子
難度       : 基礎
事前知識: Pythonの基礎。文字クラスやグループ等。
学習効果: { } が、直前の正規表現を指定した回数、ちょうど繰り返したものにマッチさせる事を理解できる。
基礎5 量指定子 { } _key-visual

{ , } 繰り返し

正規表現: 量指定子
難度       : 基礎
事前知識: Pythonの基礎。文字クラスやグループ等。
学習効果: {m,n} が、直前の正規表現を m 回から n 回、できるだけ多くの繰り返しにマッチする事が分かる。
基礎5 量指定子 { } _key-visual

*?   +?   ??   { , }?   繰り返し

正規表現: 量指定子
難度       : 基礎
事前知識: Pythonの基礎。文字クラスやグループ等。
学習効果: *?   +?   ??   { , }?   が、非貪欲 (non-greedy)のマッチになる事を理解できる。
Pythonで正規表現を使う1

正規表現をPythonから使うには ?

正規表現: Pythonから使う
難度       : 入門
事前知識: Pythonの基礎文法
学習効果: pythonから正規表現を使う一連の流れを掴む
メタキャラクタに馴染む_key-visual

ハロー ! メタキャラクタ

正規表現: メタキャラクタの概要
難度       : 入門
事前知識: 不要
学習効果: メタキャラクタの概要を掴む
正規表現の概要_key-visual

正規表現とは?

正規表現: 概要
難度       : 入門
事前知識: 不要
学習効果: 正規表現の概要を知る
正規表現の概要

PR