4

Java での正規表現の動作を理解するのに苦労していて、非常に奇妙なことに遭遇しました。以下のコードでは、「6 文字が一致しました、否定的です」というメッセージ ラベルの付いたテストで理解できない理由で、テストが突然失敗します (後続の 2 つのテストも失敗します)。あまりにも長い間これを見つめていたのでしょうか、それとも本当に何か奇妙なことが起こっているのでしょうか? これは可変長の否定先読みアサーション (?!X) とは関係ありませんが、理論や、他の人が同じ問題を経験しており、それが特定の問題ではないという確認さえあれば喜んで聞きます。私のJVM。正規表現が非常に不自然で申し訳ありませんが、実際のものを見たくありません:)

// $ java -version
// java version "1.7.0_10"
// Java(TM) SE Runtime Environment (build 1.7.0_10-b18)
// Java HotSpot(TM) 64-Bit Server VM (build 23.6-b04, mixed mode)

// test of word without agreement
String test = "plusieurs personne sont";

// match the pattern with curly braces
assertTrue("no letters matched", Pattern.compile("plusieurs personne\\b").matcher(test).find());
assertTrue("1 letters matched", Pattern.compile("plusieurs personn\\p{Alpha}{1,100}\\b").matcher(test).find());
assertTrue("2 letters matched", Pattern.compile("plusieurs person\\p{Alpha}{1,100}\\b").matcher(test).find());
assertTrue("3 letters matched", Pattern.compile("plusieurs perso\\p{Alpha}{1,100}\\b").matcher(test).find());
assertTrue("4 letters matched", Pattern.compile("plusieurs pers\\p{Alpha}{1,100}\\b").matcher(test).find());
assertTrue("5 letters matched", Pattern.compile("plusieurs per\\p{Alpha}{1,100}\\b").matcher(test).find());
assertTrue("6 letters matched", Pattern.compile("plusieurs pe\\p{Alpha}{1,100}\\b").matcher(test).find());
assertTrue("7 letters matched", Pattern.compile("plusieurs p\\p{Alpha}{1,100}\\b").matcher(test).find());
assertTrue("8 letters matched", Pattern.compile("plusieurs \\p{Alpha}{1,100}\\b").matcher(test).find());

// match the negative pattern (without s or x) with curly braces
assertTrue("no letters matched, negative", Pattern.compile("plusieurs (?!personne[sx])\\w+").matcher(test).find());
assertTrue("1 letters matched, negative", Pattern.compile("plusieurs (?!personn\\p{Alpha}{1,100}[sx])\\w+").matcher(test).find());
assertTrue("2 letters matched, negative", Pattern.compile("plusieurs (?!person\\p{Alpha}{1,100}[sx])\\w+").matcher(test).find());
assertTrue("3 letters matched, negative", Pattern.compile("plusieurs (?!perso\\p{Alpha}{1,100}[sx])\\w+").matcher(test).find());
assertTrue("4 letters matched, negative", Pattern.compile("plusieurs (?!pers\\p{Alpha}{1,100}[sx])\\w+").matcher(test).find());
assertTrue("5 letters matched, negative", Pattern.compile("plusieurs (?!per\\p{Alpha}{1,100}[sx])\\w+").matcher(test).find());
// the assertion below fails (is false) for reasons unknown
assertTrue("6 letters matched, negative", Pattern.compile("plusieurs (?!pe\\p{Alpha}{1,100}[sx])\\w+").matcher(test).find());
assertTrue("7 letters matched, negative", Pattern.compile("plusieurs (?!p\\p{Alpha}{1,100}[sx])\\w+").matcher(test).find());
assertTrue("8 letters matched, negative", Pattern.compile("plusieurs (?!\\p{Alpha}{1,100}[sx])\\w+").matcher(test).find());
4

1 に答える 1

3

先読みがどのように一致するかを見てみましょう。

pe           literal, matches "pe"
r            matches \p{Alpha}{1,100}
s            matches [sx]

そのため、否定先読みは一致しません (文字列の末尾"onne sont", はここでは関係ありません)。

次の単語が s または x で終わってはならないという考えがある場合は、\\b後に配置すると役立つ場合があります。否定的な先読みは失敗しても後悔することはなく、正規表現を不一致にする方法を見つけるためにバックトラックしないこと[sx]を常に心に留めておいてください。

UPD: ケース 5 を詳しく見て、ケース 6 と比較してみましょう。ここでは、仮定の一致 (先読み内の式) を使用するため、(ほぼ) 発生する可能性がある方法についていくつかのバリエーションを検討する必要があります。

per          literal, would match "per" -- it's always so
             -- let's try to imagine how the rest could match:
sonn         would match \p{Alpha}{1,100}
e            wouldn't match [sx], FAIL
             -- or maybe
s            would match \p{Alpha}{1,100}
o            wouldn't match [sx], FAIL
             -- or maybe yet
so           would match \p{Alpha}{1,100}
n            wouldn't match [sx], FAIL.

2 番目の単語が「personali s ation」だったら、また面白い冒険ができたでしょう。

UPD2:コメントでの議論により、ここに一般化を追加するようになりました:

正規表現は、人間の心の重要な特徴である確証バイアスを共有しているため、魅力的です。正規表現を書くとき、それらが一致することを望みます。私たちの仕事が無効な入力を防ぐことだとしても、ほとんどの場合、有効な入力について考えます。正規表現マッチャーは、通常、このプロパティを共有します:一致を望み、失敗することを嫌います。そのため、部分式 like\p{Alpha}{1,100}は、「残りの入力を照合する前に、利用可能なアルファの最長のチャンクを食べる」という意味ではありません。大まかに言えば、「 [1,100] 以内の長さのアルファの可能なすべてのチャンクを検討し、式全体が一致する方法を見つける」ことを意味します。

そのため、正規表現では、複雑な式の誤検知 (誤って受け入れられた無効な入力)を見落としがちです。この問題は発生しません。否定的な先読みを使用すると、より顕著になります。

否定先読みの内部では、regexp マッチャーが内部の式と一致することを望んでいます (外部の式が失敗します)。人間のプログラマーは、やはり外側の式を一致させたいと考えています。この例でわかるように、この要素は内部表現に関する推論に影響を与えます。私たちは、一致させるのにそれほど苦労すべきではないと考えています(そして、たとえば、部分式をばかげた方法で処理し、可能な限り長い入力を即座に消費する必要があります)。マッチャーは通常どおり動作しますが、望ましい動作に関する私たちの考えは現在、そのアルゴリズムと同期していません。そして、内側の表現の誤検知 (気づきにくい) は、外側の表現の誤検知になります (これは私たちが気づき、嫌うものです)。

于 2013-01-16T21:26:22.070 に答える