【问题标题】:Find small char array in large char array C# [duplicate]在大字符数组C#中查找小字符数组[重复]
【发布时间】:2014-03-05 09:46:47
【问题描述】:

假设我有一个包含数千个项目的大字符数组:

char[] mobyDick = "..." 使得mobyDick.Length = 2000。

我想知道该数组中是否存在某个字符数组以该顺序,以及它在哪里*。 (更新:我真的只需要知道它是否在主数组中的某个索引之后。)

char[] test = {'a','b','c','d'}

我可以做类似的事情

char[] mobyDick = "..."
string mobyString = new string(mobyDick);
if (mobyString.Contains(new string(test)))
{ do stuff}

但这对于我的情况来说并不是最佳的,因为我正在尝试编写一个必须非常快速地工作的解析器,而且我不想在每个字母左右都创建和搜索字符串。

是否有某种方法(算法或通过某种 .Net 方法)来确定 mobyDick 作为 char 数组是否包含 abcd 作为 char 数组?

【问题讨论】:

  • test 数组总是有 4 个项目吗? convert to string 并尝试找到 substring 解决方案时,您是否真的遇到过任何性能问题?
  • 测试目前可以有2-4个项目。我还没有针对完整的字符串进行测试,但我希望传递平均几千字长的字符串,所以我想尽早解决这个问题。
  • @Arcandio,你为什么有字符数组?比较的字符顺序重要吗?
  • 假设你已经猜到了naive算法; cs.utexas.edu/users/moore/best-ideas/string-searching

标签: c# char arrays


【解决方案1】:

这看起来是个有趣的问题,所以我尝试创建一个扩展方法...

 public static class ExtensionMethods
{
    public static int ContainsArray(this char[] arrayToSearchIn, char[] arrayToFind)
    {
        if (arrayToFind.Length == 0)
            return -1;

        int lengthOfArrayToFInd = arrayToFind.Length;
        int lengthOfArrayToSearchIn = arrayToSearchIn.Length;
        for (int i = 0; i < lengthOfArrayToSearchIn; i++)
        {
            if (lengthOfArrayToSearchIn - i < lengthOfArrayToFInd)
                return -1;

            if (arrayToSearchIn[i] != arrayToFind[0])
                continue;

            int arrayToFindCounter = 0;
            bool wasFound = true;
            for (int j = i; j < i + lengthOfArrayToFInd; j++)
            {
                if (arrayToFind[arrayToFindCounter] == arrayToSearchIn[j])
                    arrayToFindCounter++;
                else
                {
                    wasFound = false;
                    break;
                }
            }

            if (wasFound)
                return i;
        }

        return -1;
    }

}

这似乎(对我来说)适用于任何长度的子数组,包括空搜索 - 如果找到则返回第一次出现的位置(从零开始),否则返回 -1。

