【问题标题】:How to find biggest substring from string1 into string2如何从string1和string2中找到最长的子串
【发布时间】:2021-01-02 00:47:51
【问题描述】:

假设我有两个字符串 string1string2

var string1 = "images of canadian geese goslings";

var string2 = "Canadian geese with goslings pictures to choose from, with no signup needed";

我需要找到与string2 匹配的string1 的最大子字符串。

这里最大的子字符串是"canadian geese",它与string2匹配。

我怎样才能找到它?我尝试将string1 分解为char[] 并找到单词然后合并匹配的单词,但这没有达到我的目标。

【问题讨论】:

  • 天真的方法是获取 string1 的所有子串以降序长度(即所有长度为 n 的子串,然后是所有长度为 n-1、n-2 的子串)并检查它们是否是子串字符串 2。您找到的第一个匹配项是两者中最长的子串,尽管可能还有其他长度相同的子串。
  • @Sinatr 可能反对票是因为缺少任何代码和自己的努力
  • 一种可能的解决方案是find all substrings,然后花费最长的时间。
  • @PavelAnikhouski 您引用的答案只是在由某个分隔符分隔的字符串中搜索最长的标记(例如句子中最长的单词)
  • 您只搜索整个单词?就字符数或整个单词数而言的“最大子字符串”?

标签: c# arrays string string-matching


【解决方案1】:

我将再添加一个,使用 span/readonlymemory,这样您就可以避免分配当前答案创建的所有字符串。请注意,我没有对起始空间或结束空间进行任何检查,因为这似乎不是问题的要求。这确实会进行不区分大小写的搜索,如果您不希望这样做,您可以通过使用内置 indexof 并删除不区分大小写的比较来提高搜索效率。

    static void Main(string[] _)
    {
        var string1 = "images of canadian geese goslings";

        var string2 = "Canadian geese with goslings pictures to choose from, with no signup needed";

        var longest = FindLongestMatchingSubstring(string1, string2);

        Console.WriteLine(longest);
    }

    static string FindLongestMatchingSubstring(string lhs, string rhs)
    {
        var left = lhs.AsMemory();
        var right = rhs.AsMemory();

        ReadOnlyMemory<char> longest = ReadOnlyMemory<char>.Empty;

        for (int i = 0; i < left.Length; ++i)
        {
            foreach (var block in FindMatchingSubSpans(left, i, right))
            {
                if (block.Length > longest.Length)
                    longest = block;
            }
        }

        if (longest.IsEmpty)
            return string.Empty;

        return longest.ToString();
    }

    static IEnumerable<ReadOnlyMemory<char>> FindMatchingSubSpans(ReadOnlyMemory<char> source, int pos, ReadOnlyMemory<char> matchFrom)
    {
        int lastMatch = 0;

        for (int i = pos; i < source.Length; ++i)
        {
            var ch = source.Span[i];

            int match = IndexOfChar(matchFrom, lastMatch, ch);

            if (-1 != match)
            {
                lastMatch = match + 1;

                int end = i;

                while (++end < source.Length && ++match < matchFrom.Length)
                {
                    char lhs = source.Span[end];
                    char rhs = matchFrom.Span[match];

                    if (lhs != rhs && lhs != (char.IsUpper(rhs) ? char.ToLower(rhs) : char.ToUpper(rhs)))
                    {
                        break;
                    }
                }

                yield return source.Slice(i, end - i);
            }
        }
    }

    static int IndexOfChar(ReadOnlyMemory<char> source, int pos, char ch)
    {
        char alt = char.IsUpper(ch) ? char.ToLower(ch) : char.ToUpper(ch);

        for (int i = pos; i < source.Length; ++i)
        {
            char m = source.Span[i];

            if (m == ch || m == alt)
                return i;
        }

        return -1;
    }

【讨论】:

    【解决方案2】:

    看看下面的代码https://dotnetfiddle.net/aPyw3o

    public class Program {
    
    static IEnumerable<string> substrings(string s, int length) {
        for (int i = 0 ; i + length <= s.Length; i++) {
            var ss = s.Substring(i, length);
            if (!(ss.StartsWith(" ") || ss.EndsWith(" ")))
                yield return ss;
        }
    }
    
    public static void Main()
    {
        int count = 0;
        var string1 = "images of canadian geese goslings";
        var string2 = "Canadian geese with goslings pictures to choose from, with no signup needed";
        string result = null;
        for (int i = string1.Length; i>0 && string.IsNullOrEmpty(result); i--) {
            foreach (string s in substrings(string1, i)) {
                count++;
                if (string2.IndexOf(s, StringComparison.CurrentCultureIgnoreCase) >= 0) {
                    result = s;
                    break;
                }
            }
        }
        if (string.IsNullOrEmpty(result)) 
            Console.WriteLine("no common substrings found");
        else 
            Console.WriteLine("'" + result + "'");
        Console.WriteLine(count);
    }
    
    }   
    

    substrings 方法返回字符串s 的所有子字符串,长度为length(对于yield,请查看文档https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield)我们跳过以空格开头或结尾的子字符串,因为我们不希望空格使子字符串比实际更长)

    外部循环遍历子字符串的所有可能长度值,从最长(即string1.Length)到最短(即1)。然后检查每个找到的长度为i 的子字符串,如果它也是string2 的子字符串。如果是这种情况,我们可以停止,因为不再有任何公共子字符串,因为我们在之前的迭代中检查了所有更长的子字符串。但当然可能还有其他常见的子串,长度为i

    【讨论】:

      【解决方案3】:

      优雅的循环方法 - 结果包括鹅 "canadian geese " 后面的空格

      var string1 = "images of canadian geese goslings";
      var string2 = "Canadian geese with goslings pictures to choose from, with no signup needed";
      
      string result = "";
      
      for (int i = 0; i < string1.Length; i++)
      {
          for (int j = 0; j < string1.Length - i; j++)
          {
              //add .Trim() here if you want to ignore space characters
              string searchpattern = string1.Substring(i, j);
              if (string2.IndexOf(searchpattern,  StringComparison.OrdinalIgnoreCase) > -1 && searchpattern.Length > result.Length)
              {
                  result = searchpattern;
              }
          }
      }
      

      https://dotnetfiddle.net/q3rHjI

      旁注: canadianCanadian 不相等,因此如果要搜索不区分大小写,则必须使用 StringComparison.OrdinalIgnoreCase

      【讨论】:

      • 由于这个答案的所有 cmets 都消失了,我将再次分享我的想法:这种方法可行,但效率很低。出于多种原因 1) 它从可能的最短子字符串(即单个字符)开始,一直到最长。例如,在 canadian geese 之前检查 canadian。如果算法从尽可能长的匹配开始,然后缩短候选,它可能会在第一次匹配时停止。
      • 2) 它检查子字符串,它永远不会匹配,因为从相同索引开始的较短子字符串不匹配。例如 im 不包含在 string2 中,因此无需检查 ima 或 string1 从索引 0 开始的任何更长的子字符串。
      • 3) 它会检查可能的候选人,即使已经找到更长的匹配项。如果 canadian geese 已经找到匹配,那么任何小于 13 个字符的候选都不会是最长的公共子串,因此,不需要在 string2 中搜索它
      • @derpirscher imo: readability / mainainability > 在这种情况下的性能。
      • 是的,这是一个有效的观点。我的 cmets 并不是一种攻击,只是为了给你一些提示,告诉你如何改进你的算法。而且我的所有观点都是非常简单的改进,对可维护性没有太大影响,但对性能的影响很大。
      猜你喜欢
      • 1970-01-01
      • 2011-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多