开发者

How to create a enumerator/generator of strings or integers?

I want to have a enumerator/generator that will always provide me with the next value whenever I call say GetNext? Is this possible?

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
...

I will not iterator on it in 1 place, but rather in many places and in very different times.

How do I do this in the most elegant way? If it's also fast, that's good too.


The term "enumerator" is a little misleading here, since that's a specific type of sequence which has a beginning and an end. In your case, you are not enumerating anything per say, but just advancing to the next value indefinitely. Sounds like a good use for a 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;
    }
}

And used as:

int value = NextNumberGenerator.Current.GetNext();

Edit: some commenters have pointed out that it's entirely possible to use an IEnumerable. However, I argue that's a misuse of IEnumerable simply because that case and this one appear similar in certain ways, but are not the same. Using an extremely specific, purposeful interface because one of the multiple things it does is similar (not same) to the thing you need will come back to bite you.


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


It's part of .net

IEnumerable<T>.GetEnumerator()

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

EDIT:

So with a bit of Linq magic, your code could be:

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

I'm sure you can work the rest out for yourself.

If you truely need an "infinite" enumeration, using the yield statement might also be a good fit, but the formatting of your string makes me think that 99 is a maximum


public IEnumerable<string> GetFileNames()
{
    for (int i = 1; i <= 99; i++)
    {
        yield return string.Format("Identifier{0:00}.xml", i);
    }
}

And to use it:

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

To use it without a foreach, then you can do so like this:

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

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


how about a simple thing like this?

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


It sounds like you actually want a static function, like this:

static class FileNames {
    static int nextNumber;

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

Note that this will start with one, not zero. To make it start with zero, initialize nextNumber to -1. EDIT: Or, remove the first line (nextNumber++) and change the parameter in the second line to ++nextNumber.

Also, not that this is not thread-safe. If you want to call it on multiple threads, use Interlocked.Increment.


Enumerators are nice because you can use them in foreach loops, and play around with them using LINQ in all sorts of fun ways.

Here's an example of an Enumerator doing the sort of thing you talk about in your original question:

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

will produce:

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


The simplest way to do this is to use iterators. For example:

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

You can also return an IEnumerable instead of an IEnumerator without chaning anything but the return type. An IEnumerable is an object that creates IEnumerators that enumerate over it, and can be used in foreach loops. Note that if you return an IEnumerable, your code will run again every time one it is enumerated (by different enumerators).


To use it in different methods, you could share an IEnumerator between them in a static field.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