Home > OS >  VBA: create & add elements to collections from a set range while ignoring duplicates
VBA: create & add elements to collections from a set range while ignoring duplicates

Time:12-01

Let's say I have two columns in an excel sheet. Column A's is an list of account numbers(integers). Column B consist of the name to the department that account number(Column A) is a assigned to.

Account# Department
123445 Executive
566334 AP
221310 AC
5456546 AP
156325 Marketing
558465 Grants

My intent is to create a collection for each department. Also each account number(Column A) should be added as an element to it's assigned department's collection.

My approach has been to try a for each loop to create an collection for each department(Column B). Problem is, the sheet I'm working with has 383 account numbers and there are only 11 different departments, so my method produces a lot of duplicate collections instead of adding the account number to the already existing collection.

Please advise a solution or a better way to approach task.

CodePudding user response:

Dictionary vs Collection

Useful Links

Why the Dictionary?

  • You cannot list the keys in a collection while you can in a dictionary:

    For Each Key In dict.Keys
        Debug.Print Key
    Next Key
    
  • You need a solution (function) that uses error handling to check if a key exists in a collection while in a dictionary you just do:

    If dict.Exists(Key) Then
    
  • You need to create a new object to add it to a collection while in a dictionary you can e.g., to add a new collection, just do:

    Set dict(Key) = New Collection
    
  • Unrelated to this case yet very important, e.g. when using a number (simple data type) as the item, you cannot modify its value in a collection while in a dictionary you can e.g. just do:

    dict(Key) = 25 ' modify
    dict(Key) = dict(Key)   1 ' add 1 to count
    dict(Key) = dict(Key)   25 ' add a value to sum
    

A Dictionary of Collections Solution

Option Explicit

Sub Dict2ColumnCollsTEST()
' Need the 'Dict2ColumnColls' functions.
    
    Const KeyColumn As Long = 2
    Const ItemColumn As Long = 1
    
    Dim ws As Worksheet: Set ws = ActiveSheet ' be more specific
    
    Dim rg As Range
    Dim rCount As Long
    
    ' Create a reference to the (relevant) range.
    With ws.Range("A1").CurrentRegion
        rCount = .Rows.Count
        If rCount = 1 Then Exit Sub ' nothing or only headers
        rCount = rCount - 1
        Set rg = .Resize(rCount, Application.Max(KeyColumn, ItemColumn)) _
            .Offset(1)
    End With
    
    ' Write the data from the range to an array.
    Dim Data As Variant: Data = rg.Value
    
    ' Write the data from the array to a dictionary (by calling the function).
    Dim dict As Object
    Set dict = Dict2ColumnColls(Data, KeyColumn, ItemColumn)
    If dict Is Nothing Then Exit Sub ' error in the function (Immediate window)
    
    ' Usage (Study) Examples
    
    Dim Key As Variant
    Dim SubItem As Variant
    
    Debug.Print "Print the Keys, Their Count, the Collection (Sub)Items " _
        & "and All of Their Typenames"
    For Each Key In dict.Keys
        Debug.Print "Key = " & Key, "Items.Count = " & dict(Key).Count, _
            TypeName(dict(Key))
        For Each SubItem In dict(Key)
           Debug.Print SubItem, TypeName(SubItem)
        Next SubItem
    Next Key
    
    Debug.Print "Access (Sub)Items by Using the Key"
    Debug.Print "dict(""Executive"")(1) = " & dict("Executive")(1)
    Debug.Print "dict(""AP"")(1) = " & dict("AP")(1)
    Debug.Print "dict(""AP"")(2) = " & dict("AP")(2)
    
    Debug.Print "Loop Through an Item ('Sub-Collection') Using the Key"
    Debug.Print "(For Each SubItem In coll(""AP"")...)"
    For Each SubItem In dict("AP")
        Debug.Print SubItem
    Next SubItem

End Sub

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose:      Returns a dictionary of collections from the values
'               of two columns of a 2D one-based array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function Dict2ColumnColls( _
    ByVal Data As Variant, _
    ByVal KeyColumn As Long, _
    ByVal ItemColumn As Long) _
