Gruppera data (C#)

Gruppering syftar på åtgärden att placera data i grupper så att elementen i varje grupp delar ett gemensamt attribut. Följande bild visar resultatet av att gruppera en sekvens med tecken. Nyckeln för varje grupp är tecknet.

Diagram som visar en LINQ-grupperingsåtgärd

Standardmetoderna för frågeoperatorer som grupperar dataelement visas i följande tabell.

Metodnamn beskrivning Syntax för C#-frågeuttryck Mer information
GroupBy Grupperar element som delar ett gemensamt attribut. Ett IGrouping<TKey,TElement> objekt representerar varje grupp. group … by

-eller-

group … by … into …
Enumerable.GroupBy

Queryable.GroupBy
ToLookup Infogar element i en Lookup<TKey,TElement> (en en-till-många-ordlista) baserat på en nyckelväljare. Ej tillämpbart. Enumerable.ToLookup

I följande kodexempel används group by -satsen för att gruppera heltal i en lista beroende på om de är jämna eller udda.

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];

IEnumerable<IGrouping<int, int>> query = from number in numbers
                                         group number by number % 2;

foreach (var group in query)
{
    Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
    foreach (int i in group)
    {
        Console.WriteLine(i);
    }
}

Motsvarande fråga med metodsyntax visas i följande kod:

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];

IEnumerable<IGrouping<int, int>> query = numbers
    .GroupBy(number => number % 2);

foreach (var group in query)
{
    Console.WriteLine(group.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
    foreach (int i in group)
    {
        Console.WriteLine(i);
    }
}

I följande exempel i den här artikeln används vanliga datakällor för det här området:

public enum GradeLevel
{
    FirstYear = 1,
    SecondYear,
    ThirdYear,
    FourthYear
};

public class Student
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public required int ID { get; init; }

    public required GradeLevel Year { get; init; }
    public required List<int> Scores { get; init; }

    public required int DepartmentID { get; init; }
}

public class Teacher
{
    public required string First { get; init; }
    public required string Last { get; init; }
    public required int ID { get; init; }
    public required string City { get; init; }
}
public class Department
{
    public required string Name { get; init; }
    public int ID { get; init; }

    public required int TeacherID { get; init; }
}

Var Student och en har en betygsnivå, en primär avdelning och en serie poäng. En Teacher har också en City egenskap som identifierar det campus där läraren har klasser. A Department har ett namn och en referens till en Teacher som fungerar som avdelningschef.

Gruppfrågeresultat

Gruppering är en av de mest kraftfulla funktionerna i LINQ. Följande exempel visar hur du grupperar data på olika sätt:

  • Med en enda egenskap.
  • Med den första bokstaven i en strängegenskap.
  • Efter ett beräknat numeriskt intervall.
  • Efter booleskt predikat eller annat uttryck.
  • Med en sammansatt nyckel.

Dessutom projicerar de två sista frågorna sina resultat till en ny anonym typ som endast innehåller elevens för- och efternamn. Mer information finns i gruppsatsen.

Gruppera efter exempel på enskild egenskap

I följande exempel visas hur du grupperar källelement med hjälp av en enda egenskap för elementet som gruppnyckel. Nyckeln är ett enum, elevens år i skolan. Grupperingsåtgärden använder standardjämlikhetsjämföraren för typen.

var groupByYearQuery =
    from student in students
    group student by student.Year into newGroup
    orderby newGroup.Key
    select newGroup;

