Employee Date
---------------------
Sonja 2008-06-13
Sonja 2008-03-28
Franklyn 2010-03-21
Adita 2009-05-03
Adita 2010-12-04
Tommy 2006-11-24
Sonja 2007-09-06
Tommy 2010-08-16
Kayin 2009-05-12
Adita 2008-06-18
For Each x In MyDictionary
MsgBox MyDictionary.Item(x)
Next
Dim MyDictionary As Scripting.Dictionary
Set MyDictionary = New Scripting.Dictionary
Dim MyDictionary As Object
Set MyDictionary = CreateObject("Scripting.Dictionary")
MyDictionary.Add Key, Item
MyDictionary.CompareMode = vbBinaryCompare 'case sensitive
MyDictionary.CompareMode = vbTextCompare 'case insensitive
With MyDictionary
.CompareMode = vbBinaryCompare
.Add "foo", "lower"
.Add "FOO", "UPPER"
End With
MsgBox "There are " & MyDictionary.Count & " items"
With MyDictionary
If Not .Exists(SomeKey) Then .Add SomeKey, SomeValue
End With
On Error Resume Next
x = MyCollection("foo")
If Err = 0 Then
MsgBox x
Else
Err.Clear
MsgBox "There is no value associated with 'foo'"
End If
On Error GoTo 0
With MyDictionary
.Item("SomeKey") = "foo"
MsgBox "The value for 'SomeKey' is '" & .Item("SomeKey")
' Returns a concatenated list of the Items:
MyArray = MyDictionary.Items
MsgBox Join(MyArray, ";")
MyDictionary.Key("SomeKey") = "SomeOtherKey"
' Returns a concatenated list of the keys:
MyArray = MyDictionary.Keys
MsgBox Join(MyArray, ";")
MyDictionary.Remove "SomeKey"
MyDictionary.RemoveAll
Sub FindDistinct()
' Uses late binding
Dim arr As Variant
Dim Counter As Long
Dim coll As Collection
Dim dic As Object
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
With ThisWorkbook.Worksheets("Data")
' Clear existing results, if applicable
.Range("b1").Resize(1, .Columns.Count - 1).EntireColumn.Delete
' Transfer contents of Column A to array for processing
arr = .Range("a2", .Cells(.Rows.Count, "a")).Value
' Create a Collection
Set coll = New Collection
' Loop through array and try to add each item in turn to the Collection. Adding an item
' where the key already exists generates an error; On Error Resume Next ignores the error
' (and thus the duplicate item does not get added to the Collection)
On Error Resume Next
For Counter = 1 To UBound(arr, 1)
coll.Add arr(Counter, 1), arr(Counter, 1)
Next
On Error GoTo 0
' Write results to the worksheet and destroy Collection
.Range("c1") = "Collection"
For Counter = 1 To coll.Count
.Cells(Counter + 1, "c") = coll(Counter)
Next
Set coll = Nothing
.Range("c1").Sort Key1:=.Range("c1"), Order1:=xlAscending, Header:=xlYes
' Create Dictionary object and loop through array of values. For each value, treat it as
' both an item and a key, and set the item value using that key. Where the key already
' existed, it will simply overwrite the existing item (albeit with the same value); where
' the key did not already exist, it will create the item/key pair. CompareMode set to
' make Dictionary case insensitive.
Set dic = CreateObject("Scripting.Dictionary")
dic.CompareMode = vbTextCompare
For Counter = 1 To UBound(arr, 1)
dic.Item(arr(Counter, 1)) = arr(Counter, 1)
Next
' Write results to worksheet. First, create an array of all items (we could also have used
' the keys here, as they are the same), then write the transposed array to the worksheet (to
' force the values down a column instead of across a row)
.Range("e1") = "Dictionary"
arr = dic.Items
.Range("e2").Resize(dic.Count, 1).Value = Application.Transpose(arr)
Set dic = Nothing
.Range("e1").Sort Key1:=.Range("e1"), Order1:=xlAscending, Header:=xlYes
' Resize columns as needed
.Columns.AutoFit
End With
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
MsgBox "Done"
End Sub
' Original code for adding to Dictionary:
For Counter = 1 To UBound(arr, 1)
dic.Item(arr(Counter, 1)) = arr(Counter, 1)
Next
' Alternate code for adding to Dictionary:
On Error Resume Next
For Counter = 1 To UBound(arr, 1)
dic.Add arr(Counter, 1), arr(Counter, 1)
Next
On Error GoTo 0
' Original code for adding to Dictionary:
For Counter = 1 To UBound(arr, 1)
dic.Item(arr(Counter, 1)) = arr(Counter, 1)
Next
' Alternate code for adding to Dictionary:
On Error Resume Next
For Counter = 1 To UBound(arr, 1)
If Not dic.Exists(arr(Counter, 1)) Then dic.Add arr(Counter, 1), arr(Counter, 1)
Next
On Error GoTo 0
Sub FindDistinctCaseSensitive()
' Uses late binding
Dim arr As Variant
Dim Counter As Long
Dim dic As Object
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
With ThisWorkbook.Worksheets("Data")
' Clear existing results, if applicable
.Range("b1").Resize(1, .Columns.Count - 1).EntireColumn.Delete
' Transfer contents of Column A to array for processing
arr = .Range("a2", .Cells(.Rows.Count, "a")).Value
' Create Dictionary object and loop through array of values. For each value, treat it as
' both an item and a key, and set the item value using that key. Where the key already
' existed, it will simply overwrite the existing item (albeit with the same value); where
' the key did not already exist, it will create the item/key pair. CompareMode set to
' make Dictionary case insensitive.
Set dic = CreateObject("Scripting.Dictionary")
dic.CompareMode = vbBinaryCompare
For Counter = 1 To UBound(arr, 1)
dic.Item(arr(Counter, 1)) = arr(Counter, 1)
Next
' Write results to worksheet. First, create an array of all items (we could also have used
' the keys here, as they are the same), then write the transposed array to the worksheet (to
' force the values down a column instead of across a row)
.Range("e1") = "Dictionary"
arr = dic.Items
.Range("e2").Resize(dic.Count, 1).Value = Application.Transpose(arr)
Set dic = Nothing
.Range("e1").Sort Key1:=.Range("e1"), Order1:=xlAscending, Header:=xlYes
' Resize columns as needed
.Columns.AutoFit
End With
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
MsgBox "Done"
End Sub
Sub MakeTheList()
Dim dic As Object
Dim dic2 As Object
Dim Contents As Variant
Dim ParentKeys As Variant
Dim ChildKeys As Variant
Dim r As Long, r2 As Long
Dim LastR As Long
Dim WriteStr As String
' Create "parent" Dictionary. Each key in the parent Dictionary will be a disntict
' Code value, and each item will be a "child" dictionary. For these "children"
' Dictionaries, each key will be a distinct Product value, and each item will be the
' sum of the Quantity column for that Code - Product combination
Set dic = CreateObject("Scripting.Dictionary")
dic.CompareMode = vbTextCompare
' Dump contents of worksheet into array
With ThisWorkbook.Worksheets("Data")
LastR = .Cells(.Rows.Count, 1).End(xlUp).Row
Contents = .Range("a2:c" & LastR).Value
End With
' Loop through the array
For r = 1 To UBound(Contents, 1)
' If the current code matches a key in the parent Dictionary, then set dic2 equal
' to the "child" Dictionary for that key
If dic.Exists(Contents(r, 1)) Then
Set dic2 = dic.Item(Contents(r, 1))
' If the current Product matches a key in the child Dictionary, then set the
' item for that key to the value of the item now plus the value of the current
' Quantity
If dic2.Exists(Contents(r, 2)) Then
dic2.Item(Contents(r, 2)) = dic2.Item(Contents(r, 2)) + Contents(r, 3)
' If the current Product does not match a key in the child Dictionary, then set
' add the key, with item being the amount of the current Quantity
Else
dic2.Add Contents(r, 2), Contents(r, 3)
End If
' If the current code does not match a key in the parent Dictionary, then instantiate
' dic2 as a new Dictionary, and add an item (Quantity) using the current Product as
' the Key. Then, add that child Dictionary as an item in the parent Dictionary, using
' the current Code as the key
Else
Set dic2 = CreateObject("Scripting.Dictionary")
dic2.CompareMode = vbTextCompare
dic2.Add Contents(r, 2), Contents(r, 3)
dic.Add Contents(r, 1), dic2
End If
Next
' Add a new worksheet for the results
Worksheets.Add
[a1:b1].Value = Array("Code", "Product - Qty")
' Dump the keys of the parent Dictionary in an array
ParentKeys = dic.Keys
' Write the parent Dictionary's keys (i.e., the distinct Code values) to the worksheet
[a2].Resize(UBound(ParentKeys) + 1, 1).Value = Application.Transpose(ParentKeys)
' Loop through the parent keys and retrieve each child Dictionary in turn
For r = 0 To UBound(ParentKeys)
Set dic2 = dic.Item(ParentKeys(r))
' Dump keys of child Dictionary into array and initialize WriteStr variable (which will
' hold concatenated products and summed Quantities
ChildKeys = dic2.Keys
WriteStr = ""
' Loop through child keys and retrieve summed Quantity value for that key. Build both
' of these into the WriteStr variable. Recall that Excel uses linefeed (ANSI 10) for
' in-cell line breaks
For r2 = 0 To dic2.Count - 1
WriteStr = WriteStr & Chr(10) & ChildKeys(r2) & " - " & dic2.Item(ChildKeys(r2))
Next
' Trim leading linefeed
WriteStr = Mid(WriteStr, 2)
' Write concatenated list to worksheet
Cells(r + 2, 2) = WriteStr
Next
' Sort and format return values
[a1].Sort Key1:=[a1], Order1:=xlAscending, Header:=xlYes
With [b:b]
.ColumnWidth = 40
.WrapText = True
End With
Columns.AutoFit
Rows.AutoFit
' Destroy object variables
Set dic2 = Nothing
Set dic = Nothing
MsgBox "Done"
End Sub
Sub MakeTheList()
' uses late binding
Dim Contents As Variant
Dim r As Long
Dim dic As Object
Dim TestEmp As String
Dim TestDate As Date
Dim Keys As Variant
Dim EmpColl As Collection
Dim DateColl As Collection
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
With ThisWorkbook.Worksheets("Data")
' Dump raw data into an array
.Range("c1").Resize(1, .Columns.Count - 2).EntireColumn.Delete
Contents = .Range("a2", .Cells(.Rows.Count, "B").End(xlUp)).Value
' Set up Collections. Because we need to track two data elements (employee name and date),
' and because we cannot retrieve the keys from a Collection, we must set up two Collections:
' one to track employee names and one to track dates (both using employee name as the key)
Set EmpColl = New Collection
Set DateColl = New Collection
' Turn on error handling. Collections have no explicit existence test, so the only way to
' know if an item exists is to try to add it or retrieve it, and then trap the error if it
' does not exist
On Error Resume Next
' Loop through the array
For r = 1 To UBound(Contents, 1)
TestEmp = Contents(r, 1)
TestDate = Contents(r, 2)
' Attempt to add the employee. If employee already exists in Collection, this will
' throw a handled error
EmpColl.Add TestEmp, TestEmp
If Err = 0 Then
' No error = new employee; add the test date also
DateColl.Add TestDate, TestEmp
Else
' Error = existing employee. Check the TestDate and see if it is earlier than the
' date we already have for the employee. If TestDate is earlier, remove the current
' date from the Collection and add the newer, earlier date (items within a Collection
' cannot be reassigned)
Err.Clear
If TestDate < DateColl(TestEmp) Then
DateColl.Remove TestEmp
DateColl.Add TestDate, TestEmp
End If
End If
Next
On Error GoTo 0
' Write the results to the worksheet
.Range("d1:e1").Value = Array("Collection" & Chr(10) & "Employee", "Date")
For r = 1 To EmpColl.Count
.Cells(r + 1, "d") = EmpColl(r)
.Cells(r + 1, "e") = DateColl(EmpColl(r))
Next
' Create Dictionary and loop through array
Set dic = CreateObject("Scripting.Dictionary")
For r = 1 To UBound(Contents, 1)
TestEmp = Contents(r, 1)
TestDate = Contents(r, 2)
' Test to see if current employee already exists
If dic.Exists(TestEmp) Then
' Employee exists; update date if applicable
If TestDate < dic.Item(TestEmp) Then dic.Item(TestEmp) = TestDate
Else
' Employee does not exist, so add employee and date
dic.Add TestEmp, TestDate
End If
Next
' Write results to worksheet
Keys = dic.Keys
.Range("g1:h1").Value = Array("Dictionary" & Chr(10) & "Employee", "Date")
.Range("g2").Resize(dic.Count, 1).Value = Application.Transpose(Keys)
For r = 0 To UBound(Keys)
.Cells(r + 2, "h") = dic.Item(Keys(r))
Next
' Format worksheet
.Range("d:d").WrapText = True
.Range("g:g").WrapText = True
.Range("e:e").NumberFormat = "yyyy-mm-dd"
.Range("h:h").NumberFormat = "yyyy-mm-dd"
.Range("d:d").EntireColumn.ColumnWidth = 20
.Range("g:g").EntireColumn.ColumnWidth = 20
.Rows.AutoFit
.Columns.AutoFit
.Range("d1").Sort Key1:=.Range("d1"), Order1:=xlAscending, Header:=xlYes
.Range("g1").Sort Key1:=.Range("g1"), Order1:=xlAscending, Header:=xlYes
End With
' Destroy objects
Set EmpColl = Nothing
Set DateColl = Nothing
Set dic = Nothing
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
MsgBox "Done"
End Sub
With MyDictionary
.Add 1, "number"
.Item("1") = "text"
End With
With MyDictionary
.Add CStr(1), "number"
.Item("1") = "text"
End With
' This code is for early binding; set reference to Microsoft Scripting Runtime library
Option Explicit
Private Sub CompareObjects()
' This sub actually runs the tests
Dim coll As Collection
Dim dic As Scripting.Dictionary
Dim Counter As Long
Dim RowCounter As Long
Dim ColCounter As Long
Dim StartCollection As Date
Dim EndCollection As Date
Dim StartDicAdd As Date
Dim EndDicAdd As Date
Dim StartDicItem As Date
Dim EndDicItem As Date
Dim StartDicExists As Date
Dim EndDicExists As Date
Dim arr As Variant
Dim Results() As Long
Dim oRow As ListRow
Const Iterations As Long = 20
Const NumRows As Long = 10000
Const NumCols As Long = 50
With ThisWorkbook.Worksheets("Test Data")
'Remove rows with distinct values written to them
.Range((NumRows + 1) & ":" & .Rows.Count).Delete
StartCollection = Now
' Repeat the test several times to smooth out random fluctuations
For Counter = 1 To Iterations
For ColCounter = 1 To NumCols
' Create Collection
Set coll = New Collection
' Array transfer to speed up process
arr = .Cells(1, ColCounter).Resize(NumRows, 1).Value
' If you attempt to add an item where the key already exists, an error results
On Error Resume Next
For RowCounter = 1 To NumRows
coll.Add arr(RowCounter, 1), CStr(arr(RowCounter, 1))
Next
On Error GoTo 0
' Build an array with the return values and write them to worksheet
ReDim Results(1 To coll.Count, 1 To 1)
For RowCounter = 1 To coll.Count
Results(RowCounter, 1) = coll(RowCounter)
Next
.Cells(NumRows + 2, ColCounter).Resize(UBound(arr, 1), 1).Value = Results
Set coll = Nothing
Next
Next
EndCollection = Now
.Range((NumRows + 1) & ":" & .Rows.Count).Delete
StartDicAdd = Now
For Counter = 1 To Iterations
For ColCounter = 1 To NumCols
' Create Dictionary
Set dic = New Scripting.Dictionary
arr = .Cells(1, ColCounter).Resize(NumRows, 1).Value
' If you attempt to add an item where the key already exists, an error results
On Error Resume Next
For RowCounter = 1 To NumRows
dic.Add arr(RowCounter, 1), arr(RowCounter, 1)
Next
On Error GoTo 0
' Put keys into an array, and write array values to worksheet
arr = dic.Keys
.Cells(NumRows + 2, ColCounter).Resize(dic.Count, 1).Value = Application.Transpose(arr)
Set dic = Nothing
Next
Next
EndDicAdd = Now
.Range((NumRows + 1) & ":" & .Rows.Count).Delete
StartDicItem = Now
For Counter = 1 To Iterations
For ColCounter = 1 To NumCols
Set dic = New Scripting.Dictionary
arr = .Cells(1, ColCounter).Resize(NumRows, 1).Value
' In this approach, we use the Item property's "implicit add" capability. Within
' the loop, the Item property either reassigns the item to the key (albeit to same value
' if the key already exists, or creates a new key/item pair if not
For RowCounter = 1 To NumRows
dic.Item(arr(RowCounter, 1)) = arr(RowCounter, 1)
Next
arr = dic.Keys
.Cells(NumRows + 2, ColCounter).Resize(dic.Count, 1).Value = Application.Transpose(arr)
Set dic = Nothing
Next
Next
EndDicItem = Now
.Range((NumRows + 1) & ":" & .Rows.Count).Delete
StartDicExists = Now
For Counter = 1 To Iterations
For ColCounter = 1 To NumCols
Set dic = New Scripting.Dictionary
arr = .Cells(1, ColCounter).Resize(NumRows, 1).Value
' In this approach, we test for existence first; if the key does not exist, we add the item
For RowCounter = 1 To NumRows
If Not dic.Exists(arr(RowCounter, 1)) Then
dic.Add arr(RowCounter, 1), arr(RowCounter, 1)
End If
Next
arr = dic.Keys
.Cells(NumRows + 2, ColCounter).Resize(dic.Count, 1).Value = Application.Transpose(arr)
Set dic = Nothing
Next
Next
EndDicExists = Now
End With
' For each of the four approaches, write a record to the Results worksheet
With ThisWorkbook.Worksheets("Results")
Set oRow = .ListObjects("Stats").ListRows.Add(AlwaysInsert:=True)
oRow.Range(1, 1) = StartCollection
oRow.Range(1, 2) = "Collection"
oRow.Range(1, 3) = EndCollection - StartCollection
Set oRow = .ListObjects("Stats").ListRows.Add(AlwaysInsert:=True)
oRow.Range(1, 1) = StartCollection
oRow.Range(1, 2) = "Dictionary Add"
oRow.Range(1, 3) = EndDicAdd - StartDicAdd
Set oRow = .ListObjects("Stats").ListRows.Add(AlwaysInsert:=True)
oRow.Range(1, 1) = StartCollection
oRow.Range(1, 2) = "Dictionary Item"
oRow.Range(1, 3) = EndDicItem - StartDicItem
Set oRow = .ListObjects("Stats").ListRows.Add(AlwaysInsert:=True)
oRow.Range(1, 1) = StartCollection
oRow.Range(1, 2) = "Dictionary Exists"
oRow.Range(1, 3) = EndDicExists - StartDicExists
End With
End Sub
Sub CompareObjectsMulti()
' Use this to run the test multiple times
Dim Iterations As Long
Dim Counter As Long
On Error GoTo ErrHandler
Iterations = InputBox("How many trials do you want (each can take 2-4 minutes)", "Compare", 10)
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
For Counter = 1 To Iterations
CompareObjects
Next
ErrHandler:
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
MsgBox "Done"
End Sub
Sub CompareObjectsSingle()
' Use this to run the test one time
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
CompareObjects
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
MsgBox "Done"
End Sub
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (51)
Commented:
and it's multitude of code. That is, not everyone is fluent in VBA, and I certainly am one who has no clue about half the verbiage that is used in this article and other articles as well. I dare say I have to go through 15 - 20 articles on a particular subject before I find one that finally I can connect with. Maybe I've fallen off the turnip truck too many times, and a slower learner than most, but still I try.
May I suggest that when you write, think of those who will read your writing and who are far from expert status. It is difficult to learn when terminology is used "willy nilly" which one does not understand in the least.
I have to explain complex environmental laws to "Mom & Pops" as we say all them, and have to remind myself all the time, these folks are not engineers nor scientists, and how best to get them to understand a particular point I'm trying to get across to them.
Thanks for understanding!!
Best Holiday Wishes,
Commented:
Although Patrick has kindly commented his code above, perhaps you should have stopped when you read this Note:
---
Note: While the intended audience for this article is VBA developers, Visual Basic 6 (VB6) developers can certainly make use of the information here to implement Dictionaries in their VB6 projects. Further please note that the processing speed benchmarking in the final section of this article may not necessarily apply to VB6.
---
Author
Commented:Commented:
Commented:
View More