ここでは、例えば{ 1, 2, 3 }と{ 2, 3, 4 }という2つの配列から{ 1, 4 }という配列を作成するように、2つの配列(あるいはコレクション)の片方だけに存在する要素を抜き出す方法を紹介します。つまり、対称差を取得する方法ということになります。
ここで紹介しているコードは配列を例にしていますが、コレクションでも同じようにできます。
まずは、何も考えずにFor文を使って行う方法です。以下の例では、For文を使って配列の要素を列挙し、もう一方の配列に存在していない要素だけをコレクションに追加しています。
'比較する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 }となる
//比較する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 }となる
.NET Framework 3.5以降であれば、HashSet.SymmetricExceptWithメソッドを使うことができます。HashSetは同じ要素の重複を許さないコレクションですので、重複した要素は取り除かれます。
このメソッドは、HashSet同士で使用するのであればO(n)操作ですので、かなりパフォーマンスが良いです。それ以外でもO(n + m)操作ですので、通常よりはパフォーマンスが良いです。
'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 }となる
//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 }となる
.NET Framework 3.5以降であれば、LINQを使う方法もあります。しかし一発で行う方法はなく、幾つかのメソッドを使わなければなりません。
集合PとQとの対称差 Δ は、和集合 ∪、積集合 ∩、差集合 - を使って表すと、
P Δ Q = ( P ∪ Q ) - ( P ∩ Q )
とできます。これに従うと、次のようにして対称差を取得できます。
'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 }となる
//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 )
と表すこともできますので、これに従うと次のようになります。
'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 }となる
//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つの配列(またはコレクション)の両方に存在する要素を抽出する(積集合、共通部分を取得する)」、差集合を取得する方法は「ある配列(またはコレクション)から、別の配列に存在しない要素だけを抽出する(差集合を作成する)」で説明しています。