正規表現1(クリーネ代数で遊ぶ)

このページは[url=https://www.geogebra.org/m/ddm5798j]マス旅[/url]の一部です。[br][br]正規表現は慣れていない人が多いため嫌われがちだと思います。[br]でも、その複雑怪奇なルールを探求すると[br]意外なことに、数学とのつながりが凄く強いことがわかってきますよ。[br]その歴史とルールを調べることで、暗記ではなく、遊びの道具にしましょう。
1.数学外の知識
[br]表現というものを日常言語であれ、数式であれ、プログラミング言語であれ[br]「記号を色分けすること」から理解できるようになります。[br]それは正規表現でも同じこと。[br][br]日常言語ならば、名詞、動詞、形容詞、副詞などに色分けしたり、[br]句と節とかの区切りのコトバなどがありますね[br]数式ならば、定数、変数、数字、演算、関数、[br]演算用の添え字(上、下)、不等号などと色分けしますね。[br][br]正規表現も色分けが大事です。[br]普通の記号、普通にする記号、一般記号、抽象記号、まとまり記号、論理的な記号などなど。この言葉遣いはzenの勝手な色分けですが、素人目線の方がわかりやすい場合も[br]あるでしょうから、お付き合いください。[br][br]正規表現は、検索・置換が主な用途です。[br]つまり、探したい表現をそのままかくのではなく、少し広げて書くこと、[br]そして、広げて探して終わりではなくて、[br]探しあてた表現の部分を、意図的に置換したりするときに使われます。[br][br]特定のプログラミング言語に依存しないでかきたいので、[br]検索対象表現はシングル引用符でくくり、正規表現はダブル引用符にして、それを[br]'1','2','3'←"123"のように書くことにします。[br][br][b][size=150]<1文字を探す>[br][/size][/b][br]まずは1文字検索からいこう。[br]その用途に使えるのが、[br][b][color=#0000ff]ドット(.)ハイフン(-)かっこ([],())[/color][/b]です。[br](正規表現の予約文字を[b]メタ文字(メタキャラ)[/b]と言います。)[br][br]普通に1文字が並んだものは、ジャストそれだけを探します。[br]'a'←"a"[br]'A'←"A"[br]ドットは数字以外の一文字の代用です。たとえばこうです。[br]'make','take','fake','@ake',.... ←"[b][size=150].[/size][/b]ake"[br]でも、広すぎますね。[br]makeとtake[br]だけを拾いたいなら、[br]1文字集合を大かっこでくくるか、[br]パイプラインで区切って小カッコでくくります。[br]'make','take'←"[b][mt]ake[/b]"[br]'make','take'←"[b](make|take)[/b]"[br]この中くらいの書き方もあります。1英小文字だけとか、1英大文字だけとか、1数字だけとかならんでもよいという場合です。[br]'aake','bake','cake','dake',..,'zake'←"[b][a-z][/b]ake"[br]'Aake','Bake','Cake','Dake',..,'Zake'←"[b][A-Z][/b]ake"[br]'0ake','1ake','2ake','3ake',..,'9ake'←"[b][0-9][/b]ake"[br][br]メタ文字を普通の文字に使いたいときもあるでしょう。[br][br][b][color=#0000ff]バックスラッシュ(\)[/color][/b]が前につくと、普通の文字になりますね。[br]'3.14'←"3[b]\.[/b]14"とかくのですね。小数をふつうに探すときは小数点の前に\をつけることになるんだね。[br][br]バックスラッシュ(\)のもっと別の使い方もあります。[br]文字の普通名詞化のようなもんです。[br][0-9]とかくかわりにdigitという意味で[b]\d[/b]が数字です。なぜか[b]\D[/b]が数字以外です。[br]spaceの意味で、[b]\s[/b]が空白(スペース、タブ、改行)を、[b]\S[/b]が空白以外を指します。[br][br][br][b][size=150]<連続するかも>[br][/size][/b][br]SQL文やクラス図を作るときに、個数範囲を0以上、1以上で表す記号もあります。[br]これは便利ではあるけど、見た目と違うものが引っかかるので要注意です。[br][b][color=#0000ff]クエスチョン(?),アスタリスク(*),プラス(+)[/color][/b][br]あるかも記号(?)はあとにつけます。[br]"20000?円"→'2000円','20000円'[br]"Mac ?OS"→'Mac OS','MacOS'です。これは便利だね。[br][br]もっとすざまじい記号があります。[br][color=#0000ff][b][size=150][size=200]x*はxが0個以上、x+はxが1個以上です。[br][/size][/size][/b][/color][b][size=200]"20*円"←'2円','20円',...'2000000円',.....[br]"20+円"←'20円',...'2000000円',.....[br][br][/size][/b]連続のさぼり表記もありますよ。[br]2千万円は0が7個と分かっているけど、0の連続記述をさぼりたいなら、[br][b]{回数}と中カッコ[/b]を使います。[br]"20{7}円"←'20000000円'でOKです。[br][br][b][size=150]<アンカー記号>[br][/size][/b]^は先頭、$は末尾の表示で使われます。[br][^abc] のように大かっこの先頭につけると「abc以外の1文字」という否定の意味になります。[br][br][b][size=150]<キャプチャグループの参照番号が検索終了条件となる>[br][/size][/b][br][b][color=#0000ff][size=150]事前に()で囲まれた部分正規表現(グループ)に対して[/size][/color][/b][br][b][size=200]\1はグループ1[/size][/b]として展開されます。[br][br][b][color=#0000ff][size=200]"^(.).*\1+"[/size][/color][br][/b]は^(.)が後ろで\1として展開されて、+があるため、[b][u]あるだけ範囲を貪欲に取り出し[/u][/b]ます。[br]どこからどこまで取り出せるかを順序立てて見ていきましょう。[br][br]'abcdefghaaabb'⇒[br][b]"^(.)"[/b]は先頭文字[b]a[/b]を受け取ります。[br]'abcdefghaaabb'⇒[b][a][/b][br][b]".*"[/b]があるので、途中の文字を[b]"\1+"=a連続[/b]まで読み取ります。[br]'abcdefghaaabb'⇒[b][abcdefgh][/b][br][b]"\1+"[/b]でaaaまで読み取ります。[br]'abcdefghaaabb'⇒[b][abcdefghaaa][/b][br]
2.数学の支援
[br]さっきまでは、正規表現の例を紹介しました。[br]さて、正規表現の始まりは意外なことに、あまり、広くは語られていないようです。[br]1940年代にアメリカの数学者クリーネの発案です。[br]正規集合、正規表現という文字集合のルールを考えたそうです。[br][br]これをクリーネ代数といいます。[br][br][b][size=150]<クリーネ代数>[/size][/b][br]文字列の集合をΣとする。[br]その要素をA,Bとすると、[br]連結(連接)、つまり積ABも要素だ。[br]選択、つまり和A|Bも要素になる[br]要素を0個以上連続したもの(閉包)A*やB*も要素とする。[br]演算の優先順位は[b]閉包、連接、和[/b]の順に強い。[br]空集合も要素だ。[br]和の単位元ゼロ(0)は決してマッチしない表現[br]積の単位元単位元(1): 空文字列 ^$[br][br]a*c|dは閉包、連結、和の順番に読み取れます。[br]a(b|c)は「a(b+c)」の意味になり、ab|acは「ab+ac」の意味だから、[br]普通の代数計算の分配法則も成り立つことがわかるね。[br][br]たし算とかけ算はできるけど、たし算の逆元はないので[br]クリーネ代数は「半環」の代数と言われることもあるね。[br][br][br][b][size=150]<正規表現を数学の道具にする>[br][/size][/b][br]おもしろい例があります。[br][b]"^1?$|^(11+?)\1+$"[/b]で、なんと、[br][b]合成数であることが判断できる[/b]のです。[br][br]分解して、検索の開始と終了条件を見るとわかります。[br][br][color=#0000ff][b][size=150][size=200]"^1?$"[/size][/size][/b][/color][br]アンカー^と$にはさまれているので、"1?"のみ、つまり[b]1か空[/b]ですね。[br][color=#0000ff][b][size=200]"^(11+?)\1+$" [br][/size][/b][/color]検索グループと参照番号でできています。[br]最後部分は[b]\1+$[/b]になっていますね。[br][b]\1[/b]の代入部分が1回以上繰り返して[b]終了($)余分なものがない[/b]。[br]では検索グループとなる部分正規表現はどうでしょうか。[br][br][b]^(11+?)[/b]は先頭から1が2個以上あるかの検索を、[b][u][color=#0000ff]控えめに無欲に[/color][/u][/b]探します。[br]2個、3個、…と長くします。[br][b]^(11+)[/b]だったら、[b][u]強欲マッチなのでマッチ範囲を一気に最長のもの[/u]を[u][color=#0000ff]貪欲に[/color][/u]探します[/b]。[br]しかし、^(11+?)は、[b]マッチする範囲を地道に増やしていきます[/b]。[br]いきなり最長をめざさないのです。[br]つまり、[br][color=#0000ff][b][size=200]この「無欲」ということが[br]検索が成功するまで繰り返すという「粘り強さ」につながるのです。[/size][/b][/color][br][br]具体的な例で試してみよう。[br][br][b]N4="1111"[br]N5="11111"[br][/b][br]N4で"^1?$|^(11+?)\1+$"を調べるとどうなるでしょう。[br]前半"^1?$"条件1か空には当てはまらないので、次の|のあとを確認しましょう。[br]先頭の[b]'11'=\1[/b]にすると、[b]残りが'11'1個反復でヒット[/b]しますから、[br]"^1?$|^(11+?)\1+$"には[br][b]合格[/b]です。[br][b]全体4文字が\1がさす2文字で割り切れて合成数[/b]になっています。[br]だから、4は合成数と言えます。[br][br]N5の場合はどうでしょうか。[br]N4と同様に[b]'11'=\1[/b]にできますが、5個は2個ずつ切って余るので失敗です。[br]無欲ですが、あきらめません。[br][b]'111'=\1[/b]にしてみます。やはり失敗。[br][b]'1111'=\1[/b]にしても失敗。[br][b]'11111'[/b]にすると、あと1個入る隙間どころか[b]検索範囲は終わってますから失敗[/b]。[br]最後まで調べたので終了ですね。[br]だから、合成数テストは不合格です。[br][b]「全体5文字が\1がさす文字が2回以上入るものの検索」としての合成数テスト[br][/b]は不合格だから、素数となるのです。
3.コード化
[br][b][size=150]<Python>[br][/size][/b][br]import re[br][br]# 合成数(2以上の非素数)にマッチする正規表現[br]# ^1?$ : 0または1[br]# | : または[br]# ^(11+?)\1+$ : 2以上の長さの1の繰り返し(約数を持つ)[br]composite_pattern = re.compile(r"^1?$|^(11+?)\1+$")[br][br]def is_prime(n):[br] # n を '1' の n個の繰り返し文字列に変換してマッチング[br] return not bool(composite_pattern.match("1" * n))[br][br]# 2〜20 の素数判定[br]for i in range(1, 21):[br] print(f"{i:2d}: {'素数' if is_prime(i) else '合成数'}")[br][br][出力例][br]1: 合成数[br] 2: 素数[br] 3: 素数[br] 4: 合成数[br] 5: 素数[br] 6: 合成数[br] 7: 素数[br] 8: 合成数[br] 9: 合成数[br]10: 合成数[br]11: 素数[br]12: 合成数[br]13: 素数[br]14: 合成数[br]15: 合成数[br]16: 合成数[br]17: 素数[br]18: 合成数[br]19: 素数[br]20: 合成数[br][br][b]<geogebra>[br][/b][br]geogebraには、[br]正規表現はありませんが、IsPrime(n)という関数が標準で[br]入っているので、「自前でトリッキーなプログラミングはしなくていいですよ」[br]というスタンスでしょうね。[br][br]タイトル「IsPrimeを使おう」[br][br]N=Slider(2,20,1)[br]text1=If(IsPrime(N)==True,N+":素数", N+":合成数")[br]
IsPrimeを使おう

Bilgi: 正規表現1(クリーネ代数で遊ぶ)