C#最快的移位阵列方式

时间:2010-03-04 17:13:52

标签: c# arrays performance

如何快速将数组中的所有项目向左移动,将结尾填充为null?

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

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

21 个答案:

答案 0 :(得分:109)

这是我的测试工具......

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 Core 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

自己做出选择:)

答案 1 :(得分:57)

最快方法是使用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重叠,则此方法的行为就像sourceArray的原始值在覆盖destinationArray之前保存在临时位置一样。

因此,如果你不想分配一个新的数组,你可以为源和目标传入原始数组 - 尽管我认为权衡会稍微慢一点,因为值会通过临时持有位置

我想,正如在任何这类调查中,你应该做一些快速的基准测试。

答案 2 :(得分:11)

这是我的解决方案,类似于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);

无论阵列大小如何,都需要~29ms。

答案 3 :(得分:7)

你不能使用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

答案 4 :(得分:7)

使用

中的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可能就是这样,微软希望我们复制数组元素......

答案 5 :(得分:5)

任何倾注的灵魂找到这个主题并即将实施一个评价很高的答案。所有这些都是垃圾,我不知道为什么会这样。也许Dested首先要求新的阵列实现,或者现在已经从问题中删除了。好吧,如果您只是想移动阵列而不需要新的阵列,请参阅tdaines答案之类的答案。并阅读循环缓冲区/环形缓冲区等内容:http://en.wikipedia.org/wiki/Circular_buffer。不需要移动实际数据。移位数组的性能应该与数组的大小相关联。

答案 6 :(得分:5)

如果 绝对存在于数组中,那么我会推荐最明显的代码。

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毫秒。

答案 7 :(得分:4)

不能

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

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

  • 而非访问a[i]访问a[base+i]

  • 进行转变,只需说出base++

然后在完成1000次之后,将其复制并重新开始 这样,您每1000次轮班只能复制一次。


老笑话:
问:将寄存器移位1位需要多少IBM 360? A:33。32用于保持位,1用于移动寄存器。 (或者某些......)

答案 8 :(得分:2)

你可以这样做:

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&lt;&gt;会更有效率。然后你可以忘记第一行和最后一行并按照这样做:

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

答案 9 :(得分:2)

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

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 :(得分:1)

我知道这是一个老问题但是来自谷歌没有简单的例子,所以感谢这是重新排序列表的最简单方法,而且你不必提供它将在运行时解决的类型,

   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;
   }

答案 11 :(得分:1)

试试这个!使用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 :(得分:1)

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

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

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

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

这样做的Baytrail可以胜过Jason的所有复制8 Core Intel Xeon E5450 @ 3.00GHz

答案 13 :(得分:1)

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

GeometryPrecisionReducer

答案 14 :(得分:0)

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;
}

答案 15 :(得分:0)

需要调整相同数组大小的简单方法。

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

答案 16 :(得分:0)

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

所以它看起来像这样:

{{1}}

答案 17 :(得分:0)

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;
        }
    }

}

答案 18 :(得分:0)

数组复制是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)

在实现特定问题之前考虑问题解决方案的所有不同通常很有帮助,也许这可能是此示例可以演示的最重要的事情。

希望这有帮助!

答案 19 :(得分:0)

不正确且有点有趣的回​​答(谢谢,我会整夜待在这里!)

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);

但它没有其他选择那么快。

答案 20 :(得分:-1)

请参阅下面的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();
    }