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

ある配列(またはコレクション)から、別の配列に存在しない要素だけを抽出する(差集合を作成する)

例えば、2つの配列{ 1, 2, 3 }と{ 3, 4, 5 }があった時、はじめの配列に含まれていて2番目の配列に含まれていない要素である 1 と 2 を抽出して、{ 1, 2 }という配列を作成するように、指定した配列(またはコレクション)から、別の配列に含まれていない要素だけを抜き出す方法(つまり、差集合を取得する方法)を紹介します。また、指定した配列から、別の配列に含まれているすべての要素を削除する方法も紹介します。

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

For文を使用する方法

説明するまでもないとは思いますが、まずはFor文を使った最も基本的な方法から紹介します。

以下の例では、For文を使って基になる配列の要素を列挙して、比較する配列に存在していない要素だけをコレクションに追加しています。

VB.NET
コードを隠すコードを選択
'基になる配列
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 }となる
C#
コードを隠すコードを選択
//基になる配列
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 }となる

Enumerable.Exceptメソッドを使用する方法

.NET Framework 3.5以降でLinqを使えるのであれば、Enumerable.Exceptメソッドを使うと簡単です。

以下にExceptメソッドを使用した例を示します。この例で分かるように、前の例と違って、結果には重複した要素が含まれません。

VB.NET
コードを隠すコードを選択
'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 }となる
C#
コードを隠すコードを選択
//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 }となる

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

.NET Framework 3.5以降であれば、HashSet.ExceptWithメソッドを使うこともできます。このメソッドは、指定されたコレクションに存在する要素をすべてHashSetから削除します。このメソッドはO(n)操作ですので、パフォーマンスが高いです。

HashSetは同じ要素の重複を許しませんので、Enumerable.Exceptメソッドと同様、結果には重複した要素が含まれません。

VB.NET
コードを隠すコードを選択
'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 }となる
C#
コードを隠すコードを選択
//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.RemoveAllメソッドを使うと、指定された条件に合ったすべての要素をListから削除することができます。これを使って、比較するListのContainsメソッドがTrueを返す要素を削除するようにすれば、あるListから別のListに含まれているすべての要素を削除することができます。

なおListクラスは、.NET Framework 2.0以降で使用できます。

RemoveAllメソッドは配列では使用できませんが、配列をListに変換して使用することはできます。

VB.NET
コードを隠すコードを選択
'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 }となる
C#
コードを隠すコードを選択
//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以降で使用できます。

VB.NET
コードを隠すコードを選択
'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 }となる
C#
コードを隠すコードを選択
//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.RemoveWhereメソッドを使用する方法

基になるコレクションがHashSetクラスであれば、HashSet.RemoveWhereメソッドで同様のことができます。HashSetクラスは、.NET Framework 3.5以降で使用できます。

前述した通りHashSetは同じ要素の重複を許しませんので、結果は一意の要素になります。

VB.NET
コードを隠すコードを選択
'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 }となる
C#
コードを隠すコードを選択
//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 }となる

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

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