【问题标题】:Finding all positions of substring in a larger string in C#在 C# 中查找较大字符串中子字符串的所有位置
【发布时间】:2010-04-14 21:52:47
【问题描述】:

我有一个大字符串需要解析,我需要找到extract"(me,i-have lots. of]punctuation 的所有实例,并将每个实例的索引存储到一个列表中。

假设这段字符串位于较大字符串的开头和中间,它们都会被找到,并且它们的索引将添加到ListList 将包含 0 和其他索引,无论它是什么。

我一直在玩,string.IndexOf几乎完成了我正在寻找的工作,并且我已经编写了一些代码 - 但它不起作用,我无法做到找出问题所在:

List<int> inst = new List<int>();
int index = 0;
while (index < source.LastIndexOf("extract\"(me,i-have lots. of]punctuation", 0) + 39)
{
    int src = source.IndexOf("extract\"(me,i-have lots. of]punctuation", index);
    inst.Add(src);
    index = src + 40;
}
  • inst = 列表
  • source = 大字符串

有更好的想法吗?

【问题讨论】:

    标签: c# .net asp.net string


    【解决方案1】:

    这是它的一个示例扩展方法:

    public static List<int> AllIndexesOf(this string str, string value) {
        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);
            if (index == -1)
                return indexes;
            indexes.Add(index);
        }
    }
    

    如果你把它放到一个静态类中并使用using 导入命名空间,它会在任何字符串上显示为一个方法,你可以这样做:

    List<int> indexes = "fooStringfooBar".AllIndexesOf("foo");
    

    有关扩展方法的更多信息,http://msdn.microsoft.com/en-us/library/bb383977.aspx

    使用迭代器也一样:

    public static IEnumerable<int> AllIndexesOf(this string str, string value) {
        if (String.IsNullOrEmpty(value))
            throw new ArgumentException("the string to find may not be empty", "value");
        for (int index = 0;; index += value.Length) {
            index = str.IndexOf(value, index);
            if (index == -1)
                break;
            yield return index;
        }
    }
    

    【讨论】:

    • 为什么不使用 IEnumerable 并产生返回索引而不是索引列表?
    • @m0sa:好点。添加另一个版本只是为了好玩。
    • @PedroC88:使用yield 会使代码“懒惰”。它不会将所有索引收集到方法中的内存列表中。对性能有什么样的实际影响取决于很多因素。
    • @Paul:“不得”,如“不得”。如果您不喜欢其中的措辞,您可以随时提出修改建议,但我认为这并不难理解。
    • 注意!由于添加了value.Length,您可能会错过嵌套匹配!示例:“这是一个 NestedNestedNested 匹配测试!”与“NestedNested”匹配只会找到一个索引,而不是嵌套的索引。要解决此问题,只需在循环中添加 +=1 而不是 +=value.Length
    【解决方案2】:

    为什么不使用内置的 RegEx 类:

    public static IEnumerable<int> GetAllIndexes(this string source, string matchString)
    {
       matchString = Regex.Escape(matchString);
       foreach (Match match in Regex.Matches(source, matchString))
       {
          yield return match.Index;
       }
    }
    

    如果您确实需要重用表达式,则编译它并将其缓存在某处。在重用案例的另一个重载中将 matchString 参数更改为正则表达式 matchExpression。

    【讨论】:

    • 这不会编译
    • 什么是indexes?它没有在任何地方定义。
    • 我的错,它是一个残余。删除该行。
    • 请注意,此方法与接受的答案具有相同的缺陷。如果您的源字符串是“ccc”并且模式是“cc”,那么它将只返回一次。
    【解决方案3】:

    使用 LINQ

    public static IEnumerable<int> IndexOfAll(this string sourceString, string subString)
    {
        return Regex.Matches(sourceString, subString).Cast<Match>().Select(m => m.Index);
    }
    

    【讨论】:

    • 你忘记转义子字符串了。
    • 这比公认的解决方案更可取,因为它的圈复杂度较低。
    • 对于“aaa”和“aa”输入似乎不起作用。
    • 这是预期的行为......当您在“aaa”中匹配“aa”时,剩下的是与“aa”不匹配的“xxa”
    【解决方案4】:

    抛光版+忽略大小写的支持:

    public static int[] AllIndexesOf(string str, string substr, bool ignoreCase = false)
    {
        if (string.IsNullOrWhiteSpace(str) ||
            string.IsNullOrWhiteSpace(substr))
        {
            throw new ArgumentException("String or substring is not specified.");
        }
    
        var indexes = new List<int>();
        int index = 0;
    
        while ((index = str.IndexOf(substr, index, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) != -1)
        {
            indexes.Add(index++);
        }
    
        return indexes.ToArray();
    }
    

    【讨论】:

      【解决方案5】:

      可以使用KMP 算法以有效的时间复杂度在 O(N + M) 中完成,其中 N 是 text 的长度,M 是 pattern 的长度。

      这是实现和使用:

      static class StringExtensions
      {
          public static IEnumerable<int> AllIndicesOf(this string text, string pattern)
          {
              if (string.IsNullOrEmpty(pattern))
              {
                  throw new ArgumentNullException(nameof(pattern));
              }
              return Kmp(text, pattern);
          }
      
          private static IEnumerable<int> Kmp(string text, string pattern)
          {
              int M = pattern.Length;
              int N = text.Length;
      
              int[] lps = LongestPrefixSuffix(pattern);
              int i = 0, j = 0; 
      
              while (i < N)
              {
                  if (pattern[j] == text[i])
                  {
                      j++;
                      i++;
                  }
                  if (j == M)
                  {
                      yield return i - j;
                      j = lps[j - 1];
                  }
      
                  else if (i < N && pattern[j] != text[i])
                  {
                      if (j != 0)
                      {
                          j = lps[j - 1];
                      }
                      else
                      {
                          i++;
                      }
                  }
              }
          }
      
          private static int[] LongestPrefixSuffix(string pattern)
          {
              int[] lps = new int[pattern.Length];
              int length = 0;
              int i = 1;
      
              while (i < pattern.Length)
              {
                  if (pattern[i] == pattern[length])
                  {
                      length++;
                      lps[i] = length;
                      i++;
                  }
                  else
                  {
                      if (length != 0)
                      {
                          length = lps[length - 1];
                      }
                      else
                      {
                          lps[i] = length;
                          i++;
                      }
                  }
              }
              return lps;
          }
      

      这是一个如何使用它的示例:

      static void Main(string[] args)
          {
              string text = "this is a test";
              string pattern = "is";
              foreach (var index in text.AllIndicesOf(pattern))
              {
                  Console.WriteLine(index); // 2 5
              }
          }
      

      【讨论】:

      • 与最优 IndexOf 实现相比,它的性能如何,其中搜索开始索引设置为每次迭代的上一个匹配的结尾?
      • 比较 IndexOf 和 A​​llIndicesOf 是不正确的,因为它们的输出不同。在每次迭代中使用IndexOf方法,极大地增加了时间复杂度到O(N^2 M),而最优复杂度是O(N+M)。 KMP 与简单方法的工作方式不同,它使用预先计算的数组 (LPS) 来避免从一开始就进行搜索。推荐你阅读 KMP 算法。 Wikipedia 中“背景”部分的最后几段解释了它在 O(N) 中的工作原理。
      • C# 中的其他字符串搜索算法?
      • 我不明白为什么更多的人没有接受这个答案。它在长输入字符串和长搜索字符串上表现更好。阅读起来更复杂,但将其作为一个黑匣子并将其用作解决长输入字符串问题的首选方法是要走的路。
      【解决方案6】:
      public List<int> GetPositions(string source, string searchString)
      {
          List<int> ret = new List<int>();
          int len = searchString.Length;
          int start = -len;
          while (true)
          {
              start = source.IndexOf(searchString, start + len);
              if (start == -1)
              {
                  break;
              }
              else
              {
                  ret.Add(start);
              }
          }
          return ret;
      }
      

      这样称呼它:

      List<int> list = GetPositions("bob is a chowder head bob bob sldfjl", "bob");
      // list will contain 0, 22, 26
      

      【讨论】:

        【解决方案7】:

        @Matti Virkkunen 的回答您好

        public static List<int> AllIndexesOf(this string str, string value) {
            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);
                if (index == -1)
                    return indexes;
                indexes.Add(index);
                index--;
            }
        }
        

        但这涵盖了像 AOOAOOA 这样的测试用例 其中子字符串

        是AOOA和AOOA

        输出 0 和 3

        【讨论】:

          【解决方案8】:

          没有Regex,使用字符串比较类型:

          string search = "123aa456AA789bb9991AACAA";
          string pattern = "AA";
          Enumerable.Range(0, search.Length)
             .Select(index => { return new { Index = index, Length = (index + pattern.Length) > search.Length ? search.Length - index : pattern.Length }; })
             .Where(searchbit => searchbit.Length == pattern.Length && pattern.Equals(search.Substring(searchbit.Index, searchbit.Length),StringComparison.OrdinalIgnoreCase))
             .Select(searchbit => searchbit.Index)
          

          这将返回 {3,8,19,22}。空模式将匹配所有位置。

          对于多种模式:

          string search = "123aa456AA789bb9991AACAA";
          string[] patterns = new string[] { "aa", "99" };
          patterns.SelectMany(pattern => Enumerable.Range(0, search.Length)
             .Select(index => { return new { Index = index, Length = (index + pattern.Length) > search.Length ? search.Length - index : pattern.Length }; })
             .Where(searchbit => searchbit.Length == pattern.Length && pattern.Equals(search.Substring(searchbit.Index, searchbit.Length), StringComparison.OrdinalIgnoreCase))
             .Select(searchbit => searchbit.Index))
          

          这会返回 {3, 8, 19, 22, 15, 16}

          【讨论】:

            【解决方案9】:

            @csam理论上是正确的,虽然他的代码不会编译,可以重构

            public static IEnumerable<int> IndexOfAll(this string sourceString, string matchString)
            {
                matchString = Regex.Escape(matchString);
                return from Match match in Regex.Matches(sourceString, matchString) select match.Index;
            }
            

            【讨论】:

            • 如果他的代码不正确,您可以编辑他的帖子进行更正
            • 我没有注意到这一点。我不得不承认我不愿意这样做,以防万一我错了,虽然我不认为我是。
            • 对大字符串使用正则表达式不是一个好主意。该方法需要大量内存。
            【解决方案10】:

            我注意到至少有两个提议的解决方案不能处理重叠的搜索命中。我没有检查标有绿色复选标记的那个。这是处理重叠搜索命中的一个:

                public static List<int> GetPositions(this string source, string searchString)
                {
                    List<int> ret = new List<int>();
                    int len = searchString.Length;
                    int start = -1;
                    while (true)
                    {
                        start = source.IndexOf(searchString, start +1);
                        if (start == -1)
                        {
                            break;
                        }
                        else
                        {
                            ret.Add(start);
                        }
                    }
                    return ret;
                }
            

            【讨论】:

              【解决方案11】:
              public static Dictionary<string, IEnumerable<int>> GetWordsPositions(this string input, string[] Susbtrings)
              {
                  Dictionary<string, IEnumerable<int>> WordsPositions = new Dictionary<string, IEnumerable<int>>();
                  IEnumerable<int> IndexOfAll = null;
                  foreach (string st in Susbtrings)
                  {
                      IndexOfAll = Regex.Matches(input, st).Cast<Match>().Select(m => m.Index);
                      WordsPositions.Add(st, IndexOfAll);
              
                  }
                  return WordsPositions;
              }
              

              【讨论】:

                【解决方案12】:

                根据我用于在较大字符串中查找多个字符串实例的代码,您的代码如下所示:

                List<int> inst = new List<int>();
                int index = 0;
                while (index >=0)
                {
                    index = source.IndexOf("extract\"(me,i-have lots. of]punctuation", index);
                    inst.Add(index);
                    index++;
                }
                

                【讨论】:

                • 这里有两个问题:首先,您总是在结果列表中添加-1,这不是有效的结果。其次,由于indexOf 返回-1 和index++,代码不会终止。如果IndexOf 的结果是-1,我会使用while (true)break;
                【解决方案13】:

                我找到了这个example 并将其合并到一个函数中:

                    public static int solution1(int A, int B)
                    {
                        // Check if A and B are in [0...999,999,999]
                        if ( (A >= 0 && A <= 999999999) && (B >= 0 && B <= 999999999))
                        {
                            if (A == 0 && B == 0)
                            {
                                return 0;
                            }
                            // Make sure A < B
                            if (A < B)
                            {                    
                                // Convert A and B to strings
                                string a = A.ToString();
                                string b = B.ToString();
                                int index = 0;
                
                                // See if A is a substring of B
                                if (b.Contains(a))
                                {
                                    // Find index where A is
                                    if (b.IndexOf(a) != -1)
                                    {                            
                                        while ((index = b.IndexOf(a, index)) != -1)
                                        {
                                            Console.WriteLine(A + " found at position " + index);
                                            index++;
                                        }
                                        Console.ReadLine();
                                        return b.IndexOf(a);
                                    }
                                    else
                                        return -1;
                                }
                                else
                                {
                                    Console.WriteLine(A + " is not in " + B + ".");
                                    Console.ReadLine();
                
                                    return -1;
                                }
                            }
                            else
                            {
                                Console.WriteLine(A + " must be less than " + B + ".");
                               // Console.ReadLine();
                
                                return -1;
                            }                
                        }
                        else
                        {
                            Console.WriteLine("A or B is out of range.");
                            //Console.ReadLine();
                
                            return -1;
                        }
                    }
                
                    static void Main(string[] args)
                    {
                        int A = 53, B = 1953786;
                        int C = 78, D = 195378678;
                        int E = 57, F = 153786;
                
                        solution1(A, B);
                        solution1(C, D);
                        solution1(E, F);
                
                        Console.WriteLine();
                    }
                

                返回:

                在位置 2 找到 53 个

                在位置 4 找到 78 个
                在位置 7 找到 78 个

                57 不在 153786 中

                【讨论】:

                • 嗨,马克,我看到你是 stackoverflow 的新手。这个答案并没有给这个老问题增加任何东西,已经有更好的答案了。如果以后回答这样的问题,请尝试解释为什么您的答案包含一些其他答案中不存在的信息或价值。
                【解决方案14】:

                这个替代实现如何?

                 public static class MyExtensions
                    {
                        public static int HowMany(this string str, char needle)
                        {
                            int counter = 0;
                            int nextIndex = 0;
                            for (; nextIndex != -1; )
                            {
                                nextIndex = str.IndexOf(needle, nextIndex);
                                if (nextIndex != -1)
                                {
                                    counter++;
                                    //step over to the next char
                                    nextIndex++;
                                }
                            }
                            return counter;
                        }
                    }
                

                【讨论】:

                • 请不要在回答中提问。 解释。在这种情况下:解释为什么您的答案会改善此处给出的所有其他答案。
                猜你喜欢
                • 2017-03-27
                • 2015-09-13
                • 1970-01-01
                • 2017-03-26
                • 2012-08-03
                • 1970-01-01
                • 2012-05-21
                • 2014-05-06
                相关资源
                最近更新 更多