在C#中旋转列表的最简单方法


68

列表说我有一个列表 List<int> {1,2,3,4,5}

旋转意味着:

=> {2,3,4,5,1} => {3,4,5,1,2} => {4,5,1,2,3}

也许轮换不是最好的选择,但希望您理解我的意思

我的问题是,最简单的方法是什么(简而言之,C#4 Linq已准备就绪),并且不会受到性能(合理的性能)的影响

谢谢。


16
您可以将其实现为队列。出队和入队相同的值。
cadrell0 2012年

1
数组解决方案可以接受吗?
danze 2012年

我想要一个更灵活的列表,因为ToList非常方便
埃里克·尹

Answers:


47

您可以将其实现为队列。出队和入队相同的值。

**我不确定将列表转换为队列的性能,但是人们反对我的评论,因此我将其发布为答案。


8
唯一的性能问题是OP是否要执行其他任何操作,因为切换到队列意味着访问除头/尾项之外的任何项均效率低下。
Servy 2012年

1
已经在我的答案中添加了实际的调用,因为您没有包括它们,而我的收集了很多实现:)希望您不介意。
乔恩·斯基特

@JonSkeet一点也不。纯粹的懒惰是我把它排除在外的原因。
cadrell0 2012年

73

List<T>

最简单的方法(一List<T>)是使用:

int first = list[0];
list.RemoveAt(0);
list.Add(first);

但是,性能令人讨厌-O(n)。

数组

这基本上等同于List<T>版本,但是更多的是手动的:

int first = array[0];
Array.Copy(array, 1, array, 0, array.Length - 1);
array[array.Length - 1] = first;

LinkedList<T>

如果可以使用aLinkedList<T>代替,那会简单得多:

int first = linkedList.First;
linkedList.RemoveFirst();
linkedList.AddLast(first);

这是O(1),因为每个操作都是恒定时间。

Queue<T>

cadrell0使用队列的解决方案是一条语句,因为它Dequeue删除了元素返回了它:

queue.Enqueue(queue.Dequeue());

虽然我不能找到的这个性能特性的任何文档,我期望 Queue<T>使用阵列和索引为“虚拟起点”来实现-在这种情况下,这是另一种O(1)的解决方案。

请注意,在所有这些情况下,您都需要先检查列表是否为空。(您可以认为这是一个错误或无操作。)


1
我会在链接列表上排队。通常,圆形数组在一般情况下性能更好,无论哪种情况,语言都将所有细节都抽象掉了。
Servy 2012年

