Получить предыдущий и следующий элемент в IEnumerable с помощью LINQ

У меня есть IEnumerable настраиваемого типа. (Это я получил от SelectMany)

У меня также есть элемент (myItem) в этом IEnumerable, который мне нужен предыдущий и следующий элемент из 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) , чтобы получить предыдущий или следующий элемент.

  1. Что лучше из двух вариантов?
  2. Есть ли еще лучший вариант?

«Лучше» включает сочетание производительности (памяти и скорости) и удобочитаемости; с удобочитаемостью, что является моей главной заботой.

32
задан user270014 6 January 2012 в 18:41
поделиться

2 ответа

Вот некоторые дополнительные методы, как обещано. Названия родовые и повторно используемые с любым простым типом и есть перегрузки поиска, чтобы достигнуть товар, должен был получить следующие или предыдущие товары. Я определил бы эффективность решений и затем видел бы, где Вы могли отжать циклы.

 public static class ExtensionMethods  
{
    public static T Previous<T>(this List<T> list, T item) { 
        var index = list.IndexOf(item) - 1;
        return index > -1 ? list[index] : default(T);
    }
    public static T Next<T>(this List<T> list, T item) {
        var index = list.IndexOf(item) + 1;
        return index < list.Count() ? list[index] : default(T);
    }
    public static T Previous<T>(this List<T> list, Func<T, Boolean> lookup) { 
        var item = list.SingleOrDefault(lookup);
        var index = list.IndexOf(item) - 1;
        return index > -1 ? list[index] : default(T);
    }
    public static T Next<T>(this List<T> list, Func<T,Boolean> lookup) {
        var item = list.SingleOrDefault(lookup);
        var index = list.IndexOf(item) + 1;
        return index < list.Count() ? list[index] : default(T);
    }
    public static T PreviousOrFirst<T>(this List<T> list, T item) { 
        if(list.Count() < 1) 
            throw new Exception("No array items!");

        var previous = list.Previous(item);
        return previous == null ? list.First() : previous;
    }
    public static T NextOrLast<T>(this List<T> list, T item) { 
        if(list.Count() < 1) 
            throw new Exception("No array items!");
        var next = list.Next(item);
        return next == null ? list.Last() : next;
    }
    public static T PreviousOrFirst<T>(this List<T> list, Func<T,Boolean> lookup) { 
        if(list.Count() < 1) 
            throw new Exception("No array items!");
        var previous = list.Previous(lookup);
        return previous == null ? list.First() : previous;
    }
    public static T NextOrLast<T>(this List<T> list, Func<T,Boolean> lookup) { 
        if(list.Count() < 1) 
            throw new Exception("No array items!");
        var next = list.Next(lookup);
        return next == null ? list.Last() : next;
    }
}

И Вы можете использовать их как это.

var previous = list.Previous(obj);
var next = list.Next(obj);
var previousWithLookup = list.Previous((o) => o.LookupProperty == otherObj.LookupProperty);
var nextWithLookup = list.Next((o) => o.LookupProperty == otherObj.LookupProperty);
var previousOrFirst = list.PreviousOrFirst(obj);
var nextOrLast = list.NextOrLast(ob);
var previousOrFirstWithLookup = list.PreviousOrFirst((o) => o.LookupProperty == otherObj.LookupProperty);
var nextOrLastWithLookup = list.NextOrLast((o) => o.LookupProperty == otherObj.LookupProperty);
0
ответ дан 27 November 2019 в 20:09
поделиться

Вот дополнительный метод LINQ, который возвращает текущий объект, наряду с предыдущим и следующим. Это уступает ValueTuple типы для предотвращения выделений. Источник перечисляется однажды.

public static IEnumerable<(T Previous, T Current, T Next)> WithPreviousAndNext<T>(
    this IEnumerable<T> source, T firstPrevious = default, T lastNext = default)
{
    Queue<T> queue = new Queue<T>(2);
    queue.Enqueue(firstPrevious);
    foreach (var item in source)
    {
        if (queue.Count > 1)
        {
            yield return (queue.Dequeue(), queue.Peek(), item);
        }
        queue.Enqueue(item);
    }
    if (queue.Count > 1) yield return (queue.Dequeue(), queue.Peek(), lastNext);
}

пример Использования:

var source = Enumerable.Range(1, 5);
Console.WriteLine($"Source: {String.Join(", ", source)}");
var result = source.WithPreviousAndNext(firstPrevious: -1, lastNext: -1);
Console.WriteLine($"Result: {String.Join(", ", result)}");

Вывод:

Источник: 1, 2, 3, 4, 5
Результат: (-1, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5,-1)

Для получения предыдущего и следующего из определенного объекта (использующий разрушение кортежа ):

var (previous, current, next) = myIEnumerable
    .WithPreviousAndNext()
    .First(e => e.Current.UniqueObjectID == myItem.UniqueObjectID);
0
ответ дан 27 November 2019 в 20:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: