如何使用带有线锚的C#正则表达式Lookbehind

时间:2018-03-12 22:55:34

标签: c# regex c#-4.0 regex-negation regex-lookarounds

当使用line-begin& amp;时,我在C#的正则表达式匹配中遇到后瞻断言的问题。线端锚。 在下面的示例中,正则表达式B的行为完全符合我的预期(并在此处记录:https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference

我最初对RegEx A与第1行不匹配感到惊讶。现在我想我理解为什么RegEx A与第1行不匹配。[因为断言是零宽度 - 表达式基本上是^ \ d {2} $,这显然与4位数年份不匹配 - 这就是为什么它与第6和第6行相匹配的原因。 7]。

我知道我可以像这样重写正面断言(RegEx A):^ 19 \ d {2} $。

但我的最终目标是像RegEx C这样的正则表达式 - 使用否定断言来查找不以给定前缀开头的所有字符串。也就是说,我正在尝试使用负断言创建一个表达式,该表达式对第3行和第4行返回true而不是5-7。

RegEx D是来自C#文档的类似负面断言样本,但不使用开始/结束锚点,对于第3行和第4行也是如此,但也是5-7。

考虑到这一点,我怎样才能使负判断(如RegEx C)与line-begin / -end锚点一起使用,以便它在验证输入是单行的情况下像RegEx D中的示例那样起作用?

我想知道使用断言是否根本不可能。这意味着另一种选择是表达评估异常否定的所有正面案例(类似于在正则表达式E中使用19),但当目标是排除一个时,表达一大堆积极因素是不可能或不切实际的。特别单一(也许是复杂的)案例。

谢谢!

示例程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

namespace RegExTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] reList = new string[]
            {
                @"^(?<=19)\d{2}$",   // RegEx A
                @"(?<=19)\d{2}",     // RegEx B
                @"^(?<!19)\d{2}$",   // RegEx C
                @"(?<!19)\d{2}\b",   // RegEx D
                @"^19\d{2}$",        // RegEx E
            };

            string[] tests = new string[]
            {
                "1999",                     // Line 1
                "1851 1999 1950 1905 2003", // Line 2
                "1895",                     // Line 3
                "2095",                     // Line 4
                "195",                      // Line 5
                "18",                       // Line 6
                "19",                       // Line 7
            };
            foreach (var r in reList)
            {
                var re = new Regex(r);
                Console.WriteLine("");
                Console.WriteLine($"{r}");
                Console.WriteLine("==========================");
                foreach (var s in tests)
                {
                    Console.WriteLine($"{s}={re.IsMatch(s)}");
                    if (re.IsMatch(s))
                    {
                        foreach (Match m in re.Matches(s))
                        {
                            Console.WriteLine($"Match @ ({m.Index}, {m.Length})");
                        }
                    }
                }
            }
        }
    }
}

输出:

^(?<=19)\d{2}$
==========================
1999=False
1851 1999 1950 1905 2003=False
1895=False
2095=False
195=False
18=False
19=False

(?<=19)\d{2}
==========================
1999=True
Match @ (2, 2)
1851 1999 1950 1905 2003=True
Match @ (7, 2)
Match @ (12, 2)
Match @ (17, 2)
1895=False
2095=False
195=False
18=False
19=False

^(?<!19)\d{2}$
==========================
1999=False
1851 1999 1950 1905 2003=False
1895=False
2095=False
195=False
18=True
Match @ (0, 2)
19=True
Match @ (0, 2)

(?<!19)\d{2}\b
==========================
1999=False
1851 1999 1950 1905 2003=True
Match @ (2, 2)
Match @ (22, 2)
1895=True
Match @ (2, 2)
2095=True
Match @ (2, 2)
195=True
Match @ (1, 2)
18=True
Match @ (0, 2)
19=True
Match @ (0, 2)

^19\d{2}$
==========================
1999=True
Match @ (0, 4)
1851 1999 1950 1905 2003=False
1895=False
2095=False
195=False
18=False
19=False

1 个答案:

答案 0 :(得分:3)

您将使用普通模式的默认行为混淆外观断言。外观断言这意味着它不消耗字符。

它查找一个条件,如果它满足则将光标带回到它开始的位置,否则它会使引擎回溯或立即失败。

正则表达式A ^(?<!19)\d{2}$不应与字符串1 1999匹配,因为引擎的工作方式如下:

  1. ^断言字符串的开头(我们在位置0)
  2. (?<!19)检查前面的字符是否不是19(肯定是在 位置0我们没有前面的字符所以这满足)
  3. \d{2}消耗两位数字(我们位于第2位)
  4. $断言字符串的结尾(实际上我们还有2个字符可以到达 字符串结束,因此引擎立即失败)
  5. 所以你必须这样做^\d{2}(?<!19)\d{2}$^(?!19)\d{4}$第二个更合适。