ここでは基本型のデフォルトのソート順について見てみます。 int, long等の数値型やDateTime等の型はIComparableインターフェイスを実装していて、デフォルトではそれによって定義される順で並べ替えが行われます。 数値型では数の小さい順、文字列型では辞書順、日付型では日付の古い順での並べ替えが基本となっています。
数値型
int, long, uint, byte等の整数型、float, double, decimal等の実数型は、いずれもその数の小さい順に並べ替えられます。
int: -2147483648, -1, 0, 1, 2147483647 decimal: -79228162514264337593543950335, -1, -0.01, 0, 0.01, 1, 79228162514264337593543950335
using System;
class Sample {
static void Main()
{
var intArray = new int[] {0, 1, -1, int.MaxValue, int.MinValue};
var decimalArray = new decimal[] {
0m, 1m, -1m, 0.01m, -0.01m,
decimal.MaxValue, decimal.MinValue,
};
Array.Sort(intArray);
Array.Sort(decimalArray);
Console.Write("int: ");
Console.WriteLine(string.Join(", ", intArray));
Console.Write("decimal: ");
Console.WriteLine(string.Join(", ", decimalArray));
}
}
Imports System
Class Sample
Shared Sub Main()
Dim intArray As Integer() = New Integer() {0, 1, -1, Integer.MaxValue, Integer.MinValue}
Dim decimalArray As Decimal() = New Decimal() { _
0D, 1D, -1D, 0.01D, -0.01D, _
Decimal.MaxValue, Decimal.MinValue _
}
Array.Sort(intArray)
Array.Sort(decimalArray)
Console.Write("Integer: ")
Console.WriteLine(String.Join(", ", intArray))
Console.Write("Decimal: ")
Console.WriteLine(String.Join(", ", decimalArray))
End Sub
End Class
浮動小数点型
float, doubleの場合、NaN(非数)は-∞(負の無限大)や他の数よりも小さいと扱われる点に注意が必要です。
double: NaN, -Infinity, -1.79769313486232E+308, -1, -0.01, 0, 0.01, 1, 1.79769313486232E+308, Infinity
using System;
class Sample {
static void Main()
{
var doubleArray = new double[] {
0.0, 1.0, -1.0, 0.01, -0.01,
double.MaxValue, double.MinValue,
double.PositiveInfinity, double.NegativeInfinity, double.NaN,
};
Array.Sort(doubleArray);
Console.Write("double: ");
Console.WriteLine(string.Join(", ", doubleArray));
}
}
Imports System
Class Sample
Shared Sub Main()
Dim doubleArray As Double() = New Double() { _
0.0, 1.0, -1.0, 0.01, -0.01, _
double.MaxValue, double.MinValue, _
double.PositiveInfinity, double.NegativeInfinity, double.NaN _
}
Array.Sort(doubleArray)
Console.Write("Double: ")
Console.WriteLine(string.Join(", ", doubleArray))
End Sub
End Class
ヌル許容の数値型
ヌル許容の数値型の場合、null
/Nothing
は他のどの数よりも小さいと扱われます。
(null), -2147483648, -1, 0, 1, 2147483647,
using System;
class Sample {
static void Main()
{
var arr = new int?[] {0, 1, -1, null, int.MaxValue, int.MinValue};
Array.Sort(arr);
foreach (var val in arr) {
Console.Write("{0}, ", val == null ? "(null)" : val.ToString());
}
Console.WriteLine();
}
}
Imports System
Class Sample
Shared Sub Main()
Dim arr As Integer?() = New Integer?() {0, 1, -1, Nothing, Integer.MaxValue, Integer.MinValue}
Array.Sort(arr)
For Each val As Integer? In arr
If val Is Nothing Then
Console.Write("{0}, ", "(Nothing)")
Else
Console.Write("{0}, ", val)
End If
Next
End Sub
End Class
またNaNに対しても同様で、null
/Nothing
はNaNよりも小さいと扱われます。 つまり、小さい順位並べるとnull
< NaN < -∞ < MinValue < 0 の順になります。
(null), NaN, -Infinity, -1.79769313486232E+308, -1, 0, 1,
using System;
class Sample {
static void Main()
{
var arr = new double?[] {
0.0, 1.0, -1.0, null, double.MinValue, double.NegativeInfinity, double.NaN,
};
Array.Sort(arr);
foreach (var val in arr) {
Console.Write("{0}, ", val == null ? "(null)" : val.ToString());
}
Console.WriteLine();
}
}
Imports System
Class Sample
Shared Sub Main()
Dim arr As Double?() = New Double?() { _
0.0, 1.0, -1.0, Nothing, _
double.MinValue, double.NegativeInfinity, double.NaN _
}
Array.Sort(arr)
For Each val As Double? In arr
If val Is Nothing Then
Console.Write("{0}, ", "(Nothing)")
Else
Console.Write("{0}, ", val)
End If
Next
Console.WriteLine()
End Sub
End Class
文字列型
文字列型では、辞書順(文字の順、長さの短い順)に並べ替えられます。
a aa aaa aaaa aab aac ab aba abb ac
using System;
class Sample {
static void Main()
{
var arr = new string[] {
"a", "aa", "ab", "ac", "aaa", "aab", "aba", "abb", "aac", "aaaa",
};
Array.Sort(arr);
foreach (var val in arr) {
Console.WriteLine(val);
}
}
}
Imports System
Class Sample
Shared Sub Main()
Dim arr As String() = New String() { _
"a", "aa", "ab", "ac", "aaa", "aab", "aba", "abb", "aac", "aaaa" _
}
Array.Sort(arr)
For Each val As String In arr
Console.WriteLine(val)
Next
End Sub
End Class
ただ、カルチャや比較方法によっては単なる辞書順にはならないので注意してください。 詳しくは文字列と比較オプション・カルチャの並べ替え規則やカルチャと書式・テキスト処理・暦で解説しています。
null
/Nothing
や空の文字列(長さ0の文字列)が含まれていても並べ替えることができます。 null
/Nothing
は、空の文字列を含む他のどの文字列よりも小さいと扱われます。 つまり、空の文字列を含めて大小関係を並べると、「null/Nothing < 空の文字列 < 1文字以上の文字列」の順となります。
(null) a aa ab b
using System;
class Sample {
static void Main()
{
var arr = new string[] {
"a", "aa", "ab", "b", null, ""
};
Array.Sort(arr);
foreach (var val in arr) {
Console.WriteLine(val ?? "(null)");
}
}
}
Imports System
Class Sample
Shared Sub Main()
Dim arr As String() = New String() { _
"a", "aa", "ab", "b", Nothing, "" _
}
Array.Sort(arr)
For Each val As String In arr
Console.WriteLine(If(val, "(Nothing)"))
Next
End Sub
End Class
大文字小文字の違いを考慮したソート
文字列をソートする際、場合によっては大文字小文字の違いをどう扱うか厳密に決める必要が出て来ます。 その場合は、StringComparerを使ってどう扱うかを指定することができます。 Sortメソッド、OrderByメソッドともにIComparerを引数に取ることができ、これにStringComparerを指定することで、文字列比較の際の動作を指定できます。
using System;
using System.Collections.Generic;
class Sample {
static void Main()
{
var list = new List<string>(new string[] {
"ab", "ABC", "AA", "a", "aa", "abc", "A",
});
// 大文字・小文字の違いを無視してソート
list.Sort(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(string.Join(", ", list));
// 大文字・小文字の違いを意識してソート
list.Sort(StringComparer.Ordinal);
Console.WriteLine(string.Join(", ", list));
}
}
using System;
using System.Collections.Generic;
using System.Linq;
class Sample {
static void Main()
{
var list = new List<string>(new string[] {
"ab", "ABC", "AA", "a", "aa", "abc", "A",
});
// 大文字・小文字の違いを無視してソート
var ord1 = list.OrderBy(s => s, StringComparer.OrdinalIgnoreCase);
Console.WriteLine(string.Join(", ", ord1));
// 大文字・小文字の違いを意識してソート
var ord2 = list.OrderBy(s => s, StringComparer.Ordinal);
Console.WriteLine(string.Join(", ", ord2));
}
}
Imports System
Imports System.Collections.Generic
Class Sample
Shared Sub Main()
Dim list As New List(Of String)(New String() { _
"ab", "ABC", "AA", "a", "aa", "abc", "A" _
})
' 大文字・小文字の違いを無視してソート
list.Sort(StringComparer.OrdinalIgnoreCase)
Console.WriteLine(String.Join(", ", list))
' 大文字・小文字の違いを意識してソート
list.Sort(StringComparer.Ordinal)
Console.WriteLine(string.Join(", ", list))
End Sub
End Class
Imports System
Imports System.Collections.Generic
Imports System.Linq
Class Sample
Shared Sub Main()
Dim list As New List(Of String)(New String() { _
"ab", "ABC", "AA", "a", "aa", "abc", "A" _
})
' 大文字・小文字の違いを無視してソート
For Each str As String In list.OrderBy(Function(s) s, StringComparer.OrdinalIgnoreCase)
Console.Write("{0}, ", str)
Next
Console.WriteLine()
' 大文字・小文字の違いを意識してソート
For Each str As String In list.OrderBy(Function(s) s, StringComparer.Ordinal)
Console.Write("{0}, ", str)
Next
Console.WriteLine()
End Sub
End Class
a, A, AA, aa, ab, ABC, abc A, AA, ABC, a, aa, ab, abc
なお、IComparerについては大小関係の定義と比較、StringComparerについて文字列と比較オプション・カルチャの並べ替え規則で詳しく解説しています。
日付型
DateTimeとDateTimeOffsetは日付・時間の古い順に並べ替えられます。
並べ替えに際して、DateTimeではKindプロパティの値は無視されて日時のみが比較されます。 したがって、2000-01-01T00:00:00+00:00
(UTC)と2000-01-01T00:00:00+09:00
(ローカル時刻)と2000-01-01T00:00:00
(タイムゾーン指定なし)はいずれも同一の値として扱われます。 DateTimeの並べ替えでは、自動的にUTCに変換されてから並べ替えられるといったことはありません。 そのため、ToLocalTime・ToUniversalTimeメソッドを使って現地時刻・UTCのいずれかに統一してからソートしないと意図しない結果になる場合があります。
一方DateTimeOffsetでは、日時に加えてOffsetプロパティの値が考慮されて比較されます。 したがって、2000-01-01T00:00:00+09:00
、2000-01-01T00:00:00+00:00
、2000-01-01T00:00:00-05:00
はいずれも異なる値として比較されます。
DateTime 2000-01-01T00:00:00.0000000Z (2000-01-01T00:00:00.0000000Z) 2000-01-01T00:00:00.0000000+09:00 (1999-12-31T15:00:00.0000000Z) 2000-01-01T06:00:00.0000000Z (2000-01-01T06:00:00.0000000Z) 2000-01-01T18:00:00.0000000Z (2000-01-01T18:00:00.0000000Z) 2000-01-02T00:00:00.0000000Z (2000-01-02T00:00:00.0000000Z) 2000-01-02T00:00:00.0000000+09:00 (2000-01-01T15:00:00.0000000Z) DateTimeOffset 2000-01-01T00:00:00.0000000+09:00 (1999-12-31T15:00:00.0000000+00:00) 2000-01-01T00:00:00.0000000+00:00 (2000-01-01T00:00:00.0000000+00:00) 2000-01-01T00:00:00.0000000-05:00 (2000-01-01T05:00:00.0000000+00:00) 2000-01-02T00:00:00.0000000+09:00 (2000-01-01T15:00:00.0000000+00:00) 2000-01-02T00:00:00.0000000+00:00 (2000-01-02T00:00:00.0000000+00:00) 2000-01-02T00:00:00.0000000-05:00 (2000-01-02T05:00:00.0000000+00:00)
using System;
class Sample {
static void Main()
{
var dateTimeArray = new DateTime[] {
new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Local),
new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc),
new DateTime(2000, 1, 1, 6, 0, 0, DateTimeKind.Utc),
new DateTime(2000, 1, 1, 18, 0, 0, DateTimeKind.Utc),
new DateTime(2000, 1, 2, 0, 0, 0, DateTimeKind.Local),
new DateTime(2000, 1, 2, 0, 0, 0, DateTimeKind.Utc),
};
var dateTimeOffsetArray = new DateTimeOffset[] {
new DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan( 0, 0, 0)),
new DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan(-5, 0, 0)),
new DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan(+9, 0, 0)),
new DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan( 0, 0, 0)),
new DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan(-5, 0, 0)),
new DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan(+9, 0, 0)),
};
Array.Sort(dateTimeArray);
Array.Sort(dateTimeOffsetArray);
Console.WriteLine("DateTime");
foreach (var val in dateTimeArray) {
Console.WriteLine("{0,-35:o} ({1:o})", val, val.ToUniversalTime());
}
Console.WriteLine("DateTimeOffset");
foreach (var val in dateTimeOffsetArray) {
Console.WriteLine("{0,-35:o} ({1:o})", val, val.ToUniversalTime());
}
}
}
Imports System
Class Sample
Shared Sub Main()
Dim dateTimeArray As DateTime() = New DateTime() { _
New DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Local), _
New DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc), _
New DateTime(2000, 1, 1, 6, 0, 0, DateTimeKind.Utc), _
New DateTime(2000, 1, 1, 18, 0, 0, DateTimeKind.Utc), _
New DateTime(2000, 1, 2, 0, 0, 0, DateTimeKind.Local), _
New DateTime(2000, 1, 2, 0, 0, 0, DateTimeKind.Utc) _
}
Dim dateTimeOffsetArray As DateTimeOffset() = New DateTimeOffset() { _
New DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan( 0, 0, 0)), _
New DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan(-5, 0, 0)), _
New DateTimeOffset(2000, 1, 1, 0, 0, 0, new TimeSpan(+9, 0, 0)), _
New DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan( 0, 0, 0)), _
New DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan(-5, 0, 0)), _
new DateTimeOffset(2000, 1, 2, 0, 0, 0, new TimeSpan(+9, 0, 0)) _
}
Array.Sort(dateTimeArray)
Array.Sort(dateTimeOffsetArray)
Console.WriteLine("DateTime")
For Each val As DateTime In dateTimeArray
Console.WriteLine("{0,-35:o} ({1:o})", val, val.ToUniversalTime())
Next
Console.WriteLine("DateTimeOffset")
For Each val As DateTimeOffset In dateTimeOffsetArray
Console.WriteLine("{0,-35:o} ({1:o})", val, val.ToUniversalTime())
Next
End Sub
End Class
日付・時間の新しい順に並べ替えるには、次の例のように並べ替え順序を通常と逆の順序に定義したメソッドを用意してからソートします。 逆順(降順)でのソート方法については基本型のソートと昇順・降順でのソート §.降順でのソートを参照してください。
using System;
class Sample {
// DateTimeを通常と逆の順に並べ替えるためのメソッド
static int CompareDateTimeReverseOrder(DateTime x, DateTime y)
{
// 逆順で比較
return DateTime.Compare(y, x);
// 次のようにしても同じ
//return -DateTime.Compare(x, y);
//return y.CompareTo(x);
}
static void Main()
{
var dateTimeArray = new DateTime[] {
new DateTime(2000, 1, 1, 0, 0, 0),
new DateTime(2000, 1, 1, 12, 0, 0),
new DateTime(2000, 1, 2, 0, 0, 0),
new DateTime(2000, 2, 1, 0, 0, 0),
new DateTime(2001, 1, 1, 0, 0, 0),
};
// デフォルトの順にソート
Array.Sort(dateTimeArray);
Console.WriteLine("default order");
foreach (var val in dateTimeArray) {
Console.WriteLine("{0:f}", val);
}
// 逆順にソート
Array.Sort(dateTimeArray, CompareDateTimeReverseOrder);
Console.WriteLine("reverse order");
foreach (var val in dateTimeArray) {
Console.WriteLine("{0:f}", val);
}
}
}
Imports System
Class Sample
' DateTimeを通常と逆の順に並べ替えるためのメソッド
Shared Function CompareDateTimeReverseOrder(ByVal x As DateTime, ByVal y As DateTime) As Integer
' 逆順で比較
Return DateTime.Compare(y, x)
' 次のようにしても同じ
'Return -DateTime.Compare(x, y)
'Return y.CompareTo(x)
End Function
Shared Sub Main()
Dim dateTimeArray As DateTime() = New DateTime() { _
New DateTime(2000, 1, 1, 0, 0, 0), _
New DateTime(2000, 1, 1, 12, 0, 0), _
New DateTime(2000, 1, 2, 0, 0, 0), _
New DateTime(2000, 2, 1, 0, 0, 0), _
New DateTime(2001, 1, 1, 0, 0, 0) _
}
' デフォルトの順にソート
Array.Sort(dateTimeArray)
Console.WriteLine("default order")
For Each val As DateTime In dateTimeArray
Console.WriteLine("{0:f}", val)
Next
' 逆順にソート
Array.Sort(dateTimeArray, AddressOf CompareDateTimeReverseOrder)
Console.WriteLine("reverse order")
For Each val As DateTime In dateTimeArray
Console.WriteLine("{0:f}", val)
Next
End Sub
End Class
default order 2000年1月1日 0:00 2000年1月1日 12:00 2000年1月2日 0:00 2000年2月1日 0:00 2001年1月1日 0:00 reverse order 2001年1月1日 0:00 2000年2月1日 0:00 2000年1月2日 0:00 2000年1月1日 12:00 2000年1月1日 0:00
列挙体
列挙体は、整数型に準じた並べ替えが行われ、各メンバに与えられている値の大きさに従って小さい順に並べ替えられます。
MinusTwo (-2) MinusOne (-1) Zero (0) One (1) Two (2)
using System;
enum Number {
MinusTwo = -2,
MinusOne = -1,
Zero = 0,
One = 1,
Two = 2,
}
class Sample {
static void Main()
{
var arr = new Number[] {
Number.Zero, Number.One, Number.MinusOne, Number.Two, Number.MinusTwo,
};
Array.Sort(arr);
foreach (var val in arr) {
Console.WriteLine("{0,-8:G} ({0:D})", val);
}
}
}
Imports System
Enum Number
MinusTwo = -2
MinusOne = -1
Zero = 0
One = 1
Two = 2
End Enum
Class Sample
Shared Sub Main()
Dim arr As Number() = New Number() { _
Number.Zero, Number.One, Number.MinusOne, Number.Two, Number.MinusTwo _
}
Array.Sort(arr)
For Each val As Number In arr
Console.WriteLine("{0,-8:G} ({0:D})", val)
Next
End Sub
End Class
列挙体をメンバ名の順(値に割り当てられている名前の順)に並べるには、次のように並べ替え順序を定義する必要があります。 ただ、次の例にあるように、列挙体ではメンバ名で定義されていない値も設定できるため、そういった値をどう取り扱うかについての考慮も必要となってきます。
using System;
enum Number {
MinusTwo = -2,
MinusOne = -1,
Zero = 0,
One = 1,
Two = 2,
}
class Sample {
// 列挙体の値を比較するメソッド
static int CompareNumber(Number x, Number y)
{
// 列挙体の値を文字列(メンバ名)に変換
var xx = x.ToString("G");
var yy = y.ToString("G");
// 文字列として比較した結果を返す
return string.Compare(xx, yy);
}
static void Main()
{
var arr = new Number[] {
Number.Zero, Number.One, Number.MinusOne, Number.Two, Number.MinusTwo,
(Number)3, // <- 列挙体のメンバで定義されていない値
};
Array.Sort(arr, CompareNumber);
foreach (var val in arr) {
Console.WriteLine("{0,-8:G} ({0:D})", val);
}
}
}
Imports System
Enum Number
MinusTwo = -2
MinusOne = -1
Zero = 0
One = 1
Two = 2
End Enum
Class Sample
' 列挙体の値を比較するメソッド
Shared Function CompareNumber(ByVal x As Number, ByVal y As Number) As Integer
' 列挙体の値を文字列(メンバ名)に変換
Dim xx As String = x.ToString("G")
Dim yy As String = y.ToString("G")
' 文字列として比較した結果を返す
Return String.Compare(xx, yy)
End Function
Shared Sub Main()
Dim arr As Number() = New Number() { _
Number.Zero, Number.One, Number.MinusOne, Number.Two, Number.MinusTwo, _
CType(3, Number) _
}
Array.Sort(arr, AddressOf CompareNumber)
For Each val As Number In arr
Console.WriteLine("{0,-8:G} ({0:D})", val)
Next
End Sub
End Class
3 (3) MinusOne (-1) MinusTwo (-2) One (1) Two (2) Zero (0)