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

2つの配列(またはコレクション)の両方に存在する要素を抽出する(積集合、共通部分を取得する)

ここでは、例えば{ 1, 2, 3 }と{ 1, 4, 3 }という2つの配列から{ 1, 3 }という配列を作るというように、2つの配列(またはコレクション)の両方に含まれている要素のみを抜き出す方法を紹介します。つまり、積集合、共通部分を取得する方法ということになります。

For文を使用する方法

まずは最も基本的な、For文を使った方法から見てみましょう。以下の例では、For文を使って片方の配列の要素を列挙し、その要素がもう片方の配列に含まれているか調べ、もし含まれていれば新しく作成したコレクションに追加しています。

以下にその例を示します。

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

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

'ary1の要素を列挙する
Dim i As Integer
For Each i In ary1
    'ary2に含まれていて、newListにまだ含まれていない要素を探す
    If 0 <= Array.IndexOf(ary2, i) AndAlso Not newList.Contains(i) Then
        'newListに追加する
        newList.Add(i)
    End If
Next

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

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

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

//ary1の要素を列挙する
foreach (int i in ary1)
{
    //ary2に含まれていて、newListにまだ含まれていない要素を探す
    if (0 <= Array.IndexOf(ary2, i) &&
        !newList.Contains(i))
    {
        //newListに追加する
        newList.Add(i);
    }
}

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

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

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

.NET Framework 2.0以降であれば、Array.FindAllメソッドList(T).FindAllメソッドを使って抽出する方法があります。FindAllメソッドについて詳しくは、「配列やコレクションのフィルタ処理を行う(条件に合う要素を抜き出す)」で説明しています。

以下に、2つの配列とListで共通の要素を探す簡単な例を示します。この例では、抽出する条件を片方の配列のContainsメソッドがTrueを返す時としていますので、抽出結果の配列には同じ要素が複数含まれる可能性があります。

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

'比較する2つの配列
Dim ary1 As Integer() = New Integer() {1, 2, 3, 3}
Dim ary2 As Integer() = New Integer() {1, 3, 4, 3}

'ary1の要素の内、ary2.ContainsがTrueとなる要素を取得する
Dim newArray As Integer() = _
    Array.FindAll(ary1, AddressOf DirectCast(ary2, IList(Of Integer)).Contains)
'newListは{ 1, 3, 3 }となる

'比較する2つのList
Dim list1 As New List(Of Integer)(ary1)
Dim list2 As New List(Of Integer)(ary2)

'list1の要素の内、list2.ContainsがTrueとなる要素を取得する
Dim newList As List(Of Integer) = list1.FindAll(AddressOf list2.Contains)
'newListは{ 1, 3, 3 }となる
C#
コードを隠すコードを選択
//using System.Collections.Generic;

//比較する2つの配列
int[] ary1 = new int[] { 1, 2, 3, 3 };
int[] ary2 = new int[] { 1, 3, 4, 3 };

//ary1の要素の内、ary2.ContainsがTrueとなる要素を取得する
int[] newArray = Array.FindAll(ary1, ((IList<int>)ary2).Contains);
//newListは{ 1, 3, 3 }となる

//比較する2つのList
List<int> list1 = new List<int>(ary1);
List<int> list2 = new List<int>(ary2);

//list1の要素の内、list2.ContainsがTrueとなる要素を取得する
List<int> newList = list1.FindAll(list2.Contains);
//newListは{ 1, 3, 3 }となる

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

'比較する2つの配列
Dim ary1 As Integer() = New Integer() {1, 2, 3, 3}
Dim ary2 As Integer() = New Integer() {1, 3, 4, 3}

'ary2をHashSetに変換する
Dim hs As New HashSet(Of Integer)(ary2)
'ary1の要素の内、hs.ContainsがTrueとなる要素を取得する
Dim newArray As Integer() = Array.FindAll(ary1, AddressOf hs.Contains)
C#
コードを隠すコードを選択
//using System.Collections.Generic;

//比較する2つの配列
int[] ary1 = new int[] { 1, 2, 3, 3 };
int[] ary2 = new int[] { 1, 3, 4, 3 };

//ary2をHashSetに変換する
HashSet<int> hs = new HashSet<int>(ary2);
//ary1の要素の内、hs.ContainsがTrueとなる要素を取得する
int[] newArray = Array.FindAll(ary1, hs.Contains);

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

.NET Framework 3.5以降でLinqを使えるのであれば、Enumerable.Intersectメソッドを使って積集合のシーケンスを作成できます。

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

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

'2つの配列の積集合を作成し、配列に変換する
Dim newArray As Integer() = ary1.Intersect(ary2).ToArray()

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

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

//2つの配列の積集合を作成し、配列に変換する
int[] newArray = ary1.Intersect(ary2).ToArray();

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

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

.NET Framework 3.5以降では、HashSet.IntersectWithメソッドを使うこともできます。このメソッドは比較する両方のコレクションがHashSetの場合、O(n)操作になりますので、パフォーマンスがかなり良いです。HashSet以外との比較でもO(n + m)操作ですので、通常よりはパフォーマンスが良いです。

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

'比較する2つのHashSet
Dim hs1 As New HashSet(Of Integer)() From {1, 2, 3, 3}
Dim hs2 As New HashSet(Of Integer)() From {1, 3, 4, 3}

'両方のHashSetに含まれている要素だけにする
hs1.IntersectWith(hs2)

'配列に変換する
Dim newArray As Integer() = New Integer(hs1.Count - 1) {}
hs1.CopyTo(newArray)
C#
コードを隠すコードを選択
//using System.Collections.Generic;

//比較する2つのHashSet
HashSet<int> hs1 = new HashSet<int> { 1, 2, 3, 3 };
HashSet<int> hs2 = new HashSet<int> { 1, 3, 4, 3 };

//両方のHashSetに含まれている要素だけにする
hs1.IntersectWith(hs2);

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

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

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