Linq bestellen nach, gruppieren nach und bestellen nach jeder Gruppe?

72

Ich habe ein Objekt, das ungefähr so ​​aussieht:

public class Student
{
    public string Name { get; set; } 
    public int Grade { get; set; }
}

Ich möchte die folgende Abfrage erstellen: Gruppieren Sie die Noten nach Schülernamen, ordnen Sie jede Schülergruppe nach Noten und ordnen Sie die Gruppen nach der Höchstnote in jeder Gruppe.

So wird es aussehen:

A 100
A 80
B 80
B 50
B 40
C 70
C 30

Ich habe die folgende Abfrage erstellt:

StudentsGrades.GroupBy(student => student.Name)
    .OrderBy(studentGradesGroup => studentGradesGroup.Max(student => student.Grade));

Aber das kehrt zurück IEnumerable IGrouping, und ich habe keine Möglichkeit, die Liste darin zu sortieren, es sei denn, ich mache das in einer anderen foreachAbfrage und füge die Ergebnisse mit einer anderen Liste hinzu AddRange.

Gibt es einen schöneren Weg, das zu tun?

Rita
quelle

Antworten:

138

Sicher:

var query = grades.GroupBy(student => student.Name)
                  .Select(group => 
                        new { Name = group.Key,
                              Students = group.OrderByDescending(x => x.Grade) })
                  .OrderBy(group => group.Students.First().Grade);

Beachten Sie, dass Sie nach der Bestellung nur die erste Note in jeder Gruppe belegen können, da Sie bereits wissen, dass der erste Eintrag die höchste Note hat.

Dann könnten Sie sie anzeigen mit:

foreach (var group in query)
{
    Console.WriteLine("Group: {0}", group.Name);
    foreach (var student in group.Students)
    {
        Console.WriteLine("  {0}", student.Grade);
    }
}
Jon Skeet
quelle
3
Für diese var-Gruppenvariable gibt es keine Key-Eigenschaft.
Amy B
5
Vielen Dank! Dies ist die fertige Abfrage - studentGrades.GroupBy (student => student.Name) .Select (group => new {Students = group.OrderByDescending (x => x.Grade)}) .OrderByDescending (group => group.Students. First (). Grade) .SelectMany (group => group.Students)
Rita
20

So geht's ohne Projektion:

StudentsGrades.OrderBy(student => student.Name).
ThenBy(student => student.Grade);
Sawyer
quelle
3
Ich denke, das liegt daran, dass die Daten zwar wie gewünscht bestellt werden, aber zufällig. Die Reihenfolge der Schüler mit den besten Leistungen ist ebenfalls alphabetisch. Wenn Sie Schüler C mit 90 bewerten, sollten sie über Schüler B erscheinen. In dieser Antwort würden sie dies nicht tun.
Red
14

Ich denke, Sie möchten eine zusätzliche Projektion, die jede Gruppe einer sortierten Version der Gruppe zuordnet:

.Select(group => group.OrderByDescending(student => student.Grade))

Es scheint auch , wie Sie vielleicht danach eine weitere Abflachung Operation wollen , die Sie anstelle einer Folge von Gruppen eine Folge von Studenten geben:

.SelectMany(group => group)

Sie können beide jederzeit zu einem einzigen SelectMany Aufruf zusammenfassen, der die Projektion und das Abflachen zusammen ausführt.


EDIT: Wie Jon Skeet betont, gibt es bestimmte Ineffizienzen in der Gesamtabfrage; Die Informationen, die beim Sortieren jeder Gruppe gewonnen werden, werden nicht für die Reihenfolge der Gruppen selbst verwendet. Durch Verschieben der Sortierung jeder Gruppe vor der Reihenfolge der Gruppen selbst kann die MaxAbfrage in eine einfachere FirstAbfrage ausgewichen werden.

Ani
quelle
5

Versuche dies...

public class Student 
    {
        public int Grade { get; set; }
        public string Name { get; set; }
        public override string ToString()
        {
            return string.Format("Name{0} : Grade{1}", Name, Grade);
        }
    }

class Program
{
    static void Main(string[] args)
    {

      List<Student> listStudents = new List<Student>();
      listStudents.Add(new Student() { Grade = 10, Name = "Pedro" });
      listStudents.Add(new Student() { Grade = 10, Name = "Luana" });
      listStudents.Add(new Student() { Grade = 10, Name = "Maria" });
      listStudents.Add(new Student() { Grade = 11, Name = "Mario" });
      listStudents.Add(new Student() { Grade = 15, Name = "Mario" });
      listStudents.Add(new Student() { Grade = 10, Name = "Bruno" });
      listStudents.Add(new Student() { Grade = 10, Name = "Luana" });
      listStudents.Add(new Student() { Grade = 11, Name = "Luana" });
      listStudents.Add(new Student() { Grade = 22, Name = "Maria" });
      listStudents.Add(new Student() { Grade = 55, Name = "Bruno" });
      listStudents.Add(new Student() { Grade = 77, Name = "Maria" });
      listStudents.Add(new Student() { Grade = 66, Name = "Maria" });
      listStudents.Add(new Student() { Grade = 88, Name = "Bruno" });
      listStudents.Add(new Student() { Grade = 42, Name = "Pedro" });
      listStudents.Add(new Student() { Grade = 33, Name = "Bruno" });
      listStudents.Add(new Student() { Grade = 33, Name = "Luciana" });
      listStudents.Add(new Student() { Grade = 17, Name = "Maria" });
      listStudents.Add(new Student() { Grade = 25, Name = "Luana" });
      listStudents.Add(new Student() { Grade = 25, Name = "Pedro" });

      listStudents.GroupBy(g => g.Name).OrderBy(g => g.Key).SelectMany(g => g.OrderByDescending(x => x.Grade)).ToList().ForEach(x => Console.WriteLine(x.ToString()));
    }
}
Bruno Torres
quelle
1

Alternativ können Sie Folgendes tun:

     var _items = from a in StudentsGrades
                  group a by a.Name;

     foreach (var _itemGroup in _items)
     {
        foreach (var _item in _itemGroup.OrderBy(a=>a.grade))
        {
           ------------------------
           --------------------------
        }
     }
agileDev
quelle