C#如何制作的GetEnumerator的递归版本() [英] C# How to make a recursive version of GetEnumerator()

查看:210
本文介绍了C#如何制作的GetEnumerator的递归版本()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有人可以给我如何创建的GetEnumerator的递归版本()的意见?
中的公知的塔的可作为即相当于一个例子实际的问题,我有。一种简单的方法来显示所有动作的高度为n的磁盘堆栈是:



 无效MoveTower0(INT N,针开始,针完成,针临时)
{
如果(N大于0)
{
MoveTower0(N - 1,启动,温度,终点);
Console.WriteLine(移动从{0} {1}盘,启动完成);
MoveTower0(N - 1,温度,光洁度高,启动);
}
}



我真正想要做的是建立一个类HanoiTowerMoves实现IEnumerable并且能够让我遍历所有的动作如下:

 的foreach(移动在HanoiTowerMoves米)Console.WriteLine (米); 



走向的GetEnumerator()实现的第一步,似乎摆脱了MoveTower参数。这可以很容易地通过使用堆栈来完成。我还介绍了一类移动相结合的参数为单一变量。

 类移动
{
公众诠释N {私人集;得到; }
公众针开始{私人集;得到; }
公众针完成{私人集;得到; }
公众针温度{私人集;得到; }

公众移动(INT N,针开始,结束针,针临时)
{
N = N;
开始=启动;
完成=完成;
温度=温度;
}

公共重写字符串的ToString()
{
返回的String.Format(从磁盘移动{0} {1},开始,结束) ;
}
}

现在MoveTower可以如下重写:

 无效MoveTower1()
{
移动M = varStack.Pop();

如果(M.N大于0)
{
varStack.Push(新移动(M.N - 1,m.Start,m.Temp,m.Finish));
MoveTower1();
Console.WriteLine(米);
varStack.Push(新移动(M.N - 1,m.Temp,m.Finish,m.Start));
MoveTower1();
}
}

这版本必须调用如下:

  varStack.Push(新移动(N,Needle.A,Needle.B,Needle.Temp)); 
MoveTower1();



