我尝试使用流畅的界面来构建集合,类似于此(简化)示例:
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;
}
答案 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 端。
因此:从实现高效的不可变双端队列开始。这是一个引人入胜的数据类型;我在这里使用手指树实现:
有了这些,您的操作便成为一线工作:
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();