示例用法:

 static void Main(string[] args)
    {
        //                        0    1    2    3    4    5    6    7    8    9    0    1    2    3    4    5    6    7    8  
        char[] mobyDick = new[] {'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'd', 'a', 'z', 'y'};
        char[] test = {'a', 'b', 'c', 'd'};

        Console.WriteLine(mobyDick.ContainsArray(test));  // Position 12

        Console.ReadLine();
    }

【讨论】:

    【解决方案2】:

    这是一个使用 lambda 为您的搜索查找所有有效“起点”的方法。

    //return first index of substring or -1 for not found
    int searchForChar(char [] substring, char [] fulltext)
    {
        //all of the start points
        var indices = fulltext.Select ((b,i) => b == substring.FirstOrDefault() ? i : -1)
                              .Where(i => i != -1).ToArray();
    
        //search each start point
        foreach (var index in indices)
        {
            var found = true;
            int count = 0;
            for(int i = index; i < index + substring.Length; i++)
            {   
                found = true;
                if(substring[count++] != fulltext[i])
                {   
                    found = false;
                    break;
                }   
            }
            if (found) return index;
        }
        return -1;
    }
    

    很可能,执行此操作的一种更高效的方式类似于您在原始问题中所采用的方式。

    int searchForChar(char [] substring, char [] fulltext)
    {
        return fulltext.ToString().IndexOf(substring.ToString());
    
    }
    

    【讨论】:

    • +1 聪明而且比我的答案短得多——我要指出的唯一一点是它不处理零长度子字符串。
    • 好点,杰伊。我绝对没有做太多的输入检查。
    • 空子串情况已处理。
    • 值得注意的是,.Select.Where 最终将比将数组转换为字符串并使用 String.Contains 更昂贵,如您在问题中指出的那样。
    • @wdosanjos 你可能是对的。不确定这是否意味着我的回答值得一票否决,但这是一个很好的观点。
    【解决方案3】:

    我会尝试这种扩展方法:

    public static bool ContainsChars(this char[] source, char[] target,out int index)
    {
         int targetLength = target.Length - 1;
         int count = 0;
         char currentCharToSearch = target[0];
         for(int i=0; i<source.Length; i++)
         {
              if (source[i] == currentCharToSearch)
              {
                  count++;
                  if (count == targetLength) 
                  {
                      index = i - count + 1;
                      return true;
                  }
                  else
                  {
                      currentCharToSearch = target[count];
                  }
               }
               else
               {
                   count = 0;
                   currentCharToSearch = target[0];
               }
          }
          index = -1;
          return false;
    }
    

    用法:

    var c1 = new char[] { 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'h', 't' };
    var c2 = new char[] { 'c', 'h', 't' };
    
    int index;
    var result = c1.ContainsChars(c2,out index); // true index = 6
    
    c2 = new char[] { 'c', 't', 'h' };
    var result2 = c1.ContainsChars(c2,out index); // false index = -1
    

    【讨论】:

    • 这不会返回 它在哪里,这是要求的一部分。
    • @MikeChristensen 现在可以了
    【解决方案4】:

    试试这个:

    private bool Contains(char[] mobyDick, char[] test)
    {
        for (int i = 0; i < mobyDick.Length - test.Length + 1; i++)
        {
            bool found = true;
    
            for (int j = 0; j < test.Length; j++)
            {
                if (mobyDick[i + j] != test[j])
                {
                    found = false;
                    break;
                }
            }
    
            if (found) return true;
        }
    
        return false;
    }
    

    【讨论】:

    • 我知道 OP 说过他们只使用长度在 2 到 4 个字符之间的测试数组,但这不适用于其中包含一个元素的 char 数组。
    • 实际上效果很好。而且我不会想到停止检查子循环的中断。非常感谢!
    • @Arcandio 我的答案最终与此非常相似,但我使用 lamda 查找所有可能的起点 - 即子字符串第一个字母所在的索引。然后它会进行 X 次搜索,其中 X 是找到的索引数。
    • @Arcandio 我会在这里看看 Gray 的答案 - 它更强大一些。
    • 我修复了外循环的一个小错误。
    【解决方案5】:

    for 循环先在大数组中搜索测试用例的第一个字符,然后将测试数组中的连续字符与大数组的连续成员进行比较,怎么样?

    【讨论】:

    • 我在考虑这个,但似乎还有很长的路要走。我可能最终会将此作为我的备用计划。
    【解决方案6】:

    为了记录,这里是另一个使用通用扩展方法的解决方案。它适用于任何实现 IComparable 的数组类型。

    void Main()
    {
        var c1 = new char[] { 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'h', 't' };
        var c2 = new char[] { 'c', 'h', 't' };
    
        if (c1.Contains(c2))
        {
            // do something
        }
    
        int i = c1.IndexOf(c2);
    }
    
    public static class ArrayExtensions
    {
        public static bool Contains<T>(this T[] array, T[] subarray) where T : IComparable
        {
            return array.IndexOf(subarray) >= 0;
        }
    
        public static int IndexOf<T>(this T[] array, T[] subarray) where T : IComparable
        {
            for (int i = 0; i < array.Length - subarray.Length + 1; i++)
            {
                bool found = true;
    
                for (int j = 0; j < subarray.Length; j++)
                {
                    if (array[i + j].CompareTo(subarray[j]) != 0)
                    {
                        found = false;
                        break;
                    }
                }
    
                if (found) return i;
            }
    
            return -1;
        }
    }
    

    【讨论】:

      【解决方案7】:

      使用这个:

      var search = mobyDick.Intersect(test);
      if (search.ToArray().Length > 0)
      {
      //do something
      }
      

      LINQ - Set Operators

      【讨论】:

      • 如果较小元素中的元素存在于较大数组中,则无论顺序如何,这将匹配。
      • OP 专门说I want to find out if a certain array of characters exists in that array in that order
      猜你喜欢
      • 2012-12-27
      • 1970-01-01
      • 2018-06-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-21
      • 1970-01-01
      • 2015-02-22
      相关资源
      最近更新 更多