注意:配列やコレクション内の要素を検索する方法は、「配列やコレクション内に指定された要素があるか調べ、その位置を知る」に移動しました。
ここでは、配列やコレクション内の要素を並び替える方法を説明します。さらに、並び替えの方法を変更する方法も紹介します。
基本的には、配列の並び替えは、Array.Sortメソッド(.NET Framework 2.0以降では、Array.Sortジェネリックメソッド)で行います。コレクションの並び替えも、Sortメソッドです。
Array.Sortは、.NET Framework 4.5以降では、イントロソート(Introsort)アルゴリズムを使用し、計算時間は最悪でO(n log n)です。.NET Framework 4.0以前では、クイックソート(quicksort)アルゴリズムを使用し、計算時間は平均でO(n log n)、最悪でO(n ^ 2)です。
ArrayListやList<T>のSortメソッドも、内部ではArray.Sortを使用しています。
早速ですが、Sortメソッドを使って文字列の配列を並び替える例を示します。
'並び替えを行う配列 Dim ary As String() = New String() {"b", "aaaaa", "cc"} '並び替える Array.Sort(ary) '.NET Framework 2.0以降では次のようにジェネリックメソッドを使えるが、 '上記のようにしても自動的にジェネリックメソッドが使われる 'Array.Sort(Of String)(ary) 'aryは { "aaaaa", "b", "cc" } となる
//並び替えを行う配列 string[] ary = new string[] { "b", "aaaaa", "cc" }; //並び替える Array.Sort(ary); //.NET Framework 2.0以降では次のようにジェネリックメソッドを使えるが、 //上記のようにしても自動的にジェネリックメソッドが使われる //Array.Sort<string>(ary); //aryは { "aaaaa", "b", "cc" } となる
次にコレクションを並び替える例を示します。ここでは、ArrayListを並び替えています。
'並び替えるArrayListを作成する Dim al As New System.Collections.ArrayList() al.Add("b") al.Add("aaaaa") al.Add("cc") '並び替える al.Sort() 'alは { "aaaaa", "b", "cc" } となる
//並び替えるArrayListを作成する System.Collections.ArrayList al = new System.Collections.ArrayList(); al.Add("b"); al.Add("aaaaa"); al.Add("cc"); //並び替える al.Sort(); //alは { "aaaaa", "b", "cc" } となる
上記のように何もしないでSortメソッドを呼び出した時は、要素のCompareToメソッドによってどのように並び替えられるかが決まります。CompareToは2つのオブジェクトの内どちらが大きいかを判断するメソッドで、Sortは要素が小さい順に並び替えます。よってこの方法では、要素の型がメンバとしてCompareToメソッドを持っていなければならず、そのためにIComparableインターフェイスが実装されている必要があります。IComparableインターフェイスを実装する方法については「自作クラスの配列やコレクションでSortやBinarySearchができるようにする」で詳しく説明していますので、そちらをご覧ください。
上記のようにCompareToメソッドによる並び替えではなく、並び替え方を独自に指定することもできます。そのためにはIComparerインターフェイスを実装したクラスを定義します。IComparerインターフェイスのメンバにはCompareメソッドしかなく、IComparable.CompareToメソッドの代わりにこのメソッドで2つのオブジェクトを比較します。
この方法であれば、IComparableインターフェイスを実装していない要素でも並び替えることができます。
Compareメソッドでは、1番目のパラメータと2番目のパラメータで渡されるオブジェクトを比較し、1番目の方が2番目より小さいときは負の整数、大きいときは正の整数、同じときは0を返すようにします。
以下に、文字列の長さで並び替えできるようにしたIComparerを実装したクラスの例を示します。
'並び替える方法を定義するクラス 'IComparerインターフェイスを実装する Public Class LengthComparer Implements System.Collections.IComparer 'xがyより小さいときはマイナスの数、大きいときはプラスの数、同じときは0を返す Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _ Implements System.Collections.IComparer.Compare 'Nothingが最も小さいとする If x Is Nothing AndAlso y Is Nothing Then Return 0 End If If x Is Nothing Then Return -1 End If If y Is Nothing Then Return 1 End If 'String型以外の比較はエラー If Not (TypeOf x Is String) Then Throw New ArgumentException("String型でなければなりません。", "x") ElseIf Not (TypeOf y Is String) Then Throw New ArgumentException("String型でなければなりません。", "y") End If '文字列の長さを比較する Return DirectCast(x, String).Length.CompareTo(DirectCast(y, String).Length) 'または、次のようにもできる 'Return DirectCast(x, String).Length - DirectCast(y, String).Length End Function End Class
//並び替える方法を定義するクラス //IComparerインターフェイスを実装する public class LengthComparer : System.Collections.IComparer { //xがyより小さいときはマイナスの数、大きいときはプラスの数、同じときは0を返す public int Compare(object x, object y) { //nullが最も小さいとする if (x == null && y == null) { return 0; } if (x == null) { return -1; } if (y == null) { return 1; } //String型以外の比較はエラー if (!(x is string)) { throw new ArgumentException("String型でなければなりません。", "x"); } else if (!(y is string)) { throw new ArgumentException("String型でなければなりません。", "y"); } //文字列の長さを比較する return ((string)x).Length.CompareTo(((string)y).Length); //または、次のようにもできる //return ((string)x).Length - ((string)y).Length; } }
補足:このコードではnullが一番小さいとしていますが、ArgumentExceptionのような例外をスローしてもよいでしょう。
このクラスを使用して並び替えを行うコードを以下に示します。
'並び替えを行う配列を作成 Dim ary As String() = New String() {"b", "aaaaa", "cc"} 'LengthComparerを使って、文字列の長さで並び替える Dim comp As New LengthComparer() Array.Sort(ary, comp) 'aryは { "b", "cc", "aaaaa" } となる
//並び替えを行う配列を作成 string[] ary = new string[] { "b", "aaaaa", "cc" }; //LengthComparerを使って、文字列の長さで並び替える LengthComparer comp = new LengthComparer(); Array.Sort(ary, comp); //aryは { "b", "cc", "aaaaa" } となる
文字列の配列を大文字小文字を区別しないで並び替えたり、カルチャに依存しないで並べ替えたりするならば、あらかじめ用意されているIComparerを使うこともできます(ただし、.NET Framework 2.0以降)。詳しくは、「文字列の配列やコレクションを並び替える」で説明します。
.NET Framework 2.0以降では、IComparer<T>ジェネリックインターフェイスを実装した方が良いでしょう。
先ほどのLengthComparerクラスにIComparer<T>ジェネリックインターフェイスも実装した例を示します。使い方は、先ほどと同じです。
'並び替える方法を定義するクラス 'IComparerジェネリックインターフェイスを実装する Public Class LengthComparer Implements System.Collections.IComparer Implements System.Collections.Generic.IComparer(Of String) 'xがyより小さいときはマイナスの数、大きいときはプラスの数、同じときは0を返す Public Function Compare(ByVal x As String, ByVal y As String) As Integer _ Implements System.Collections.Generic.IComparer(Of String).Compare 'Nothingが最も小さいとする If x Is Nothing AndAlso y Is Nothing Then Return 0 End If If x Is Nothing Then Return -1 End If If y Is Nothing Then Return 1 End If '文字列の長さを比較する Return x.Length.CompareTo(y.Length) End Function Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer _ Implements System.Collections.IComparer.Compare 'Nothingが最も小さいとする If x Is Nothing AndAlso y Is Nothing Then Return 0 End If If x Is Nothing Then Return -1 End If If y Is Nothing Then Return 1 End If 'String型以外の比較はエラー If Not (TypeOf x Is String) Then Throw New ArgumentException("String型でなければなりません。", "x") ElseIf Not (TypeOf y Is String) Then Throw New ArgumentException("String型でなければなりません。", "y") End If Return Me.Compare(DirectCast(x, String), DirectCast(y, String)) End Function End Class
//並び替える方法を定義するクラス //IComparerジェネリックインターフェイスを実装する public class LengthComparer : System.Collections.IComparer, System.Collections.Generic.IComparer<string> { //xがyより小さいときはマイナスの数、大きいときはプラスの数、同じときは0を返す public int Compare(string x, string y) { //nullが最も小さいとする if (x == null && y == null) { return 0; } if (x == null) { return -1; } if (y == null) { return 1; } //文字列の長さを比較する return x.Length.CompareTo(y.Length); } public int Compare(object x, object y) { //nullが最も小さいとする if (x == null && y == null) { return 0; } if (x == null) { return -1; } if (y == null) { return 1; } //String型以外の比較はエラー if (!(x is string)) { throw new ArgumentException("String型でなければなりません。", "x"); } else if (!(y is string)) { throw new ArgumentException("String型でなければなりません。", "y"); } return this.Compare((string)x, (string)y); } }
これは、IComparerインターフェイスとはまた別の方法です。.NET Framework 2.0からは、Comparison<T>ジェネリックデリゲートを使って並び替え方を変更することもできます。この方法ならば、IComparerインターフェイスを実装したクラスを作成する手間が省けます。ただし、ジェネリックではないコレクションではこの方法は使えません。
以下にこの方法による例を示します。まず次のようなメソッドを作成し、並び替え方を指定します。やっていることは先のCompareメソッドと同じです。
'xがyより小さいときはマイナスの数、大きいときはプラスの数、同じときは0を返す Public Shared Function CompareByLength( _ ByVal x As String, ByVal y As String) As Integer 'Nothingが最も小さいとする If x Is Nothing AndAlso y Is Nothing Then Return 0 End If If x Is Nothing Then Return -1 End If If y Is Nothing Then Return 1 End If '文字列の長さを比較する Return x.Length - y.Length End Function
//xがyより小さいときはマイナスの数、大きいときはプラスの数、同じときは0を返す public static int CompareByLength(string x, string y) { //nullが最も小さいとする if (x == null && y == null) { return 0; } if (x == null) { return -1; } if (y == null) { return 1; } //文字列の長さを比較する return x.Length - y.Length; }
このメソッドを使って並び替えを行うには、次のようにします。
'並び替えを行う配列を作成 Dim ary As String() = New String() {"b", "aaaaa", "cc"} '並び替えを行う Array.Sort(ary, AddressOf CompareByLength) 'aryは { "b", "cc", "aaaaa" } となる
//並び替えを行う配列を作成 string[] ary = new string[] { "b", "aaaaa", "cc" }; //並び替えを行う Array.Sort(ary, CompareByLength); //aryは { "b", "cc", "aaaaa" } となる
C#の匿名メソッドが使えるのであれば、上と同じことを次のように簡単に記述することができます。残念ながらVB.NETでは匿名メソッドを使えません。
//並び替えを行う配列を作成 string[] ary = new string[] { "b", "aaaaa", "cc" }; //並び替えを行う Array.Sort(ary, delegate(string x, string y) { return x.Length.CompareTo(y.Length); });
もしラムダ式が使えるのであれば(VB9、C#3.0、.NET Framework 3.5、Visual Studio 2008以降)、次のようにしてさらに簡単に記述することができます。(nullのチェックは省略しています。)
'並び替えを行う配列を作成 Dim ary As String() = New String() {"b", "aaaaa", "cc"} '並び替えを行う Array.Sort(ary, _ Function(x As String, y As String) x.Length.CompareTo(y.Length))
//並び替えを行う配列を作成 string[] ary = new string[] { "b", "aaaaa", "cc" }; //並び替えを行う Array.Sort(ary, (x, y) => x.Length.CompareTo(y.Length));
.NET Framework 3.5からは、LINQ(統合言語クエリ、Language-Integrated Query)によって並び替えることもできます。この方法では、並び替える配列(またはコレクション)はそのままで、並び替えられた新しいシーケンスが返されます。なおLINQを使用するには、参照設定に「System.Core.dll」を追加する必要があります。
LINQによる並べ替えの詳細はMSDNの「Language-Integrated Query (LINQ) データの並べ替え」などに任せるとして、ここでは簡単な例のみを示します。
まずはLINQで普通に配列を並び替える例です。OrderByメソッドを使用しています。ここでは配列を並び替えていますが、コレクションでも同じようにできます。
'Imports System.Linq '並び替えを行う配列 Dim strs As String() = New String() {"b", "aaaaa", "cc"} '要素を昇順で並び替える Dim query = strs.OrderBy(Function(s) s) 'クエリ式を使うと、次のように書ける 'Dim query = From s In strs _ ' Order By s _ ' Select s '配列を作成する Dim sortedStrs As String() = query.ToArray() 'sortedStrsは { "aaaaa", "b", "cc" } となる
//using System.Linq; //並び替えを行う配列 string[] strs = new string[] { "b", "aaaaa", "cc" }; //要素を昇順で並び替える var query = strs.OrderBy(s => s); //クエリ式を使うと、次のように書ける //var query = from s in strs // orderby s // select s; //配列を作成する string[] sortedStrs = query.ToArray(); //sortedStrsは { "aaaaa", "b", "cc" } となる
次に、文字列の配列を文字列長順に並び替える例を示します。
'Imports System.Linq '並び替えを行う配列 Dim strs As String() = New String() {"b", "aaaaa", "cc"} '要素のLengthプロパティの大きさで並び替える Dim query = strs.OrderBy(Function(s) s.Length) 'クエリ式を使うと、次のように書ける 'Dim query = From s In strs _ ' Order By s.Length _ ' Select s '配列を作成する Dim sortedStrs As String() = query.ToArray() 'sortedStrsは { "b", "cc", "aaaaa" } となる
//using System.Linq; //並び替えを行う配列 string[] strs = new string[] { "b", "aaaaa", "cc" }; //要素のLengthプロパティの大きさで並び替える var query = strs.OrderBy(s => s.Length); //クエリ式を使うと、次のように書ける //var query = from s in strs // orderby s.Length // select s; //配列を作成する string[] sortedStrs = query.ToArray(); //sortedStrsは { "b", "cc", "aaaaa" } となる
文字列の長さで並び替え、同じ長さならば普通の文字列の大きさで並び替えるには、次のようにします。2番目以降の並び替えには、ThenByメソッドを使います。
'Imports System.Linq '並び替えを行う配列 Dim strs As String() = New String() {"d", "aaaaa", "cc", "b"} '文字列の長さで並び替え、同じ長さならば普通の並び替え Dim query = strs.OrderBy(Function(s) s.Length).ThenBy(Function(s) s) 'クエリ式を使うと、次のように書ける 'Dim query = From s In strs _ ' Order By s.Length, s _ ' Select s '配列を作成する Dim sortedStrs As String() = query.ToArray() 'sortedStrsは { "b", "d", "cc", "aaaaa" } となる
//using System.Linq; //並び替えを行う配列 string[] strs = new string[] { "d", "aaaaa", "cc", "b" }; //文字列の長さで並び替え、同じ長さならば普通の並び替え var query = strs.OrderBy(s => s.Length).ThenBy(s => s); //クエリ式を使うと、次のように書ける //var query = from s in strs // orderby s.Length, s // select s; //配列を作成する string[] sortedStrs = query.ToArray(); //sortedStrsは { "b", "d", "cc", "aaaaa" } となる
補足:上記の例でThenByをOrderByとした場合は、長さで並び替えた後、普通の並び替えが行われるため、結果としては、通常に並び替えを行い、同じならば長さで並び替えたのと同じになります。
文字列の配列を文字列の長さの降順で並び替えるには、次のようにOrderByDescendingメソッドを使います。なお2番目以降を降順で並び替える場合は、ThenByDescendingメソッドを使います。
'Imports System.Linq '並び替えを行う配列 Dim strs As String() = New String() {"b", "aaaaa", "cc"} 'Lengthプロパティの大きさで降順に並び替える Dim query = strs.OrderByDescending(Function(s) s.Length) 'クエリ式を使うと、次のように書ける 'Dim query = From s In strs _ ' Order By s.Length Descending _ ' Select s '配列を作成する Dim sortedStrs As String() = query.ToArray() 'sortedStrsは { "aaaaa", "cc", "b" } となる
//using System.Linq; //並び替えを行う配列 string[] strs = new string[] { "b", "aaaaa", "cc" }; //Lengthプロパティの大きさで降順に並び替える var query = strs.OrderByDescending(s => s.Length); //クエリ式を使うと、次のように書ける //var query = from s in strs // orderby s.Length descending // select s; //配列を作成する string[] sortedStrs = query.ToArray(); //sortedStrsは { "aaaaa", "cc", "b" } となる
配列やコレクションを並び替える方法は、今まで紹介したものが一般的です。ここからは、指定した方法で並び替える方法として考えられる少し変わった方法をさらに幾つか紹介します。
SortedListクラスはキーと値のペアのコレクションで、キーで並び替えられます。キーの並び替え方は、コンストラクタでIComparerを指定することによって変えることもできます。
.NET Framework 2.0以降では、ジェネリックバージョンを使用できます。
キーは一意でなければならないため、複数の要素が同じ値のキーになるならば、この方法は使用できません。例えば下の例のように文字列の長さで並び替えたい時、同じ長さの文字列が複数存在していると、うまく行きません。
SortedListは、要素が追加された時に並び替えが行われるようです。要素を追加した時の操作はO(n)で、要素が末尾に追加されるときはO(log n)です。
以下の例では、SortedListクラスのキーに文字列の長さを指定することで、文字列の長さで並び替えられるようにしています。
'並び替えを行う配列 Dim ary As String() = New String() {"b", "aaaaa", "cc"} 'SortedListを作成 Dim sl As New System.Collections.SortedList() '文字列の長さをキーとしてコレクションに追加する For Each s As String In ary sl(s.Length) = s 'Addメソッドで追加すると、すでにキーが存在していると例外がスローされる 'sl.Add(s.Length, s) Next '並び替えた結果を表示する Console.WriteLine("SortedListで並び替え") For i As Integer = 0 To sl.Count - 1 Console.WriteLine("{0}: {1}", i, sl.GetByIndex(i)) Next
//並び替えを行う配列 string[] ary = new string[] { "b", "aaaaa", "cc" }; //SortedListを作成 System.Collections.SortedList sl = new System.Collections.SortedList(); //文字列の長さをキーとしてコレクションに追加する foreach (string s in ary) { sl[s.Length] = s; //Addメソッドで追加すると、すでにキーが存在していると例外がスローされる //sl.Add(s.Length, s); } //並び替えた結果を表示する Console.WriteLine("SortedListで並び替え"); for (int i = 0; i < sl.Count; i++) { Console.WriteLine("{0}: {1}", i, sl.GetByIndex(i)); }
Array.Sort(Array, Array)メソッドを使うと、一方の配列に基づいて、もう一方の配列を並び替えることができます。この方法でも、IComparerによって並び替え方を変更することができます。
.NET Framework 2.0以降では、適切なジェネリックメソッドが自動的に呼び出されます。
以下にArray.Sort(Array, Array)メソッドを使って文字列の長さで並び替えを行う例を示します。
'並び替えを行う配列 Dim ary As String() = New String() {"b", "aaaaa", "cc"} '並び替えの対象となる配列を作成 Dim keys As Integer() = New Integer(ary.Length - 1) {} For i As Integer = 0 To keys.Length - 1 '文字列の長さを格納する keys(i) = ary(i).Length Next '並び替えを行う Array.Sort(keys, ary) 'aryは { "b", "cc", "aaaaa" } 'keysは { 1, 2, 5 } になる
//並び替えを行う配列 string[] ary = new string[] { "b", "aaaaa", "cc" }; //並び替えの対象となる配列を作成 int[] keys = new int[ary.Length]; for (int i = 0; i < keys.Length; i++) { //文字列の長さを格納する keys[i] = ary[i].Length; } //並び替えを行う Array.Sort(keys, ary); //aryは { "b", "cc", "aaaaa" } //keysは { 1, 2, 5 } になる
Array.Sortメソッド、List.Sortメソッド、LINQの3つの並び替え方法について、ベンチマークの結果が「Sort String Array」で紹介されています。これによると、List.Sortメソッドが若干速く、要素数が多いときはLINQが遅いようですが、3つの方法の速さの違いはそれほどないようです。ただしこのベンチマークでは、Sortメソッドは配列やコレクションのコピーを作成してから並び替えているため、コピーを作成しないで並び替えるのであれば、LINQとの差はもっと出るかもしれません。
文字列の配列やコレクションを並び替える方法については、「文字列の配列やコレクションを並び替える」でさらに補足しています。そちらでは、大文字、小文字を区別しない並び替えや、カルチャに依存しない並び替えなどについて説明しています。