【问题标题】:C# quickest way to shift arrayC# 移动数组的最快方法
【发布时间】:2011-01-23 18:52:37
【问题描述】:

如何快速将数组中的所有项目向左移动一位,并用 null 填充末尾?

例如,[0,1,2,3,4,5,6] 将变为 [1,2,3,4,5,6,null]

编辑:我说得很快,但我想我的意思是有效率。我需要在不创建列表或其他数据结构的情况下执行此操作。这是我需要在尽可能短的时间内完成数十万次的事情。

【问题讨论】:

  • 您上面的示例不是“移位” - 移位意味着删除数组中的第一个元素,您希望从任何索引中删除。
  • 如果你需要做几十万次,你可能使用了错误的数据结构。
  • 尽管有编译器指令,队列实际上比数组快。队列维护开始和结束指针,因此它可以在 O(1) 时间内移除第一个元素(或最后一个元素)(相对于数组的 O(n))。如果要从中间删除值,那么链表会更好,但这会改变其他操作的复杂性。
  • 去向:呵呵。 “还有什么比数组更快?!”……好吧,在这种情况下是链表……但是,我将不得不在这里扮演魔鬼的拥护者并问“如果数组中只有 100 个项目,它真的需要多快?这是您的应用程序的瓶颈吗?”。除非你在做一些对性能很重要的事情,否则不要再关心了。如果您正在做一些性能关键的事情,那么让我们看看一些探查器数字吗?
  • 在您的示例中,您要求移动数组的元素。但是,选择的答案会将您的数组视为不可变的并创建一个新的...由于需要调用分配器,因此速度较慢。

标签: c# arrays performance


【解决方案1】:

这是我的测试工具...

var source = Enumerable.Range(1, 100).Cast<int?>().ToArray();
var destination = new int?[source.Length];

var s = new Stopwatch();
s.Start();
for (int i = 0; i < 1000000;i++)
{
    Array.Copy(source, 1, destination, 0, source.Length - 1);
}
s.Stop();
Console.WriteLine(s.Elapsed);

以下是每种解决方案 100 万次迭代的性能结果(8 核 Intel Xeon E5450 @ 3.00GHz)

                       100 elements  10000 elements
For Loop                     0.390s         31.839s 
Array.Copy()                 0.177s         12.496s
Aaron 1                      3.789s         84.082s
Array.ConstrainedCopy()      0.197s         17.658s

自己做出选择:)

【讨论】:

  • 不。我只是自己尝试了一下,得到了相同的结果。我猜Array.Copy 赢了。该死,这是一种快速的方法。
  • Array.Copy 可能更快,因为它可以做你在 C# 中做不到的事情。它很可能只是在玩页表并设置标志以强制写入时复制。
  • 如果可以的话,我会给你+100。只有两种方法可以解决像这样的性能“问题”。 A) 分析并选择最好的,或 B) 确定您并不真正关心现实条件下的几百毫秒的差异。首先编写代码以提高可读性,然后仅在需要时进行分析!
  • 我建议也测试Buffer.BlockCopy。而@RobertDavis:页表操作只会提供Array.Copy 公开的一小部分功能。例如,它需要对齐,在同一页面中没有其他内容,等等。不,Array.Copy 只是本机代码中的副本,在循环外部进行了边界检查。 Buffer.BlockCopy 应该是更优化的传输,它可能使用 SSE 加载和存储等技巧来一次移动多个元素。
  • 其实根据代码,系统运行C函数memmove来完成大部分繁重的工作。对于所有想要了解更多信息的人,请查看 clr/src/vm/comsystem.cpp,并查找 SystemNative::ArrayCopy。从referencesource.microsoft.com/#mscorlib/system/… 调用
【解决方案2】:

最快的方法是使用Array.Copy,在最终实现中使用批量内存传输操作(类似于memcpy):

var oldArray = new int?[] { 1, 2, 3, 4, 5, 6 };
var newArray = new int?[oldArray.Length];
Array.Copy(oldArray, 1, newArray, 0, oldArray.Length - 1);
// newArray is now { 2, 3, 4, 5, 6, null }

