如何使用流畅的界面构建序列?
How to build a sequence using a 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;
}
您可以执行以下操作:
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}
),我认为是合理的性能。
你可以这样做:
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();
这个聚会有点晚了,但这里有一些想法。
首先,考虑解决更一般的问题:
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()
然而,它缺少您想要的 "fluency",它假定 相同的 操作应用于序列中的每个元素。
你注意到深度嵌套的迭代器块是有问题的,你是对的;它们在时间和堆栈的线性消耗方面具有二次性能,两者都很糟糕。
这是一种有效实施解决方案的有趣方式。
问题是您需要 both 便宜地访问序列的 "right" 末端,以便对 most 进行操作最近添加了 元素,但您还需要廉价访问序列的 left 末尾以枚举它。普通队列和堆栈只能廉价访问 one 端。
因此:首先要实现一个高效的不可变双端队列。这是一个 迷人的 数据类型;我这里有一个使用手指树的实现:
一旦你有了它,你的操作就是一行:
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();
}
}
我正在尝试使用流畅的界面来构建一个集合,类似于这个(简化的)示例:
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;
}
您可以执行以下操作:
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}
),我认为是合理的性能。
你可以这样做:
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();
这个聚会有点晚了,但这里有一些想法。
首先,考虑解决更一般的问题:
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()
然而,它缺少您想要的 "fluency",它假定 相同的 操作应用于序列中的每个元素。
你注意到深度嵌套的迭代器块是有问题的,你是对的;它们在时间和堆栈的线性消耗方面具有二次性能,两者都很糟糕。
这是一种有效实施解决方案的有趣方式。
问题是您需要 both 便宜地访问序列的 "right" 末端,以便对 most 进行操作最近添加了 元素,但您还需要廉价访问序列的 left 末尾以枚举它。普通队列和堆栈只能廉价访问 one 端。
因此:首先要实现一个高效的不可变双端队列。这是一个 迷人的 数据类型;我这里有一个使用手指树的实现:
一旦你有了它,你的操作就是一行:
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();
}
}