tuplas - C#: Obtener valores máximos y mínimos de propiedades arbitrarias de todos los elementos en una lista
maximo y minimo c# (8)
Tengo una lista especializada que contiene elementos de tipo IThing
:
public class ThingList : IList<IThing>
{...}
public interface IThing
{
Decimal Weight { get; set; }
Decimal Velocity { get; set; }
Decimal Distance { get; set; }
Decimal Age { get; set; }
Decimal AnotherValue { get; set; }
[...even more properties and methods...]
}
A veces necesito saber el máximo o mínimo de cierta propiedad de todas las cosas en la lista. Debido a "Dime, no preguntes", dejamos que la Lista lo resuelva:
public class ThingList : IList<IThing>
{
public Decimal GetMaximumWeight()
{
Decimal result = 0;
foreach (IThing thing in this) {
result = Math.Max(result, thing.Weight);
}
return result;
}
}
Eso es muy lindo Pero a veces necesito el peso mínimo, a veces la velocidad máxima y así sucesivamente. No quiero un GetMaximum*()/GetMinimum*()
para cada propiedad.
Una solución sería la reflexión. Algo así como (¡sostén tu nariz, fuerte olor a código!):
Decimal GetMaximum(String propertyName);
Decimal GetMinimum(String propertyName);
¿Hay formas mejores y menos olorosas para lograr esto?
Gracias, Eric
Editar: @Matt: .Net 2.0
Conclusión: no hay mejor forma para .Net 2.0 (con Visual Studio 2005). Tal vez deberíamos pasar a .Net 3.5 y Visual Studio 2008 pronto. Gracias chicos.
Conclusión: Hay formas diferentes que son mucho mejores que la reflexión. Dependiendo del tiempo de ejecución y la versión de C #. Eche un vistazo a la respuesta de Jon Skeets por las diferencias. Todas las respuestas son muy útiles.
Iré por la sugerencia de Sklivvz (métodos anónimos). Hay varios fragmentos de código de otras personas (Konrad Rudolph, Matt Hamilton y Coincoin) que implementan la idea Sklivvz. Solo puedo "aceptar" una respuesta, desafortunadamente.
Muchas gracias. Todos pueden sentirse "aceptados", aunque solo Sklivvz obtiene los créditos ;-)
Conclusión: no hay mejor forma para .Net 2.0 (con Visual Studio 2005).
Parece que has entendido mal las respuestas (especialmente las de Jon). Puedes usar la opción 3 de su respuesta. Si no quiere usar LinqBridge, puede usar un delegado e implementar el método Max
usted mismo, de manera similar al método que publiqué:
delegate Decimal PropertyValue(IThing thing);
public class ThingList : IList<IThing> {
public Decimal Max(PropertyValue prop) {
Decimal result = Decimal.MinValue;
foreach (IThing thing in this) {
result = Math.Max(result, prop(thing));
}
return result;
}
}
Uso:
ThingList lst;
lst.Max(delegate(IThing thing) { return thing.Age; });
¿Qué tal una solución .Net 2 generalizada?
public delegate A AggregateAction<A, B>( A prevResult, B currentElement );
public static Tagg Aggregate<Tcoll, Tagg>(
IEnumerable<Tcoll> source, Tagg seed, AggregateAction<Tagg, Tcoll> func )
{
Tagg result = seed;
foreach ( Tcoll element in source )
result = func( result, element );
return result;
}
//this makes max easy
public static int Max( IEnumerable<int> source )
{
return Aggregate<int,int>( source, 0,
delegate( int prev, int curr ) { return curr > prev ? curr : prev; } );
}
//but you could also do sum
public static int Sum( IEnumerable<int> source )
{
return Aggregate<int,int>( source, 0,
delegate( int prev, int curr ) { return curr + prev; } );
}
(Editado para reflejar la respuesta de .NET 2.0 y LINQBridge en VS2005 ...)
Aquí hay tres situaciones: aunque el OP solo tiene .NET 2.0, otras personas que enfrentan el mismo problema pueden no ...
1) Usando .NET 3.5 y C # 3.0: use LINQ para objetos como este:
decimal maxWeight = list.Max(thing => thing.Weight);
decimal minWeight = list.Min(thing => thing.Weight);
2) Usando .NET 2.0 y C # 3.0: use LINQBridge y el mismo código
3) Usando .NET 2.0 y C # 2.0: use LINQBridge y métodos anónimos:
decimal maxWeight = Enumerable.Max(list, delegate(IThing thing)
{ return thing.Weight; }
);
decimal minWeight = Enumerable.Min(list, delegate(IThing thing)
{ return thing.Weight; }
);
(No tengo un compilador de C # 2.0 a mano para probar lo anterior; si se queja de una conversión ambigua, envíe el delegado a Func <IThing, decimal>).
LINQBridge funcionará con VS2005, pero no obtendrá métodos de extensión, expresiones lambda, expresiones de consulta, etc. Claramente, la migración a C # 3 es una opción más agradable, pero prefiero usar LINQBridge para implementar la misma funcionalidad yo mismo.
Todas estas sugerencias implican caminar la lista dos veces si necesita obtener tanto el máximo como el mínimo. Si tiene una situación en la que está cargando desde el disco de forma perezosa o algo así, y desea calcular varios agregados de una sola vez, es posible que desee consultar mi código "Push LINQ" en MiscUtil . (Eso funciona con .NET 2.0 también).
Aquí hay un intento, usando C # 2.0, en la idea de Skilwz.
public delegate T GetPropertyValueDelegate<T>(IThing t);
public T GetMaximum<T>(GetPropertyValueDelegate<T> getter)
where T : IComparable
{
if (this.Count == 0) return default(T);
T max = getter(this[0]);
for (int i = 1; i < this.Count; i++)
{
T ti = getter(this[i]);
if (max.CompareTo(ti) < 0) max = ti;
}
return max;
}
Lo usarías así:
ThingList list;
Decimal maxWeight = list.GetMaximum(delegate(IThing t) { return t.Weight; });
Para C # 2.0 y .Net 2.0, puede hacer lo siguiente para Max:
public delegate Decimal GetProperty<TElement>(TElement element);
public static Decimal Max<TElement>(IEnumerable<TElement> enumeration,
GetProperty<TElement> getProperty)
{
Decimal max = Decimal.MinValue;
foreach (TElement element in enumeration)
{
Decimal propertyValue = getProperty(element);
max = Math.Max(max, propertyValue);
}
return max;
}
Y así es como lo usarías:
string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
Max(array, delegate(string e) { return e.Length;});
Aquí es cómo lo haría con C # 3.0, .Net 3.5 y Linq, sin la función anterior:
string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
array.Max( e => e.Length);
Si estabas usando .NET 3.5 y LINQ:
Decimal result = myThingList.Max(i => i.Weight);
Eso haría que el cálculo de Min y Max sea bastante trivial.
Si usa .NET 3.5, ¿por qué no usar lambdas?
public Decimal GetMaximum(Func<IThing, Decimal> prop) {
Decimal result = Decimal.MinValue;
foreach (IThing thing in this)
result = Math.Max(result, prop(thing));
return result;
}
Uso:
Decimal result = list.GetMaximum(x => x.Weight);
Esto es fuertemente tipado y eficiente. También hay métodos de extensión que ya hacen exactamente esto.
Sí, debes usar un delegado y métodos anónimos.
Para ver un ejemplo, mira aquí .
Básicamente, debe implementar algo similar al método Buscar de Listas .
Aquí hay una implementación de muestra
public class Thing
{
public int theInt;
public char theChar;
public DateTime theDateTime;
public Thing(int theInt, char theChar, DateTime theDateTime)
{
this.theInt = theInt;
this.theChar = theChar;
this.theDateTime = theDateTime;
}
public string Dump()
{
return string.Format("I: {0}, S: {1}, D: {2}",
theInt, theChar, theDateTime);
}
}
public class ThingCollection: List<Thing>
{
public delegate Thing AggregateFunction(Thing Best,
Thing Candidate);
public Thing Aggregate(Thing Seed, AggregateFunction Func)
{
Thing res = Seed;
foreach (Thing t in this)
{
res = Func(res, t);
}
return res;
}
}
class MainClass
{
public static void Main(string[] args)
{
Thing a = new Thing(1,''z'',DateTime.Now);
Thing b = new Thing(2,''y'',DateTime.Now.AddDays(1));
Thing c = new Thing(3,''x'',DateTime.Now.AddDays(-1));
Thing d = new Thing(4,''w'',DateTime.Now.AddDays(2));
Thing e = new Thing(5,''v'',DateTime.Now.AddDays(-2));
ThingCollection tc = new ThingCollection();
tc.AddRange(new Thing[]{a,b,c,d,e});
Thing result;
//Max by date
result = tc.Aggregate(tc[0],
delegate (Thing Best, Thing Candidate)
{
return (Candidate.theDateTime.CompareTo(
Best.theDateTime) > 0) ?
Candidate :
Best;
}
);
Console.WriteLine("Max by date: {0}", result.Dump());
//Min by char
result = tc.Aggregate(tc[0],
delegate (Thing Best, Thing Candidate)
{
return (Candidate.theChar < Best.theChar) ?
Candidate :
Best;
}
);
Console.WriteLine("Min by char: {0}", result.Dump());
}
}
Los resultados:
Max by date: I: 4, S: w, D: 10/3/2008 12:44:07 AM
Min by char: I: 5, S: v, D: 9/29/2008 12:44:07 AM