1
@Servy:是的,这是公平的评论。链表允许更轻松地进行反向旋转,因为.NET没有双端队列:(尽管您显然可以轻松地构建一个...
Jon Skeet 2012年

1
'RemoveFirst()'不无效吗?该文档实际上显示了OP确切要求的示例:msdn.microsoft.com/en-us/library/ms132181.aspx。您必须首先使用linkedList.First来掌握第一个节点?
Pero P. 2012年

@PeroPejovic:抱歉,是的。我写了示例然后检查了一下,很失望地发现它不起作用,却忘记了对其进行修复。
乔恩·斯基特

1
@JonSkeet刚刚,您的意思是从背面拿起并放在正面,而不是扭转所有元素。无论哪种情况,如果需要的话,都可以很容易地用圆形数组实现,但是不能通过公开的“ Queue”类获得。商定的双端队列虽然不错。
Servy 2012年

25

我用这个:

public static List<T> Rotate<T>(this List<T> list, int offset)
{
    return list.Skip(offset).Concat(list.Take(offset)).ToList();
}

聪明,优雅,简单...到目前为止最好的解决方案...请记住,您必须存储偏移值,才能在每次调用中传递正确的值。
Cabuxa.Mapache

非常好的和优雅的解决方案,非常感谢。
SefaŞahinoğlu18年

9

似乎有些回答者将此视为探索数据结构的机会。尽管这些答案是有益的和有用的,但它们并不是很明显。

Linq'ish的方法是:您获得一个扩展方法,该方法返回一个懒惰的IEnumerable,该IEnumerable知道如何构建所需的内容。此方法不会修改源,应该仅在必要时分配源的副本。

public static IEnumerable<IEnumerable<T>> Rotate<T>(this List<T> source)
{
  for(int i = 0; i < source.Count; i++)
  {
    yield return source.TakeFrom(i).Concat(source.TakeUntil(i));
  }
}

  //similar to list.Skip(i-1), but using list's indexer access to reduce iterations
public static IEnumerable<T> TakeFrom<T>(this List<T> source, int index)
{
  for(int i = index; i < source.Count; i++)
  {
    yield return source[i];
  }
}

  //similar to list.Take(i), but using list's indexer access to reduce iterations    
public static IEnumerable<T> TakeUntil<T>(this List<T> source, int index)
{
  for(int i = 0; i < index; i++)
  {
    yield return source[i];
  }
}

用作:

List<int> myList = new List<int>(){1, 2, 3, 4, 5};
foreach(IEnumerable<int> rotation in myList.Rotate())
{
  //do something with that rotation
}

3

这个怎么样:

var output = input.Skip(rot)
                  .Take(input.Count - rot)
                  .Concat(input.Take(rot))
                  .ToList();

rot旋转点的数量在哪里-必须小于input列表中元素的数量。

如@ cadrell0答案所示,如果这是您对列表所做的全部操作,则应使用队列而不是列表。


2

我的解决方案可能太基本了(我不想说这很la脚...),而不是LINQ'ish。
但是,它具有相当好的性能。

int max = 5; //the fixed size of your array.
int[] inArray = new int[5] {0,0,0,0,0}; //initial values only.

void putValueToArray(int thisData)
{
  //let's do the magic here...
  Array.Copy(inArray, 1, inArray, 0, max-1);
  inArray[max-1] = thisData;
}

1

尝试

List<int> nums = new List<int> {1,2,3,4,5};
var newNums = nums.Skip(1).Take(nums.Count() - 1).ToList();
newNums.Add(nums[0]);

虽然,我更喜欢乔恩·斯基特的回答。


事实上,你看多乔恩的曲list.Add(list.RemoveAt(0));
nawfal

1

我对阵列的解决方案:

    public static void ArrayRotate(Array data, int index)
    {
        if (index > data.Length)
            throw new ArgumentException("Invalid index");
        else if (index == data.Length || index == 0)
            return;

        var copy = (Array)data.Clone();

        int part1Length = data.Length - index;

        //Part1
        Array.Copy(copy, 0, data, index, part1Length);
        //Part2
        Array.Copy(copy, part1Length, data, 0, index);
    }

1

我为此使用了以下扩展名:

static class Extensions
{
    public static IEnumerable<T> RotateLeft<T>(this IEnumerable<T> e, int n) =>
        n >= 0 ? e.Skip(n).Concat(e.Take(n)) : e.RotateRight(-n);

    public static IEnumerable<T> RotateRight<T>(this IEnumerable<T> e, int n) =>
        e.Reverse().RotateLeft(n).Reverse();
}

它们当然很容易(OP标题请求),并且它们具有合理的性能(OP书面请求)。这是我在LINQPad 5中使用高于平均水平的笔记本电脑运行的一个小演示:

void Main()
{
    const int n = 1000000;
    const int r = n / 10;
    var a = Enumerable.Range(0, n);

    var t = Stopwatch.StartNew();

    Console.WriteLine(a.RotateLeft(r).ToArray().First());
    Console.WriteLine(a.RotateLeft(-r).ToArray().First());
    Console.WriteLine(a.RotateRight(r).ToArray().First());
    Console.WriteLine(a.RotateRight(-r).ToArray().First());

    Console.WriteLine(t.ElapsedMilliseconds); // e.g. 236
}

1

您可以将以下代码用于左旋转。

List<int> backUpArray = array.ToList();

for (int i = 0; i < array.Length; i++)
{
    int newLocation = (i + (array.Length - rotationNumber)) % n;
    array[newLocation] = backUpArray[i];
}

1

您可以在.net框架中发挥出色。

我了解到,您要做的不只是一种迭代行为,还包括一种新的集合类型。因此,我建议您尝试基于IEnumerable的扩展方法,该方法可与Collections,Lists等一起使用...

class Program
{
    static void Main(string[] args)
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7 };

        IEnumerable<int> circularNumbers = numbers.AsCircular();

        IEnumerable<int> firstFourNumbers = circularNumbers
            .Take(4); // 1 2 3 4

        IEnumerable<int> nextSevenNumbersfromfourth = circularNumbers
            .Skip(4).Take(7); // 4 5 6 7 1 2 3 
    }
}

