Sequence to extend and forget

Posted on

Problem

After receiving some feetback about my previous attempt to create reusable, easily extendable sequence generators I try once again to create such a framework.

This time there is no inheritance. Just extension methods.

The new core is the Sequence class:

public class Sequence
{
    public Sequence(int count) { Count = count; }

    public int Count { get; }

    public static Sequence Generate(int count)
    {
        return new Sequence(count);
    }

    public IEnumerable<T> Generate<T>(Func<IEnumerable<T>> generator) => generator().Take(Count);  
}

By itself it doesn’t do much. You need to extend it with your extensions that hook up to it and provide it with the actual generator for your sequence.

Here are some examples for the Fibonacci sequence:

static class FibonacciSequenceExtensions
{
    public static IEnumerable<T> Fibonacci<T>(this Sequence sequence, T firstTwo, T firstStep, Func<T, T, T> sum) 
    {
        return sequence.Generate(() => FibonacciGenerator(firstTwo, firstStep, sum));
    }

    public static IEnumerable<int> Fibonacci(this Sequence sequence, int firstTwo, int firstStep)
    {
        return sequence.Generate(() => FibonacciGenerator(firstTwo, firstStep, (x, y) => x + y));
    }

    public static IEnumerable<TimeSpan> Fibonacci(this Sequence sequence, TimeSpan firstTwo, TimeSpan firstStep)
    {
        return sequence.Generate(() => FibonacciGenerator(firstTwo, firstStep, (x, y) => x + y));
    }

    private static IEnumerable<T> FibonacciGenerator<T>(T firstTwo, T firstStep, Func<T, T, T> sum)
    {
        var preview = firstTwo;
        var current = sum(firstTwo, firstStep);

        yield return preview;
        yield return preview;
        yield return current;

        while (true)
        {
            var newCurrent = sum(preview, current);
            yield return newCurrent;
            preview = current;
            current = newCurrent;
        }
    }
}

Then you can use it like this:

var result = Sequence.Generate(10).Fibonacci(3, 4).ToList();

What’s the purpose of this? To have only a single class for all sequences. You don’t have to remember where you’ve implemented some sequence. You can use them all via a single class.


In case someone is interested how the final version looks like I put it here under the previous quesiton because it’s the inheritance edition that I decided to keep.

Solution

The API looks ok, IMHO, except for this syntax:

Sequence.Generate(10).Generate(...); // huh?

The downside is that for every sequence, I have to write at least two methods. First, method, to generate an actual sequence (FibonacciGenerator()), second method to wire it up to sequence.Generate (Fibonacci<T>()). This looks like a lot of extra code for what essentially could have been:

SequenceExtensions.FibonacciGenerator(...).Take(count);

Some middle ground can be probably achieved if you use Range instead:

Enumerable.Range(0, count) //extend this
          .Fibonacci(3, 4);

This way one method should be enough in most cases. And you can either drop the Sequence class altogether or leave it as a pretty wrapper around Enumerable.Range or similar loop abstraction:

Sequence.Create(10)      //returns basic 0..9 sequence (IEnumerable<int>)
        .Fibonacci(3, 4) //returns Fibonacci sequence, using initial sequence as loop

I am still not convinced that all this boiler plate code is worth the effort though. But oh well. 🙂

P.S. I think your Fibonacci sequence implementation is a bit unconventional. There is no “step” in classical implementation:

every number after the first two is the sum of the two preceding ones

So if I were to see Fibonacci(3, 4) in code, I would assume, that 3 and 4 are the first two numbers of the sequence, and that it will go as 3, 4, 7, 11 and not as 3, 3, 7, 10, 17 (3+3 =/= 7).

Leave a Reply

Your email address will not be published. Required fields are marked *