解析エンジニアの自動化 blog

コツコツと自動化した方法を残す blog

C# の HashSet と SortedSet



こんにちは。
仕事の自動化にやりがいと達成感を感じるガッくんです。


この記事の目次



背景・目的


履歴を管理したり、重複を避ける場合には HashSet か SortedSet を使うのが王道だと思います。

HashSet と SortedSet がいったいどれほどの威力なのかを確かめました。



動作環境


Windows 7
Visual Studio 2017



プログラム

ソースコード

重複のあるランダムな List から重複のない List を作成します。

なお、 1 行目の #define のコメントを外すと、 List の値を出力する事ができるようになります。

//#define OUTPUT_VALUES
 
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
 
namespace speed_​​comparison_duplicate
{
    class Program
    {
        static void Main(string[] args)
        {
            ///////////////////////////////////////////////////////////////////////////////
            // 重複のあるランダムな values List を作成し、
            // values List から重複のない List を作成する。
            ///////////////////////////////////////////////////////////////////////////////
            // 重複のあるランダムな values List の作成
            //    ある数の配列を用意する。
            //    重複がない様に連番で初期化する。
            //    乱数を使って配列の値を入れ替えて並び順をランダムにする。
            //    ランダムに並べ替えた配列を List に 2 回代入する。
            ///////////////////////////////////////////////////////////////////////////////
            // values List から重複のない List の作成
            //    values List を調べて追加されたことのない要素で有れば、
            //    List に要素を追加する。
            //    パターンA
            //        Contains メソッドで values List の有無を調べる
            //    パターンB
            //        BinarySearch メソッドで values List の有無を調べる
            //    パターンC
            //        HashSet で重複のない List を作成する
            //    パターンD
            //        SortedSet で重複のない List を作成する
            ///////////////////////////////////////////////////////////////////////////////
 
 
 
            ///////////////////////////////////////////////////////////////////////////////
            // 重複のあるランダムな values List の作成
            //    ある数の配列を用意する。
            //    重複がない様に連番で初期化する。
            //    乱数を使って配列の値を入れ替えて並び順をランダムにする。
            ///////////////////////////////////////////////////////////////////////////////
            Stopwatch sw = new Stopwatch();
            sw.Reset();
            sw.Start();
 
            // cntArray 個の配列作成
            int cntArray = 30000;
            int[] values = new int[cntArray];
 
            // 配列の初期化
            int startValue = 523;
            for (int i = 0; i < values.Length; i++)
            {
                values[i] = startValue + i;
            }
           
            // 乱数発生器作成する
            Random r = new Random();
           
            // 配列を cntSwap 回シャッフルする
            int cntSwap = cntArray;
            for (int i = 0; i < cntSwap; i++)
            {
                // 入れ替える配列インデックスをランダムに選定する
                int j = r.Next(0, values.Length);
 
                // 配列の値の入れ替え
                int tmpJ = values[j];
                values[j] = values[i];
                values[i] = tmpJ;
            }
 
            // cntDuplicate 個の重複のあるランダムな values List の作成
            int cntDuplicate = 2;
            List values_List = new List();
            for (int i = 0; i < cntDuplicate; i++)
            {
                for (int j = 0; j < values.Length; j++)
                {
                    values_List.Add(values[j]);
                }
            }
 
            sw.Stop();
            Console.WriteLine("配列作成終了");
 
            string filepath = "C:\\Users\\UserName\\Desktop\\log.txt";
            StreamWriter w = new StreamWriter(filepath, false, Encoding.GetEncoding("shift_jis"));
 
            TimeSpan ts = sw.Elapsed;
            w.Write("*   値のランダムな values List の個数:");
            w.WriteLine(values_List.Count);
            w.Write("                               重複数:");
            w.WriteLine(cntDuplicate);
            w.Write("値のランダムな values List の作成時間:");
            w.WriteLine($" {ts.Hours}時間 {ts.Minutes}分 {ts.Seconds}秒 {ts.Milliseconds}ミリ秒");
 
#if (OUTPUT_VALUES)
            w.WriteLine(string.Format("{0,10}", "Index") + ":" + string.Format("{0,20}", "Values"));
            for (int i = 0; i < values_List.Count; i++)
            {
                w.WriteLine(string.Format("{0,10}", i) + ":" + string.Format("{0,20}", values_List[i]));
            }
#endif
 
            ///////////////////////////////////////////////////////////////////////////////
            // values List の空ループの時間調査
            ///////////////////////////////////////////////////////////////////////////////
            sw.Reset();
            sw.Start();
 
            for (int i = 0; i < values_List.Count; i++)
            {
            }

            sw.Stop();
            Console.WriteLine("空ループ終了");
 
            ts = sw.Elapsed;
            w.WriteLine("");
            w.Write("*          values List の空ループ時間:");
            w.WriteLine($" {ts.Hours}時間 {ts.Minutes}分 {ts.Seconds}秒 {ts.Milliseconds}ミリ秒");
 
            ///////////////////////////////////////////////////////////////////////////////
            // values List から重複のない List の作成
            //    values List を調べて追加されたことのない要素で有れば、
            //    List に要素を追加する。
            //    パターンA
            //        Contains メソッドで values List の有無を調べる
            ///////////////////////////////////////////////////////////////////////////////
            sw.Reset();
            sw.Start();
 
            List list1 = new List();
 
            for(int i = 0; i < values_List.Count; i++)
            {
                if (!list1.Contains(values_List[i]))
                {
                    list1.Add(values_List[i]);
                }
            }
 
            sw.Stop();
            Console.WriteLine(" Contains 終了");
 
            ts = sw.Elapsed;
            w.WriteLine("");
            w.Write("*             重複のない list1 の個数:");
            w.WriteLine(list1.Count);
            w.Write("          重複のない list1 の作成時間:");
            w.WriteLine($" {ts.Hours}時間 {ts.Minutes}分 {ts.Seconds}秒 {ts.Milliseconds}ミリ秒");
 
#if (OUTPUT_VALUES)
            w.WriteLine(string.Format("{0,10}", "Index") + ":" + string.Format("{0,20}", "Values"));
            for (int i = 0; i < list1.Count; i++)
            {
                w.WriteLine(string.Format("{0,10}", i) + ":" + string.Format("{0,20}", list1[i]));
            }
#endif
 
            ///////////////////////////////////////////////////////////////////////////////
            // values List から重複のない List の作成
            //    values List を調べて追加されたことのない要素で有れば、
            //    List に要素を追加する。
            //    パターンB
            //        BinarySearch メソッドで values List の有無を調べる
            ///////////////////////////////////////////////////////////////////////////////
            sw.Reset();
            sw.Start();
 
            List list2 = new List();
 
            for (int i = 0; i < values_List.Count; i++)
            {
                int idx = list2.BinarySearch(values_List[i]);
                if (idx < 0)
                {
                    list2.Add(values_List[i]);
                    list2.Sort();
                }
            }
 
            sw.Stop();
            Console.WriteLine(" BinarySearch 終了");
 
            ts = sw.Elapsed;
            w.WriteLine("");
            w.Write("*             重複のない list2 の個数:");
            w.WriteLine(list1.Count);
            w.Write("          重複のない list2 の作成時間:");
            w.WriteLine($" {ts.Hours}時間 {ts.Minutes}分 {ts.Seconds}秒 {ts.Milliseconds}ミリ秒");
 
#if (OUTPUT_VALUES)
            w.WriteLine(string.Format("{0,10}", "Index") + ":" + string.Format("{0,20}", "Values"));
            for (int i = 0; i < list2.Count; i++)
            {
                w.WriteLine(string.Format("{0,10}", i) + ":" + string.Format("{0,20}", list2[i]));
            }
#endif
 
            ///////////////////////////////////////////////////////////////////////////////
            // values List から重複のない List の作成
            //    values List を調べて追加されたことのない要素で有れば、
            //    List に要素を追加する。
            //    パターンC
            //        HashSet で重複のない List を作成する
            ///////////////////////////////////////////////////////////////////////////////
            sw.Reset();
            sw.Start();
 
            HashSet hash = new HashSet();
 
            for (int i = 0; i < values_List.Count; i++)
            {
                hash.Add(values_List[i]);
            }
 
            List hashList = hash.ToList();
 
            sw.Stop();
            Console.WriteLine(" HashSet 終了");
 
            ts = sw.Elapsed;
            w.WriteLine("");
            w.Write("*              重複のない hash の個数:");
            w.WriteLine(hash.Count);
            w.Write("           重複のない hash の作成時間:");
            w.WriteLine($" {ts.Hours}時間 {ts.Minutes}分 {ts.Seconds}秒 {ts.Milliseconds}ミリ秒");
 
#if (OUTPUT_VALUES)
            w.WriteLine(string.Format("{0,10}", "Index") + ":" + string.Format("{0,20}", "Values"));
            for (int i = 0; i < hashList.Count; i++)
            {
                w.WriteLine(string.Format("{0,10}", i) + ":" + string.Format("{0,20}", hashList[i]));
            }
#endif
 
            ///////////////////////////////////////////////////////////////////////////////
            // values List から重複のない List の作成
            //    values List を調べて追加されたことのない要素で有れば、
            //    List に要素を追加する。
            //    パターンD
            //        SortedSet で重複のない List を作成する
            ///////////////////////////////////////////////////////////////////////////////
            sw.Reset();
            sw.Start();
 
            SortedSet sset = new SortedSet();
 
            for (int i = 0; i < values_List.Count; i++)
            {
                sset.Add(values_List[i]);
            }
 
            List ssetList = sset.ToList();
 
            sw.Stop();
            Console.WriteLine(" SortedSet 終了");
 
            ts = sw.Elapsed;
            w.WriteLine("");
            w.Write("*              重複のない sset の個数:");
            w.WriteLine(hash.Count);
            w.Write("           重複のない sset の作成時間:");
            w.WriteLine($" {ts.Hours}時間 {ts.Minutes}分 {ts.Seconds}秒 {ts.Milliseconds}ミリ秒");
 
#if (OUTPUT_VALUES)
            w.WriteLine(string.Format("{0,10}", "Index") + ":" + string.Format("{0,20}", "Values"));
            for (int i = 0; i < ssetList.Count; i++)
            {
                w.WriteLine(string.Format("{0,10}", i) + ":" + string.Format("{0,20}", ssetList[i]));
            }
#endif
 
            ///////////////////////////////////////////////////////////////////////////////
            // 終了処理
            ///////////////////////////////////////////////////////////////////////////////
            w.Close();
            Console.WriteLine("");
            Console.Write("何かキーを押して下さい。");
            Console.ReadKey();
        }
    }
}



結果

ちょっと見づらいですが、結果です。
テキストエディタにコピペしてもらうと、もっと見やすくなると思います。


*   値のランダムな values List の個数:60000
                               重複数:2
値のランダムな values List の作成時間: 0時間 0分 0秒 1ミリ秒
 
*          values List の空ループ時間: 0時間 0分 0秒 0ミリ秒
 
*             重複のない list1 の個数:30000
          重複のない list1 の作成時間: 0時間 0分 2秒 619ミリ秒
 
*             重複のない list2 の個数:30000
          重複のない list2 の作成時間: 0時間 0分 8秒 441ミリ秒
 
*              重複のない hash の個数:30000
           重複のない hash の作成時間: 0時間 0分 0秒 1ミリ秒
 
*              重複のない sset の個数:30000
           重複のない sset の作成時間: 0時間 0分 0秒 17ミリ秒



コメント

HashSet と SortedSet の威力が絶大でした。

HashSet と SortedSet では HashSet が圧倒的に早かったです。
SortedSet はソートしてる分遅かったのでしょうか。



以上