LINQチートシート的なもの

  • 97
    いいね
  • 2
    コメント

LINQチートシート的なもの

UnityのC#のコード(幾つかの公式Assetとか)読んでたらFindとかいっぱい使ってあって悲しくなったんでLINQのメソッド一覧的なの作ってみました。

お願いだからforeach+List.AddじゃなくてSelect使ってとか
お願いだから引数配列じゃなくてIEnumerableにしてとか
なんでforeach回すだけなのにToArrayやってるの?とか

明らかに適切な場所じゃないとこで使っていて悲しいかったんや・・・
とりあえずこんな事できるよ~的なの作ってみました。
とりあえずC#書く人なら常識的なところ辺りまで

※ファイルの頭に当然using System.Linq;入れています
※ソースコード形式です。各メソッド最後の行は出力を示します
※IEnumerableをシーケンス表記で統一しています。(リストと読み替えてもいいと思います)

※LINQは基本的に少しだけ低速ですが気にするようなレベルのことってそんなに無いと思います。というかこのレベルで高速化したい場合はその前にILの勉強してどういうふうにコンパイルされるかが予測できるようになってからでいいと思います。

このページではこんなメソッドProgramに作って出力してます

        public static void Write<T>(string tag, IEnumerable<T> e)
        {
            Console.WriteLine(tag.PadRight(20) + String.Join(",", e));
        }

        public static void Write<T>(string tag, T e)
        {
            Console.WriteLine(tag.PadRight(20) + e);
        }

基礎的なところ

ほんとうによく使うメソッド
7割くらいはこれで完結しそうなレベルでよく使うのでこれだけは覚えておいて欲しいです。

csharp,Foundation.cs
namespace LINQCheatSheet
{
    class Foundation
    {
        /// <summary>
        /// 基本的なメソッド
        /// </summary>
        public static void Start()
        {
            //0~9までの配列
            var enumerable = Enumerable.Range(0, 10).ToArray();

            /*
            ** f:IEnumerable -> IEnumerable メソッド
            ** 出力がシーケンスのメソッド
            */

            //条件にあったものを検索して取得する(filter)
            //0,2,4,6,8
            var evens = enumerable.Where(i => i%2 == 0);
            Program.Write("Where",evens);

            //シーケンスのすべての値に関数を適用したものを返す(map,射影)
            //0,1,4,9,16,25,36,49,64,81
            var aquares = enumerable.Select(i => i * i);
            Program.Write("Select",aquares);

            //先頭からいくつか取る(head)
            //0,1,2
            var oneTwoThree = enumerable.Take(3);
            Program.Write("Take",oneTwoThree);

            //幾つかを飛ばして残りを取る
            //7,8,9
            var sevenEightNine = enumerable.Skip(7);
            Program.Write("Skip",sevenEightNine);

            //先頭から条件を満たさなくなるまでとる(Whereとは一度でも条件を満たさなくなったらストップする点でちがう)
            //0,1,2,3
            var while4 = enumerable.TakeWhile(i => (i + 1)%5 != 0);
            Program.Write("TakeWhile",while4);

            //条件を満たしている間スキップして残りをとる
            //4,5,6,7,8,9
            var skip4 = enumerable.SkipWhile(i => (i + 1) % 5 != 0);
            Program.Write("SkipWhile",skip4);


            /*
            ** 一つの値を探すメソッド
            ** 利用順としては First >>>>>>> Single >= Last くらい
            */

            //先頭を取得する
            //0
            var first = enumerable.First();
            Program.Write("First",first);

            //条件を満たす最初の要素を取得する
            //条件をみたすものがない場合例外になる
            //enumerable.First(i => i < -10); これは例外
            //1
            var firstOdds = enumerable.First(i => i%2 == 1);
            Program.Write("First(f)", firstOdds);

            //Firstの条件をみたすものがないときはdefault(T)を返すパターン
            //0
            var default0 = enumerable.FirstOrDefault(i => i < -10);
            Program.Write("FirstOrDefault(f)", default0);


            //最後を取得する
            //9
            var last = enumerable.Last();
            Program.Write("Last", last);

            //条件を満たす最後の要素を取得する
            //もちろんOrDefaultもある
            //8
            var lastEven = enumerable.Last(i => i % 2 == 0);
            Program.Write("Last(f)", lastEven);


            //シーケンスの中に要素が一つしかない場合それを返す
            //それ以外の場合は例外
            //19
            var single = new int[1] {19}.Single();
            Program.Write("Single", single);

            //条件を満たす唯一の要素を取得する
            //複数ある場合は例外
            //もちろんOrDefaultもある(見つからない場合はdefault(T)だが複数の場合は例外)
            //9
            var nine = enumerable.Single(i => i == 9);
            Program.Write("Single(f)", nine);
        }
    }
}

チェックメソッド

数を数えたりif文と併用することが多いメソッド


