如何使用流畅的界面构建序列?

时间:2017-08-13 15:55:42

标签: c# .net algorithm linq fluent-interface

我尝试使用流畅的界面来构建集合,类似于此(简化)示例:

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

我能想出的最佳解决方案是将Add()添加为:

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

这似乎增加了很多在每次通话中重复的开销。

有更好的方法吗?

更新: 在我的匆忙中,我过度简化了这个例子,并遗漏了一个重要的要求。现有coll中的最后一项影响下一项。所以,一个稍微简化的例子:

   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;
}

3 个答案:

答案 0 :(得分:2)

这次聚会有点晚了,但是这里有一些想法。

首先,考虑解决更普遍的问题:

public static IEnumerable<A> AggregateSequence<S, A>(
  this IEnumerable<S> items,
  A initial,
  Func<A, R, A> f) 
{
  A accumulator = initial;
  yield return accumulator;
  foreach(S item in items)
  {
    accumulator = f(accumulator, item);
    yield return accumulator;
  }
}

现在您的程序就是new[]{2, 3, 4}.AggregateSequence(1, (a, s) => a * 10 + s).ToArray()

但是缺少所需的“流利性”,并且假定相同操作应用于序列中的每个元素。

您应该注意到深度嵌套的迭代器块是有问题的;它们在时间和堆栈线性消耗方面都具有二次性能,两者都很糟糕。

这是一种有效实施解决方案的有趣方式。

问题是您需要两者便宜地访问序列的“右”端,以便对最近添加的元素进行操作,但是您还需要廉价访问序列的 left 末尾以进行枚举。普通队列和堆栈仅廉价访问 one 端。

因此:从实现高效的不可变双端队列开始。这是一个引人入胜的数据类型;我在这里使用手指树实现:

https://blogs.msdn.microsoft.com/ericlippert/2008/01/22/immutability-in-c-part-10-a-double-ended-queue/

https://blogs.msdn.microsoft.com/ericlippert/2008/02/12/immutability-in-c-part-eleven-a-working-double-ended-queue/

有了这些,您的操作便成为一线工作:

static IDeque<T> StartWith<T>(T t) => Deque<T>.Empty.EnqueueRight(t);
static IDeque<T> Op<T>(this IDeque<T> d, Func<T, T> f) => d.EnqueueRight(f(d.PeekRight()));
static IDeque<int> Times10Plus(this IDeque<int> d, int j) => d.Op(i => i * 10 + j);

以明显的方式修改IDeque<T>Deque<T>以实现IEnumerable<T>,然后免费获得ToArray。或将其作为扩展方法:

static IEnumerable<T> EnumerateFromLeft(this IDeque<T> d)
{
  var c = d;
  while (!c.IsEmpty) 
  { 
    yield return c.PeekLeft(); 
    c = c.DequeueLeft(); 
  }
}

答案 1 :(得分:1)

您可以执行以下操作:

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();
    }
}

请注意,我正在使用IReadOnlyList来以高效的方式索引列表,并且能够在需要时获取最后一个元素。如果你需要一个懒惰的枚举,那么我认为你坚持原来的想法。

果然,以下内容:

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

产生预期结果({1, 12, 123, 1234}),我认为是合理的表现。

答案 2 :(得分:0)

您可以这样做:

public interface ISequence
{
    ISequenceOp StartWith(int i);
}

public interface ISequenceOp
{
    ISequenceOp Times10Plus(int i);
    int[] ToArray();
}

public class Sequence : ISequence
{
    public ISequenceOp StartWith(int i)
    {
        return new SequenceOp(i);
    }
}

public class SequenceOp : ISequenceOp
{
    public List<int> Sequence { get; set; }

    public SequenceOp(int startValue)
    {
        Sequence = new List<int> { startValue };
    }

    public ISequenceOp Times10Plus(int i)
    {
        Sequence.Add(Sequence.Last() * 10 + i);
        return this;
    }

    public int[] ToArray()
    {
        return Sequence.ToArray();
    }
}

然后就是:

var x = new Sequence();
var a = x.StartWith(1).Times10Plus(2).Times10Plus(3).Times10Plus(4).ToArray();