Jak za pomocą LINQ wybrać obiekt z minimalną lub maksymalną wartością właściwości

Mam obiekt Person z zerową datą własności. Czy jest sposób na użycie LINQ do zapytania listy obiektów Person dla tego z najwcześniejszą / najmniejszą wartością Dateoffice.

Oto od czego zacząłem:

var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));

Null DateOfBirth wartości są ustawiane na DateTime.MaxValue w celu wykluczenia ich z min (zakładając, że przynajmniej jeden ma określony DOB).

Ale wszystko, co dla mnie robi, to ustawić firstBornDate na wartość DateTime. Co chciałbym dostać jest obiektem, który odpowiada temu. Czy muszę napisać drugie zapytanie w ten sposób:

var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
Czy jest na to szczuplejszy sposób?
 370
Author: Chris Marisic, 2009-05-27

12 answers

People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) <
    curMin.DateOfBirth ? x : curMin))
 249
Author: Paul Betts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-24 10:31:06

Niestety nie ma wbudowanej metody, aby to zrobić.

PM> Install-Pakiet morelinq

var firstBorn = People.MinBy(p => p.DateOfBirth ?? DateTime.MaxValue);

Alternatywnie możesz użyć implementacji, którą mamy w MoreLINQ , W MinBy.cs . (Jest odpowiedni MaxBy, oczywiście.) Oto bebechy:

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector)
{
    return source.MinBy(selector, null);
}

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException("source");
    if (selector == null) throw new ArgumentNullException("selector");
    comparer = comparer ?? Comparer<TKey>.Default;

    using (var sourceIterator = source.GetEnumerator())
    {
        if (!sourceIterator.MoveNext())
        {
            throw new InvalidOperationException("Sequence contains no elements");
        }
        var min = sourceIterator.Current;
        var minKey = selector(min);
        while (sourceIterator.MoveNext())
        {
            var candidate = sourceIterator.Current;
            var candidateProjected = selector(candidate);
            if (comparer.Compare(candidateProjected, minKey) < 0)
            {
                min = candidate;
                minKey = candidateProjected;
            }
        }
        return min;
    }
}

Zwróci wyjątek, jeśli sekwencja jest pusta, i zwróci pierwszy element z minimalną wartością, jeśli jest więcej niż jeden.

 189
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-12 20:38:02

Uwaga: załączam tę odpowiedź dla kompletności, ponieważ po nie wspomniano, co jest źródłem danych i nie powinniśmy żadnych założeń.

To zapytanie daje poprawną odpowiedź, ale Może być wolniejsze, ponieważ może być konieczne sortowanie wszystkich pozycji w People, w zależności od struktury danych People:

var oldest = People.OrderBy(p => p.DateOfBirth ?? DateTime.MaxValue).First();

UPDATE: właściwie nie powinienem nazywać tego rozwiązania "naiwnym" , ale użytkownik musi wiedzieć, o co pyta. Tego rozwiązania "powolność" zależy od podstawowych danych. Jeśli jest to tablica lub List<T>, to LINQ to Objects nie ma innego wyboru, jak najpierw posortować całą kolekcję przed zaznaczeniem pierwszej pozycji. W tym przypadku będzie to wolniejsze niż inne rozwiązanie sugerowane. Jeśli jednak jest to tabela LINQ do SQL i DateOfBirth jest indeksowaną kolumną, to SQL Server użyje indeksu zamiast sortowania wszystkich wierszy. Inne niestandardowe implementacje IEnumerable<T> mogą również korzystać z indeksów (patrz i4o: Indexed LINQ , lub obiekt bazy danych db4o ) i uczynić to rozwiązanie szybszym niż Aggregate() lub MaxBy()/MinBy() które muszą jednorazowo odtworzyć całą kolekcję. W rzeczywistości LINQ to Objects mogło (w teorii) tworzyć specjalne przypadki w OrderBy() dla sortowanych zbiorów, takich jak SortedList<T>, ale tak nie jest, o ile mi wiadomo.

 103
Author: Lucas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-27 18:28:17
People.OrderBy(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)).First()

Zrobiłby sztuczkę

 58
Author: Rune FS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-20 18:33:57

Więc prosisz o ArgMin lub ArgMax. C# nie ma do nich wbudowanego API.

[[20]} Szukałem czystego i skutecznego (O(N) w czasie) sposobu, aby to zrobić. I chyba znalazłem:

Ogólna forma tego wzoru to:

var min = data.Select(x => (key(x), x)).Min().Item2;
                            ^           ^       ^
              the sorting key           |       take the associated original item
                                Min by key(.)

Specjalnie, używając przykładu w pytaniu oryginalnym:

Dla C # 7.0 i nowszych, które obsługują krotkę wartości :

var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;

Dla wersji C# przed 7.0 można użyć typu anonymous zamiast:

var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;

Działają, ponieważ zarówno typ krotki wartości, jak i typ anonimowy mają sensowne domyślne porównywanie: for (x1, y1) i (x2, y2), najpierw porównuje x1 vs x2, Następnie y1 vs y2. Dlatego wbudowany .Min może być używany na tych typach.

A ponieważ zarówno anonimowy typ, jak i krotka wartości są typami wartości, powinny być bardzo wydajne.

Uwaga

W moich powyższych implementacjach ArgMin założyłem DateOfBirth, aby przyjąć Typ DateTime dla prostota i przejrzystość. Pierwotne pytanie prosi o wykluczenie tych wpisów z polem null DateOfBirth:

