【问题标题】:Regex C# replace matched fields with multiple results正则表达式 C# 用多个结果替换匹配的字段
【发布时间】:2015-07-25 16:17:34
【问题描述】:

我有这段文字:

iif(instr(|Wellington, New Zealand|,|,|)>0,|Wellington, New Zealand|,|Wellington, New Zealand| & |, | & |New Zealand|) & | to | & iif(instr(|Jeddah, Saudi Arabia|,|,|)>0,|Jeddah, Saudi Arabia|,|Jeddah, Saudi Arabia| & |, | & |Saudi Arabia|) & iif(|Jeddah, Saudi Arabia|=||,||,| via | & |Jeddah, Saudi Arabia|)

我可以对文本(如下)进行正则表达式来获取| 字符之间的所有元素的集合。我得到了 18 场比赛,比赛 #1 是 |,|

MatchCollection fields = Regex.Matches(str, @"\|.*?\|");

然后我想用占位符替换每个匹配项,例如 ~0~~1~~2~ 等直到 ~17~,这样我就可以运行我的其余代码。我不在乎是否所有的通用文本都被相同的占位符替换,如果我使用所有 18 个占位符,这会在占位符中留下空隙。

我的问题是我不能直接替换,因为替换字符串 |Jeddah, Saudi Arabia|,|,| 的这部分中的元素 #1 (|,|) 将替换它找到的第一个实例,其中正则表达式正确识别 |Jeddah, Saudi Arabia|作为一场比赛,|,| 作为另一场比赛。

我寻求的结果是这样的:

iif(instr(~0~,~1~)>0,~0~,~0~ & ~2~ & ~3~) & ~4~ & iif(instr(~5~,~1~)>0,~5~,~5~ & ~2~ & ~6~) & iif(~5~=~7~,~7~,~8~ & ~5~)

一旦我知道我有多少匹配项,我就会构建一个数组中的数字越来越多。我保留原始值并稍后将它们交换回来,这是容易的部分。

