Регулярное выражение Java не соответствует диапазону ascii, ведет себя иначе, чем регулярное выражение python

Я хочу фильтровать строки из документов так же, как sklearn CountVectorizer делает. Он использует следующее регулярное выражение: (?u)\b\w\w+\b. Этот код Java должен вести себя так же:

Pattern regex = Pattern.compile("(?u)\\b\\w\\w+\\b");
Matcher matcher = regex.matcher("this is the document.!? äöa m²");

while(matcher.find()) {
    String match = matcher.group();
    System.out.println(match);
}

Но это не дает желаемого результата, как в python:

this
is
the
document
äöa
m²

Вместо этого он выводит:

this
is
the
document

Что я могу сделать, чтобы включить символы, отличные от ascii, как это делает python RegeEx?


person Daniel Kirchner    schedule 21.03.2018    source источник
comment
Попробуйте "(?U)\\b\\w\\w+\\b" или просто "(?U)\\w{2,}"   -  person Wiktor Stribiżew    schedule 21.03.2018
comment
@WiktorStribiżew, который работает для äöa, но не работает для   -  person ctwheels    schedule 21.03.2018
comment
МОЖЕТ быть, это полезно? stackoverflow.com/questions/6381752 /   -  person Lance Toth    schedule 21.03.2018
comment
Спасибо! Это работает для немецких букв, но по-прежнему не включает знак квадрата (²), есть идеи, как это исправить?   -  person Daniel Kirchner    schedule 21.03.2018
comment
@LanceToth я работаю с Java, а не с JavaScript   -  person Daniel Kirchner    schedule 21.03.2018
comment
Вы хотите убедиться, что Unicode \w в регулярном выражении Java соответствует тем же символам, что и Unicode \w в Python?   -  person Wiktor Stribiżew    schedule 21.03.2018
comment
@DanielKirchner извините, неправильно прочитал   -  person Lance Toth    schedule 21.03.2018
comment
Хорошо, то же самое, но для Java stackoverflow.com/questions /10894122/   -  person Lance Toth    schedule 21.03.2018
comment
@WiktorStribiżew точно, у меня есть довольно большой файл документа, с которым я это тестирую, единственная разница на данный момент (между python и java) заключается в том, что python выбирает ²,½,³.   -  person Daniel Kirchner    schedule 21.03.2018
comment
@LanceToth теперь работает для Unicode с (?U), теперь отсутствуют только экспоненты и дроби.   -  person Daniel Kirchner    schedule 21.03.2018
comment
Чтобы просто поддерживать надстрочные/индексные числа, вы можете расширить шаблон до "(?U)[\\w\\p{No}]{2,}".   -  person Wiktor Stribiżew    schedule 21.03.2018
comment
@WiktorStribiżew Большое спасибо, кажется, это работает!   -  person Daniel Kirchner    schedule 21.03.2018
comment
Возможный дубликат эквивалентов Unicode для \w и \b в Регулярные выражения Java?   -  person Tamas Rev    schedule 21.03.2018
comment
Нет, это не обман, но весьма полезно.   -  person Wiktor Stribiżew    schedule 22.03.2018


Ответы (2)


Как предложил Виктор в комментариях, вы можете использовать (?U) для включения флага UNICODE_CHARACTER_CLASS. Хотя это позволяет сопоставить äöa, это все еще не соответствует . Это потому, что UNICODE_CHARACTER_CLASS с \w не распознает ² как допустимый буквенно-цифровой символ. В качестве замены \w можно использовать [\pN\pL_]. Это соответствует числам Юникода \pN и буквам Юникода \pL (плюс _). Класс символов \pN Unicode включает класс символов \pNo, который включает в себя класс символов Дополнение к латинице 1 — пунктуация и символы Latin-1 (в него входит ²³¹). В качестве альтернативы вы можете просто добавить класс символов Unicode \pNo в класс символов с \w. Это означает, что следующие регулярные выражения правильно соответствуют вашим строкам:

[\pN\pL_]{2,}         # Matches any Unicode number or letter, and underscore
(?U)[\w\pNo]{2,}      # Uses UNICODE_CHARACTER_CLASS so that \w matches Unicode.
                      # Adds \pNo to additionally match ²³¹

