Sub UniqueItems()
Dim rngOutput As Range
Dim dic_A As Object
Dim dic_B As Object
Dim lng As Long
Dim lngRange As Long
Dim varItems As Variant
Dim strMessage As String
varItems = False
On Error Resume Next
Set varItems = Application.InputBox _
(Title:="Select Output cell", _
Prompt:="Where do you want the duplicates to be output?", Type:=8)
If Err.Number = 0 Then 'user didn't push cancel
On Error GoTo 0
Set rngOutput = varItems
Set dic_A = CreateObject("Scripting.Dictionary")
Set dic_B = CreateObject("Scripting.Dictionary")
strMessage = "Select the first range that you want to compare."
strMessage = strMessage & vbNewLine & vbNewLine
strMessage = strMessage & "If your ranges form a contiguous block (i.e. the ranges are side-by-side), select the entire block."
varItems = Application.InputBox(Title:="Select first range...", _
Prompt:=strMessage, _
Type:=8)
If VarType(varItems) <> vbBoolean Then 'User didn't cancel
If UBound(varItems, 2) > 1 Then 'Data is in a contigous block
UniqueDictionary varItems, lngRange, dic_A, dic_B
Else
'User will select individual blocks in an endless loop
'that will only be escaped when they push Cancel
lngRange = 1
UniqueDictionary varItems, lngRange, dic_A, dic_B
Do Until "Hell" = "Freezes Over" 'We only want to exit the loop once the user pushes Cancel
lngRange = lngRange + 1
strMessage = "Select the " & lngRange & OrdinalSuffix(lngRange) & " range that you want to compare."
strMessage = strMessage & vbNewLine & vbNewLine
strMessage = strMessage & "If you have no more ranges to add, push Cancel"
varItems = Application.InputBox(Title:="Select " & lngRange & OrdinalSuffix(lngRange) & " range...", _
Prompt:=strMessage, _
Type:=8)
If VarType(varItems) = vbBoolean Then
lngRange = lngRange - 1
Exit Do
Else:
UniqueDictionary varItems, lngRange, dic_A, dic_B
End If
Loop
End If 'If UBound(varItems, 2) > 1 Then
'Write any duplicate items back to the worksheet.
If dic_B.Count > 0 Then
If dic_B.Count < 65537 Then
rngOutput.Resize(dic_B.Count) = Application.Transpose(dic_B.Items)
Else
'The dictionary is too big to transfer to the workheet
' because Application.Transfer can't handle more than
' 65536 items.
' So we'll transfer it to a variant array, then transfer
' that array to the worksheet.
ReDim varOutput(1 To dic_B.Count, 1 To 1)
For lng = 1 To dic_B.Count
varOutput(lng, 1) = dic_B.Item(lng)
Next lng
rngOutput.Resize(dic_B.Count) = varOutput
End If
Else:
MsgBox "There were no numbers unique to all " & lngRange & " columns."
End If
End If 'If VarType(varItems) <> vbBoolean Then 'User didn't cancel
End If 'If Err.Number = 0 Then 'user didn't push cancel
'Cleanup
Set dic_A = Nothing
Set dic_B = Nothing
End Sub
Private Function UniqueDictionary(varItems As Variant, ByRef lngRange As Long, ByVal dic_A As Object, ByVal dic_B As Object)
Dim lng As Long
Dim dic_dedup As Object
Dim varItem As Variant
Dim lPass As Long
Set dic_dedup = CreateObject("Scripting.Dictionary")
For lPass = 1 To UBound(varItems, 2)
If UBound(varItems, 2) > 1 Then lngRange = lngRange + 1
For lng = 1 To UBound(varItems)
If Not dic_A.exists(varItems(lng, lPass)) Then
dic_A.Add varItems(lng, lPass), varItems(lng, lPass)
dic_B.Add varItems(lng, lPass), varItems(lng, lPass)
Else
If dic_B.exists(varItems(lng, lPass)) Then dic_B.Remove varItems(lng, lPass)
End If
Next
Next
End Function
Function OrdinalSuffix(ByVal Num As Long) As String
'Code from http://www.cpearson.com/excel/ordinal.aspx
Dim N As Long
Const cSfx = "stndrdthththththth" ' 2 char suffixes
N = Num Mod 100
If ((Abs(N) >= 10) And (Abs(N) <= 19)) _
Or ((Abs(N) Mod 10) = 0) Then
OrdinalSuffix = "th"
Else
OrdinalSuffix = Mid(cSfx, _
((Abs(N) Mod 10) * 2) - 1, 2)
End If
End Function