C#の配列やListをソートする (List.Sort)

C#

配列やListクラスを使う時、中の要素が順番に並びかわっていると扱いやすかったりしますが、そんなときの為に順番を並び替えるメソッドが用意されています。

ListクラスにはSortメソッドがあり、配列の場合は ArrayクラスのSortメソッドを利用します。


Sortメソッドの種類

Sortメソッドには引数が異なるいくつかのメソッドが用意されています。
それぞれの特徴を理解して使いこなしていきましょう。
  • Sort()         ・・・既定の比較子を使って並べ替え
  • Sort(IComparer<T>)  ・・・指定したIComparer<T>インターフェースで並べ替え
  • Sort(Comparison<T>) ・・・指定したComparison<T>デリゲートで並べ替え
※ListクラスもArrayクラスも使い方はほぼ一緒です


既定の比較子で並べ替え

引数で並べ替える条件を指定しないタイプのメソッドです。
既定の比較子というのは List の各要素が持っている IComparable インターフェースを指しています。

たとえば List<string> のような stringクラスのリストの場合、stringクラスが持つ CompareToメソッドの戻り値を条件にして昇順に並べ替えが行われます。
(IComparableインターフェースを持たないオブジェクトを要素にするListに対してSort()を行うと例外InvalidOperationExceptionがスローされます)

ちなみに、List<int> や List<double> といったリストは正しく Sort() メソッドを実行できます。
int と System.Int32構造体は同じものなので IComparableインターフェースを持っているのです。


以下の例では TestItem というクラスに IComparable<T>インターフェースを実装してソートしています。

    public class TestItem : IComparable<TestItem>
    {
        public int x;
        public int y;
 
        public int CompareTo(TestItem o)
        {
            if (null == o) return 1;
            if (x < o.x) return -1;
            if (x > o.x) return  1;
            if (y < o.y) return -1;
            if (y > o.y) return  1;
            return  0;
        }
    }
private void Test()
{
    var list = new List<TestItem>();
 
    list.Add(new TestItem() { x = 5, y = 4 });
    list.Add(new TestItem() { x = 2, y = 1 });
    list.Add(new TestItem() { x = 4, y = 7 });
    list.Add(new TestItem() { x = 6, y = 3 });
    list.Add(new TestItem() { x = 9, y = 9 });
    list.Add(new TestItem() { x = 5, y = 2 });
 
    list.Sort();
 
    foreach (var item in list)
    {
        System.Console.WriteLine("X=" + item.x + ",Y=" + item.y);
    }
}
出力結果は以下のようになります。

X=2,Y=1
X=4,Y=7
X=5,Y=2
X=5,Y=4
X=6,Y=3
X=9,Y=9


IComparer<T>インターフェースで並べ替え

上記で使用した IComparable<T>インターフェースと似ていて混乱しそうですが以下の違いがあります。
  • IComparable<T>インターフェースは自身と引数のオブジェクトを比較する。
  • IComparer<T>インターフェースは引数で渡された2つのオブジェクトを比較する。
以下の例では Listクラスを継承した TestListクラスを作成し、そこに IComparer<T>インターフェースを実装してソートしています。

    public class TestItem
    {
        public int x;
        public int y;
    }
    public class TestList : List<TestItem>, IComparer<TestItem>
    {
        public int Compare(TestItem o1, TestItem o2)
        {
            if ((null == o1) && (null == o2)) return  0;
            if ((null == o1) && (null != o2)) return -1;
            if ((null != o1) && (null == o2)) return  1;
 
            if (o1.x < o2.x) return -1;
            if (o1.x > o2.x) return  1;
            if (o1.y < o2.y) return -1;
            if (o1.y > o2.y) return  1;
            return 0;
        }
 
        public void SortXY()
        {
            Sort(this);
        }
    }
private void Test()
{
    var list = new TestList();
 
    list.Add(new TestItem() { x = 5, y = 4 });
    list.Add(new TestItem() { x = 2, y = 1 });
    list.Add(new TestItem() { x = 4, y = 7 });
    list.Add(new TestItem() { x = 6, y = 3 });
    list.Add(new TestItem() { x = 9, y = 9 });
    list.Add(new TestItem() { x = 5, y = 2 });
 
    list.SortXY();
 
    foreach (var item in list)
    {
        System.Console.WriteLine("X=" + item.x + ",Y=" + item.y);
    }
}


Comparison<T>デリゲートで並べ替え

何度も使い回す並べ替え条件ならインターフェースとして実装するのが良さそうですが、1度限りの並べ替えなら、デリゲートを使う方がお手軽かもしれません。

以下の例では、デリゲートをラムダ式で実装しています。

    public class TestItem
    {
        public int x;
        public int y;
    }
private void Test()
{
    var list = new List<TestItem>();
 
    list.Add(new TestItem() { x = 5, y = 4 });
    list.Add(new TestItem() { x = 2, y = 1 });
    list.Add(new TestItem() { x = 4, y = 7 });
    list.Add(new TestItem() { x = 6, y = 3 });
    list.Add(new TestItem() { x = 9, y = 9 });
    list.Add(new TestItem() { x = 5, y = 2 });
 
    list.Sort((o1, o2) =>
        {
            if ((null == o1) && (null == o2)) return  0;
            if ((null == o1) && (null != o2)) return -1;
            if ((null != o1) && (null == o2)) return  1;
 
            if (o1.x < o2.x) return -1;
            if (o1.x > o2.x) return  1;
            if (o1.y < o2.y) return -1;
            if (o1.y > o2.y) return  1;
            return 0;
        });
 
    foreach (var item in list)
    {
        System.Console.WriteLine("X=" + item.x + ",Y=" + item.y);
    }
}

※デリゲートの詳細は「C#のデリゲート (delegate) って何?」をご覧ください
※ラムダ式の詳細は「C#のラムダ式【=>】って何?」をご覧ください



コメント