LINQ: Skip and Take

Continuing my series of posts on LINQ today we will take a look at another four methods: Take(), Skip(), TakeWhile() and SkipWhile().

Overview

Compared to some of LINQ’s other extension methods, the methods we will look at here are pretty straight forward.

Take() and TakeWhile() can be used to enumerate only a subsequence of either a given length, or while a certain condition is met respectively.

Skip() and SkipWhile() does the opposite: it either skips the given number of items, or skips as long as a given condition is met.

Examples

Take()

Let us start with an easy example of taking the first three elements of an array.

var ints = new int[] { 1, 2, 3, 4, 5 };

var a = ints.Take(3);

foreach(var n in a)
{
    // enumerates 1, 2, 3
}

Note that similar to many other LINQ methods, Take() is implemented using deferred execution. This means that the value a does not contain any numbers yet, but only knows how to enumerate the first few elements of the array.

If we were to modify the array and enumerate a afterwards, this change would be reflected.

var ints = new int[] { 1, 2, 3, 4, 5 };

var a = ints.Take(3);

ints[1] = 10;

foreach(var n in a)
{
    // enumerates 1, 10, 3
}

Note further that Take() will not throw an exception if it reaches the end of the sequence unexpectedly. It will simply return fewer values.

var ints = new int[] { 1, 2 };

foreach(var n in ints.Take(3))
{
    // enumerates 1, 2
}

Skip()

The Skip() method works similarly, except that it skips the number of items specified.

var ints = new int[] { 1, 2, 3, 4, 5 };

foreach(var n in ints.Skip(3))
{
    // enumerates 4, 5
}

Similarly to Take(), this method also enumerates in a deferred manner, and thus reflects changes made before enumerating. It also behaves nicely with short sequences, and simply returns an empty one, if the sequence is shorter than the given number to skip.

TakeWhile()

TakeWhile() is slightly more complicated, compared to the above methods. Instead of taking a simple number, it takes a delegate that returns a boolean value. If that value is true, the method takes the corresponding element and continues enumerating. If the delegate returns false, enumeration is stopped.

var ints = new int[] { 1, 2, 3, 4, 5 };

foreach(var n in ints.TakeWhile(x => x < 3))
{
    // enumerates 1, 2
}

Or

var ints = new int[] { 5, 7, 9, 11 };

foreach(var n in ints.TakeWhile(isPrime))
{
    // enumerates 5, 7
}

/* .. */

bool isPrime(int x)
{
    // returns true if and only if x is prime
}

In addition there exists a second overload of TakeWhile() that takes a delegate that also includes an int parameter. Similar to the equivalent of Select() this value will be the index of the given item.

Using it we can for example take all numbers, for as long as they are squares of their index.

var ints = new int[] { 0, 1, 4, 9, 15, 25 };

foreach(var n in ints.TakeWhile((x, i) => x == i * i))
{
    // enumerates 0, 1, 4, 9
}

SkipWhile()

By now I am sure it is clear how SkipWhile() functions. It has the same two overloads as TakeWhile(), but instead skips all items, until the delegate returns false for one of them. This item and all following ones are then returned.

var ints = new int[] { 1, 2, 3, 4, 5 };

foreach(var n in ints.SkipWhile(x => x < 3))
{
    // enumerates 3, 4, 5
}

foreach(var n in ints.SkipWhile((x, i) => x == i + 1))
{
    // enumerates nothing
}

Unsurprisingly, both TakeWhile() and SkipWhile() use deferred execution just like the previous two methods.

Combinations and other uses

As I mentioned in the beginning, these methods are some of the easiest to understand within LINQ. That does not mean they are not powerful however.

On their own, they can be clearly used to get the first few, or all but the first few elements of a sequence – a useful feature in itself for sure.

However, I have found that usually this becomes even more useful in combination with other LINQ methods.

For example, we can use Take() combined with OrderBy() to extract the smallest few elements of a sequence.

var ints = new int[] { 15, 61, 47, 34, 78, 13, 55, 38 };

foreach(var n in ints.OrderBy(i => i).Take(3))
{
    // enumerates 13, 15, 34
}

Or maybe we are interested in only the last few elements of a sequence using Take() and Reverse()

var ints = new int[] { 1, 2, 3, 4, 5 };

foreach(var n in ints.Reverse().Take(2))
{
    // enumerates 5, 4
}

And of course, we can use both Skip() and Take() together to extract a specific sub-sequence from our original collection by starting index and count.

var ints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

foreach(var n in ints.Skip(3).Take(4))
{
    // enumerates 4, 5, 6, 7
}

Alternatively we might use a combination of OrderBy() SkipWhile() and TakeWhile() to get a sorted list of all our values in a specific interval.

var ints = new int[] { c };

foreach(var n in ints
    .OrderBy(i => i)
    .SkipWhile(i => i < 30)
    .TakeWhile(i => i < 60)
    )
{
    // enumerates 34, 38, 47, 55
}

Conclusion

In this post we discussed the four LINQ methods Take(), Skip(), TakeWhile() and SkipWhile() and their different overloads.

I hope this has given you an overview of what these methods can be used for, and how you can combine them with each other and other LINQ methods to form more complicated queries on collections.

Make sure to drop me a comment if you found this useful, or if there are other parts of LINQ you would like me to take a closer look at here.

Until next time,

Enjoy the pixels!

Leave a Reply