いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Linq入門記-27 (LINQ To Object, GroupBy)


今回は、GroupBy拡張メソッドについてです。


GroupBy拡張メソッドは、クエリ式での「group xx by xx.xx」に対応する拡張メソッドです。
LINQでのグルーピングについては、以前の記事で記述していますので、そちらを参照下さればと思います。


GroupBy拡張メソッドは、オーバーロードが非常に多く、全部で8個あります。
書式は以下の通りです。

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector
)

public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    IEqualityComparer<TKey> comparer
)

public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector
)

public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TKey, IEnumerable<TSource>, TResult> resultSelector
)

public static IEnumerable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector,
    IEqualityComparer<TKey> comparer
)

public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector,
    Func<TKey, IEnumerable<TElement>, TResult> resultSelector
)

public static IEnumerable<TResult> GroupBy<TSource, TKey, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TKey, IEnumerable<TSource>, TResult> resultSelector,
    IEqualityComparer<TKey> comparer
)

public static IEnumerable<TResult> GroupBy<TSource, TKey, TElement, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector,
    Func<TKey, IEnumerable<TElement>, TResult> resultSelector,
    IEqualityComparer<TKey> comparer
)


大別すると以下のようになります。

  1. keySelectorのみを指定する版
  2. keySelectorとelementSelectorを指定する版
  3. keySelectorとresultSelectorを指定する版
  4. keySelectorとelementSelectorとresultSelectorを指定する版


注意点として、resultSelectorを指定する版では戻り値が

IEnumerable<IGrouping<TKey, TElement>>

ではなく

IEnumerable<TResult>

となる部分です。


個人的に、resultSelectorを指定する版を利用する事が今の所ほとんどありません。
(実際にresultSelectorを指定する版を処理する場合は、ほぼクエリ式で書いています)
もっぱら利用するのは、keySelectorとelementSelectorを指定する版です。