Так почему же \w не соответствует ² в Java, но соответствует в Python?


интерпретация Java

Глядя на Реализация OpenJDK 8-b132 Pattern, мы получаем следующую информацию (я удалил информацию, не имеющую отношения к ответу на вопрос):

Поддержка Юникода

Следующие предопределенные классы символов и классы символов POSIX соответствуют рекомендациям Приложения C: свойства совместимости регулярных выражений Unicode< /em>, если указан флаг UNICODE_CHARACTER_CLASS.

\w Словесный символ: [\p{Alpha}\p{gc=Mn}\p{gc=Me}\p{gc=Mc}\p{Digit}\p{gc=Pc}\p{IsJoin_Control}]

Здорово! Теперь у нас есть определение для \w при использовании флага (?U). Включив эти классы символов Unicode в этот замечательный инструмент, вы точно узнаете, что каждый из эти классы символов Unicode совпадают. Не делая этот пост слишком длинным, я просто скажу вам, что ни один из следующих классов не соответствует ²:

  • \p{Alpha}
  • \p{gc=Mn}
  • \p{gc=Me}
  • \p{gc=Mc}
  • \p{Digit}
  • \p{gc=Pc}
  • \p{IsJoin_Control}

интерпретация Python

Так почему же Python соответствует ²³¹, когда флаг u используется вместе с \w? Это было очень сложно отследить, но я покопался в исходном коде Python (я использовал Python 3.6. 5rc1 - 13.03.2018). После удаления большого количества пуха о том, как это называется, в основном происходит следующее:

  • \w определяется как CATEGORY_UNI_WORD, к которому затем добавляется префикс SRE_. SRE_CATEGORY_UNI_WORD звонит SRE_UNI_IS_WORD(ch)
  • SRE_UNI_IS_WORD определяется как (SRE_UNI_IS_ALNUM(ch) || (ch) == '_').
  • SRE_UNI_IS_ALNUM вызывает Py_UNICODE_ISALNUM, который, в свою очередь, определяется как (Py_UNICODE_ISALPHA(ch) || Py_UNICODE_ISDECIMAL(ch) || Py_UNICODE_ISDIGIT(ch) || Py_UNICODE_ISNUMERIC(ch)).
  • Важным здесь является Py_UNICODE_ISDECIMAL(ch), определяемый как Py_UNICODE_ISDECIMAL(ch) _PyUnicode_IsDecimalDigit(ch).

Теперь давайте посмотрим на метод _PyUnicode_IsDecimalDigit(ch):

int _PyUnicode_IsDecimalDigit(Py_UCS4 ch)
{
    if (_PyUnicode_ToDecimalDigit(ch) < 0)
        return 0;
    return 1;
}

Как мы видим, этот метод возвращает 1, если _PyUnicode_ToDecimalDigit(ch) < 0. Так как же выглядит _PyUnicode_ToDecimalDigit?

int _PyUnicode_ToDecimalDigit(Py_UCS4 ch)
{
    const _PyUnicode_TypeRecord *ctype = gettyperecord(ch);

    return (ctype->flags & DECIMAL_MASK) ? ctype->decimal : -1;
}

Отлично, в основном, если байт символа в кодировке UTF-32 имеет флаг DECIMAL_MASK, это будет оценено как true, и будет возвращено значение, большее или равное 0.

Значение байта в кодировке UTF-32 для ² равно 0x000000b2, а наш флаг DECIMAL_MASK равен 0x02. 0x000000b2 & 0x02 оценивается как true, поэтому ² считается допустимым буквенно-цифровым символом Unicode в python, поэтому \w с флагом u соответствует ².

person ctwheels    schedule 21.03.2018

Остался еще один шаг: вам нужно указать, что \w также включает символы юникода. Pattern.UNICODE_CHARACTER_CLASS для спасения:

    Pattern regex = Pattern.compile("(?u)\\b\\w\\w+\\b", Pattern.UNICODE_CHARACTER_CLASS);
                                                   // ^^^^^^^^^^
    Matcher matcher = regex.matcher("this is the document.!? äöa m²");

    while(matcher.find()) {
        String match = matcher.group();
        System.out.println(match);
    }
person Tamas Rev    schedule 21.03.2018