LINQ: Introducing The Take Last Operators

LINQ With C# (Portuguese)

Some time ago I needed to retrieve the last items of a sequence that satisfied some criteria and, looking at the operators available in the Enumerable class, I noticed that there wasn’t such operator.

The only way to achieve this was to reverse the sequence, take the items that satisfied the criteria and reverse the resulting sequence. Something like this:

sequence.Reverse().TakeWhile(criteria).Reverse();

Looks quite simple, right? First we call the Reverse method to produce a new sequence with the same items as the original sequence but in the reverse order, then we call the TakeWhile method to take the first items that satisfy the criteria and then call the Reverse method again to restore the original order of the items.

The problem with this approach is that the Reverse method buffers the entire sequence before iterating through its items in the reverse order - and the above code uses it twice. This means iterating over all items in the original sequence and buffer them all, iterating over first items of the resulting sequence that satisfy the criteria and buffer them all and, finally, iterate over that result to produce the final sequence.

If you’re counting, you’ve come to the conclusion that all items in the original sequence will be iterated over once and the ones in the resulting sequence will be iterated again three times. If the original sequence is large, this can take lots of memory and time.

There’s another issue if you’re using the variant the uses the index of the item in the original sequence in the evaluation of the selection criteria (>). When we reverse the order of the items, the indexes will be reversed and the predicate must take that in account, which might not be possible if you don’t know the number of items in the original sequence.

There must be a better way, and that’s why I implemented the Take Last Operators:

Name Description Example
TakeLast<TSource>(IEnumerable<TSource>)

Returns a specified number of contiguous elements from the end of a sequence.

int[] grades = { 59, 82, 70, 56, 92, 98, 85 };

var topThreeGrades = grades
                     .OrderBy(grade => grade)
                     .TakeLast(3);

Console.WriteLine("The top three grades are:");
foreach (int grade in topThreeGrades)
{
    Console.WriteLine(grade);
}
/*
This code produces the following output:

The top three grades are:
98
92
85
*/
TakeLastWhile<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>)

Returns the elements from the end of a sequence as long as the specified condition is true.

string[] fruits =
    {
        "apple",
        "passionfruit",
        "banana",
        "mango",
        "orange",
        "blueberry",
        "grape",
        "strawberry"
    };

var query = fruits
            .TakeLastWhile(fruit => string.Compare("orange", fruit, true) != 0);

foreach (string fruit in query)
{
    Console.WriteLine(fruit);
}

/*
This code produces the following output:
blueberry
grape
strawberry
*/
TakeLastWhile<TSource>(IEnumerable<TSource>, Func<TSource, Int32, Boolean>)

Returns the elements from the end of a sequence as long as the specified condition is true.

string[] fruits =
    {
        "apple",
        "passionfruit",
        "banana",
        "mango",
        "orange",
        "blueberry",
        "grape",
        "strawberry"
    };

var query = fruits
            .TakeLastWhile((fruit, index) => fruit.Length >= index);

foreach (string fruit in query)
{
    Console.WriteLine(fruit);
}

/*
This code produces the following output:

strawberry
*/

You can find these (and more) operators in my CodePlex project for LINQ utilities and operators: PauloMorgado.Linq

Comments are closed.