views:

100

answers:

8

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.

+2  A: 

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

spender
Thanks, but how do you generate those values I posted above?
Joan Venge
I think the OP's use of the word "enumerator" is a little misleading. See my answer.
Rex M
Thanks spender, yeah maybe for string but for even numbers, I would like it to be infinite but your reply is good.
Joan Venge
I'd go with a method that that returns IEnumerable that uses the yield statment, with the caveat of Rex that it's really an abuse because used incorrectly it will never terminate. I'd still go that route, though.
spender
For an "infinite" supply of even numbers (up to the maximum value of Int32 anyway!): Enumerable.Range(0, Int32.MaxValue).Where(n => n % 2 == 0).GetEnumerator(). Or use Int64 if you really need it. If you truly need "infinite" you'll need to get a BigInteger implementation from somewhere and write an iterator method.
itowlson
Thanks, is the Range method gonna create a huge collection every time you call the the variable that stores? Or just for once when you get the enumerator?
Joan Venge
Range isn't generating a huge collection up front, if that's what you mean. The memory usage of range, in this situation, will be minimal. Calling ToList or ToArray on the resulting IEnumerable might be problematic.
spender
Thanks spender. I got it now.
Joan Venge
+5  A: 

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.

Rex M
Thanks so I don't need to implement IEnumerable<T>, right? Can I? If so that would make the singleton shorter but not sure if it's possible. So I could say yield return String.Format ( "Identifier{0}.xml", val++ );
Joan Venge
nice idea but why not remove the "current" part and using it directly in the getnext? so NextNumberGenerator.GetNext();
Fredou
@Joan `IEnumerable` is for iterating over a sequence. It's only generally useful for cases like in a `foreach` - and in this case wouldn't work, since there's no end (the foreach would run forever).@Fredou the singleton pattern makes the class more flexible. Both static and instance uses are possible.
Rex M
Thanks Rex. Would static and instance versions would be different? Or combined?
Joan Venge
Also would this be better called Generator or Provider?
Joan Venge
@Rex: I respectfully disagree. `IEnumerable` does not need to be used in a `foreach`, nor it infinite, even in this case if you take care to use a `break` when you are done with the sequence.
Matthew Scharley
@Matthew That would be circumventing a part of the IEnumerable pattern - the enumerator is responsible for knowing when to stop.
Rex M
@Joan the example I provided is both - it's an instance type with an extra property to return the static instance.
Rex M
Thanks Rex. But the general practice is to have a singleton that has both or one is better? I am probably gonna do only an instance.
Joan Venge
@Joan it depends on your application needs. If you need to increment the count globally, it should be a singleton. If you need localized instances that track their own counts, use an instance in each place.
Rex M
Rex: maybe I've misunderstood you, but I'm not sure how Matthew's comment "circumvents a part of the IEnumerable pattern." IEnumerable doesn't mandate an end, and infinite enumerators are very useful because they allow calling code to impose its own termination condition instead of baking it into the enumerator. E.g. foreach (BigInteger p in AllPrimes().Where(p <= maxValue)) { ... }. The AllPrimes() enumerator never "breaks"; the calling code just stops enumerating it.
itowlson
@itowlson I disagree. The description of IEnumerable is to support forward-only read access to a collection or sequence of items. To me that implies a finite set. In programming a structure which *literally* represents an infinite set - e.g. it will quite happily keep going forever unless some other piece of code intervenes - seems like an antipattern to me.
Rex M
@Rex I disagree, an infinite set is still a legitimate set, which can be legitimately iterated upon.
phoebus
A: 

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.

SLaks
+2  A: 
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();
}
Matthew Scharley
You beat me to it by 30 seconds!
XXXXX
Thanks Matthew. How do I use this in multiple places? Like GetFileNames().Next in method0, later inside method1 GetFileNames().Next, and then inside method2, etc? Sort of different parts of the code is asking for the next element.
Joan Venge
I'm not sure how this is a good idea - doing n I/O operations for each iteration?
Rex M
@Rex: I assumed the first example was going to be used to find a filename that doesn't exist yet. How else would you go about that?
Matthew Scharley
@Matthew preexisting filenames and such are speculative, since we don't know the details of the application. The OP only asked for a good way to (globally I think) advance a value each time it's retrieved. In your example I would find the first lowest available filename once, and then increment it internally and only do another I/O operation to see if safe to save. That's 1 I/O operation per filename instead of prev+=n
Rex M
If you're saving lots of files, save the IEnumerable outside the loop and manipulate it manually, and you get this behaviour. If you're just looking for a single file, use foreach and save yourself the trouble. I fail to see the issue here.
Matthew Scharley
@Matthew I think we're missing each other. In your foreach case, it finds the first available filename. Let's say we eventually end up using that 10 times. The first time, it will find 01. The 2nd time, it will go through 2. The 5th it will go through 5, and by 10, 10. My other thought is that if you want to use it outside a Foreach (e.g. just IEnumerator.MoveNext) then using IEnumerable at all is unnecessary - just using it because you need to move next and one of its methods is called MoveNext. (Also check your code - I think you may have confused IEnumerator and IEnumerable)
Rex M
Using IEnumerable means your code is flexible and can be used in a loop or not.
Matthew Scharley
+2  A: 
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);
280Z28
Thanks, is the Range method gonna create a huge collection every time you call the numberEnumerator? Or just for once?
Joan Venge
It's a lazy enumeration (it generates the elements only as they're needed).
280Z28
Thanks but it still has to evaluate all of Range before it can go to Select, right? I mean when it's called?
Joan Venge
No, it just creates another lazy enumerable that wraps the first one.
Matthew Scharley
A: 

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
XXXXX
+1  A: 

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.

SLaks
Thanks but if you use return String.Format(CultureInfo.InvariantCulture, "Indentifier{0:00}.xml", nextNumber++); then you would need to initialize at -1, right?
Joan Venge
No, that would start counting at 0 though. If you want to start from 1 and code like that, use `++nextNumber`
Matthew Scharley
@Matthew: I incremented `nextNumber` in a separate line, so you are wrong. Had the method been only one line, you'd have been right.
SLaks
@Joan: Wrong. If you write `return String.Format(CultureInfo.InvariantCulture, "Indentifier{0:00}.xml", ++nextNumber)` (prefix, not suffix), then you'd need to start at -1.
SLaks
Sorry, I meant you wouldnt need to start from -1, as opposed to what you said in your post, to make it simple.
Joan Venge
+1  A: 

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