VBAを用いた集合の理解 ② aを集合Aの要素として追加または除去

昨日から高校数学の学び直しとして、VBAを用いた集合の理解に挑戦している。
infoment.hatenablog.com

今日も、昨日の続きから。
f:id:Infoment:20210513230007p:plain

集合の理解といいつつ、二日目にして早くも逸脱する。なぜなら集合を配列で表したため、個人的に要素の追加および削除を機能として盛り込みたくなったから。

そこで前段として、まず「要素を持たない集合(=空集合)」を準備する。
空集合については、こちらを参照されたし。
manabitimes.jp

今回は集合を「配列」と同義に扱っているので、空集合は空配列とすれば
何かと都合がよい。

クラスモジュール(MathSet)

' 空集合(=空配列)
Public Property Get EmptySet() As Variant
    EmptySet = Array()
End Property

配列に要素を追加するとなれば、「どこに?」を決める必要がある。
特に多次元配列に於いては、どこに加えるにしてもややこしくなりそうだ。

そこで思い切って、要素の追加及び削除の過程で、配列は全て一次元配列に
変換することにした。今回は一旦すべての要素をコレクションに加えた上で、
一次元配列に変換する。

' コレクションから一次元配列を作成する。
Public Function ToArray(source_col As Collection) As Variant
    Dim arr() As Variant
    ReDim arr(1 To source_col.Count)
    Dim i As Long
        For i = 1 To source_col.Count
            arr(i) = source_col.Item(i)
        Next
    
        ToArray = arr
        
End Function

配列では、同じ要素を複数格納することがある。そこで今回は、重複の可否を
切り替えられるようにしてみた。なお、配列内に格納済の要素を追加する際、
要素の重複不可とする場合は、戻り値を空配列にすることとした。
(私的な決め事)。

' 指定文字列が、ある集合の要素であるか否かを返す。
' a ∈ A:aは集合Aの要素である。
Public Function IsElement(ByVal element As Variant, _
                          ByVal target_set As Variant, _
                       Optional LookAt As XlLookAt = xlWhole) As Boolean
        
    ' 集合ではないものが指定された場合、Falseを返す。
        If Not IsArray(target_set) Then Exit Function
    
    ' 配列内の値と順にelementを照合し、同じものがある時点でTrueを返す。
    ' 従って、この関数はtarget_set内で複数の同じ値が含まれる場合を除外しない。
    Dim a As Variant
        If LookAt = xlPart Then element = "*" & element & "*"
    
        For Each a In target_set
            If a Like element Then
                IsElement = True
                Exit Function
            End If
        Next
        
End Function
' 指定文字列を、ある集合に追加する。
' ※「ある集合」を強制的に1次元配列とし、その末尾に「指定文字列」を追加。
Public Function AddElement(ByVal element As Variant, _
                           ByVal target_set As Variant, _
                        Optional duplicable As Boolean = True) As Variant
    
        ' 指定文字列がすでに指定集合(指定配列)に含まれている場合。
        If IsElement(element, target_set) Then
            ' 要素の重複を不可とするならば、空集合(空配列)を返す。
            If Not duplicable Then
                AddElement = EmptySet
                Exit Function
            End If
        End If
        
    ' 上記以外の場合、いったん全てコレクションに格納ののち、一次元配列に
    ' 格納しなおす。
    Dim col As Collection
    Set col = New Collection
    Dim a As Variant
        For Each a In target_set
            col.Add a
        Next
        col.Add element
        
        AddElement = ToArray(col)
        
End Function
' 指定文字列を、ある集合から除去する。
' ※「ある集合」は強制的に、一次元配列に変換される。
' ※「ある集合」に指定文字列が複数ある場合、すべて除去する。
Public Function RemoveElement(ByVal element As Variant, _
                              ByVal target_set As Variant) As Variant
    
    ' 指定文字列が指定集合(指定配列)に含まれていない場合、
    ' 空集合(空配列)を返す。
    If Not IsElement(element, target_set) Then
        RemoveElement = EmptySet
        Exit Function
    End If
        
    ' 上記以外の場合、指定文字を除く配列を作成する。
    Dim col As Collection
    Set col = New Collection
    Dim a As Variant
        For Each a In target_set
            If a <> element Then
                col.Add a
            End If
        Next
        
        RemoveElement = ToArray(col)
        
End Function

それでは、テストしてみよう。

標準モジュール

Sub test()

    Dim MS As VBAProject.MathSet
    Set MS = New VBAProject.MathSet
        
    Dim 果物 As Variant
        果物 = Array("りんご", "みかん", "ばなな")
        
        ' "みかん"を除去。
        果物 = MS.RemoveElement("みかん", 果物)
        Debug.Print Join(果物, ";")
        
        ' "ぶどう"を追加。
        果物 = MS.AddElement("ぶどう", 果物)
        Debug.Print Join(果物, ";")
        
        ' "ばなな"を追加(重複)
        果物 = MS.AddElement("ばなな", 果物, True)
        Debug.Print Join(果物, ";")
              
        ' 更に"ばなな"を追加(重複不可)
        果物 = MS.AddElement("ばなな", 果物, False)
        Debug.Print Join(果物, ";")
        
End Sub

結果がこちら。最後の「ばなな追加」は、格納済みの「ばなな」との
重複不可により戻り値が空配列となって、イミディエイトウィンドウ
には何も表示されていない。
f:id:Infoment:20210513233106p:plain

次回は「部分集合」の判別に挑戦です。

参考まで。