views:

146

answers:

3

If I am walking through an IEnumerable<T>, is there any way to get a new IEnumerable<T> representing the remaining items after the current one.

For example, I would like to write an extension method IEnumerator<T>.Remaining():

IEnumerable<int> sequence = ...
IEnumerator<int> enumerator = sequence.GetEnumerator();

if (enumerator.MoveNext() && enumerator.MoveNext()) {
    IEnumerable<int> rest = enumerator.Remaining();
    // 'rest' would contain elements in 'sequence' start at the 3rd element
}

I'm thinking of the collection of a sort of singly-linked list, so there should be a way to represent any remaining elements, right? I don't see any way to do this exposed on either IEnumerable<T> or IEnumerator<T>, so maybe it's incompatible with the notion of a potentially unbounded, nondeterministic sequence of elements.

+2  A: 

Take and Skip are the two methods you want to use:

IEnumerable<int> sequence = ...
IEnumerable<int> pair = sequence.Take(2); //First two elements
IEnumerable<int> remaining = sequence.Skip(2);
Eric Hauser
I think you're missing the point of the question...
jball
I'm aware of these methods, but they require you to know which number element you're at. Suppose I've walked part way through a sequence and want all the rest, I'd rather not have a counter to keep track of how far I've come. (See @jball's comment to the question).
Henry Jackson
+4  A: 

If you must use IEnumerator<T> and not IEnumerable<T> (where all the good extension methods are) here's two simple methods.

This one can only be enumerated a single time (and is bound to the original enumerable, which means you can end up with exceptions if another thread changes the source list):

public static IEnumerable<T> Remaining<T>( this IEnumerator<T> value ) {
    while( value.MoveNext() ) {
        yield return value.Current;
    }
}

And this one builds a list and can be enumerated repeatedly (and is disconnected from the original enumerator so you don't have to worry about your source IEnumerable changing):

public static IEnumerable<T> Remaining<T>( this IEnumerator<T> value ) {
    List<T> list = new List<T>();
    while( value.MoveNext() ) list.Add( value.Current );

    return list;
}
Adam Sills
+2  A: 

If you want to take an IEnumerator<T> and get an IEnumerable<T> representing the rest of the sequence, taken literally, you're going to have to do some magic to get there.

The reason for this is that in general sense, an enumerable can be enumerated multiple times, whereas an enumerator cannot, it is just one of those "multiple times" by itself.

First, you can try to figure out what kind of collection you're dealing with, and thus return an appropriate enumerator on top of the rest of the original enumerator. The reason you're going.

Or... you can just cache the rest of the enumerator into a new collection and return that. This will of course consume your original enumerator, whatever that might be, and could be expensive, in terms of time or memory.

Or... you can do what several have suggested, don't actually return the enumerator, but instead use the Skip and Take methods of the enumerable class to return what you want. This will return a new enumerable, that each time it is enumerated, it will enumerate over the original enumerable, skip the first two items, and produce the rest.

Let me reword that last paragraph. If you don't try to return the rest of the IEnumerator<T> as a new enumerable, but instead just deal with the original collection, it becomes much easier to deal with.

Here's some code that caches the elements. It has the benefit that if you produce 2 or more enumerators (or even just 1) from the resulting enumerable, and then let the enumerable go out of scope, as the enumerators start moving through the elements, it will allow the garbage collector to start collecting the elements that have been passed by.

In other words, if you do this:

var enumerable = enumerator.Remaining();
var enumerator1 = enumerable.GetEnumerator();
var enumerator2 = enumerable.GetEnumerator();

enumerator1.MoveNext();
enumerator2.MoveNext();
<-- at this point, enumerable is no longer used, and the first (head) element
    of the enumerable is no longer needed (there's no way to get to it)
    it can be garbage collected.

Of course, if you keep the enumerable around, and enumerate over all the elements in it, it will produce an in-memory copy of all the elements from the original enumerable, which, as I said, can be be costly.

Anyway, here's the code. It is not thread-safe:

using System;
using System.Collections.Generic;
using System.Collections;

namespace SO2829956
{
    public class EnumeratorEnumerable<T> : IEnumerable<T>
    {
        private class Node
        {
            public T Value;
            public Node Next;
        }

        private class Enumerator : IEnumerator<T>
        {
            private IEnumerator<T> _Enumerator;
            private Node _Current;

            public Enumerator(IEnumerator<T> enumerator, Node headElement)
            {
                _Enumerator = enumerator;
                _Current = headElement;
            }

            public T Current
            {
                get { return _Current.Value; }
            }

            public void Dispose()
            {
                _Enumerator.Dispose();
            }

            object IEnumerator.Current
            {
                get { return Current; }
            }

            public bool MoveNext()
            {
                if (_Current.Next != null)
                {
                    _Current = _Current.Next;
                    return true;
                }
                else if (_Enumerator.MoveNext())
                {
                    _Current.Next = new Node
                    {
                        Value = _Enumerator.Current
                    };
                    _Current = _Current.Next;
                    return true;
                }
                else
                {
                    _Enumerator.Dispose();
                    return false;
                }
            }

            public void Reset()
            {
                throw new NotImplementedException();
            }
        }

        private IEnumerator<T> _Enumerator;
        private Node _FirstElement;

        public EnumeratorEnumerable(IEnumerator<T> enumerator)
        {
            _Enumerator = enumerator;
            _FirstElement = new Node
            {
                Next = null,
                Value = enumerator.Current
            };
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new Enumerator(_Enumerator, _FirstElement);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    public static class EnumeratorExtensions
    {
        public static IEnumerable<T> Remaining<T>(
            this IEnumerator<T> enumerator)
        {
            return new EnumeratorEnumerable<T>(enumerator);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<int> values = new List<int> { 1, 2, 3, 4, 5 };
            IEnumerator<int> enumerator = values.GetEnumerator();
            enumerator.MoveNext();
            enumerator.MoveNext();

            var enumerable = enumerator.Remaining();
            foreach (var i in enumerable)
                Console.Out.WriteLine(i);
            foreach (var i in enumerable)
                Console.Out.WriteLine(i);
        }
    }
}

The output of running this program is:

3
4
5
3
4
5
Lasse V. Karlsen