前回のLINQ to Objects 備忘録1に続いて備忘録を掲載します。今回はgroup by, join, distinct, union, intersect, except,count,sum,max,min,avarage,aggregateを扱います。データはLINQ to Objects 備忘録1で作成したデータを使用します。

確認環境は.NET 3.5, 開発環境はVisual Studio 2008 Professionalです。

今回取り扱っているクエリ式,拡張メソッド

クエリ式,拡張メソッド 簡単な説明
group, GroupBy拡張メソッド 集合の要素に対して、SQLのGroup By句に該当する処理を行います。
join,Join拡張メソッド 集合の要素に対して、SQLのINNER JOINに該当する処理を行います。
group into, GroupJoin拡張メソッド 集合の要素に対して、SQLのLEFT OUTER JOINに該当する処理を行います。
Distinct,Union,Intersect,
Except拡張メソッド
集合に対して、集合演算(和、積、差)を実施します。
Count,LongCount,Sum,Min,Max,
Average,Aggregate拡張メソッド
集合に対して、集約演算を行います。

1.LINQクエリメモ

1.1 Group演算子

SQLのgroup byにあたるLINQの演算子はgroup by です。対応する拡張メソッドはGroupBy()です。group by演算の結果は演算結果はIGrouping<TKey, TElement>インタフェースを実装したクラスのインスタンスのリストになります。サンプルを掲載します。

var categories = InitializeData();
var query =
    from c in categories
    group c by c.Name;

foreach (var g in query)
{
    Console.WriteLine("Key:" + g.Key + "-----------");
    foreach (var item in g)
    {
        Console.Write(item.ToString());
    }
}

var query1 = categories.GroupBy(c => c.Name, (key, elements) => new { Key = key, Count = elements.Count() });

foreach (var g in query1)
{
    Console.WriteLine("Key:{0}, Count:{1}", g.Key, g.Count);
}

実行結果は次のようになります。

Key:CS-----------
Category:ID=1, Name=CS
  Article:ID=1, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=2, Title=CSArticle2, ReleaseDate=2004/02/01, ArticleType=Memo
Category:ID=4, Name=CS
  Article:ID=6, Title=SqlArticle1, ReleaseDate=2008/10/03, ArticleType=Memo
Key:ASP-----------
Category:ID=2, Name=ASP
  Article:ID=3, Title=CSArticle1, ReleaseDate=2004/01/01, ArticleType=Code
  Article:ID=4, Title=AspArticle1, ReleaseDate=2002/12/21, ArticleType=Memo
Key:SQL-----------
Category:ID=3, Name=SQL
  Article:ID=5, Title=SqlArticle1, ReleaseDate=2006/02/03, ArticleType=Code
Key:CS, Count:2
Key:ASP, Count:1
Key:SQL, Count:1

1.2 Join演算子

SQLのinnjer join等価結合にあたるLINQのクエリ式です。対応する拡張メソッドはJoin()です。joinを行う場合は結合条件に左にfromで指定したオブジェクト,右にjoinで指定したリストを記載します。

var anotations = new[] { new { CategoryID = 1, Anotation= "CSharp Programming" }, new {CategoryID = 2, Anotation = "ASP.NET Programming"} };
var categories = InitializeData();

var query =
    from c in categories
    join a in anotations
         on c.ID equals a.CategoryID
    select new { c.ID, c.Name, a.Anotation };

foreach (var item in query)
{
     Console.WriteLine(item);
}

実行結果は次のようになります。

{ ID = 1, Name = CS, Anotation = CSharp Programming }
{ ID = 2, Name = ASP, Anotation = ASP.NET Programming }

1.3 GroupJoin演算子

join演算の場合、結合するオブジェクトのリストに該当するデータのみが選択されますが、SQLのLEFT OUTER JOINと同じようにinnerで指定したリストと結合するオブジェクトがなくても、抽出対象としたい場合はjoin intoを使用します。対応する拡張メソッドはGroupJoinです。サンプルを掲載します。

var anotations = new[] { new { CategoryID = 1, Anotation = "CSharp Programming" }, new { CategoryID = 2, Anotation = "ASP.NET Programming" } };
var categories = InitializeData();
var query1 =
    from c in categories
    join a in anotations
        on c.ID equals a.CategoryID
        into anotationList
    select new { c.ID, c.Name, Anotations = anotationList, Count = anotationList.Count() };

foreach (var item in query1)
{
    Console.WriteLine(item);
    foreach (var i in item.Anotations)
    {
        Console.WriteLine(i.Anotation);
    }
}

処理結果が次のようになります。Join演算のサンプルと異なり、結合していないCategoryもselectの結果に含まれていることがわかります。

{ ID = 1, Name = CS, Anotations = System.Linq.Lookup`2+Grouping[System.Int32,<>f__AnonymousType1`2[System.Int32,System.String]], Count = 1 }
CSharp Programming
{ ID = 2, Name = ASP, Anotations = System.Linq.Lookup`2+Grouping[System.Int32,<>
f__AnonymousType1`2[System.Int32,System.String]], Count = 1 }
ASP.NET Programming
{ ID = 3, Name = SQL, Anotations = <>f__AnonymousType1`2[System.Int32,System.String][], Count = 0 }
{ ID = 4, Name = CS, Anotations = <>f__AnonymousType1`2[System.Int32,System.String][], Count = 0 }

