Verileri Gruplandırma (C#)

Gruplandırma, her gruptaki öğelerin ortak bir özniteliği paylaşması için verileri gruplara yerleştirme işlemini ifade eder. Aşağıdaki çizimde, bir karakter dizisini gruplandırma sonuçları gösterilmektedir. Her grubun anahtarı karakterdir.

Diagram that shows a LINQ Grouping operation

Veri öğelerini gruplandıran standart sorgu işleci yöntemleri aşağıdaki tabloda listelenmiştir.

Yöntem Adı Açıklama C# Sorgu İfadesi Söz Dizimi Daha Fazla Bilgi
GroupBy Ortak bir özniteliği paylaşan öğeleri gruplandırma. Bir IGrouping<TKey,TElement> nesne her grubu temsil eder. group … by

-veya-

group … by … into …
Enumerable.GroupBy

Queryable.GroupBy
Tolookup Bir anahtar seçici işlevine göre öğeleri bire Lookup<TKey,TElement> çok sözlüğe ekler. Uygulanamaz. Enumerable.ToLookup

Aşağıdaki kod örneği, listedeki tamsayıları çift mi yoksa tek mi olduklarına göre gruplandırmak için yan tümcesini kullanır group by .

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

Yöntem söz dizimini kullanan eşdeğer sorgu aşağıdaki kodda gösterilir:

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

Bu makaledeki aşağıdaki örneklerde bu alan için ortak veri kaynakları kullanılır:

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

Her Student birinin bir not düzeyi, bir birincil bölüm ve bir dizi puanı vardır. Ayrıca, Teacher öğretmenin ders aldığı kampüsü tanımlayan bir City özelliği de vardır. A'nın Department bir adı ve bölüm başkanı olarak görev yapan bir Teacher kişi için bir referansı vardır.

Sorgu sonuçlarını gruplandırma

Gruplandırma, LINQ'in en güçlü özelliklerinden biridir. Aşağıdaki örneklerde verileri çeşitli yollarla gruplandırma gösterilmektedir:

  • Tek bir özellik tarafından.
  • Dize özelliğinin ilk harfine göre.
  • Hesaplanan bir sayısal aralığa göre.
  • Boole koşuluna veya başka bir ifadeye göre.
  • Bileşik anahtarla.

Buna ek olarak, son iki sorgu sonuçlarını yalnızca öğrencinin adını ve aile adını içeren yeni bir anonim türe yansıtmış olur. Daha fazla bilgi için group yan tümcesine bakın.

Tek özelliğe göre gruplandırma örneği

Aşağıdaki örnekte, öğesinin tek bir özelliğini grup anahtarı olarak kullanarak kaynak öğelerin nasıl gruplandırdığı gösterilmektedir. Anahtar, enumokuldaki öğrencinin yılıdır. Gruplandırma işlemi, tür için varsayılan eşitlik karşılaştırıcısını kullanır.

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

Yöntem söz dizimini kullanan eşdeğer kod aşağıdaki örnekte gösterilmiştir:

// 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}");
    }
}

Değere göre gruplandırma örneği

Aşağıdaki örnekte, grup anahtarı için nesnenin özelliğinden başka bir şey kullanarak kaynak öğelerin nasıl gruplandırdığı gösterilmektedir. Bu örnekte anahtar, öğrencinin aile adının ilk harfidir.

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

Grup öğelerine erişmek için iç içe foreach gereklidir.

Yöntem söz dizimini kullanan eşdeğer kod aşağıdaki örnekte gösterilmiştir:

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

Aralığa göre gruplandırma örneği

Aşağıdaki örnekte, grup anahtarı olarak sayısal bir aralık kullanarak kaynak öğelerin nasıl gruplandırdığı gösterilmektedir. Sorgu daha sonra sonuçları yalnızca öğrencinin ait olduğu ilk ve aile adını ve yüzdebirlik aralığını içeren anonim bir türe projeler. Sonuçları görüntülemek için tam Student nesnenin kullanılması gerekmeyen anonim bir tür kullanılır. GetPercentile , öğrencinin ortalama puanına göre yüzdebirlik değeri hesaplayan yardımcı bir işlevdir. yöntemi 0 ile 10 arasında bir tamsayı döndürür.

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

