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.
Önemli
Bu örnekler bir System.Collections.Generic.IEnumerable<T> veri kaynağı kullanır. Veri kaynaklarını ve ifade ağaçlarını System.Linq.IQueryProviderkullanan System.Linq.IQueryable<T> veri kaynakları. İfade ağaçlarının izin verilen C# söz diziminde sınırlamaları vardır. Ayrıca EF Core gibi her IQueryProvider
veri kaynağı daha fazla kısıtlama uygulayabilir. Veri kaynağınızın belgelerine bakın.
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, enum
okuldaki öğ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
, innerGroup
ve 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 newGroup
yeni 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}");
}