2008-09-30 24 views
18

tôi có một danh sách đặc biệt chứa các mặt hàng loại IThing:C#: Bắt các giá trị tối đa và tối thiểu tính độc đoán của tất cả các mục trong một danh sách

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...] 
} 

Đôi khi tôi cần phải biết tối đa hoặc tối thiểu của một số tài sản của tất cả những điều trong danh sách. Vì "Không yêu cầu", chúng tôi cho phép Danh sách phát hiện ra:

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

Thats rất đẹp. Nhưng đôi khi tôi cần trọng lượng tối thiểu, đôi khi vận tốc tối đa và vân vân. Tôi không muốn một cặp GetMaximum*()/GetMinimum*() cho mỗi thuộc tính duy nhất.

Một giải pháp sẽ được phản ánh. Một cái gì đó như (giữ mũi của bạn, mã mạnh mùi!):

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

Có cách nào tốt hơn, ít mùi để thực hiện điều này?

Cảm ơn, Eric

Edit: @ Matt: Net 2.0

Kết luận: Không có cách nào tốt hơn cho Net 2.0 (với Visual Studio 2005). Có lẽ chúng ta nên chuyển sang .Net 3.5 và Visual Studio 2008 đôi khi sớm. Cảm ơn các bạn.

Kết luận: Có nhiều cách khác nhau tốt hơn nhiều so với phản ánh. Tùy thuộc vào thời gian chạy và phiên bản C#. Có một cái nhìn tại Jon Skeets trả lời cho sự khác biệt. Tất cả các câu trả lời đều rất hữu ích.

Tôi sẽ đi tìm gợi ý Sklivvz (các phương thức ẩn danh). Có một số đoạn mã từ những người khác (Konrad Rudolph, Matt Hamilton và Coincoin) thực hiện ý tưởng Sklivvz. Tôi chỉ có thể "chấp nhận" một câu trả lời, thật không may.

Cảm ơn bạn rất nhiều. tất cả các bạn có thể cảm thấy "chấp nhận", altough chỉ Sklivvz được các khoản tín dụng ;-)

+0

Tôi đã thêm triển khai hoạt động – Sklivvz

Trả lời

10

Có, bạn nên sử dụng một phương thức đại biểu và ẩn danh.

Ví dụ, xem here.

Về cơ bản, bạn cần triển khai một cái gì đó tương tự như Find method of Lists.

Đây là một thực hiện mẫu

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

Kết quả:

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

Điều này thật tuyệt vời. –

19

Nếu bạn được bạn sử dụng .NET 3.5 và LINQ:

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

Điều đó sẽ làm cho việc tính toán của Min và Max khá tầm thường .

8

Nếu sử dụng .NET 3.5, tại sao không sử dụng 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; 
} 

Cách sử dụng:

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

này được gõ mạnh và hiệu quả. Ngoài ra còn có các phương pháp mở rộng đã làm chính xác điều này.

31

(Edited để phản ánh .NET 2.0 câu trả lời, và LINQBridge trong VS2005 ...)

Có ba tình huống ở đây - mặc dù OP chỉ có .NET 2.0, những người khác phải đối mặt với cùng một vấn đề có thể không ..

1) sử dụng .NET 3.5 và C# 3.0: sử dụng LINQ to Objects như thế này:.

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

2) sử dụng .NET 2.0 và C# 3.0: sử dụng LINQBridge và cùng mã

3) Sử dụng .NET 2.0 và C# 2.0: sử dụng LINQBridge và phương pháp vô danh:

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

(Tôi không có một trình biên dịch C# 2.0 để tay để kiểm tra ở trên - nếu nó phàn nàn về một chuyển đổi mơ hồ, hãy truyền đại biểu đến Func < IThing, thập phân>.)

LINQBridge sẽ làm việc với VS2005, nhưng bạn không nhận được các phương pháp mở rộng, biểu thức lambda, biểu thức truy vấn, v.v. Di chuyển rõ ràng sang C# 3 là một lựa chọn đẹp hơn, nhưng tôi thích sử dụng LINQBridge để thực hiện tương tự chức năng bản thân mình.

Tất cả các đề xuất này liên quan đến việc đi bộ danh sách hai lần nếu bạn cần nhận cả giá trị tối đa và tối thiểu. Nếu bạn có một tình huống mà bạn đang tải từ đĩa uể oải hoặc một cái gì đó như thế, và bạn muốn tính toán một số tập hợp trong một lần, bạn có thể muốn xem mã số "Push LINQ" của tôi trong MiscUtil. (Điều đó cũng hoạt động với .NET 2.0.)

2

Kết luận: Không có cách nào tốt hơn cho Net 2.0 (với Visual Studio 2005).

Có vẻ như bạn đã hiểu sai câu trả lời (đặc biệt là Jon). Bạn có thể sử dụng tùy chọn 3 từ câu trả lời của mình. Nếu bạn không muốn sử dụng LinqBridge bạn vẫn có thể sử dụng một đại biểu và thực hiện các phương pháp Max mình, tương tự như phương pháp này tôi đã đăng:

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

Cách sử dụng:

ThingList lst; 
lst.Max(delegate(IThing thing) { return thing.Age; }); 
+0

Jon đã chỉnh sửa câu trả lời của mình khi tôi rút ra kết luận. – EricSchaefer

3

Đối với C# 2.0 và Net 2.0, bạn có thể làm như sau để tối đa:

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

và đây là cách bạn sẽ sử dụng nó:

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

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

Đây là cách bạn sẽ làm điều đó với C# 3.0, Net 3.5 và LINQ, không có chức năng trên:

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

Dưới đây là một nỗ lực, sử dụng C# 2.0, tại ý tưởng Skilwz của.

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

Bạn muốn sử dụng nó như thế này:

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

Phương pháp này cho phép bạn nhận được tối đa bất kỳ thuộc tính nào có loại thực hiện IComparable. Vì vậy, bạn có thể nhận được tối đa, ví dụ, một thuộc tính DateTime hoặc một chuỗi cũng như các số thập phân. –

+0

Matt, nó có thể được tổng quát bằng cách sử dụng một đại biểu mà trả về "tốt nhất" giữa các mục. Nếu "tốt nhất" là ít nhất bạn gen tối thiểu, nếu nó lớn hơn thì bạn sẽ nhận được tối đa. – Sklivvz

+0

Điểm tốt. Nếu kiểu ủy nhiệm là "ThingComparer", lấy hai IThings và trả về một bool, thì phương thức này có thể làm cả Max và Min. Không biết làm thế nào mà gel với người hỏi "nói không hỏi" triết học mặc dù. –

2

Làm thế nào về một giải pháp tổng quát 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; }); 
} 
Các vấn đề liên quan