对可迭代版本的下一步是实现类:

 类HanoiTowerMoves:IEnumerable的<移动和GT; 
{
堆栈<移动和GT; varStack;
INT N; //磁盘

公共HanoiTowerMoves(INT N)
{
this.n = N的数量;
varStack =新的堆栈<移动和GT; ();
}

公众的IEnumerator<移动和GT;的GetEnumerator()
{
// ???????????????????????????? }

//编译器要求:
的IEnumerator IEnumerable.GetEnumerator()
{
返回的GetEnumerator();
}
}

现在对我来说最大的问题是:什么是的GetEnumerator的身体()是什么样子?
谁能解开这个谜我吗?



下面是我创建的控制台应用程序的Program.cs文件的代码。

 使用系统; 
使用System.Collections.Generic; System.Collections中使用
;

/ *河内
的塔* ===============
*假设你有针A N盘塔,这是为了最终在针B.
*的大画面是先前N-1盘的整个堆栈移动到Temp针,
*表则第N磁盘移动到B,然后将临时堆栈中使用作为新的临时针B点。
*的这反映在递归设置的方式。
* /

命名ConsoleApplication1
{
静态类主要
{
静态无效的主要(字串[] args)
{
INT N;
Console.WriteLine(河内塔);

,而(真)
{
Console.Write(磁盘\r\\\
Enter数:);

如果
{
断点(int.TryParse(到Console.ReadLine(),出N)!);
}

HanoiTowerMoves移动=新HanoiTowerMoves(N);
moves.Run(1); //算法版本号,见下文
}
}
}

级移至
{
公众诠释N {私人集;得到; }
公众针开始{私人集;得到; }
公众针完成{私人集;得到; }
公众针温度{私人集;得到; }

公众移动(INT N,针开始,结束针,针临时)
{
N = N;
开始=启动;
完成=完成;
温度=温度;
}

公共重写字符串的ToString()
{
返回的String.Format(从磁盘移动{0} {1},开始,结束) ;
}
}

针枚举{A,B,温度}

类HanoiTowerMoves:IEnumerable的<移动和GT;
{
堆栈<移动和GT; varStack;
INT N; //磁盘

公共HanoiTowerMoves(INT N)
{
this.n = N的数量;
varStack =新的堆栈<移动和GT; ();
}

公共无效运行(INT版)
{
开关(版本)
{
的情况下0://原始版本
MoveTower0(N,Needle.A,Needle.B,Needle.Temp);
中断;

案例1://没有参数(通过堆栈传递,即参数值)
varStack.Push(新移动(N,Needle.A,Needle.B,Needle.Temp));
MoveTower1();
中断;

案例2://枚举
的foreach(在这个移动米)
{
Console.WriteLine(米);
}

中断;
}
}

空隙MoveTower0(中间体N,针开始,针光洁度,针温度)
{
如果(N 0)
{
MoveTower0(N - 1,启动,温度,终点);
Console.WriteLine(移动从{0} {1}盘,启动完成);
MoveTower0(N - 1,温度,光洁度高,启动);
}
}

无效MoveTower1()
{
移动M = varStack.Pop();

如果(M.N大于0)
{
varStack.Push(新移动(M.N - 1,m.Start,m.Temp,m.Finish));
MoveTower1();
Console.WriteLine(米);
varStack.Push(新移动(M.N - 1,m.Temp,m.Finish,m.Start));
MoveTower1();
}
}

公众的IEnumerator<移动和GT;的GetEnumerator()
{
产量突破; // ????????????????????????????
}

/ *
无效MoveTower1()
{
移动M = varStack.Pop();

如果(M.N大于0)
{
varStack.Push(新移动(M.N - 1,m.Start,m.Temp,m.Finish));
MoveTower1();
Console.WriteLine(米); ?产生回报米;
varStack.Push(新移动(M.N - 1,m.Temp,m.Finish,m.Start));
MoveTower1();
}
}
* /

//编译器要求:
的IEnumerator IEnumerable.GetEnumerator()
{
返回的GetEnumerator();
}
}
}


解决方案

您的方法是不错,但我认为你得太多有点问题。让我们退后一步。你有一个递归算法:

 无效MoveTowerConsole(INT N,针开始,针结束,针临时)
{
如果(N大于0)
{
MoveTowerConsole(N - 1,启动,温度,终点);
Console.WriteLine(移动从{0} {1}盘,启动完成);
MoveTowerConsole(N - 1,温度,光洁度高,启动);
}
}



该算法的输出是一堆控制台输出。假设,而不是你希望算法的输出为的这将是输出到控制台的字符串序列。的让我们有理由什么这样的方法会是什么样子。



首先,我们将其重命名。其次,它的返回类型不能为空。它必须是的IEnumerable<串>

 的IEnumerable<串> MoveTower(INT N,针开始,针结束,针临时)
{
如果(N大于0)
{
MoveTower(N - 1,启动,温度,光洁度);
Console.WriteLine(移动从{0} {1}盘,启动完成);
MoveTower(N - 1,温度,光洁度高,启动);
}
}



这是正确的?不,我们不返回任何东西,我们仍然倾销到控制台。 ?什么是我们想要的迭代器产生:我们希望迭代产生:




  • 所有的动作必要第一递归步骤

  • 当前移动

  • 所有的动作要求第二递归步骤



所以,我们修改了算法产生的:

 的IEnumerable<串> MoveTower(INT N,针开始,针结束,针临时)
{
如果(N大于0)
{
的foreach(在MoveTower字符串移动(N - 1,开始,温度,光洁度))
收益回报的举动;
收益回报的String.Format(移动从{0} {1}盘,启动完成);
的foreach(在MoveTower(N串动 - 1,温度,光洁度,开始))
收益回报的举动;
}
}

和我们就大功告成了!简单的作为。没有必要为限定一整类转递归算法成递归枚举; 。让编译器为你做这项工作。



如果您想更换成枚举动作的方法这一点,那么做到这一点:

 的IEnumerable<移动和GT; MoveTower(INT N,针开始,针结束,针临时)
{
如果(N大于0)
{
的foreach(在MoveTower动动(N - 1,开始,温度,光洁度))
收益回报的举动;
收益回报新移动(启动,完成);
的foreach(动动在MoveTower(N - 1,温度,光洁度,开始))
收益回报的举动;
}
}

现在,我会批评的基础上,该代码效率。通过以这种方式使递归统计员,你在做什么是构建N统计员链。当你需要的下一个项目,顶部枚举调用下一个枚举器调用下一个枚举......下到谷底,正深。所以,每一个步骤,现在其实需要n步来完成。我会倾向于解决问题,而不递归出于这个原因



练习:重写上面的迭代器块,这样它确实没有递归所有的。使用一个明确的堆栈解决方案是向正确方向迈出的一步,但它仍然递归。 ?你能适应它,这样没有递归完成



如果你在写一个类实现的IEnumerable<弯曲;移动> 那么您可以在一个简单的方式适应上面的代码:

 类MoveIterator:IEnumerable的<移动> 
{
公众的IEnumerator<移动和GT;的GetEnumerator()
{
的foreach(移动在MoveTower(无论)移动)
收益回报的举动;
}

您可以使用收益的回报来实现,返回一个枚举的方法的的枚举


Can somebody give me advice on how to create a recursive version of 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:

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:

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

The first step towards a GetEnumerator() implementation seems to get rid of the MoveTower parameters. This can easily be done by using a stack. I also introduced a class Move that combines the parameters into a single variable.

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:

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? Can somebody solve this mystery for me?

Below is the code of Program.cs of the console application I created.

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> 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. When you need the next item, the top enumerator calls the next enumerator calls the next enumerator... down to the bottom, n deep. So each step now actually takes n steps to complete. 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:

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.

这篇关于C#如何制作的GetEnumerator的递归版本()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