views:

171

answers:

6

I am having a difficult time with a seemingly easy and embarrassing problem. All I want is the next element in an IEnumberable without using Skip(1).Take(1).Single(). This example illustrates the basic problem.

private char _nextChar;
private IEnumerable<char> getAlphabet()
{
    yield return 'A';
    yield return 'B';
    yield return 'C';
}
public void sortAlphabet()
{
     foreach (char alpha in getAlphabet())
     {
         switch (alpha)
         {
             case 'A':  //When A pops up, I want to get the next element, ie 'B'
                 _nextChar = getAlphabet().Skip(1).Take(1).Single();
                 break;
             case 'B': //When B pops up, I want 'C' etc
                 _nextChar = getAlphabet().Skip(1).Take(1).Single();
                 break;
         }
     }
}

Other than being ugly, this example works. But let's say that the IEnumerable contained 2 million elements, then the LINQ statement makes the program execute unbearably slow. What I want is simple. I just want the next element in an IEnumberable<>. All my problems would be solved if there was a function like:

_nextChar = getAlphabet().moveNext() //or getNext()

It is much preferred if the solution keeps the same structure/layout/functionality of the example however, I am flexible. My program is a file parser, and among the 2 million lines of text are some keys like "money=324" where "money" and "324" are neighbor elements in the IEnumberable and when the parser comes across "money" I want "324". (who doesn't? :D Sorry for bad pun.)

+4  A: 

I reckon you want this:

    public void sortAlphabet() {
        using (var enu = getAlphabet().GetEnumerator()) {
            while (enu.MoveNext()) {
                switch (enu.Current) {
                    case 'A':
                        enu.MoveNext();
                        _nextChar = enu.Current;
                        break;
                }
            }
        }
    }

Note that this consumes the next element, just what you want if I read your question right.

Hans Passant
Should really use `using` around `GetEnumerator`...
Timwi
Yes, the motherboard will catch fire if you forget that.
Hans Passant
Always. The generic IEnumerable<> interface inherits from IDisposable.
SealedSun
@Timwi, @SealedSun: In theory, yes. In practice, most `IEnumerator<T>` implementations don't really *do* anything in their `Dispose` method; it's just there in case it's needed. Obviously it's good practice to use `using` when you're dealing with some arbitrary `IEnumerator<T>` type; but I think Hans's original answer was really not problematic given that in the example code provided, the type of enumerator is actually known, and it is clear that the `Dispose` method would do nothing for that type.
Dan Tao
+11  A: 

All my problems would be solved if there was a function like:

_nextChar = getAlphabet().moveNext() //or getNext()

There is a function exactly like that. It just belongs to IEnumerator<T>, not IEnumerable<T>!

private char _nextChar;
private IEnumerable<char> getAlphabet()
{
    yield return 'A';
    yield return 'B';
    yield return 'C';
}

public void sortAlphabet()
{
    using (var enumerator = getAlphabet().GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            char alpha = enumerator.Current;
            switch (alpha)
            {
                case 'A':
                    if (enumerator.MoveNext())
                    {
                        _nextChar = enumerator.Currrent;
                    }
                    else
                    {
                        // You decide what to do in this case.
                    }
                    break;
                case 'B':
                    // etc.
                    break;
            }
        }
    }
}

Here's a question for you, though. Is it necessary that this code use an IEnumerable<char>, rather than an IList<char>? I ask because, as if this weren't obvious, the code would be much simpler if you had random access to the items returned by getAlphabet by index (and if someone is tempted to point out that you can do this with ElementAt, please, just get that idea out of your head right now).

I mean, consider what the code would look like in this case:

private char _nextChar;
private IList<char> getAlphabet()
{
    return Array.AsReadOnly(new[] { 'A', 'B', 'C' });
}

public void sortAlphabet()
{
    IList<char> alphabet = getAlphabet();
    for (int i = 0; i < alphabet.Count - 1; ++i)
    {
        char alpha = alphabet[i];
        switch (alpha)
        {
            case 'A':
                _nextChar = alphabet[i + 1];
                break;
            case 'B':
                // etc.
                break;
        }
    }
}