As Object
' Needs the 'DoesCollectionKeyExist' function.
    Const ProcName As String = "Dict2ColumnColls"
    'On Error GoTo ClearError
    
    Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
    dict.CompareMode = vbTextCompare
    
    Dim r As Long
    Dim sKey As String
    
    For r = 1 To UBound(Data, 1)
        sKey = CStr(Data(r, KeyColumn))
        If Len(sKey) > 0 Then ' array element is not empty
            If Not dict.Exists(sKey) Then ' key doesn't exist
                Set dict(sKey) = New Collection
            'Else ' key exists
            End If
            dict(sKey).Add Data(r, ItemColumn)
        'Else ' array element is empty
        End If
    Next r

    Set Dict2ColumnColls = dict

ProcExit:
    Exit Function
ClearError:
    Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
              & "    " & "Run-time error '" & Err.Number & "':" & vbLf _
              & "    " & Err.Description
    Resume ProcExit
End Function

A Collection of Collections Solution

Sub Coll2ColumnCollsTEST()
' Need the 'Coll2ColumnColls' and 'DoesCollectionKeyExist' functions.
    
    Const KeyColumn As Long = 2
    Const ItemColumn As Long = 1
    
    Dim ws As Worksheet: Set ws = ActiveSheet ' be more specific
    
    Dim rg As Range
    Dim rCount As Long
    
    ' Create a reference to the (relevant) range.
    With ws.Range("A1").CurrentRegion
        rCount = .Rows.Count
        If rCount = 1 Then Exit Sub ' nothing or only headers
        rCount = rCount - 1
        Set rg = .Resize(rCount, Application.Max(KeyColumn, ItemColumn)) _
            .Offset(1)
    End With
    
    ' Write the data from the range to an array.
    Dim Data As Variant: Data = rg.Value
    
    ' Write the data from the array to a collection (by calling the function).
    Dim coll As Collection
    Set coll = Coll2ColumnColls(Data, KeyColumn, ItemColumn)
    If coll Is Nothing Then Exit Sub ' error in the function (Immediate window)
    
    ' Usage (Study) Examples
    
    Dim Item As Variant
    Dim SubItem As Variant
    
    Debug.Print "Print the Sub-Items, All Counts and All Typenames" & vbLf _
        & "(no way to list the keys (""Item.Count""))"
    For Each Item In coll
        Debug.Print "Item.Count=" & Item.Count, TypeName(Item)
        For Each SubItem In Item
           Debug.Print SubItem, TypeName(SubItem)
        Next SubItem
    Next Item
    
    Debug.Print "Access the Sub-Items by Using the Key"
    Debug.Print "coll(""Executive"")(1) = " & coll("Executive")(1)
    Debug.Print "coll(""AP"")(1) = " & coll("AP")(1)
    Debug.Print "coll(""AP"")(2) = " & coll("AP")(2)
    
    If DoesCollectionKeyExist(coll, "AP") Then
        Debug.Print "Loop Through an Item ('Sub-Collection') Using the Key"
        Debug.Print "(For Each SubItem In coll(""AP"")...)"
        For Each SubItem In coll("AP")
            Debug.Print SubItem
        Next SubItem
    End If

End Sub

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose:      Returns a collection of collections from the values
'               of two columns of a 2D one-based array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function Coll2ColumnColls( _
    ByVal Data As Variant, _
    ByVal KeyColumn As Long, _
    ByVal ItemColumn As Long) _
As Collection
' Needs the 'DoesCollectionKeyExist' function.
    Const ProcName As String = "Coll2ColumnColls"
    On Error GoTo ClearError
    
    Dim coll As Collection: Set coll = New Collection
    
    Dim subcoll As Collection
    Dim r As Long
    Dim sKey As String
    
    For r = 1 To UBound(Data, 1)
        sKey = CStr(Data(r, KeyColumn))
        If Len(sKey) > 0 Then ' array element is not empty
            If Not DoesCollectionKeyExist(coll, sKey) Then ' key doesn't exist
                Set subcoll = New Collection
                coll.Add subcoll, sKey
            'Else ' key exists
            End If
            coll(sKey).Add Data(r, ItemColumn)
        'Else ' array element is empty
        End If
    Next r

    Set Coll2ColumnColls = coll

ProcExit:
    Exit Function
ClearError:
    Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
              & "    " & "Run-time error '" & Err.Number & "':" & vbLf _
              & "    " & Err.Description
    Resume ProcExit
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose:      Returns a boolean indicating whether a key of a collection
'               exists.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function DoesCollectionKeyExist( _
    ByVal coll As Collection, _
    ByVal Key As String) _
As Boolean
    On Error GoTo ClearError
    IsObject (coll(Key))
    DoesCollectionKeyExist = True
ProcExit:
    Exit Function
ClearError:
    Resume ProcExit
End Function
  • Related