简体   繁体   English

C#如何制作GetEnumerator()的递归版本

[英]C# How to make a recursive version of GetEnumerator()

Can somebody give me advice on how to create a recursive version of GetEnumerator()? 有人可以给我关于如何创建GetEnumerator()的递归版本的建议吗? The well-known Towers of Hanoi problem may serve as an example that is comparable to the actual problem I have. 众所周知的河内塔问题可以作为一个与我遇到的实际问题相当的例子。 A simple algorithm to show all moves for a stack of disks of height n is: 显示高度为n的磁盘堆栈的所有移动的简单算法是:

void MoveTower0 (int n, Needle start, Needle finish, Needle temp)
{
  if (n > 0)
  {
    MoveTower0 (n - 1, start, temp, finish);
    Console.WriteLine ("Moving disk from {0} to {1}", start, finish);
    MoveTower0 (n - 1, temp, finish, start);
  }
}

What I actually want to do is set up a class HanoiTowerMoves that implements IEnumerable and that enables me to iterate over all moves as follows: 我真正想要做的是建立一个实现IEnumerable的HanoiTowerMoves类,这使我可以按如下方式迭代所有移动:

foreach (Move m in HanoiTowerMoves) Console.WriteLine (m);

The first step towards a GetEnumerator() implementation seems to get rid of the MoveTower parameters. 迈向GetEnumerator()实现的第一步似乎摆脱了MoveTower参数。 This can easily be done by using a stack. 这可以通过使用堆栈轻松完成。 I also introduced a class Move that combines the parameters into a single variable. 我还介绍了一个Move类,它将参数组合成一个变量。

class Move
{
  public int N { private set; get; }
  public Needle Start { private set; get; }
  public Needle Finish { private set; get; }
  public Needle Temp { private set; get; }

  public Move (int n, Needle start, Needle finish, Needle temp)
  {
    N = n;
    Start = start;
    Finish = finish;
    Temp = temp;
  }

  public override string ToString ()
  {
    return string.Format ("Moving disk from {0} to {1}", Start, Finish);
  }
}

Now MoveTower can be rewritten as follows: 现在可以按如下方式重写MoveTower:

void MoveTower1 ()
{
  Move m = varStack.Pop ();

  if (m.N > 0)
  {
    varStack.Push (new Move (m.N - 1, m.Start, m.Temp, m.Finish));
    MoveTower1 ();
    Console.WriteLine (m);
    varStack.Push (new Move (m.N - 1, m.Temp, m.Finish, m.Start));
    MoveTower1 ();
  }
}

This version must be called as follows: 必须按如下方式调用此版本:

varStack.Push (new Move (n, Needle.A, Needle.B, Needle.Temp));
MoveTower1 ();

The next step towards an iterable version is to implement the class: 可迭代版本的下一步是实现类:

class HanoiTowerMoves : IEnumerable<Move>
{
  Stack<Move> varStack;
  int n; // number of disks

  public HanoiTowerMoves (int n)
  {
    this.n = n;
    varStack = new Stack<Move> ();
  }

  public IEnumerator<Move> GetEnumerator ()
  {
    // ????????????????????????????  }

  // required by the compiler:
  IEnumerator IEnumerable.GetEnumerator ()
  {
    return GetEnumerator ();
  }
}

Now the big question to me is: what does the body of GetEnumerator () look like? 现在最重要的问题是:GetEnumerator()的主体是什么样的? Can somebody solve this mystery for me? 有人可以为我解开这个谜团吗?

Below is the code of Program.cs of the console application I created. 下面是我创建的控制台应用程序的Program.cs代码。

using System;
using System.Collections.Generic;
using System.Collections;

/* Towers of Hanoi
 * ===============
 * Suppose you have a tower of N disks on needle A, which are supposed to end up on needle B.
 * The big picture is to first move the entire stack of the top N-1 disks to the Temp needle,
 * then move the N-th disk to B, then move the Temp stack to B using A as the new Temp needle.
 * This is reflected in the way the recursion is set up.
 */

namespace ConsoleApplication1
{
  static class main
  {
    static void Main (string [] args)
    {
      int n;
      Console.WriteLine ("Towers of Hanoi");

      while (true)
      {
        Console.Write ("\r\nEnter number of disks: ");

        if (!int.TryParse (Console.ReadLine (), out n))
        {
          break;
        }

        HanoiTowerMoves moves = new HanoiTowerMoves (n);
        moves.Run (1); // algorithm version number, see below
      }
    }
  }

  class Move
  {
    public int N { private set; get; }
    public Needle Start { private set; get; }
    public Needle Finish { private set; get; }
    public Needle Temp { private set; get; }

    public Move (int n, Needle start, Needle finish, Needle temp)
    {
      N = n;
      Start = start;
      Finish = finish;
      Temp = temp;
    }

    public override string ToString ()
    {
      return string.Format ("Moving disk from {0} to {1}", Start, Finish);
    }
  }

