替换字符串中的重叠匹配项(正则表达式或字符串操作)

Replacing overlapping matches in a string (regex or string operations)

我一直在尝试查找给定字符串中所有出现的子串,并将特定出现的地方替换为另一个子串(条件对问题不重要)。 我需要的是找到所有出现的事件(甚至是重叠的)并能够轻松替换我选择的特定事件。

问题是,如果我不使用前瞻,我将找不到重叠的事件(例如,在“aaa”中查找“aa”只会找到第一个“aa”序列,因为第二个与第一个重叠一):

var regex = new Regex(Regex.Escape("aa"));
regex.Matches("aaa").Count;

第二行的值:1 预期: 2

如果我使用 lookahead,我会找到所有出现的地方,但替换不起作用(例如,将“a”中的“a”替换为“b”,将导致“ba”而不是“b”) :

var regex = new Regex(Regex.Escape("(?=a)"));
regex.Replace("a", "b");

替换结果:ba 预期:b

当然,这些都是简单的示例,以简单的方式展示问题,但我需要它来处理任何示例。 我知道我可以轻松地搜索两者,或者手动检查这个词,但是这个代码片段会 运行 很多次,需要既高效又可读。

关于找到重叠事件同时仍然能够正确替换的任何想法/技巧?我应该使用正则表达式吗?

要获得重叠的结果,您必须将搜索模式移动一个字符,次数与搜索字符串的长度一样多。

假设包含 aaaaaa 的文本和 aaa 的 seachrstring(4 个预期匹配项),将使用搜索模式完成三个正则表达式搜索:

  • aaa(2 场比赛)
  • (?<=a)aaa(1 场比赛)
  • (?<=aa)aaa(1 场比赛)

同样适用于更复杂的搜索,例如 abababa 中的 aba

private static IEnumerable<Match> GetOverlappingMatches(string text, string searchstring)
{
    IEnumerable<Match> combinedMatches = Enumerable.Empty<Match>();

    for (int i = 0; i < searchstring.Length; i++)
    {
        combinedMatches = combinedMatches.Concat(GetMatches(text, searchstring, i));
    }

    return combinedMatches.Distinct(new MatchComparer());
}

private static IEnumerable<Match> GetMatches(string text, string searchstring, int shifts)
{
    string lookahead = $"(?<={searchstring.Substring(0, shifts)})";
    string pattern = $"{lookahead}{searchstring}";
    return Regex.Matches(text, pattern);
}

您还想添加一个 MatchComparer 来过滤双重匹配。

public class MatchComparer : IEqualityComparer<Match>
{
    public bool Equals(Match x, Match y)
    {
        return x.Index == y.Index
            && x.Length == y.Length;
    }

    public int GetHashCode([DisallowNull] Match obj)
    {
        return obj.Index ^ obj.Length;
    }
}

我想我会放弃正则表达式并编写一个简单的循环如下(有改进的余地),因为我认为它会更快更容易理解。

        public IEnumerable<int> FindStartingOccurrences(string input, string pattern)
        {
            var occurrences = new List<int>();

            for (int i=0; i<input.Length; i++)
            {
                if (input.Length+1 > i+pattern.Length)
                {
                    if (input.Substring(i, pattern.Length) == pattern)
                    {
                        occurrences.Add(i);
                    }
                }
            }

            return occurrences;
        }

然后这样调用:

var occurrences = FindStartingOccurrences("aaabbaaaaaccaadaaa", "aa");