Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Linq to get the last N elements of a collection?

Tags:

c#

linq

Given a collection, is there a way to get the last N elements of that collection? If there isn't a method in the framework, what would be the best way to write an extension method to do this?

like image 267
Matthew Groves Avatar asked Aug 10 '10 20:08

Matthew Groves


People also ask

Does LINQ preserve order?

Therefore, by default, PLINQ does not preserve the order of the source sequence. In this regard, PLINQ resembles LINQ to SQL, but is unlike LINQ to Objects, which does preserve ordering.


2 Answers

collection.Skip(Math.Max(0, collection.Count() - N)); 

This approach preserves item order without a dependency on any sorting, and has broad compatibility across several LINQ providers.

It is important to take care not to call Skip with a negative number. Some providers, such as the Entity Framework, will produce an ArgumentException when presented with a negative argument. The call to Math.Max avoids this neatly.

The class below has all of the essentials for extension methods, which are: a static class, a static method, and use of the this keyword.

public static class MiscExtensions {     // Ex: collection.TakeLast(5);     public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int N)     {         return source.Skip(Math.Max(0, source.Count() - N));     } } 

A brief note on performance:

Because the call to Count() can cause enumeration of certain data structures, this approach has the risk of causing two passes over the data. This isn't really a problem with most enumerables; in fact, optimizations exist already for Lists, Arrays, and even EF queries to evaluate the Count() operation in O(1) time.

If, however, you must use a forward-only enumerable and would like to avoid making two passes, consider a one-pass algorithm like Lasse V. Karlsen or Mark Byers describe. Both of these approaches use a temporary buffer to hold items while enumerating, which are yielded once the end of the collection is found.

like image 74
kbrimington Avatar answered Sep 28 '22 02:09

kbrimington


coll.Reverse().Take(N).Reverse().ToList();   public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> coll, int N) {     return coll.Reverse().Take(N).Reverse(); } 

UPDATE: To address clintp's problem: a) Using the TakeLast() method I defined above solves the problem, but if you really want the do it without the extra method, then you just have to recognize that while Enumerable.Reverse() can be used as an extension method, you aren't required to use it that way:

List<string> mystring = new List<string>() { "one", "two", "three" };  mystring = Enumerable.Reverse(mystring).Take(2).Reverse().ToList(); 
like image 40
James Curran Avatar answered Sep 28 '22 02:09

James Curran