【问题标题】:"Tight" repeated key/value match“紧密”重复键/值匹配
【发布时间】:2018-04-12 14:59:26
【问题描述】:

具有“紧密”重复键/值对模式的字符串(对于本示例,键为“名称”,值应为单个小写单词)

string text = "name: abc name: def name: ghi name: jkl";

应该转化为输出

abc、def、ghi、jkl、

而模式中的任何干扰(可以说是“不紧密”),如

string text = "name: abc x name: def name: ghi name: jkl";

应该导致匹配失败,类似于

abc, ## 发生异常:x 无法与模式匹配 ##

我试过了

string text = "name: abc name: def name: ghi name: jkl";
string pattern = @"name:\s*([a-z])*\s*";

MatchCollection ms = Regex.Matches(text, pattern);

foreach (Match m in ms)
{
    Console.Write(m.Groups[1].Value+", ");
}

但它会返回

c, f, i, l,

是什么导致了这种奇怪的行为,我该如何解决?

【问题讨论】:

    标签: c# regex


    【解决方案1】:

    您只需将* 移动到括号内即可捕获完整的字符串。如果你想防止无效输入,那么正则表达式并不是绝对必要的。这假设您的值不能有空格,因为这将是一个更难解决的问题。

    void Main()
    {
        string validText = "name: abc name: def name: ghi name: jkl";
        string invalidText = "name: abc x name: def name: ghi name: jkl";
        string validPattern = @"name:\s*([a-z]*)\s*";
    
        if (!Validate(invalidText))
        {
            try
            {
                throw new Exception("invalid input");
            }
            catch (Exception exception)
            {
                Console.WriteLine($"Input '{invalidText}' produces: {exception.Message}");
            }
        }
    
        MatchCollection ms = Regex.Matches(validText, validPattern);
    
        Console.Write($"Input '{validText}' produces: ");
        foreach (Match m in ms)
        {
            Console.Write(m.Groups[1].Value + ", ");
        }
    }
    
    public static bool Validate(string input)
    {
        var pairs = input.Split(' ');
        return !pairs.Where((item, index) => index % 2 != 0).Any(item => item.EndsWith(":"));
    }
    
    // Input 'name: abc x name: def name: ghi name: jkl' produces: invalid input
    // Input 'name: abc name: def name: ghi name: jkl' produces: abc, def, ghi, jkl, 
    

    https://regex101.com/r/qsQNr1/1

    【讨论】:

    • 这只能回答一半问题。
    • 天哪,我太笨了。本来我是对的,但后来我玩了括号......那么检测令人不安的“x”呢?
    • 已更新,试图回答另一半问题。
    • @DanWilson:嗯,这只是涵盖了与模式的一个非常具体的偏差。但我想从模式序列中找出任何偏差。
    • 根据之前的评论更新。
    【解决方案2】:

    与大多数其他正则表达式风格不同,C# (.Net) 的引擎实际上通过 Group 类的 Captures 属性跟踪重复捕获。

    Group.Captures 属性

    获取与捕获组匹配的所有捕获的集合,按最内左最先的顺序(或如果使用RegexOptions.RightToLeft 选项修改正则表达式,则按最内最右最先的顺序)。

    这意味着通过访问Groups[1](如下面的代码所示)然后访问Captures 属性,我们实际上是在获取字符串上每个重复捕获的值。


    代码

    See code in use here

    using System;
    using System.Linq;
    using System.Text.RegularExpressions;
    
    class Example {
    
        static void Main() {
            string[] strings = new string[]{
                "name: abc name: def name: ghi name: jkl",
                "name: abc x name: def name: ghi name: jkl"
            };
            Regex regex = new Regex(@"^(?:name: *([a-z]+) *)+$");
            foreach(string s in strings) {
                if(regex.IsMatch(s)) {
                    Match match = regex.Match(s);
                    Console.WriteLine(string.Join(", ", from Capture c in match.Groups[1].Captures select c.Value));
                } else {
                    Console.WriteLine("Invalid input");
                }
            }
        }
    }
    

    结果

    name: abc name: def name: ghi name: jkl     # abc, def, ghi, jkl
    name: abc x name: def name: ghi name: jkl   # Invalid input
    

    【讨论】:

    • 就是这样!我已经怀疑它与捕获有关,但我不知道如何使用它。您是否知道我如何检测匹配首先失败的字符串中的哪个索引(即“x”)?
    • @oliver 不幸的是,Regex 类不包含这样的属性。使用Regex 类完成此操作的唯一方法是在其上运行第二个正则表达式(例如^(?:name: *[a-z]+ *)+)以获取失败的位置。否则,您实际上可以编辑源代码以执行您想要的操作,如Regex index in matching string where the match failed 的此答案中所述
    【解决方案3】:

    你就不能用吗

    var result = "name: abc name: def name: ghi name: jkl".Split(new [] { "name: " }, StringSplitOptions.None).Where(a=>!String.IsNullOrEmpty(a)).ToArray();
    

    【讨论】:

    • 当然可以,但我不想这样做,因为代码往往会变得丑陋且与 Split 混淆。使用 RegEx 匹配模式要干净得多,毕竟这就是它的用途。
    • @oliver 不建议过度使用正则表达式,只需保持代码简单即可。 en.wikipedia.org/wiki/KISS_principle
    • 好吧,推荐这个的人没有考虑到我喜欢正则表达式 ;-)
    • 我也这样做,只是考虑其他不喜欢 Regex 的开发人员需要阅读您的代码:P
    • "...不喜欢...":是的,没错,但在这种情况下,我可以完全控制。这是一个私人项目。
    【解决方案4】:

    我终于也找到了获取“错误位置”的解决方案,即。重复匹配首先失败的位置。我希望任何有同样问题的人都能偶然发现这个错误的答案:

    string s = "name: abc x name: def name: ghi name: jkl";
    string pattern = @"\Gname:\s*([a-z]+)\s*";
    
    int endOfLastMatch = 0;
    
    // by using the \G anchor we can manage pattern repetition in a for-loop
    for (Match m = Regex.Match(s, pattern); m.Success; m = m.NextMatch())
    {
        Console.WriteLine(m.Groups[1].Value+ ", ");
    
        // keep track of until where matches were successful
        endOfLastMatch = m.Index + m.Length;
    }
    
    // in case that the match has failed, report where it has happened
    if (endOfLastMatch != s.Length) 
    {
        int reportSize = Math.Min(s.Length-endOfLastMatch, 10);
        string remainder = s.Substring(endOfLastMatch, reportSize);
        Console.WriteLine("Error: RegEx match failed at index "
            +endOfLastMatch+" (\""+remainder+"...\")");
    }
    

    输出:

    abc,

    错误:正则表达式匹配在索引 10 处失败(“x 名称:de...”)

    魔术发生在\G 锚点中,它允许我们使用NextMatch 进行模式重复,同时强制下一个匹配紧跟上一个匹配。可以说,它是等同于^(行或字符串开始)锚点的匹配边界。

    代价是我们不是在一个块中解析文本(就像在接受的答案中一样),而是在一个 for 循环中。不过因为比较小,我觉得还是可以接受的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-23
      • 1970-01-01
      • 1970-01-01
      • 2017-11-20
      • 1970-01-01
      • 2016-08-08
      • 2020-10-15
      相关资源
      最近更新 更多