已编辑:根据文档:

如果 sourceArray 和destinationArray 重叠,则此方法的行为就像在覆盖destinationArray 之前将sourceArray 的原始值保存在临时位置。

因此,如果您不想分配新数组,则可以为源数组和目标数组传入原始数组——尽管我认为权衡会降低性能,因为这些值会通过临时持有位置.

我想,就像在任何此类调查中一样,您应该做一些快速的基准测试。

【讨论】:

  • Copy 方法中的两个数组可能会重叠,因此您不需要创建另一个数组。 Array.Copy(oldArray, 1, oldArray, 0, oldArray.Length - 1) 以及将最后一个元素设置为 null 也可以。
  • 我还没有运行测试,但我认为 for 循环方法最终会更快地运行 int eh 日志。不过感谢您的回复。
  • 如果您还没有“运行测试”,显然您并不太关心性能!您不妨使用 ToList、Remove、ToArray。
  • @Dested:除非for 循环编译成传统memcpy 使用的相同精益循环,否则情况并非如此。例如,请参阅waldev.blogspot.com/2008/05/…
  • @Ben M - 优化器有时会非常棒。唯一确定的方法是对其进行基准测试,但 for 循环很有可能编译为相同的精益循环,并且它可以避免 Array.Copy 方法在每次调用时必须执行的所有检查和计划。毕竟,复制是一种非常灵活的方法。
【解决方案3】:

这是我的解决方案,类似于 Task 的解决方案,因为它是一个简单的数组包装器,并且需要 O(1) 时间将数组向左移动。

public class ShiftyArray<T>
{
    private readonly T[] array;
    private int front;

    public ShiftyArray(T[] array)
    {
        this.array = array;
        front = 0;
    }

    public void ShiftLeft()
    {
        array[front++] = default(T);
        if(front > array.Length - 1)
        {
            front = 0;
        }
    }

    public void ShiftLeft(int count)
    {
        for(int i = 0; i < count; i++)
        {
            ShiftLeft();
        }
    }

    public T this[int index]
    {
        get
        {
            if(index > array.Length - 1)
            {
                throw new IndexOutOfRangeException();
            }

            return array[(front + index) % array.Length];
        }
    }

    public int Length { get { return array.Length; } }
}

通过 Jason Punyon 的测试代码运行它...

int?[] intData = Enumerable.Range(1, 100).Cast<int?>().ToArray();
ShiftyArray<int?> array = new ShiftyArray<int?>(intData);

Stopwatch watch = new Stopwatch();
watch.Start();

for(int i = 0; i < 1000000; i++)
{
    array.ShiftLeft();
}

watch.Stop();

Console.WriteLine(watch.ElapsedMilliseconds);

无论数组大小如何,大约需要 29 毫秒。

【讨论】:

  • "~29ms" 不是一个孤立的可用数字 - 您需要在同一系统上至少有一个其他实现的时间和数据来比较它。
  • 移位是最快的,是的,但是迭代数组比较慢
【解决方案4】:

使用Array.Copy() 方法,如

int?[] myArray = new int?[]{0,1,2,3,4};
Array.Copy(myArray, 1, myArray, 0, myArray.Length - 1);
myArray[myArray.Length - 1] = null

Array.Copy 可能是这样,微软希望我们复制数组元素...

【讨论】:

  • Array.Copy 可能是微软希望我们复制数组元素的方式 - 将此答案归档在“落入成功之坑”下。如果它很明显并且是 C#,那么你就知道其余的了。
【解决方案5】:

您不能使用 System.Collections.Generic.Queue 代替数组吗?

我觉得您需要对您的值执行操作以丢弃它,因此使用队列似乎更合适:

// dummy initialization
        System.Collections.Generic.Queue<int> queue = new Queue<int>();
        for (int i = 0; i < 7; ++i ) { queue.Enqueue(i); }// add each element at the end of the container

        // working thread
        if (queue.Count > 0)
            doSomething(queue.Dequeue());// removes the last element of the container and calls doSomething on it

