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

Ich möchte die Eltern durch das niedrigste Alter ihrer Kinder bestellen, um das zweite oder dritte Kind im Falle einer Krawatte.Order Eltern Sammlung von Mindestwerten in Kindersammlung in Linq

Die nächstgelegene ich gekommen bin ist dies, die nur Aufträge durch das jüngste Kind:

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

Dies ist nicht für die zweiten nicht-Konto (oder dritten, etc.) Jüngste im Fall eines Unentschiedens.

Angesichts dieser 3 Eltern mit entsprechenden Kind Alter, ich möchte sie in dieser Reihenfolge kommen.

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

möglich Duplikat [Gibt es eine eingebaute Möglichkeit, IEnumerable zu vergleichen (durch ihre Elemente)?] (Http://stackoverflow.com/questions/2811725/is-there-a-built-in-way-to-compare-ienumerablet-by-their-elements) –

+0

Insbesondere können Sie die Implementierung von dort als 'Compare'-Methode wie so: 'eltern.OrderBy (p => p.Kinder.Wählen (x => x.Age) .ToList(), neuer SequenceComparer ())' (wobei 'SequenceComparer .Compare' ist die Implementierung bei dieser Verbindung) –

Antwort

3

Also, was Sie versuchen, auf einer konzeptionellen Ebene zu tun, ist zwei Sequenzen zu vergleichen. Anstatt zu versuchen, diese spezielle Sequenz speziell zu behandeln, können wir einfach einen Vergleicher schreiben, der zwei beliebige Sequenzen vergleichen kann.

Es wird durch die Elemente in der Reihenfolge die Elemente an der gleichen Position vergleichen, und wenn es ein Paar findet, die nicht gleich sind, dann kennt es das Ergebnis.

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

Jetzt können wir einfach diesen Vergleich verwenden, wenn OrderBy Aufruf:

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

+1, das 'Compare' Funktion ist so nett ... Ich denke, außer wenn die Sequenzen unterschiedliche Längen haben. – Rawling

+1

@Rawling Ja, Sie können hinzufügen '.DefaultIfEmpty (x.Count(). CompareTo (y.Count()))' direkt nach dem wo, um diesen Fall zu decken, aber das ist beide Sequenzen zweimal durchlaufen und vollständig, was eine Menge ist mehr Arbeit als benötigt wird.Die einzige Alternative wäre, die gesamte LINQ-Lösung zu verwerfen und alles durch manuelle Iteration zu tun, was ich vermeiden wollte. – Servy

0

könnten Sie ThenBy verwenden und nehmen Sie die 2. und 3. Kinder. Aber das ist nicht skalierbar, so hängt es von den Bedürfnissen der impl

Wenn Sie etwas robuster wollen, könnten Sie Folgendes tun. Es wird für diesen speziellen Fall funktionieren. Ich werde sehen, ob ich es optimieren kann allgemeinere obwohl sein :)

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

Sie müssen so etwas wie diese Erweiterung Methode schreiben:

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

Generika Umsetzung:

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

Das Obige wurde an den folgenden Daten getestet: A (4,1,2) B (2,4) C (2,2) D() 'sortiert als' DACB ' –

+0

Schöne Verwendung von Reißverschluss. Ich habe es gegen meinen Test ausgeführt, der alle Randfälle überprüft, die ich denken konnte :) –

Verwandte Themen