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

2つの配列(あるいはコレクション)の片方だけに存在する要素を抽出する(対称差を取得する)

ここでは、例えば{ 1, 2, 3 }と{ 2, 3, 4 }という2つの配列から{ 1, 4 }という配列を作成するように、2つの配列(あるいはコレクション)の片方だけに存在する要素を抜き出す方法を紹介します。つまり、対称差を取得する方法ということになります。

ここで紹介しているコードは配列を例にしていますが、コレクションでも同じようにできます。

For文を使用する方法

まずは、何も考えずにFor文を使って行う方法です。以下の例では、For文を使って配列の要素を列挙し、もう一方の配列に存在していない要素だけをコレクションに追加しています。

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

'どちらかの配列にしか含まれない要素を覚えておくためのコレクション
'.NET Framework 2.0以降ならば、List(Of Integer)を使った方が良い
Dim newList As New System.Collections.ArrayList(ary1.Length + ary2.Length)

'ary1に含まれていてary2に含まれていない要素を追加する
Dim i As Integer
For Each i In ary1
    If Array.IndexOf(ary2, i) < 0 Then
        newList.Add(i)
    End If
Next

'ary2に含まれていてary1に含まれていない要素を追加する
For Each i In ary2
    If Array.IndexOf(ary1, i) < 0 Then
        newList.Add(i)
    End If
Next

'結果を配列に変換する
Dim newArray As Integer() = _
    DirectCast(newList.ToArray(GetType(Integer)), Integer())

'newArrayは{ 1, 1, 4, 4 }となる
C#
コードを隠すコードを選択
//比較する2つの配列
int[] ary1 = new int[] { 1, 1, 2, 3, 3 };
int[] ary2 = new int[] { 2, 2, 3, 4, 4 };

//どちらかの配列にしか含まれない要素を覚えておくためのコレクション
//.NET Framework 2.0以降ならば、List<int>を使った方が良い
System.Collections.ArrayList newList =
    new System.Collections.ArrayList(ary1.Length + ary2.Length);

//ary1に含まれていてary2に含まれていない要素を追加する
foreach (int i in ary1)
{
    if (Array.IndexOf(ary2, i) < 0)
    {
        newList.Add(i);
    }
}

//ary2に含まれていてary1に含まれていない要素を追加する
foreach (int i in ary2)
{
    if (Array.IndexOf(ary1, i) < 0)
    {
        newList.Add(i);
    }
}

//結果を配列に変換する
int[] newArray = (int[])newList.ToArray(typeof(int));

//newArrayは{ 1, 1, 4, 4 }となる

HashSet.SymmetricExceptWithメソッドを使用する方法

.NET Framework 3.5以降であれば、HashSet.SymmetricExceptWithメソッドを使うことができます。HashSetは同じ要素の重複を許さないコレクションですので、重複した要素は取り除かれます。

このメソッドは、HashSet同士で使用するのであればO(n)操作ですので、かなりパフォーマンスが良いです。それ以外でもO(n + m)操作ですので、通常よりはパフォーマンスが良いです。

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

'比較する2つの配列(コレクションでも可)
Dim ary1 As Integer() = New Integer() {1, 1, 2, 3, 3}
Dim ary2 As Integer() = New Integer() {2, 2, 3, 4, 4}

'HashSetに変換する
Dim hs1 As New HashSet(Of Integer)(ary1)
Dim hs2 As New HashSet(Of Integer)(ary2)

'片方のHashSetにしか存在しない要素だけにする
hs1.SymmetricExceptWith(hs2)

'配列に変換する
Dim resultArray As Integer() = New Integer(hs1.Count - 1) {}
hs1.CopyTo(resultArray, 0)

'resultArrayは{ 1, 4 }となる
C#
コードを隠すコードを選択
//using System.Collections.Generic;

//比較する2つの配列(コレクションでも可)
int[] ary1 = new int[] { 1, 1, 2, 3, 3 };
int[] ary2 = new int[] { 2, 2, 3, 4, 4 };

//HashSetに変換する
HashSet<int> hs1 = new HashSet<int>(ary1);
HashSet<int> hs2 = new HashSet<int>(ary2);

//片方のHashSetにしか存在しない要素だけにする
hs1.SymmetricExceptWith(hs2);

//配列に変換する
int[] resultArray = new int[hs1.Count];
hs1.CopyTo(resultArray, 0);

//resultArrayは{ 1, 4 }となる

LINQを使用する方法

.NET Framework 3.5以降であれば、LINQを使う方法もあります。しかし一発で行う方法はなく、幾つかのメソッドを使わなければなりません。

集合PとQとの対称差 Δ は、和集合 ∪、積集合 ∩、差集合 - を使って表すと、

P Δ Q = ( P ∪ Q ) - ( P ∩ Q )

とできます。これに従うと、次のようにして対称差を取得できます。

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

'比較する2つの配列(コレクションでも可)
Dim ary1 As Integer() = New Integer() {1, 1, 2, 3, 3}
Dim ary2 As Integer() = New Integer() {2, 2, 3, 4, 4}

'ary1とary2の和集合と積集合の差を取る
Dim resultArray As Integer() = _
    ary1.Union(ary2).Except(ary1.Intersect(ary2)).ToArray()

'resultArrayは{ 1, 4 }となる
C#
コードを隠すコードを選択
//using System.Linq;

//比較する2つの配列(コレクションでも可)
int[] ary1 = new int[] { 1, 1, 2, 3, 3 };
int[] ary2 = new int[] { 2, 2, 3, 4, 4 };

//ary1とary2の和集合と積集合の差を取る
int[] resultArray = ary1.Union(ary2).Except(ary1.Intersect(ary2)).ToArray();

//resultArrayは{ 1, 4 }となる

また、

P Δ Q = ( P - Q ) ∪ ( Q - P )

と表すこともできますので、これに従うと次のようになります。

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

'比較する2つの配列(コレクションでも可)
Dim ary1 As Integer() = New Integer() {1, 1, 2, 3, 3}
Dim ary2 As Integer() = New Integer() {2, 2, 3, 4, 4}

'ary1とary2の差とary2とary1の差を連結する
Dim resultArray As Integer() = _
    ary1.Except(ary2).Concat(ary2.Except(ary1)).ToArray()

'resultArrayは{ 1, 4 }となる
C#
コードを隠すコードを選択
//using System.Linq;

//比較する2つの配列(コレクションでも可)
int[] ary1 = new int[] { 1, 1, 2, 3, 3 };
int[] ary2 = new int[] { 2, 2, 3, 4, 4 };

//ary1とary2の差とary2とary1の差を連結する
int[] resultArray = ary1.Except(ary2).Concat(ary2.Except(ary1)).ToArray();

//resultArrayは{ 1, 4 }となる

なお、和集合を取得する方法は「要素が重複しないようにして、複数の配列(またはコレクション)をマージする(和集合を取得する)」、積集合を取得する方法は「2つの配列(またはコレクション)の両方に存在する要素を抽出する(積集合、共通部分を取得する)」、差集合を取得する方法は「ある配列(またはコレクション)から、別の配列に存在しない要素だけを抽出する(差集合を作成する)」で説明しています。

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

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