LINQつかいはじめました


いまさらですが LINQつかいはじめました
LINQ式は前々から覚えようと思いつつ、分量も少ないので 必要になるまで遅延評価していましたが
そろそろ必要になったので 勉強しました
トータルでも1日かからず 完全に理解しました

お題

2次元配列からなる整数の配列が与えられる
{ 1,2,3 },{ 1,4,5 },{ 1,2,6 };
この中から 3より大きい要素のみ取得する
ただし 配列の形状は崩さず 空集合もみとめない
つまり 下記の結果が得られる
{4, 5},{6}

勉強&回答

クエリ形式の練習

SQLっぽい構文がある。
なぜクエリ構文だと 最後にSelectかGroupByがないとNGかは よくわからない

int[] a_ = { 2, 7, 5, 8, 1, 6 };

// 本当は var o1 = ... と書くが 勉強のため型を書く
IEnumerable<int> o1 =
    from a in a_
    where a > 5
    orderby a descending
    select a;                       // クエリ形式だと select句必要


// 結果
{8,7,6}

メソッドチェーン

一般的にはこちらの形式が好まれるのでこちらに統一する
SelectなくてもOK

IEnumerable<int> o2 =
    a_                              // from a in a_
    .Where(x => x > 5)              // where a > 5
    .OrderByDescending(x => x)      //orderby a descending
    ;                               //select a;


// 結果
{8,7,6}

ForEach

ForEachを使えば 全ての要素に対して処理を行える
(LINQ式ではないが メソッドチェーンなので LINQ以外も繋げられる)

a_
    .Where(x => x > 5)
    .OrderByDescending(x => x)
    .ToList()
    .ForEach(x => Console.Write(x));


// 結果
876

Selectは誤り

上記をLINQのSelect()を使えば出来そうだが 何も出力されない
Selectの戻りを何か工夫すればいけるんだろうか?
ここはよく理解できてない
(多分Selectの戻りが IEnumerableだからだとおもうが深く追わない)

a_
.Where(x => x > 5)
    .OrderByDescending(x => x)
    .Select(x => { Console.Write(x); return 1; });

Selectで射影し要素の値を2倍値にする

a_
.Where(x => x > 5)
    .OrderByDescending(x => x)
    .Select(x => x * 2)
    .ToList()
    .ForEach(x => Console.Write("{0}, ", x));

//結果
{16, 14, 12}

Anyをつかい 配列に2が含まれるかどうか

a_.Any(x => x == 2);


//結果
True

Anyを使い 2を含む配列のみ取得

int[][] a2_;
a2_ = new int[3][];
a2_[0] = new int[]{ 1,2,3 };
a2_[1] = new int[]{ 1,4,5 };
a2_[2] = new int[]{ 1,2,6 };

a2_
.Where(x => 
    x.Any(y => y == 2)  
)


//結果
{1, 2, 3}
{1, 2, 6}

y>3を含む配列取得

IEnumerable<int[]> o3 = 
a2_
.Where(x =>
    x.Any(y => y > 3)
);


//結果
{1, 4, 5}
{1, 2, 6}

SelectManyは多次元配列を1次元に投影する

IEnumerable<int> o4 =
    a2_
        .SelectMany(x => x
            .Where(y => y > 3
    ));


//結果
{4, 5, 6}

Selectは配列構造維持したまま射影できる

IEnumerable<int[]> o5 =
a2_
    .Select(x => x
        .Where(y => y > 3)
        .ToArray()
    )
;


//結果
{}
{4, 5}
{6}

まだ空集合が入ってる

空集合を除去

IEnumerable<int[]> o6 =
a2_
    .Select(x => x
        .Where(y => y > 3)
        .ToArray()
    )
    .Where(x => x.Count() > 0)
;


//結果
{4, 5}
{6}

望み通りのデータが出来た!!

パフォーマンスのためAnyを使う&int[][]に

IEnumerable<int[]> o6 =
a2_
    .Select(x => x
        .Where(y => y > 3)
        .ToArray()
    )
    .Where(x => x.Any())
    .ToArray()
;


//結果
{4, 5}
{6}

総括

ToArrayが入ってしまうので速度的に気になるのだけど
foreachで書くことを考えると
とてもシンプルに出来たと思う

LINQは正義!