  enum Needle { A, B, Temp }

  class HanoiTowerMoves : IEnumerable<Move>
  {
    Stack<Move> varStack;
    int n;            // number of disks

    public HanoiTowerMoves (int n)
    {
      this.n = n;
      varStack = new Stack<Move> ();
    }

    public void Run (int version)
    {
      switch (version)
      {
        case 0: // Original version
          MoveTower0 (n, Needle.A, Needle.B, Needle.Temp);
          break;

        case 1: // No parameters (i.e. argument values passed via stack)
          varStack.Push (new Move (n, Needle.A, Needle.B, Needle.Temp));
          MoveTower1 ();
          break;

        case 2: // Enumeration
          foreach (Move m in this)
          {
            Console.WriteLine (m);
          }

          break;
      }
    }

    void MoveTower0 (int n, Needle start, Needle finish, Needle temp)
    {
      if (n > 0)
      {
        MoveTower0 (n - 1, start, temp, finish);
        Console.WriteLine ("Moving disk from {0} to {1}", start, finish);
        MoveTower0 (n - 1, temp, finish, start);
      }
    }

    void MoveTower1 ()
    {
      Move m = varStack.Pop ();

      if (m.N > 0)
      {
        varStack.Push (new Move (m.N - 1, m.Start, m.Temp, m.Finish));
        MoveTower1 ();
        Console.WriteLine (m);
        varStack.Push (new Move (m.N - 1, m.Temp, m.Finish, m.Start));
        MoveTower1 ();
      }
    }

    public IEnumerator<Move> GetEnumerator ()
    {
      yield break; // ????????????????????????????
    }

    /*
      void MoveTower1 ()
      {
        Move m = varStack.Pop ();

        if (m.N > 0)
        {
          varStack.Push (new Move (m.N - 1, m.Start, m.Temp, m.Finish));
          MoveTower1 ();
          Console.WriteLine (m); ? yield return m;
          varStack.Push (new Move (m.N - 1, m.Temp, m.Finish, m.Start));
          MoveTower1 ();
        }
      }
    */

    // required by the compiler:
    IEnumerator IEnumerable.GetEnumerator ()
    {
      return GetEnumerator ();
    }
  }
}

Your approach is pretty good but I think you are overthinking the problem somewhat. 你的方法很不错,但我认为你在某种程度上过度思考问题。 Let's take a step back. 我们退一步吧。 You have a recursive algorithm: 你有一个递归算法:

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

The algorithm's output is a bunch of console output. 算法的输出是一堆控制台输出。 Suppose instead you wished the algorithm's output to be the sequence of strings that is going to be output to the console. 假设您希望算法的输出是将要输出到控制台的字符串序列。 Let's reason about what such a method would look like. 让我们来看看这种方法会是什么样子。

First off, we'll rename it. 首先,我们将重命名它。 Second, its return type cannot be void. 其次,它的返回类型不能无效。 It must be IEnumerable<string> : 它必须是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);
  } 
}

Is this right? 这是正确的吗? No. We are not returning anything, we're still dumping to the console. 不,我们没有退货,我们仍然倾向于控制台。 What do we wish the iterator to yield? 我们希望迭代器能够产生什么? We wish the iterator to yield: 我们希望迭代器产生:

  • all the moves necessary for the first recursive step 第一个递归步骤所需的所有动作
  • the current move 目前的举动
  • all the moves necessary for the second recursive step 第二个递归步骤所需的所有动作

So we modify the algorithm to yield those: 所以我们修改算法来产生那些:

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

And we're done! 我们完成了! Easy as that. 很简单。 There is no need to be defining a whole class to turn a recursive algorithm into a recursive enumerator; 没有必要定义整个类来将递归算法转换为递归枚举器; let the compiler do that work for you. 让编译器为您完成这项工作。

If you want to change this into a method that enumerates "moves", then do that: 如果要将其更改为枚举“移动”的方法,请执行以下操作:

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

Now, I would criticize this code on the basis of efficiency. 现在,我会在效率的基础上批评这段代码。 By making recursive enumerators in this manner, what you are doing is building a chain of n enumerators. 通过以这种方式创建递归枚举器,您正在做的是构建一个n个枚举器链。 When you need the next item, the top enumerator calls the next enumerator calls the next enumerator... down to the bottom, n deep. 当你需要下一个项目时,顶级枚举器调用下一个枚举器调用下一个枚举器...向下到底,n深。 So each step now actually takes n steps to complete. 所以每个步骤现在实际上需要n步才能完成。 I would be inclined to solve the problem without recursion for that reason. 因为这个原因,我倾向于在没有递归的情况下解决问题。

Exercise : Rewrite the iterator block above so that it does no recursion at all . 练习 :重写上面的迭代器块,以便它没有递归 Your solution that uses an explicit stack is a step in the right direction, but it still does recursion. 使用显式堆栈的解决方案是朝着正确方向迈出的一步,但它仍然会进行递归。 Can you adapt it so that no recursion is done? 你可以调整它,以便不进行递归吗?

