【问题标题】:c# clone a stackc# 克隆一个堆栈
【发布时间】:2011-11-15 12:31:23
【问题描述】:

我的代码中有几个堆栈用于跟踪我的逻辑位置。在某些时候我需要复制堆栈,但我似乎无法以保留顺序的方式克隆它们。我只需要浅复制(引用,而不是对象)。

正确的方法是什么?还是应该使用其他类型的堆栈?

注意:我看到了这个帖子Stack Clone Problem: .NET Bug or Expected Behaviour?,但不确定如何为 Stack 类设置克隆方法。

注意 #2:我使用 System.Collection.Generic.Stack

【问题讨论】:

    标签: c# .net stack clone


    【解决方案1】:
    var clonedStack = new Stack<T>(new Stack<T>(oldStack));
    

    你可以把它写成扩展方法

    public static Stack<T> Clone<T>(this Stack<T> stack) {
        Contract.Requires(stack != null);
        return new Stack<T>(new Stack<T>(stack));
    }
    

    这是必要的,因为我们在这里使用的Stack&lt;T&gt; 构造函数是Stack&lt;T&gt;(IEnumerable&lt;T&gt; source),当然,当您为Stack&lt;T&gt; 迭代IEnumerable&lt;T&gt; 实现时,它会重复从堆栈中弹出项目,从而提供它们以与您希望它们进入新堆栈的顺序相反的顺序发送给您。因此,执行此过程两次将导致堆栈的顺序正确。

    或者:

    var clonedStack = new Stack<T>(oldStack.Reverse());
    
    
    public static Stack<T> Clone<T>(this Stack<T> stack) {
        Contract.Requires(stack != null);
        return new Stack<T>(stack.Reverse());
    }
    

    同样,我们必须以与迭代堆栈的输出相反的顺序遍历堆栈。

    我怀疑这两种方法之间存在性能差异。

    【讨论】:

    • 可悲的是,奇怪的是,这并没有保留顺序 :((我假设堆栈的双重初始化是错字还是这是一个技巧?)
    • @Angrius:它确实保留了订单。双重实例化是一种导致订单被保留的技巧。
    • @Angrius:“双重初始化”是必需的,因为每个都颠倒了顺序。如果你把它反转两次,你就会得到原来的顺序。
    • @Angrius:此外,它不保留订单并不奇怪。请参阅我对它发生原因的解释。为Stack&lt;T&gt; 实现IEnumerable&lt;T&gt; 非常有意义;如果为Stack&lt;T&gt; 执行IEnumerable&lt;T&gt; 除了反复pop 和yield 之外,我会感到困惑。 Stack&lt;T&gt;(IEnumerable&lt;T&gt; source) 的构造函数非常有意义;如果Stack&lt;T&gt; 的这个构造函数除了从source 中拉出并推动我会感到困惑。 IMO 没有什么奇怪的。
    • 虽然堆栈构造函数按顺序推送事物是有意义的,但在“现实生活”中通过获取项目列表并将它们按最后一项推送来构建堆栈是很常见的。例如,C 和派生语言中的可选参数通常从右向左推送,以便于按顺序读出。
    【解决方案2】:

    这是一种使用 LINQ 的简单方法:

    var clone = new Stack<ElementType>(originalStack.Reverse());
    

    您可以创建一个扩展方法来简化此操作:

    public static class StackExtensions
    {
        public static Stack<T> Clone<T>(this Stack<T> source)
        {
            return new Stack<T>(originalStack.Reverse());
        }
    }
    

    用法:

    var clone = originalStack.Clone();
    

    【讨论】:

    • 为我工作。这也保留了原始堆栈。
    • @Diego Mijelshon:不,它没有!它从顶部弹出并重复到堆栈底部。
    • @Jason 你是对的,我误解了我的测试结果。是 Stack 构造函数得到了“错误”的东西(即为了克隆的目的)
    【解决方案3】:

    如果您不需要实际的Stack`1,而只是想要一个现有Stack`1 的快速副本,您可以按照Pop 返回的相同顺序进行操作,另一种选择是复制Stack`1变成Queue`1。然后,这些元素将按照与原始 Stack`1 中的 Pop 相同的顺序 Dequeue

    using System;
    using System.Collections.Generic;
    
    class Test
    {
      static void Main()
      {
        var stack = new Stack<string>();
    
        stack.Push("pushed first, pop last");
        stack.Push("in the middle");
        stack.Push("pushed last, pop first");
    
        var quickCopy = new Queue<string>(stack);
    
        Console.WriteLine("Stack:");
        while (stack.Count > 0)
          Console.WriteLine("  " + stack.Pop());
        Console.WriteLine();
        Console.WriteLine("Queue:");
        while (quickCopy.Count > 0)
          Console.WriteLine("  " + quickCopy.Dequeue());
      }
    }
    

    输出:

    堆: 最后推,先弹出 在中间 先推,最后弹出 队列: 最后推,先弹出 在中间 先推,最后弹出

    【讨论】:

      【解决方案4】:

      对于那些关心性能的人。还有其他几种方法可以迭代原始堆栈成员而不会对性能造成很大损失:

      public T[] Stack<T>.ToArray();
      public void Stack<T>.CopyTo(T[] array, int arrayIndex);
      

      我编写了一个粗略的程序(链接将在文章末尾提供)来测量性能,并为已经建议的实现添加了两个测试(参见 Clone1Clone2 em>),以及 ToArrayCopyTo 方法的两个测试(参见 Clone3Clone4,它们都使用更高效的 Array.Reverse 方法)。

      public static class StackExtensions
      {
          public static Stack<T> Clone1<T>(this Stack<T> original)
          {
              return new Stack<T>(new Stack<T>(original));
          }
      
          public static Stack<T> Clone2<T>(this Stack<T> original)
          {
              return new Stack<T>(original.Reverse());
          }
      
          public static Stack<T> Clone3<T>(this Stack<T> original)
          {
              var arr = original.ToArray();
              Array.Reverse(arr);
              return new Stack<T>(arr);
          }
      
          public static Stack<T> Clone4<T>(this Stack<T> original)
          {
              var arr = new T[original.Count];
              original.CopyTo(arr, 0);
              Array.Reverse(arr);
              return new Stack<T>(arr);
          }
      }
      

      结果是:

      • 克隆 1:318,3766 毫秒
      • 克隆 2:269,2407 毫秒
      • 克隆 3:50,6025 毫秒
      • Clone4:37,5233 毫秒 - 获胜者

      如我们所见,使用 CopyTo 方法的方法快 8 倍,同时实现非常简单直接。此外,我对堆栈大小的最大值进行了快速研究:Clone3Clone4 测试在 OutOfMemoryException 发生之前适用于更大的堆栈大小:

      • Clone1:67108765 个元素
      • 克隆 2:67108765 个元素
      • Clone3:134218140 个元素
      • Clone4:134218140 个元素

      Clone1Clone2 的上述结果更小,因为显式/隐式定义了额外的集合,因此影响了内存消耗。因此,Clone3Clone4 方法允许以更少的内存分配更快地克隆堆栈实例。使用反射可以获得更好的结果,但情况不同 :)

      完整的节目列表可以在here找到。

      【讨论】:

      • 用 BenchmarkDotNet 证明结果。从 CPU 和内存的角度来看,Clone4 仍然是最有效的方法。 1. Clone1 (703.87 ms, 128 MB) 2. Clone2 (535.94 ms, 128 MB) 3. Clone3 (71.60 ms, 64 MB) 4. Clone4 (56.20 ms, 64 MB)
      猜你喜欢
      • 2018-12-29
      • 2016-08-05
      • 2016-12-20
      • 2015-04-25
      • 1970-01-01
      • 2011-10-22
      • 1970-01-01
      • 2020-12-17
      • 2018-03-30
      相关资源
      最近更新 更多