andrew_milonavic
Board Regular
- Joined
- Nov 16, 2016
- Messages
- 98
Hi everyone,
I'm using a VBA that calculates unique combinations from multiple columns. It works great! but in some cases there are 400000+ combinations before sorting and it runs really slow. I'm far from a VBA expert, any way to speed it up?
Any help would be great!
Thanks
Andrew
I'm using a VBA that calculates unique combinations from multiple columns. It works great! but in some cases there are 400000+ combinations before sorting and it runs really slow. I'm far from a VBA expert, any way to speed it up?
VBA Code:
Option Explicit
Sub NameCombos()
'https://www.mrexcel.com/forum/excel-questions/1106189-all-combinations-multiple-columns-without-duplicates.html
Dim lLastColumn As Long
Dim lLastUsedColumn As Long
Dim aryNames As Variant
Dim lColumnIndex As Long
Dim lWriteRow As Long
Dim bCarry As Boolean
Dim lWriteColumn As Long
Dim rngWrite As Range
Dim lFirstWriteColumn As Long
Dim lLastWriteColumn As Long
Dim oFound As Object
Dim lRefColumn As Long
Dim lInUseRow As Long
Dim lCarryColumn As Long
Dim lPrint As Long
Dim lLastIteration As Long
Dim lIterationCount As Long
Dim sErrorMsg As String
Dim bShowError As Boolean
Dim lLastRow As Long
Dim lLastRowDeDuped As Long
Dim aryDeDupe As Variant
Dim sName As String
Dim bDupeName As Boolean
Dim oSD As Object
Dim rngCell As Range
Dim varK As Variant, varI As Variant, varTemp As Variant, lIndex As Long
Dim lRowIndex As Long
Dim rngSortRange As Range
Dim dteStart As Date
Dim sOutput As String
Dim lFirstHSortColumn As Long
Dim lLastHSortColumn As Long
Dim rngReplace As Range
sErrorMsg = "Ensure a Worksheet is active with a header row starting in A1" & _
"and names under each header entry."
If TypeName(ActiveSheet) <> "Worksheet" Then
bShowError = True
End If
If bShowError Then
MsgBox sErrorMsg, , "Problems Found in Data"
GoTo End_Sub
End If
lLastColumn = Range("A1").CurrentRegion.Columns.Count
lLastUsedColumn = ActiveSheet.UsedRange.Columns.Count
ReDim aryNames(1 To 2, 1 To lLastColumn) '1 holds the in-use entry row
'How many combinations? (Order does not matter)
lLastIteration = 1
For lColumnIndex = 1 To lLastColumn
aryNames(1, lColumnIndex) = 2
aryNames(2, lColumnIndex) = Cells(Rows.Count, lColumnIndex).End(xlUp).Row
lLastIteration = lLastIteration * (aryNames(2, lColumnIndex) - 1)
Next
lRefColumn = lLastColumn + 1
lFirstWriteColumn = lLastColumn + 2
lLastWriteColumn = (2 * lLastColumn) + 1
Select Case MsgBox("Process a " & lLastColumn & " column table with " & _
lLastIteration & " possible combinations?" & vbLf & vbLf & _
"WARNING: Columns right of the input range will be erased before continuing.", vbOKCancel + vbCritical + _
vbDefaultButton2, "Process table?")
Case vbCancel
GoTo End_Sub
End Select
dteStart = Now()
'Clear all columns right of input range
If lLastUsedColumn > lLastColumn Then
Range(Cells(1, lLastColumn + 1), Cells(1, lLastUsedColumn)).EntireColumn.ClearContents
End If
Cells(1, lLastWriteColumn + 1).Value = "ComboID"
'Add Output Header
Range(Cells(1, 1), Cells(1, lLastColumn)).Copy Destination:=Cells(1, lFirstWriteColumn)
'Start checking combinations
lWriteRow = 2
For lIterationCount = 1 To lLastIteration
If lIterationCount / 1000 = lIterationCount \ 1000 Then Application.StatusBar = _
lIterationCount & " / " & lLastIteration
'Reset the Dupe Name flag
bDupeName = False
'Check Active Combo for Dupe Names
'Initialize the scripting dictionary
Set oSD = CreateObject("Scripting.Dictionary")
oSD.CompareMode = vbTextCompare
'Load names into scripting dictionary
For lColumnIndex = lLastColumn To 1 Step -1
sName = Cells(aryNames(1, lColumnIndex), lColumnIndex).Value
oSD.Item(sName) = oSD.Item(sName) + 1
Next
'If there are names, and at least one duplicate, set the bDupeName flag
If oSD.Count > 0 Then
varK = oSD.keys
varI = oSD.Items
For lIndex = 1 To oSD.Count
If varI(lIndex - 1) > 1 Then
bDupeName = True: Exit For
End If
Next
End If
If Not bDupeName Then
'The current row had names and no duplicates
'Print Active Combo to the lWriteRow row
For lColumnIndex = lLastColumn To 1 Step -1
lWriteColumn = lColumnIndex + lLastColumn + 2
Set rngWrite = Range(Cells(lWriteRow, lFirstWriteColumn), Cells(lWriteRow, lLastWriteColumn))
Cells(lWriteRow, lRefColumn + lColumnIndex).Value = Cells(aryNames(1, lColumnIndex), lColumnIndex).Value
Next
'Uncomment next row to see the lIterationCount for the printed row
Cells(lWriteRow, lLastWriteColumn + 1).Value = lIterationCount
'Point to the next blank row
lWriteRow = lWriteRow + 1
End If
'Increment Counters
'Whether the line had duplicates or not, move to the next name in the
' rightmost column, if it was ag the last name, go to the first name in that column and
' move the name in the column to the left down to the next name (recursive check if THAT
' column was already using the last name for remaining columns to the left)
aryNames(1, lLastColumn) = aryNames(1, lLastColumn) + 1
If aryNames(1, lLastColumn) > aryNames(2, lLastColumn) Then
bCarry = True
lCarryColumn = lLastColumn
Do While bCarry = True And lCarryColumn > 0
aryNames(1, lCarryColumn) = 2
bCarry = False
lCarryColumn = lCarryColumn - 1
If lCarryColumn = 0 Then Exit Do
aryNames(1, lCarryColumn) = aryNames(1, lCarryColumn) + 1
If aryNames(1, lCarryColumn) > aryNames(2, lCarryColumn) Then bCarry = True
Loop
End If
'Check counter values (for debug)
' Debug.Print lWriteRow,
' For lPrint = 1 To lLastColumn
' Debug.Print aryNames(1, lPrint) & ", ";
' Next
' Debug.Print
DoEvents
Next
Application.StatusBar = "Sorting"
Application.ScreenUpdating = False
'Copy row names to right so that each copied row can be sorted alphabetically left to right
' this will allow the Excel remove duplicate fuction to remove rows that have identical names
' in all of their sorted columns.
lLastRow = Cells(Rows.Count, lFirstWriteColumn).End(xlUp).Row
Range(Cells(1, lFirstWriteColumn), Cells(lLastRow, lLastWriteColumn)).Copy Destination:=Cells(1, lLastWriteColumn + 2)
lFirstHSortColumn = lLastWriteColumn + 2
lLastHSortColumn = Cells(1, Columns.Count).End(xlToLeft).Column
'Sort each row
Application.ScreenUpdating = False
lLastRow = Cells(Rows.Count, lFirstWriteColumn).End(xlUp).Row
For lRowIndex = 2 To lLastRow
Set rngSortRange = Range(Cells(lRowIndex, lFirstHSortColumn), Cells(lRowIndex, lLastHSortColumn))
ActiveSheet.Sort.SortFields.Clear
ActiveSheet.Sort.SortFields.Add Key:=rngSortRange, _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ActiveSheet.Sort
.SetRange rngSortRange
.Header = xlNo
.MatchCase = False
.Orientation = xlLeftToRight
.SortMethod = xlPinYin
.Apply
End With
Next
'Check for duplicate rows in HSort Columns
' Can only happen if names are duplicated within an input column
' Build aryDeDupe -- Array(1, 2, 3,...n) -- to exclude iteration # column
lLastRow = Cells(Rows.Count, lFirstWriteColumn).End(xlUp).Row
ReDim aryDeDupe(0 To lLastHSortColumn - lFirstHSortColumn)
lIndex = 0
For lColumnIndex = lFirstHSortColumn To lLastHSortColumn
aryDeDupe(lIndex) = CInt(lColumnIndex - lFirstWriteColumn + 1)
lIndex = lIndex + 1
Next
ActiveSheet.Cells(1, lFirstWriteColumn).CurrentRegion.RemoveDuplicates Columns:=(aryDeDupe), Header:=xlYes
'Above line won't work unless there are parens around the Columns argument ?????
lLastRowDeDuped = Cells(Rows.Count, lFirstWriteColumn).End(xlUp).Row
sOutput = lLastIteration & vbTab & " possible combinations" & vbLf & _
lLastRow - 1 & vbTab & " unique name combinations" & vbLf & _
IIf(lLastRowDeDuped <> lLastRow, lLastRow - lLastRowDeDuped & vbTab & " duplicate rows removed." & vbLf, "") & _
lLastRowDeDuped - 1 & vbTab & " printed." & vbLf & vbLf & _
Format(Now() - dteStart, "hh:mm:ss") & " to process."
ActiveSheet.UsedRange.Columns.AutoFit
MsgBox sOutput, , "Output Report"
Debug.Print sOutput
End_Sub:
Application.ScreenUpdating = True
Application.StatusBar = False
End Sub
Any help would be great!
Thanks
Andrew