2008-09-30 27 views
18

tengo una lista especializada que contiene elementos de tipo IThing:C#: Obtener los valores máximo y mínimo de propiedades arbitrarias de todos los elementos de una lista

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 un cierto 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 agradable. Pero a veces necesito el peso mínimo, a veces la velocidad máxima y así sucesivamente. No quiero un par GetMaximum*()/GetMinimum*() para cada propiedad.

Una solución sería la reflexión. Algo así como (taparse la nariz, fuerte olor código!):

Decimal GetMaximum(String propertyName); 
Decimal GetMinimum(String propertyName); 

¿Hay formas mejores y menos mal olor de lograr esto?

Gracias, Eric

Editar: @ Matt: .Net 2.0

Conclusión: No hay mejor manera para .Net 2.0 (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. todo lo que puede sentirse "aceptado", Altough sólo se pone Sklivvz los créditos ;-)

+0

He agregado una implementación que funciona – Sklivvz

Respuesta

10

Sí, debe usar un delegado y métodos anónimos.

Para ver un ejemplo, vea here.

Básicamente debe implementar algo similar al Find method of Lists.

Aquí está un ejemplo de implementación

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

+0

Esto es simplemente increíble. –

19

Si estaba utilizando .NET 3.5 y LINQ:

Decimal result = myThingList.Max(i => i.Weight); 

Eso haría que el cálculo del mínimo y máximo bastante trivial .

8

Si usa .NET 3.5, ¿por qué no utilizar 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); 

Esta es fuertemente tipado y eficiente. También hay métodos de extensión que ya hacen exactamente esto.

31

(editado para reflejar .NET 2.0 respuesta, y LINQBridge en VS2005 ...)

Hay tres situaciones aquí - aunque el OP sólo se tiene .NET 2.0, otras personas que enfrentan el mismo problema no puede .. .

1) uso de .NET 3.5 y C# 3.0: utilizar LINQ a objetos como esta:

decimal maxWeight = list.Max(thing => thing.Weight); 
decimal minWeight = list.Min(thing => thing.Weight); 

2) uso de .NET 2.0 y C# 3.0: utilizar LINQBridge y el mismo código de

3) Utilizando .NET 2.0 y C# 2.0: utilizar 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 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 preferiría usar LINQBridge para implementar el mismo funcionalidad yo mismo.

Todas estas sugerencias implican recorrer la lista dos veces si necesita obtener el máximo y 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 también funciona con .NET 2.0.)

2

Conclusión: No hay mejor manera para .Net 2.0 (con Visual Studio 2005)

Pareces haber entendido mal las respuestas (especialmente las de Jon). Puedes usar la opción 3 de su respuesta. Si no desea utilizar LinqBridge todavía se puede utilizar un delegado y poner en práctica el método Max mismo, similar al método que he publicado:

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

Jon editó su respuesta cuando estaba llegando a la conclusión. – EricSchaefer

3

para C# 2.0 y .Net 2.0 se 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 aquí es cómo se usa:

string[] array = new string[] {"s","sss","ddsddd","333","44432333"}; 

Max(array, delegate(string e) { return e.Length;}); 

Aquí es cómo usted 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); 
3

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

Se podría utilizar de esta manera:

ThingList list; 
Decimal maxWeight = list.GetMaximum(delegate(IThing t) { return t.Weight; }); 
+0

Este método le permite obtener el máximo de cualquier propiedad cuyo tipo implemente IComparable. Para que pueda obtener el máximo de, por ejemplo, una propiedad DateTime o una cadena, así como decimales. –

+0

Matt, se puede generalizar utilizando un delegado que devuelve el "mejor" entre los elementos. Si el "mejor" es el mínimo tu mínimo gen, si es el mayor entonces obtienes el máximo. – Sklivvz

+0

Buen punto. Si el tipo de delegado fuera un "ThingComparer", tomando dos IThings y devolviendo un bool, entonces este método podría hacer tanto Max como Min. Sin embargo, no sé cómo se combina con la filosofía de "no preguntar". –

2

¿Qué tal una solución generalizada .Net 2?

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