Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I take the first n elements from an enumeration, and then still use the rest of the enumeration?

Tags:

c#

.net

linq

Suppose I have an IEnumerable<T>, and I want to take the first element and pass the remaining elements to some other code. I can get the first n elements using Take(n), but how can I then access the remaining elements without causing the enumeration to re-start?

For example, suppose I have a method ReadRecords that accepts the records in a CSV file as IEnumerable<string>. Now suppose that within that method, I want to read the first record (the headers), and then pass the remaining records to a ReadDataRecords method that also takes IEnumerable<string>. Like this:

void ReadCsv(IEnumerable<string> records)
{
    var headerRecord = records.Take(1);
    var dataRecords = ???

    ReadDataRecords(dataRecords);
}

void ReadDataRecords(IEnumerable<string> records)
{
    // ...
}

If I were prepared to re-start the enumeration, then I could use dataRecords = records.Skip(1). However, I don't want to re-start it - indeed, it might not be re-startable.

So, is there any way to take the first records, and then the remaining records (other than by reading all the values into a new collection and re-enumerating them)?

like image 655
Gary McGill Avatar asked Apr 01 '14 23:04

Gary McGill


2 Answers

This is an interesting question, I think you can use a workaround like this, instead of using LINQ get the enumerator and use it:

private void ReadCsv(IEnumerable<string> records)
{
     var enumerator = records.GetEnumerator();
     enumerator.MoveNext();
     var headerRecord = enumerator.Current;
     var dataRecords = GetRemainingRecords(enumerator);
}

public IEnumerable<string> GetRemainingRecords(IEnumerator<string> enumerator)
{
    while (enumerator.MoveNext())
    {
       if (enumerator.Current != null)
            yield return enumerator.Current;
    }
}

Update: According to your comment here is more extended way of doing this:

public static class CustomEnumerator
{
    private static int _counter = 0;
    private static IEnumerator enumerator;
    public static IEnumerable<T> GetRecords<T>(this IEnumerable<T> source)
    {
        if (enumerator == null) enumerator = source.GetEnumerator();

        if (_counter == 0)
        {
            enumerator.MoveNext();
            _counter++;
            yield return (T)enumerator.Current;
        }
        else
        {
            while (enumerator.MoveNext())
            {
                yield return (T)enumerator.Current;
            }
            _counter = 0;
            enumerator = null;
        }
    } 
}

Usage:

private static void ReadCsv(IEnumerable<string> records)
{
     var headerRecord = records.GetRecords();
     var dataRecords = records.GetRecords();
}
like image 98
Selman Genç Avatar answered Nov 08 '22 23:11

Selman Genç


Yes, use the IEnumerator from the IEnumerable, and you can maintain the position across method calls;

A simple example;

public class Program
{
    static void Main(string[] args)
    {
        int[] arr = new [] {1, 2, 3};
        IEnumerator enumerator = arr.GetEnumerator();
        enumerator.MoveNext();
        Console.WriteLine(enumerator.Current);
        DoRest(enumerator);
    }

    static void DoRest(IEnumerator enumerator)
    {
        while (enumerator.MoveNext())
            Console.WriteLine(enumerator.Current);
    }
}
like image 32
RJ Lohan Avatar answered Nov 08 '22 23:11

RJ Lohan