【问题标题】:Regex to repeat a capture across a CDL?正则表达式在 CDL 中重复捕获?
【发布时间】:2010-05-26 15:52:12
【问题描述】:

我有一些这种形式的数据:

@"Managers Alice, Bob, Charlie
Supervisors Don, Edward, Francis"

我需要这样的平面输出:

@"Managers Alice
Managers Bob
Managers Charlie
Supervisors Don
Supervisors Edward
Supervisors Francis"

上面的实际“职位”可以是任何一个单词,没有离散的列表可供使用。

用\r\n 替换,  很简单,第一个替换也是如此:

Replace (^|\r\n)(\S+\s)([^,\r\n]*),\s
With $1$2$3\r\n$2

但是今天我一直在逃避如何捕获其他名称并应用相同的前缀。有什么建议吗?

我正在寻找一系列一个或多个 RegEx.Replace() 调用,没有任何 LINQ 或 C# 中的程序代码,这当然是微不足道的。 实现不是直接在 C# 中代码,我正在配置一个通用解析工具,它使用一系列 .NET 正则表达式来转换来自各种来源的传入数据以用于多种用途。

【问题讨论】:

    标签: c# .net regex


    【解决方案1】:

    这是一个纯替换解决方案:

    string s = @"Managers Alice, Bob, Charlie
    Supervisors Don, Edward, Francis";
    Regex r = new Regex(@"(?:^\w+)?( \w+)(?<=^(\w+)\b.*)[,\r\n]*",
        RegexOptions.Multiline);
    string s1 = r.Replace(s0, "$2$1\r\n");
    

    匹配每个名称后,后向查找返回到当前行的开头以捕获标题。 (?:^\w+)? 和 [,\r\n]* 仅用于消耗您不想保留的字符串部分。

    【讨论】:

    • 效果很好,谢谢!我做了一个小改动:添加了杂项。在开头构造 (?m) 以避免必须在 RegEx 构造函数中设置 Multiline 标志(在我的情况下,正则表达式是从表中加载的,而不是直接调用,因此必须内联设置标志)。
    【解决方案2】:

    如果可以使用 LINQ,为什么还要使用正则表达式?

    string s = "Managers Alice, Bob, Charlie\r\nSupervisors Don, Edward, Francis";
    
    var result =
        from line in s.Split(new string[] { "\r\n" }, StringSplitOptions.None)
        let parts = line.Split(new char[] { ' ' }, 2)
        let title = parts[0]
        let names = parts[1]
        from name in names.Split(new char[] { ',' })
        select title.Trim() + " " + name.Trim();
    

    string.Join("\r\n", result)是

    经理爱丽丝 经理鲍勃 经理查理 监事唐 监事爱德华 监事弗朗西斯

    【讨论】:

    • 谢谢,但我正在寻找 RegEx 解决方案,而不是 LINQ。
    【解决方案3】:

    既然您强调了正则表达式的必要性,这里的解决方案应该适合您。

    string input = @"Managers Alice, Bob, Charlie
    Supervisors Don, Edward, Francis";
    string pattern = @"(?<Title>\w+)\s+(?:(?<Names>\w+)(?:,\s+)?)+";
    
    foreach (Match m in Regex.Matches(input, pattern))
    {
        Console.WriteLine("Title: {0}", m.Groups["Title"].Value);
        foreach (Capture c in m.Groups["Names"].Captures)
        {
            Console.WriteLine(c.Value);
        }
    
        Console.WriteLine();
    }
    

    主要概念是使用命名的“Title”组来存储职位名称并在以后引用它们。名称存储在捕获集合中。当然,该模式只有在数据格式正确时才有效,如您的示例数据中给出的那样。

    模式分解如下:(?&lt;Title&gt;\w+)\s+(?:(?&lt;Names&gt;\w+)(?:,\s+)?)+

    • (?&lt;Title&gt;\w+)\s+ - 匹配第一个空格之前的标题,并将其放置在名为 Title 的组中。后面必须至少有一个空格。
    • (?:(?\w+)(?:,\s+)?)+ - 名称通过(?&lt;Names&gt;\w+) 部分存储在Names 组中,并且匹配逗号和至少一个空格 (但由于使用了(?:...),因此未捕获)通过(?:,\s+)? 部分,它是可选的,因为? 放置在它之后。最后,模式的整个部分都包含在一个组中,该组必须至少匹配一次(?:...)+,但由于我们只捕获我们感兴趣的部分,因此没有被捕获。

    【讨论】:

    • 我可以根据需要执行尽可能多的 RegEx.Replace() 调用,但我无法在此工具中编写任何 C# 代码。我认为这在 RegEx 中是可行的,它可能需要平衡组。
    • @richard 当然改变了事情。我只使用Regex.Replace 看到的问题是如何拆分名称。 Regex.Replace 本身本质上是递归的,但是使用我上面的模式和替换模式只会给你职位和最终名称(即Names 组的匹配值)。如果可能的话,拆分每个名称并将结果替换为每个名称之前的标题前缀 Regex.Replace 将是一个挑战。到目前为止,我想不出办法来解决这个问题。
    【解决方案4】:

    你可以搜索

    ^(\w+)[ \t]+(\w+),[ \t]+(.+)$
    

    全部替换为

    \1 \2\r\n\1 \3
    

    您需要将它应用到您的示例中两次,如果经理列表增加到四个,则需要三次,等等。

    所以,在 C# 中:

    resultString = Regex.Replace(subjectString, @"^(\w+)[ \t]+(\w+),[ \t]+(.+)$", @"$1 $2\r\n$1 $3", RegexOptions.Multiline);
    

    解释:

    ^: 匹配行首

    (\w+)[ \t]+:匹配任意数量的alnum字符,捕获匹配;匹配后面的空格

    (\w+):匹配下一个“单词”,然后

    ,[ \t]+(.+)$ 匹配逗号、空格和后面的任何内容,直到行尾。仅当该行仍包含需要拆分的内容时才会匹配。

    【讨论】:

    • “名称”的实际数量不是离散的......可能是 1 或 100。大多数行少于 10,所以虽然我不想留下边缘情况,但我可能有像这样暴力破解它。
    • 好吧,如果你不能在你的应用程序中编写循环,你就别无选择......
    猜你喜欢
    • 1970-01-01
    • 2011-09-19
    • 1970-01-01
    • 2017-09-13
    • 2011-03-11
    • 2017-01-07
    • 2019-12-19
    • 1970-01-01
    相关资源
    最近更新 更多