如何让正则表达式匹配只添加一次到匹配集合中?

Posted

技术标签:

【中文标题】如何让正则表达式匹配只添加一次到匹配集合中?【英文标题】:How can I get a regex match to only be added once to the matches collection? 【发布时间】:2010-10-14 13:17:17 【问题描述】:

我有一个字符串,其中有几个 html cmets。我需要计算一个表达式的唯一匹配项。

例如,字符串可能是:

var teststring = "<!--X1-->Hi<!--X1-->there<!--X2-->";

我目前使用它来获取匹配项:

var regex = new Regex("<!--X.-->");
var matches = regex.Matches(teststring);

这个结果是 3 场比赛。但是,我希望只有 2 个匹配项,因为只有两个唯一匹配项。

我知道我可能可以遍历生成的 MatchCollection 并删除额外的匹配,但我希望有一个更优雅的解决方案。

澄清:示例字符串与实际使用的字符串相比已大大简化。很容易有一个 X8 或 X9,并且每个字符串中可能有几十个。

【问题讨论】:

【参考方案1】:

我会像这样使用Enumerable.Distinct Method:

string subjectString = "<!--X1-->Hi<!--X1-->there<!--X2--><!--X1-->Hi<!--X1-->there<!--X2-->";
var regex = new Regex(@"<!--X\d-->");
var matches = regex.Matches(subjectString);
var uniqueMatches = matches
    .OfType<Match>()
    .Select(m => m.Value)
    .Distinct();

uniqueMatches.ToList().ForEach(Console.WriteLine);

输出这个:

<!--X1-->  
<!--X2-->

对于正则表达式,你可以使用这个吗?

(<!--X\d-->)(?!.*\1.*)

似乎至少可以在 RegexBuddy 中处理您的测试字符串 =)

// (<!--X\d-->)(?!.*\1.*)
// 
// Options: dot matches newline
// 
// Match the regular expression below and capture its match into backreference number 1 «(<!--X\d-->)»
//    Match the characters “<!--X” literally «<!--X»
//    Match a single digit 0..9 «\d»
//    Match the characters “-->” literally «-->»
// Assert that it is impossible to match the regex below starting at this position (negative lookahead) «(?!.*\1.*)»
//    Match any single character «.*»
//       Between zero and unlimited times, as many times as possible, giving back as needed (greedy) «*»
//    Match the same text as most recently matched by capturing group number 1 «\1»
//    Match any single character «.*»
//       Between zero and unlimited times, as many times as possible, giving back as needed (greedy) «*»

【讨论】:

我喜欢这个想法,但不幸的是结果不是预期的。在我的单元测试(它有更大的字符串)中,当我应该收到 4 个结果时,我得到了 8 个结果。不确定 RegexBuddy 和我正在使用的有什么区别。 :( 另外,我尝试使用 Distinct() 但 MatchCollection,即使它源自 IEnumerable,似乎也无法识别这一点。 你更大的字符串是多少?在 MatchCollection 上,您很可能必须使用 var stuff = theMatchCollection.OfType().Select(m => m.Value).Distinct() 或其他东西。 可能无法将其粘贴到此处...通常会制作一个 4k html 文件。我正在寻找更多不同的东西。越来越近了......当前版本看起来类似于上面的类型。 :) Linq 和 Lambdas 对我来说还是有点新。 太棒了!很好的答案……我自己会花 30 分钟来弄清楚修改后的例子。【参考方案2】:

看来你在做两件不同的事情:

    匹配 cmets,如 // 寻找唯一的 cmets 集

因此将这些作为两个不同的步骤处理是相当合乎逻辑的:

var regex = new Regex("<!--X.-->");
var matches = regex.Matches(teststring);

var uniqueMatches = matches.Cast<Match>().Distinct(new MatchComparer());

class MatchComparer : IEqualityComparer<Match>

    public bool Equals(Match a, Match b)
    
        return a.Value == b.Value;
    

    public int GetHashCode(Match match)
    
        return match.Value.GetHashCode();
    

【讨论】:

你测试过这个吗?出于某种原因,即使这是包含它的第二个答案,我也无法让 Distinct() 与 MatchCollection 一起使用。我正在使用 .NET3.5 并且在我的 using 语句中有 System.Linq。 你应该使用 OfType 而不是 Cast 我想确保它抛出异常,如果除了匹配之外的任何东西出现。 OfType 将继续并忽略可能不是 Match 的内容,这可能会隐藏潜在的问题。【参考方案3】:

提取 cmets 并将它们存储在一个数组中。然后您可以过滤掉唯一值。

但我不知道如何在 C# 中实现这一点。

【讨论】:

【参考方案4】:

取决于你有多少个 Xn 你可以使用:

(\<!--X1--\>)1.*(\<!--X2--\>)1

这只会匹配每次出现的 X1、X2 等,前提是它们是按顺序排列的。

【讨论】:

【参考方案5】:

将评论的内部部分捕获为一个组。然后将这些字符串放入哈希表(字典)中。然后向字典询问它的计数,因为它会自我清除重复。

var teststring = "<!--X1-->Hi<!--X1-->there<!--X2-->";
var tokens = new Dicationary<string, string>();
Regex.Replace(teststring, @"<!--(.*)-->",   
     match =>   
     tokens[match.Groups[1].Value] = match.Groups[1].Valuel;  
     return ""; 
     );
var uniques = tokens.Keys.Count;

通过使用 Regex.Replace 构造,您可以在每次匹配时调用一个 lambda。由于您对替换不感兴趣,因此不要将其设置为等于任何内容。

您必须使用 Group[1],因为 group[0] 是整个匹配项。 我只是在两边重复同样的事情,这样更容易放入只存储唯一键的字典中。

【讨论】:

【参考方案6】:

如果你想从 MatchCollection 中得到一个不同的匹配列表而不转换为字符串,你可以使用这样的东西:

 var distinctMatches = matchList.OfType<Match>().GroupBy(x => x.Value).Select(x =>x.First()).ToList();

我知道已经 12 年了,但有时我们需要这种解决方案,所以我想分享一下。 C# 进化了,.NET 进化了,所以现在更容易了。

【讨论】:

以上是关于如何让正则表达式匹配只添加一次到匹配集合中?的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式简单语法以及常用正则表达式

正则表达式

正则表达式简单语法

正则表达式基本用法

正则表达式基本用法

十二,正则表达式整理