I grabbed the following from my vault of Sorting routines. Please ignore some of my naming conventions :).
Upon review, I noticed an issue with my CompareNaturalNum()
routine where it considered
"1.01.1.3.1" and "1.01.1.3.1.1" the same. I've fixed it in the following code, and shown how to use it.
QuickSortMultiNaturalNum - A Quick sort for variant arrays, where you specify the column to be sorted.
Public Sub QuickSortMultiNaturalNum(strArray As Variant, intBottom As Long, intTop As Long, intSortIndex As Long, Optional intLowIndex As Long, Optional intHighIndex As Long = -1)
Dim strPivot As String, strTemp As String
Dim intBottomTemp As Long, intTopTemp As Long
Dim i As Long
intBottomTemp = intBottom
intTopTemp = intTop
If intHighIndex < intLowIndex Then
If (intBottomTemp <= intTopTemp) Then
intLowIndex = LBound(strArray, 2)
intHighIndex = UBound(strArray, 2)
End If
End If
strPivot = strArray((intBottom + intTop) \ 2, intSortIndex)
While (intBottomTemp <= intTopTemp)
' < comparison of the values is a descending sort
While (CompareNaturalNum(strArray(intBottomTemp, intSortIndex), strPivot) < 0 And intBottomTemp < intTop)
intBottomTemp = intBottomTemp + 1
Wend
While (CompareNaturalNum(strPivot, strArray(intTopTemp, intSortIndex)) < 0 And intTopTemp > intBottom)
intTopTemp = intTopTemp - 1
Wend
If intBottomTemp < intTopTemp Then
For i = intLowIndex To intHighIndex
strTemp = Var2Str(strArray(intBottomTemp, i))
strArray(intBottomTemp, i) = Var2Str(strArray(intTopTemp, i))
strArray(intTopTemp, i) = strTemp
Next
End If
If intBottomTemp <= intTopTemp Then
intBottomTemp = intBottomTemp + 1
intTopTemp = intTopTemp - 1
End If
Wend
'the function calls itself until everything is in good order
If (intBottom < intTopTemp) Then QuickSortMultiNaturalNum strArray, intBottom, intTopTemp, intSortIndex, intLowIndex, intHighIndex
If (intBottomTemp < intTop) Then QuickSortMultiNaturalNum strArray, intBottomTemp, intTop, intSortIndex, intLowIndex, intHighIndex
End Sub
CompareNaturalNum - Custom Compare function
Function CompareNaturalNum(string1 As Variant, string2 As Variant) As Long
'string1 is less than string2 -1
'string1 is equal to string2 0
'string1 is greater than string2 1
Dim n1 As Long, n2 As Long
Dim iPosOrig1 As Long, iPosOrig2 As Long
Dim iPos1 As Long, iPos2 As Long
Dim nOffset1 As Long, nOffset2 As Long
If Not (IsNull(string1) Or IsNull(string2)) Then
iPos1 = 1
iPos2 = 1
Do While iPos1 <= Len(string1)
If iPos2 > Len(string2) Then
CompareNaturalNum = 1
Exit Function
End If
If isDigit(string1, iPos1) Then
If Not isDigit(string2, iPos2) Then
CompareNaturalNum = -1
Exit Function
End If
iPosOrig1 = iPos1
iPosOrig2 = iPos2
Do While isDigit(string1, iPos1)
iPos1 = iPos1 + 1
Loop
Do While isDigit(string2, iPos2)
iPos2 = iPos2 + 1
Loop
nOffset1 = (iPos1 - iPosOrig1)
nOffset2 = (iPos2 - iPosOrig2)
n1 = Val(Mid(string1, iPosOrig1, nOffset1))
n2 = Val(Mid(string2, iPosOrig2, nOffset2))
If (n1 < n2) Then
CompareNaturalNum = -1
Exit Function
ElseIf (n1 > n2) Then
CompareNaturalNum = 1
Exit Function
End If
' front padded zeros (put 01 before 1)
If (n1 = n2) Then
If (nOffset1 > nOffset2) Then
CompareNaturalNum = -1
Exit Function
ElseIf (nOffset1 < nOffset2) Then
CompareNaturalNum = 1
Exit Function
End If
End If
ElseIf isDigit(string2, iPos2) Then
CompareNaturalNum = 1
Exit Function
Else
If (Mid(string1, iPos1, 1) < Mid(string2, iPos2, 1)) Then
CompareNaturalNum = -1
Exit Function
ElseIf (Mid(string1, iPos1, 1) > Mid(string2, iPos2, 1)) Then
CompareNaturalNum = 1
Exit Function
End If
iPos1 = iPos1 + 1
iPos2 = iPos2 + 1
End If
Loop
' Everything was the same so far, check if Len(string2) > Len(String1)
' If so, then string1 < string2
If Len(string2) > Len(string1) Then
CompareNaturalNum = -1
Exit Function
End If
Else
If IsNull(string1) And Not IsNull(string2) Then
CompareNaturalNum = -1
Exit Function
ElseIf IsNull(string1) And IsNull(string2) Then
CompareNaturalNum = 0
Exit Function
ElseIf Not IsNull(string1) And IsNull(string2) Then
CompareNaturalNum = 1
Exit Function
End If
End If
End Function
isDigit - Simple function to let you know if the string value is a digit (0-9)
Function isDigit(ByVal str As String, pos As Long) As Boolean
Dim iCode As Long
If pos <= Len(str) Then
iCode = Asc(Mid(str, pos, 1))
If iCode >= 48 And iCode <= 57 Then isDigit = True
End If
End Function
Var2Str - Since it deals with Variants, the values could be Null
, so convert it to a string
Public Function Var2Str(Value As Variant, Optional TrimSpaces As Boolean = True) As String
If IsNull(Value) Then
'Var2Str = vbNullString
Exit Function
End If
If TrimSpaces Then
Var2Str = Trim(Value)
Else
Var2Str = CStr(Value)
End If
End Function
Test - Here is sample code of how to use it. Just change the Range values. The last 1
in the call to QuickSortMultiNaturalNum
is the column to be sorted (the column the ID's are in).
Sub Test()
Dim Target As Range
Dim vData 'as Variant
Dim Rows As Long
' Set Target to the CurrentRegion of cells around "A1"
Set Target = Range("A1").CurrentRegion
' Copy the values to a variant
vData = Target.Value2
' Get the high/upper limit of the array
Rows = Target.Rows.Count 'UBound(vData, 1)
' Sor The variant array, passing the variant, lower limit, upper limit and the index of the column to be sorted.
QuickSortMultiNaturalNum vData, 1, Rows, 1
' Paste the values back onto the sheet. For testing, you may want to paste it to another sheet/range
Range("A1").Resize(Target.Rows.Count, Target.Columns.Count).Value = vData
End Sub