Subscribe to CurtisRutland.Write("...");        RSS Feed
-----

Extension methods and operator overloads for integer sequence composition.

Icon Leave Comment
I've covered this topic before, in my other blog, but I've actually decided to give this another try. Surprisingly, it started with me writing a method that creates an IEnumerable of prime numbers. I needed to compose a sequence of numbers, and I remembered that there's really no simple way of doing that.

So I revisited my old Seq class. I decided I wanted to try operator overloading, so instead of making Seq static, I made it implement IEnumerable, and overloaded the bit shift operators.

But that's just syntactic sugar. I also wrote extension methods to actually compose the sequences. Here they are:


public static class SeqExt {

    public static IEnumerable<int> To(this int start, int end) {
        if (start <= end)
            while (start <= end)
                yield return start++;
        else
            while (start >= end)
                yield return start--;
    }

    public static IEnumerable<int> To(this int start, int end, Func<int, int> by) {
        if (start == end) {
            yield return start;
            yield break;
        }
        if (start <= end) {
            while (start <= end) {
                int x = start;
                start = by(start);
                yield return x;
            }
        }
        else {
            while (start >= end) {
                int x = start;
                start = by(start);
                yield return x;
            }
        }
    }

    public static IEnumerable<int> To(this IEnumerable<int> collection, int end) {
        int start = collection.First();
        if (start == end) {
            yield return start;
            yield break;
        }
        int skip = 0;
        if (start <= end) {
            while (true) {
                var tmp = collection.Skip(skip++).Take(1);
                if (!tmp.Any())
                    yield break;
                else {
                    start = tmp.Single();
                    if (start > end)
                        yield break;
                    else
                        yield return start;
                }
            }
        }
        else {
            while (true) {
                var tmp = collection.Skip(skip++).Take(1);
                if (!tmp.Any())
                    yield break;
                else {
                    start = tmp.Single();
                    if (start < end)
                        yield break;
                    else
                        yield return start;
                }
            }
        }
    }

    public static IEnumerable<int> By(this int start, Func<int, int> by) {
        while (true) {
            int x = start;
            start = by(start);
            yield return x;
        }
    }
    
}



To has several overloads. One will decide whether to count up or down, and then count until it reaches the upper bound (which is inclusive, btw).

Another will do the same, but instead of counting, it will apply the result of the Func<int, int> named by to each in sequence, until it reaches or passes the upper limit.

The last will take an existing IEnumerable and iterate over it until it reaches or exceeds the upper bound.

All three work in both directions, positive and negative. However, the last two require that the iterator and the end parameter be "matched up", so to speak. For example, if I want to iterate from 0 to -10, but provide x => x + 1 as the by function, I'll never reach -1. This is still technically valid, since IEnumerables can be infinite. You just have to know that it's a possible outcome.

The last extension method is By. This creates an IEnumerable by taking a starting int, and applying a func<int, int> to it infinitely. Remember, this is valid, since IEnumerables are evaluated lazily. 1.By(x => x+5).Take(10) is perfectly valid, since it'll iterate the first 10 values in the IEnumerable, then stop. This method was mostly added to work together with the second overload for to:

var nums = 1.By(x => x + 5).To(100);


Now, these are nice, but I wanted them to use some fancy operator overloading. So I made the Seq class:

public class Seq : IEnumerable<int>{

    private IEnumerable<int> sequence;

    #region IEnumerable<int> Members

    public IEnumerator<int> GetEnumerator() {
        return sequence.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator() {
        return sequence.GetEnumerator();
    }

    #endregion

    private Seq(int i) {
        sequence = Enumerable.Repeat(i, 1);
    }

    private Seq(IEnumerable<int> ienum) {
        sequence = ienum;
    }

    public static Seq From(int i) {
        return new Seq(i);
    }

    public static Seq From(IEnumerable<int> i) {
        return new Seq(i);
    }

    public static Seq operator >>(Seq x, int y) {
        if (!x.Any())
            throw new ArgumentException("Initial sequence must have at least one value.", "x");
        if(!(x.Skip(1).Any()))
            return new Seq(x.Single().To(y));
        else
            return new Seq(x.To(y));
    }

    public static Seq operator <<(Seq x, int y) {
        if(!x.Any() || x.Skip(1).Any())
            throw new ArgumentException("Initial sequence must have at one and only one value.", "x");
        return new Seq(x.Single().By(i => i + y));
    }

    public static Seq Comp(int from, int to) {
        return new Seq(from) >> to;
    }

    public static Seq Comp(int from, int by, int to) {
        return new Seq(from) << by >> to;
    }
}


Seq has a few ways of working. I prefer to make the constructors private, because Seq.From looks a bit more proper than new Seq, and doesn't need to be parenthesized.

I've overloaded the bit shift operators (>> and <<) to act as to and by, respectively, to allow for this syntax:

var s = Seq.From(1) >> 10;
//same as var s = 1.To(10);
var s2 = Seq.From(1) << 2 >> 10;
//same as var s2 = 1.By(2).To(10);


Which could be read "s equals From 1 to 10", and "s2 equals From 1 By 2 To 10" respectively. It's purely syntactic preference whether to use the Extension methods or the Seq class. I like both, though I probably would come down on the extension methods. They are slightly shorter, and they don't break from standard C# operators.

I also added a more standard set of static methods for composition: Comp. With them, you can do the same thing as so:

var s = Seq.Comp(1, 10);
//same as 1.To(10);
var s2 = Seq.Comp(1,2,10);
//same as 1.By(2).To(10);


Oh, one other extension method I forgot to share: Just a convenient method for printing enumerables:

public static void Print<T>(this IEnumerable<T> collection, string header = "", string footer = "", TextWriter tw = null) {
    if (tw == null) tw = Console.Out;
    if (!string.IsNullOrEmpty(header))
        tw.WriteLine(header);
    foreach (var t in collection)
        tw.WriteLine(t);
    if (!string.IsNullOrEmpty(footer))
       tw.WriteLine(footer);
}


Anyway, I thought I'd share that with you. Here are some tests to show the different syntaxes you can use for these compositions.

Spoiler

0 Comments On This Entry

 

Trackbacks for this entry [ Trackback URL ]

There are no Trackbacks for this entry

Recent Entries

Recent Comments

1 user(s) viewing

1 Guests
0 member(s)
0 anonymous member(s)