Isn't that much easier?

Dan Tao
Your first answer is exactly what I am looking for (sometimes a fresh outlook on something is what one is looking for). However, I am slightly confused by your suggestion. I do not want random access. I want, in this case, the letters as they come. And, I may be wrong, isn't getAlphabet() holding the entire contents in memory? I don't want that, as in my case getAlphabet() would holding 2 million lines.
Nick Babcock
Ah I just realized that there could be some confusion. The alphabet thing was just an example of my problem. In reality I don't do anything with the alphabet or char for that matter. If wished I can provide relevant code, but you already answered my question.
Nick Babcock
@Nick: No need for further clarification. Even shortly after posting my suggestion I remembered that you had said 2 million items; so obviously storing the contents in memory would not make sense in your case. I'm leaving the idea up there, though, in case anyone else finds him-/herself facing a similar problem in a scenario where accessing the members of the collection by index might actually be an option.
Dan Tao
+1  A: 

As was pointed out in another answer, there is a MoveNext() method, and you have access to it for all enumerables via the IEnumerator<T> interface returned by a call to IEnumerable<T>.GetEnumerator(). However, working with MoveNext() and Current can feel somewhat "low-level".

If you'd prefer a foreach loop to process your getAlphabet() collection, you could write an extension method that returns elements from any enumerable in pairs of two:

public static IEnumerable<T[]> InPairsOfTwo<T>(this IEnumerable<T> enumerable)
{
    if (enumerable.Count() < 2) throw new ArgumentException("...");

    T lastItem = default(T);
    bool isNotFirstIteration = false;

    foreach (T item in enumerable)
    {
        if (isNotFirstIteration)
        {
            yield return new T[] { lastItem, item };
        }
        else
        {
            isNotFirstIteration = true;
        }
        lastItem = item;
    }
}

You'd use it as follows:

foreach (char[] letterPair in getAlphabet().InPairsOfTwo())
{
    char currentLetter = letterPair[0],
         nextLetter    = letterPair[1];        

    Console.WriteLine("#  {0}, {1}", currentLetter, nextLetter);
}

And you'd get the following output:

#  A, B
#  B, C

(Note that while the above extension method returns pairs of two items each, the pairs overlap by one item! You essentially get each item as well as a look-ahead. If you wanted the extension method to return the last item by itself, you could adapt it by adapting the buffering method used.)

stakx
A: 

As alluded to earlier, a state machine is ideally suited for these cases. It also matches the way we think about the problem, so readability is excellent. I'm not sure exactly what you want to do with the code, so the example below returns the next character when it encounters it. The state machine scales well to complex tasks, and can be modeled and hand-checked on paper.

enum State
{
    Scan,
    SaveAndExit
};

public void SortAlphabet()
{
    State state = State.Scan; // initialize

    foreach(char c in getAlphabet())
    {
        switch (state):
        {
            case State.Scan:
                if (c == 'A' ||
                    c == 'B')
                    state = State.SaveAndExit;
                break;
            case State.SaveAndExit:
                return (c);
                break;
        }
    }
}
bright
A: 

Your code will return 'B' every time, because you call getAlphabet(), which returns a new IEnumerable each time.

Based on what you're trying to do, I'd probably suggest using index-based iteration instead of an enumerator. If you used MoveNext to get the next element, you'd mess up your loop, so using an index-based retrieval would work more cleanly with much less overhead.

Dan Puzey
A: 

If you're using .NET 4.0 then what you're trying to achieve is very simple:

var alphabet = getAlphabet();
var offByOneAlphabet = alphabet.Skip(1);

foreach (var pair in alphabet.Zip(offByOneAlphabet, (a, b) => Tuple.Create(a, b)))
    Console.WriteLine("Letter: {0}, Next: {1}", pair.Item1, pair.Item2);

// prints:
//    Letter: A, Next: B
//    Letter: B, Next: C

If you using anything less than .NET 4.0, its still very easy to define your own Zip function and Tuple class.

Juliet