LoginSignup
23
23

More than 5 years have passed since last update.

LINQでIE<T>からランダムに要素を取り出す

Posted at

C#、LINQネタです。

LINQでIE<T>を扱っていると、「ランダムに要素を1つ取り出したい」と思うことが時々あります。なのでそういう時は拡張メソッドで

static T Random<T>(this IEnumerable<T> ie)

を実装すると便利ですね。
今回は3種類のRandom()の実装を要素数100000の整数の列挙に対して1000回行った実行時間でパフォーマンスを比較しますが、IE<T>の内部実装によって挙動が変わるかもしれないので、サンプルの方も、
IE<T>型、配列型、List<T>型の3種類を用意しました。最終的なコードがこちら

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace RandomPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            var sw = new Stopwatch();
            //IE<int>
            var sample1 = Enumerable.Repeat(0, 100000);
            //配列
            var sample2 = sample1.ToArray();
            //List
            var sample3 = sample1.ToList();
            var samples = new IEnumerable<int>[] { sample1, sample2, sample3 };

            foreach(var i in Enumerable.Range(0, 3))
            {
                Console.WriteLine("----sample: " + SampleName(i) + "----");
                Console.WriteLine("--OrderBy--");
                sw.Restart();
                foreach(var _ in Enumerable.Range(0, 1000))
                {
                    RandomOrderBy(samples[i]);
                }
                Console.WriteLine(sw.ElapsedMilliseconds);
                Console.WriteLine("--ElementAt--");
                sw.Restart();
                foreach(var _ in Enumerable.Range(0, 1000))
                {
                    RandomElementAt(samples[i]);
                }
                Console.WriteLine(sw.ElapsedMilliseconds);
                Console.WriteLine("--Skip--");
                sw.Restart();
                foreach(var _ in Enumerable.Range(0, 1000))
                {
                    RandomSkip(samples[i]);
                }
                Console.WriteLine(sw.ElapsedMilliseconds);
            }

            Console.ReadLine();
        }

        static Random _Rand = new Random();

        static T RandomOrderBy<T>(IEnumerable<T> ie)
        {
            return ie.OrderBy(x => _Rand.Next()).First();
        }

        static T RandomElementAt<T>(IEnumerable<T> ie)
        {
            return ie.ElementAt(_Rand.Next(ie.Count()));
        }

        static T RandomSkip<T>(IEnumerable<T> ie)
        {
            return ie.Skip(_Rand.Next(ie.Count())).First();
        }

        static string SampleName(int i)
        {
            if(i == 0) return "IEnumerable<T>";
            else if(i == 1) return "Array<T>";
            else return "List<T>";
        }
    }
}

そして実行結果がこちら

----sample: IEnumerable<T>----
--OrderBy--
29947
--ElementAt--
757
--Skip--
735
----sample: Array<T>----
--OrderBy--
28894
--ElementAt--
0
--Skip--
259
----sample: List<T>----
--OrderBy--
28515
--ElementAt--
0
--Skip--
319

-RandomOrderBy
遅い。とにかく遅い。内部実装によらず遅い。論外。

-RandomElementAt
クッソ早い。IE<T>型はおそらく内部でインデックス化されていないために若干の時間ロスがあるが、配列、List<T>型ではあまりにも早い。多分これが最強。

-RandomSkip
これも結構早い。ElementAtには勝てない。

結論: ElementAtを使うべき。

だいたい予想通りの結果になりましたがOrderByのあまりの遅さとElementAtのあまりの早さに笑いました。
ElementAtは早かったですが、さらにこれは多分要素数に非依存なので、他の2つとくらべて100000件よりもずっと大きな列挙に対しても同じようなパフォーマンスが得られると思います。

以上LINQ小ネタでした

23
23
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
23