【问题标题】:How to create a enumerator/generator of strings or integers?如何创建字符串或整数的枚举器/生成器?
【发布时间】:2009-10-21 01:22:33
【问题描述】:

我想要一个枚举器/生成器,每当我调用 GetNext 时,它总是会为我提供下一个值?这可能吗?

Examples:

myStringEnumerator.GetNext()
  -> returns "Identifier01.xml"
myStringEnumerator.GetNext()
  -> returns "Identifier02.xml"
myStringEnumerator.GetNext()
  -> returns "Identifier03.xml"
myStringEnumerator.GetNext()
  -> returns "Identifier04.xml"
myStringEnumerator.GetNext()
  -> returns "Identifier05.xml"
...

evenNumberGenerator.GetNext()
  -> returns 0
evenNumberGenerator.GetNext()
  -> returns 2
evenNumberGenerator.GetNext()
  -> returns 4
evenNumberGenerator.GetNext()
  -> returns 6
evenNumberGenerator.GetNext()
  -> returns 8
evenNumberGenerator.GetNext()
  -> returns 10
...

我不会在一个地方迭代它,而是在很多地方和非常不同的时间迭代。

如何以最优雅的方式做到这一点?如果它也很快,那也很好。

【问题讨论】:

    标签: c# .net collections ienumerable


    【解决方案1】:

    “枚举数”一词在这里有点误导,因为这是一种特定类型的序列,它有开始和结束。在您的情况下,您并没有枚举任何内容,而只是无限期地推进到下一个值。 singleton 听起来很有用:

    class NextNumberGenerator
    {
        private static NextNumberGenerator instance = new NextNumberGenerator();
    
        public static NextNumberGenerator Current
        {
            get { return instance; }
        }
    
        private int currentNumber = 0;
    
        public int GetNext()
        {
            return ++currentNumber;
        }
    
        public int GetNextEven()
        {
            currentNumber++;
            return (currentNumber % 2 == 0) ? currentNumber : ++currentNumber;
        }
    
        public int GetNextOdd()
        {
            currentNumber++;
            return (currentNumber % 2 != 0) ? currentNumber : ++currentNumber;
        }
    }
    

    并用作:

    int value = NextNumberGenerator.Current.GetNext();
    

    编辑:一些评论者指出完全可以使用 IEnumerable。但是,我认为这是对 IEnumerable 的滥用,因为这种情况和这个情况在某些方面看起来很相似,但并不相同。使用一个非常具体、有目的的界面,因为它所做的多项事情中的一项与您需要的事情相似(不一样)会回来咬您。

    【讨论】:

    • 谢谢,所以我不需要实现 IEnumerable,对吧?我可以吗?如果是这样,那会使单例更短,但不确定是否可能。所以我可以说 yield return String.Format ("Identifier{0}.xml", val++);
    • 好主意,但为什么不删除“当前”部分并直接在 getnext 中使用它呢?所以 NextNumberGenerator.GetNext();
    • @Rex:我不同意。 IEnumerable 不需要在 foreach 中使用,也不是无限的,即使在这种情况下,如果您在完成序列后小心使用 break
    • Rex:也许我误解了你,但我不确定 Matthew 的评论如何“绕过 IEnumerable 模式的一部分”。 IEnumerable 不强制结束,无限枚举器非常有用,因为它们允许调用代码强加自己的终止条件,而不是将其烘焙到枚举器中。例如。 foreach (BigInteger p in AllPrimes().Where(p
    • @Rex 我不同意,无限集仍然是合法集,可以合法地对其进行迭代。
    【解决方案2】:
    var stringEnumerator = Enumerable.Range(1, 99).Select(i => string.Format("Identifier{0:00}.xml", i));
    var numberEnumerator = Enumerable.Range(0, int.MaxValue/2).Select(i => 2*i);
    

    【讨论】:

    • 谢谢,Range 方法是否会在您每次调用 numberEnumerator 时创建一个巨大的集合?还是只有一次?
    • 这是一个惰性枚举(它只在需要时生成元素)。
    • 谢谢,但它仍然需要评估所有 Range,然后才能转到 Select,对吗?我的意思是它什么时候被调用?
    • 不,它只是创建了另一个惰性枚举来包装第一个。
    【解决方案3】:

    它是 .net 的一部分

    IEnumerable<T>.GetEnumerator()
    

    http://msdn.microsoft.com/en-us/library/s793z9y2.aspxhttp://msdn.microsoft.com/en-us/library/78dfe2yb.aspx

    编辑:

    因此,使用一点 Linq 魔法,您的代码可能是:

    var myStringEnumerator= Enumerable
                            .Range(1,99)
                            .Select(n=>"Identifier"+n.ToString("00")+".xml")
                            .GetEnumerator();
    if(myStringEnumerator.MoveNext())
        //myStringEnumerator.Current;
    

    我相信你可以自己解决剩下的问题。

    如果你真的需要一个“无限”枚举,使用 yield 语句可能也很合适,但你的字符串格式让我认为 99 是最大值

    【讨论】:

    • 谢谢,但是您如何生成我在上面发布的那些值?
    • 我认为 OP 对“枚举器”一词的使用有点误导。看我的回答。
    • 谢谢spender,是的,也许是字符串,但对于偶数,我希望它是无限的,但你的回复很好。
    • 对于偶数的“无限”供应(直到 Int32 的最大值!): Enumerable.Range(0, Int32.MaxValue).Where(n => n % 2 == 0).GetEnumerator()。或者如果您真的需要它,请使用 Int64。如果你真的需要“无限”,你需要从某个地方获取一个 BigInteger 实现并编写一个迭代器方法。
    • Range 不会预先生成大量集合,如果这就是您的意思的话。在这种情况下,范围的内存使用量将是最小的。在生成的 IEnumerable 上调用 ToList 或 ToArray 可能会出现问题。
    【解决方案4】:
    public IEnumerable<string> GetFileNames()
    {
        for (int i = 1; i <= 99; i++)
        {
            yield return string.Format("Identifier{0:00}.xml", i);
        }
    }
    

    并使用它:

    string filename;
    foreach (string tmp in GetFileNames())
    {
        if (!File.Exists(tmp))
        {
            filename = file;
            break;
        }
    }
    

    要在没有 foreach 的情况下使用它,你可以这样做:

    IEnumerator<string> names = GetFileNames().GetEnumerator();
    names.MoveNext();
    string name1 = names.Current;
    names.MoveNext();
    string name2 = names.Current;
    // etc, etc.
    

    为了满足 Rex M:

    IEnumerator<string> filenames = GetFileNames().GetEnumerator();
    while (filenames.MoveNext())
    {
        if (!File.Exists(filenames.Current))
            break;
    }
    
    while(haveFilesToWrite)
    {
        // Use filenames.Current to open a file and write it
        filenames.MoveNext();
    }
    

    【讨论】:

    • 你比我快 30 秒!
    • 谢谢马修。如何在多个地方使用它?就像方法0中的GetFileNames().Next,稍后在method1中GetFileNames().Next,然后在method2中,等等?代码的不同部分要求下一个元素。
    • 我不确定这是个好主意 - 每次迭代都执行 n 个 I/O 操作?
    • @Rex:我假设第一个示例将用于查找尚不存在的文件名。否则你会怎么做?
    • @Matthew 预先存在的文件名等都是推测性的,因为我们不知道应用程序的详细信息。 OP 只要求一种好方法来(我认为在全球范围内)每次检索一个值时提升它。在您的示例中,我会找到第一个最低的可用文件名一次,然后在内部递增它,并且只执行另一个 I/O 操作以查看是否可以安全保存。这是每个文件名的 1 个 I/O 操作,而不是 prev+=n
    【解决方案5】:

    这样简单的事情怎么样?

    public partial class Form1 : Form
    {
        private void Form1_Load(object sender, EventArgs e)
        {
            myGen myStringEnumerator = new myGen(myGen.wanted.nextNum);
            myGen evenNumberGenerator = new myGen(myGen.wanted.EvenNum);
    
            for (int i = 0; i <= 5; i++)
            {
                MessageBox.Show(string.Format("Identifier{0}.xml", myStringEnumerator.GetNext().ToString("00")));
            }
    
            for (int i = 0; i <= 5; i++)
            {
                MessageBox.Show(evenNumberGenerator.GetNext().ToString());
            }
        }
    }
    
    public class myGen
    {
        private int num = 0;
        public enum wanted
        {
            nextNum,
            EvenNum,
            OddNum
        }
        private wanted _wanted;
    
        public myGen(wanted wanted)
        {
            _wanted = wanted;
        }
    
        public int GetNext()
        {
            num++;
            if (_wanted == wanted.EvenNum)
            {
                if (num % 2 == 1)
                {
                    num++;
                }
            }
            else if (_wanted == wanted.OddNum)
            {
                if (num % 2 == 0)
                {
                    num++;
                }
            }
            return num;
        }
    }
    

    【讨论】:

      【解决方案6】:

      听起来你实际上想要一个静态函数,像这样:

      static class FileNames {
          static int nextNumber;
      
          public static string NextName() {
              nextNumber++;
              return String.Format(CultureInfo.InvariantCulture, "Indentifier{0:00}.xml", nextNumber);
          }
      }
      

      请注意,这将从一开始,而不是零。要使其从零开始,请将 nextNumber 初始化为 -1。 编辑:或者,删除第一行 (nextNumber++) 并将第二行中的参数更改为 ++nextNumber

      另外,并不是说这不是线程安全的。如果要在多个线程上调用它,请使用Interlocked.Increment

      【讨论】:

      • 谢谢,但如果你使用 return String.Format(CultureInfo.InvariantCulture, "Indentifier{0:00}.xml", nextNumber++);那么你需要在 -1 初始化,对吧?
      • 不,那会从 0 开始计数。如果您想从 1 开始并编写类似的代码,请使用 ++nextNumber
      • @Matthew:我在单独的行中增加了nextNumber,所以你错了。如果方法只有一行,那你就对了。
      • @Joan:错了。如果你写return String.Format(CultureInfo.InvariantCulture, "Indentifier{0:00}.xml", ++nextNumber)(前缀,而不是后缀),那么你需要从-1开始。
      • 对不起,为了简单起见,我的意思是你不需要从 -1 开始,而不是你在帖子中所说的。
      【解决方案7】:

      枚举器很不错,因为您可以在 foreach 循环中使用它们,并使用 LINQ 以各种有趣的方式来玩弄它们。

      这是一个枚举器的示例,它执行您在原始问题中谈到的那种事情:

      static class MyEnumerators 
      {
          IEnumerable <string> NumberedStringEnumerator (string format, int start, int count)
          {
              for ( int n = 0; n < count; ++n )
                  yield return string.Format (format, start + n);
          }
      }
      
      static void Main ()
      {
          foreach ( string s in NumberedStringEnumerator ("Identifier{0:2D}.xml", 1, 5) )
              Console.WriteLine (s);
      }
      

      将产生:

      Identifier01.xml
      Identifier02.xml
      Identifier03.xml
      Identifier04.xml
      Identifier05.xml
      

      【讨论】:

        【解决方案8】:

        最简单的方法是使用iterators。例如:

        public static IEnumerator<int> GetNumbers() {
            for (int i = 0; i < 25; i += 3)
                yield return i;
        }
        
        //You can also do it without a loop, like this:
        public static IEnumerator<string> GetNames() {
            yield return "Identifier01.xml";
            yield return "Identifier02.xml";
            yield return "Identifier03.xml";
            yield return "Identifier04.xml";
            yield return "Identifier05.xml";
        }
        

        您也可以返回 IEnumerable 而不是 IEnumerator,而无需更改返回类型以外的任何内容。 IEnumerable 是一个创建 IEnumerators 枚举它的对象,并且可以在foreach 循环中使用。请注意,如果您返回一个 IEnumerable,您的代码将在每次枚举时再次运行(由不同的枚举器)。


        要在不同的方法中使用它,您可以在静态字段中在它们之间共享一个 IEnumerator。

        【讨论】:

          猜你喜欢
          • 2023-03-04
          • 1970-01-01
          • 1970-01-01
          • 2014-06-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多