【问题标题】:How to properly match word separators in C# without matching additional characters如何在 C# 中正确匹配单词分隔符而不匹配其他字符
【发布时间】:2013-10-29 20:24:32
【问题描述】:

为新手问题道歉,但 C# 不是我的第一语言。

我正在尝试为给定内容中的单词之间的所有分隔符建立一个索引列表,并考虑标点符号。我希望使用正则表达式 \b (单词“边界”),但它与我没想到的各种东西相匹配。这是我写的方法:

internal static IList<int> GetBreakIndexesInContent(string content)
{
    IList<int> indices = new List<int>();
    if (content != null) 
    {
        foreach (Match match in Regex.Matches(content, @"\b"))
        {
            Console.WriteLine("INDEX:[" + match.Index + "]   CHAR:[" + content.Text[match.Index] + "]   UNICODE:[" + (int)content.Text[match.Index] + "]");
            indices.Add(match.Index);
        }
    }
    return indices;
}

给定以下 100 个字符的字符串:

"Lorem ipsum dolor sit amet, tritani quaestio suscipiantur mea ea, duo et impedit facilisi evertitur."

我希望我的方法生成一个长度为 14 个元素的列表,其中第一个索引是位置 5,第二个位置是 11,依此类推(忽略位置 26 和 64 处的逗号,以及99)。相反,这是我得到的输出:

//COUNT: [30]
INDEX:[0]   CHAR:[L]   UNICODE:[76]
INDEX:[5]   CHAR:[ ]   UNICODE:[32]
INDEX:[6]   CHAR:[i]   UNICODE:[105]
INDEX:[11]   CHAR:[ ]   UNICODE:[32]
INDEX:[12]   CHAR:[d]   UNICODE:[100]
INDEX:[17]   CHAR:[ ]   UNICODE:[32]
INDEX:[18]   CHAR:[s]   UNICODE:[115]
INDEX:[21]   CHAR:[ ]   UNICODE:[32]
INDEX:[22]   CHAR:[a]   UNICODE:[97]
INDEX:[26]   CHAR:[,]   UNICODE:[44]
INDEX:[28]   CHAR:[t]   UNICODE:[116]
INDEX:[35]   CHAR:[ ]   UNICODE:[32]
INDEX:[36]   CHAR:[q]   UNICODE:[113]
INDEX:[44]   CHAR:[ ]   UNICODE:[32]
INDEX:[45]   CHAR:[s]   UNICODE:[115]
INDEX:[57]   CHAR:[ ]   UNICODE:[32]
INDEX:[58]   CHAR:[m]   UNICODE:[109]
INDEX:[61]   CHAR:[ ]   UNICODE:[32]
INDEX:[62]   CHAR:[e]   UNICODE:[101]
INDEX:[64]   CHAR:[,]   UNICODE:[44]
INDEX:[66]   CHAR:[d]   UNICODE:[100]
INDEX:[69]   CHAR:[ ]   UNICODE:[32]
INDEX:[70]   CHAR:[e]   UNICODE:[101]
INDEX:[72]   CHAR:[ ]   UNICODE:[32]
INDEX:[73]   CHAR:[i]   UNICODE:[105]
INDEX:[80]   CHAR:[ ]   UNICODE:[32]
INDEX:[81]   CHAR:[f]   UNICODE:[102]
INDEX:[89]   CHAR:[ ]   UNICODE:[32]
INDEX:[90]   CHAR:[e]   UNICODE:[101]
INDEX:[99]   CHAR:[.]   UNICODE:[46]

我之所以不简单地尝试匹配" " 或稍后只是过滤ASCII 32,是因为这需要对不一定在所有单词之间使用空格的外语保持敏感。另外,因为我不想无意中将多个空间捕获为单独的“分隔符”。

我真的希望\b 能成为真正的单词分离的一个很好的标准包罗万象,但似乎并非如此。我可以“自己动手”,但如果 C# 已经有某种工具来处理这个问题,我希望我可以省去重新发明轮子的麻烦。

当然,我们将不胜感激。

谢谢, 格雷格。

【问题讨论】:

  • \b 是一种被称为 zero-width 转义序列的东西 --- 从技术上讲,它实际上不匹配任何 character ---但匹配所有,嗯...boundaries,这就是为什么您看到的结果比您预期的要多。试试\b(?&lt;=[a-zA-Z])。这使用了一种称为肯定的后向断言的东西来断言边界之前的字符是一个字母。如果您不希望 spaces 成为这些边界的一部分,请尝试 \b(?&lt;=[a-zA-Z])(?!\s),它只是添加一个 negative lookahead assertion 来断言后面的字符边界不是空格。
  • 阅读本文了解更多信息:regular-expressions.info/wordboundaries.html

标签: c# regex string parsing


【解决方案1】:

如果正则表达式中单词字符的定义 (\w) 满足您的需要(对此,请继续阅读),您可以匹配非单词字符(例如,使用其反字符类来匹配单词之间的插页式内容) , \W. 解决方案可能很简单