なので、以下のサンプルではresultSelectorを利用した版は記述してません。m(_ _)m
以下、サンプルです。

    #region LinqSamples-25
    public class LinqSamples25 : IExecutable
    {
        class Person
        {
            public int    Id      { get; set; }
            public string Name    { get; set; }
            public string Team    { get; set; }
            public string Project { get; set; }
                
            public override string ToString()
            {
                return string.Format("[ID={0}, NAME={1}]", Id, Name);
            }
        }
        
        public void Execute()
        {
            var persons = new List<Person>
                          {
                              new Person{ Id = 1001, Name = "gsf_zero1", Team = "A", Project = "P1" },
                              new Person{ Id = 1000, Name = "gsf_zero2", Team = "B", Project = "P1" },
                              new Person{ Id = 111,  Name = "gsf_zero3", Team = "B", Project = "P2" },
                              new Person{ Id = 9889, Name = "gsf_zero4", Team = "C", Project = "P2" },
                              new Person{ Id = 9889, Name = "gsf_zero5", Team = "A", Project = "P1" },
                              new Person{ Id = 100,  Name = "gsf_zero6", Team = "C", Project = "P2" }
                          };
            
            //
            // GroupBy拡張メソッドは、シーケンスの要素をグループ化する際に利用できる。
            // クエリ式にて、[group xx by xx.xx]とする場合、実行時にGroupBy拡張メソッドに置き換えられる。
            //
            // 概念としては、SQLのGROUP BYと同じである。
            //
            // GroupBy拡張メソッドには、全部で8つのオーバーロードが存在する。
            // よく利用されるのは、以下のものとなると思われる。
            //
            //   ・keySelectorのみを指定するもの
            //   ・keySelectorとelementSelectorを指定するもの。
            //   ・複合キーでのグループ化
            //
            // GroupBy拡張メソッドの戻り値は、
            //     IEnumerable<IGrouping<TKey, TElement>>
            // となる。IGroupingインターフェースは、グルーピングを表すインターフェースであり
            // Keyプロパティが定義されている。
            //
            // このインターフェース自身も、IEnumerableインターフェースを継承しているので
            // グルーピングを行った場合は、以下のようにして2重のループでデータを取得する。
            //
            // var query = xxx.GroupBy(item => item.Key);
            // foreach (var group in query)
            // {
            //     Console.WriteLine(group.Key);
            //     foreach (var item in group)
            //     {
            //         Console.WriteLine(item);
            //     }
            // }
            //
            
            //
            // keySelectorのみを指定.
            //
            // 以下のクエリ式と同じとなる。
            //   from  thePerson in persons
            //   group thePerson by thePerson.Team
            //
            Console.WriteLine("============ keySelectorのみのGroupBy ==============");
            var query1 = persons.GroupBy(thePerson => thePerson.Team);
            foreach (var group in query1)
            {
                Console.WriteLine("=== {0}", group.Key);
                foreach (var thePerson in group)
                {
                    Console.WriteLine("\t{0}", thePerson);
                }
            }

            //
            // keySelectorとelementSelectorを指定.
            //
            // 以下のクエリ式と同じとなる。
            //   from   thePerson in persons
            //   group  thePerson.Name by thePerson.Team
            //
            Console.WriteLine("\n============ elementSelectorを指定したGroupBy ==============");
            var query2 = persons.GroupBy(thePerson => thePerson.Team, thePerson => thePerson.Name);
            foreach (var group in query2)
            {
                Console.WriteLine("=== {0}", group.Key);
                foreach (var name in group)
                {
                    Console.WriteLine("\t{0}", name);
                }
            }
            
            //
            // 複合キーにてグループ化.
            //
            // 以下のクエリ式と同じとなる。
            //   from  thePerson in persons
            //   group thePerson by new { thePerson.Project, thePerson.Team }
            //
            Console.WriteLine("\n============ 複合キーを指定したGroupBy ==============");
            var query3 = persons.GroupBy(thePerson => new { thePerson.Project, thePerson.Team });
            foreach (var group in query3)
            {
                Console.WriteLine("=== {0}", group.Key);
                foreach (var thePerson in group)
                {
                    Console.WriteLine("\t{0}", thePerson);
                }
            }
            
            //
            // 以下のクエリ式と同じとなる。
            //   from    thePerson in persons
            //   group   thePerson by new { thePerson.Project, thePerson.Team } into p
            //   orderby p.Key.Project descending, p.Key.Team descending
            //   select  p
            //
            Console.WriteLine("\n============ 複合キーとorderbyを指定したGroupBy ==============");
            var query4 = persons
                            .GroupBy(thePerson => new { thePerson.Project, thePerson.Team })
                            .OrderByDescending(group => group.Key.Project)
                            .ThenByDescending(group => group.Key.Team);
            
            foreach (var group in query4)
            {
                Console.WriteLine("=== {0}", group.Key);
                foreach (var thePerson in group)
                {
                    Console.WriteLine("\t{0}", thePerson);
                }
            }
        }
    }
    #endregion

出力結果は以下のようになります。

  ============ keySelectorのみのGroupBy ==============
  === A
  	[ID=1001, NAME=gsf_zero1]
  	[ID=9889, NAME=gsf_zero5]
  === B
  	[ID=1000, NAME=gsf_zero2]
  	[ID=111, NAME=gsf_zero3]
  === C
  	[ID=9889, NAME=gsf_zero4]
  	[ID=100, NAME=gsf_zero6]
  
  ============ elementSelectorを指定したGroupBy ==============
  === A
  	gsf_zero1
  	gsf_zero5
  === B
  	gsf_zero2
  	gsf_zero3
  === C
  	gsf_zero4
  	gsf_zero6
  
  ============ 複合キーを指定したGroupBy ==============
  === { Project = P1, Team = A }
  	[ID=1001, NAME=gsf_zero1]
  	[ID=9889, NAME=gsf_zero5]
  === { Project = P1, Team = B }
  	[ID=1000, NAME=gsf_zero2]
  === { Project = P2, Team = B }
  	[ID=111, NAME=gsf_zero3]
  === { Project = P2, Team = C }
  	[ID=9889, NAME=gsf_zero4]
  	[ID=100, NAME=gsf_zero6]
  
  ============ 複合キーとorderbyを指定したGroupBy ==============
  === { Project = P2, Team = C }
  	[ID=9889, NAME=gsf_zero4]
  	[ID=100, NAME=gsf_zero6]
  === { Project = P2, Team = B }
  	[ID=111, NAME=gsf_zero3]
  === { Project = P1, Team = B }
  	[ID=1000, NAME=gsf_zero2]
  === { Project = P1, Team = A }
  	[ID=1001, NAME=gsf_zero1]
  	[ID=9889, NAME=gsf_zero5]

================================
過去の記事については、以下のページからご参照下さい。