Eu tenho um objeto Person com uma propriedade Nullable DateOfBirth. Existe uma maneira de usar o LINQ para consultar uma lista de objetos Pessoa para aquele com o menor / menor valor DateOfBirth.
Aqui está o que eu comecei:
var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));
Os valores nulos de DateOfBirth são definidos como DateTime.MaxValue para excluí-los da consideração Mínima (supondo que pelo menos um tenha um DOB especificado).
Mas tudo o que faz por mim é definir firstBornDate como um valor DateTime. O que eu gostaria de obter é o objeto Person que corresponde a isso. Preciso escrever uma segunda consulta assim:
var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
Ou existe uma maneira mais enxuta de fazer isso?
a.Min(x => x.foo);
max("find a word of maximal length in this sentence".split(), key=len)
retorna a string 'sentença'. Em C #"find a word of maximal length in this sentence".Split().Max(word => word.Length)
calcula que 8 é o maior comprimento de qualquer palavra, mas não dizer-lhe que a palavra mais longa é .Respostas:
fonte
curMin == null
?curMin
só poderia sernull
se você estivesse usandoAggregate()
uma semente que énull
.Infelizmente, não há um método interno para fazer isso, mas é fácil de implementar por si mesmo. Aqui estão as entranhas:
Exemplo de uso:
Observe que isso gerará uma exceção se a sequência estiver vazia e retornará o primeiro elemento com o valor mínimo se houver mais de um.
Como alternativa, você pode usar a implementação que temos MoreLINQ , no MinBy.cs . (Há um correspondente
MaxBy
, é claro.)Instale via console do gerenciador de pacotes:
fonte
OBSERVAÇÃO: incluo esta resposta para que seja completo, pois o OP não mencionou qual é a fonte de dados e não devemos fazer suposições.
Esta consulta fornece a resposta correta, mas pode ser mais lenta, pois pode ser necessário classificar todos os itens
People
, dependendo da estrutura de dadosPeople
:ATUALIZAÇÃO: Na verdade, não devo chamar essa solução de "ingênua", mas o usuário precisa saber contra o que está consultando. A "lentidão" desta solução depende dos dados subjacentes. Se for uma matriz ou
List<T>
, o LINQ to Objects não terá outra opção a não ser classificar a coleção inteira primeiro antes de selecionar o primeiro item. Nesse caso, será mais lento que a outra solução sugerida. No entanto, se esta for uma tabela LINQ to SQL eDateOfBirth
for uma coluna indexada, o SQL Server usará o índice em vez de classificar todas as linhas. OutrasIEnumerable<T>
implementações personalizadas também podem usar índices (consulte i4o: LINQ indexado ou o banco de dados de objetos db4o ) e tornar essa solução mais rápida do queAggregate()
ouMaxBy()
/MinBy()
que precisa repetir a coleção inteira uma vez. De fato, o LINQ to Objects poderia (em teoria) criar casos especiaisOrderBy()
para coleções classificadas comoSortedList<T>
, mas isso não acontece, tanto quanto eu sei.fonte
Faria o truque
fonte
Então você está pedindo
ArgMin
ouArgMax
. O C # não possui uma API interna para esses.Eu tenho procurado uma maneira limpa e eficiente (O (n) no tempo) de fazer isso. E acho que encontrei um:
A forma geral desse padrão é:
Especialmente, usando o exemplo na pergunta original:
Para C # 7.0 e superior que suporta tupla de valor :
Para a versão C # anterior à 7.0, o tipo anônimo pode ser usado:
Eles funcionam porque ambos, tupla de valor e tipo anônimo, têm comparadores padrão sensíveis: para (x1, y1) e (x2, y2), ele primeiro compara
x1
vsx2
, entãoy1
vsy2
. É por isso que o built-in.Min
pode ser usado nesses tipos.E como o tipo anônimo e a tupla de valor são tipos de valor, eles devem ser muito eficientes.
NOTA
Nas minhas
ArgMin
implementações acima , assumiDateOfBirth
tipoDateTime
por simplicidade e clareza. A pergunta original pede para excluir essas entradas comDateOfBirth
campo nulo :Isso pode ser alcançado com uma pré-filtragem
Portanto, é irrelevante para a questão da implementação
ArgMin
ouArgMax
.NOTA 2
A abordagem acima tem uma ressalva de que, quando houver duas instâncias com o mesmo valor mínimo, a
Min()
implementação tentará comparar as instâncias como um desempatador. No entanto, se a classe das instâncias não implementarIComparable
, será gerado um erro de tempo de execução:Felizmente, isso ainda pode ser corrigido de forma bastante limpa. A idéia é associar um "ID" distante a cada entrada que serve como desempate inequívoco. Podemos usar um ID incremental para cada entrada. Ainda usando as pessoas envelhecem como exemplo:
fonte
Solução sem pacotes extras:
Além disso, você pode envolvê-lo em extensão:
e neste caso:
A propósito ... O (n ^ 2) não é a melhor solução. Paul Betts deu uma solução mais gorda que a minha. Mas a minha ainda é a solução LINQ e é mais simples e mais curta do que outras soluções aqui.
fonte
fonte
Uso perfeitamente simples de agregado (equivalente a dobrar em outros idiomas):
A única desvantagem é que a propriedade é acessada duas vezes por elemento de sequência, o que pode ser caro. Isso é difícil de consertar.
fonte
A seguir está a solução mais genérica. Ele basicamente faz a mesma coisa (na ordem O (N)), mas em qualquer tipo IEnumberable e pode ser misturado com tipos cujos seletores de propriedades podem retornar nulos.
Testes:
fonte
EDITAR novamente:
Desculpa. Além de perder o anulável, eu estava olhando para a função errada,
Min <(Of <(TSource, TResult>)>) (IEnumerable <(Of <(TSource>)>), Func <(Of <(TSource, TResult>)>)) retorna o tipo de resultado como você disse.
Eu diria que uma solução possível é implementar IComparable e usar Min <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>)) , que realmente retorna um elemento do IEnumerable. Obviamente, isso não ajuda se você não pode modificar o elemento. Acho o design da MS um pouco estranho aqui.
Obviamente, você sempre pode fazer um loop for se precisar, ou usar a implementação do MoreLINQ que Jon Skeet forneceu.
fonte
Outra implementação, que poderia funcionar com chaves seletoras que permitem valor nulo, e para a coleção do tipo de referência retorna nulo se nenhum elemento adequado for encontrado. Isso pode ser útil para processar os resultados do banco de dados, por exemplo.
Exemplo:
fonte
Eu também estava procurando algo semelhante, de preferência sem usar uma biblioteca ou classificar a lista inteira. Minha solução acabou parecida com a pergunta em si, apenas um pouco simplificada.
fonte
var min = People.Min(...); var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == min...
Caso contrário, ele receberá o min repetidamente até encontrar o que você está procurando.