【问题标题】:Count regex replaces (C#)计数正则表达式替换 (C#)
【发布时间】:2023-04-07 06:35:01
【问题描述】:

有没有办法计算 Regex.Replace 调用的替换次数?

例如对于Regex.Replace("aaa", "a", "b");,我想取出数字3(结果是"bbb");对于Regex.Replace("aaa", "(?<test>aa?)", "${test}b");,我想取出数字2(结果是"aabab")。

我能想到的方法:

  1. 使用 MatchEvaluator 递增捕获的变量,手动进行替换
  2. 获取 MatchCollection 并对其进行迭代,手动进行替换并保持计数
  3. 首先搜索并获取 MatchCollection,从中获取计数,然后进行单独替换

方法 1 和 2 需要手动解析 $ 替换,方法 3 需要正则表达式匹配字符串两次。有没有更好的办法。

【问题讨论】:

  • 这是一个简单的命令行实用程序,可以通过任何正则表达式搜索和替换模式作为命令行参数调用。所以理想情况下会想要一个不预先假设模式知识的通用解决方案。真的,虽然这是出于兴趣 - 在.Net中这样做的最佳方式是什么?看起来像手动解析 $ 替换的 MatchEvaluator 方法是前进的方向,但它有点混乱:(

标签: c# .net regex replace


【解决方案1】:

感谢 Chevex 和 Guffa。我开始寻找一种更好的方法来获取结果,并发现 Match 类上有一个 Result 方法可以进行替换。那是拼图的缺失部分。示例代码如下:

using System.Text.RegularExpressions;

namespace regexrep
{
    class Program
    {
        static int Main(string[] args)
        {
            string fileText = System.IO.File.ReadAllText(args[0]);
            int matchCount = 0;
            string newText = Regex.Replace(fileText, args[1],
                (match) =>
                {
                    matchCount++;
                    return match.Result(args[2]);
                });
            System.IO.File.WriteAllText(args[0], newText);
            return matchCount;
        }
    }
}

使用包含 aaa 的文件 test.txt,命令行regexrep test.txt "(?<test>aa?)" ${test}b 将设置 %errorlevel% 为 2 并将文本更改为 aabab。

【讨论】:

  • (匹配匹配)可以简化为变量名“match”,因为类型是隐含的。
  • 我不知道匹配对象上的 Result() 方法。这是一种快速简便的方法,可以在每场比赛中执行一些功能,同时仍然让 Replace() 完成它的工作。干得好西蒙。
  • 其他答案有我的赞成票,但对我来说关键是 match.Result,所以接受我自己的答案。谢谢大家的帮助。
【解决方案2】:

您可以使用为每个替换运行的MatchEvaluator,这样您就可以计算它发生了多少次:

int cnt = 0;
string result = Regex.Replace("aaa", "a", m => {
  cnt++;
  return "b";
});

第二种情况比较棘手,因为您必须产生与替换模式相同的结果:

int cnt = 0;
string result = Regex.Replace("aaa", "(?<test>aa?)", m => {
  cnt++;
  return m.Groups["test"] + "b";
});

【讨论】:

  • 这是与我的答案等效的 lambda:3
  • @Chevex:我花了一点时间来回答,因为我先测试了代码。 ;)
  • 查看我对 Chevex 答案的评论——这只有在您事先知道模式的情况下才有效。否则你需要解析正则表达式替换字符串。
  • 我不需要测试代码,我直接从一个已经使用了几个月的项目中复制了它,并添加了一个计数变量。然后,我将您的语法与 lambda 一起使用,并调整了我的答案以使用 lambda 而不是声明的方法。 ;)
  • 另外,我在您发布后为您的答案 +1。所以你的尖刻讽刺是不必要的。
【解决方案3】:

应该这样做。

     int count = 0;
     string text = Regex.Replace(text,
          @"(((http|ftp|https):\/\/|www\.)[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?)", //Example expression. This one captures URLs.
          match =>
          {
               string replacementValue = String.Format("<a href='{0}'>{0}</a>", match.Value);
               count++;
               return replacementValue;
          });

我不在我的开发计算机上,所以我现在不能这样做,但我稍后会进行实验,看看是否有办法使用 lambda 表达式而不是声明方法 IncrementCount( ) 只是为了增加一个 int。

EDIT 修改为使用 lambda 表达式,而不是声明另一个方法。

EDIT2 如果您事先不知道该模式,您仍然可以在匹配对象中获取所有分组(您引用的 $ 组),因为它们作为 GroupCollection 包含在内。像这样:

     int count = 0;
     string text = Regex.Replace(text,
          @"(((http|ftp|https):\/\/|www\.)[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?)", //Example expression. This one captures URLs.
          match =>
          {
               string replacementValue = String.Format("<a href='{0}'>{0}</a>", match.Value);
               count++;
               foreach (Group g in match.Groups)
               {
                    g.Value; //Do stuff with g.Value
               }
               return replacementValue;
          });

【讨论】:

  • 这会起作用(谢谢!),但基本上是我的方法 1。要使其适用于通用输入和输出,您需要解析替换中的 ${test},所以我们需要更复杂的东西(我指的是“手动解析 $ 替换”)。
  • Match 对象包括 $ 组作为附加到它们的 GroupCollection。如果您事先不知道表达式并且不知道将包含多少组,请像我的 EDIT2 那样循环遍历组集合。我没有对答案中的 group 值做任何事情,但你应该很容易看出你是怎么做的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-21
  • 2022-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多