BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

LINQ
.NET 3.5+

Taking Every Nth Item from a Sequence

The standard query operators include methods to extract items from the start of a sequence or to skip several items. This article describes a custom operator with a similar syntax that creates a sequence from every nth item in a source collection.

TakeEvery Operator

In this article we'll create a new extension method similar to the Take and Skip members found in Language-Integrated Query's (LINQ) standard query operators. Rather than taking a number of items from the start of a collection, or skipping several items and returning the remainder of a sequence, we'll create a method that retrieves items that are equally spaced. For example, the TakeEvery method will permit you to extract every third item from a sequence. We'll also allow the starting point to be set, perhaps allowing you to obtain every second item starting from the fifth element of the source. As with the Take and Skip methods, TakeEvery will use deferred execution.

TakeEvery Overloads

Our new operator has two overloaded versions. The basic version allows you to specify how often that you wish to extract a value from the source sequence, starting with the first item. If you were to set the argument to two, this would return the first, third, fifth item, and so on. To allow the first item to obtain to be specified, the second overload includes an additional parameter that determines how many items will be skipped before any are yielded.

The code for the overloaded methods is shown below. The simpler of the two extension methods calls the more complex, passing a default value for the number of items to be skipped at the start of the process. You can also think of this as being the index of the first item to return. The public methods simply check that the input values are valid, throwing an exception if they are not. This ensures that invalid values are captured immediately. Once the validation is complete, the TakeEveryImpl method is called. This performs the taking and skipping actions using deferred execution.

public static class TakeEveryExtensions
{
    public static IEnumerable<T> TakeEvery<T>(this IEnumerable<T> sequence, int every)
    {
        return sequence.TakeEvery(every, 0);
    }

    public static IEnumerable<T> TakeEvery<T>(
        this IEnumerable<T> sequence, int every, int skipInitial)
    {
        if (sequence == null) throw new ArgumentNullException("sequence");
        if (every < 1) throw new ArgumentException("'every' must be 1 or greater");
        if (skipInitial < 0)
            throw new ArgumentException("'skipInitial' must be 0 or greater");

        return TakeEveryImpl(sequence, every, skipInitial);
    }
}

Implementation Method

The implementation method, which uses an iterator for deferred execution, is quite simple. First we obtain an enumerator for the source sequence. We then cycle through all of the elements in the sequence, yielding values periodically according to the input parameters.

The control for which items to return comes from the toSkip parameter. This is decremented each time we move to the next item in the enumerator. When it reaches zero, it is reset to the period specified in the every argument and the current item is yielded. At first the toSkip argument allows identification of the first item to be returned. After the first yield, the period becomes regular.

private static IEnumerable<T> TakeEveryImpl<T>(
    IEnumerable<T> sequence, int every, int toSkip)
{
    var enumerator = sequence.GetEnumerator();
    while (enumerator.MoveNext())
    {
        if (toSkip == 0)
        {
            yield return enumerator.Current;
            toSkip = every;
        }
        toSkip--;
    }
}

Testing the Method

We can test the method by extracting some values from an array. In the sample code below a string containing the alphabet is split into a character array. TakeEvery is then used to create two new sequences. The first operation takes every third letter. The second extracts every third letter after skipping a single item.

char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();

var everyThird = letters.TakeEvery(3);
var everyThirdFromB = letters.TakeEvery(3, 1);

/* RESULTS

everyThird = { A, D, G, J, M, P, S, V, Y }
everyThirdFromB = { B, E, H, K, N, Q, T, W, Z }

*/
24 October 2012