2013-12-12 47 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。我运行它反对我的测试,检查我能想到的所有边缘情况:) –