【问题标题】:weird regex behavior in the tokenization标记化中奇怪的正则表达式行为
【发布时间】:2016-04-15 22:03:24
【问题描述】:

我正在使用以下正则表达式进行标记:

reg = new Regex("([ \\t{}%$^&*():;_–`,\\-\\d!\"?\n])");

正则表达式应该稍后过滤掉所有内容,但是我遇到问题的输入字符串格式如下:

; "string1"; "string2"; "string...n";

字符串的结果:; "social life"; "city life"; "real life" 我知道应该如下所示:

; White " social White life " ; White " city White life " ;  White " real White life "

但是有一个问题,我得到以下形式的输出

; empty White empty " social White life " empty ; empty White empty " city White life " empty ; empty White empty " real White life " empty

White:表示空白, empty:表示拆分数组中的空条目。

我的拆分代码如下:

string[] ret = reg.Split(input);
 for (int i = 0; i < ret.Length; i++)
        {
            if (ret[i] == "")
                Response.Write("empty<br>");
            else
                if (ret[i] == " ")
                    Response.Write("White<br>");
                else
                    Response.Write(ret[i] + "<br>");
        }

为什么我得到这些空条目?尤其是当有; 后跟空格后跟" 时,结果如下所示:

; empty White empty "

我能解释一下为什么该命令会添加空条目吗?以及如何在没有任何额外 O(n) 复杂性或使用另一个数据结构作为 ret

的情况下删除它们

【问题讨论】:

  • 为了简化这一点,您是否只是想获取引号之间的句子(然后拆分单个单词)?
  • 我需要对所有内容进行标记,然后我将删除除 " 和 ; 之外的所有符号
  • Regex.Split() 在下列情况下总是会添加一个空元素: - 如果在输入字符串的开头或结尾找到匹配项,则在开头包含一个空字符串或返回数组的末尾。 - 如果多个匹配项彼此相邻,则将一个空字符串插入到数组中。例如,将字符串拆分为单个连字符会导致返回的数组在找到两个相邻连字符的位置包含一个空字符串,如以下代码所示。
  • 我不明白匹配空字符串的标准是什么。 拆分数组中的空条目是什么意思?

标签: c# regex tokenize


【解决方案1】:

根据我的经验,在正则表达式匹配时拆分几乎总是不是最好的主意。通过普通匹配,您将获得更好的结果。

而且正则表达式非常适合用于标记化目的,因为它们让您可以非常轻松地实现状态机,请看一下:

\G(?:
  (?<string> "(?>[^"\\]+|\\.)*" )
| (?<separator> ; )
| (?<whitespace> \s+ )
| (?<invalid> . )
)

Demo - 当然可以和RegexOptions.IgnorePatternWhitespace一起使用。

在这里,每个匹配项将具有以下属性:

  • 它将在上一个匹配的末尾开始,因此不会有不匹配的文本
  • 它将包含恰好一个匹配组
  • 组名告诉你令牌类型
  • 您可以忽略whitespace 组,如果遇到匹配的invalid 组,您应该提出错误。

string 组将匹配整个带引号的字符串,它可以处理字符串中的\" 等转义。

invalid 组应始终位于模式的最后。您可以为其他类型添加规则。

一些示例代码:

var regex = new Regex(@"
    \G(?:
      (?<string> ""(?>[^""\\]+|\\.)*"" )
    | (?<separator> ; )
    | (?<whitespace> \s+ )
    | (?<invalid> . )
    )
", RegexOptions.IgnorePatternWhitespace);

var input = "; \"social life\"; \"city life\"; \"real life\"";

var groupNames = regex.GetGroupNames().Skip(1).ToList();

foreach (Match match in regex.Matches(input))
{
    var groupName = groupNames.Single(name => match.Groups[name].Success);
    var group = match.Groups[groupName];

    Console.WriteLine("{0}: {1}", groupName, group.Value);
}

这会产生以下内容:

separator: ;
whitespace:
string: "social life"
separator: ;
whitespace:
string: "city life"
separator: ;
whitespace:
string: "real life"

看看处理这些结果比使用 split 容易多少?

【讨论】:

  • 感谢您的详细解答
猜你喜欢
  • 2012-03-02
  • 2018-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-17
  • 1970-01-01
  • 2013-07-13
相关资源
最近更新 更多