では、ここで何が起こっているか見てみましょう。ありがたいことに、正規表現を適用するとどうなるかまで、問題はすでに単純化されています
(d+)*@
文字列に
ddddd
が欠落しているため、明らかに一致できません@
。しかし、正規表現エンジンがこれをすぐに理解できないのはなぜでしょうか?
本質(d+)*
的には、次の一致によって満たすことができます (各グループはスペースで区切られています)。
ddddd
dddd d
ddd dd
dd ddd
d dddd
ddd d d
dd d dd
d d ddd
dd dd d
d dd dd
d ddd d
d d d dd
d d dd d
d dd d d
dd d d d
d d d d d
したがって、文字列を分割せずに文字列を照合する方法は 1 つ、文字列を 2 つの文字列に分割する方法は 4 つ、文字列を 3 つに分割する方法は 6 つ、文字列を 4 つに分割する方法は 4 つ、文字列を 5 つの文字列に分割する方法は 1 つです。 . これらのすべての組み合わせは、最終的に次の@
.
なぜもっと早くそれを理解しないのですか?まあ、一部の正規表現エンジンは、おそらくこのような単純化された例でそれを行うことができます. ラリー・ウォールがそれをカバーしていると思います。しかし、実際の正規表現はもう少し複雑なので、事前に把握するのははるかに難しいと思います. さらに、このすべての組み合わせ試行が起こらないようにする簡単な方法があります。しかし、私は後でそれに戻ります。
私は、これらのちっぽけな 5 よりも長い文字列に対して、そのような組み合わせがいくつあるのか疑問に思っていましたd
。
長さのあるひもを取りましょうm
(さまざまな位置で切り離すことができますm-1
)。としましょうn = m-1
。次に、次のように組み合わせの数を計算できます。
1 + (n!/(1! * (n-1)!)) + (n!/(2! * (n-2)!)) + ... + (n!/(n! * (n-n)!))
最初と最後の項目は常に 1 ですが、その間の項目は非常に大きくなる可能性があります。小さな Python プログラムを書きましょう:
>>> from math import factorial as f
>>> def comb(n,x):
... return f(n) // (f(x) * f(n-x))
...
>>> def ways_to_split(len):
... return 1 + sum(comb(len-1,x) for x in range(1,len))
...
>>> ways_to_split(5)
16
OK、うまくいくようです。もっと大きなものを試してみましょう:
>>> ways_to_split(10)
512
>>> ways_to_split(40)
549755813888
>>> ways_to_split(100)
633825300114114700748351602688
ねえ、ここにパターンがあります: ways_to_split(n)
is equal to 2**(n-1)
. 証明については、Mathematics SEを参照してください。ともかく。正規表現にはO(2^n)
複雑さがあります。さて、これで時間がかかる理由がわかりました...
ありがたいことに、多くの正規表現エンジンはこれに対する保護を提供します: 所有量指定子またはアトミック キャプチャ グループ。
(d++)*
また
(?>d+)*
どちらも、d+
他の組み合わせを試すために一致したものが再び放棄されないようにします。良いニュースですね。
JavaScript を使用している場合はそうではありません。これらの機能のいずれもサポートしていません。
したがって、次のようにバックトラッキングの影響を受けないように正規表現を書き直す必要があります。
それ以外の
(([_\.\-]?[a-zA-Z0-9]+)*)
使用する
[a-zA-Z0-9]+([_.-][a-zA-Z0-9]+)*
または、いずれにせよ、正規表現を使用して電子メール アドレスを検証しようとするのはやめてください。