可以在字符类中使用 .NET RegEx 反向引用来排除以前匹配的字符吗?

Rol*_*oly 2 c# regex

我试图完成的任务是给定一个输入模式,例如 1 2 3 3 2 4 2 1,通过字典查找符合给定模式的单词。在我的代码中,我尝试将给定的字符串转换为正则表达式,如下所示:

(?<1>.)(?<2>.)(?<3>.)(\k<3>)(\k<2>)(?<4>.)(\k<2>)( \k<1>)

(在任何人开始抨击这里使用点之前,因为我的输入是一个只有真实单词的字典文件,我留下了点以获得更清晰的表达,而不是指定字符范围。)

这个表达式设法正确地找到了这个词,但它有一个缺陷。使用 1 2 3 4 5 6 等模式时,问题变得非常明显。我的算法生成以下正则表达式:

(?<1>.)(?<2>.)(?<3>.)(?<4>.)(?<5>.)(?<6>.)

这是错误的,因为它将匹配任何 6 个字符的字符串,而没有考虑到每个组不应该匹配任何已被先前组匹配的字符。换句话说,它没有考虑到每个字母都是不同的;没有重复。

所以我尝试在互联网上寻找语法以排除字符类中的命名组,即

[^\1](无效)、[^(\k<1>)](无效)、[^${1}](无效)...等。

在 .NET 文档中,它表明 \p{name} 是字符类中的有效语法,但我尝试了 [^\p{1}] 并且也不起作用。

所以,问题仍然存在......是否可以从进一步匹配中排除命名组?或者,我还能如何解决这个问题?

更新

根据我在这里得到的回复发布我的最终解决方案。此方法采用一个字符串,指定一个人正在寻找的模式,并将其转换为一个正则表达式,然后我将其应用到字典中并找到符合该模式的所有单词。

    string pattern = "12332421";

    private void CreateRegEx()
    {
        string regex = "^";

        for( int i = 0; i < pattern.Length; i++ )
        {
            char c = pattern[i];
            if (char.IsDigit(c))
            {
                if (isUnique(c))
                {
                    regex += "(.)(?!.*\\" + c + ")(?<!\\" + c + ".+)";
                }
                else
                {
                    if (isFirstOccurrence(c, i))
                        regex += "(.)";                        
                    else
                        regex += "\\" + c;
                }
            }
            else if (char.IsLetter(c))
                regex += c + "";
            else if (c == '?')
                regex += ".";
        }

        regex += "$";

        reg = new Regex(regex, RegexOptions.IgnoreCase);
    }

    private bool isUnique(char c)
    {
        return pattern.IndexOf(c) == pattern.LastIndexOf(c);
    }

    private bool isFirstOccurrence(char c, int i)
    {
        return pattern.IndexOf(c) == i;
    }

    public List<string> GetMatches()
    {
        return dictionary.FindAll(x => reg.IsMatch(x));
    }
Run Code Online (Sandbox Code Playgroud)

再次感谢您的精彩回复。

Abe*_*bel 5

答案是不。不能在 .NET 正则表达式的字符类中使用反向引用。对不起。请参阅下文,了解针对您的情况的解决方法。

“它表明 \p{name} 在字符类中是有效的语法”

是的。但是 .NET 文档并没有说名称将从反向引用中解释。它必须是一个 unicode 文字类字符串。

“换句话说,它没有考虑到每个字母都是不同的;没有重复。”

我明白,这意味着匹配所有的e f a x,只有fxe f e x。换句话说:匹配唯一的字符,不匹配重复的字符。

解决方案

我对您的问题的理解如下:匹配字符串中所有在其前后没有重复的唯一单词(子表达式、字符)。您应该使用的基本正则表达式是这样的:

(subexpr)(?!.*\1)(?<!\1.+)
Run Code Online (Sandbox Code Playgroud)

subexpr只有在匹配字符串中出现一次时才会找到该单词。例如,如果我们将其更改为匹配eine f a x而不是 in e f e x,它将如下所示:

(e)(?!.*\1)(?<!\1.+)
Run Code Online (Sandbox Code Playgroud)

您可以将其概括为匹配字符串中的每个唯一字母:

(.)(?!.*\1)(?<!\1.+)
Run Code Online (Sandbox Code Playgroud)

if 将匹配e, f, aand xine f a x并且只有fand xin e f e x。这可能是上面表达式的通用替换,您不再需要重复 1,2,3 等捕获。

这个怎么运作

(更新)也许很高兴知道上面的正则表达式是如何工作的:

(subexpr)   # grab subexpression (can be any valid grouped regex)
(?!.*\1)    # negative look forward with a backrefence: if followed somewhere by itself, fail
(?<!\1.+)   # negative look backward with backref: if preceded somewhere by itself, fail
Run Code Online (Sandbox Code Playgroud)

应用解决方案

一个词有一个模式。SUCCUBUS 是 1 2 3 3 2 4 2 1. PAST 是 1 2 3 4. 基于该模式,正则表达式应该匹配具有相同模式的单词:相同长度的单词,在同一位置重复相同的字母:PAST和 RANT 具有相同的模式。LOOK 和 HEEL 有相同的图案,但不是 HERE。

采用之前的解决方案,我们通过遵循以下规则,根据您的问题域进行调整:

  1. 一个唯一的字母表示为 (.)(?!.*\X)(?<!\X.+)
  2. 重复的字母表示为 (.)
  3. 重复发生的位置由\X(无括号!)表示
  4. \X 表示与您的模式编号的反向引用

例子:

# SUCCUBUS is 1 2 3 3 2 4 2 1 (only 4 is unique)
(.)                      # nr 1 in pattern
(.)                      # nr 2 in pattern
(.)                      # nr 3 in pattern
\3                       # repeat 3
\2                       # repeat 2
(.)(?!.*\4)(?<!\4.+)     # nr 4 UNIQUE!
\2                       # repeat 2
\1                       # repeat 1

# PAST (all unique: 1 2 3 4)
(.)(?!.*\1)(?<!\1.+)    # nr 1 in pattern
(.)(?!.*\2)(?<!\2.+)    # nr 2 in pattern
(.)(?!.*\3)(?<!\3.+)    # nr 3 in pattern
(.)(?!.*\4)(?<!\4.+)    # nr 4 in pattern
Run Code Online (Sandbox Code Playgroud)

这种模式应该很容易自动化到您当前的系统中。

测试此正则表达式和其他正则表达式(只需复制并粘贴我的)的绝佳方法是Regex Hero,免费在线 SilverLight .NET 正则表达式测试器。对于其他在线测试人员,请参阅我对他们的概览图表

更新:删除了之前不相关的更新说明

更新 1:在另一个解决方案的评论中,您说您希望能够匹配符合模式的子字符串。自然地,这对前瞻/后视带来了挑战:就像现在一样,他们会查看整个字符串。将.*and替换为.+表达式所在位置的相对长度,PAST 的 pos 3 变为 then(.)(?!.{1}\3)(?<!\3.{2})并且 pos 4 将变为 (.)(?!.{2}\3)(?<!\3.{3})

更新 2:以同样的方式,可以通过删除第一个表达式中的回溯并删除最后一个表达式中的前瞻来稍微优化,如果它们需要是唯一的: pos 1 变为 (.)(?!.{3}\3)pos 4 变为(.)(?<!\3.{3})