2013-12-12 16 views
6
Parent{ List<Child> Children {get;set;} } 
Child { int Age {get;set;} } 

Tôi muốn đặt hàng cha mẹ theo độ tuổi thấp nhất của con cái, tiếp tục cho trẻ thứ hai hoặc thứ ba trong trường hợp cà vạt.Đặt mua bộ sưu tập gốc theo giá trị tối thiểu trong bộ sưu tập trẻ em tại LINQ

Gần nhất tôi đã đi là thế này, mà chỉ đơn đặt hàng của con gái út:

parents.OrderBy(p => p.Children.Min(c => c.Age)) 

này không chiếm thứ hai (hoặc thứ ba, vv) trẻ nhất trong trường hợp của một tie.

Với 3 cha mẹ có độ tuổi tương ứng của trẻ em, tôi muốn chúng xuất hiện theo thứ tự này.

  • P1 1,2,7
  • P2 1,3,6
  • P3 1,4,5
+1

thể trùng lặp của [Có cách nào tích hợp để so sánh IEnumerable (bởi các yếu tố của họ)?] (Http://stackoverflow.com/questions/2811725/is-there-a-built-in-way-to-compare-ienumerablet-by-their-elements) –

+0

Cụ thể, bạn có thể sử dụng cách triển khai từ đó như một phương thức 'So sánh' như như vậy: 'parents.OrderBy (p => p.Children.Select (x => x.Age) .ToList(), new SequenceComparer ())' (trong đó 'SequenceComparer .Compare' là việc thực hiện tại liên kết đó) –

Trả lời

3

Vì vậy, những gì bạn đang cố gắng làm, ở mức khái niệm, được so sánh hai chuỗi. Thay vì cố gắng đặc biệt cho trường hợp này, chúng ta có thể viết một trình so sánh có khả năng so sánh hai chuỗi bất kỳ.

Nó sẽ đi qua các mục trong chuỗi so sánh các mục ở cùng một vị trí, và sau đó nếu nó tìm thấy một cặp không bằng nhau, nó sẽ biết kết quả.

public class SequenceComparer<TSource> : IComparer<IEnumerable<TSource>> 
{ 
    private IComparer<TSource> comparer; 
    public SequenceComparer(IComparer<TSource> comparer = null) 
    { 
     this.comparer = comparer ?? Comparer<TSource>.Default; 
    } 
    public int Compare(IEnumerable<TSource> x, IEnumerable<TSource> y) 
    { 
     return x.Zip(y, (a, b) => comparer.Compare(a, b)) 
       .Where(n => n != 0) 
       .DefaultIfEmpty(x.Count().CompareTo(y.Count())) 
       .First(); 
    } 
} 

Bây giờ chúng ta chỉ đơn giản là có thể sử dụng comparer này khi gọi OrderBy:

var query = parents.OrderBy(parent => parent.Children 
    .OrderBy(child => child.Age) 
    .Select(child => child.Age) 
    , new SequenceComparer<int>()); 
+0

+1, mà 'So sánh' chức năng rất gọn gàng ... Tôi đoán trừ khi các trình tự có độ dài khác nhau. – Rawling

+1

@Rawling Yeah, bạn có thể thêm '.DefaultIfEmpty (x.Count(). CompareTo (y.Count()))' ngay sau khi ở đâu để bao gồm trường hợp đó, nhưng đó là lặp lại cả hai chuỗi hai lần và đầy đủ, đó là rất nhiều nhiều công việc hơn là cần thiết.Giải pháp thay thế duy nhất sẽ là giải quyết toàn bộ giải pháp LINQ và thực hiện tất cả thông qua việc lặp lại thủ công mà tôi muốn tránh. – Servy

0

Bạn có thể sử dụng ThenBy và lấy 2 và trẻ em 3. Tuy nhiên, đó không phải là khả năng mở rộng, do đó, nó phụ thuộc vào nhu cầu của impl

Nếu bạn muốn một cái gì đó mạnh mẽ hơn, bạn có thể làm như sau. Nó sẽ làm việc cho trường hợp cụ thể này. Tôi sẽ xem liệu tôi có thể tối ưu hóa nó để được chung chung hơn mặc dù :)

