Подтвердить что ты не робот

С# клонировать стек

У меня в моем коде несколько стеков, которые я использую для отслеживания моего логического местоположения. В определенные моменты мне нужно дублировать стеки, но я не могу клонировать их таким образом, чтобы сохранить порядок. Мне нужно только мелкое дублирование (ссылки, а не объект).

Каким будет правильный способ? Или я должен использовать несколько других стеков?

ПРИМЕЧАНИЕ. Я видел эту запись Проблема клонирования стека: ошибка .NET или ожидаемое поведение?, но не знаю, как настроить метод клонирования для класса Stack.

ПРИМЕЧАНИЕ # 2: Я использую System.Collection.Generic.Stack

4b9b3361

Ответ 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<T>, который мы здесь используем, Stack<T>(IEnumerable<T> source) и, конечно, когда вы выполняете итерацию по реализации IEnumerable<T> для Stack<T> он будет многократно поп-предметов из стека, тем самым подавая их вам в обратном порядке, в котором вы хотите, чтобы они вошли в новый стек. Поэтому выполнение этого процесса дважды приведет к тому, что стек будет в правильном порядке.

В качестве альтернативы:

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

Опять же, мы должны пройти стек в обратном порядке от выхода из итерации по стеку.

Я сомневаюсь, что существует разница в производительности между этими двумя методами.

Ответ 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();

Ответ 3

Для тех, кто заботится о производительности. Есть несколько других способов, как итерации через исходные элементы стека без больших потерь в производительности:

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

Я написал грубую программу (ссылка будет предоставлена ​​в конце сообщения) для измерения производительности и добавлена ​​два теста для уже предложенных реализаций (см. Clone1 и Clone2) и два теста для ToArray и CopyTo (см. Clone3 и Clone4, оба из них используют более эффективный метод 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);
    }
}

Результаты:

  • Clone1: 318,3766 мс
  • Clone2: 269,2407 ms
  • Clone3: 50,6025 мс
  • Clone4: 37,5233 мс - победитель

Как мы видим, подход с использованием метода CopyTo в 8 раз быстрее, и в то же время реализация довольно проста и понятна. Кроме того, я сделал быстрое исследование максимального значения размера стека: тесты Clone3 и Clone4 работали для большего размера стека до появления OutOfMemoryException:

  • Clone1: 67108765 элементы
  • Clone2: 67108765 элементы
  • Clone3: 134218140 элементов
  • Элементы Clone4: 134218140

Результаты выше для Clone1 и Clone2 меньше из-за дополнительных коллекций, которые были явно/неявно определены и, следовательно, повлияли на потребление памяти. Таким образом, подходы Clone3 и Clone4 позволяют клонировать экземпляр стека быстрее и с меньшим распределением памяти. Вы можете получить еще лучшие результаты, используя Reflection, но это другая история:)

Полный список программ можно найти здесь.

Ответ 4

Если вам не нужно фактическое Stack`1, а просто хотите быстро копию существующего Stack`1, что вы можете идти в том же порядке, что Pop возвращается, другой вариант, чтобы дублировать Stack`1 в Queue`1. Элементы будут тогда Dequeue в том же порядке, что они будут Pop от оригинала Stack`1.

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

Вывод:

Stack:
  pushed last, pop first
  in the middle
  pushed first, pop last

Queue:
  pushed last, pop first
  in the middle
  pushed first, pop last