VBAを用いた集合の理解 ⑤ 差集合と補集合

先日から高校数学の学び直しとして、VBAを用いた集合の理解に挑戦している。
infoment.hatenablog.com
今日も、先日の続きから。
f:id:Infoment:20210524221721p:plain

今回は、集合Bから集合Aの要素を除いた差を求めてみる。

差集合(B-A)

作戦は至ってシンプル。
集合Bに相当する配列Bから、集合Aに相当する配列Aの要素を除去するだけ。

  1. 配列Bの要素をキー情報とした辞書を作成。
  2. 配列Aの要素が辞書に存在する場合、辞書から同キーを取り除く。
  3. 残ったキーを配列として取得。

今回も結果をソートしたので、こちらを再掲。

Public Function SortArray(ByVal source_array As Variant, _
                       Optional sort_order As Excel.XlSortOrder = xlAscending) As Variant
                       
    Dim aryList As Object
    Set aryList = CreateObject("System.Collections.ArrayList")
    Dim s As Variant
    
        For Each s In source_array
            Call aryList.Add(s)
        Next
    
        Select Case sort_order
            Case xlAscending
                ' 昇順でソート。
                Call aryList.Sort
            Case xlDescending
                ' 昇順でソートののち、降順へ反転。
                Call aryList.Sort
                Call aryList.Reverse
        End Select
    
        SortArray = aryList.ToArray
        
End Function

差集合を得る関数がこちら。

' 集合Aと集合Bの差集合を取得。
' B-A
' ※配列Aと配列Bの次元数に関わらず、AまたはBの要素全てを
'  一次元配列に格納する。
' ※AまたはB内で重複していた要素は、この処理により重複除去される。
Public Function GetDifferenceSet(ByVal set_A As Variant, _
                                 ByVal set_B As Variant, _
                              Optional check_subset As Boolean = True, _
                              Optional sort_order As Excel.XlSortOrder) As Variant

        If Not IsArray(set_A) Or Not IsArray(set_B) Then Exit Function
        
        ' 配列Aが配列Bに内包されているか確認。
        ' これを不問とする場合、配列Aが配列Bに存在しない要素を含んでいても関係なく、
        ' 単に配列Aと配列Bとに共に存在する要素を引くことになる。
        If check_subset Then
            If Not IsSubset(set_A, set_B) Then
                GetDifferenceSet = EmptySet
                Exit Function
            End If
        End If
                
    Dim a As Variant
    Dim Dict As Object
    Set Dict = CreateObject("Scripting.Dictionary")
    
        ' まず、集合Bの要素を全て辞書に登録する。
        ' 次いで、集合Aの要素が辞書に存在する場合、
        ' 辞書から除去していく。
        ' 最終的に残った辞書のキーが、B-Aとなる。
        For Each a In set_B
            ' 必要なのはkey情報のみ。item情報は不問のため、
            ' 何でもよい。今回は簡単のため「1」をセット。
            Dict(a) = 1
        Next
        
        For Each a In set_A
            If Dict.Exists(a) Then
                Dict.Remove a
            End If
        Next
        
        ' 辞書に何も登録されていない場合、差集合は存在しない。
        If Dict.Count = 0 Then
            GetDifferenceSet = EmptySet
            Exit Function
        End If

    Dim arr As Variant
        arr = Dict.keys
        If sort_order <> 0 Then
            arr = SortArray(arr, sort_order)
        End If
        
        GetDifferenceSet = arr
        
End Function

なお、集合Bが全体集合である場合、B-Aは補集合となる(=じゃない方?)。

' 全体集合
Public UniversalSet As Variant
' 集合Aの補集合を取得。
' 上記差集合における集合Bが、特に全体集合Uであるとき。
' U-A
' ※配列Aと配列Uの次元数に関わらず、AまたはUの要素全てを
'  一次元配列に格納する。
' ※AまたはU内で重複していた要素は、この処理により重複除去される。
Public Function GetComplementarySet(ByVal set_A As Variant, _
                                 Optional sort_order As Excel.XlSortOrder) As Variant

    GetComplementarySet = GetDifferenceSet(set_A, UniversalSet, sort_order)
        
End Function

結果の確認

それでは、簡単なテストを一つ。

Sub test()

    Dim MS As VBAProject.MathSet
    Set MS = New VBAProject.MathSet
        
    Dim 果物1 As Variant
        果物1 = Array("りんご", "みかん", "ばなな")
    Dim 果物2 As Variant
        果物2 = Array("みかん")
                
        Debug.Print Join(MS.GetDifferenceSet(果物2, 果物1), ", ")

End Sub

f:id:Infoment:20210524223033p:plain

上手くいったように見えるが、配列Aが配列Bに含まれるか否かを不問とする引数(check_subset)を設けている時点で、数学的にNGかもしれない。

でも、これができると後々便利なので、大目に見てもらえると有難いです。

参考まで。