#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();
Это проверит вашу последовательность ровно один раз.