代码之家  ›  专栏  ›  技术社区  ›  EricSchaefer

C:获取列表中所有项目任意属性的最大值和最小值

  •  18
  • EricSchaefer  · 技术社区  · 16 年前

    我有一个专门的清单,上面有各种类型的物品 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...]
    }
    

    有时我需要知道列表中所有事物的某个属性的最大值或最小值。因为“告诉我不要问”,我们让列表弄清楚:

    public class ThingList : IList<IThing>
    {
        public Decimal GetMaximumWeight()
        {
            Decimal result = 0;
            foreach (IThing thing in this) {
                result = Math.Max(result, thing.Weight);
            }
            return result;
        }
    }
    

    那很好。但是有时候我需要最小的重量,有时候最大的速度等等。我不想要 GetMaximum*()/GetMinimum*() 为每个属性配对。

    一个解决办法是反思。比如(你的鼻子,强烈的代码味道!):

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

    有没有更好的,不那么难闻的方法来完成这个任务?

    谢谢, 埃里克

    编辑:@matt:.net 2.0

    结论:对于.NET 2.0(使用Visual Studio 2005),没有更好的方法。也许我们应该尽快转移到.NET 3.5和Visual Studio 2008。谢谢,伙计们。

    结论:有不同的方法比反射法好得多。取决于运行时和C版本。看看乔恩·斯基茨的不同之处。所有的答案都很有帮助。

    我会选择sklivz建议(匿名方法)。其他人(Konrad Rudolph、Matt Hamilton和Coincoin)提供了几个实现sklivz思想的代码片段。不幸的是,我只能“接受”一个答案。

    非常感谢你。你们都能感觉到“被接受”,尽管只有斯科利夫兹获得了学分;-)

    8 回复  |  直到 16 年前
        1
  •  10
  •   Sklivvz    16 年前

    是的,您应该使用委托和匿名方法。

    有关示例,请参见 here .

    基本上,您需要实现类似于 Find method of Lists .

    这是一个示例实现

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

    结果:

    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

        2
  •  32
  •   Jon Skeet    16 年前

    (编辑以反映.NET 2.0答案,以及VS2005中的LinqBridge…)

    这里有三种情况-虽然OP只有.NET 2.0,但是其他面临同样问题的人可能不会……

    1)使用.NET 3.5和C 3.0:使用Linq来处理如下对象:

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

    2)使用.NET 2.0和C 3.0:使用 LINQBridge 同样的代码

    3)使用.NET 2.0和C 2.0:使用 林奇桥 匿名方法:

    decimal maxWeight = Enumerable.Max(list, delegate(IThing thing) 
        { return thing.Weight; }
    );
    decimal minWeight = Enumerable.Min(list, delegate(IThing thing)
        { return thing.Weight; }
    );
    

    (我没有C 2.0编译器来测试上面的内容-如果它抱怨不明确的转换,请将委托强制转换为func<ithing,decimal>)

    LinqBridge将与VS2005一起使用,但您无法获得扩展方法、lambda表达式、查询表达式等。显然,迁移到C_3是一个更好的选择,但我宁愿使用LinqBridge来实现相同的功能。

    如果需要同时获得max和min,所有这些建议都需要浏览列表两次。如果您遇到了一种情况,即从磁盘延迟加载或类似的情况,并且您希望一次计算多个聚合,那么您可能需要查看 "Push LINQ" 代码在 MiscUtil . (也适用于.NET 2.0。)

        3
  •  19
  •   Sklivvz    16 年前

    如果您使用的是.NET 3.5和Linq:

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

    这将使得最小值和最大值的计算变得非常简单。

        4
  •  8
  •   Sklivvz    16 年前

    如果使用.NET 3.5,为什么不使用lambda?

    public Decimal GetMaximum(Func<IThing, Decimal> prop) {
        Decimal result = Decimal.MinValue;
        foreach (IThing thing in this)
            result = Math.Max(result, prop(thing));
    
        return result;
    }
    

    用途:

    Decimal result = list.GetMaximum(x => x.Weight);
    

    这是强类型和高效的。还有一些扩展方法已经做到了这一点。

        5
  •  3
  •   Coincoin    16 年前

    对于C 2.0和.NET 2.0,最多可以执行以下操作:

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

    下面是您将如何使用它:

    string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
    
    Max(array, delegate(string e) { return e.Length;});
    

    以下是在不使用上述函数的情况下,如何使用C 3.0、.NET 3.5和LINQ执行此操作:

    string[] array = new string[] {"s","sss","ddsddd","333","44432333"};
    array.Max( e => e.Length);
    
        6
  •  3
  •   Matt Hamilton    16 年前

    这是一个尝试,使用C 2.0,在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;
    }
    

    您可以这样使用它:

    ThingList list;
    Decimal maxWeight = list.GetMaximum(delegate(IThing t) { return t.Weight; });
    
        7
  •  2
  •   Konrad Rudolph    16 年前

    结论:对于.NET 2.0(使用Visual Studio 2005),没有更好的方法。

    你似乎误解了答案(尤其是乔恩的答案)。你可以从他的答案中选择3。 如果不想使用LinqBridge,仍然可以使用委托并实现 Max 方法自己,类似于我发布的方法:

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

    用途:

    ThingList lst;
    lst.Max(delegate(IThing thing) { return thing.Age; });
    
        8
  •  2
  •   Keith    16 年前

    一个通用的.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; } );
    }