Enumerable Interleave Extension Method

A recent stackoverflow question, which I didn’t bookmark and now I’m unable to find, inspired me to implement an extension method for Enumerable that allows to insert a constant element between each pair of elements in a sequence. Kind of what String.Join does for strings, but maintaining an enumerable as the return value.

Having done the single element part I got a bit carried away and ended up expanding it adding overloads to support interleaving elements of another sequence and support for a predicate to control when interleaving takes place.

I have to confess that I did this for fun and now I can’t think of any real usage scenario, nonetheless, it may prove useful for someone.

First a simple example:

var target = new string[] { "(", ")", "(", ")" };

var result = target.Interleave(".", (f, s) => f == "(");

// Prints: (.)(.)
Console.WriteLine(String.Join(string.Empty, result));

And now the untested but documented implementation:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public static class EnumerableExtensions
{
    /// <summary>
    /// Iterates infinitely over a constant element.
    /// </summary>
    /// <typeparam name="T">
    /// The type of element in the sequence.
    /// </typeparam>
    private class InfiniteSequence<T> : IEnumerable<T>, IEnumerator<T>
    {
        public InfiniteSequence(T element) { this.Element = element; }

        public T Element { get; private set; }

        public IEnumerator<T> GetEnumerator() { return this; }

        IEnumerator IEnumerable.GetEnumerator() { return this; }

        T IEnumerator<T>.Current { get { return this.Element; } }

        void IDisposable.Dispose() { }

        object IEnumerator.Current { get { return this.Element; } }

        bool IEnumerator.MoveNext() { return true; }

        void IEnumerator.Reset() { }
    }

    /// <summary>
    /// Interleaves the specified <paramref name="element"/> between each pair of elements in the <paramref name="target"/> sequence.
    /// </summary>
    /// <typeparam name="T">
    /// The type of elements in the sequence.
    /// </typeparam>
    /// <param name="target">
    /// The target sequence to be interleaved.
    /// </param>
    /// <param name="element">
    /// The element used to perform the interleave operation.
    /// </param>
    /// <exception cref="ArgumentNullException">
    /// <paramref name="target"/> or <paramref name="element"/> is a null reference.
    /// </exception>
    /// <returns>
    /// The <paramref name="target"/> sequence interleaved with the specified <paramref name="element"/>.
    /// </returns>
    public static IEnumerable<T> Interleave<T>(
        this IEnumerable<T> target,
        T element)
    {
        if (target == null)
            throw new ArgumentNullException("target");

        if (element == null)
            throw new ArgumentNullException("element");

        return InterleaveInternal(target, new InfiniteSequence<T>(element), (f, s) => true);
    }

    /// <summary>
    /// Interleaves the specified <paramref name="element"/> between each pair of elements in the <paramref name="target"/> sequence.
    /// </summary>
    /// <remarks>
    /// The interleave operation is interrupted as soon as the <paramref name="target"/> sequence is exhausted; If the number of <paramref name="elements"/> to be interleaved are not enough to completely interleave the <paramref name="target"/> sequence then the remainder of the sequence is returned without being interleaved.
    /// </remarks>
    /// <typeparam name="T">
    /// The type of elements in the sequence.
    /// </typeparam>
    /// <param name="target">
    /// The target sequence to be interleaved.
    /// </param>
    /// <param name="elements">
    /// The elements used to perform the interleave operation.
    /// </param>
    /// <exception cref="ArgumentNullException">
    /// <paramref name="target"/> or <paramref name="element"/> is a null reference.
    /// </exception>
    /// <returns>
    /// The <paramref name="target"/> sequence interleaved with the specified <paramref name="elements"/>.
    /// </returns>
    public static IEnumerable<T> Interleave<T>(
        this IEnumerable<T> target,
        IEnumerable<T> elements)
    {
        if (target == null)
            throw new ArgumentNullException("target");

        if (elements == null)
            throw new ArgumentNullException("elements");

        return InterleaveInternal(target, elements, (f, s) => true);
    }

    /// <summary>
    /// Interleaves the specified <paramref name="element"/> between each pair of elements in the <paramref name="target"/> sequence that satisfy <paramref name="predicate"/>.
    /// </summary>
    /// <typeparam name="T">
    /// The type of elements in the sequence.
    /// </typeparam>
    /// <param name="target">
    /// The target sequence to be interleaved.
    /// </param>
    /// <param name="element">
    /// The element used to perform the interleave operation.
    /// </param>
    /// <param name="predicate">
    /// A predicate used to assert if interleaving should occur between two target elements.
    /// </param>
    /// <exception cref="ArgumentNullException">
    /// <paramref name="target"/> or <paramref name="element"/> or <paramref name="predicate"/> is a null reference.
    /// </exception>
    /// <returns>
    /// The <paramref name="target"/> sequence interleaved with the specified <paramref name="element"/>.
    /// </returns>
    public static IEnumerable<T> Interleave<T>(
        this IEnumerable<T> target,
        T element,
        Func<T, T, bool> predicate)
    {
        if (target == null)
            throw new ArgumentNullException("target");

        if (element == null)
            throw new ArgumentNullException("element");

        if (predicate == null)
            throw new ArgumentNullException("predicate");

        return InterleaveInternal(target, new InfiniteSequence<T>(element), predicate);
    }

    /// <summary>
    /// Interleaves the specified <paramref name="element"/> between each pair of elements in the <paramref name="target"/> sequence that satisfy <paramref name="predicate"/>.
    /// </summary>
    /// <remarks>
    /// The interleave operation is interrupted as soon as the <paramref name="target"/> sequence is exhausted; If the number of <paramref name="elements"/> to be interleaved are not enough to completely interleave the <paramref name="target"/> sequence then the remainder of the sequence is returned without being interleaved.
    /// </remarks>
    /// <typeparam name="T">
    /// The type of elements in the sequence.
    /// </typeparam>
    /// <param name="target">
    /// The target sequence to be interleaved.
    /// </param>
    /// <param name="elements">
    /// The elements used to perform the interleave operation.
    /// </param>
    /// <param name="predicate">
    /// A predicate used to assert if interleaving should occur between two target elements.
    /// </param>
    /// <exception cref="ArgumentNullException">
    /// <paramref name="target"/> or <paramref name="element"/> or <paramref name="predicate"/> is a null reference.
    /// </exception>
    /// <returns>
    /// The <paramref name="target"/> sequence interleaved with the specified <paramref name="elements"/>.
    /// </returns>
    public static IEnumerable<T> Interleave<T>(
        this IEnumerable<T> target,
        IEnumerable<T> elements,
        Func<T, T, bool> predicate)
    {
        if (target == null)
            throw new ArgumentNullException("target");

        if (elements == null)
            throw new ArgumentNullException("elements");

        if (predicate == null)
            throw new ArgumentNullException("predicate");

        return InterleaveInternal(target, elements, predicate);
    }

    private static IEnumerable<T> InterleaveInternal<T>(
        this IEnumerable<T> target,
        IEnumerable<T> elements,
        Func<T, T, bool> predicate)
    {
        var targetEnumerator = target.GetEnumerator();

        if (targetEnumerator.MoveNext())
        {
            var elementsEnumerator = elements.GetEnumerator();

            while (true)
            {
                T first = targetEnumerator.Current;

                yield return first;

                if (!targetEnumerator.MoveNext())
                    yield break;

                T second = targetEnumerator.Current;

                bool interleave = true &&
                    predicate(first, second) &&
                    elementsEnumerator.MoveNext();

                if (interleave)
                    yield return elementsEnumerator.Current;
            }
        }
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s