DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

2つの配列(またはコレクション)を比較して、等しいか調べる

ここでは、2つの配列(またはコレクション)を比べて、両者の要素がすべて同じか調べる方法を紹介します。

For文を使用する方法

まず思いつくのは、For文を使って1つ1つの要素が等しいかを調べる方法です。例えば、以下のような方法です。

VB.NET
コードを隠すコードを選択
'比較する2つの配列
Dim ary1 As String() = New String() {"いち", "に", "さん"}
Dim ary2 As String() = New String() {"いち", "に", "さん"}

'結果を格納する変数
Dim isEqual As Boolean = True

If Object.ReferenceEquals(ary1, ary2) Then
    '同一のインスタンスの時は、同じとする
    isEqual = True
ElseIf ary1 Is Nothing OrElse ary2 Is Nothing _
    OrElse ary1.Length <> ary2.Length Then
    'どちらかがNULLか、要素数が異なる時は、同じではない
    isEqual = False
Else
    '1つ1つの要素が等しいかを調べる
    Dim i As Integer
    For i = 0 To ary1.Length - 1
        'ary1の要素のEqualsメソッドで、ary2の要素と等しいか調べる
        If Not ary1(i).Equals(ary2(i)) Then
            '1つでも等しくない要素があれば、同じではない
            isEqual = False
            Exit For
        End If
    Next
End If

'結果を表示する
If isEqual Then
    Console.WriteLine("2つの配列は等しいです")
Else
    Console.WriteLine("2つの配列は等しくありません")
End If
C#
コードを隠すコードを選択
//比較する2つの配列
string[] ary1 = new string[] { "いち", "に", "さん" };
string[] ary2 = new string[] { "いち", "に", "さん" };

//結果を格納する変数
bool isEqual = true;

if (object.ReferenceEquals(ary1, ary2))
{
    //同一のインスタンスの時は、同じとする
    isEqual = true;
}
else if (ary1 == null || ary2 == null
    || ary1.Length != ary2.Length)
{
    //どちらかがNULLか、要素数が異なる時は、同じではない
    isEqual = false;
}
else
{
    //1つ1つの要素が等しいかを調べる
    for (int i = 0; i < ary1.Length; i++)
    {
        //ary1の要素のEqualsメソッドで、ary2の要素と等しいか調べる
        if (!ary1[i].Equals(ary2[i]))
        {
            //1つでも等しくない要素があれば、同じではない
            isEqual = false;
            break;
        }
    }
}

//結果を表示する
if (isEqual)
{
    Console.WriteLine("2つの配列は等しいです");
}
else
{
    Console.WriteLine("2つの配列は等しくありません");
}

SequenceEqualメソッドを使用する方法

.NET Framework 3.5以降でLINQを使えるのであれば、Enumerable.SequenceEqualメソッドでできます。

VB.NET
コードを隠すコードを選択
'比較する2つの配列(コレクションでも可)
Dim ary1 As String() = New String() {"いち", "に", "さん"}
Dim ary2 As String() = New String() {"いち", "に", "さん"}

'2つのシーケンスが等しいか調べる
Dim isEqual As Boolean = ary1.SequenceEqual(ary2)

'結果を表示する
If isEqual Then
    Console.WriteLine("2つの配列は等しいです")
Else
    Console.WriteLine("2つの配列は等しくありません")
End If
C#
コードを隠すコードを選択
//比較する2つの配列(コレクションでも可)
string[] ary1 = new string[] { "いち", "に", "さん" };
string[] ary2 = new string[] { "いち", "に", "さん" };

//2つのシーケンスが等しいか調べる
bool isEqual = ary1.SequenceEqual(ary2);

//結果を表示する
if (isEqual)
{
    Console.WriteLine("2つの配列は等しいです");
}
else
{
    Console.WriteLine("2つの配列は等しくありません");
}

上の例のようにSequenceEqualメソッドを使用すると、Equalsメソッド(EqualityComparer"T".Default)を使って要素を比較します。要素を比較する方法を変更するには、SequenceEqualメソッドの2番目の引数にIEqualityComparerを指定します。IEqualityComparerについては「IEqualityComparerを指定して、一致する条件を変更する」で説明していますので、そちらをご覧ください。

IStructuralEquatable.Equalsメソッドを使用して、配列を比較する

.NET Framework 4.0以降で配列を比較するのであれば、IStructuralEquatable.Equalsメソッドを使う方法もあります。

このメソッドも2番目の引数にIEqualityComparerを渡して、要素を比較する方法を指定します。下の例のように、ここにStructuralComparisons.StructuralEqualityComparerを指定すると、要素をIStructuralEquatableにキャストできる場合はIStructuralEquatable.Equalsで、できない場合は要素のEqualsで比較します。つまり、例えば要素が配列だった場合は、その配列内の要素も比較されます。

VB.NET
コードを隠すコードを選択
'Imports System.Collections.Generic

'比較する2つの配列
Dim ary1 As String() = New String() {"いち", "に", "さん"}
Dim ary2 As String() = New String() {"いち", "に", "さん"}

'2つの配列が等しいか調べる
Dim isEqual As Boolean = DirectCast(ary1, IStructuralEquatable).Equals( _
    ary2, StructuralComparisons.StructuralEqualityComparer)

'結果を表示する
If isEqual Then
    Console.WriteLine("2つの配列は等しいです")
Else
    Console.WriteLine("2つの配列は等しくありません")
End If
C#
コードを隠すコードを選択
//using System.Collections;