namespace LINQCheatSheet
{
    class Check
    {
        /// <summary>
        /// チェックするときにとか使えるメソッド
        /// </summary>
        public static void Start()
        {

            //0~9までの配列
            var enumerable = Enumerable.Range(0, 10).ToArray();


            //要素数数える
            //配列ならLengthと同じ
            //10
            int count = enumerable.Count();
            Program.Write("Count",count);

            //条件を満たす要素数を返す
            //Where(f).Count()と同値
            //5
            int countEven = enumerable.Count(i => i%2 == 0);
            Program.Write("Count(f)", countEven);

            //すべての要素が条件をみたすならtrueさもなくばfalse
            //true
            bool all = enumerable.All(i => i < 10);
            Program.Write("All(f)",all);

            //一つでも要素があればtrue
            //Allには無い(恒真命題になるからね)
            //true
            bool any = enumerable.Any();
            Program.Write("Any", any);

            //条件を満たす要素が一つでもあればtrue
            //Count() > 0とかしちゃダメよ
            //true
            bool anyEven = enumerable.Any(i => i%2 == 0);
            Program.Write("Any(f)", anyEven);



        }
    }
}

他のリスト系オブジェクトに変換

一応解説しておくとLINQのWhereとかSelectは呼ばれたタイミングでは何もしません。
実際にその処理が走るのは使われる時です。(foreachとかで)
で二回以上同じシーケンスを使いまわしたりすると逆に遅くなってしまうのでインスタンス化したほうが良いです。

たとえばこれだけではConsole.WriteLineは呼ばれません。
var enumerable.Where( i => { Console.WriteLine(i);true });
こういうふうにされてで初めて呼ばれます
var array = enumerable.ToArray();

ここまで知っていると9割以上はこれですみます

namespace LINQCheatSheet
{

    class Instantiate
    {
        /// <summary>
        /// インスタンス化するメソッド
        /// Enumerableを返さないメソッドもインスタン化するけど
        /// ここではリスト系を返すメソッドを
        /// 出力は変わらないのであんまりなし
        /// </summary>
        public static void Start()
        {
            //配列化 一番単純なデータ構造なので普通はこれを選択
            //0~9までの配列
            var enumerable = Enumerable.Range(0, 10).ToArray();

            //List<T>化
            //あんまり用途はないかも
            var list = enumerable.ToList();

            //Dictionary化
            //1,24,9,16,25,36,49,64,81
            var dictionary = enumerable.ToDictionary(i => i, i => i*i);
            Program.Write("Dictionary",dictionary.Values);


        }
    }
}

数学的な奴

あんま使わないけど簡単だし
あ、Average忘れてる


namespace LINQCheatSheet
{
    /// <summary>
    /// 数学関数的なやつ
    /// </summary>
    class Math
    {
        public static void Start()
        {
            //0~9までの配列
            var enumerable = Enumerable.Range(0, 10).ToArray();

            //最大値
            //9
            int max = enumerable.Max();
            Program.Write("Max",max);

            //関数適用した後の最大値
            //81
            int max2 = enumerable.Max( i=> i* i);
            Program.Write("Max(f)",max2);

            //最小値
            //0
            int min = enumerable.Min();
            Program.Write("Min", min);

            //関数適用した後の最小値
            //5
            int min2 = enumerable.Min(i => i + 5 );
            Program.Write("Min(f)", min2);


        }
    }
}


シーケンス生成メソッド

この辺りから若干複雑&あんま使わないかも?なメソッドです。

Linqにはシーケンスを操作するメソッド以外にもシーケンスを作成するメソッドがあります。
多くは簡易的なものでSelectとかと組み合わせると真価を発揮する?かも?
たまに便利


namespace LINQCheatSheet
{
    class Generate
    {
        /// <summary>
        /// シーケンスを作成するメソッド
        /// </summary>
        public static void Start()
        {

            //startからcount個の連番のシーケンスを生成します
            //0,1,2,3,4,5,6,7,8,9
            var enumerable = Enumerable.Range(0, 10);
            Program.Write("Range",enumerable);

            //Rangeにはいくつ飛ばすという引数がありませんが、もしそのようなシーケンスが作りたい場合は
            //Selectと合わせて作ります
            //0,2,4,6,8,10,12,14,16,18
            var evens = enumerable.Select(i => i*2);
            Program.Write("Range+Select", evens);

            //同じ値をcount回繰り返すシーケンスを作ります
            //4,4,4,4,4
            var repeat = Enumerable.Repeat(4, 5);
            Program.Write("Repeat", repeat);

            //Repeatは同じ値を繰り返すだけで毎回式を評価するわけではないことに注意

            //例えばこれはNext1回の結果が5回繰り返されるだけに注意
            //n,n,n,n,nになる(nは一つの同じ値)
            //var rnd = new Random();
            //Enumerable.Repeat(rnd.Next(), 5);

            //ランダムな5つの数値がほしいならこうすべき
            //(インスタンス生成とかも同じ要領で)
            //_は値を使わないことの明示(Rxとかの慣例ってだけです)
            var rnd = new Random();
            var rnds = Enumerable.Repeat(0,5).Select(_ => rnd.Next()); //new int[5]で良い気もする
            Program.Write("Randoms", rnds);

            //空のシーケンスを返すだけ(いわゆるφ空集合)
            //あんまり用途ないけど戻り値nullだと駄目なパターンとか
            //型引数は推論できないため明示してあげる必要あり
            //出力なし
            var empty = Enumerable.Empty<int>();
            Program.Write("Empty", empty);


        }
    }
}

