Ваш подход довольно хорош, но я думаю, что вы несколько обдумали проблему. Давайте сделаем шаг назад. У вас есть рекурсивный алгоритм:
void MoveTowerConsole (int n, Needle start, Needle finish, Needle temp)
{
if (n > 0)
{
MoveTowerConsole (n - 1, start, temp, finish);
Console.WriteLine ("Moving disk from {0} to {1}", start, finish);
MoveTowerConsole (n - 1, temp, finish, start);
}
}
Вывод алгоритма - это набор консольных выводов. Предположим, что вместо этого вы хотите, чтобы вывод алгоритма был последовательностью строк, которые будут выводиться на консоль. Давайте рассмотрим, как будет выглядеть такой метод.
Прежде всего, мы переименуем его. Во-вторых, его тип возврата не может быть пустым. Это должно быть IEnumerable<string>
:
IEnumerable<string> MoveTower(int n, Needle start, Needle finish, Needle temp)
{
if (n > 0)
{
MoveTower(n - 1, start, temp, finish);
Console.WriteLine ("Moving disk from {0} to {1}", start, finish);
MoveTower(n - 1, temp, finish, start);
}
}
Это правильно? Нет. Мы ничего не возвращаем, мы все еще выкидываем на консоль. Что мы хотим, чтобы итератор дал? Мы хотим, чтобы итератор дал:
- все ходы, необходимые для первого рекурсивного шага
- текущий ход
- все ходы, необходимые для второго рекурсивного шага
Таким образом, мы модифицируем алгоритм, чтобы получить те:
IEnumerable<string> MoveTower(int n, Needle start, Needle finish, Needle temp)
{
if (n > 0)
{
foreach(string move in MoveTower(n - 1, start, temp, finish))
yield return move;
yield return string.Format("Moving disk from {0} to {1}", start, finish);
foreach(string move in MoveTower(n - 1, temp, finish, start))
yield return move;
}
}
И мы закончили! Легко как то. Нет необходимости определять целый класс, чтобы превратить рекурсивный алгоритм в рекурсивный перечислитель; пусть компилятор сделает это за вас.
Если вы хотите изменить это на метод, который перечисляет «ходы», то сделайте это:
IEnumerable<Move> MoveTower(int n, Needle start, Needle finish, Needle temp)
{
if (n > 0)
{
foreach(Move move in MoveTower(n - 1, start, temp, finish))
yield return move;
yield return new Move(start, finish);
foreach(Move move in MoveTower(n - 1, temp, finish, start))
yield return move;
}
}
Теперь я бы критиковал этот код на основе эффективности. Делая рекурсивные перечислители таким образом, вы создаете цепочку из n перечислителей. Когда вам нужен следующий элемент, верхний перечислитель вызывает следующий перечислитель, вызывает следующий перечислитель ... вниз, на глубину n. Таким образом, каждый шаг теперь фактически состоит из n шагов. По этой причине я был бы склонен решить проблему без рекурсии.
Упражнение : переписать приведенный выше блок итератора, чтобы он вообще не выполнял рекурсию . Ваше решение, использующее явный стек, является шагом в правильном направлении, но все же выполняет рекурсию. Можете ли вы адаптировать его так, чтобы рекурсия не выполнялась?
Если вы хотите написать класс, реализующий IEnumerable<Move>
, то вы можете адаптировать приведенный выше код простым способом:
class MoveIterator : IEnumerable<Move>
{
public IEnumerator<Move> GetEnumerator()
{
foreach(Move move in MoveTower(whatever))
yield return move;
}
Вы можете использовать yield return для реализации метода, который возвращает перечислитель или перечисляемый .