【讨论】:

    【解决方案6】:

    对于任何找到此线程并即将实施高度评价的答案之一的灵魂。都是垃圾,不知道为什么。也许 Dested 最初要求一个新的数组实现,或者现在已经从问题中删除了一些东西。好吧,如果您只是想移动阵列并且不需要新阵列,请参阅像 tdaines's answer 这样的答案。并阅读诸如循环缓冲区/环形缓冲区之类的内容:http://en.wikipedia.org/wiki/Circular_buffer。无需移动实际数据。移动数组的性能不应与数组的大小相关。

    【讨论】:

    • 环形缓冲区更快,因为它避免了复制,但它不提供相同的 API。您不能将环形缓冲区传递给需要连续存储的函数,例如,因为数据存储在两个块中。
    【解决方案7】:

    如果它绝对必须在一个数组中,那么我会推荐最明显的代码。

    for (int index = startIndex; index + 1 < values.Length; index++)
         values[index] = values[index + 1];
    values[values.Length - 1] = null;
    

    这使优化器有最多机会在安装程序的任何目标平台上找到最佳方法。

    编辑:

    我刚刚借用了 Jason Punyon 的测试代码,恐怕他是对的。 Array.Copy 获胜!

        var source = Enumerable.Range(1, 100).Cast<int?>().ToArray();
        int indexToRemove = 4;
    
        var s = new Stopwatch();
        s.Start();
        for (int i = 0; i < 1000000; i++)
        {
            Array.Copy(source, indexToRemove + 1, source, indexToRemove, source.Length - indexToRemove - 1);
            //for (int index = indexToRemove; index + 1 < source.Length; index++)
            //    source[index] = source[index + 1]; 
        }
        s.Stop();
        Console.WriteLine(s.Elapsed);
    

    Array.Copy 在我的机器上需要 103 到 150 毫秒。

    for 循环在我的机器上需要 269 到 338 毫秒。

    【讨论】:

    • 它确实取决于数组的大小,在我的机器上:如果它低于 54,那么 for 循环实际上更快
    【解决方案8】:

    你不能

    • 为数组分配额外的 1000 个元素

    • 有一个整数变量int base = 0

    • 而不是访问a[i]访问a[base+i]

    • 要轮班,只需说base++

    然后在您完成 1000 次后,将其复制下来并重新开始。
    这样一来,您每 1000 个班次只需复制一次。


    老笑话:
    问:一个寄存器移位一位需要多少 IBM 360?
    A: 33. 32 保持位,1 移动寄存器。 (或一些这样的......)

    【讨论】:

    • 我实际上最终做了这样的事情。
    • 这基本上是其他几个人建议的“使用带有开始和结束指针的队列”。但解释清楚。
    【解决方案9】:

    您可以使用与源和目标相同的数组进行快速就地复制:

    static void Main(string[] args)
            {
                int[] array = {0, 1, 2, 3, 4, 5, 6, 7};
                Array.ConstrainedCopy(array, 1, array, 0, array.Length - 1);
                array[array.Length - 1] = 0;
            }
    

    【讨论】:

      【解决方案10】:

      你可以这样做:

      var items = new int?[] { 0, 1, 2, 3, 4, 5, 6 };  // Your array
      var itemList = new List<int?>(items);  // Put the items in a List<>
      itemList.RemoveAt(1); // Remove the item at index 1
      itemList.Add(null); // Add a null to the end of the list
      items = itemList.ToArray(); // Turn the list back into an array
      

      当然,完全摆脱数组而只使用 List 会更有效。然后你可以忘记第一行和最后一行,像这样:

      var itemList = new List<int?> { 0, 1, 2, 3, 4, 5, 6 };
      itemList.RemoveAt(1); // Remove the item at index 1
      itemList.Add(null); // Add a null to the end of the list
      

      【讨论】:

      • 嘿!托管编程是否完全窃取了考虑性能的能力?
      • 您说这不是最有效的方法是绝对正确的。如果这个操作会发生很多,那么这不是正确的答案。就像我说的那样,从数组到 List 再返回到数组效率不高,所以我会跳过数组而只使用 List 开始。如果这是一个仅偶尔发生的操作,那么这将工作得很好并且很容易理解。另外,这种方式可以处理不断变化的列表长度。
      【解决方案11】:

      试试这个!使用 Linq。不需要第二个数组。

              var i_array = new int?[] {0, 1, 2, 3, 4, 5, 6 };
      
              i_array = i_array.Select((v, k) => new { v = v, k = k }).
                  Where(i => i.k > 0).Select(i => i.v).ToArray();
      
              Array.Resize(ref i_array, i_array.Length + 1);
      

      输出: [0,1,2,3,4,5,6] 将变为 [1,2,3,4,5,6,null]

      【讨论】:

        【解决方案12】:

        如果您拥有内存,您可以考虑使用Unsafe Code 和良好的老式指针。

        让自己成为内存流并将其锁定或使用Marshal.AllocHGlobal 在其中构造所有数组,并在开头和结尾添加一点填充。 一次递增或递减所有数组指针。您仍然需要循环并设置您的空值。

        如果您需要选择性地增加或减少数组,则必须在它们之间添加填充。

        数组是令人难以置信的低级数据结构,如果您以低级方式处理它们,您可以从中获得巨大的性能。

        执行此操作的baytrail 的性能可能优于 Jason 的所有复制 8 核 Intel Xeon E5450 @ 3.00GHz

        【讨论】:

        • 即使使用不安全的代码,C# 也无法让您访问最大化内存吞吐量所需的 SSE 指令。
        【解决方案13】:

        未测试此代码,但它应该将所有值右移一位。请注意,最后三行代码是有效移动数组所需的全部内容。

        public class Shift : MonoBehaviour {
             //Initialize Array
             public int[] queue;
        
             void Start () {
                  //Create Array Rows
                  queue = new int[5];
        
                  //Set Values to 1,2,3,4,5
                  for (int i=0; i<5;i++)
                  {
                       queue[i] = i + 1;
                  }
        
                  //Get the integer at the first index
                  int prev = queue[0];
        
                  //Copy the array to the new array.
                  System.Array.Copy(queue, 1, queue, 0, queue.Length - 1);
        
                  //Set the last shifted value to the previously first value.
                  queue[queue.Length - 1] = prev;
        

        【讨论】:

        • 有趣的解决方案!
        【解决方案14】:

        我认为最好和最有效的方法是使用 Buffer.BlockCopy 函数。 您将为数组设置源和目标,源的偏移量为 1。根据您的数组类型(我假设它是 int),1 int = 4 个字节,因此您必须传入 4 作为此参数的第二个参数功能。请注意,偏移量是字节偏移量。

        所以它看起来像这样:

        int bytes2copy = yourArray.length - 4;
        Buffer.BlockCopy(yourArray, 4, yourArray, 0, bytes2copy);
        yourArray[yourArray.length-1] = null;
        

        【讨论】:

          【解决方案15】:

          数组复制是一个 O(n) 操作并创建一个新数组。 虽然数组复制当然可以快速有效地完成,但您所说的问题实际上可以以完全不同的方式解决,而无需(如您所要求的)创建一个新的数组/数据结构并且每个只创建一个小的包装对象实例数组:

          using System;
          using System.Text;
          
          public class ArrayReindexer
          {
              private Array reindexed;
              private int location, offset;
          
              public ArrayReindexer( Array source )
              {
                  reindexed = source;
              }
          
              public object this[int index]
              {
                  get
                  {
                      if (offset > 0 && index >= location)
                      {
                          int adjustedIndex = index + offset;
                          return adjustedIndex >= reindexed.Length ? "null" : reindexed.GetValue( adjustedIndex );
                      }
          
                      return reindexed.GetValue( index );
                  }
              }
          
              public void Reindex( int position, int shiftAmount )
              {
                  location = position;
                  offset = shiftAmount;
              }
          
              public override string ToString()
              {
                  StringBuilder output = new StringBuilder( "[ " );
                  for (int i = 0; i < reindexed.Length; ++i)
                  {
                      output.Append( this[i] );
                      if (i == reindexed.Length - 1)
                      {
                          output.Append( " ]" );
                      }
                      else
                      {
                          output.Append( ", " );
                      }
                  }
          
                  return output.ToString();
              }
          }
          

          通过以这种方式包装和控制对数组的访问,我们现在可以演示如何使用 O(1) 方法调用解决问题...

          ArrayReindexer original = new ArrayReindexer( SourceArray );
          Console.WriteLine( "   Base array: {0}", original.ToString() );
          ArrayReindexer reindexed = new ArrayReindexer( SourceArray );
          reindexed.Reindex( 1, 1 );
          Console.WriteLine( "Shifted array: {0}", reindexed.ToString() );
          

          将产生输出:

          基本数组:[ 0, 1, 2, 3, 4, 5, 6 ]
          移位数组:[ 0, 2, 3, 4, 5, 6, null ]

          我敢打赌,这样的解决方案对您不起作用是有原因的,但我相信这确实符合您最初声明的要求。 8)

          在实施特定解决方案之前考虑问题的所有不同种类解决方案通常很有帮助,这可能是本示例可以展示的最重要的事情。

          希望这会有所帮助!

          【讨论】:

            【解决方案16】:

            不正确且有点有趣的答案(谢谢,我会整晚都在这里!)

            int?[] test = new int?[] {0,1,2,3,4,5,6 };
            
                    int?[] t = new int?[test.Length];
                    t = test.Skip(1).ToArray();
                    t[t.Length - 1] = null; 
            

            本着仍在使用 Skip 的精神(别问我,我知道 LINQ 扩展方法最糟糕的用法),我想重写它的唯一方法是

                    int?[] test = new int?[] { 0, 1, 2, 3, 4, 5, 6 };
            
                    int?[] t = new int?[test.Length];
                    Array.Copy(test.Skip(1).ToArray(), t, t.Length - 1);
            

            但它绝不比其他选项快。

            【讨论】:

            • 这实际上将您要保留的最后一个元素变为空...所以我没有在我的答案中对其进行分析。
            • @Jason,你到底在说什么?我得到的结果是 [1,2,3,4,5,6,null]
            • @Stan R.:代码发布结果为 [1,2,3,4,5,null]。您可以立即看到没有保留长度。 test.Skip(1).ToArray() 的长度将小于 test。
            • @Jason,啊,我明白了..ToArray 实际上创建了一个全新的数组。哇,def需要对这种东西更加小心。感谢您指出了这一点。我会重写我的答案,然后你可以分析它:)
            • @Jason,是的,我知道..我的错误。
            【解决方案17】:

            我知道这是一个老问题,但来自 Google 并没有简单的示例,因此这是重新排序列表的最简单方法,并且您不必提供它将在运行时解决的类型,

               private static List<T> reorderList<T>(List<T> list){
                   List<T> newList = new List<T>();
            
                   list.ForEach(delegate(T item)
                   {
                       newList.Add(item);
                   });
            
                   return newList;
               }
            

            【讨论】:

              【解决方案18】:
              using System;
              using System.Threading;
              
              namespace ShiftMatrix
              {
                  class Program
                  {
                      static void Main(string[] args)
                      {
              
                          MatrixOperation objMatrixOperation = new MatrixOperation();
              
                          //Create a matrix
                          int[,] mat = new int[,]
                      {
                      {1, 2},
                      {3,4 },
                      {5, 6},
                      {7,8},
                      {8,9},
                      };
              
                          int type = 2;
                          int counter = 0;
                          if (type == 1)
                          {
                              counter = mat.GetLength(0);
                          }
                          else
                          {
                              counter = mat.GetLength(1);
                          }
                          while (true)
                          {
                              for (int i = 0; i < counter; i++)
                              {
                                  ShowMatrix(objMatrixOperation.ShiftMatrix(mat, i, type));
                                  Thread.Sleep(TimeSpan.FromSeconds(2));
                              }
                          }
                      }
                      public static void ShowMatrix(int[,] matrix)
                      {
                          int rows = matrix.GetLength(0);
                          int columns = matrix.GetLength(1);
                          for (int k = 0; k < rows; k++)
                          {
                              for (int l = 0; l < columns; l++)
                              {
                                  Console.Write(matrix[k, l] + " ");
                              }
                              Console.WriteLine();
                          }
                      }
                  }
                  class MatrixOperation
                  {
                      public int[,] ShiftMatrix(int[,] origanalMatrix, int shift, int type)
                      {
                          int rows = origanalMatrix.GetLength(0);
                          int cols = origanalMatrix.GetLength(1);
              
                          int[,] _tmpMatrix = new int[rows, cols];
                          if (type == 2)
                          {
                              for (int x1 = 0; x1 < rows; x1++)
                              {
                                  int y2 = 0;
                                  for (int y1 = shift; y2 < cols - shift; y1++, y2++)
                                  {
                                      _tmpMatrix[x1, y2] = origanalMatrix[x1, y1];
                                  }
                                  y2--;
                                  for (int y1 = 0; y1 < shift; y1++, y2++)
                                  {
                                      _tmpMatrix[x1, y2] = origanalMatrix[x1, y1];
                                  }
                              }
                          }
                          else
                          {
                              int x2 = 0;
                              for (int x1 = shift; x2 < rows - shift; x1++, x2++)
                              {
                                  for (int y1 = 0; y1 < cols; y1++)
                                  {
                                      _tmpMatrix[x2, y1] = origanalMatrix[x1, y1];
                                  }
                              }
                              x2--;
                              for (int x1 = 0; x1 < shift; x1++, x2++)
                              {
                                  for (int y1 = 0; y1 < cols; y1++)
                                  {
                                      _tmpMatrix[x2, y1] = origanalMatrix[x1, y1];
                                  }
                              }
              
                          }
                          return _tmpMatrix;
                      }
                  }
              
              }
              

              【讨论】:

                【解决方案19】:

                请参阅下面的 C# 代码以从字符串中删除空格。数组中的那个移位字符。性能是 O(n)。没有使用其他数组。所以也没有额外的内存。

                    static void Main(string[] args)
                    {
                        string strIn = System.Console.ReadLine();
                
                        char[] chraryIn = strIn.ToCharArray();
                
                        int iShift = 0;
                        char chrTemp;
                        for (int i = 0; i < chraryIn.Length; ++i)
                        {
                            if (i > 0)
                            {
                                chrTemp = chraryIn[i];
                                chraryIn[i - iShift] = chrTemp;
                                chraryIn[i] = chraryIn[i - iShift];
                            }
                            if (chraryIn[i] == ' ') iShift++;
                            if (i >= chraryIn.Length - 1 - iShift) chraryIn[i] = ' ';
                        }
                       System.Console.WriteLine(new string(chraryIn));
                       System.Console.Read();
                    }
                

                【讨论】:

                • 为什么是 -1 ?有人可以解释一下吗?
                • 因为您在此过程中创建了一个额外的 char 数组和字符串......问题是关于数组中元素的有效移动,所以它是题外话
                • 感谢你,杰拉德。是的,我正在将字符串转换为 char 数组。我应该更清楚。
                【解决方案20】:

                a 是整数数组,d 是数组必须左移的次数。

                static int[] rotLeft(int[] a, int d) 
                {
                     var innerLoop = a.Length - 1;
                     for(var loop=0; loop < d; loop++)
                     {
                         var res = a[innerLoop];
                         for (var i= innerLoop; i>=0; i--)
                         {
                            var tempI = i-1;
                            if (tempI < 0)
                            {
                                tempI = innerLoop;
                            }        
                            var yolo = a[tempI];
                            a[tempI] = res;
                            res = yolo;
                         }
                     }
                     return a;
                }
                

                【讨论】:

                  【解决方案21】:

                  当您需要调整同一个数组的大小时的简单方法。

                              var nLength = args.Length - 1;
                              Array.Copy(args, 1, args, 0, nLength);
                              Array.Resize(ref args, nLength);
                  

                  【讨论】:

                    猜你喜欢
                    • 2015-07-27
                    • 2013-09-05
                    • 2010-11-19
                    • 1970-01-01
                    • 2023-03-29
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-12-21
                    相关资源
                    最近更新 更多