Gruplar ve grup öğeleri üzerinde yineleme yapmak için iç içe geçmiş foreach gerekir. Yöntem söz dizimini kullanan eşdeğer kod aşağıdaki örnekte gösterilmiştir:

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

Karşılaştırmaya göre gruplandırma örneği

Aşağıdaki örnekte, boole karşılaştırma ifadesi kullanılarak kaynak öğelerin nasıl gruplandırılmaya başlandığı gösterilmektedir. Bu örnekte Boole ifadesi, öğrencinin ortalama sınav puanının 75'ten büyük olup olmadığını test ediyor. Önceki örneklerde olduğu gibi, tam kaynak öğesi gerekli olmadığından sonuçlar anonim bir türe yansıtılır. Anonim türdeki özellikler üyenin Key özellikleri haline gelir.

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

Yöntem söz dizimini kullanan eşdeğer sorgu aşağıdaki kodda gösterilir:

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

Anonim türe göre gruplandırma

Aşağıdaki örnekte birden çok değer içeren bir anahtarı kapsüllemek için anonim bir türün nasıl kullanılacağı gösterilmektedir. Bu örnekte, ilk anahtar değeri öğrencinin aile adının ilk harfidir. İkinci anahtar değeri, öğrencinin ilk sınavda 85'in üzerinde puan alıp alamadığını belirten bir Boole değeridir. Grupları anahtardaki herhangi bir özelliğe göre sıralayabilirsiniz.

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

Yöntem söz dizimini kullanan eşdeğer sorgu aşağıdaki kodda gösterilir:

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

İç içe geçmiş grup oluşturma

Aşağıdaki örnekte, LINQ sorgu ifadesinde iç içe grupların nasıl oluşturulacağı gösterilmektedir. Öğrenci yılı veya not düzeyine göre oluşturulan her grup daha sonra kişilerin adlarına göre gruplara ayrılır.

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

İç içe bir foreach grubun iç öğelerini yinelemek için üç iç içe döngü gerekir.
(Gerçek türlerini görmek için fare imlecini yineleme değişkenleri outerGroup, innerGroupve innerGroupElement üzerine getirin.)

Yöntem söz dizimini kullanan eşdeğer sorgu aşağıdaki kodda gösterilir:

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

Gruplandırma işleminde alt sorgu gerçekleştirme

Bu makalede, kaynak verileri gruplar halinde sıralayan ve her grup üzerinde ayrı ayrı bir alt sorgu gerçekleştiren bir sorgu oluşturmanın iki farklı yolu gösterilmektedir. Her örnekteki temel teknik, adlı bir devamlılık kullanarak kaynak öğeleri gruplandırmak ve ardından ile newGroupyeni bir alt sorgu oluşturmaktırnewGroup. Bu alt sorgu, dış sorgu tarafından oluşturulan her yeni grup için çalıştırılır. Bu özel örnekte son çıkış bir grup değil, anonim türlerden oluşan düz bir dizidir.

Gruplandırma hakkında daha fazla bilgi için bkz . group yan tümcesi. Devamlılıklar hakkında daha fazla bilgi için bkz . içine. Aşağıdaki örnek, veri kaynağı olarak bellek içi bir veri yapısı kullanır, ancak her tür LINQ veri kaynağı için aynı ilkeler geçerlidir.

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

Önceki kod parçacığındaki sorgu, yöntem söz dizimi kullanılarak da yazılabilir. Aşağıdaki kod parçacığında yöntem söz dizimi kullanılarak yazılmış bir eşanlamlı eşdeğer sorgu vardır.

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

Ayrıca bkz.