3
\$\begingroup\$

The last question about sequence generators Growing potatoes in delayed sequences was only about potatoes. I thougt why not make it work with apples and oranges too so I made it generic.


The base class got a new name and a generic argument and now looks like this:

public abstract class GeneratedSequence<T> : IEnumerable<T>
{
    protected GeneratedSequence(int count) { Count = count; }

    public int Count { get; }

    public IEnumerator<T> GetEnumerator() => Generate().Take(Count).GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    protected abstract IEnumerable<T> Generate();
}

The RegularSequence has been upgraded and is now generic too:

public class RegularSequence<T> : GeneratedSequence<T>
{
    private readonly T _value;
    public RegularSequence(T value, int count) : base(count) { _value = value; }
    protected override IEnumerable<T> Generate()
    {
        while (true) yield return _value;
    }
}

The biggest change undergone however the FibonnaciSequence. Generics don't support arithmetic operations so I needed to add a lambda for the sum. Now it looks like this:

public class FibonacciSequence<T> : GeneratedSequence<T>
{
    private T _preview;
    private T _current;
    private readonly Func<T, T, T> _sum;

    public FibonacciSequence(T firstTwo, T firstStep, int count, Func<T, T, T> sum) : base(count)
    {
        _sum = sum;
        _preview = firstTwo;
        _current = _sum(_preview, firstStep);
    }   

    protected override IEnumerable<T> Generate()
    {
        yield return _preview;
        yield return _preview;
        yield return _current;

        while (true)
        {
            var newCurrent = _sum(_preview, _current);
            yield return newCurrent;
            _preview = _current;
            _current = newCurrent;
        }
    }
}

To simplify the creation process I also added a new FibonacciSequenceFactory:

public class FibonacciSequenceFactory
{
    public static FibonacciSequence<TimeSpan> Create(TimeSpan firstTwo,TimeSpan firstStep, int count)
    {
        return new FibonacciSequence<TimeSpan>(firstTwo, firstStep, count, (x, y) => x + y);
    }

    public static FibonacciSequence<int> Create(int firstTwo, int firstStep, int count)
    {
        return new FibonacciSequence<int>(firstTwo, firstStep, count, (x, y) => x + y);
    }
}

I've applied the same pattern to the GeometricSequence but as it already had a lambda for the incrementation I just changed the argument to T:

public class GeometricSequence<T> : GeneratedSequence<T>
{
    private T _current;
    private readonly Func<T, T> _increment;
    public GeometricSequence(T first, Func<T, T> increment, int count) : base(count)
    {
        _current = first;
        _increment = increment;
    }
    protected override IEnumerable<T> Generate()
    {
        yield return _current;

        while (true)
        {
            yield return (_current = _increment(_current));
        };
    }
}

public class GeometricSequenceFactory
{
    public static GeometricSequence<TimeSpan> Double(TimeSpan first, int count)
    {
        return new GeometricSequence<TimeSpan>(first, x => TimeSpan.FromTicks(x.Ticks * 2), count);
    }

    public static GeometricSequence<TimeSpan> Triple(TimeSpan first, int count)
    {
        return new GeometricSequence<TimeSpan>(first, x => TimeSpan.FromTicks(x.Ticks * 3), count);
    }

    public static GeometricSequence<TimeSpan> Halve(TimeSpan first, int count)
    {
        return new GeometricSequence<TimeSpan>(first, x => TimeSpan.FromTicks(x.Ticks / 2), count);
    }
}

Usage example:

var fs = FibonacciSequenceFactory.Create(2, 4, 10);
\$\endgroup\$
1
\$\begingroup\$

It looks like you just need a bunch of generator methods:

static class Sequence
{
    public static IEnumerable<T> Constant<T>(T value, int count = int.MaxValue)
    {
        return GeometricSequence(value, x => x, count);
    }

    public static IEnumerable<T> Geometric<T>(T value, Func<T, T> increment, int count = int.MaxValue)
    {
        for (int i = 0; i < count; i++)
        {
            yield return value;
            value = increment(value);
        }
    }

    public static IEnumerable<int> Fibonacci(...)
    {
        //your implementation
    }
}

The rest you can do with regular LINQ operations. Want to get a Fibonacci sequence for Timespan? Just call Select:

  Sequence.Fibonacci(...)
          .Select(Timespan.FromMilliseconds);

and that's it. Why would you want to complicate things further? Do you want to avoid this extra line of code by adding an overly engineered class hierarchy of sequences? Not worth it, if you ask me. I mean, I saw people implementing SelectFrist method, because apparently writing:

.Select(x => ...)
.First();

is too much work. But meh...

\$\endgroup\$
  • \$\begingroup\$ This could actually work if... the sequence were a singleton so that I can write sequence extensions for it. Currently there is no way to extend it as the class is static and the user cannot add his own sequence. I have to think about it. Preferably he should be able to do Sequence.Generate.Fibonacci where the Generate is an instance for which extensions can be written. \$\endgroup\$ – t3chb0t Nov 14 '16 at 10:14
  • \$\begingroup\$ Yes, but why extend it? There is no protected state to access, no methods to override, nothing. What do you get from inheritance? I think we've all created some MathEx class to compliment System.Mathat some point, nothing wrong with that. The user of your class can do the same. That being said, you can always make Sequence class non-static. I just don't see the point. \$\endgroup\$ – Nikita B Nov 14 '16 at 10:56
  • \$\begingroup\$ MathEx - exactly! That's because the Math class is not open for extension. You have two classes and you need to search for the APIs. I'd like it to be extendable so that the user doesn't have to think well, where did I put this sequence this time? With a single point of extension you always know where to find things. I'd like to achieve maximum (re)usability. For that reason the Math class should provide a Calc property so that you can easily extend it and simply write Math.Calc.Abs or whatever. Easy peasy to extend, to find and to use ;-) I'll try to write something. \$\endgroup\$ – t3chb0t Nov 14 '16 at 11:08
  • \$\begingroup\$ Well, I get it. You want to keep the methods grouped together. The thing is, you probably won't be able to achieve this with static classes. Say, I extend Calc instance with a couple of methods, and call the extended version CalcEx. What next? :) I could then set it to Math.Calc static property, but it is of base type. How do I call my new methods? Do I cast? Do I use different static class? Do, God forbid, I use static generics? :) Frankly, neither of those options sound better, than good ol' MathEx. =) \$\endgroup\$ – Nikita B Nov 14 '16 at 11:30
  • \$\begingroup\$ Still, I am looking forward to what you can come up with. ^^ I might be too conservative here and/or utterly wrong. \$\endgroup\$ – Nikita B Nov 14 '16 at 11:30
3
\$\begingroup\$

A slightly different approach is to avoid the sublclassing and instead feed the generic DelaySequence class with a generator function. It makes it more flexible.

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

namespace CR146856
{
  public class MyDelaySequence<T> : IEnumerable<T>
  {
    Func<IEnumerable<T>> m_generator;
    int m_count = 0;

    /// <summary>
    /// A contructor for a plain sequence of regular delays
    /// </summary>
    /// <param name="count">Number of retries.</param>
    /// <param name="delay">The delay after each retry.</param>
    public MyDelaySequence(int count, T delay)
    {
      m_count = count;
      m_generator = () =>
      {
        return Enumerable.Range(1, count).Select(i => delay);
      };
    }

    /// <summary>
    /// Constructor for a custom delay generator.
    /// </summary>
    /// <param name="count">Number of retries.</param>
    /// <param name="generator">A custom generator of delays between retries.</param>
    public MyDelaySequence(int count, Func<IEnumerable<T>> generator)
    {
      m_count = count;
      m_generator = generator;
    }

    public IEnumerator<T> GetEnumerator()
    {
      return m_generator().Take(m_count).GetEnumerator();
    }

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

  class Program
  {
    static IEnumerable<int> GeometricDelayGenerator()
    {
      int delay = 1000;
      while (true)
      {
        yield return delay;
        delay += delay;
      }
    }

    static IEnumerable<int> FibonacciIntDelayGenerator()
    {
      int first = 1000;

      yield return first;
      yield return first;

      int second = first + 3000;
      yield return second;


      while (true)
      {
        yield return first + second;
        int tmp = first;
        first = second;
        second = first + tmp;
      }
    }

    static IEnumerable<TimeSpan> FibonacciTimeSpanDelayGenerator()
    {
      TimeSpan first = TimeSpan.FromSeconds(1);

      yield return first;
      yield return first;

      TimeSpan second = first + TimeSpan.FromSeconds(3);
      yield return second;


      while (true)
      {
        yield return first + second;
        TimeSpan tmp = first;
        first = second;
        second = first + tmp;
      }
    }

    static void Main(string[] args)
    {
      try
      {
        TimeSpan seqDelay = TimeSpan.FromSeconds(1);
        int count = 7;

        foreach (var ts in new MyDelaySequence<TimeSpan>(count, FibonacciTimeSpanDelayGenerator))
        {
          Console.WriteLine(ts);
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine($"ERROR: {ex.Message}");
      }

      Console.WriteLine("END");
      Console.ReadLine();
    }
  }
}

That said I tend to think you overdo a rather simple construct:

    foreach (var delay in FibonacciIntDelayGenerator().Take(count))
    {
      Console.WriteLine($"If service not responding wait in {delay} ms and retry.");
    }

EDIT:

In case of a reusable library of functions I would do it the static way:

  public static class MyDelayGenerators
  {
    public static IEnumerable<T> DelayGenerator<T>(int retries, T initialDelay, Func<T, T> offsetFunc)
    {
      T delay = initialDelay;
      for (int i = 0; i < retries; i++)
      {
        yield return delay;
        delay = offsetFunc(delay);
      }
    }

    public static IEnumerable<T> GeometricDelayGenerator<T>(int retries, T initialDelay, Func<T, T> sum)
    {
      return DelayGenerator(retries, initialDelay, sum);
    }

    public static IEnumerable<int> GeometricDelayGenerator(int retries, int initialDelay)
    {
      return DelayGenerator(retries, initialDelay, (d) => d + d);
    }

    public static IEnumerable<TimeSpan> GeometricDelayGenerator(int retries, TimeSpan initialDelay)
    {
      return DelayGenerator(retries, initialDelay, (d) => d + d);
    }

    public static IEnumerable<int> RegularDelayGenerator(int retries, int delay)
    {
      for (int i = 0; i < retries; i++)
      {
        yield return delay;
      }
    }

    public static IEnumerable<TimeSpan> RegularDelayGenerator(int retries, TimeSpan delay)
    {
      for (int i = 0; i < retries; i++)
      {
        yield return delay;
      }
    }

    public static IEnumerable<T> FibonacciDelayGenerator<T>(int retries, T first, T firstOffset, Func<T, T, T> sum)
    {
      yield return first;
      yield return first;

      T second = sum(first, firstOffset);
      yield return second;

      retries -= 3;
      for (int i = 0; i < retries; i++)
      {
        T tmp = sum(first, second);
        yield return tmp;
        first = second;
        second = tmp;
      }
    }

    public static IEnumerable<int> FibonacciDelayGenerator(int retries, int first = 1000, int firstOffset = 1000)
    {
      return FibonacciDelayGenerator(retries, first, firstOffset, (f, s) =>
      {
        int tmp = f;
        f = s;
        s = f + tmp;
        return s;
      });
    }

    public static IEnumerable<TimeSpan> FibonacciDelayGenerator(int retries, TimeSpan first, TimeSpan firstOffset)
    {
      return FibonacciDelayGenerator(retries, first, firstOffset, (f, s) =>
      {
        TimeSpan tmp = f;
        f = s;
        s = f + tmp;
        return s;
      });
    }
  }

It has a number of predefined functions and a set of generic as well to be use on the fly.

\$\endgroup\$
  • \$\begingroup\$ I'm not happy with the repetition of the generator implementation for various types. The purpose of making it generic was to have the algorithm only in one place but make it work with the TimeSpan, int or any other type. I like the idea with the generator function to avoid subclassing. I need to perform a few experiments to see if I can make it generic too and pass the lambda for the arithmetic somehow. CR146856 I do the same ;-D \$\endgroup\$ – t3chb0t Nov 13 '16 at 8:36
  • \$\begingroup\$ @t3chb0t: You may be right. I think of the generator as a rather local function (a lambda maybe) where you probably think of it as a reusable collection of functions/classes. \$\endgroup\$ – Henrik Hansen Nov 13 '16 at 8:44
  • \$\begingroup\$ Exactly. I don't want to think about implementing it ever agian ;-) They should be reusable components so that for my next project (whatever it will be) I just want to install it as a package and I'm done. As far as I've just tried, there seem to be no way to create a general generator method because differenct sequences require different parameters and arithmetics. \$\endgroup\$ – t3chb0t Nov 13 '16 at 8:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.