例えば、2つの配列{ 1, 2, 3 }と{ 3, 4, 5 }があった時、はじめの配列に含まれていて2番目の配列に含まれていない要素である 1 と 2 を抽出して、{ 1, 2 }という配列を作成するように、指定した配列(またはコレクション)から、別の配列に含まれていない要素だけを抜き出す方法(つまり、差集合を取得する方法)を紹介します。また、指定した配列から、別の配列に含まれているすべての要素を削除する方法も紹介します。
ここで紹介しているコードの多くは配列を例にしていますが、コレクションでも同じようにできます。
説明するまでもないとは思いますが、まずはFor文を使った最も基本的な方法から紹介します。
以下の例では、For文を使って基になる配列の要素を列挙して、比較する配列に存在していない要素だけをコレクションに追加しています。
'基になる配列 Dim ary1 As Integer() = New Integer() {1, 2, 3, 1, 3} 'この配列に存在しない要素を抜き出す Dim ary2 As Integer() = New Integer() {3, 4, 5} '結果を入れるコレクション '.NET Framework 2.0以降ならば、List(Of Integer)を使った方が良い Dim resultList As New System.Collections.ArrayList(ary1.Length) Dim i As Integer For Each i In ary1 'ary2に含まれていないか確認する If Array.IndexOf(ary2, i) < 0 Then '含まれていなければ、リストに加える resultList.Add(i) End If Next '結果を配列に変換する Dim resultArray As Integer() = _ DirectCast(resultList.ToArray(GetType(Integer)), Integer()) 'resultArrayは{ 1, 2, 1 }となる
//基になる配列 int[] ary1 = new int[] { 1, 2, 3, 1, 3 }; //この配列に存在しない要素を抜き出す int[] ary2 = new int[] { 3, 4, 5 }; //結果を入れるコレクション //.NET Framework 2.0以降ならば、List<int>を使った方が良い System.Collections.ArrayList resultList = new System.Collections.ArrayList(ary1.Length); foreach (int i in ary1) { //ary2に含まれていないか確認する if (Array.IndexOf(ary2, i) < 0) { //含まれていなければ、リストに加える resultList.Add(i); } } //結果を配列に変換する int[] resultArray = (int[])resultList.ToArray(typeof(int)); //resultArrayは{ 1, 2, 1 }となる
.NET Framework 3.5以降でLinqを使えるのであれば、Enumerable.Exceptメソッドを使うと簡単です。
以下にExceptメソッドを使用した例を示します。この例で分かるように、前の例と違って、結果には重複した要素が含まれません。
'Imports System.Linq '基になる配列(コレクションでも可) Dim ary1 As Integer() = New Integer() {1, 2, 3, 1, 3} 'この配列(コレクションでも可)に存在しない要素を抜き出す Dim ary2 As Integer() = New Integer() {3, 4, 5} '2つのシーケンスの差集合を作成して、配列に変換する Dim resultArray As Integer() = ary1.Except(ary2).ToArray() 'resultArrayは{ 1, 2 }となる
//using System.Linq; //基になる配列(コレクションでも可) int[] ary1 = new int[] { 1, 2, 3, 1, 3 }; //この配列(コレクションでも可)に存在しない要素を抜き出す int[] ary2 = new int[] { 3, 4, 5 }; //2つのシーケンスの差集合を作成して、配列に変換する int[] resultArray = ary1.Except(ary2).ToArray(); //resultArrayは{ 1, 2 }となる
.NET Framework 3.5以降であれば、HashSet.ExceptWithメソッドを使うこともできます。このメソッドは、指定されたコレクションに存在する要素をすべてHashSetから削除します。このメソッドはO(n)操作ですので、パフォーマンスが高いです。
HashSetは同じ要素の重複を許しませんので、Enumerable.Exceptメソッドと同様、結果には重複した要素が含まれません。
'Imports System.Collections.Generic '基になる配列(コレクションでも可) Dim ary1 As Integer() = New Integer() {1, 2, 3, 1, 3} 'この配列(コレクションでも可)に存在しない要素を抜き出す Dim ary2 As Integer() = New Integer() {3, 4, 5} 'ary1からHashSetを作成する Dim hs1 As New HashSet(Of Integer)(ary1) 'ary2に含まれる要素を削除する hs1.ExceptWith(ary2) '配列に変換する Dim resultArray As Integer() = New Integer(hs1.Count - 1) {} hs1.CopyTo(resultArray, 0) 'resultArrayは{ 1, 2 }となる
//using System.Collections.Generic; //基になる配列(コレクションでも可) int[] ary1 = new int[] { 1, 2, 3, 1, 3 }; //この配列(コレクションでも可)に存在しない要素を抜き出す int[] ary2 = new int[] { 3, 4, 5 }; //ary1からHashSetを作成する HashSet<int> hs1 = new HashSet<int>(ary1); //ary2に含まれる要素を削除する hs1.ExceptWith(ary2); //配列に変換する int[] resultArray = new int[hs1.Count]; hs1.CopyTo(resultArray, 0); //resultArrayは{ 1, 2 }となる
List.RemoveAllメソッドを使うと、指定された条件に合ったすべての要素をListから削除することができます。これを使って、比較するListのContainsメソッドがTrueを返す要素を削除するようにすれば、あるListから別のListに含まれているすべての要素を削除することができます。
なおListクラスは、.NET Framework 2.0以降で使用できます。
RemoveAllメソッドは配列では使用できませんが、配列をListに変換して使用することはできます。
'Imports System.Collections.Generic '基になるList Dim list1 As New List(Of Integer)(New Integer() {1, 2, 3, 1, 3}) 'このListに存在する要素をlist1から削除する Dim list2 As New List(Of Integer)(New Integer() {3, 4, 5}) 'list2.ContainsがTrueを返す要素をlist1からすべて削除する list1.RemoveAll(AddressOf list2.Contains) 'list1は{ 1, 2, 1 }となる
//using System.Collections.Generic; //基になるList List<int> list1 = new List<int>(new int[] { 1, 2, 3, 1, 3 }); //このListに存在する要素をlist1から削除する List<int> list2 = new List<int>(new int[] { 3, 4, 5 }); //list2.ContainsがTrueを返す要素をlist1からすべて削除する list1.RemoveAll(list2.Contains); //list1は{ 1, 2, 1 }となる
「c# - Removal operation in List<string> - Stack Overflow」によると、List.ContainsメソッドがO(n)操作なのに対してHashSet.ContainsメソッドはO(1)操作のため、こちらを使用した方がパフォーマンスが良いということです。HashSetクラスは.NET Framework 3.5以降で使用できます。
'Imports System.Collections.Generic '基になるList Dim list1 As New List(Of Integer)(New Integer() {1, 2, 3, 1, 3}) 'このListに存在する要素をlist1から削除する Dim list2 As New List(Of Integer)(New Integer() {3, 4, 5}) 'list2からHashSetを作成する Dim hs2 As New HashSet(Of Integer)(list2) 'hs.ContainsがTrueを返す要素をlist1からすべて削除する list1.RemoveAll(AddressOf hs2.Contains) 'list1は{ 1, 2, 1 }となる
//using System.Collections.Generic; //基になるList List<int> list1 = new List<int>(new int[] { 1, 2, 3, 1, 3 }); //このListに存在する要素をlist1から削除する List<int> list2 = new List<int>(new int[] { 3, 4, 5 }); //list2からHashSetを作成する HashSet<int> hs2 = new HashSet<int>(list2); //hs.ContainsがTrueを返す要素をlist1からすべて削除する list1.RemoveAll(hs2.Contains); //list1は{ 1, 2, 1 }となる
基になるコレクションがHashSetクラスであれば、HashSet.RemoveWhereメソッドで同様のことができます。HashSetクラスは、.NET Framework 3.5以降で使用できます。
前述した通りHashSetは同じ要素の重複を許しませんので、結果は一意の要素になります。
'Imports System.Collections.Generic '基になるHashSet Dim hs1 As New HashSet(Of Integer)() From {1, 2, 3, 1, 3} 'このHashSetに存在しない要素を抜き出す Dim hs2 As New HashSet(Of Integer)() From {3, 4, 5} 'hs1からhs2.ContainsがTrueを返す要素を削除する hs1.RemoveWhere(AddressOf hs2.Contains) 'hs1は{ 1, 2 }となる
//using System.Collections.Generic; //基になるHashSet HashSet<int> hs1 = new HashSet<int> { 1, 2, 3, 1, 3 }; //このHashSetに存在しない要素を抜き出す HashSet<int> hs2 = new HashSet<int> { 3, 4, 5 }; //hs1からhs2.ContainsがTrueを返す要素を削除する hs1.RemoveWhere(hs2.Contains); //hs1は{ 1, 2 }となる