Naturally Sort a Directory in VBA - vba

I am running a VBA script through a directory but I need to go through the files as if they were sorted in Windows Explorer. For example I have a directory like so:
32 Read.log
64 Write.log
256 Read.log
512 Write.log
1024 Write.log
4 Read.log
When I sort it with VBA, it only looks at the first character to sort it like so:
1024 Write.log
256 Read.log
32 Read.log
4 Read.log
512 Write.log
64 Write.log
Any ideas on how to sort from smallest to largest number before I go through the directory?

I actually went to the trouble of building an algorithm for it:
Dim a As Variant
Dim c As String
Dim d As String
Dim x As Long
Dim y As Long
Dim s As Boolean
Dim p As Long
Dim q As Long
Dim e As Long
Dim n1 As String
Dim n2 As String
'Create a dummy array to test
a = Array("1024 Write.log", "256 Read.log", "32 Read.log", "4 Read.log", "512 Write.log", "64 Write.log")
'Loop through the array and look for values that need to change position
For x = LBound(a) To UBound(a) - 1
For y = x + 1 To UBound(a)
'Check if the values at x and y must be swapped
s = False
'Loop through each character in both strings to do a compare
If Len(a(x)) > Len(a(y)) Then e = Len(a(x)) Else e = Len(a(y))
For p = 1 To e
If Len(a(x)) < p Then
'y is longer, so it should come last
Exit For
ElseIf Len(a(y)) < p Then
'y is shorter, so it should come first
s = True
Exit For
ElseIf InStr("0123456789", Mid(a(x), p, 1)) = 0 Or InStr("0123456789", Mid(a(y), p, 1)) = 0 Then
'The char at p in x or y is not a number, so do a text compare
If Mid(a(x), p, 1) < Mid(a(y), p, 1) Then
Exit For
ElseIf Mid(a(x), p, 1) > Mid(a(y), p, 1) Then
s = True
Exit For
End If
Else
'The char at p for both x and y are numbers, so get the whole numbers and compare
'Get the number for x
n1 = ""
q = p
Do While q <= Len(a(x)) And InStr("0123456789", Mid(a(x), q, 1)) <> 0
n1 = n1 & Mid(a(x), q, 1)
q = q + 1
Loop
'Get the number for y
n2 = ""
q = p
Do While q <= Len(a(y)) And InStr("0123456789", Mid(a(y), q, 1)) <> 0
n2 = n2 & Mid(a(y), q, 1)
q = q + 1
Loop
If Len(n1) > Len(n2) Then
'n1 is a bigger number, so it should be last
s = True
Exit For
ElseIf Len(n1) < Len(n2) Then
'n1 is smaller, so it should remain first
Exit For
ElseIf n1 > n2 Then
'n1 is a bigger number, so it should be last
s = True
Exit For
ElseIf n1 < n2 Then
'n1 is smaller, so it should remain first
Exit For
End If
End If
Next
'Do the swap
If s Then
c = a(y)
a(y) = a(x)
a(x) = c
End If
Next
Next
'Verify that it worked
c = ""
For p = LBound(a) To UBound(a)
c = c & a(p) & vbCrLf
Next
MsgBox c

Once the data has been imported into Excel, parse the data so the size is in column A and the name is in column B. Then insure (or convert) the data in column A to be values rather than Text. Then sort columns A & B by A Ascending.

Read the directory into a dictionary object as in CreateObject("Scripting.Dictionary") and write a function that will sort the dictionary in the exact way you want.
An example can be found at this question:
Sort dictionary
EDIT: If you already have it in an array, you can adjust the code to just sort the array
EDIT: Simple example of using a dictionary:
Dim vArray As Variant
Dim vDict As Object
Dim i As Variant
vArray = Array("F1", "F2", "F3")
Set vDict = CreateObject("Scripting.Dictionary")
For i = LBound(vArray) To UBound(vArray)
vDict.Add i, vArray(i)
Next
For Each i In vDict
MsgBox "Key: " & i & ", Value: " & vDict(i)
Next

Related

VBA Offset within Loop - taking forever to run