public static class CircularEnumerable
{
    public static IEnumerable<T> AsCircular<T>(this IEnumerable<T> source)
    {
        if (source == null)
            yield break; // be a gentleman

        IEnumerator<T> enumerator = source.GetEnumerator();

        iterateAllAndBackToStart:
        while (enumerator.MoveNext()) 
            yield return enumerator.Current;

        enumerator.Reset();
        if(!enumerator.MoveNext())
            yield break;
        else
            yield return enumerator.Current;
goto iterateAllAndBackToStart;
    }
}
  • 合理的表现
  • 灵活

如果您想走得更远,请像旋转样品一样旋转CircularList并按住相同的枚举器以跳过Skip()


0

下面是我的方法。谢谢

public static int[] RotationOfArray(int[] A, int k)
  {
      if (A == null || A.Length==0)
          return null;
      int[] result =new int[A.Length];
      int arrayLength=A.Length;
      int moveBy = k % arrayLength;
      for (int i = 0; i < arrayLength; i++)
      {
          int tmp = i + moveBy;
          if (tmp > arrayLength-1)
          {
              tmp =  + (tmp - arrayLength);
          }
          result[tmp] = A[i];             
      }        
      return result;
  }

请在您的代码中添加一些解释,以帮助读者理解为什么这可以解决问题。此外,这是否真的可以为现有答案添加任何内容?
Gert Arnold

0
public static int[] RightShiftRotation(int[] a, int times) {
  int[] demo = new int[a.Length];
  int d = times,i=0;
  while(d>0) {
    demo[d-1] = a[a.Length - 1 - i]; d = d - 1; i = i + 1;
  }
  for(int j=a.Length-1-times;j>=0;j--) { demo[j + times] = a[j]; }
  return demo;
}

请在您的答案中添加一些评论。
Vasilii Muravev

当一个问题已经有这么多答案时,您需要说明是什么使您的答案与众不同。
Gert Arnold

0

使用Linq,

List<int> temp = new List<int>();     

 public int[] solution(int[] array, int range)
    {
        int tempLength = array.Length - range;

        temp = array.Skip(tempLength).ToList();

        temp.AddRange(array.Take(array.Length - range).ToList());

        return temp.ToArray();
    }

-1

我被要求以最小的内存使用率反转字符数组。

char[] charArray = new char[]{'C','o','w','b','o','y'};

方法:

static void Reverse(ref char[] s)
{
    for (int i=0; i < (s.Length-i); i++)
    {
        char leftMost = s[i];
        char rightMost = s[s.Length - i - 1];

        s[i] = rightMost;
        s[s.Length - i - 1] = leftMost;
    }
}

-1

如何使用模块化算法:

public void UsingModularArithmetic()
{ 
  string[] tokens_n = Console.ReadLine().Split(' ');
  int n = Convert.ToInt32(tokens_n[0]);
  int k = Convert.ToInt32(tokens_n[1]);
  int[] a = new int[n];

  for(int i = 0; i < n; i++)
  {
    int newLocation = (i + (n - k)) % n;
    a[newLocation] = Convert.ToInt32(Console.ReadLine());
  }

  foreach (int i in a)
    Console.Write("{0} ", i);
}

因此,当我从控制台读取内容时,基本上将值添加到数组中。


问题中没有提到控制台输入。为了使它成为一个有效的答案,您需要从头开始List<int> {1,2,3,4,5}并说明代码如何进行轮换。
谜团

我认为问题是“让我说一个清单”。它没有说清单是如何准备的。我要显示的是在准备输入列表时,或者基本上是在将数据添加到输入列表本身时,您可以使用模块化算术来解决问题。即使您要使用现有列表,仍可以使用上述逻辑来准备新的轮换列表。
Naphstor

1
我建议您从列表中明确显示如何执行此操作。显示额外的内容可能不值得。
谜团

我同意这一点。但是我只是给出了以不同方式实施解决方案的想法。
Naphstor

以其他方式显示它可能不值得。您有一个有趣的答案,但是由于它没有从列表中显示如何做到,所以很难理解它如何应用于问题。
谜团
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.