1.4 集合演算Distinct,Union,Intersect,Except拡張メソッド

selectで選択されたリストのうち、ユニークなインスタンスのみのリストを返すのがDistinct()拡張メソッドです。既定では同一のオブジェクトかの判定はGetHashCodeとEqualsが使われますが、EqualityComparer<T>を使用すると、同一のオブジェクトかの判定方法をカスタマイズすることができます。

int[] values = new int[] { 4, 1, 2, 3, 4, 1, 2 };
var query = (from i in values
              select i).Distinct();

foreach (var item in query)
{
    Console.WriteLine(item.ToString());
}

結果は次のようになります。

4
1
2
3

2つの集合の和集合を求めるのがUnion拡張メソッドです。EqualityComparer<T>を使用すると、オブジェクトの同一性の判定方法をカスタマイズできます。

int[] v1 = new int[] { 4, 1, 2, 3, 4, 1, 2 };
int[] v2 = new int[] { 1,7, 2, 5 };
var query = v1.Union(v2);

foreach (var item in query)
{
    Console.WriteLine(item.ToString());
}

結果は次のようになります。

4
1
2
3
7
5

2つの集合の求めるのがIntersect拡張メソッドです。EqualityComparer<T>を使用すると、オブジェクトの同一性の判定方法をカスタマイズできます。

int[] v1 = new int[] { 4, 1, 2, 3, 4, 1, 2 };
int[] v2 = new int[] { 1, 7, 2, 5 };
var query = v1.Intersect(v2);

foreach (var item in query)
{
    Console.WriteLine(item.ToString());
}

結果は次のようになります。

1
2

差集合(比較する集合に存在しない値の集合)を求めるのがExcept拡張メソッドです。EqualityComparer<T>を使用すると、オブジェクトの同一性の判定方法をカスタマイズできます。

int[] v1 = new int[] { 4, 1, 2, 3, 4, 1, 2 };
int[] v2 = new int[] { 1, 7, 2, 5 };
var query = v1.Except(v2);

foreach (var item in query)
{
    Console.WriteLine(item.ToString());
}

結果は次のようになります。

4
3

1.5 集約関数Count,LongCount,Sum,Min,Max,Average,Aggregate拡張メソッド

要素数をカウントする拡張メソッドがCount拡張メソッドです。返り値をlong型としたい場合はLongCount拡張メソッドを使用します。オーバーライド版を使用すると、要素をカウント対象に含めるかの判定ロジックを指定することができるようになります。

int[] v1 = new int[] { 4, 1, 2, 3, 4, 1, 2 };

var query = (from v in v1
              where v >= 3
             select v).Min();

Console.WriteLine(query.ToString());

 

3

要素の総和を求めるのがSum拡張メソッドです。selecterを指定するオーバーロード版を使用すると、総和を求める各要素の値を選択することができます。

int[] v1 = new int[] { 4, 1, 2, 3, 4, 1, 2 };

var query = (from v in v1
             where v >= 3
            select v).Sum();

Console.WriteLine(query.ToString());

実行結果は次のようになります。

11

集合のなかの最大値、最小値を求めるときに使用できるのがMax,Min拡張メソッドです。それぞれselecterを指定するオーバーロード版が用意されており、最大、最小を求める各要素の値を選択することができます。

var categories = InitializeData();
var query = (from c in categories
             from a in c.Articles
           select a).Max(a => a.ReleaseDate);

Console.WriteLine(query.ToString());
int[] v1 = new int[] { 4, 1, 2, 3, 4, 1, 2 };
var query1 = v1.Min();

Console.WriteLine(query1.ToString());

実行は次のようになります。

2008/10/03 0:00:00
1

集合の平均を求めるのがAverage拡張メソッドです。Max,Minと同様に、selecterを指定するオーバーロード版を使用すると、平均を求めるのに使用する各要素の値を選択することができます。

var categories = InitializeData();

var query = (from c in categories
             from a in c.Articles
             select a).Average(a => a.ID);

Console.WriteLine(query.ToString());

実行結果は次のようになります。

3.5

独自にカスタマイズした集約演算を行う場合は、Aggregate拡張メソッドを使用します。繰り返し呼び出される、Funcデリゲート内に集約操作を記述できます。オーバーロード版を使用すると、異なる型の演算結果を求めることができます。

var categories = InitializeData();

var query = categories.SelectMany(c => c.Articles).Aggregate((a1, a2) => (a1.ID > a2.ID) ? a1 : a2);
Console.WriteLine(query.ToString());

var query1 = categories.SelectMany(c => c.Articles).Aggregate(0, (v, a) => v + a.ID);
Console.WriteLine(query1.ToString());

実行結果は次のようになります。

Article:ID=6, Title=SqlArticle1, ReleaseDate=2008/10/03, ArticleType=Memo
21

今回のサンプルの掲載は以上です。

次のLinq To Objects備忘録3に続きます。