Pengelompokan Data (C#)

Pengelompokan mengacu pada operasi penempatan data ke dalam kelompok sehingga elemen dalam setiap kelompok memiliki atribut umum yang sama. Ilustrasi berikut menunjukkan hasil pengelompokan urutan karakter. Kunci untuk setiap kelompok adalah karakter.

Diagram yang memperlihatkan operasi Pengelompokan LINQ

Metode operator kueri standar yang mengelompokkan elemen data tercantum dalam tabel berikut.

Nama Metode Deskripsi Sintaksis Ekspresi Kueri C# Informasi Selengkapnya
GroupBy Mengelompokkan elemen yang memiliki atribut umum yang sama. Objek IGrouping<TKey,TElement> mewakili setiap grup. group … by

-atau-

group … by … into …
Enumerable.GroupBy

Queryable.GroupBy
ToLookup Memasukkan elemen ke dalam Lookup<TKey,TElement> (kamus satu-ke-banyak) berdasarkan fungsi pemilih kunci. Tidak berlaku. Enumerable.ToLookup

Contoh kode berikut menggunakan klausul group by untuk mengelompokkan bilangan bulat dalam daftar sesuai dengan apakah bilangan bulat tersebut genap atau ganjil.

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

Kueri yang setara menggunakan sintaks metode diperlihatkan dalam kode berikut:

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

Contoh berikut dalam artikel ini menggunakan sumber data umum untuk area ini:

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

Masing-masing Student memiliki tingkat kelas, departemen utama, dan serangkaian skor. A Teacher juga memiliki City properti yang mengidentifikasi kampus tempat guru mengadakan kelas. A Department memiliki nama, dan referensi untuk siapa Teacher yang menjabat sebagai kepala departemen.

Mengelompokkan hasil kueri

Pengelompokan adalah salah satu kemampuan paling kuat dari LINQ. Contoh berikut ini menunjukkan cara mengelompokkan data dengan berbagai cara:

  • Oleh satu properti.
  • Dengan huruf pertama dari properti string.
  • Dengan rentang numerik yang dihitung.
  • Dengan predikat Boolean atau ekspresi lainnya.
  • Berdasarkan kunci senyawa.

Selain itu, dua kueri terakhir memproyeksikan hasilnya menjadi jenis anonim baru yang hanya berisi nama depan dan keluarga siswa. Untuk informasi lebih lanjut, lihatlah klausa grup.

Kelompokkan berdasarkan contoh properti tunggal

Contoh berikut ini menunjukkan cara mengelompokkan elemen sumber dengan menggunakan satu properti elemen sebagai kunci grup. Kuncinya adalah enum, tahun siswa di sekolah. Operasi pengelompokan menggunakan pembanding kesetaraan default untuk jenisnya.

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

Kode yang setara menggunakan sintaks metode diperlihatkan dalam contoh berikut:

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

Kelompokkan berdasarkan nilai contoh

Contoh berikut ini menunjukkan cara mengelompokkan elemen sumber dengan menggunakan sesuatu selain properti objek untuk kunci grup. Dalam contoh ini, kuncinya adalah huruf pertama dari nama keluarga siswa.

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

Foreach berlapis diperlukan untuk mengakses item grup.

Kode yang setara menggunakan sintaks metode diperlihatkan dalam contoh berikut:

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

Kelompokkan menurut contoh rentang

Contoh berikut ini menunjukkan cara mengelompokkan elemen sumber dengan menggunakan rentang numerik sebagai kunci grup. Kueri kemudian memproyeksikan hasilnya ke dalam jenis anonim yang hanya berisi nama depan dan keluarga dan rentang persentil tempat siswa berada. Jenis anonim digunakan karena tidak perlu menggunakan objek lengkap Student untuk menampilkan hasilnya. GetPercentile adalah fungsi pembantu yang menghitung persentil berdasarkan nilai rata-rata siswa. Metode ini mengembalikan bilangan bulat antara 0 dan 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}");
    }
}

Foreach berlapis diperlukan untuk mengulangi item grup dan grup. Kode yang setara menggunakan sintaks metode diperlihatkan dalam contoh berikut:

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

Kelompokkan dengan contoh perbandingan

Contoh berikut ini menunjukkan cara mengelompokkan elemen sumber dengan menggunakan ekspresi perbandingan Boolean. Dalam contoh berikut ini, ekspresi Boolean menguji apakah nilai ujian rata-rata siswa lebih besar dari 75. Seperti dalam contoh sebelumnya, hasilnya diproyeksikan menjadi jenis anonim karena elemen sumber lengkap tidak diperlukan. Properti dalam jenis anonim menjadi properti pada Key anggota.

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

Kueri yang setara menggunakan sintaks metode diperlihatkan dalam kode berikut:

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

Kelompok menurut jenis anonim

Contoh berikut menunjukkan cara menggunakan tipe anonim untuk merangkum kunci yang berisi beberapa nilai. Dalam contoh ini, nilai kunci pertama adalah huruf pertama dari nama keluarga siswa. Nilai kunci kedua adalah Boolean yang menentukan apakah siswa mencetak lebih dari 85 pada ujian pertama. Anda bisa memesan grup berdasarkan properti apa pun di kunci.

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

Kueri yang setara menggunakan sintaks metode diperlihatkan dalam kode berikut:

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

Buat grup bertingkat

Contoh berikut menunjukkan cara membuat grup berlapis dalam ekspresi kueri LINQ. Setiap kelompok yang dibuat menurut tahun siswa atau tingkat kelas selanjutnya dibagi lagi menjadi kelompok-kelompok berdasarkan nama individu.

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

Tiga perulangan berlapis foreach diperlukan untuk melakukan iterasi di atas elemen dalam grup berlapis.
(Arahkan kursor mouse ke atas variabel iterasi, , outerGroup, innerGroupdan innerGroupElement untuk melihat jenis aktualnya.)

Kueri yang setara menggunakan sintaks metode diperlihatkan dalam kode berikut:

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

Melakukan subkueri pada operasi pengelompokan

Artikel ini memperlihatkan dua cara berbeda untuk membuat kueri yang mengurutkan data sumber ke dalam grup, lalu menjalankan subkueri pada setiap grup satu per satu. Teknik dasar dalam setiap contoh adalah mengelompokkan elemen sumber dengan menggunakan kelanjutan bernama newGroup, dan kemudian menghasilkan subkueri baru terhadap newGroup. Subkueri ini dijalankan terhadap setiap grup baru yang dibuat oleh kueri luar. Dalam contoh khusus ini output akhir bukan grup, tetapi urutan datar dari jenis anonim.

Untuk informasi selengkapnya tentang cara pengelompokan, lihat klausul group. Untuk informasi selengkapnya tentang kelanjutan, lihatlahke dalam. Contoh berikut ini menggunakan struktur data dalam memori sebagai sumber data, tetapi prinsip yang sama berlaku untuk segala jenis sumber data LINQ.

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

Kueri dalam cuplikan sebelumnya juga dapat ditulis menggunakan sintaks metode. Cuplikan kode berikut memiliki kueri yang setara secara semantik yang ditulis menggunakan sintaks metode.Cuplikan kode berikut memiliki kueri yang setara secara semantik yang ditulis menggunakan sintaks metode.

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

Lihat juga