Подтвердить что ты не робот

Что делает (?! A) {0}? означает в регулярном выражении Java?

Вдохновленный вопросом если {0} квантификатор действительно имеет смысл, я начал играть с некоторыми регулярными выражениями, содержащими квантор {0} и написал эту небольшую java-программу, которая просто разбивает тестовая фраза на основе различных тестовых регулярных выражений:

private static final String TEST_STR =
    "Just a test-phrase!! 1.2.3.. @ {(t·e·s·t)}";

private static void test(final String pattern) {
    System.out.format("%-17s", "\"" + pattern + "\":");
    System.out.println(Arrays.toString(TEST_STR.split(pattern)));
}

public static void main(String[] args) { 
    test("");
    test("{0}");
    test(".{0}");
    test("([^.]{0})?+");
    test("(?!a){0}");
    test("(?!a).{0}");
    test("(?!.{0}).{0}");
    test(".{0}(?<!a)");
    test(".{0}(?<!.{0})");
} 

== > Результат:

"":              [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"{0}":           [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
".{0}":          [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"([^.]{0})?+":   [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"(?!a){0}":      [, J, u, s, t,  , a,  , t, e, s, t, -, p, h, r, a, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"(?!a).{0}":     [, J, u, s, t,  a,  , t, e, s, t, -, p, h, ra, s, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
"(?!.{0}).{0}":  [Just a test-phrase!! 1.2.3.. @ {(t·e·s·t)}]
".{0}(?<!a)":    [, J, u, s, t,  , a , t, e, s, t, -, p, h, r, as, e, !, !,  , 1, ., 2, ., 3, ., .,  , @,  , {, (, t, ·, e, ·, s, ·, t, ), }]
".{0}(?<!.{0})": [Just a test-phrase!! 1.2.3.. @ {(t·e·s·t)}]

Следующий не удивил меня:

  • "", ".{0}" и "([^.]{0})?+" просто разбиваются перед каждым символом и это имеет смысл из-за квантора 0.
  • "(?!.{0}).{0}" и ".{0}(?<!.{0})" ничего не соответствуют. Имеет смысл для меня: отрицательный Lookahead/Lookbehind для токена с 0-квантованием не будет соответствовать.

Что удивило меня:

  • "{0}" и "(?!a){0}": Я действительно ожидал здесь исключения, поскольку предыдущий токен не поддаётся количественной оценке: для {0} нет ничего предшествующего и для (?!a){0} не просто как отрицательный результат. Оба подходят только перед каждым char, почему? Если я попробую это регулярное выражение в валидаторе javascript, я получаю "не количественную ошибку", см. Демонстрацию здесь! Это регулярное выражение обрабатывается по-разному в Java и Javascript?
  • "(?!a).{0}" и ".{0}(?<!a)": здесь немного удивления: они соответствуют перед каждым char фразой, кроме до/после a. Я понимаю, что в (?!a).{0} (?!a) Negative Lookahead часть утверждает, что невозможно сопоставить a буквально, но я смотрю вперед .{0}. Я думал, что это не сработает с 0-квантованным токеном, но похоже, что я тоже могу использовать Lookahead.

== > Итак, оставшаяся загадка для меня - вот почему (?!a){0} на самом деле сопоставляется перед каждым char в моей тестовой фразе. Разве это не должно быть неправильным шаблоном и бросать исключение PatternSyntaxException или что-то в этом роде?


Update:

Если я запускаю тот же код Java в действии Android, результат другой! Там regex (?!a){0} действительно бросает исключение PatternSyntaxException, см.

03-20 22:43:31.941: D/AndroidRuntime(2799): Shutting down VM
03-20 22:43:31.950: E/AndroidRuntime(2799): FATAL EXCEPTION: main
03-20 22:43:31.950: E/AndroidRuntime(2799): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.appham.courseraapp1/com.appham.courseraapp1.MainActivity}: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 6:
03-20 22:43:31.950: E/AndroidRuntime(2799): (?!a){0}
03-20 22:43:31.950: E/AndroidRuntime(2799):       ^
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2180)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.access$600(ActivityThread.java:141)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1234)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.os.Handler.dispatchMessage(Handler.java:99)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.os.Looper.loop(Looper.java:137)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.main(ActivityThread.java:5041)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.lang.reflect.Method.invokeNative(Native Method)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.lang.reflect.Method.invoke(Method.java:511)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at dalvik.system.NativeStart.main(Native Method)
03-20 22:43:31.950: E/AndroidRuntime(2799): Caused by: java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 6:
03-20 22:43:31.950: E/AndroidRuntime(2799): (?!a){0}
03-20 22:43:31.950: E/AndroidRuntime(2799):       ^
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.util.regex.Pattern.compileImpl(Native Method)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.util.regex.Pattern.compile(Pattern.java:407)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.util.regex.Pattern.<init>(Pattern.java:390)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.util.regex.Pattern.compile(Pattern.java:381)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.lang.String.split(String.java:1832)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at java.lang.String.split(String.java:1813)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at com.appham.courseraapp1.MainActivity.onCreate(MainActivity.java:22)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.Activity.performCreate(Activity.java:5104)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
03-20 22:43:31.950: E/AndroidRuntime(2799):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
03-20 22:43:31.950: E/AndroidRuntime(2799):     ... 11 more

Почему regex в Android ведет себя иначе, чем простая Java?

4b9b3361

Ответ 1

Я искал источник oracles java 1.7.

"{0}"

Я нашел код, который бросает "Висячий метасимвол", когда он находит?, * или + в основном цикле. То есть, не сразу после некоторой литеральной, группы, "." или где-либо еще, где явно проверяются квантификаторы. По какой-то причине { не входит в этот список. В результате он проходит через все проверки для специальных символов и начинает синтаксический анализ для строковой строки. Первым символом, с которым он сталкивается, является {, который сообщает синтаксическому анализатору, что пришло время прекратить разбор строки и проверить кванторы.

В результате "{n}" будет соответствовать пустой строке n раз.

Другим результатом является то, что второй "x{m}{n}" будет сначала соответствовать x m раз, затем соответствует пустой строке n раз, эффективно игнорируя {n}, как упоминалось в комментарии @Kobi в комментариях выше.

Кажется, это ошибка, но меня не удивило бы, если бы они захотели сохранить ее для обратной совместимости.

"(?!a){0}"

"(?!a)" является просто a node, который поддается количественной оценке. Вы можете проверить, является ли следующий символ "а" 10 раз. Он будет возвращать тот же результат каждый раз, хотя, поэтому он не очень полезен. В нашем случае он проверяет, является ли следующий символ "а" 0 раз, что всегда будет успешным.

Обратите внимание, что в качестве оптимизации, когда совпадение имеет длину 0, например здесь, квантификатор никогда не бывает жадным. Это также предотвращает бесконечную рекурсию в случае "(?!a)*".

"(?!a).{0}" & ".{0}(?<!a)"

Как упоминалось выше, {0} выполняет проверку 0 раз, что всегда выполняется. Он эффективно игнорирует все, что приходит перед ним. Это означает, что "(?!a).{0}" совпадает с "(?!a)", который имеет ожидаемый результат.

Аналогичен для другого.

Android отличается

Как упоминалось в @GenericJam, андроид - это другая реализация и может иметь разные характеристики в этих случаях. Я тоже пытался посмотреть на этот источник, но андроид действительно использует собственный код:)