如何在C#中正确匹配单词分隔符而不匹配其他字符

时间:2013-10-29 20:24:33

标签: c# regex string parsing

抱怨新问题,但C#不是我的第一语言。

我正在尝试在给定的内容中构建单词之间所有分隔符的索引列表,以计算标点符号。我希望使用Regex \ b(单词'boundary'),但它匹配我没想到的各种东西。这是我写的方法:

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#已经有某种处理这个问题的工具,我可以省去重新发明轮子的麻烦。

当然,任何帮助都会受到赞赏。

谢谢, 格雷格。

3 个答案:

答案 0 :(得分:2)

如果正则表达式(\w)中单词字符的定义符合您的需要(为此,请继续阅读),您可以匹配非单词字符(例如,使用其反向单词之间的insterstitial内容)字符类\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)可能不起作用,因为在正则表达式中,“单词”不一定是你认为的。字符类\w的开头是C语言标识符([A-Za-z0-9_])中允许的字符集。如果你是一个使用正则表达式来编写符号源代码的C程序员,那将非常有用。通过文字的文字翻译不太好。

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

  • (信,小写)
  • Lu (字母,大写字母)
  • Lt (letter,title-case)
  • 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)字符之间的边界处锚定匹配。

因此...

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

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

如果一个简单的角色类不适合你,你需要使用各种前瞻/后视断言来匹配你想要的东西。

答案 1 :(得分:1)

单词边界匹配位置示例:

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

所以你可以看到比你想象的更多的比赛。

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

因此\b可以是(?<=\w)(?=\W|$)(?<=\W|^)(?=\w)

答案 2 :(得分:1)

我并不是要打这么长的评论。我想我不妨把它转移到答案上。

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

您可能需要将表达式与外观断言结合起来,以达到您想要的效果。

例如,

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

使用正面的后置断言来确保您只匹配字母后面的字边界。但是,这会考虑空格分隔符,我不确定你想要做什么,在这种情况下,

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

添加了一个附加条件 - 这次是否定前瞻断言,以确保您只匹配单词边界后面没有空格字符。