使用LINQ获取IEnumerable中的上一个和下一个项目

Bob*_*hiv 32 c# linq ienumerable

我有一个自定义类型的IEnumerable.(我是从SelectMany那里得到的)

我在IEnumerable中也有一个项目(myItem),我希望IEnumerable中的上一个和下一个项目.

目前,我正在做这样的想法:

var previousItem = myIEnumerable.Reverse().SkipWhile( 
    i => i.UniqueObjectID != myItem.UniqueObjectID).Skip(1).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

我可以通过简单地省略来获得下一个项目.Reverse.

或者,我可以:

int index = myIEnumerable.ToList().FindIndex( 
    i => i.UniqueObjectID == myItem.UniqueObjectID)
Run Code Online (Sandbox Code Playgroud)

然后.ElementAt(index +/- 1)用来获取上一个或下一个项目.

  1. 哪两个选项更好?
  2. 有没有更好的选择?

"更好"包括性能(内存和速度)和可读性的组合; 可读性是我的主要关注点.

Bin*_*ier 39

首先

"更好"包括性能(内存和速度)的组合

一般来说,你不能同时拥有这两者,经验法则是,如果你优化速度,它会花费内存,如果你优化内存,它会花费你的速度.

有一个更好的选择,在内存和速度前端都表现良好,并且可以以可读的方式使用(我对功能名称不满意,但是,FindItemReturningPreviousItemFoundItemAndNextItem有点拗口).

所以,它看起来像是自定义查找扩展方法的时候了...

public static IEnumerable<T> FindSandwichedItem<T>(this IEnumerable<T> items, Predicate<T> matchFilling)
{
    if (items == null)
        throw new ArgumentNullException("items");
    if (matchFilling == null)
        throw new ArgumentNullException("matchFilling");

    return FindSandwichedItemImpl(items, matchFilling);
}

private static IEnumerable<T> FindSandwichedItemImpl<T>(IEnumerable<T> items, Predicate<T> matchFilling)
{
    using(var iter = items.GetEnumerator())
    {
        T previous = default(T);
        while(iter.MoveNext())
        {
            if(matchFilling(iter.Current))
            {
                yield return previous;
                yield return iter.Current;
                if (iter.MoveNext())
                    yield return iter.Current;
                else
                    yield return default(T);
                yield break;
            }
            previous = iter.Current;
        }
    }
    // If we get here nothing has been found so return three default values
    yield return default(T); // Previous
    yield return default(T); // Current
    yield return default(T); // Next
}
Run Code Online (Sandbox Code Playgroud)

如果需要多次引用项目,可以将结果缓存到列表中,但它会返回找到的项目,前面跟上一项,然后是下面的项目.例如

var sandwichedItems = myIEnumerable.FindSandwichedItem(item => item.objectId == "MyObjectId").ToList();
var previousItem = sandwichedItems[0];
var myItem = sandwichedItems[1];
var nextItem = sandwichedItems[2];
Run Code Online (Sandbox Code Playgroud)

如果第一个或最后一个项目可能需要根据您的要求进行更改,则返回默认值.

希望这可以帮助.

  • 感觉有点矫枉过正,但仍然是一个很好的答案. (6认同)
  • 抱歉构建错误和缺少测试,只是想知道该方法应该做什么_I还限制我在SO答案上"工作"的时间:)_你总是可以让它返回一个包含三个项目的元组而不是屈服于项目. (3认同)
  • @Dimitre:现在你已经说过我想把它称为`FindExcentraGallumbits() (2认同)
  • @BinaryWorrier:一个更好的名字:FindTriad. (2认同)

spe*_*der 19

为了便于阅读,我将加载IEnumerable到链接列表中:

var e = Enumerable.Range(0,100);
var itemIKnow = 50;
var linkedList = new LinkedList<int>(e);
var listNode = linkedList.Find(itemIKnow);
var next = listNode.Next.Value; //probably a good idea to check for null
var prev = listNode.Previous.Value; //ditto
Run Code Online (Sandbox Code Playgroud)

  • @novaterata:例如,从“ IEnumerable”创建“队列”将“复制”在“ IEnumerable”中的引用。因此,不会复制IEnumerable指向的对象,但会复制对这些对象的引用。它和序列中的商品一样昂贵。如果这是我需要定期做的事情,我会找到另一种方法。_表示我对链接列表和队列一无所知,但我不喜欢执行不必要的复制_ (2认同)

PHe*_*erg 13

通过创建用于为当前元素建立上下文的扩展方法,您可以使用如下的Linq查询:

var result = myIEnumerable.WithContext()
    .Single(i => i.Current.UniqueObjectID == myItem.UniqueObjectID);
var previous = result.Previous;
var next = result.Next;
Run Code Online (Sandbox Code Playgroud)

扩展名将是这样的:

public class ElementWithContext<T>
{
    public T Previous { get; private set; }
    public T Next { get; private set; }
    public T Current { get; private set; }

    public ElementWithContext(T current, T previous, T next)
    {
        Current = current;
        Previous = previous;
        Next = next;
    }
}

public static class LinqExtensions
{
    public static IEnumerable<ElementWithContext<T>> 
        WithContext<T>(this IEnumerable<T> source)
    {
        T previous = default(T);
        T current = source.FirstOrDefault();

        foreach (T next in source.Union(new[] { default(T) }).Skip(1))
        {
            yield return new ElementWithContext<T>(current, previous, next);
            previous = current;
            current = next;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案是危险的。应避免多次枚举来源不明的“IEnumerable”。它可能会导致对数据库、文件系统或 Web 资源的多次点击。如果必须这样做,请首先使用 [`ToList`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.tolist) 创建一个缓冲区。 (2认同)