//比較する2つの配列
string[] ary1 = new string[] { "いち", "に", "さん" };
string[] ary2 = new string[] { "いち", "に", "さん" };

//2つの配列が等しいか調べる
bool isEqual = ((IStructuralEquatable)ary1).Equals(ary2,
    StructuralComparisons.StructuralEqualityComparer);

//結果を表示する
if (isEqual)
{
    Console.WriteLine("2つの配列は等しいです");
}
else
{
    Console.WriteLine("2つの配列は等しくありません");
}

vjslib.dllのArrays.equalsメソッドを使用して、配列を比較する

J#のライブラリ「vjslib.dll」にはArrays.equalsメソッドというものがあり、これを使って2つの配列を比較できます。 Arrays.equalsメソッドには幾つかのオーバーロードがあり、Byte、Int16、Int32、Int64、Single、Double、Boolean、Char、Object型の配列を比較できます。なおこの方法は32ビット環境でのみ使用できるようです。

もしvjslib.dllがなければ、Microsoft Visual J# 再頒布可能パッケージをインストールする必要があります。

以下のサンプルを実行するには、参照設定に「vjslib.dll」を追加する必要があります。

VB.NET
コードを隠すコードを選択
'比較する2つの配列
Dim ary1 As Integer() = New Integer() {0, 1, 2, 3}
Dim ary2 As Integer() = New Integer() {0, 1, 2, 3}

'2つの配列が等しいか調べる
Dim isEqual As Boolean = java.util.Arrays.equals(ary1, ary2)

'結果を表示する
If isEqual Then
    Console.WriteLine("2つの配列は等しいです")
Else
    Console.WriteLine("2つの配列は等しくありません")
End If
C#
コードを隠すコードを選択
//比較する2つの配列
int[] ary1 = new int[] { 0, 1, 2, 3 };
int[] ary2 = new int[] { 0, 1, 2, 3 };

//2つの配列が等しいか調べる
bool isEqual = java.util.Arrays.equals(ary1, ary2);

//結果を表示する
if (isEqual)
{
    Console.WriteLine("2つの配列は等しいです");
}
else
{
    Console.WriteLine("2つの配列は等しくありません");
}

アンセーフコードを使用して、バイト型配列を比較する

バイト型配列を比較する場合は、さらに高速な方法があります。その一つが、アンセーフコードを使用する方法です。

Uhuru Mkate: Byte Array Comparison Benchmarks」に多くの方法が紹介されていますが、その中で「unsafeLongCompare」とされている方法(元記事は、「performance - C# byte[] comparison without bound checks - Stack Overflow」)を以下に紹介させていただきます。

なおアンセーフコードはVB.NETではサポートされていません。また、プロジェクトのプロパティで「ビルド」-「アンセーフコードの許可」を有効にする必要があります。

C#
コードを隠すコードを選択
public static bool ArrayCompare64(byte[] a, byte[] b)
{
    if (object.ReferenceEquals(a, b))
    {
        return true;
    }
    if (a == null || b == null || a.Length != b.Length)
    {
        return false;
    }

    int len = a.Length;
    unsafe
    {
        fixed (byte* ap = a, bp = b)
        {
            long* alp = (long*)ap;
            long* blp = (long*)bp;
            for (; len >= 8; len -= 8)
            {
                if (*alp != *blp)
                {
                    return false;
                }
                alp++;
                blp++;
            }
            byte* ap2 = (byte*)alp;
            byte* bp2 = (byte*)blp;
            for (; len > 0; len--)
            {
                if (*ap2 != *bp2)
                {
                    return false;
                }
                ap2++;
                bp2++;
            }
        }
    }
    return true;
}

msvcrt.dllのmemcmpを使用して、バイト型配列を比較する

同じくバイト型配列を高速で比較する方法として、Microsoft Visual C ランタイムライブラリ(msvcrt.dll)のmemcmpを使う方法もあります。

VB.NET
コードを隠すコードを選択
<System.Runtime.InteropServices.DllImport("msvcrt.dll", _
    CallingConvention:=System.Runtime.InteropServices.CallingConvention.Cdecl)> _
Private Shared Function memcmp(b1 As Byte(), b2 As Byte(), count As UIntPtr) _
    As Integer
End Function

Public Shared Function ByteArrayCompare(a As Byte(), b As Byte()) As Boolean
    If Object.ReferenceEquals(a, b) Then
        Return True
    End If
    If a Is Nothing OrElse b Is Nothing OrElse a.Length <> b.Length Then
        Return False
    End If

    Return memcmp(a, b, New UIntPtr(CUInt(a.Length))) = 0
End Function
C#
コードを隠すコードを選択
[System.Runtime.InteropServices.DllImport("msvcrt.dll",
    CallingConvention = System.Runtime.InteropServices.CallingConvention.Cdecl)]
private static extern int memcmp(byte[] b1, byte[] b2, UIntPtr count);

public static bool ByteArrayCompare(byte[] a, byte[] b)
{
    if (object.ReferenceEquals(a, b))
    {
        return true;
    }
    if (a == null || b == null || a.Length != b.Length)
    {
        return false;
    }

    return memcmp(a, b, new UIntPtr((uint)a.Length)) == 0;
}

どの方法が一番速いか?

Uhuru Mkate: Byte Array Comparison Benchmarks」などによると、IStructuralEquatableを使った方法はかなり遅く、SequenceEqualもFor文で単純に比較する方法より遅いようです。バイト型配列であれば、アンセーフコードとmemcmpの方法は共に同じ位高速のようです。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。