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

我想按照子女的最低年齡命令父母,如果打結,則繼續到第二個或第三個子女。通過Linq的子集合中的最小值對父集合進行訂購

我來最接近的是這一點,這只是訂單減少的最小的孩子:

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

這並不佔第二(或第三等)最年輕的平局的情況下。

鑑於這3位父母有相應的孩子年齡,我希望他們按照這個順序出來。

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

可能重複[是否有一個內置的方式來比較的IEnumerable (由它們的元素)?(HTTP:/ /stackoverflow.com/questions/2811725/is-there-a-built-in-way-to-compare-ienumerablet-by-their-elements) –

+0

具體來說,你可以使用像'Compare'這樣的方法所以:'parents.OrderBy(p => p.Children.Select(x => x.Age).ToList(),new SequenceComparer'(其中'SequenceComparer .Compare「是該鏈接處的實現) –

回答

3

所以你想要做的,在概念層面上,是比較兩個序列。我們可以簡單地寫一個能比較任何兩個序列的比較器,而不是試圖對這個特定的序列進行特殊處理。

它會通過序列中的項目比較相同位置上的項目,然後如果發現一對不相等的對,它就會知道結果。

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

現在,我們可以調用OrderBy時,只需使用該比較器:

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

+1,'比較'函數是如此整潔...我猜想除了如果序列是不同的長度。 – Rawling

+1

@Rawling是的,你可以添加'.DefaultIfEmpty(x.Count()。CompareTo(y.Count()))'在這個案例的後面,但是這兩個序列迭代兩次和完全,這是很多比需要更多的工作。唯一的選擇是廢棄整個LINQ解決方案,並通過人工迭代完成,這是我想避免的。 – Servy

0

你可以使用ThenBy,並採取第二和第三個孩子。但是,這是不可擴展的,所以它取決於impl的需求。如果您想要更強大的功能,可以執行以下操作。它將適用於這個特定的情況。我要看看我是否能優化它是更通用雖然:)

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

你需要寫這樣的擴展方法:

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

通用實現:

/// <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

上面已經在以下數據上進行了測試:'A(4,1,2)B(2,4)C(2,2)D()'類似於'DACB ' –

+0

很好的使用zip。我運行它反對我的測試,檢查我能想到的所有邊緣情況:) –