【问题讨论】:

    标签: c# regex replace


    【解决方案1】:

    我会使用一些 lambda 函数:

    // This one gets the index from the list of matches
    private static string LookupReplace(string text, List<string> newList)
    {
        var result = "~" + newList.IndexOf(text).ToString() + "~";
        return result;
    }
    
    // This one just increments a global counter
    private static string NumberedReplace()
    {
        i++;
        return "~" + i.ToString() + "~";
    }
    
    public static int i = -1;
    
    public static void Main()
    {   
        string text = "iif(instr(|Wellington, New Zealand|,|,|)>0,|Wellington, New Zealand|,|Wellington, New Zealand| & |, | & |New Zealand|) & | to | & iif(instr(|Jeddah, Saudi Arabia|,|,|)>0,|Jeddah, Saudi Arabia|,|Jeddah, Saudi Arabia| & |, | & |Saudi Arabia|) & iif(|Jeddah, Saudi Arabia|=||,||,| via | & |Jeddah, Saudi Arabia|)";
        var re = new Regex(@"\|.*?\|");
        var newList = re.Matches(text)
                        .OfType<Match>()
                        .Select(m => m.Value)
                        .ToList();
        // First replace with index
        string result = re.Replace(text, x => LookupReplace(x.Value, newList));
        Console.WriteLine(result);
    
        // Second replace with counter
        result = re.Replace(text, x => NumberedReplace());
        Console.WriteLine(result);
    }
    

    ideone demo

    每次替换的输出:

    iif(instr(~0~,~1~)>0,~0~,~0~ & ~4~ & ~5~) & ~6~ & iif(instr(~7~,~1~)>0,~7~,~7~ & ~4~ & ~12~) & iif(~7~=~14~,~14~,~16~ & ~7~)
    iif(instr(~0~,~1~)>0,~2~,~3~ & ~4~ & ~5~) & ~6~ & iif(instr(~7~,~8~)>0,~9~,~10~ & ~11~ & ~12~) & iif(~13~=~14~,~15~,~16~ & ~17~)
    

    【讨论】:

    • 谢谢 Jerry - 你的第一种方式正是我所追求的。
    【解决方案2】:
    \|,\|(?=[^(|]*(\|[^(|]*\|)*[^(|]*\))
    

    您可以使用lookahead 来检查|,| 是否被捕获以进行替换,在) 之前不会留下杂散的|。参见演示。

    https://regex101.com/r/mT0iE7/14

    【讨论】:

      【解决方案3】:

      嗯……有点难以解释,但基本上是根据你得到的东西来构建的……

      给定匹配项,我将它们添加到列表中,并使用Distinct LINQ 函数获取唯一匹配项,并使用OrderBy LINQ 函数将它们从最长到最短排序。然后只需遍历生成的 RouteMap 并替换原始字符串。

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text.RegularExpressions;
      
      public class Program
      {
          public static void Main()
          {
              var input = "iif(instr(|Wellington, New Zealand|,|,|)>0,|Wellington, New Zealand|,|Wellington, New Zealand| & |, | & |New Zealand|) & | to | & iif(instr(|Jeddah, Saudi Arabia|,|,|)>0,|Jeddah, Saudi Arabia|,|Jeddah, Saudi Arabia| & |, | & |Saudi Arabia|) & iif(|Jeddah, Saudi Arabia|=||,||,| via | & |Jeddah, Saudi Arabia|)";
      
              Console.WriteLine(input);
      
              var re = new Regex(@"\|.*?\|");
      
              var matches = re.Matches(input);
      
              var mz = new List<string>();
      
              foreach(Match m in matches) 
              {
                  mz.Add(m.Groups[0].ToString());
              }
      
              var routeMap = mz.Distinct().OrderByDescending(n => n.Length).ToList(); //Get distinct, and sort it longest to shortest... need it this way or it won't do the replacement correctly.
      
              for (var i = 0; i < routeMap.Count; i++) 
              {
                  input = input.Replace(routeMap[i], string.Format("~{0}~", i));          
              }
      
              Console.WriteLine(input);
      
              Console.WriteLine();
              Console.WriteLine("The route map replacement key:");
              var idx = 0;
              routeMap.ForEach(m => Console.WriteLine("{0}: {1}", idx++, m));
      
          }
      }
      

      运行示例:https://dotnetfiddle.net/PIuuae

      【讨论】:

        【解决方案4】:

        我提出了一个获得您的第二个输出选项的建议。

        您可以使用MatchEvaluator 将匹配项传递给单独的方法,并在该方法内增加一个“全局”计数器:

            public string ReplaceMatch(Match m)
            {
                i++;
                return "~" + i.ToString() + "~";
        
            }
            public static int i = -1;
            // ... then, in your calling method
        
            var txt = "iif(instr(|Wellington, New Zealand|,|,|)>0,|Wellington, New Zealand|,|Wellington, New Zealand| & |, | & |New Zealand|) & | to | & iif(instr(|Jeddah, Saudi Arabia|,|,|)>0,|Jeddah, Saudi Arabia|,|Jeddah, Saudi Arabia| & |, | & |Saudi Arabia|) & iif(|Jeddah, Saudi Arabia|=||,||,| via | & |Jeddah, Saudi Arabia|)";
            var fields = Regex.Matches(txt, @"\|.*?\|");
            var txt2 = Regex.Replace(txt, @"\|.*?\|", new MatchEvaluator(ReplaceMatch));
        

        输出:

        iif(instr(~0~,~1~)>0,~2~,~3~ & ~4~ & ~5~) & ~6~ & iif(instr(~7~,~8~)>0,~9~,~10~ & ~11~ & ~12~) & iif(~13~=~14~,~15~,~16~ & ~17~)
        

        与这些占位符对应的匹配值保存在fields 变量中,因此稍后您将能够匹配它们。

        编辑:对于选项 1(这是您编辑问题后的唯一选项),答案是创建一个包含不同项目的字典,并在替换方法中使用它:

        var txt = "iif(instr(|Wellington, New Zealand|,|,|)>0,|Wellington, New Zealand|,|Wellington, New Zealand| & |, | & |New Zealand|) & | to | & iif(instr(|Jeddah, Saudi Arabia|,|,|)>0,|Jeddah, Saudi Arabia|,|Jeddah, Saudi Arabia| & |, | & |Saudi Arabia|) & iif(|Jeddah, Saudi Arabia|=||,||,| via | & |Jeddah, Saudi Arabia|)";
        var fields = Regex.Matches(txt, @"\|.*?\|").Cast<Match>().Select(p=> p.Value).Distinct().Select((s, i) => new { s, i }).ToDictionary(x => x.s, x => x.i);
        var txt3 = Regex.Replace(txt, @"\|.*?\|", m => string.Format("~{0}~", fields[m.Value]));
        

        输出:

        iif(instr(~0~,~1~)>0,~0~,~0~ & ~2~ & ~3~) & ~4~ & iif(instr(~5~,~1~)>0,~5~,~5~ & ~2~ & ~6~) & iif(~5~=~7~,~7~,~8~ & ~5~)
        

        【讨论】:

        • 谢谢,但我已经测试了更多,第二个选项稍后会使我的代码失败。我需要返回我现在编辑显示的第一个选项。
        • 我已经为当前输出添加了解决方案。看起来只有 2 行 :)
        • 感谢您的第二个建议 - 我已经尝试了 Jerry 的建议,并在您发布消息时上床睡觉。你的看起来也很优雅。干杯
        • 我明白了。我也在使用 lambdas BTW。 Jerry 只是将代码拆分为一个单独的方法,而我将所有 LINQ 表达式放在 1 行,并在 MatchEvaluator 中使用 lambda。
        猜你喜欢
        • 1970-01-01
        • 2012-09-28
        • 2015-11-30
        • 1970-01-01
        • 1970-01-01
        • 2012-08-11
        • 2021-09-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多