Quindi stai chiedendo ArgMinoArgMax . C # non ha un'API integrata per quelli.
Ho cercato un modo pulito ed efficiente (O (n) in tempo) per farlo. E penso di averne trovato uno:
La forma generale di questo modello è:
var min = data.Select(x => (key(x), x)).Min().Item2;
^ ^ ^
the sorting key | take the associated original item
Min by key(.)
In particolare, usando l'esempio nella domanda originale:
Per C # 7.0 e versioni successive che supportano la tupla valore :
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
Per la versione C # precedente alla 7.0, è possibile utilizzare invece il tipo anonimo :
var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;
Funzionano perché sia la tupla di valore che il tipo anonimo hanno comparatori predefiniti ragionevoli: per (x1, y1) e (x2, y2), confronta prima x1vsx2 , quindi y1vs y2. Ecco perché il built-in .Minpuò essere utilizzato su questi tipi.
E poiché sia il tipo anonimo che la tupla di valore sono tipi di valore, dovrebbero essere entrambi molto efficienti.
NOTA
Nelle mie ArgMinimplementazioni di cui sopra ho assunto la DateOfBirthpremessa DateTimeper semplicità e chiarezza. La domanda originale chiede di escludere quelle voci con nullDateOfBirth campo :
I valori Null DateOfBirth sono impostati su DateTime.MaxValue per escluderli dal corrispettivo Min (presupponendo che almeno uno abbia un DOB specificato).
Può essere ottenuto con un pre-filtro
people.Where(p => p.DateOfBirth.HasValue)
Quindi è irrilevante per la questione dell'implementazione ArgMin o ArgMax.
NOTA 2
L'approccio sopra ha un avvertimento che quando ci sono due istanze con lo stesso valore minimo, l' Min()implementazione proverà a confrontare le istanze come un tie-breaker. Tuttavia, se la classe delle istanze non viene implementataIComparable , verrà generato un errore di runtime:
Almeno un oggetto deve implementare IComparable
Fortunatamente, questo può ancora essere risolto piuttosto chiaramente. L'idea è di associare un "ID" distanziato a ciascuna voce che funge da inequivocabile tie-breaker. Possiamo usare un ID incrementale per ogni voce. Sempre usando l'età della gente come esempio:
var youngest = Enumerable.Range(0, int.MaxValue)
.Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;