Null dateofbirth wartości są ustawiane na DateTime.MaxValue w celu wykluczenia ich z min (zakładając, że przynajmniej jeden ma określony DOB).

Można to osiągnąć za pomocą wstępnego filtrowania
people.Where(p => p.DateOfBirth.HasValue)

Więc nie ma znaczenia kwestia implementacji ArgMin czy ArgMax.

Uwaga 2

Powyższe podejście ma zastrzeżenie jeśli istnieją dwie instancje o tej samej wartości minimalnej, implementacja Min() spróbuje porównać instancje jako tie-breaker. Jeśli jednak Klasa instancji nie zaimplementuje IComparable, to zostanie wyświetlony błąd runtime:

Co najmniej jeden obiekt musi zaimplementować IComparable

Na szczęście nadal można to naprawić dość czysto. Chodzi o to, aby skojarzyć odległość "ID" z każdym wpisem, który służy jako jednoznaczny tie-breaker. Możemy użyj przyrostowego ID dla każdego wpisu. Nadal używa ludzi wieku jako przykładu:

var youngest = Enumerable.Range(0, int.MaxValue)
               .Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;
 11
Author: KFL,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-27 18:05:44
public class Foo {
    public int bar;
    public int stuff;
};

void Main()
{
    List<Foo> fooList = new List<Foo>(){
    new Foo(){bar=1,stuff=2},
    new Foo(){bar=3,stuff=4},
    new Foo(){bar=2,stuff=3}};

    Foo result = fooList.Aggregate((u,v) => u.bar < v.bar ? u: v);
    result.Dump();
}
 4
Author: JustDave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-23 13:01:02

Nie sprawdzałem, ale to musi zrobić oczekiwaną rzecz:

var itemWithMaxValue = SomeListOfClass.OrderByDescending(i => i.SomeFloat).FirstOrDefault();

I min:

var itemWithMinValue = SomeListOfClass.OrderByDescending(i => i.SomeFloat).LastOrDefault();
 2
Author: Andrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-02 03:24:38

Sprawdziliśmy to tylko dla Entity framework 6.0.0>: można to zrobić:

var MaxValue = dbContext.YourDataClass.Select(x => x.ColumnToFindMaxValueFrom).Max();
var MinValue = dbContext.YourDataClass.Select(x => x.ColumnToFindMinValueFrom).Min();
 2
Author: netfed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-30 01:01:22

Poniżej znajduje się bardziej ogólne rozwiązanie. Zasadniczo robi to samo(w porządku O (N)), ale na dowolnych typach IEnumberable i może być mieszany z typami, których selektory właściwości mogą zwracać null.

public static class LinqExtensions
{
    public static T MinBy<T>(this IEnumerable<T> source, Func<T, IComparable> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }
        if (selector == null)
        {
            throw new ArgumentNullException(nameof(selector));
        }
        return source.Aggregate((min, cur) =>
        {
            if (min == null)
            {
                return cur;
            }
            var minComparer = selector(min);
            if (minComparer == null)
            {
                return cur;
            }
            var curComparer = selector(cur);
            if (curComparer == null)
            {
                return min;
            }
            return minComparer.CompareTo(curComparer) > 0 ? cur : min;
        });
    }
}

Testy:

var nullableInts = new int?[] {5, null, 1, 4, 0, 3, null, 1};
Assert.AreEqual(0, nullableInts.MinBy(i => i));//should pass
 2
Author: Zafar Ameem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 20:57:11

EDIT again:

Przepraszam. Poza pominięciem nullable patrzyłem na złą funkcję,

Min)>)) (IEnumerable)>)), Func)>)) zwraca typ wyniku, jak powiedziałeś.

Powiedziałbym, że jednym z możliwych rozwiązań jest zaimplementowanie IComparable i użycie Min)>) (IEnumerable)>)), co naprawdę zwraca element z liczby mnogiej. Oczywiście, to nie pomaga. ty, jeśli nie możesz zmodyfikować elementu. Uważam, że projekt MS jest trochę dziwny.

Oczywiście zawsze możesz wykonać pętlę for, jeśli zajdzie taka potrzeba, lub użyć implementacji MoreLINQ, którą dał Jon Skeet.

 0
Author: Matthew Flaschen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-27 08:23:16

Sam szukałem czegoś podobnego, najlepiej bez korzystania z biblioteki lub sortowania całej listy. Moje rozwiązanie skończyło się podobnie do samego pytania, tylko trochę uproszczone.

var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == People.Min(p2 => p2.DateOfBirth));
 0
Author: Are,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-26 08:59:33

Aby uzyskać maksimum lub min właściwości z tablicy obiektów:

Utwórz listę, która przechowuje każdą wartość właściwości:

list<int> values = new list<int>;

Dodaj wszystkie wartości właściwości do listy:

foreach (int i in obj.desiredProperty)
{    values.add(i);  }

Pobierz max lub min z listy:

int Max = values.Max;
int Min = values.Min;

Teraz możesz zapętlić tablicę obiektów i porównać wartości właściwości, które chcesz sprawdzić z max lub min int:

foreach (obj o in yourArray)
{
    if (o.desiredProperty == Max)
       {return o}

    else if (o.desiredProperty == Min)
        {return o}
}
 -2
Author: David Edel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-13 12:34:16