Search code examples
c#linqienumerable

Getting head and tail from IEnumerable that can only be iterated once


I have a sequence of elements. The sequence can only be iterated once and can be "infinite".

What is the best way get the head and the tail of such a sequence?

Update: A few clarifications that would have been nice if I included in the original question :)

  • Head is the first element of the sequence and tail is "the rest". That means the the tail is also "infinite".

  • When I say infinite, I mean "very large" and "I wouldn't want to store it all in memory at once". It could also have been actually infinite, like sensor data for example (but it wasn't in my case).

  • When I say that it can only be iterated once, I mean that generating the sequence is resource heavy, so I woundn't want to do it again. It could also have been volatile data, again like sensor data, that won't be the same on next read (but it wasn't in my case).


Solution

  • Decomposing IEnumerable<T> into head & tail isn't particularly good for recursive processing (unlike functional lists) because when you use the tail operation recursively, you'll create a number of indirections. However, you can write something like this:

    I'm ignoring things like argument checking and exception handling, but it shows the idea...

    Tuple<T, IEnumerable<T>> HeadAndTail<T>(IEnumerable<T> source) {
      // Get first element of the 'source' (assuming it is there)
      var en = source.GetEnumerator();
      en.MoveNext();
      // Return first element and Enumerable that iterates over the rest
      return Tuple.Create(en.Current, EnumerateTail(en));
    }
    
    // Turn remaining (unconsumed) elements of enumerator into enumerable
    IEnumerable<T> EnumerateTail<T>(IEnumerator en) {
      while(en.MoveNext()) yield return en.Current; 
    }
    

    The HeadAndTail method gets the first element and returns it as the first element of a tuple. The second element of a tuple is IEnumerable<T> that's generated from the remaining elements (by iterating over the rest of the enumerator that we already created).