【问题标题】:Function / Regular Expression to match string parts within a larger string & highlight the parts函数/正则表达式匹配较大字符串中的字符串部分并突出显示部分
【发布时间】:2017-08-01 05:51:11
【问题描述】:

我正在尝试构建一个函数,该函数采用搜索字符串并匹配较大字符串中的部分并突出显示它们。请参见下面的示例:

原字符串: 由于飞机上的头顶空间有限,我向您保证,托运行李不收取任何费用,我可以继续填写所有托运行李表格你。

要搜索和突出显示的文本:不收费,我填写表格

预期结果:由于飞机上的头顶空间有限,我向您保证,托运行李将免费 ,我可以继续填写所有 为您托运行李表格

我可以搜索完整的字符串或使用子字符串一次搜索一个单词,但两者都不会产生所需的结果。诀窍可能是以某种方式从完整字符串开始递归搜索,然后逐渐将其分解成更小的部分,直到部分匹配。有几个假设:

  • 搜索必须尽可能贪婪,即在尝试匹配较小部分或单个单词之前匹配字符串的较大部分。
  • 搜索将始终在找到任何匹配项之后继续前进,即如果在位置 x 找到前 2 个单词,则单词 3 和 4 将始终位于 x 之后,而不是 x 之前。

希望这是有道理的。谁能指出我正确的方向?我已经搜索了该站点,但没有找到与我要查找的内容相似的内容。

谢谢

【问题讨论】:

  • 如果您现在包含一次尝试创建正则表达式的尝试,那么这将是一个完美的正则表达式问题,我将始终将其用作指向用户“如何发布完美的正则表达式问题”的参考"
  • 创建正则表达式的好资源:regex101.com
  • 这是否意味着你有var lst = new List<string>() {"no fee", "I fill out the forms"};
  • 不,我的搜索字符串是var lst = new List<string>() {"no fee, I fill out the forms"},需要从完整字符串开始搜索,但根据需要部分递归。
  • 看看this demo。虽然添加了一些冗余标签,但总体结果可能是您所需要的。

标签: c# regex string


【解决方案1】:

如果这对您有帮助,请告诉我。它不使用正则表达式来查找字符串,只是IndexOf

它首先获取要突出显示的单词Tuple,代表单词的开始索引和结束索引。

它使用将围绕单词的前缀和后缀突出显示文本(这里:html标签)。

static void Main(string[] args)
{
    var input = "Since there is limited overhead space on the plane, I assure you, there will be no fee for checking the bags, I can go ahead and fill out all the checked baggage forms for you";
    var searchExpression = "no fee, I fill out the forms";

    var highlightedInput = HighlightString(input, searchExpression, "<b>", "</b>");

    Console.WriteLine(highlightedInput);
    Console.ReadLine();
}

public static IEnumerable<Tuple<int, int>> GetHighlights(string input, string searchExpression)
{
    var splitIntoWordsRegex = new Regex(@"\W+");
    var words = splitIntoWordsRegex.Split(searchExpression);
    return GetHighlights(input, words);
}

public static IEnumerable<Tuple<int, int>> GetHighlights(string input, IEnumerable<string> searchExpression)
{
    var highlights = new List<Tuple<int, int>>();

    var lastMatchedIndex = 0;
    foreach (var word in searchExpression)
    {
        var indexOfWord = input.IndexOf(word, lastMatchedIndex,  StringComparison.CurrentCulture);
        var lastIndexOfWord = indexOfWord + word.Length;

        highlights.Add(new Tuple<int, int>(indexOfWord, lastIndexOfWord));

        lastMatchedIndex = lastIndexOfWord;
    }

    return highlights;
}

public static string HighlightString(string input, string searchExpression, string highlightPrefix, string highlightSufix)
{
    var highlights = GetHighlights(input, searchExpression).ToList();

    var output = input;
    for (int i = 0, j = highlights.Count; i<j; i++)
    {
        int diffInputOutput = output.Length - input.Length;
        output = output.Insert(highlights[i].Item1 + diffInputOutput, highlightPrefix);

        diffInputOutput = output.Length - input.Length;
        output = output.Insert(highlights[i].Item2 + diffInputOutput, highlightSufix);
    }

    return output;
}

================== 编辑======================

为了减少突出显示的最小/最大索引,您可以使用下面的代码。虽然不是最漂亮的,但能胜任。