I'm brand new to programming, and I figured VBA is a good place for me to start since I do a lot of work in Excel.
I created a macro that takes an integer from an input box (I've been using 2, 3 and 4 to test) and it creates a set of a 4-tier hierarchy of that number; e.g. entering "2" would produce
1.0.0.0
1.0.0.1
1.0.0.2
1.0.1.0
1.0.1.1
1.0.1.2 etc.
I got the macro to work as intended, but it takes forever to run. I think it's the offsets within the loops that are slowing it down. Does anyone have any suggestions to speed this up? Any general feedback is welcome as well.
Sub Tiers()
'Input Box
Dim Square As Integer
Square = InputBox("Enter Number of Tiers")
Range("f5").Select
Selection.Value = 0
With Application
.ScreenUpdating = False
End With
'Rows down
Dim g As Integer
Dim h As Integer
Dim i As Integer
Dim j As Integer
'Start For loops
For g = 1 To Square
For h = 0 To Square
For i = 0 To Square
For j = 0 To Square
'calculate offsets and place values of loop variables
Dim step As Long
step = ((g - 1) * (Square + 1) ^ 3 - 1 + (h * (Square + 1) ^ 2) + Square * i + i + j + 1)
Selection.Offset(step, 0).Value = j
Selection.Offset(step, -1).Value = i
Selection.Offset(step, -2).Value = h
Selection.Offset(step, -3).Value = g
Next j
Next i
Next h
Next g
With Application
.ScreenUpdating = True
End With
End Sub
Thanks
Further to my comment below your post, looping and writing to sheets like this will be too slow. Write to an array and then write the array to worksheet. This ran in a blink of an eye.
Is this what you are trying?
Sub Sample()
Dim TempArray() As Long
Dim n As Long
Dim g As Long, h As Long, i As Long, j As Long
Dim reponse As Variant
'~~> Accept only numbers
reponse = Application.InputBox(Prompt:="Enter Number of Tiers", Type:=1)
If reponse <> False Then
For g = 1 To reponse
For h = 0 To reponse
For i = 0 To reponse
For j = 0 To reponse
n = n + 1
Next j
Next i
Next h
Next g
ReDim Preserve TempArray(1 To n, 1 To 4)
n = 1
For g = 1 To reponse
For h = 0 To reponse
For i = 0 To reponse
For j = 0 To reponse
TempArray(n, 1) = g
TempArray(n, 2) = h
TempArray(n, 3) = i
TempArray(n, 4) = j
n = n + 1
Next j
Next i
Next h
Next g
'~~> Replace this with the relevant sheet
Sheet1.Range("A1").Resize(UBound(TempArray), 4).Value = TempArray
End If
End Sub
Screenshot:
The step calculation seems superfluous:
step = ((g - 1) * (Square + 1) ^ 3 - 1 + (h * (Square + 1) ^ 2) + Square * i + i + j + 1)
Try the following:
Sub Tiers()
'Input Box
Dim Square As Long
Square = InputBox("Enter Number of Tiers")
With Application
.ScreenUpdating = False
End With
'Rows down
Dim g As Long
Dim h As Long
Dim i As Long
Dim j As Long
Dim step As Long
step = 1
For g = 1 To Square
For h = 0 To Square
For i = 0 To Square
For j = 0 To Square
Range("F5").Offset(step, 0).Value = j
Range("F5").Offset(step, -1).Value = i
Range("F5").Offset(step, -2).Value = h
Range("F5").Offset(step, -3).Value = g
step = step + 1
Next j
Next i
Next h
Next g
With Application
.ScreenUpdating = True
End With
End Sub

Why do multiple consecutive unequal conditions not work in vba?

I was wondering why the following syntax does not work the way I thought it would in VBA, and what I should do to ensure it does;
For a = 1 To 10
For b = 1 To 10
For c = 1 To 10
If a <> b <> c Then
MsgBox (a & " " & b & " " & c)
End If
Next c
Next b
Next a
This is a simplified example, which can still be manually obtained with:
if a<>b and b<>c and c<>a then
But my actual intended code has 10 such variables multiple times, which makes it unfeasible with 55 unequal conditions, or likely for me to make a typo. I think there is a more efficient way but I have not found it.
Ps. My goal is to only have a message box pop up if all the variables are unique.
I have obtained my goal, though it can probably be done much more efficient than:
For a = 1 To 10
check(a) = True
For b = 1 To 10
If check(b) = False Then
check(b) = True
For c = 1 To 10
If check(c) = False Then
check(c) = True
For d = 1 To 10
If check(d) = False Then
check(d) = True
For e = 1 To 10
If check(e) = False Then
check(e) = True
MsgBox (a & " " & b & " " & c & " " & d & " " & e)
End If
check(e) = False
check(a) = True
check(b) = True
check(c) = True
check(d) = True
Next e
End If
check(d) = False
check(a) = True
check(b) = True
check(c) = True
Next d
End If
check(c) = False
check(a) = True
check(b) = True
Next c
End If
check(b) = False
check(a) = True
Next b
Next a
Here is an implementation of the Johnson-Trotter algorithm for enumerating permutations. It is a small modification of one that I wrote once when playing around with brute-force solutions to the Traveling Salesman Problem. Note that it returns a 2-dimensional array, which might consume a lot of memory. It is possible to refactor it so that it is a sub where the permutations are consumed rather than stored. Just replace the part of the code near the bottom (where the current permutation, perm, is stored in the array perms) by the code that uses the permutation.
Function Permutations(n As Long) As Variant
'implements Johnson-Trotter algorithm for
'listing permutations. Returns results as a variant array
'Thus not feasible for n > 10 or so
Dim perm As Variant, perms As Variant
Dim i As Long, j As Long, k As Long, r As Long, D As Long, m As Long
Dim p_i As Long, p_j As Long
Dim state As Variant
m = Application.WorksheetFunction.Fact(n)
ReDim perm(1 To n)
ReDim perms(1 To m, 1 To n) As Integer
ReDim state(1 To n, 1 To 2) 'state(i,1) = where item i is currently in perm
'state(i,2) = direction of i
k = 1 'will point to current permutation
For i = 1 To n
perm(i) = i
perms(k, i) = i
state(i, 1) = i
state(i, 2) = -1
Next i
state(1, 2) = 0
i = n 'from here on out, i will denote the largest moving
'will be 0 at the end
Do While i > 0
D = state(i, 2)
'swap
p_i = state(i, 1)
p_j = p_i + D
j = perm(p_j)
perm(p_i) = j
state(i, 1) = p_j
perm(p_j) = i
state(j, 1) = p_i
p_i = p_j
If p_i = 1 Or p_i = n Then
state(i, 2) = 0
Else
p_j = p_i + D
If perm(p_j) > i Then state(i, 2) = 0
End If
For j = i + 1 To n
If state(j, 1) < p_i Then
state(j, 2) = 1
Else
state(j, 2) = -1
End If
Next j
'now find i for next pass through loop
If i < n Then
i = n
Else
i = 0
For j = 1 To n
If state(j, 2) <> 0 And j > i Then i = j
Next j
End If
'record perm in perms:
k = k + 1
For r = 1 To n
perms(k, r) = perm(r)
Next r
Loop
Permutations = perms
End Function
Tested like:
Sub test()
Range("A1:G5040").Value = Permutations(7)
Dim A As Variant, i As Long, s As String
A = Permutations(10)
For i = 1 To 10
s = s & " " & A(3628800, i)
Next i
Debug.Print s
End Sub
The first 20 rows of output look like:
Also, 2 1 3 4 5 6 7 8 9 10 is printed in the immediate window. My first version used a vanilla variant away and caused an out-of-memory error with n = 10. I tweaked it so that perms is redimensioned to contain integers (which consume less memory than variants) and is now able to handle 10. It takes about 10 seconds on my machine to run the test code.
You could simply add a check right after the beginning of each inner loop, like follows
For a = 1 To 10
For b = 1 To 10
If b <> a Then '<-- this check will make sure subsequent inner loops shouldn't bother but for their loops variables
For c = 1 To 10
If c <> b Then '<-- same comment as preceeding one
For d = 1 to 10
If d <> c then MsgBox (a & " " & b & " " & c & " " & d) '<-- last check for last two variables
Next d
End If
Next c
End If
Next b
Next a
Try putting all those variables into the array and checking the array for duplicates, if none found, display the message box. Something like this:
Sub dupfind()
Dim ArrHelper(2) As Long
Dim k As Long
Dim j As Long
Dim ans As Long
Dim dupl As Boolean
Dim ArrAnswers() As Long
ans = 0
For a = 1 To 10
ArrHelper(0) = a
For b = 2 To 10
ArrHelper(1) = b
For c = 1 To 10
ArrHelper(2) = c
dupl = False
For k = 0 To UBound(ArrHelper) - 1
For j = k + 1 To UBound(ArrHelper)
If ArrHelper(k) = ArrHelper(j) Then
dupl = True
End If
Next j
Next k
If dupl = False Then
ReDim Preserve ArrAnswers(3, ans)
ArrAnswers(0, ans) = a
ArrAnswers(1, ans) = b
ArrAnswers(2, ans) = c
ans = ans + 1
End If
Next c
Next b
Next a
End Sub
Read your edit regarding storing permutations and changed the code a bit

Recursive Function in Excel VBA using GoTo, Characters Combinations from Array

I want to create a recursive function in Excel VBA without using nested Loops. I used GoTo to do it as I think it is very fast as compared to For loop etc. PROBLEM: problem is that the first Label i.e. 'a' does not perform all iterations and the required combinations are not returned so. From the given array 'arr' there should be 39 combinations but just 14 are returned. I i try to change some lines of code the total iterations 'iNum' returns 39, but not 39 combinations (combinations starting from 'a' are always missing). Please help, Thanks.
Function rec_n()
Dim a As Integer, b As Integer, c As Integer
Dim aSize As Integer, iNum As Integer
Dim myStr As String
'Dim arr As Variant
Dim arr(5) As String
'arr = Array("a", "b", "c", "d")
arr(0) = "a"
arr(1) = "b"
arr(2) = "c"
'arr(3) = "d"
aSize = 3 - 1
'a = 0: b = 0: c = 0
a: If a < aSize Then
myStr = myStr & arr(a) & ", "
a = a + 1: iNum = iNum + 1
b: If b < aSize Then
myStr = myStr & arr(a) & arr(b) & ", "
b = b + 1: iNum = iNum + 1
c: If c < aSize Then
'On Error Resume Next
myStr = myStr & arr(a) & arr(b) & arr(c) & ", "
c = c + 1: iNum = iNum + 1
GoTo c
Else
c = 0
'MsgBox c
End If
GoTo b
Else
b = 0
'MsgBox b
End If
GoTo a
End If
EndFunc:
MsgBox iNum & vbLf & myStr
Range("a2").Value = myStr
End Function
EDITED:
The code is resulting in just these combinations:
a, ba, bba, bbb, bb, bca, bcb, b, ca, cba, cbb, cb, cca, ccb,
where as these 39 are expected:
a, b, c, aa, ab, ac, ba, bb, bc, ca, cb, cc, aaa, aab, aac, aba, abb,
abc, aca, acb, acc, baa, bab, bac, bba, bbb, bbc, bca, bcb, bcc, caa,
cab, cac, cba, cbb, cbc, cca, ccb, ccc,
Here is a goto-free recursive approach:
Function StringsFrom(A As Variant, Optional maxlen As Variant) As Variant
'returns a 0-based array of all strings of length <= maxlen
'with elements drawn from A
'A is assumed to be 0-based array
'If maxlen is missing then it is taken to be the number of elements in A
Dim strings As Variant
Dim newstrings As Variant
Dim i As Long, j As Long, k As Long, m As Long, n As Long
If IsMissing(maxlen) Then maxlen = 1 + UBound(A)
m = UBound(A)
If maxlen < 1 Then Exit Function
If maxlen = 1 Then
'basis case -- return a copy of A - coerced to be strings if needed
ReDim newstrings(0 To m)
For i = 0 To m
newstrings(i) = CStr(A(i))
Next i
Else
strings = StringsFrom(A, maxlen - 1)
n = UBound(strings)
ReDim newstrings(0 To n + (m + 1) ^ maxlen)
'first copy strings to newstrings:
For i = 0 To n
newstrings(i) = strings(i)
Next i
k = n + 1 'points to current index in newstrings
'now -- load up the rest using a nested loop:
For i = 0 To m
For j = n + 1 - (m + 1) ^ (maxlen - 1) To n
newstrings(k) = A(i) & strings(j)
k = k + 1
Next j
Next i
End If
StringsFrom = newstrings
End Function
For e.g. maxlen = 4 and A has 5 strings it will first find all strings of length <= maxlen - 1 = 3 and then tack on the characters to those strings of length exactly 3. I had to do a bit of arithmetic to get the indices just right.
Here is some testing code:
Sub test()
Dim start As Double, elapsed As Double, A As Variant, B As Variant
A = Array("a", "b", "c")
B = StringsFrom(A)
MsgBox Join(B, " ") & vbCrLf & 1 + UBound(B) & " strings"
A = Array("a", "b", "c", "d", "e", "f", "g")
start = Timer
B = StringsFrom(A)
elapsed = Timer - start
MsgBox Round(elapsed, 2) & " seconds to process " & 1 + UBound(B) & " strings"
End Sub
The first test correctly gives 3 + 9 + 27 = 39 strings, and the second test gives (on my machine) the message: "0.68 seconds to process 960799 strings". When I increase A more I run out of memory before time is an issue.
ON EDIT: Here is a non-recursive approach. It is slower than the recursive approach but isn't subject to out of memory problems. It is based on the idea that if e.g. your letters are "abc" then you can view e.g. strings of length 4 from those letters as being numbers in base 3 ( = Len("abc")) so to enumerate them just count from 0 to 3^4 -1 = 80, translate each number to base 3, then use the correspondence `0 <=> "a", 1 <=> "b" etc.):
Sub Enumerate(letters As String, maxlen As Long, Optional display As Boolean = True)
'letters is assumed to have no repeated characters
Dim i As Long, j As Long, n As Long, q As Long, r As Long
Dim counter As Long
Dim s As String
Dim A As Variant
n = Len(letters)
ReDim A(0 To n - 1)
For i = 1 To n
A(i - 1) = Mid(letters, i, 1)
Next i
For i = 1 To maxlen
For j = 0 To n ^ i - 1
s = ""
q = j
If q = 0 Then
s = A(0)
Else
Do While q > 0
r = q Mod n
q = Int(q / n)
s = A(r) & s
Loop
End If
s = String(i - Len(s), A(0)) & s
counter = counter + 1
If display Then Debug.Print s
Next j
Next i
Debug.Print counter
End Sub
Tested like:
Sub test2()
Dim start As Double, elapsed As Double
Enumerate "abc", 3
start = Timer
Enumerate "abcdefghijklmnopqrstuvwxyz", 5, False
elapsed = Timer - start
Debug.Print Round(elapsed, 2)
End Sub
output of the time part of the test: shows that it takes (on my machine) about 18 seconds to loop through all of the (over 12.3 million) strings from the lower case standard alphabet of length <= 5. Some improvements are possible, but you aren't going to be able to get the sorts of speed you would need to brute force long strings drawn from a large alphabet.
VBA is an interpreted language. I think of it as a good tool for putzing around the solar system. If you want to explore the galaxy -- use C. If you want to explore other galaxies -- hope that quantum computers can be made to work.
On Further Edit: For fun I wrote a different version of Enumerate. It is about 33% faster than the last version and can loop generate nearly a million strings per second (at least on my somewhat average laptop). It is still based on thinking of strings as numbers in base n = length(letters) but simulates adding 1 to go from 1 number to the next, with an array used to look up which character results from "adding one" to a letter:
Sub Enumerate2(letters As String, maxlen As Long, Optional display As Boolean = True)
'letters is assumed to have no repeated characters
'prints all letter combos of length <= maxlen
'this one simulates the process of adding one to a string
Dim i As Long, j As Long, k As Long, n As Long, p As Long
Dim carry As Boolean
Dim counter As Long
Dim s As String
Dim num As Variant
Dim Successor(127) As String, Z As String, digit As String
n = Len(letters)
For i = 1 To n - 1
Successor(Asc(Mid(letters, i, 1))) = Mid(letters, i + 1, 1)
Next i
Z = Mid(letters, 1, 1) 'the "zero" of the base-n system
Successor(Asc(Mid(letters, n, 1))) = Z
For i = 1 To maxlen
ReDim num(1 To i) 'used to count from 0 to n^i - 1 in base n
For k = 1 To i
num(k) = Z
Next k
For j = 0 To n ^ i - 1
'get current s
s = Join(num, "")
counter = counter + 1
'now add 1 to num
carry = True
p = i 'points to rightmost "digit"
Do While p > 0 And carry
digit = Successor(Asc(num(p)))
If digit <> Z Then carry = False
num(p) = digit
p = p - 1
Loop
'the real code would go here:
If display Then Debug.Print s
Next j
Next i
Debug.Print counter
End Sub

Generate List of All 2^n subsets

I'm looking for code in VBA to generate all subsets of the items in a passed array.
Below is simple code to select all N choose 2 subsets of array size N.
Looking to augment this for N choose (N-1)... all the way down to N choose 1.
Option Base 1
Sub nchoose2()
iarray = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
n = UBound(iarray)
x = 1
t = 0
r = 0
Do While (n - x) >= 1
For i = 1 To (n - x)
Cells((i + t), 1) = iarray(x)
Cells((i + t), 2) = iarray(i + x)
Next i
x = x + 1
t = t + (n - (1 + r))
r = r + 1
Loop
End Sub
In addition to the Gray-code algorithm, you can also exploit the correspondence between subsets of an n-element set and binary vectors of length n. The following code illustrates this approach:
Sub AddOne(binaryVector As Variant)
'adds one to an array consisting of 0s and 1s
'thought of as a binary number in little-endian
'the vector is modified in place
'all 1's wraps around to all 0's
Dim bit As Long, carry As Long, i As Long, n As Long
carry = 1
n = UBound(binaryVector)
i = LBound(binaryVector)
Do While carry = 1 And i <= n
bit = (binaryVector(i) + carry) Mod 2
binaryVector(i) = bit
i = i + 1
carry = IIf(bit = 0, 1, 0)
Loop
End Sub
Function listSubsets(items As Variant) As Variant
'returns a variant array of collections
Dim lb As Long, ub As Long, i As Long, j As Long, numSets As Long
Dim vect As Variant 'binary vector
Dim subsets As Variant
lb = LBound(items)
ub = UBound(items)
ReDim vect(lb To ub)
numSets = 2 ^ (1 + ub - lb)
ReDim subsets(1 To numSets)
For i = 1 To numSets
Set subsets(i) = New Collection
For j = lb To ub
If vect(j) = 1 Then subsets(i).Add items(j)
Next j
AddOne vect
Next i
listSubsets = subsets
End Function
Function showCollection(c As Variant) As String
Dim v As Variant
Dim i As Long, n As Long
n = c.Count
If n = 0 Then
showCollection = "{}"
Exit Function
End If
ReDim v(1 To n)
For i = 1 To n
v(i) = c(i)
Next i
showCollection = "{" & Join(v, ", ") & "}"
End Function
Sub test()
Dim stooges As Variant
Dim stoogeSets As Variant
Dim i As Long
stooges = Array("Larry", "Curly", "Moe")
stoogeSets = listSubsets(stooges)
For i = LBound(stoogeSets) To UBound(stoogeSets)
Debug.Print showCollection(stoogeSets(i))
Next i
End Sub
Running the code results in the following output:
{}
{Larry}
{Curly}
{Larry, Curly}
{Moe}
{Larry, Moe}
{Curly, Moe}
{Larry, Curly, Moe}
I asked a similar question a while back (2005) and received this excellent code from John Coleman:
Sub MAIN()
Dim i As Long, st As String
Dim a(1 To 12) As Integer
Dim ary
For i = 1 To 12
a(i) = i
Next i
st = ListSubsets(a)
ary = Split(st, vbCrLf)
For i = LBound(ary) To UBound(ary)
Cells(i + 1, 1) = ary(i)
Next i
End Sub
Function ListSubsets(Items As Variant) As String
Dim CodeVector() As Integer
Dim i As Integer
Dim lower As Integer, upper As Integer
Dim SubList As String
Dim NewSub As String
Dim done As Boolean
Dim OddStep As Boolean
OddStep = True
lower = LBound(Items)
upper = UBound(Items)
ReDim CodeVector(lower To upper) 'it starts all 0
Do Until done
'Add a new subset according to current contents
'of CodeVector
NewSub = ""
For i = lower To upper
If CodeVector(i) = 1 Then
If NewSub = "" Then
NewSub = Items(i)
Else
NewSub = NewSub & ", " & Items(i)
End If
End If
Next i
If NewSub = "" Then NewSub = "{}" 'empty set
SubList = SubList & vbCrLf & NewSub
'now update code vector
If OddStep Then
'just flip first bit
CodeVector(lower) = 1 - CodeVector(lower)
Else
'first locate first 1
i = lower
Do While CodeVector(i) <> 1
i = i + 1
Loop
'done if i = upper:
If i = upper Then
done = True
Else
'if not done then flip the *next* bit:
i = i + 1
CodeVector(i) = 1 - CodeVector(i)
End If
End If
OddStep = Not OddStep 'toggles between even and odd steps
Loop
ListSubsets = SubList
End Function
The original question and answer:
John Coleman

matching text EXACTLY within vba IF statement

I looked through the questions here and though there is a lot of stuff about matching similar strings with the instr function etc, there isn't much about exact matching.
I'm looping through a list of names classified by id where each id has its own corresponding benchmark. Unfortunately all the benchmark names are something along the lines of "Barclays" x Index where there are a ton of similar sounding names such as Barclays US Aggregate Index, Barclays Intermediate Us Aggregate Index etc... and just trying to match gives an output.. but the wrong data points. Here is my code for reference.. the issue is in 2nd elseif of the loop.
I was wondering if there is an easy method to resolve this.
For i = 1 To lastrow
Sheets(source).Activate
If source = "Historical" Then
If Range("A" & i).Value = delimit2 Then
benchmark_name = Sheets(source).Range("L" & i).Value
j = j + 10
name = Sheets(source).Range("A" & i + 1).Value
Sheets(output_sht).Range("D" & j - 3) = "Portfolio"
Sheets(output_sht).Range("E" & j - 3) = benchmark_name
ElseIf benchmark_name <> vbNullString _
And Range("A" & i).Value = benchmark_name Then
If IsNumeric(Sheets(source).Range("F" & i).Value) Then
Alt_return3 = Sheets(source).Range("F" & i).Value
If IsEmpty(Sheets(output_sht).Cells(j, col1)) Then
Sheets(output_sht).Cells(j, col1) = Alt_return3 / 100
End If
End If
If IsNumeric(Sheets(source).Range("G" & i).Value) Then
Alt_return5 = Sheets(source).Range("G" & i).Value
If IsEmpty(Sheets(output_sht).Cells(j + 1, col1)) Then
Sheets(output_sht).Cells(j + 1, col1) = Alt_return5 / 100
End If
End If
'
If IsNumeric(Sheets(source).Range("H" & i).Value) Then
Alt_returnINC = Sheets(source).Range("H" & i).Value
If IsEmpty(Sheets(output_sht).Cells(j + 2, col1)) Then
Sheets(output_sht).Cells(j + 2, col1) = Alt_returnINC / 100
End If
Sheets(output_sht).Range("D" & j & ":E" & j + 5).NumberFormat = "0.00%"
End If
Sheets(output_sht).Range("C" & j) = period
Sheets(output_sht).Range("C" & j + 1) = period2
Sheets(output_sht).Range("C" & j + 2) = period3
Else
End If
End If
Next i
Comment as answer because I cannot comment:
Aren't you looking for the Like operator?
And you should add to the top of your code: Option compare text
More info on the like operator
I know you are looking for an exact match. However you may want to consider trying a FuzzyMatch.
http://code.google.com/p/fast-vba-fuzzy-scoring-algorithm/source/browse/trunk/Fuzzy1
You can download/import this function to your workbook and then call it with the 2 strings/names you are comparing and it will return a score.
If I were you I'd loop through all the possible names and take the highest score.
Which in your case would be 100% if you are looking for an exact match.
This will add time to your procedure but it might help you.
===EDITED
========= Here is the code. Add this to your Module.
Option Explicit
Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
'To be placed in the Declarations area
'_____________________________________
Sub TestFuzzy()
Dim t As Long, a As Long, i As Long
t = GetTickCount
For i = 1 To 100000
a = Fuzzy("Sorin Sion", "Open Source")
Next
Debug.Print "Similarity score: " & a & "; " & i - 1 & " iterations took " & _
GetTickCount - t & " milliseconds"
End Sub
'TestFuzzy's result should look like:
'Similarity score: 0.3; 100000 iterations took 2094 milliseconds
'The test was done on an Intel processor at 3.2GHz
'_____________________________________
Public Function Fuzzy(ByVal s1 As String, ByVal s2 As String) As Single
Dim i As Integer, j As Integer, k As Integer, d1 As Integer, d2 As Integer, p As Integer
Dim c As String, a1 As String, a2 As String, f As Single, o As Single, w As Single
'
' ******* INPUT STRINGS CLEANSING *******
'
s1 = UCase(s1) 'input strings are converted to uppercase
d1 = Len(s1)
j = 1
For i = 1 To d1
c = Mid(s1, i, 1)
Select Case c
Case "0" To "9", "A" To "Z" 'filter the allowable characters
a1 = a1 & c 'a1 is what remains from s1 after filtering
j = j + 1
End Select
Next
If j = 1 Then Exit Function 'if s1 is empty after filtering
d1 = j - 1
s2 = UCase(s2)
d2 = Len(s2)
j = 1
For i = 1 To d2
c = Mid(s2, i, 1)
Select Case c
Case "0" To "9", "A" To "Z"
a2 = a2 & c
j = j + 1
End Select
Next
If j = 1 Then Exit Function
d2 = j - 1
k = d1
If d2 < d1 Then
'to prevent doubling the code below s1 must be made the shortest string,
'so we swap the variables
k = d2
d2 = d1
d1 = k
s1 = a2
s2 = a1
a1 = s1
a2 = s2
Else
s1 = a1
s2 = a2
End If
If k = 1 Then 'degenerate case, where the shortest string is just one character
If InStr(1, s2, s1, vbBinaryCompare) > 0 Then
Fuzzy = 1 / d2
Else
Fuzzy = 0
End If
Else '******* MAIN LOGIC HERE *******
i = 1
f = 0
o = 0
Do 'count the identical characters in s1 and s2 ("frequency analysis")
p = InStr(1, s2, Mid(s1, i, 1), vbBinaryCompare)
'search the character at position i from s1 in s2
If p > 0 Then 'found a matching character, at position p in s2
f = f + 1 'increment the frequency counter
s2 = Left(s2, p - 1) & "~" & Mid(s2, p + 1)
'replace the found character with one outside the allowable list
'(I used tilde here), to prevent re-finding
Do 'check the order of characters
If i >= k Then Exit Do 'no more characters to search
If Mid(s2, p + 1, 1) = Mid(s1, i + 1, 1) Then
'test if the next character is the same in the two strings
f = f + 1 'increment the frequency counter
o = o + 1 'increment the order counter
i = i + 1
p = p + 1
Else
Exit Do
End If
Loop
End If
If i >= k Then Exit Do
i = i + 1
Loop
If o > 0 Then o = o + 1 'if we got at least one match, adjust the order counter
'because two characters are required to define "order"
finish:
w = 2 'Weight of characters order match against characters frequency match;
'feel free to experiment, to get best matching results with your data.
'If only frequency is important, you can get rid of the second Do...Loop
'to significantly accelerate the code.
'By altering a bit the code above and the equation below you may get rid
'of the frequency parameter, since the order counter increments only for
'identical characters which are in the same order.
'However, I usually keep both parameters, since they offer maximum flexibility
'with a variety of data, and both should be maintained for this project
Fuzzy = (w * o + f) / (w + 1) / d2
End If
End Function
==================
So once you have it then just add something like this.
Dim strA, strB, hiScore(1 to 3), tempScore
With Thisworkbook.ActiveSheet
For a = 1 to .Usedrange.Rows.Count ' Scans Column 1
strA = .cells(a,1) ' Barclays Aggregate Index
For b = 1 to .usedrange.rows.count ' Compares Col 1 to Col 2
strB = .cells(b,2) ' Barclays Aggregate Other Index
tempScore = Fuzzy(strA, strB)
If tempScore > hiScore then
hiScore(1) = tempScore
hiScore(2) = a
hiScore(3) = b
End If
Next b
' Do your Action with the Best Match Here
If hiScore(1) = 1 then ' (100% - perfect match)
' Copies col 3 from the row that the best strB match was on
' to col 4 from the row strA was on
.Cells(a,4) = .Cells(hiScore(3),3)
End If
' ====
' Reset Variables
hiScore = ""
tempScore = ""
Next a
End with