我有一系列元素。该序列只能迭代一次并且可以是“无限的”。
获得这样一个序列的头部和尾部的最佳方法是什么?
答案 0 :(得分:23)
将IEnumerable<T>
分解为头部&amp; tail不是特别适合递归处理(与功能列表不同),因为当你递归地使用 tail 操作时,你将创建许多间接。但是,你可以这样写:
我忽略了参数检查和异常处理之类的事情,但它显示了这个想法......
Tuple<T, IEnumerable<T>> HeadAndTail<T>(IEnumerable<T> source) {
// Get first element of the 'source' (assuming it is there)
var en = source.GetEnumerator();
en.MoveNext();
// Return first element and Enumerable that iterates over the rest
return Tuple.Create(en.Current, EnumerateTail(en));
}
// Turn remaining (unconsumed) elements of enumerator into enumerable
IEnumerable<T> EnumerateTail<T>(IEnumerator en) {
while(en.MoveNext()) yield return en.Current;
}
HeadAndTail
方法获取第一个元素并将其作为元组的第一个元素返回。元组的第二个元素是IEnumerable<T>
,它是从剩余的元素生成的(通过迭代我们已经创建的其余枚举数)。
答案 1 :(得分:1)
虽然此处的其他方法建议将yield return
用于tail
枚举,但这种方法会增加不必要的嵌套开销。更好的方法是将Enumerator<T>
转换回可以与foreach
一起使用的内容:
public struct WrappedEnumerator<T>
{
T myEnumerator;
public T GetEnumerator() { return myEnumerator; }
public WrappedEnumerator(T theEnumerator) { myEnumerator = theEnumerator; }
}
public static class AsForEachHelper
{
static public WrappedEnumerator<IEnumerator<T>> AsForEach<T>(this IEnumerator<T> theEnumerator) {return new WrappedEnumerator<IEnumerator<T>>(theEnumerator);}
static public WrappedEnumerator<System.Collections.IEnumerator> AsForEach(this System.Collections.IEnumerator theEnumerator)
{ return new WrappedEnumerator<System.Collections.IEnumerator>(theEnumerator); }
}
如果对通用WrappedEnumerator
和非通用IEnumerable<T>
使用单独的IEnumerable
结构,则可以让它们分别实现IEnumerable<T>
和IEnumerable
;但是,它们不会真正遵守IEnumerable<T>
合同,它指定应该可以多次调用GetEnumerator()
,每次调用都返回一个独立的枚举器。
另一个重要警告是,如果在AsForEach
上使用IEnumerator<T>
,则生成的WrappedEnumerator
应该完全一次。如果从未枚举,则基础IEnumerator<T>
将永远不会调用其Dispose
方法。
将上述提供的方法应用于手头的问题,可以很容易地在GetEnumerator()
上调用IEnumerable<T>
,读出前几项,然后使用AsForEach()
来转换余数,使其可以与ForEach
循环一起使用(或者,如上所述,将其转换为IEnumerable<T>
的实现)。然而,重要的是要注意,调用GetEnumerator()
会产生Dispose
生成的IEnumerator<T>
的义务,并且执行头/尾分割的类将无法做到这一点,如果没有曾经在尾巴上调用GetEnumerator()
。
答案 2 :(得分:1)
显然,每次调用 HeadAndTail 都应该再次枚举序列(除非使用了某种缓存)。例如,请考虑以下事项:
var a = HeadAndTail(sequence);
Console.WriteLine(HeadAndTail(a.Tail).Tail);
//Element #2; enumerator is at least at #2 now.
var b = HeadAndTail(sequence);
Console.WriteLine(b.Tail);
//Element #1; there is no way to get #1 unless we enumerate the sequence again.
出于同样的原因, HeadAndTail 无法作为单独的 Head 和 Tail 方法实现(除非你想要第一次调用< strong> Tail 再次枚举序列,即使它已经通过调用 Head 进行了枚举。
此外, HeadAndTail 不应返回 IEnumerable 的实例(因为它可以多次枚举)。
这给我们留下了唯一的选择: HeadAndTail 应该返回 IEnumerator ,为了使事情更加明显,它应该接受 IEnumerator 作为好吧(我们只是将 GetEnumerator 的调用从 HeadAndTail 内部移到外面,强调它只能一次性使用。)
现在我们已经制定了要求,实现非常简单:
class HeadAndTail<T> {
public readonly T Head;
public readonly IEnumerator<T> Tail;
public HeadAndTail(T head, IEnumerator<T> tail) {
Head = head;
Tail = tail;
}
}
static class IEnumeratorExtensions {
public static HeadAndTail<T> HeadAndTail<T>(this IEnumerator<T> enumerator) {
if (!enumerator.MoveNext()) return null;
return new HeadAndTail<T>(enumerator.Current, enumerator);
}
}
现在它可以像这样使用:
Console.WriteLine(sequence.GetEnumerator().HeadAndTail().Tail.HeadAndTail().Head);
//Element #2
或者像这样的递归函数:
TResult FoldR<TSource, TResult>(
IEnumerator<TSource> sequence,
TResult seed,
Func<TSource, TResult, TResult> f
) {
var headAndTail = sequence.HeadAndTail();
if (headAndTail == null) return seed;
return f(headAndTail.Head, FoldR(headAndTail.Tail, seed, f));
}
int Sum(IEnumerator<int> sequence) {
return FoldR(sequence, 0, (x, y) => x+y);
}
var array = Enumerable.Range(1, 5);
Console.WriteLine(Sum(array.GetEnumerator())); //1+(2+(3+(4+(5+0)))))
答案 3 :(得分:-1)
可能不是最好的方法,但如果您使用.ToList()
方法,那么您可以将元素放在位置[0]
和[Count-1]
中,如果Count&gt; 0
但你应该指明“只能迭代一次”是什么意思
答案 4 :(得分:-1)
.First()
和.Last()
到底出了什么问题?虽然是的,我必须同意那些问“无限列表的尾部是什么意思”的人......这个概念没有意义,IMO。