Сортировать список и выбирать объект на основе порядка по

#c# #list #linq

#c# #Список #linq

Вопрос:

У меня есть поля List<MyClass> with Status и Date . Я хочу вернуть один, MyClass где Status = X .

Проблема в том, что в списке может быть больше одного, и в этом случае я хочу отсортировать по Date и вернуть последний.

Можно ли это сделать в LINQ в одном выражении?

Комментарии:

1. list.Where(x => x.Status == X).OrderBy(x => x.Date).Last();

2. Или MaxyBy из MoreLINQ: list. Где(x => x.Status == X). maxBy(x => x.Date). First()

3. @SlavaUtesinov Вам лучше добавить свое предложение в качестве ответа

Ответ №1:

Вы можете использовать лямбда-выражение:

 var yourResult = dbo.YourList.OrderByDescending(t=>t.Date).FirstOrDefault(t=>t.Status == 'X');
  

Комментарии:

1. В чем смысл сначала сортировать и только потом делать выбор? Что произойдет, если будет 1 МЛН записей?

2. Это сэкономит одну дополнительную проверку, однако для большего списка это может незначительно снизить производительность.

3. предположим, что 1 МЛН записей имеют 90% статуса ‘X’.

4. Не забывайте, что проверка в цикле — это O(N) операция, в то время как лучшие алгоритмы сортировки имеют сложность O(N*log(N))

Ответ №2:

Если list он уже находится в памяти, попробуйте это:

 var answer = list.Where(x => x.Status == X).OrderBy(x => x.Date).LastOrDefault();
  

В случае Entity Framework попробуйте другой подход:

 var answer = context.Table.Where(x => x.Status == X).OrderByDescending(x => x.Date).FirstOrDefault();
  

Комментарии:

1. Это просто детали реализации.

Ответ №3:

Итак, у вас есть последовательность MyClass объектов и объект X , и вы хотите найти самый новый MyClass объект, значение для которого Status равно X

 var result = myList.Where(myItem => myItem.Status == X)
                   .OrderByDescending(myItem => myItem.Date)
                   .FirstOrDefault();
  

Хотя это сработает, сортировка не очень эффективна: после того, как вы нашли первый элемент, он сортирует 2-й, 3-й и т.д., В то время как вы знаете, что он не будет использовать эти другие элементы, так зачем их сортировать? Если вы используете Aggregate , вам нужно будет перечислить только один раз

 var result = myList.Where(...)
    .Aggregate( (newestItem, nextItem) => (newestItem.Date < nextItem.Date) ? 
           newestItem : nextItem);
  

Это помещает первый элемент в newestItem и сканирует остальную часть списка. Если какой-либо nextItem содержит новые данные, то поместите этот следующий элемент в newestItem, в противном случае не меняйте newestItem. В конце верните newestItem, который является тем, у которого самая последняя дата, которую вы хотите.

Это работает, только если вы уверены, что после where остался хотя бы один элемент. Если это также должно работать с пустыми списками, рассмотрите возможность создания функции расширения:

 static TResult GetNewestOrDefault<TSource, TResult>(this IEnumerable<TSource> source,
   Func<Tsource, DateTime> dateSelector)
{
    var enumerator = source.GetEnumerator();

    if (enumerator.MoveNext())
    {   // at least one element; initialize newest:
        TSource newestElement = enumerator.Current;
        DateTime newestDate = dateSelector(newest);

        // scan the rest of the sequence and check if newer:
        while (enumerator.MoveNext())
        {
            Tsource nextElement = enumerator.Current;
            Datetime nextDate = dateSelector(nextElement);

            // if next element has a newer date, remember it as newest:

            if (newestDate < nextDate
            {
                newestElement = nextElement;
                newestDate = nextDate,
            }
        }
        // scanned all elements exactly once
        return newestElement;
    }
    else
       // empty sequence, return default
       return default(TResult);
}
  

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

 MyClass result = myList.Where(myItem => myItem.Status == X)
                       .GetNewestOrDefault();
  

Это проверит вашу последовательность ровно один раз.