Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

linq extension method to take elements from the end of the sequence

There is the enumerable extension method

Take<TSource>(
    IEnumerable<TSource> source,
    int count
)

which takes the first count elements from the start.

Is there a way to take the elements from the end? or even better a way to take the elements from an offset to the end?

Thanks

like image 808
Fabiano Avatar asked Sep 23 '10 17:09

Fabiano


2 Answers

finiteList.Reverse().Take(count).Reverse();

or

finiteList.Skip(finiteList.Count() - count)

There is some overhead in doing this so a custom method would be better.

Update: A custom method

public static class EnumerableExtensions
{
    public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (count < 0) throw new ArgumentOutOfRangeException("count");

        if (count == 0) yield break;

        var queue = new Queue<T>(count);

        foreach (var t in source)
        {
            if (queue.Count == count) queue.Dequeue();

            queue.Enqueue(t);
        }

        foreach (var t in queue)
            yield return t;
    }
}

Update: Changed the code a littlebit with ideas from dtb´s answer :-)

Comment to Bear: Look at this example:

var lastFive = Enumerable.Range(1, 10).TakeLast(5);
var lastFive2 = Enumerable.Range(1, 10).TakeLast2(5); //Bear´s way

Queue<int> q = (Queue<int>)lastFive2;
q.Dequeue();

//Is lastFive2 still last five? no...

You could potentially change the values of lastFive2 and therefore that approach can be unsafe or at least it´s not the functional way.

To Bear:

What I meant about safe is this:

var lastFive2 = Enumerable.Range(1, 10).TakeLast2(5); //Bear´s way

//some = Some method which you don't control - it could be from another assembly which represents a crazy plugin etc.
some(lastFive2);
//Now what?

In these cases you would have to make a copy to be sure. But in most cases your way would be fine - and a little bit more efficient than this so +1 :)

An idea is to use a queue which only have internal Enqueue etc.

like image 168
Lasse Espeholt Avatar answered Sep 17 '22 14:09

Lasse Espeholt


MoreLINQ provides a TakeLast extension method:

var last10 = finiteList.TakeLast(10);

To take the elements from an offset to the end, Enumerable.Skip should do the trick:

var allFromOffsetToEnd = finiteList.Skip(offset);
like image 31
dtb Avatar answered Sep 19 '22 14:09

dtb