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

时间:2009-10-14 17:35:34

标签: c# regex

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

  

(小于?1>)(?&LT 2 - ;)(?&LT 3的密度;)(\ K&3; GT)(\ K&LT 2 - )(?&4; GT;)( \ K&LT 2 - )(\ K&LT 1为卤素;)

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

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

  

(小于?1>)(?&LT 2 - ;)(?&LT 3的密度;)(?&4; GT;)(?< 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));
    }

再次感谢您的回应。

2 个答案:

答案 0 :(得分:4)

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

  

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

是的,确实如此。但.NET文档并未说明将从反向引用解释名称。它必须是unicode文字类字符串。

  

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

我知道这意味着要匹配e f a x中的所有fx以及e f e x。换句话说:匹配唯一字符,不匹配重复的字符。

解决方案

我理解你的问题如下:匹配字符串中所有独特的单词(子表达式,字符),这些单词在自身之前或之后没有重复。您应该使用的基本正则表达式是:

(subexpr)(?!.*\1)(?<!\1.+)

只有在匹配字符串中出现一次时才会找到单词subexpr。例如,如果我们更改它以匹配e中的e f a x而不是e f e x中的(e)(?!.*\1)(?<!\1.+) ,则它将如下所示:

(.)(?!.*\1)(?<!\1.+)

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

e

如果匹配f中的axe f a xf以及xe f e x中的(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 {1}}。这可能是上面表达式的通用替换,您不再需要重复1,2,3等捕获。

如何运作

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

(.)(?!.*\X)(?<!\X.+)

应用解决方案

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

采用以前的解决方案,我们通过坚持以下规则将其调整到您的问题域:

  1. 一个唯一的字母由(.)
  2. 表示
  3. 重复的字母由\X
  4. 表示
  5. 重复发生的位置由\X(没有括号!)
  6. 表示
  7. # 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 表示对您的模式编号进行反向引用
  8. 示例:

    .*

    此模式应该可以轻松自动进入当前系统。

    测试这个和其他正则表达式(只是复制和粘贴我的)的一个很好的方法是Regex Hero, free online SilverLight .NET regex tester。对于其他在线测试人员,see my overview chart of them

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

    更新1:在其他解决方案的评论中,您说您希望能够匹配适合该模式的子字符串。当然,这对负面前瞻/后退提出了挑战:就像现在一样,他们会看整个字符串。将.+(.)(?!.{1}\3)(?<!\3.{2})替换为表达式在该位置的相对长度,将PAST的pos 3变为(.)(?!.{2}\3)(?<!\3.{3}),将pos 4变为(.)(?!.{3}\3)

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

答案 1 :(得分:2)

为了做这样的事情,你可以在匹配新组之前使用负向前看。

我将使用更通用的PCRE表示法:

(.)((?!\1).)((?!\1|\2).)\3\2((?!\1\2\3).)\2\1

上面的正则表达式将与字符串12332421匹配,但不会与1211242111111111匹配。

一个简短的解释:

(.)           // match any character (except line breaks) and store it in group 1
(             // open group 2
  (?!\1)      //   if looking ahead group 1 cannot be seen,
  .           //   match any character (except line breaks)
)             // close group 2
(             // open group 3
  (?!\1|\2)   //   if looking ahead group 1 or 2 cannot be seen,
  .           //   match any character (except line breaks)
)             // close group 3
\3            // back referencing group 3
\2            // back referencing group 2
(             // open group 4
  (?!\1\2\3)  //   if looking ahead group 1, 2 or 3 cannot be seen,
  .           //   match any character (except line breaks)
)             // close group 4
\2            // back referencing group 2
\1            // back referencing group 1

当然,你并不需要将#4分组,因为你没有回来引用它。

您可能同意我的观点,即正则表达式不是进行此类匹配的最佳工具......

修改

好吧,我不知道你将如何构建这些正则表达式,但无法想象它比这个简单接受模式和目标字符串的小方法更容易,并测试它们是否匹配:

public class Test {

    public static boolean matchesPattern(String text, String pattern) {
        if(text.length() != pattern.length()) return false;
        Map<Character, Character> mappings = new HashMap<Character, Character>();
        for(int index = 0; index < pattern.length(); index++) {
            Character patternChar = pattern.charAt(index);
            Character textChar = text.charAt(index);
            if(mappings.containsKey(patternChar)) {
                if(mappings.get(patternChar) != textChar) return false;
            } 
            else {
                if(mappings.values().contains(textChar)) return false;
                mappings.put(patternChar, textChar);
            }
        }
        return true;
    }

    public static void main(String[] args) {
        String pattern = "abccbdba";
        String[] tests = {"12332421", "12112421", "11111111"};
        for(String t : tests) {
            System.out.println(t+" -> "+matchesPattern(t, pattern));
        }
    }
}

产生以下输出:

12332421 -> true
12112421 -> false
11111111 -> false