3

我想像 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 RegEx 一样?

4

2 回答 2

3

正如 Wiktor 在评论中所建议的那样,您可以使用(?U)打开 flag UNICODE_CHARACTER_CLASS。虽然这确实允许匹配äöa,但这仍然不匹配。那是因为UNICODE_CHARACTER_CLASSwith\w不能识别²为有效的字母数字字符。作为替代品\w,您可以使用[\pN\pL_]. 这匹配 Unicode 数字\pN和 Unicode 字母\pL(加号_)。\pNUnicode字符类包括字符类,其中\pNo包括Latin 1 Supplement - Latin-1 punctuation and symbols字符类(它包括²³¹)。或者,您可以将\pNoUnicode 字符类添加到字符类中\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 ²³¹

那么为什么在 Java 中不\w匹配²,但在 Python 中却匹配呢?


Java的解释

查看OpenJDK 8-b132 的Pattern实现,我们得到以下信息(我删除了与回答问题无关的信息):

Unicode 支持

以下预定义字符类POSIX 字符类符合附件 C 的建议: Unicode 正则表达式的兼容性属性,当 指定标志时。UNICODE_CHARACTER_CLASS

\w 一个字字符:[\p{Alpha}\p{gc=Mn}\p{gc=Me}\p{gc=Mc}\p{Digit}\p{gc=Pc}\p{IsJoin_Control}]

伟大的!现在我们有了一个何时使用标志的定义。将这些 Unicode 字符类插入这个神奇的工具将准确地告诉您这些 Unicode 字符类中的每一个匹配什么。在不让这篇文章超长的情况下,我会继续告诉你以下课程都不匹配:\w(?U)²

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

Python的解释

²³¹那么当u标志与 结合使用时,为什么 Python 会匹配\w?这个很难追踪,但我深入研究了Python 的源代码(我使用了 Python 3.6.5rc1 - 2018-03-13)。在消除了很多关于如何调用它的绒毛之后,基本上会发生以下情况:

  • \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;
}

如我们所见,此方法返回1if _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 编码字节具有标志,这将评估为 true,并且将返回DECIMAL_MASK大于或等于的值。0

²is的UTF-32 编码字节值0x000000b2,我们的标志DECIMAL_MASK0x02. 0x000000b2 & 0x02计算结果为 true,因此²在 python 中被视为有效的 Unicode 字母数字字符,因此\w带有u标志 matches ²

于 2018-03-21T18:28:43.220 回答
0

还剩下一步:您还需要指定\w包含 unicode 字符。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);
    }
于 2018-03-21T15:20:02.477 回答