private static readonly Regex rxWord = new Regex( @"\w+" ) ;
static IEnumerable<string> ParseWords( string s )
{
  return rxWord.Matches(s).Cast<Match>().Select( m => m.Value ) ;
}

private static Regex rxNonWord = new Regex( @"\W+" ) ;
private static IEnumerable<string> ParseNonWords( string s )
{
  return rxNonWord.Matches(s).Cast<Match>().Select( m => m.Value ) ;
}

但是从你所说的你想要做的事情来看,从Unicode categories that the CLR supports 组成你的字符类或单词分隔符可能更容易。

此外,使用正则表达式“word”和“non-word”类(\w\W)以及它们之间的边界(\b)可能不起作用,因为在 regex-speak 中,a “词”不一定是你想的那样。字符类 \w 最初是作为 C 语言标识符 ([A-Za-z0-9_]) 中允许的字符集。如果您是使用正则表达式通过源代码查找符号的 C 程序员,这将非常有用。不太适合在随意的文本中翻找单词。

CLR 正则表达式中\w 的当前定义是它匹配包含在任何这些 Unicode 类别中的任何字符:

  • Li(字母,小写)
  • Lu(字母,大写)
  • Lt(字母,标题大小写)
  • Lo(字母,其他)
  • Lm(字母、修饰符)
  • Nd(数字,十进制数字)
  • Pc(标点符号、连接符)此类别包括 10 个字符。至少在英语中,这里最常见的是_ (0x005F) 又名下划线或LOWLINE。

要说的是\w[\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nd}\p{Pc}]的懒惰写法。

非单词字符类\W 与此相反。这完全等同于说[^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Lm}\p{Nd}\p{Pc}]

零宽度锚点\b 不会“匹配”任何东西:就像它的姐妹^$ 一样,\b 将匹配项锚定 到特定位置。在 \b 的情况下,该位置是单词 (\w) 和非单词 (\W) 字符之间的边界。 \b 有一个表亲 \B,它匹配逆:它将匹配锚定在两个单词 (\w) 或两个非单词 (\W) 字符之间的边界。

所以...

您需要首先提出适合您的问题领域的“单词”定义。这比看起来更难:例如,“二十三”是一个词还是两个词? “前妻”呢?或者像“抽象表现主义”这样的复合词怎么样,取决于上下文是一个或两个词(你会在字典中找到“抽象”、“表现主义”和“抽象表现主义”作为单独的条目)。

如果您可以定义一个符合该定义的字符类,那么一切都很好。要匹配单词之间的插页式内容,您所要做的就是定义它的反字符类。

如果一个简单的字符类不能满足你,你需要使用各种前瞻/后瞻断言来匹配你想要的。

【讨论】:

  • 很棒的概述。尽管我没有将其作为“答案”,但我还是给了它一个冲击,因为它确实值得加分。
【解决方案2】:

我不是故意要打这么长的评论。我想我不妨将其移至答案。

\b 匹配单词和非单词字符之间的所有边界,ie\w\W 之间,包括字符串开头和您的第一个字母之间、字母和空格之间(在空格的两边),等等。

您可能需要将您的表达式与 lookaround assertions 结合起来以实现您想要的。

例如,

\b(?<=[a-zA-Z])

使用肯定的后向断言来确保您只匹配字母后面的单词边界。但是,这会考虑空格分隔符,我不确定您是否想要这样做,在这种情况下,

\b(?<=[a-zA-Z])(?!\s)

添加了一个附加条件——这次是一个否定的前瞻断言,以确保您只匹配不带空格字符的单词边界。

【讨论】:

  • 谢谢!看来,我对正则表达式也相对一无所知。我从来没有听说过环视。现在阅读它们!你的回答让我离我需要去的地方只有一丁点距离。关于环视的更多阅读,我相信我能得到其余的。 :) 再次感谢!
  • @GregGauthier - 很高兴为您提供帮助。如果您遇到困难,请随时发表评论。环顾四周可能是一件棘手的事情。并且以防万一您有一天将正则表达式带到其他语言,请记住 C# 具有特别灵活的外观实现,eg 大多数其他语言不支持“可变宽度后视”,而C# 支持,而像 Javascript 等一些语言甚至根本不支持后视。
【解决方案3】:

单词边界匹配位置示例:

 In   Lorem   ipsum   dolor   sit   amet, 
^  ^ ^     ^ ^     ^ ^     ^ ^   ^ ^    ^^  

因此,您可以看到比您想象的更多的匹配项。

从技术上讲,边界是一个断言。断言存在于“字符之间”。
当他们坐在角色之间时,他们倾向于向前看或向后看。

所以\b 可以是(?&lt;=\w)(?=\W|$)(?&lt;=\W|^)(?=\w)

【讨论】:

    猜你喜欢
    • 2016-12-06
    • 2021-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-01
    • 2016-11-16
    • 2013-06-04
    相关资源
    最近更新 更多