public static class myExt 
    { 
    public static List<Parent> OrderByWithTieBreaker(this List<Parent> parents, int depth = 0) 
    { 
     if (depth > parents[0].Children.Count()) 
     return parents; 
     var returnedList = new List<Parent>(); 

     Func<Parent, int> keySelector = x => 
    { 
     IEnumerable<Child> enumerable = x.Children.OrderBy(y => y.Age).Skip(depth); 
     if (!enumerable.Any()) 
     return 0; //If no children left, then return lowest possible age 
     return enumerable.Min(z => z.Age); 
    }; 
     var orderedParents = parents.OrderBy(keySelector); 
     var groupings = orderedParents.GroupBy(keySelector); 
     foreach (var grouping in groupings) 
     { 
     if (grouping.Count() > 1) 
     { 
      var innerOrder = grouping.ToList().OrderByWithTieBreaker(depth + 1); 
      returnedList = returnedList.Union(innerOrder).ToList(); 
     } 
     else 
      returnedList.Add(grouping.First()); 
     } 
     return returnedList; 
    } 
    } 
    [TestFixture] 
    public class TestClass 
    { 
    public class Parent { public string Name { get; set; } public List<Child> Children { get; set; } } 
    public class Child { public int Age {get;set;} } 

    [Test] 
    public void TestName() 
    { 
     var parents = new List<Parent> 
     { 
      new Parent{Name="P3", Children = new List<Child>{new Child{Age=1}, new Child{Age=3}, new Child{Age=6}, new Child{Age=7}}}, 
      new Parent{Name="P4", Children = new List<Child>{new Child{Age=1}, new Child{Age=3}, new Child{Age=6}, new Child{Age=7}}}, 
      new Parent{Name="P2", Children = new List<Child>{new Child{Age=1}, new Child{Age=3}, new Child{Age=6}}}, 
      new Parent{Name="P1", Children = new List<Child>{new Child{Age=1}, new Child{Age=2}, new Child{Age=7}}}, 
      new Parent{Name="P5", Children = new List<Child>{new Child{Age=1}, new Child{Age=4}, new Child{Age=5}}} 
     }; 
     var f = parents.OrderByWithTieBreaker(); 
     int count = 1; 
     foreach (var d in f) 
     { 
     Assert.That(d.Name, Is.EqualTo("P"+count)); 
     count++; 
     } 
    } 
2

Bạn sẽ cần phải viết một cái gì đó giống như phương pháp mở rộng này:

var orderedParents = parents.OrderBy(p => p.Children, c => c.Age); 

Generic thực hiện:

/// <summary> 
/// Given a way to determine a collection of elements (for example 
/// children of a parent) and a comparable property of those items 
/// (for example age of a child) this orders a collection of elements 
/// according to the sorting order of the property of the first element 
/// of their respective collections. In case of a tie, fall back to 
/// subsequent elements as appropriate. 
/// </summary> 
public static IOrderedEnumerable<T> OrderBy<T, TKey, TValue>(this IEnumerable<T> @this, Func<T, IEnumerable<TKey>> getKeys, Func<TKey, TValue> getValue) 
    where TValue : IComparable<TValue> 
{ 
    return @this.OrderBy(x => x, new KeyComparer<T, TKey, TValue>(getKeys, getValue)); 
} 

private class KeyComparer<T, TKey, TValue> : IComparer<T> 
    where TValue : IComparable<TValue> 
{ 
    private Func<T, IEnumerable<TKey>> GetKeys; 
    private Func<TKey, TValue> GetValue; 

    public KeyComparer(Func<T, IEnumerable<TKey>> getKeys, Func<TKey, TValue> getValue) 
    { 
     this.GetKeys = getKeys; 
     this.GetValue = getValue; 
    } 

    public int Compare(T x, T y) 
    { 
     var xKeys = GetKeys(x).OrderBy(GetValue).Select(GetValue); 
     var yKeys = GetKeys(y).OrderBy(GetValue).Select(GetValue); 

     foreach (var pair in xKeys.Zip(yKeys, Tuple.Create)) 
     { 
      if (pair.Item1.CompareTo(pair.Item2) != 0) 
       return pair.Item1.CompareTo(pair.Item2); 
     } 

     return xKeys.Count().CompareTo(yKeys.Count()); 
    } 
} 
+0

Ở trên đã được thử nghiệm trên các dữ liệu sau: 'A (4,1,2) B (2,4) C (2,2) D()' sắp xếp như 'DACB ' –

+0

Sử dụng mã zip đẹp. Tôi chạy nó chống lại thử nghiệm của tôi kiểm tra tất cả các trường hợp cạnh tôi có thể nghĩ đến :) –

Các vấn đề liên quan