按Linq中子集合中的最小值对父集合进行排序

时间:2013-12-12 19:30:59

标签: c# linq

Parent{ List<Child> Children {get;set;} }
Child { int Age {get;set;} }

我想在孩子最低年龄的时候订购父母,在领带的情况下,请向第二或第三个孩子订购。

我最接近的是这个,只有最小的孩子订购:

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

在平局情况下,这并不是第二(或第三等)最年轻的。

鉴于这3名父母有相应的孩子年龄,我希望他们按此顺序出来。

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

3 个答案:

答案 0 :(得分: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>());

答案 1 :(得分: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());
    }
}

答案 2 :(得分: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++;
      }
    }