使用 LINQ 获取 IEnumerable 中的上一个和下一个项目
我有一个自定义类型的 IEnumerable。 (我从 SelectMany 获得的)
我在 IEnumerable 中还有一个项目 (myItem),我希望从 IEnumerable 中获取上一个和下一个项目。
目前,我正在做这样的事情:
var previousItem = myIEnumerable.Reverse().SkipWhile(
i => i.UniqueObjectID != myItem.UniqueObjectID).Skip(1).FirstOrDefault();
我可以通过简单地省略 .Reverse
来获取下一个项目。
或者,我可以:
int index = myIEnumerable.ToList().FindIndex(
i => i.UniqueObjectID == myItem.UniqueObjectID)
然后使用 .ElementAt(index +/- 1)
获取上一个或下一个项目。
- 这两个选项之间哪个更好?
- 还有更好的选择吗?
“更好”包括性能(内存和速度)和可读性的结合;可读性是我最关心的问题。
I have an IEnumerable of a custom type. (That I've gotten from a SelectMany)
I also have an item (myItem) in that IEnumerable that I desire the previous and next item from the IEnumerable.
Currently, I'm doing the desired like this:
var previousItem = myIEnumerable.Reverse().SkipWhile(
i => i.UniqueObjectID != myItem.UniqueObjectID).Skip(1).FirstOrDefault();
I can get the next item by simply ommitting the .Reverse
.
or, I could:
int index = myIEnumerable.ToList().FindIndex(
i => i.UniqueObjectID == myItem.UniqueObjectID)
and then use .ElementAt(index +/- 1)
to get the previous or next item.
- Which is better between the two options?
- Is there an even better option available?
"Better" includes a combination of performance (memory and speed) and readability; with readability being my primary concern.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
通过创建一个扩展方法来建立当前元素的上下文,您可以使用如下的 Linq 查询:
扩展将如下所示:
By creating an extension method for establishing context to the current element you can use a Linq query like this:
The extension would be something like this:
您可以将可枚举缓存在列表中,
通过索引对其进行迭代
,然后当前元素是
myList[i]
,前一个元素是myList[i-1]
,并且下一个元素是myList[i+1]
(不要忘记列表中第一个和最后一个元素的特殊情况。)
You could cache the enumerable in a list
iterate over it by index
then the current element is
myList[i]
, the previous element ismyList[i-1]
, and the next element ismyList[i+1]
(Don't forget about the special cases of the first and last elements in the list.)
你真的把事情复杂化了:
有时,仅仅一个
for
循环会更好地做某事,我认为为你想要做的事情提供一个更清晰的实现/You are really over complicating things:
Sometimes just a
for
loop is going to be better to do something, and I think provide a clearer implementation of what you are trying to do/下面是一个 LINQ 扩展方法,它返回当前项以及上一项和下一项。它产生
ValueTuple< /code>
值以避免分配。源被枚举一次。
用法示例:
输出:
要获取特定项目的上一个和下一个,您可以使用 元组解构:
Here is a LINQ extension method that returns the current item, along with the previous and the next. It yields
ValueTuple<T, T, T>
values to avoid allocations. The source is enumerated once.Usage example:
Output:
To get the previous and the next of a specific item, you could use tuple deconstruction:
CPU
完全取决于对象在序列中的位置。如果它位于末尾,我预计第二个会更快,超过 2 倍(但只是一个常数因子)。如果它位于开头,第一个会更快,因为您不需要遍历整个列表。
内存
第一个是迭代序列而不保存序列,因此内存占用会非常小。第二种解决方案将占用与列表长度*引用+对象+开销一样多的内存。
CPU
Depends entirely on where the object is in the sequence. If it is located at the end I would expect the second to be faster with more than a factor 2 (but only a constant factor). If it is located in the beginning the first will be faster because you don't traverse the whole list.
Memory
The first is iterating the sequence without saving the sequence so the memory hit will be very small. The second solution will take as much memory as the length of the list * references + objects + overhead.
我想我会尝试使用 Linq 的 Zip 来回答这个问题。
这将返回一个匿名类型 {previous,current,next}。
不幸的是,这仅适用于索引 1,2 和 3。
此版本适用于所有索引。
两个版本都使用 Linq 提供的惰性求值。
I thought I would try to answer this using Zip from Linq.
This will return a anonymous type {previous,current,next}.
Unfortunately this will only work for indexes 1,2 and 3.
This version will work for all indexes.
Both version use lazy evaluation courtesy of Linq.
这里有一些承诺的扩展方法。这些名称是通用的,可与任何简单类型重用,并且存在查找重载来获取获取下一个或上一个项目所需的项目。我会对解决方案进行基准测试,然后看看在哪里可以挤出周期。
你可以像这样使用它们。
Here are some extension methods as promised. The names are generic and reusable with any type simple and there are lookup overloads to get at the item needed to get the next or previous items. I would benchmark the solutions and then see where you could squeeze cycles out.
And you can use them like this.
我使用以下技术:
产生此结果:
请注意,第一个
Previous
值和最后一个Next
值均为空。它使用以下扩展方法:
I use the following technique:
Which produces this result:
Notice that there are nulls for the first
Previous
value, and the lastNext
value.It uses the following extension method:
如果您需要 myIEnumerable 中的每个元素,我只需迭代它并保留对前 2 个元素的引用。在循环体中,我将对第二个前一个元素进行处理,当前元素将是其后代,第一个元素将是其祖先。
如果您只需要一个元素,我会选择您的第一种方法。
If you need it for every element in myIEnumerable I’d just iterate through it keeping references to the 2 previous elements. In the body of the loop I'd do the processing for the second previous element and the current would be its descendant and first previous its ancestor.
If you need it for only one element I'd choose your first approach.
首先
一般来说,你不能两者兼得,经验法则是,如果你优化速度,它会消耗内存,如果你优化内存,它会消耗内存你速度。
有一个更好的选项,它在内存和速度方面都表现良好,并且可以以可读的方式使用(我对函数名称不满意,但是,
FindItemReturningPreviousItemFoundItemAndNextItem
有点满口)。因此,看起来是时候使用自定义查找扩展方法了,例如 . 。 。
如果您需要多次引用这些项目,您可以将其结果缓存到列表中,但它会返回找到的项目,前面是前一个项目,后面是后面的项目。例如
,如果它是第一个或最后一个项目,则返回的默认值可能需要根据您的要求进行更改。
希望这有帮助。
First off
In general you can't have both, the rule of thumb is, if you optimise for speed, it'll cost memory, if you optimise for memory, it'll cost you speed.
There is a better option, that performs well on both memory and speed fronts, and can be used in a readable manner (I'm not delighted with the function name, however,
FindItemReturningPreviousItemFoundItemAndNextItem
is a bit of a mouthful).So, it looks like it's time for a custom find extension method, something like . . .
You can cache the result of this to a list if you need to refer to the items more than once, but it returns the found item, preceded by the previous item, followed by the following item. e.g.
The defaults to return if it's the first or last item may need to change depending on your requirements.
Hope this helps.
为了便于阅读,我将
IEnumerable
加载到链接列表中:For readability, I'd load the
IEnumerable
into a linked list: