C#のDictionaryをソートする3つの方法

C#でDictionaryの内容をソートする方法を3つ紹介したいと思います。

標準でこれを使えば解決というのはなかなかないので目的に合った方法を選択する必要があります。

前提条件

説明に使用するデータ構造は下の通りです。

// 必要な宣言
using System.Collections.Generic;
using System.Linq;

// 値に使用するクラス
  public class Item
  {
      // プロパティ
      public int ID { get; set; }
      public string No { get; set; }
      // メソッド
      public override string ToString() { return $"ID = {this.ID}, No = {this.No}"; }
  }

// 説明に使用するDictionary
var itemTable = new Dictionary<int, Item>()
{
    { 0, new Item() { ID = 0, No = "0" } },
    { 3, new Item() { ID = 3, No = "3" } },
    { 2, new Item() { ID = 2, No = "2" } },
    { 1, new Item() { ID = 1, No = "1" } },
};

その1:拡張メソッドを利用する

一番簡単な方法です。

System.Linqに定義されている拡張メソッドを利用する方法です。

昇順はOrderByメソッド、降順はOrderByDescendingメソッドを使用します。

using System.Linq; // この宣下が必要

public static void DisplayItems_1(IDictionary<int, Item> itemTable)
{
    // キーで昇順
    IOrderedEnumerable<KeyValuePair<int, Item>> _table_1 =
        itemTable.OrderBy(selector =>
        {
            return selector.Key;
        });

    // キーで降順
    IOrderedEnumerable<KeyValuePair<int, Item>> _table_2 =
        itemTable.OrderByDescending(selector =>
        {
            return selector.Key;
        });
}

その2:SortedDictionaryを利用する

これは割とめんどくさいです(独自の整列規則を持ちたい場合これしかないですが、ちゃんとやろうとすると結構すごく面倒かもしれません。

Dictionary に要素を追加した時に中身を自動で整列済みの状態にしてくれる SortedDictionary を使用する方法です。Dictionary と SortedDictionary は両方とも IDictionary を継承しているので変数を受け渡すときは IDictionary で操作できます。(Dictionaryと同じ感覚で操作ができます。

インスタンスの生成は SortedDictionary です。 引数なしのコンストラクタでインスタンス化するとTKeyに対しデフォルトの整列規則(文字コード順)が適用されます。

// 宣言:SortedDictionaryとしてインスタンスを作成
var itemTable = new SortedDictionary<int, Item>();

この状態で冒頭のテーブルの内容を foreach で列挙すると内容が整列されています。

//public static void DisplayItems_1(SortedDictionary<int, Item> itemTable)
public static void DisplayItems_1(IDictionary<int, Item> itemTable)
{
    foreach (KeyValuePair<int, Item> item in itemTable)
    {
        Console.WriteLine($"{item.Key} = ,{item.Value.ToString()}");
    }
}
// 出力内容:デフォルトはKeyの昇順に整列される
// 0 = ,ID = 0, No = 0
// 1 = ,ID = 1, No = 1
// 2 = ,ID = 2, No = 2
// 3 = ,ID = 3, No = 3

次は独自の整列規則を持つ場合です。コンストラクタに自作の整列規則定義 IComparer を与えます。

IComparer は interface のため、先ずは継承して自作のクラスを作成します。

// 自作の整列規則クラス
public sealed class SortRule : IComparer<int> // <>の中身はKeyの型を指定する
{
    // IComparerの実装
    public int Compare(int x, int y)
    {
        return y - x;
    }
}

次にSortedDictionaryのコンストラクタにこのクラスを指定してあげます。

var itemTable = new SortedDictionary<int, Item>(new SortRule())

上記のDictionaryをforeachで列挙すると以下の通り逆順になります。

public static void DisplayItems_1(SortedDictionary<int, Item> itemTable)
{
    foreach (KeyValuePair<int, Item> item in itemTable)
    {
        Console.WriteLine($"{item.Key} = ,{item.Value.ToString()}");
    }
}
// 出力内容:逆順になっている
// 3 = ,ID = 3, No = 3
// 2 = ,ID = 2, No = 2
// 1 = ,ID = 1, No = 1
// 0 = ,ID = 0, No = 0

Dicrionary に比べると追加するときに動作が若干重いため実行速度に制約がある場合使えない可能性があります。

その3:リストにしてからソートする

ちょっと変わった方法です。

一旦リストにしてからソートします。List の Linq メソッドは色々できるのでそれを利用します。

// キーと値両方をリスト化からソート
List<KeyValuePair<int, Item>> keyValueList = itemTable.ToList();
keyValueList.Sort(); // Sortメソッドのデフォルトで整列

// キーをリスト化してソート
List<int> keyList = itemTable.Keys.ToList();
keyList.Sort();

// 値をリスト化してソート
List<Item> valueList = itemTable.Values.ToList();
valueList.Sort();

List.Sort に整列規則を指定するデリゲート(Comparison)が指定できるので必要に応じて整列規則をラムダなどで指定します。

無理やり整列するため動作速度が非常に低速で、メモリも倍以上使用してしまいますが脳には優しいのかもしれません。

関係する記事

Dictionary の以下基本的な使い方の紹介です。

takachan.hatenablog.com