If you are bent upon writing a class that implements IEnumerable<Move> then you can adapt the code above in a straightforward way: 如果您一心想编写实现IEnumerable<Move>的类,那么您可以直接调整上面的代码:

class MoveIterator : IEnumerable<Move>
{
    public IEnumerator<Move> GetEnumerator()
    {
        foreach(Move move in MoveTower(whatever))
            yield return move;
    }

You can use yield return to implement a method that returns an enumerator or an enumerable . 您可以使用yield return来实现返回枚举器枚举的方法

Your non-recursive solution is good -- building a pushdown automaton (a state machine with a stack, essentially) is a standard technique for building an iterative version of a recursive solution. 您的非递归解决方案很好 - 构建下推自动机(具有堆栈的状态机,本质上)是构建递归解决方案的迭代版本的标准技术。 And in fact, this is very similar to how we generate code for iterator and async blocks. 事实上,这与我们为迭代器和异步块生成代码的方式非常相似。

However in this specific case you don't need to pull out the heavy machinery of a pushdown automaton with a switch and a current state. 但是在这种特殊情况下,您不需要使用开关和当前状态拉出下推式自动机的重型机械。 You could just do this: 你可以这样做:

IEnumerable<Move> MoveTowerConsole (int size, Needle start, Needle finish, Needle temp) 
{   
  if (size <= 0) yield break;
  var stack = new Stack<Work>();
  stack.Push(new Work(size, start, finish, temp));
  while(stack.Count > 0)
  {
    var current = stack.Pop();
    if (current.Size == 1) 
      yield return new Move(current.Start, current.Finish);
    else
    {
       // Push the work in the *opposite* order that it needs to be done.
       stack.Push(new Work(current.Size - 1, current.Temp, current.Finish, current.Start));
       stack.Push(new Work(1, current.Start, current.Finish, current.Temp));
       stack.Push(new Work(current.Size - 1, current.Start, current.Temp, current.Finish));

     }
} 

You already know exactly what work you need to be doing after the current recursive step, so there's no need to bounce around a switch to put the three bits of work on the stack. 您已经确切知道在当前递归步骤之后需要做什么工作,因此不需要在开关周围跳动以将三位工作放在堆栈上。 Just queue all the work up at once for a given step. 只需为给定步骤排队所有工作。

Non-recursive version: 非递归版本:

// Non-recursive version -- state engine
//rta.Push (State.Exit);
//parameters.Push (new Move (n, Needle.A, Needle.B, Needle.Temp));
//MoveTower3 ();

enum State { Init, Call1, Call2, Rtrn, Exit }

{  
  ...

  #region Non-recursive version -- state engine
  static void MoveTower3 ()
  {
    State s = State.Init;
    Move m = null;

    while (true)
      switch (s)
      {
        case State.Init:
          m = moveStack.Pop ();
          s = (m.n <= 0) ? State.Rtrn : State.Call1;
          break;
        case State.Call1:
          rta.Push (State.Call2); // where do I want to go after the call is finished
          moveStack.Push (m);    // save state for second call
          moveStack.Push (new Move (m.n-1, m.start, m.temp, m.finish)); // parameters
          s = State.Init;
          break;
        case State.Call2:
          m = moveStack.Pop ();  // restore state from just before first call
          Console.WriteLine (m);
          rta.Push (State.Rtrn);
          moveStack.Push (new Move (m.n-1, m.temp, m.finish, m.start));
          s = State.Init;
          break;
        case State.Rtrn:
          s = rta.Pop ();
          break;
        case State.Exit:
          return;
      }
  }
  #endregion

  #region Enumeration
  static IEnumerable<Move> GetEnumerable (int n)
  {
    Stack<Move> moveStack = new Stack<Move> ();
    Stack<State> rta = new Stack<State> (); // 'return addresses'
    rta.Push (State.Exit);
    moveStack.Push (new Move (n, Needle.A, Needle.B, Needle.Temp));
    State s = State.Init;
    Move m = null;

    while (true)
      switch (s)
      {
        case State.Init:
          m = moveStack.Pop ();
          s = (m.n <= 0) ? State.Rtrn : State.Call1;
          break;
        case State.Call1:
          rta.Push (State.Call2); // where do I want to go after the call is finished
          moveStack.Push (m);    // save state for second call
          moveStack.Push (new Move (m.n-1, m.start, m.temp, m.finish)); // parameters
          s = State.Init;
          break;
        case State.Call2:
          m = moveStack.Pop ();  // restore state from just before first call
          yield return m;
          rta.Push (State.Rtrn);
          moveStack.Push (new Move (m.n-1, m.temp, m.finish, m.start));
          s = State.Init;
          break;
        case State.Rtrn:
          s = rta.Pop ();
          break;
        case State.Exit:
          yield break;
      }
  }
  #endregion
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM