Search code examples
c#.netalgorithmlinqfluent-interface

How to build a sequence using a fluent interface?


I'm trying to using a fluent interface to build a collection, similar to this (simplified) example:

   var a = StartWith(1).Add(2).Add(3).Add(4).ToArray();
   /* a = int[] {1,2,3,4};  */

The best solution I can come up with add Add() as:

  IEnumerable<T> Add<T>(this IEnumerable<T> coll, T item)
  {
     foreach(var t in coll) yield return t;
     yield return item;
  }

Which seems to add a lot of overhead that going to be repeated in each call.

IS there a better way?

UPDATE: in my rush, I over-simplified the example, and left out an important requirement. The last item in the existing coll influences the next item. So, a slightly less simplified example:

   var a = StartWith(1).Times10Plus(2).Times10Plus(3).Times10Plus(4).ToArray();
   /* a = int[] {1,12,123,1234};  */

public static IEnumerable<T> StartWith<T>(T x)
{
    yield return x;
}

static public  IEnumerable<int> Times10Plus(this IEnumerable<int> coll, int item)
{
    int last = 0;
    foreach (var t in coll)
    {
        last = t;
        yield return t;
    }
    yield return last * 10 + item;
}

Solution

  • You could do the following:

    public static class MySequenceExtensions
    {
        public static IReadOnlyList<int> Times10Plus(
            this IReadOnlyList<int> sequence, 
            int value) => Add(sequence, 
                              value,
                              v => sequence[sequence.Count - 1] * 10 + v);
    
        public static IReadOnlyList<T> Starts<T>(this T first)
            => new MySequence<T>(first);
    
        public static IReadOnlyList<T> Add<T>(
            this IReadOnlyList<T> sequence,
            T item,
            Func<T, T> func)
        {
            var mySequence = sequence as MySequence<T> ?? 
                             new MySequence<T>(sequence);
            return mySequence.AddItem(item, func);
        }
    
        private class MySequence<T>: IReadOnlyList<T>
        {
            private readonly List<T> innerList;
    
            public MySequence(T item)
            {
                innerList = new List<T>();
                innerList.Add(item);
            }
    
            public MySequence(IEnumerable<T> items)
            {
                innerList = new List<T>(items);
            }
    
            public T this[int index] => innerList[index];
            public int Count => innerList.Count;
    
            public MySequence<T> AddItem(T item, Func<T, T> func)
            {
                Debug.Assert(innerList.Count > 0);
                innerList.Add(func(item));
                return this;
            }
    
            public IEnumerator<T> GetEnumerator() => innerList.GetEnumerator();
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
    }
    

    Note that I'm using IReadOnlyList to make it possible to index into the list in a performant way and be able to get the last element if needed. If you need a lazy enumeration then I think you are stuck with your original idea.

    And sure enough, the following:

    var a = 1.Starts().Times10Plus(2).Times10Plus(3).Times10Plus(4).ToArray();
    

    Produces the expected result ({1, 12, 123, 1234}) with, what I think is, reasonable performance.