tutorial sintaxis query español c# .net linq

c# - sintaxis - Cómo usar LINQ para seleccionar un objeto con un valor de propiedad mínimo o máximo



sintaxis linq c# (12)

Así que estás pidiendo ArgMin o ArgMax . C # no tiene una API incorporada para esos.

He estado buscando una forma limpia y eficiente (O (n) en el tiempo) de hacer esto. Y creo que encontré uno:

La forma general de este patrón es:

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

Especialmente, usando el ejemplo en la pregunta original:

Para C # 7.0 y superior que soporta valor tupla :

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

Para la versión C # anterior a 7.0, se puede usar el tipo anónimo en su lugar:

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

Funcionan porque tanto la tupla de valor como el tipo anónimo tienen comparadores predeterminados razonables: para (x1, y1) y (x2, y2), primero compara x1 x2 , luego y1 contra y2 . Es por eso que el .Min se puede usar en esos tipos.

Y dado que tanto el tipo anónimo como la tupla de valor son tipos de valor, deberían ser muy eficientes.

NOTA

En mis implementaciones anteriores de ArgMin asumí que DateOfBirth tomaba el tipo DateTime por simplicidad y claridad. La pregunta original solicita excluir esas entradas con el campo nulo DateOfBirth :

Los valores nulos de DateOfBirth se configuran en DateTime.MaxValue para excluirlos de la consideración mínima (suponiendo que al menos uno tiene un DOB específico).

Se puede lograr con un prefiltro.

people.Where(p => p.DateOfBirth.HasValue)

Por lo tanto, no tiene importancia la cuestión de implementar ArgMin o ArgMax .

NOTA 2

El enfoque anterior tiene la advertencia de que cuando hay dos instancias que tienen el mismo valor mínimo, la implementación Min() intentará comparar las instancias como un desempate. Sin embargo, si la clase de las instancias no implementa IComparable , se generará un error de tiempo de ejecución:

Al menos un objeto debe implementar IComparable.

Afortunadamente, esto todavía se puede arreglar de manera bastante limpia. La idea es asociar una "ID" distante con cada entrada que sirva como el desempate ambiguo. Podemos usar una identificación incremental para cada entrada. Sigo usando la edad de las personas como ejemplo:

var youngest = Enumerable.Range(0, int.MaxValue) .Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;

Tengo un objeto Person con una propiedad DateOfBirth anulable. ¿Hay una manera de usar LINQ para consultar una lista de objetos Person para el que tiene el valor DateOfBirth más antiguo / pequeño?

Esto es lo que empecé con:

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

Los valores nulos de DateOfBirth se configuran en DateTime.MaxValue para excluirlos de la consideración mínima (suponiendo que al menos uno tiene un DOB específico).

Pero todo lo que hace por mí es establecer firstBornDate en un valor DateTime. Lo que me gustaría obtener es el objeto Persona que coincide con eso. ¿Necesito escribir una segunda consulta así?

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

¿O hay una forma más magra de hacerlo?


Desafortunadamente no hay un método incorporado para hacer esto.

PM> Install-Package morelinq

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

Alternativamente, puede usar la implementación que tenemos en MoreLINQ , en MinBy.cs . (Hay un MaxBy correspondiente, por supuesto). Aquí están las agallas de esto:

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; } }

Tenga en cuenta que esto generará una excepción si la secuencia está vacía y devolverá el primer elemento con el valor mínimo si hay más de uno.


EDITAR de nuevo:

Lo siento. Además de perder el nullable, estaba mirando la función incorrecta,

Min <(Of <(TSource, TResult>)>) (IEnumerable <(Of <(TSource>)>), Func <(Of <(TSource, TResult>)>)) devuelve el tipo de resultado como dijiste.

Yo diría que una posible solución es implementar IComparable y usar Min <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>)) , que realmente devuelve un elemento desde IEnumerable. Por supuesto, eso no te ayuda si no puedes modificar el elemento. Me parece un poco extraño el diseño de MS aquí.

Por supuesto, siempre puede hacer un bucle for si lo necesita, o usar la implementación de MoreLINQ que dio Jon Skeet.


La siguiente es la solución más genérica. Básicamente, hace lo mismo (en orden O (N)) pero en cualquier tipo de IEnumberable y se puede mezclar con tipos cuyos selectores de propiedades podrían devolver nulo.

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; }); } }

Pruebas:

var nullableInts = new int?[] {5, null, 1, 4, 0, 3, null, 1}; Assert.AreEqual(0, nullableInts.MinBy(i => i));//should pass


NOTA: Incluyo esta respuesta para completarla ya que el OP no mencionó cuál es la fuente de datos y no debemos hacer suposiciones.

Esta consulta da la respuesta correcta, pero podría ser más lenta, ya que podría tener que ordenar todos los elementos en People , dependiendo de la estructura de datos que las People son:

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

ACTUALIZACIÓN: En realidad, no debería llamar "ingenua" a esta solución, pero el usuario necesita saber contra qué está consultando. La "lentitud" de esta solución depende de los datos subyacentes. Si se trata de una matriz o List<T> , LINQ to Objects no tiene más remedio que ordenar toda la colección antes de seleccionar el primer elemento. En este caso, será más lento que la otra solución sugerida. Sin embargo, si esta es una tabla de LINQ to SQL y DateOfBirth es una columna indexada, entonces SQL Server usará el índice en lugar de ordenar todas las filas. Otras IEnumerable<T> personalizadas de IEnumerable<T> también podrían hacer uso de índices (consulte i4o: Indexed LINQ , o la base de datos de objetos db4o ) y hacer que esta solución sea más rápida que Aggregate() o MaxBy() / MinBy() que deben MinBy() toda la colección. una vez. De hecho, LINQ to Objects podría haber hecho (en teoría) casos especiales en OrderBy() para colecciones ordenadas como SortedList<T> , pero no lo hace, por lo que sé.


No comprobé, pero esto debe hacer lo esperado:

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

y min:

var itemWithMinValue = SomeListOfClass.OrderByDescending(i => i.SomeFloat).LastOrDefault();


Para obtener el máximo o mínimo de una propiedad de una matriz de objetos:

Haga una lista que almacene el valor de cada propiedad:

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

Agregue todos los valores de propiedad a la lista:

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

Obtener el máximo o mínimo de la lista:

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

Ahora puede recorrer su matriz de objetos y comparar los valores de propiedad que desea verificar con el máximo o mínimo int:

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


Solo he revisado esto para Entity framework 6.0.0>: Esto se puede hacer:

var MaxValue = dbContext.YourDataClass.Select(x => x.ColumnToFindMaxValueFrom).Max(); var MinValue = dbContext.YourDataClass.Select(x => x.ColumnToFindMinValueFrom).Min();


Yo estaba buscando algo similar a mí mismo, preferiblemente sin usar una biblioteca o clasificar toda la lista. Mi solución terminó similar a la pregunta en sí misma, solo se simplificó un poco.

var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == People.Min(p2 => p2.DateOfBirth));


People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) < curMin.DateOfBirth ? x : curMin))


People.OrderBy(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)).First()

Haría el truco


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(); }