我有以下扩展方法来查找序列中的元素,然后返回两个IEnumerable<T>
:一个包含该元素之前的所有元素,一个包含元素和后面的所有元素。我更喜欢这种方法是懒惰的,但我还没有找到办法做到这一点。任何人都可以提出解决方案吗?
public static PartitionTuple<T> Partition<T>(this IEnumerable<T> sequence, Func<T, bool> partition)
{
var a = sequence.ToArray();
return new PartitionTuple<T>
{
Before = a.TakeWhile(v => !partition(v)),
After = a.SkipWhile(v => !partition(v))
};
}
执行sequence.ToArray()
会立即失去懒惰要求。但是,如果没有那条线,昂贵的迭代sequence
可能会重复两次。而且,根据调用代码的作用,可以多次。
答案 0 :(得分:4)
您可以使用Lazy
对象确保在迭代两个分区之一之前,源序列不会转换为数组:
public static PartitionTuple<T> Partition<T>(
this IEnumerable<T> sequence, Func<T, bool> partition)
{
var lazy = new Lazy<IEnumerable<T>>(() => sequence.ToArray());
return new PartitionTuple<T>
{
Before = lazy.MapLazySequence(s => s.TakeWhile(v => !partition(v))),
After = lazy.MapLazySequence(s => s.SkipWhile(v => !partition(v)))
};
}
我们将使用此方法推迟评估延迟,直到迭代序列本身为止:
public static IEnumerable<TResult> MapLazySequence<TSource, TResult>(
this Lazy<IEnumerable<TSource>> lazy,
Func<IEnumerable<TSource>, IEnumerable<TResult>> filter)
{
foreach (var item in filter(lazy.Value))
yield return item;
}
答案 1 :(得分:1)
这是一个有趣的问题,要做到正确,你必须知道“正确”是什么。对于操作的语义,我认为这个定义是有道理的:
我不完全确定我是否正确处理了匹配对象,但我希望你明白这一点。我将很多工作推迟到PartitionTuple<T>
课程,以便能够变得懒惰。
public class PartitionTuple<T>
{
IEnumerable<T> source;
IList<T> before, after;
Func<T, bool> partition;
public PartitionTuple(IEnumerable<T> source, Func<T, bool> partition)
{
this.source = source;
this.partition = partition;
}
private void EnsureMaterialized()
{
if(before == null)
{
before = new List<T>();
after = new List<T>();
using(var enumerator = source.GetEnumerator())
{
while(enumerator.MoveNext() && !partition(enumerator.Current))
{
before.Add(enumerator.Current);
}
while(!partition(enumerator.Current) && enumerator.MoveNext());
while(enumerator.MoveNext())
{
after.Add(enumerator.Current);
}
}
}
}
public IEnumerable<T> Before
{
get
{
EnsureMaterialized();
return before;
}
}
public IEnumerable<T> After
{
get
{
EnsureMaterialized();
return after;
}
}
}
public static class Extensions
{
public static PartitionTuple<T> Partition<T>(this IEnumerable<T> sequence, Func<T, bool> partition)
{
return new PartitionTuple<T>(sequence, partition);
}
}
答案 2 :(得分:1)
这是一个通用的解决方案,它会记住任何IEnumerable<T>
以确保它只迭代一次,而不会强制整个事情迭代:
public class MemoizedEnumerable<T> : IEnumerable<T>, IDisposable
{
private readonly IEnumerator<T> _childEnumerator;
private readonly List<T> _itemCache = new List<T>();
public MemoizedEnumerable(IEnumerable<T> enumerableToMemoize)
{
_childEnumerator = enumerableToMemoize.GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
return _itemCache.Concat(EnumerateOnce()).GetEnumerator();
}
public void Dispose()
{
_childEnumerator.Dispose();
}
private IEnumerable<T> EnumerateOnce()
{
while (_childEnumerator.MoveNext())
{
_itemCache.Add(_childEnumerator.Current);
yield return _childEnumerator.Current;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public static class EnumerableExtensions
{
public static IEnumerable<T> Memoize<T>(this IEnumerable<T> enumerable)
{
return new MemoizedEnumerable<T>(enumerable);
}
}
要将其用于分区问题,请执行以下操作:
var memoized = sequence.Memoize();
return new PartitionTuple<T>
{
Before = memoized.TakeWhile(v => !partition(v)),
After = memoized.SkipWhile(v => !partition(v))
};
这将最多只迭代sequence
一次。
答案 3 :(得分:0)
通常,您只需返回自定义类的某个对象,该对象实现IEnumerable<T>
,但仅提供枚举请求的结果。
您也可以实现IQueryable<T>
(继承IEnumerable)而不是IEnumerable<T>
,但是需要使用linq for sql
提供的查询来构建覆盖功能:正在执行的数据库查询仅在最终的枚举请求中。