从IEnumerable获取头部和尾部,只能迭代一次

时间:2011-03-09 12:22:27

标签: c# linq ienumerable

我有一系列元素。该序列只能迭代一次并且可以是“无限的”。

获得这样一个序列的头部和尾部的最佳方法是什么?

5 个答案:

答案 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。