Enumerable Interleave Extension Method
- by João Angelo
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;
}
}
}
}