tsunami

log in
history

C# 3.0: SelfJoinByOffset

Luke Breuer
2012-10-08 09:39 UTC

Uses Tuple<T1, T2>.
[Flags]
public enum IncludeFringe
{
    None = 0,
    Begin = 1,
    End = 2,
    BeginAndEnd = Begin | End
}

/// <summary>
/// Joins a sequence to itself, offset by some number of elements.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <param name="offset"></param>
/// <returns>
/// A Tuple with the second element being <code>offset</code> elements past the first.
/// The second element will be null for the last <code>offset</code> element(s) of the list.
/// </returns>
public static IEnumerable<Tuple<T, T>> SelfJoinByOffset<T>(this IEnumerable<T> list, int offset, IncludeFringe fringes)
{
    if (offset <= 0)
        throw new ArgumentOutOfRangeException("offset", "Must be greater than zero.");

    /* Let's say the offset is 3, and we have 5 elements total
     * 
     * A B C D E
     * 
     * Then we will always have 3 elements in the Queue.  Here's the Q before we start yield returning:
     * 
     * A B C D E
     * ^ ^ ^       enqueued
     * 
     * Now we yield return [(A, D), (B, E), (C, null), (D, null), (E, null)]
     * 
     * What we're really doing is:
     *   yield return (Q.Dequeue, e.Current);
     */

    bool includeBegin = (fringes & IncludeFringe.Begin) != 0;
    bool includeEnd = (fringes & IncludeFringe.End) != 0;

    using (IEnumerator<T> e = list.GetEnumerator())
    {
        var q = new Queue<T>(offset);
        bool hasMore = true;

        while (offset-- > 0 && hasMore)
        {
            if (hasMore = e.MoveNext())
            {
                q.Enqueue(e.Current);

                if (includeBegin)
                    yield return new Tuple<T, T>(default(T), e.Current);
            }
        }

        while (q.Count > 0)
        {
            if (hasMore = e.MoveNext())
                q.Enqueue(e.Current);

            if (!hasMore && !includeEnd)
                yield break;

            yield return new Tuple<T, T>(q.Dequeue(), hasMore ? e.Current : default(T));
        }
    }
}
The input (with offset 1 and IncludeFringe.End)
1, 2, 3, 4
would get returned as 4 Tuple<int, int>s:
Tuple<int, int>.First:  1, 2, 3, 4
Tuple<int, int>.Second: 2, 3, 4, 0 <
                                 ^ this is default(int)