foreach (var yearGroup in groupByYearQuery)
{
    Console.WriteLine($"Key: {yearGroup.Key}");
    foreach (var student in yearGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

Motsvarande kod med hjälp av metodsyntax visas i följande exempel:

// Variable groupByLastNamesQuery is an IEnumerable<IGrouping<string,
// DataClass.Student>>.
var groupByYearQuery = students
    .GroupBy(student => student.Year)
    .OrderBy(newGroup => newGroup.Key);

foreach (var yearGroup in groupByYearQuery)
{
    Console.WriteLine($"Key: {yearGroup.Key}");
    foreach (var student in yearGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

Exempel på gruppering efter värde

I följande exempel visas hur du grupperar källelement med hjälp av något annat än en egenskap för objektet för gruppnyckeln. I det här exemplet är nyckeln den första bokstaven i elevens familjenamn.

var groupByFirstLetterQuery =
    from student in students
    let firstLetter = student.LastName[0]
    group student by firstLetter;

foreach (var studentGroup in groupByFirstLetterQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

Kapslad foreach krävs för åtkomst till gruppobjekt.

Motsvarande kod med hjälp av metodsyntax visas i följande exempel:

var groupByFirstLetterQuery = students
    .GroupBy(student => student.LastName[0]);

foreach (var studentGroup in groupByFirstLetterQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup)
    {
        Console.WriteLine($"\t{student.LastName}, {student.FirstName}");
    }
}

Gruppera efter ett intervallexempel

I följande exempel visas hur du grupperar källelement med hjälp av ett numeriskt intervall som en gruppnyckel. Frågan projicerar sedan resultatet till en anonym typ som endast innehåller det första namnet och familjenamnet och percentilintervallet som eleven tillhör. En anonym typ används eftersom det inte är nödvändigt att använda det fullständiga Student objektet för att visa resultatet. GetPercentile är en hjälpfunktion som beräknar en percentil baserat på elevens genomsnittliga poäng. Metoden returnerar ett heltal mellan 0 och 10.

static int GetPercentile(Student s)
{
    double avg = s.Scores.Average();
    return avg > 0 ? (int)avg / 10 : 0;
}

var groupByPercentileQuery =
    from student in students
    let percentile = GetPercentile(student)
    group new
    {
        student.FirstName,
        student.LastName
    } by percentile into percentGroup
    orderby percentGroup.Key
    select percentGroup;

foreach (var studentGroup in groupByPercentileQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key * 10}");
    foreach (var item in studentGroup)
    {
        Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
    }
}

Kapslad foreach krävs för att iterera över grupper och gruppobjekt. Motsvarande kod med hjälp av metodsyntax visas i följande exempel:

static int GetPercentile(Student s)
{
    double avg = s.Scores.Average();
    return avg > 0 ? (int)avg / 10 : 0;
}

var groupByPercentileQuery = students
    .Select(student => new { student, percentile = GetPercentile(student) })
    .GroupBy(student => student.percentile)
    .Select(percentGroup => new
    {
        percentGroup.Key,
        Students = percentGroup.Select(s => new { s.student.FirstName, s.student.LastName })
    })
    .OrderBy(percentGroup => percentGroup.Key);

foreach (var studentGroup in groupByPercentileQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key * 10}");
    foreach (var item in studentGroup.Students)
    {
        Console.WriteLine($"\t{item.LastName}, {item.FirstName}");
    }
}

Gruppera efter jämförelseexempel

I följande exempel visas hur du grupperar källelement med hjälp av ett booleskt jämförelseuttryck. I det här exemplet testar det booleska uttrycket om en elevs genomsnittliga examenspoäng är större än 75. Precis som i tidigare exempel projiceras resultaten till en anonym typ eftersom det fullständiga källelementet inte behövs. Egenskaperna i den anonyma typen blir egenskaper för Key medlemmen.

var groupByHighAverageQuery =
    from student in students
    group new
    {
        student.FirstName,
        student.LastName
    } by student.Scores.Average() > 75 into studentGroup
    select studentGroup;

foreach (var studentGroup in groupByHighAverageQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup)
    {
        Console.WriteLine($"\t{student.FirstName} {student.LastName}");
    }
}

Motsvarande fråga med metodsyntax visas i följande kod:

var groupByHighAverageQuery = students
    .GroupBy(student => student.Scores.Average() > 75)
    .Select(group => new
    {
        group.Key,
        Students = group.AsEnumerable().Select(s => new { s.FirstName, s.LastName })
    });

foreach (var studentGroup in groupByHighAverageQuery)
{
    Console.WriteLine($"Key: {studentGroup.Key}");
    foreach (var student in studentGroup.Students)
    {
        Console.WriteLine($"\t{student.FirstName} {student.LastName}");
    }
}

Gruppera efter anonym typ

I följande exempel visas hur du använder en anonym typ för att kapsla in en nyckel som innehåller flera värden. I det här exemplet är det första nyckelvärdet den första bokstaven i elevens familjenamn. Det andra nyckelvärdet är ett booleskt värde som anger om eleven fick över 85 poäng på det första provet. Du kan beställa grupperna efter valfri egenskap i nyckeln.

var groupByCompoundKey =
    from student in students
    group student by new
    {
        FirstLetterOfLastName = student.LastName[0],
        IsScoreOver85 = student.Scores[0] > 85
    } into studentGroup
    orderby studentGroup.Key.FirstLetterOfLastName
    select studentGroup;

foreach (var scoreGroup in groupByCompoundKey)
{
    var s = scoreGroup.Key.IsScoreOver85 ? "more than 85" : "less than 85";
    Console.WriteLine($"Name starts with {scoreGroup.Key.FirstLetterOfLastName} who scored {s}");
    foreach (var item in scoreGroup)
    {
        Console.WriteLine($"\t{item.FirstName} {item.LastName}");
    }
}

Motsvarande fråga med metodsyntax visas i följande kod:

var groupByCompoundKey = students
    .GroupBy(student => new
    {
        FirstLetterOfLastName = student.LastName[0],
        IsScoreOver85 = student.Scores[0] > 85
    })
    .OrderBy(studentGroup => studentGroup.Key.FirstLetterOfLastName);

foreach (var scoreGroup in groupByCompoundKey)
{
    var s = scoreGroup.Key.IsScoreOver85 ? "more than 85" : "less than 85";
    Console.WriteLine($"Name starts with {scoreGroup.Key.FirstLetterOfLastName} who scored {s}");
    foreach (var item in scoreGroup)
    {
        Console.WriteLine($"\t{item.FirstName} {item.LastName}");
    }
}

Skapa en kapslad grupp

I följande exempel visas hur du skapar kapslade grupper i ett LINQ-frågeuttryck. Varje grupp som skapas enligt elevår eller betygsnivå delas sedan in ytterligare i grupper baserat på individernas namn.

var nestedGroupsQuery =
    from student in students
    group student by student.Year into newGroup1
    from newGroup2 in
    from student in newGroup1
    group student by student.LastName
    group newGroup2 by newGroup1.Key;

foreach (var outerGroup in nestedGroupsQuery)
{
    Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
    foreach (var innerGroup in outerGroup)
    {
        Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
        foreach (var innerGroupElement in innerGroup)
        {
            Console.WriteLine($"\t\t{innerGroupElement.LastName} {innerGroupElement.FirstName}");
        }
    }
}

Tre kapslade foreach loopar krävs för att iterera över de inre elementen i en kapslad grupp.
(Hovra musmarkören över iterationsvariablerna , outerGroup, innerGroupoch innerGroupElement för att se deras faktiska typ.)

Motsvarande fråga med metodsyntax visas i följande kod:

var nestedGroupsQuery =
    students
    .GroupBy(student => student.Year)
    .Select(newGroup1 => new
    {
        newGroup1.Key,
        NestedGroup = newGroup1
            .GroupBy(student => student.LastName)
    });

foreach (var outerGroup in nestedGroupsQuery)
{
    Console.WriteLine($"DataClass.Student Level = {outerGroup.Key}");
    foreach (var innerGroup in outerGroup.NestedGroup)
    {
        Console.WriteLine($"\tNames that begin with: {innerGroup.Key}");
        foreach (var innerGroupElement in innerGroup)
        {
            Console.WriteLine($"\t\t{innerGroupElement.LastName} {innerGroupElement.FirstName}");
        }
    }
}

Utföra en underfråga i en grupperingsåtgärd

Den här artikeln visar två olika sätt att skapa en fråga som beställer källdata i grupper och sedan utför en underfråga över varje grupp individuellt. Den grundläggande tekniken i varje exempel är att gruppera källelementen med hjälp av en fortsättning med namnet newGroup, och sedan generera en ny underfråga mot newGroup . Den här underfrågan körs mot varje ny grupp som skapas av den yttre frågan. I det här exemplet är de slutliga utdata inte en grupp, utan en platt sekvens av anonyma typer.

Mer information om hur du grupperar finns i gruppsatsen. Mer information om fortsättningar finns i. I följande exempel används en minnesintern datastruktur som datakälla, men samma principer gäller för alla typer av LINQ-datakällor.

var queryGroupMax =
    from student in students
    group student by student.Year into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore = (
            from student2 in studentGroup
            select student2.Scores.Average()
        ).Max()
    };

var count = queryGroupMax.Count();
Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)
{
    Console.WriteLine($"  {item.Level} Highest Score={item.HighestScore}");
}

Frågan i föregående kodfragment kan också skrivas med hjälp av metodsyntax. Följande kodfragment har en semantiskt likvärdig fråga skriven med hjälp av metodsyntax.

var queryGroupMax =
    students
        .GroupBy(student => student.Year)
        .Select(studentGroup => new
        {
            Level = studentGroup.Key,
            HighestScore = studentGroup.Max(student2 => student2.Scores.Average())
        });

var count = queryGroupMax.Count();
Console.WriteLine($"Number of groups = {count}");

foreach (var item in queryGroupMax)
{
    Console.WriteLine($"  {item.Level} Highest Score={item.HighestScore}");
}

Se även