从只能迭代一次的 IEnumerable 获取头和尾
我有一系列元素。该序列只能迭代一次并且可以是“无限”的。
获取此类序列的头部和尾部的最佳方法是什么?
更新:如果我将其包含在原始问题中,那就太好了:)
Head 是序列的第一个元素,tail 是“其余的”。这意味着尾巴也是“无限”的。
当我说无限时,我的意思是“非常大”和“我不想一次将其全部存储在内存中”。它实际上也可能是无限的,例如传感器数据(但在我的情况下不是)。
当我说它只能迭代一次时,我的意思是生成序列需要大量资源,所以我不想再这样做。它也可能是易失性数据,再次像传感器数据一样,在下次读取时不会相同(但在我的情况下不是)。
I have a sequence of elements. The sequence can only be iterated once and can be "infinite".
What is the best way get the head and the tail of such a sequence?
Update: A few clarifications that would have been nice if I included in the original question :)
Head is the first element of the sequence and tail is "the rest". That means the the tail is also "infinite".
When I say infinite, I mean "very large" and "I wouldn't want to store it all in memory at once". It could also have been actually infinite, like sensor data for example (but it wasn't in my case).
When I say that it can only be iterated once, I mean that generating the sequence is resource heavy, so I woundn't want to do it again. It could also have been volatile data, again like sensor data, that won't be the same on next read (but it wasn't in my case).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
将
IEnumerable
分解为 head & tail 对于递归处理来说并不是特别好(与函数列表不同),因为当您递归地使用 tail 操作时,您将创建许多间接寻址。但是,您可以编写如下内容:我忽略了参数检查和异常处理等内容,但它显示了这个想法...
HeadAndTail
方法获取第一个元素并将其作为第一个元素返回元组的。元组的第二个元素是 IEnumerableDecomposing
IEnumerable<T>
into head & tail isn't particularly good for recursive processing (unlike functional lists) because when you use the tail operation recursively, you'll create a number of indirections. However, you can write something like this:I'm ignoring things like argument checking and exception handling, but it shows the idea...
The
HeadAndTail
method gets the first element and returns it as the first element of a tuple. The second element of a tuple isIEnumerable<T>
that's generated from the remaining elements (by iterating over the rest of the enumerator that we already created).显然,每次调用 HeadAndTail 都应该再次枚举序列(除非使用某种缓存)。例如,请考虑以下情况:
出于同样的原因,HeadAndTail 无法实现为单独的 Head 和 Tail 方法(除非您甚至想要首先调用 Tail 再次枚举序列,即使它已经通过调用 Head 枚举过)。
此外,HeadAndTail 不应返回 IEnumerable 的实例(因为它可能会被枚举多次)。
这给我们留下了唯一的选择:HeadAndTail应该返回IEnumerator,并且,为了让事情变得更明显,它也应该接受IEnumerator(我们只是将 GetEnumerator 的调用从 HeadAndTail 内部移动到外部,以强调它仅供一次性使用)。
现在我们已经解决了需求,实现就非常简单了:
现在它可以像这样使用:
或者在像这样的递归函数中:
Obviously, each call to HeadAndTail should enumerate the sequence again (unless there is some sort of caching used). For example, consider the following:
For the same reason, HeadAndTail could not be implemented as separate Head and Tail methods (unless you want even the first call to Tail to enumerate the sequence again even if it was already enumerated by a call to Head).
Additionally, HeadAndTail should not return an instance of IEnumerable (as it could be enumerated multiple times).
This leaves us with the only option: HeadAndTail should return IEnumerator, and, to make things more obvious, it should accept IEnumerator as well (we're just moving an invocation of GetEnumerator from inside the HeadAndTail to the outside, to emphasize it is of one-time use only).
Now that we have worked out the requirements, the implementation is pretty straightforward:
And now it can be used like this:
Or in recursive functions like this:
虽然此处的其他方法建议对
tail
枚举使用yield return
,但这种方法会增加不必要的嵌套开销。更好的方法是将Enumerator
转换回可以与foreach
一起使用的东西:如果使用单独的
WrappedEnumerator
结构通用IEnumerable
和非通用IEnumerable
,可以让它们实现IEnumerable
和IEnumerable
> 分别;不过,他们不会真正遵守IEnumerable
约定,该约定指定应该可以多次调用GetEnumerator()
,并且每次调用都会返回一个独立的枚举器。另一个重要的警告是,如果在
IEnumerator
上使用AsForEach
,则应准确枚举生成的WrappedEnumerator
。 > 一次。如果从未枚举它,则底层IEnumerator
将永远不会调用其Dispose
方法。将上面提供的方法应用于当前的问题,可以轻松地在
IEnumerable
上调用GetEnumerator()
,读出前几项,然后然后使用AsForEach()
转换余数,以便可以将其与ForEach
循环一起使用(或者,如上所述,将其转换为的实现) IEnumerable
)。但值得注意的是,调用GetEnumerator()
会产生Dispose
生成的IEnumerator
的义务,以及执行该操作的类如果没有任何东西在尾部调用GetEnumerator()
,那么头/尾分割将无法做到这一点。While other approaches here suggest using
yield return
for thetail
enumerable, such an approach adds unnecessary nesting overhead. A better approach would be to convert theEnumerator<T>
back into something that can be used withforeach
:If one used separate
WrappedEnumerator
structs for the genericIEnumerable<T>
and non-genericIEnumerable
, one could have them implementIEnumerable<T>
andIEnumerable
respectively; they wouldn't really obey theIEnumerable<T>
contract, though, which specifies that it should be possible to possible to callGetEnumerator()
multiple times, with each call returning an independent enumerator.Another important caveat is that if one uses
AsForEach
on anIEnumerator<T>
, the resultingWrappedEnumerator
should be enumerated exactly once. If it is never enumerated, the underlyingIEnumerator<T>
will never have itsDispose
method called.Applying the above-supplied methods to the problem at hand, it would be easy to call
GetEnumerator()
on anIEnumerable<T>
, read out the first few items, and then useAsForEach()
to convert the remainder so it can be used with aForEach
loop (or perhaps, as noted above, to convert it into an implementation ofIEnumerable<T>
). It's important to note, however, that callingGetEnumerator()
creates an obligation toDispose
the resultingIEnumerator<T>
, and the class that performs the head/tail split would have no way to do that if nothing ever callsGetEnumerator()
on the tail.可能不是最好的方法,但如果您使用
.ToList()
方法,您可以获取位置[0]
和[Count- 1]
,如果计数> 0.但是您应该指定“只能迭代一次”是什么意思
probably not the best way to do it but if you use the
.ToList()
method you can then get the elements in position[0]
and[Count-1]
, if Count > 0.But you should specify what do you mean by "can be iterated only once"
.First()
和.Last()
到底有什么问题?虽然是的,我必须同意那些问“无限列表的尾部意味着什么”的人......这个概念没有意义,IMO。What exactly is wrong with
.First()
and.Last()
? Though yeah, I have to agree with the people who asked "what does the tail of an infinite list mean"... the notion doesn't make sense, IMO.