它获取一个单词的所有可用索引(感谢Finding ALL positions of a substring in a large string in C#)。将它们添加到 highlights,然后操作此集合以保持关闭匹配项不是您需要的。

public static IEnumerable<Tuple<int, int>> GetHighlights(string input, IEnumerable<string> searchExpression)
{
    var highlights = new List<Tuple<string, int, int>>();

    // Finds all the indexes for 
    // all the words found.
    foreach (var word in searchExpression)
    {
        var allIndexesOfWord = AllIndexesOf(input, word, StringComparison.InvariantCultureIgnoreCase);
        highlights.AddRange(allIndexesOfWord.Select(index => new Tuple<string, int, int>(word, index, index + word.Length)));
    }

    // Reduce the scope of the highlights in order to 
    // keep the indexes as together as possible.
    var firstWord = searchExpression.First();
    var firstWordIndex = highlights.IndexOf(highlights.Last(x => String.Equals(x.Item1, firstWord)));

    var lastWord = searchExpression.Last();
    var lastWordIndex = highlights.IndexOf(highlights.Last(x => String.Equals(x.Item1, lastWord)));

    var sanitizedHighlights = highlights.SkipWhile((x, i) => i < firstWordIndex);
    sanitizedHighlights = sanitizedHighlights.TakeWhile((x, i) => i <= lastWordIndex);

    highlights = new List<Tuple<string, int, int>>();
    foreach (var word in searchExpression.Reverse())
    {
        var lastOccurence = sanitizedHighlights.Last((x) => String.Equals(x.Item1, word));
        sanitizedHighlights = sanitizedHighlights.TakeWhile(x => x.Item3 < lastOccurence.Item2);
        highlights.Add(lastOccurence);
    }

    highlights.Reverse();

    return highlights.Select(x => new Tuple<int, int>(x.Item2, x.Item3));
}

public static List<int> AllIndexesOf(string str, string value, StringComparison comparison)
{
    if (String.IsNullOrEmpty(value))
        throw new ArgumentException("the string to find may not be empty", "value");

    List<int> indexes = new List<int>();
    for (int index = 0; ; index += value.Length)
    {
        index = str.IndexOf(value, index, comparison);
        if (index == -1)
            return indexes;
        indexes.Add(index);
    }
}

使用此代码和文本:

"No, about the fee, since there is limited overhead space on the plane, I assure you, there will be no fee for checking the bags, I can go ahead and fill out all the checked baggage forms for you."

我得到了以下结果:

不,关于费用,因为飞机上的头顶空间有限,我向你保证,托运行李将 费用可以继续为您填写所有托运行李表格

================================================ =======

编辑 2 使用正则表达式方法,结合之前的尝试获得的经验。
请注意,如果没有找到表达式中的每个单词,则不会找到高亮。

public static IEnumerable<Tuple<int,int>> GetHighlights(string expression, string search)
{
    var highlights = new List<Tuple<string, int, int>>();

    var wordsToHighlight = new Regex(@"(\w+|[^\s]+)").
        Matches(search).
        Cast<Match>().
        Select(x => x.Value);

    foreach(var wordToHighlight in wordsToHighlight)
    {
        Regex findMatchRegex = null;
        if (new Regex(@"\W").IsMatch(wordToHighlight))
            findMatchRegex = new Regex(String.Format(@"({0})", wordToHighlight), RegexOptions.IgnoreCase);  // is punctuation
        else
            findMatchRegex = new Regex(String.Format(@"((?<!\w){0}(?!\w))", wordToHighlight), RegexOptions.IgnoreCase); // si word

        var matches = findMatchRegex.Matches(expression).Cast<Match>().Select(match => new Tuple<string, int, int>(wordToHighlight, match.Index, match.Index + wordToHighlight.Length));

        if (matches.Any())
            highlights.AddRange(matches);
        else
            return new List<Tuple<int, int>>();
    }

    // Reduce the scope of the highlights in order to 
    // keep the indexes as together as possible.
    var firstWord = wordsToHighlight.First();
    var firstWordIndex = highlights.IndexOf(highlights.Last(x => String.Equals(x.Item1, firstWord)));

    var lastWord = wordsToHighlight.Last();
    var lastWordIndex = highlights.IndexOf(highlights.Last(x => String.Equals(x.Item1, lastWord)));

    var sanitizedHighlights = highlights.SkipWhile((x, i) => i < firstWordIndex);
    sanitizedHighlights = sanitizedHighlights.TakeWhile((x, i) => i <= lastWordIndex);

    highlights = new List<Tuple<string, int, int>>();
    foreach (var word in wordsToHighlight.Reverse())
    {
        var lastOccurence = sanitizedHighlights.Last((x) => String.Equals(x.Item1, word));
        sanitizedHighlights = sanitizedHighlights.TakeWhile(x => x.Item3 < lastOccurence.Item2);
        highlights.Add(lastOccurence);
    }

    highlights.Reverse();

    return highlights.Select(x => new Tuple<int, int>(x.Item2, x.Item3));
}

还需要注意的是,这种方法现在可以处理标点符号。找到以下结果。

输入:
No, about the fee, since there is limited overhead space on the plane, I assure you, there will be no fee for checking the bags, I can go ahead and fill out all the checked baggage forms for you.

搜索:
no fee, I fill out the forms

输出:
不,关于费用,因为飞机上的头顶空间有限,我向你保证,托运行李 费用 可以继续为您填写所有托运行李表格 .

输入:
When First Class Glass receives your call, we will assign a repair person to visit you to assist.

搜索:
we assign a repair person

输出:
当 First Class Glass 接到您的电话时,我们分配 a 维修 拜访您以提供帮助。

【讨论】:

  • 谢谢,非常感谢您的帮助。鉴于我没有可行的解决方案,这要好得多。但它并不适用于所有情况,我正在努力解决它。
  • 你会遇到一个不起作用的案例吗?也许只是需要一点点(希望)
  • 第一,它忽略了标点符号,但我可以接受。更重要的是,如果您将输入字符串更改为"No, about the fee, since there is limited overhead space on the plane, I assure you, there will be no fee for checking the bags, I can go ahead and fill out all the checked baggage forms for you.",它会捕获第一次出现的 no 和 fee (InvariantCultureIgnoreCase)。它应该找到字符串后面出现的连续“无费用”
  • 查看编辑,让我知道这是否适合您。
  • 所以它在某些情况下效果更好,但在其他情况下会出错。
猜你喜欢
  • 1970-01-01
  • 2015-10-25
  • 2010-09-15
  • 1970-01-01
  • 2012-11-25
  • 1970-01-01
  • 1970-01-01
  • 2023-04-02
相关资源
最近更新 更多