在C#中将第一个数组元素移动到末尾

4

这是一个基础问题(我是C#新手),但在C#中是否有一种有效的方法将数组的第一个元素移动到末尾?

我发现了这个问题,它描述了ruby中的.rotate方法,但我无法找到类似的方法在C#中。

如果我有一个数组:

[1, 2, 3, 4, 5]

在C#中是否有一个函数可以返回:
[2, 3, 4, 5, 1]

提前感谢!

编辑:答案

最好的解决方案是使用LinkedList<T>,正如许多人建议的那样,并如Alex's answer中所示。他建议的解决方案是使用:

list.AddLast(list.RemoveFirst());

可以在 for 循环 中运行:

    void func<T>(LinkedList<T> list, int rotate) {
    for(var i = 0; i < rotate; i++) {
       list.AddLast(list.RemoveFirst());
    }
}

感谢大家的帮助!

2
不可以,但是你可以创建一个... - Fabjan
3
旋转并不等同于移动或交换。你询问的是交换第一个和最后一个值。交换两个值只需要几行代码。你尝试过了吗?你所说的“高效”是什么意思?你只是在复制整数。 - Panagiotis Kanavos
5
不是两个交换……你必须交换每个元素。 - itsme86
2
@PanagiotisKanavos OP *正在尝试将第一个元素移动到末尾。没有断开连接。 - itsme86
3
从原帖和链接的帖子中可以很清楚地看出,无论他们使用什么词语,他们都想要实现的目标是非常明确的。 - Sudsy1002
显示剩余8条评论
8个回答

9

有许多方法可以实现这一点。其中一种方法是:

var result = arr.Skip(1).Concat(arr.Take(1))

这很酷,但它需要将类型转换为数组,因为所有这些方法都返回 IEnumerable。另外,它实际上返回原始数组还是创建一个新的数组? - Marko
2
根据结果的使用情况,IEnumerable可能已经足够了。但是如果需要数组,只需在末尾加上.ToArray()即可。 - Magnus

7
如果你使用 LinkedList<T> 而不是 array<T>,你可以直接使用以下代码:
list.AddLast(list.RemoveAndGetFirst());

编辑:RemoveAndGetFirst() 可以像扩展方法一样:

LinkedListNode<T> elem = list.First;
list.RemoveFirst();
return elem;

复杂度为O(1)。当您多次执行此操作时:

void func<T>(LinkedList<T> list, int rotate) {
    for(var i = 0; i < rotate; i++) {
       list.AddLast(list.RemoveFirst());
    }
}

你将面临一个复杂度为O(N)的问题[其中N是旋转次数]。从性能上来说,这是最好的解决方案。
如果你真的需要使用数组,那么这可能是一种简单的解决方法:
var tmp = list[0];
for(var i = 1; i < list.Length; i++) {
   list[i - 1] = list[i];
}
list[list.Length - 1] = tmp;

(请注意,没有范围检查)

但是如果您需要经常执行此操作,则这将非常耗时。如果您需要多次执行此操作:

void func<T>(T[] list, int rotate) {
    for(var j = 0; j < rotate; j++) {
       var tmp = list[0];
       for(var i = 1; i < list.Length; i++) {
           list[i - 1] = list[i];
       }
       list[list.Length - 1] = tmp;
    }
}

如果你不加改进地进行旋转操作,最终会得到O(N^2) = O(N * M)(其中N是元素的数量,M是旋转的数量),这将非常糟糕。如果你事先知道你经常需要执行此操作,更好的方法是:

void func<T>(T[] list, int rotate {
    for(var j = 0; j < list.Length; j++) {
        var tmp = list[j];
        var ix = (rotate + j) % list.Length;
        list[j] = list[ix];
        list[ix] = tmp;
    }
}

这将导致O(N) [其中N是元素的数量]。

正如其他人已经建议的那样,如果您在多个位置需要此功能,则编写扩展方法是一个好主意。


我认为在第二个例子中应该是i < list.Length,如果它是一个数组的话。 - PJRobot
1
这个改了吗?因为LinkedList.RemoveFirst()返回的是void,所以这段代码无法编译。 - Gener4tor
1
@Gener4tor:我修改了答案,包括描述RemoveAndGetFirst()方法内容的扩展。 - Alex

4

使用 Array.Copy 将元素复制到其本身只是进行了移位 ;)

var array = new int[]{1, 2, 3, 4, 5};

var head = array[0];
Array.Copy(array, 1, array, 0, array.Length- 1);
array[array.Length - 1] = head;

作为一个扩展方法,返回一个新数组,就像 Ruby 版本一样
static class ArrayRotateExtensions {
    public static int[] Rotate(this int[] arr, int offset) {
        var l = arr.Length;
        var rot = new int[l];

        if (offset == 0) {
            Array.Copy(arr, 0, rot, 0, l);
            return rot;
        }

        // constrain rotations greater than array length, it's the same result anyway
        offset = offset % l;

        // negative rotation is equal to positive rotation length - offset
        if (offset < 0) {
            offset += l;
        }

        Array.Copy(arr, offset, rot, 0, l - offset);
        Array.Copy(arr, 0, rot, l - offset, offset);

        return rot;
    }
}

这将允许你做

var array = new int[]{1, 2, 3, 4, 5};
var rotated = array.Rotate(1);

可以任意旋转。

唯一的缺点是你需要为想要使用它的每种数组类型添加一个版本。


3

看起来现在变成了代码高尔夫 :-) 所以这是我的贡献:

