【问题标题】:Easiest way to Rotate a List in c#在 C# 中旋转列表的最简单方法
【发布时间】:2012-03-30 18:04:01
【问题描述】:

列表说我有一个列表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 就绪),并且不会受到性能的影响(合理的性能)

谢谢。

【问题讨论】:

  • 您可以将其实现为队列。 Dequeue 和 Enqueue 的值相同。
  • 阵列解决方案可以接受吗?
  • 我想要一个列表,更灵活,Array on,因为 ToList 非常方便

标签: c# arrays linq list


【解决方案1】:

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>

如果您可以改用LinkedList<T>,那就简单多了:

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

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

Queue<T>

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

queue.Enqueue(queue.Dequeue());

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

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

【讨论】:

  • 我会选择链表上的队列。在一般情况下,循环数组通常表现更好,在任何一种情况下,语言都会抽象出所有细节。
  • @Servy:是的,这是公平的评论。链表允许 reverse 更轻松地旋转,因为 .NET 没有双端队列 :( 您显然可以轻松构建一个...
  • 'RemoveFirst()` 不是无效的吗?文档实际上显示了 OP 所要求的示例:msdn.microsoft.com/en-us/library/ms132181.aspx。您必须最初使用linkedList.First 来获取第一个节点?
  • @PeroPejovic:抱歉,是的。我写了这个例子然后检查,很失望地发现它不起作用,忘记修复它。
  • @JonSkeet 刚刚意识到你的意思是反转,就像从后面拍摄并放在前面,而不是反转所有元素。在任何一种情况下,如果您愿意,可以很容易地使用循环数组来实现,但不能通过公开的“队列”类获得。不过,同意的双端队列会很好。
【解决方案2】:

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

**我不确定将列表转换为队列的性能,但人们赞成我的评论,所以我将其发布为答案。

【讨论】:

  • 唯一的性能问题是如果 OP 正在做其他事情,因为切换到队列意味着访问除第一个/最后一个项目之外的任何内容都是低效的。
  • 已将实际调用添加到我的答案中,因为您没有将它们包括在内,而我的几乎是在收集实现:) 希望您不介意。
  • @JonSkeet 一点也不。纯粹的懒惰是我把它排除在外的原因。
【解决方案3】:

我用这个:

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

【讨论】:

  • 智能、优雅和简单...迄今为止最好的解决方案...只记住您必须存储偏移值才能在每次调用中传递正确的值。
  • 非常好的和优雅的解决方案,非常感谢。
【解决方案4】:

似乎有些回答者将此视为探索数据结构的机会。虽然这些答案内容丰富且有用,但它们并不是很像 Linq'ish。

Linq'ish 的方法是:你得到一个扩展方法,它返回一个惰性 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
}

【讨论】:

    【解决方案5】:

    这个怎么样:

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

    其中rot 是要旋转的点数 - 必须小于 input 列表中的元素数。

    正如@cadrell0 的回答所示,如果这就是您对列表所做的全部,您应该使用队列而不是列表。

    【讨论】:

      【解决方案6】:

      我的解决方案可能太基础了(我不想说它是蹩脚的......)而不是 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;
      }
      

      【讨论】:

        【解决方案7】:

        试试

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

        不过,我更喜欢 Jon Skeet 的回答。

        【讨论】:

        • 确实,你的看起来比 Jon 的 list.Add(list.RemoveAt(0)); 更复杂
        【解决方案8】:

        我的数组解决方案:

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

        【讨论】:

          【解决方案9】:

          我为此使用了以下扩展:

          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
          }
          

          【讨论】:

            【解决方案10】:

            您可以使用以下代码进行左旋转。

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

            【讨论】:

              【解决方案11】:

              你可以在.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()

              【讨论】:

                【解决方案12】:

                以下是我的方法。谢谢

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

                【讨论】:

                • 请在您的代码中添加一些解释,以帮助读者理解为什么这样可以解决问题。另外,这真的会为现有答案添加任何内容吗?
                【解决方案13】:
                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;
                }
                

                【讨论】:

                • 请在您的答案中添加一些 cmets。
                • 当一个问题已经有这么多答案时,您需要解释是什么让您的答案在其中独一无二。
                【解决方案14】:

                使用 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();
                    }
                

                【讨论】:

                  【解决方案15】:

                  如果您使用的是字符串,则可以使用 ReadOnlySpans 非常有效地执行此操作:

                  ReadOnlySpan<char> apiKeySchema = "12345";
                  const int apiKeyLength = 5;
                  for (int i = 0; i < apiKeyLength; i++)
                  {
                      ReadOnlySpan<char> left = apiKeySchema.Slice(start: i, length: apiKeyLength - i);
                      ReadOnlySpan<char> right = apiKeySchema.Slice(start: 0, length: i);
                      Console.WriteLine(string.Concat(left, right));
                  }       
                  

                  输出:

                  12345
                  23451
                  34512
                  45123
                  51234

                  【讨论】:

                    【解决方案16】:

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

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

                    【讨论】:

                      【解决方案17】:

                      如何使用模运算:

                      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&lt;int&gt; {1,2,3,4,5} 开始,并展示您的代码如何进行轮换。
                      • 我相信问题是“假设我有一个列表”。它没有说明清单是如何准备的。我要展示的是在准备输入列表时,或者基本上在将数据添加到输入列表本身时,您可以使用模运算并解决问题。即使您想使用现有列表,您仍然可以使用上述逻辑来准备新的旋转列表。
                      • 我建议您从列表中明确展示如何执行此操作。展示一些额外的东西可能不值得。
                      • 我同意这一点。但我只是提出了以不同方式实施解决方案的想法。
                      • 以不同的方式显示它可能不值得。你有一个有趣的答案,但很难理解它是如何应用于这个问题的,因为它没有显示如何从列表中做到这一点。
                      猜你喜欢
                      • 2013-04-22
                      • 2011-05-18
                      • 1970-01-01
                      • 2017-12-11
                      • 2020-05-22
                      • 2010-12-01
                      • 1970-01-01
                      • 2020-09-16
                      相关资源
                      最近更新 更多