group 句 (C# リファレンス)
group 句は、グループのキー値に一致するゼロ以上のアイテムを含む IGrouping<TKey, TElement> オブジェクトを返します。たとえば、各文字列の最初の文字に従って文字列のシーケンスをグループ化できます。この場合、最初の文字が char 型のキーで、各 IGrouping<TKey, TElement> オブジェクトの Key プロパティに格納されます。キーの型はコンパイラによって推論されます。
次の例に示すように、クエリ式の末尾に group 句を使用できます。
// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery1 =
from student in students
group student by student.Last[0];
各グループに対して追加のクエリ操作を実行する場合、into コンテキスト キーワードを使用して一時的な識別子を指定できます。into を使用するときは、次のコードの抜粋に示すように、クエリを継続し、最後は select ステートメントまたは別の group 句にする必要があります。
// Group students by the first letter of their last name
// Query variable is an IEnumerable<IGrouping<char, Student>>
var studentQuery2 =
from student in students
group student by student.Last[0] into g
orderby g.Key
select g;
into を指定した場合と指定しない場合の group の完成度の高い使用例については、このトピックの「使用例」を参照してください。
グループ クエリの結果を列挙する
group クエリによって生成される IGrouping<TKey, TElement> オブジェクトは、基本的にリストのリストであるため、各グループのアイテムをアクセスするには、入れ子にされた foreach ループを使用する必要があります。外側のループがグループ キーを反復処理し、内側のループがグループ自体の各アイテムを反復処理します。グループにはキーがある場合がありますが、要素はありません。前のコード例でクエリを実行する foreach ループは次のようになります。
// Iterate group items with a nested foreach. This IGrouping encapsulates
// a sequence of Student objects, and a Key of type char.
// For convenience, var can also be used in the foreach statement.
foreach (IGrouping<char, Student> studentGroup in studentQuery2)
{
Console.WriteLine(studentGroup.Key);
// Explicit type for student could also be used here.
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}", student.Last, student.First);
}
}
キーの型
グループ キーは、文字列、組み込み数値型、ユーザー定義の名前付き型などの任意の型、または匿名型です。
文字列でグループ化
前のコード例では char を使用しました。代わりに、完全な姓などの文字列のキーも簡単に指定できます。
// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
var studentQuery3 =
from student in students
group student by student.Last;
ブールでグループ化
キーにブール値を使用して、結果を 2 つのグループに分ける方法を次の例に示します。値は、group 句内のサブ式によって生成されます。
class GroupSample1
{
// The element type of the data source.
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public List<int> Scores;
}
public static List<Student> GetStudents()
{
// Use a collection initializer to create the data source. Note that each element
// in the list contains an inner sequence of scores.
List<Student> students = new List<Student>
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
};
return students;
}
static void Main()
{
// Obtain the data source.
List<Student> students = GetStudents();
// Group by true or false.
// Query variable is an IEnumerable<IGrouping<bool, Student>>
var booleanGroupQuery =
from student in students
group student by student.Scores.Average() >= 80; //pass or fail!
// Execute the query and access items in each group
foreach (var studentGroup in booleanGroupQuery)
{
Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
}
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Low averages
Omelchenko, Svetlana:77.5
O'Donnell, Claire:72.25
Garcia, Cesar:75.5
High averages
Mortensen, Sven:93.5
Garcia, Debra:88.25
*/
数値の範囲でグループ化
次の例では、式を使用して、パーセンタイルの範囲を表す数値のグループ キーを作成します。group 句でメソッドを 2 回呼び出す必要がないように、メソッド呼び出しの結果を格納する便利な場所として let を使用しています。また、"0 で除算" 例外を回避するために、group 句内のコードで、学生の平均がゼロでないことを確認しています。クエリ式でメソッドを安全に使用する方法の詳細については、「方法 : クエリ式の例外を処理する (C# プログラミング ガイド)」を参照してください。
class GroupSample2
{
// The element type of the data source.
public class Student
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public List<int> Scores;
}
public static List<Student> GetStudents()
{
// Use a collection initializer to create the data source. Note that each element
// in the list contains an inner sequence of scores.
List<Student> students = new List<Student>
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 72, 81, 60}},
new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {99, 89, 91, 95}},
new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {72, 81, 65, 84}},
new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {97, 89, 85, 82}}
};
return students;
}
// This method groups students into percentile ranges based on their
// grade average. The Average method returns a double, so to produce a whole
// number it is necessary to cast to int before dividing by 10.
static void Main()
{
// Obtain the data source.
List<Student> students = GetStudents();
// Write the query.
var studentQuery =
from student in students
let avg = (int)student.Scores.Average()
group student by (avg == 0 ? 0 : avg / 10) into g
orderby g.Key
select g;
// Execute the query.
foreach (var studentGroup in studentQuery)
{
int temp = studentGroup.Key * 10;
Console.WriteLine("Students with an average between {0} and {1}", temp, temp + 10);
foreach (var student in studentGroup)
{
Console.WriteLine(" {0}, {1}:{2}", student.Last, student.First, student.Scores.Average());
}
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Students with an average between 70 and 80
Omelchenko, Svetlana:77.5
O'Donnell, Claire:72.25
Garcia, Cesar:75.5
Students with an average between 80 and 90
Garcia, Debra:88.25
Students with an average between 90 and 100
Mortensen, Sven:93.5
*/
複合キーでグループ化
複数のキーに従って要素をグループ化するには、複合キーを使用します。複合キーは、キー要素の保持に匿名型または名前付き型を使用して作成します。次の例では、surname という名前のメンバーおよび city という名前のメンバーと共にクラス Person が宣言されていることを想定しています。group 句によって、同じ姓で同じ都市の人物のセットごとに個別のグループが作成されます。
group person by new {name = person.surname, city = person.city};
別のメソッドにクエリ変数を渡す必要がある場合、名前付き型を使用します。キーに自動実装プロパティを使用して特別なクラスを作成し、Equals メソッドおよび GetHashCode メソッドをオーバーライドします。構造体を使用することもできます。この場合、これらのメソッドのオーバーライドは必ずしも必要ではありません。詳細については、「方法 : 自動実装するプロパティを使用して簡易クラスを実装する (C# プログラミング ガイド)」および「方法: ディレクトリ ツリーで重複するファイルを問い合わせる (LINQ)」を参照してください。後者のトピックには、名前付き型と共に複合キーを使用する方法を示すコード例があります。
使用例
グループに追加のクエリ ロジックが適用されない場合の、グループごとにソース データを並べる標準的なパターンを次の例に示します。これは、継続なしのグループ化と呼ばれます。それぞれの最初の文字に従って、文字列の配列内の要素がグループ化されます。クエリの結果は、char 型の Key パブリック プロパティを格納する IGrouping<TKey, TElement> 型、およびグループ内の各アイテムを格納する IEnumerable<T> コレクションです。
group 句の結果は、シーケンスのシーケンスです。このため、返された各グループ内の個々の要素にアクセスするには、次の例に示すように、グループ キーを反復処理するループ内で、入れ子にされた foreach ループを使用します。
class GroupExample1
{
static void Main()
{
// Create a data source.
string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese" };
// Create the query.
var wordGroups =
from w in words
group w by w[0];
// Execute the query.
foreach (var wordGroup in wordGroups)
{
Console.WriteLine("Words that start with the letter '{0}':", wordGroup.Key);
foreach (var word in wordGroup)
{
Console.WriteLine(word);
}
}
// Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Words that start with the letter 'b':
blueberry
banana
Words that start with the letter 'c':
chimpanzee
cheese
Words that start with the letter 'a':
abacus
apple
*/
グループの作成後に、継続と into を使用して、グループに対して追加ロジックを実行する方法を次の例に示します。詳細については、「into (C# リファレンス)」を参照してください。次の例では、各グループを照会し、キー値が母音であるものだけを選択します。
class GroupClauseExample2
{
static void Main()
{
// Create the data source.
string[] words2 = { "blueberry", "chimpanzee", "abacus", "banana", "apple", "cheese", "elephant", "umbrella", "anteater" };
// Create the query.
var wordGroups2 =
from w in words2
group w by w[0] into grps
where (grps.Key == 'a' || grps.Key == 'e' || grps.Key == 'i'
|| grps.Key == 'o' || grps.Key == 'u')
select grps;
// Execute the query.
foreach (var wordGroup in wordGroups2)
{
Console.WriteLine("Groups that start with a vowel: {0}", wordGroup.Key);
foreach (var word in wordGroup)
{
Console.WriteLine(" {0}", word);
}
}
// Keep the console window open in debug mode
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Groups that start with a vowel: a
abacus
apple
anteater
Groups that start with a vowel: e
elephant
Groups that start with a vowel: u
umbrella
*/
解説
コンパイル時に、group 句は GroupBy メソッドの呼び出しに変換されます。
参照
処理手順
方法: 入れ子になったグループを作成する (C# プログラミング ガイド)
方法: クエリ結果をグループ化する (C# プログラミング ガイド)
方法: グループ化操作でサブクエリを実行する (C# プログラミング ガイド)