var x = new[] { 1, 2, 3, 4, 5 };

var y = Enumerable.Range(1, x.Length).Select(i => x[i % x.Length]).ToArray();

2
< p > 在LINQ中没有类似的功能的原因很可能是开发LINQ的人认为这不是绝对必要的东西...< /p > < p > 如果你真的需要,可以创建一个扩展方法。 大致如下:< /p >
    public static IEnumerable<T> Rotate<T>(this IEnumerable<T> elements, int number)
    {
        var elemetsList = elements as IList<T> ?? elements.ToList();
        var list = new List<T>(elemetsList.Count);

        if (number > elemetsList.Count - 1)
        {
            throw new ArgumentException(nameof(number));
        }

        for (int i = number; i < elemetsList.Count; i++)
        {
            list.Add(elemetsList[i]);
        }

        for (int i = 0; i < number; i++)
        {
            list.Add(elemetsList[i]);
        }

        return list;
    }

并使用它:

var arr = new int[] {1, 2, 3, 4, 5};
int[] result = arr.Rotate(1).ToArray();
int[] result2 = arr.Rotate(3).ToArray();

输出:

2 3 4 5 1

4 5 1 2 3

这个解决方案相当高效。 对于长度为500,000的数组,在我的机器上只需要7毫秒就可以执行完。


2
试试这个...
using System;

public class Program
{   
    public static  int[] arrData = new int[5]{1,2,3,4,5};

    public static void Main()
    {
        Console.WriteLine("\nOriginal array\n");
        foreach(var item in arrData)
        {
            Console.WriteLine(item.ToString());
        }
        Console.WriteLine("\nShift to last\n");
        arrData = shiftLast(arrData);
        foreach(var item in arrData)
        {
            Console.WriteLine(item.ToString());
        }
    }

    public static int[] shiftLast(int[] arr)
    {
        int last = arr[arr.Length - 1];
        int first= arr[0];
        arr[arr.Length - 1] = first;
        arr[0] = last;
        return arr;
    }
}

请尝试在这里运行。

祝好!


1
据我所知,数组没有这样的方法。如果您经常这样做,也许应该考虑使用不同的对象(List<T>Stack<T>等)。
但即使使用数组,您也可以使用扩展方法实现简单的功能:
public static int[] MoveFirstToLast (this int[] obj)
{
    int movedValue = obj[0];
    (int i = 1; i < obj.Length; i++)
    {
        obj[i - 1] = obj[i];
    }
    obj[obj.Length - 1] = movedValue;
    return obj;
}

然后使用方法只是:

int[] myArray = //whatever;
int[] changedArray = myArray.MoveFirstToLast();

@RufusL 我的错,谢谢。 - Pinx0

1
也许像这样 -
        static void Main( string[] args ) {
            Console.WriteLine(string.Join(", ", getArray(new int[] { 1, 2, 3, 4, 5 })));
            Console.Read();
            return;
        }
        static int[] getArray( int[] arr ) {
            List<int> O = new List<int>();
            for (int x = 1, l = arr.Length; x < l; x++) {
                O.Add(arr[x]);
            }
            O.Add(arr[0]);
            return O.ToArray();
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接