2つのシーケンスの合成

覚えておいて得するのは多分ConcatとZipくらい
ほかは使った記憶がほぼない・・・

別分野では使うかもだけど・・・


namespace LINQCheatSheet
{
    class Connect
    {
        /// <summary>
        /// 2つのシーケンスを合成する
        /// といっても合成方法はいろいろ
        /// Ixだと2つ以上の合成できたり
        /// </summary>
        public static void Start()
        {
            //合成なので0~4のシーケンスとそれを自乗したシーケンスの2つをご用意
            var enumerable = Enumerable.Range(0, 5).ToArray();
            var other = enumerable.Select(i => i*i).ToArray();

            //単純に2つつなげるだけ(cat)
            //0,1,2,3,4,0,1,4,9,16
            var concat = enumerable.Concat(other);
            Program.Write("Concat",concat);

            //concatの次点で使いやすいメソッド
            //2つのシーケンスのi番目の要素同士を第二引数のFunc<TFirst, TSecond>で合成する
            //.Net4以上なのでUnityでは使えない$形式のフォーマットも使えない・・・悲しみ
            //(0,0),(1,1),(2,4),(3,9),(4,16)
            var zip = enumerable.Zip(other, (e, o) => $"({e},{o})");
            Program.Write("Zip", zip);


            //こっからあんまり使った記憶が無い

            //Zipより複雑なバージョン
            //Zipはi番目の要素同士を合成していたがこちらは要素それぞれがキーを作成して
            //そのキーが一致していたら第4引数で合成する 結構ややこしいし重いし使いドコロ難しい
            //(0,1),(1,4),(2,9),(3,16)

            var join = enumerable.Join(other, e => (e + 1)*(e + 1), o => o, (e, o) => $"({e},{o})");
            Program.Write("Join", join);

            //因みに同じキーを生成したものが複数ある場合それぞれが合致する
            //(0,0),(0,1),(1,0),(1,1),(2,0),(2,1),(3,0),(3,1),(4,0),(4,1)
            var joinDuplicate = enumerable.Join(other, e => 0, o => System.Math.Max(o - 1, 0), (e, o) => $"({e},{o})");
            Program.Write("JoinDuplicate", joinDuplicate);

            /*
            *  以下集合演算
            */

            //いわゆる和集合
            //0,1,2,3,4,9,16
            var union = enumerable.Union(other);
            Program.Write("Union",union);

            //いわゆる積集合
            //0,1,4
            var intersect = enumerable.Intersect(other);
            Program.Write("Intersect", intersect);

            //いわゆる差集合
            //0,1,4
            var except = enumerable.Except(other);
            Program.Write("Except", except);

            //ちなみに排他的論理和取りたければこうすればいい
            //2,3,9,16
            var xor = union.Except(intersect);
            Program.Write("Xor",xor);
        }
    }
}

追記分

あんまり使わないから完全に忘れていてAdventCallender2016の記事を書いてて思い出したメソッドを簡単に追記しておきます。

リストの中身をキャストする

リストの中身を全部キャストしたいときはOfTypeを使用します。
Castというメソッドもありますが、こちらはキャストに失敗した場合例外を投げます。

ちょうどOfTypeがasキャストでCastが(T)って書くタイプのキャストですね。

重複の除去

重複の除去にはDistinctを使用します。
コマンドラインでいうuniqとかSQLのDistinctに相当します。

参照型の比較はこちらを参照してください。
C# LINQ Distinct を自作クラスのコレクションで使う方法

これに限らずC#の比較メソッドは割合未だに規定の等価演算子かIEqualityComparerを使わせることが多くて面倒くさいです。
Funcとかで等価性検証させてくれないかしら?

一応何やってるか一見わからないのでおすすめはしたくないけれどGroupByしてからその要素の一個だけ取るとかは可能かな

おまけ

おすすめの本

個人的にLINQでおすすめの本はこちらです。
パラパラ眺められるレベルの本ですし対して高くもないので便利です。
あ、アフェリエイトとかじゃないので安心して飛んでください。

C#プログラムの効率的な書き方
https://www.amazon.co.jp/%E3%80%90%E7%9C%81%E3%82%A8%E3%83%8D%E5%AF%BE%E5%BF%9C%E3%80%91-C-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E5%8A%B9%E7%8E%87%E7%9A%84%E3%81%AA%E6%9B%B8%E3%81%8D%E6%96%B9-%E5%B7%9D%E4%BF%A3-%E6%99%B6/dp/4774149756/ref=sr_1_3?s=books&ie=UTF8&qid=1468739468&sr=1-3&keywords=LINQ%E3%80%80C%23

もっと使いたい

便利な拡張としてIxなんてーのもあります
が、そこまでいるかぁ?ということもしばしば

Unityの現状

どこかのUnityPatchでFirstとかがiosでクラッシュする問題は解消しているようです。
一方Zipとか使えなくて悲しいです・・・

エイプリールフールネタになる前に早くC#6.0を・・・をー!!