I am looking some help with a code to compare 2 strings and rank them in accordance of their matching with the original criteria. The code should ignore the sequence, For example, A1 contains words "Jon Smith" (original value), and B1 "Smith Jon", which are the same ranking. But if C1 contains "Jon Smith Junior", this should have a lower rank than "Jon Smith" or "Smith Jon".
Any one can help?
StackOverflow is not a coding service, and you should provide your code, but in this case I was interested in the task. Here is a possible solution.
Run the checkme - it simply takes the two strings and splits them into arrays. Then it counts how many times the values in arrOne present in arrTwo. With this information, it gives some kind of a result.
Option Explicit
Public Function CompareTwo(strOne As String, strTwo As String) As Double
Dim arrOne As Variant
Dim arrTwo As Variant
Dim varOne As Variant
Dim varTwo As Variant
Dim lngCounter As Long
arrOne = Split(strOne)
arrTwo = Split(strTwo)
For Each varOne In arrOne
For Each varTwo In arrTwo
If varOne = varTwo Then
lngCounter = lngCounter + 1
End If
Next varTwo
Next varOne
CompareTwo = lngCounter / (UBound(arrOne) + 1)
End Function
Public Sub CheckMe()
Debug.Print CompareTwo("Smith Jon", "Jon Smith")
Debug.Print CompareTwo("Jon Smith Junior", "Jon Smith")
Debug.Print CompareTwo("Jon Smith Junior Ale 6", "Jon Smith Ale 6")
End Sub
I came up with this one. It creates two arrays, one where the two key names are found within a given cell in Column B, and another with how many words are in each array element of arr1. Then it sends the two arrays to a Sort2 Sub, which was written by member Gary's Student and can be found here. It presumes the multiple choice names are in column "B" and that "Jon" and "Smith" are hardcoded - but can be drawn from another column with a little change to the code.
Column B contains:
Jon Smith
Smith Jon Junior
Smith Jon
Sub create2arr()
Dim myArr() As Variant, name1 As String, name2 As String, firstMarker As Boolean, myArrayCounter As Long, myArray2Counter As Long
Dim splitArr() As String, wordCountArr() As Variant
name1 = "Jon"
name2 = "Smith"
ReDim myArr(1 To 1)
ReDim myArr2(1 To 1)
ReDim wordCountArr(1 To 1)
myArrayCounter = 1
myArray2Counter = 1
For I = 1 To 3
splitArr = Split(Sheet6.Range("B" & I))
For J = LBound(splitArr) To UBound(splitArr)
If UCase(splitArr(J)) = UCase(name1) Or UCase(splitArr(J)) = UCase(name2) Then
If firstMarker = True Then
myArr(myArrayCounter) = Sheet6.Range("B" & I)
wordCountArr(myArrayCounter) = UBound(splitArr) + 1
myArrayCounter = myArrayCounter + 1
ReDim Preserve myArr(1 To myArrayCounter)
ReDim Preserve wordCountArr(1 To myArrayCounter)
firstMarker = False
Else
firstMarker = True
End If
End If
Next J
Next I
For I = 1 To UBound(myArr)
Debug.Print myArr(I)
Next I
Call sort2(wordCountArr, myArr)
For I = 1 To UBound(myArr)
Debug.Print myArr(I)
Next I
End Sub
Sub sort2(key() As Variant, other() As Variant)
Dim I As Long, J As Long, Low As Long
Dim Hi As Long, Temp As Variant
Low = LBound(key)
Hi = UBound(key)
J = (Hi - Low + 1) \ 2
Do While J > 0
For I = Low To Hi - J
If key(I) > key(I + J) Then
Temp = key(I)
key(I) = key(I + J)
key(I + J) = Temp
Temp = other(I)
other(I) = other(I + J)
other(I + J) = Temp
End If
Next I
For I = Hi - J To Low Step -1
If key(I) > key(I + J) Then
Temp = key(I)
key(I) = key(I + J)
key(I + J) = Temp
Temp = other(I)
other(I) = other(I + J)
other(I + J) = Temp
End If
Next I
J = J \ 2
Loop
End Sub
Related
I have a table with two Columns Product and Price($).
Product
Price($)
A
100
B
400
C
350
D
50
E
515
F
140
I am trying to use vba to get combination of value of all products that will not exceed $500. I have been trying with this code and I am not sure how to proceed from this point on.
Sub getCombination()
Dim price As Long
Dim limit As Long
Dim i As Integer
Dim j As Integer
Dim combination As String
limit = 500
combination = ""
Range("B2").Activate
price = Range("B2").Value
For i = 1 To 6
For j = 1 To 6
If price <= limit Then
price = price + ActiveCell.Offset(j, 0).Value
combination = combination & ActiveCell.Offset(0, -1).Value & "," & ActiveCell.Offset(1, -1).Value
End If
Next j
Next i
ActiveCell.Offset(1, 0).Activate
MsgBox combination
End Sub
My Expected output is something like
A,B
A,C
A,C,D
B,D
C,F
A,D
C,D
(Please note: Not All output combinations are specified here!)
How should I proceed with the existing code? Or do I really have a better way for me to implement this?
Since the item can be used or not, that is a binary response. Using a binary number with the same number of digits as the number of items we can do all the combinations and do the testing:
Sub getCombination()
Dim rngArr As Variant
rngArr = ActiveSheet.Range("A2:B7")
Dim cnt As Long
cnt = 2 ^ UBound(rngArr, 1) - 1
Dim OutArray As Variant
ReDim OutArray(1 To cnt, 1 To 2)
Dim k As Long
k = 1
Dim i As Long
For i = 1 To cnt
Dim bin As String
bin = Application.Dec2Bin(i, UBound(rngArr, 1))
Dim delim As String
delim = ""
Dim j As Long
For j = 1 To UBound(rngArr, 1)
If Mid(bin, j, 1) = "1" Then
OutArray(k, 1) = OutArray(k, 1) & delim & rngArr(j, 1)
delim = ", "
OutArray(k, 2) = OutArray(k, 2) + rngArr(j, 2)
End If
Next j
If OutArray(k, 2) <= 500 Then
k = k + 1
Else
OutArray(k, 1) = ""
OutArray(k, 2) = 0
End If
Next i
Dim fnlarr As Variant
ReDim fnlarr(1 To k - 1)
For i = 1 To k - 1
fnlarr(i) = OutArray(i, 1)
Next i
Debug.Print Join(fnlarr, " | ")
End Sub
I am trying to write a simple randomizing program that reads from a column of names and randomly writes them to three columns of four. I have something that kind of works, but it is duplicating my names and I can figure out how to fix it with arrays or collections as those wont let me compare values. Thank you in advance.
Goal: randomization without doubling up two names
Problem: comparing and writing (to worksheet) collection and/or arrays
Option Explicit
Private Sub Randomize_Click()
Dim HowMany As Integer
Dim NoOfNames As Long
Dim RandomNumber As Integer
Dim Names(), dub() As String 'Array to store randomly selected names
Dim i, j, r, a, p As Byte
Dim CellsOut As Long 'Variable to be used when entering names onto worksheet
Dim ArI As Byte 'Variable to increment through array indexes
Application.ScreenUpdating = False
HowMany = 4 ' use with a third loops?
CellsOut = 4
For a = 1 To 6
For r = 1 To 3
For j = 2 To 5
'CellsOut = i 'turn this into loops
ReDim Names(1 To 4) 'Set the array size to how many names required
NoOfNames = Application.CountA(Worksheets("Employees").Range("A:A")) - 1 ' Find how many
names in the list
i = 1
Do While i <= HowMany
RandomNo:
RandomNumber = Application.RandBetween(2, NoOfNames + 1)
dub = RandomNumber
'dub.Add Unit.Value
If Names(i) = Cells(RandomNumber, 1).Value Then
'If Names(i) = dub(Unit) Then
GoTo RandomNo
End If
Names(i) = Worksheets("Employees").Cells(RandomNumber, 1).Value ' Assign random
name to the array
i = i + 1 '
Loop
'Loop through the array and enter names onto the worksheet
For ArI = LBound(Names) To UBound(Names)
Cells(CellsOut, j) = Names(ArI)
CellsOut = CellsOut + 1
Next ArI
Application.ScreenUpdating = True
CellsOut = 4
Next j
Next r
Next a
End Sub
Display
Names
Random Names
Current Setup
This solution uses the dictionary to randomize numbers which I was exploring earlier today.
The complete code goes into a standard module.
Adjust the three constants at the beginning of randomizeNames.
You only run randomizeNames, e.g. via a command button:
Private Sub Randomize_Click()
randomizeNames
End Sub
The Code
Option Explicit
Sub randomizeNames()
' Constants
Const srcFirst As String = "A2"
Const NoC As Long = 3
Const tgtFirst As String = "C2"
' Define Source First Cell Range ('cel').
Dim cel As Range
Set cel = Range(srcFirst)
' Define Source Last Cell Range ('rng').
Dim rng As Range
Set rng = Cells(Rows.Count, cel.Column).End(xlUp)
' Define Source Column Range ('rng').
Set rng = Range(cel, rng)
' Define Number of Elements (names) ('NoE').
Dim NoE As Long
NoE = rng.Rows.Count
' Write values from Source Column Range to Source Array ('Source').
Dim Source As Variant
If NoE > 1 Then
Source = rng.Value
Else
ReDim Source(1 To 1, 1 To 1)
Source(1, 1) = rng.Value
End If
' Define Random Numbers Array ('RNA').
Dim RNA As Variant
' This line uses both functions.
RNA = getDictionary(Dictionary:=getRandomDictionary(1, NoE), _
FirstOnly:=True)
' Instead of numbers, write elements from Source Array
' to Random Number Array (Random Names Array).
Dim i As Long
For i = 1 To NoE
RNA(i, 1) = Source(RNA(i, 1), 1)
Next i
' Define Number of Rows in Target Array ('NoR') and the Remainder
' of elements ('Remainder').
Dim NoR As Long
NoR = Int(NoE / NoC)
Dim Remainder As Long
Remainder = NoE Mod NoC
If Remainder > 0 Then
NoR = NoR + 1
Else
Remainder = NoC
End If
' Define Target Array ('Target').
Dim Target As Variant
ReDim Target(1 To NoR, 1 To NoC)
' Declare additional variables.
Dim j As Long ' Target Array Columns Counter
Dim k As Long ' Random Names Array Rows Counter
' Write values from Random Names Array to Target Array.
For i = 1 To NoR - 1
For j = 1 To NoC
k = k + 1
Target(i, j) = RNA(k, 1)
Next j
Next i
For j = 1 To Remainder
k = k + 1
Target(i, j) = RNA(k, 1)
Next j
' Define Target First Cell Range ('cel').
Set cel = Range(tgtFirst)
' Clear contents from Target First Cell Range to bottom-most cell
' of last column of Target Range.
cel.Resize(Rows.Count - cel.Row + 1, NoC).ClearContents
' Write values from Target Array to Target Range.
Range(tgtFirst).Resize(NoR, NoC).Value = Target
End Sub
Function getRandomDictionary(ByVal LowOrHigh As Long, _
ByVal HighOrLow As Long) _
As Object
' Define Numbers Dictionary ('dict').
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
' Define the lower number ('Minimum') and the Number of Elements ('NoE').
Dim NoE As Long
Dim Minimum As Long
If LowOrHigh < HighOrLow Then
Minimum = LowOrHigh
NoE = HighOrLow - LowOrHigh + 1
Else
Minimum = HighOrLow
NoE = LowOrHigh - HighOrLow + 1
End If
' Write random list of numbers to Numbers Dictionary.
Dim Current As Long
Do
' Randomize ' Takes considerably longer.
Current = Int(Minimum + NoE * Rnd)
dict(Current) = Empty
Loop Until dict.Count = NoE
' Write result.
Set getRandomDictionary = dict
End Function
Function getDictionary(Dictionary As Object, _
Optional ByVal Horizontal As Boolean = False, _
Optional ByVal FirstOnly As Boolean = False, _
Optional ByVal Flip As Boolean = False) _
As Variant
' Validate Dictionary.
If Dictionary Is Nothing Then
GoTo ProcExit
End If
Dim NoE As Long
NoE = Dictionary.Count
If NoE = 0 Then
GoTo ProcExit
End If
' Write values from Dictionary to Data Array ('Data').
Dim Data As Variant
Dim Key As Variant
Dim i As Long
If Not Horizontal Then
If Not FirstOnly Then
ReDim Data(1 To NoE, 1 To 2)
If Not Flip Then
For Each Key In Dictionary.Keys
i = i + 1
Data(i, 1) = Key
Data(i, 2) = Dictionary(Key)
Next Key
Else
For Each Key In Dictionary.Keys
i = i + 1
Data(i, 1) = Dictionary(Key)
Data(i, 2) = Key
Next Key
End If
Else
ReDim Data(1 To NoE, 1 To 1)
If Not Flip Then
For Each Key In Dictionary.Keys
i = i + 1
Data(i, 1) = Key
Next Key
Else
For Each Key In Dictionary.Keys
i = i + 1
Data(i, 1) = Dictionary(Key)
Next Key
End If
End If
Else
If Not FirstOnly Then
ReDim Data(1 To 2, 1 To NoE)
If Not Flip Then
For Each Key In Dictionary.Keys
i = i + 1
Data(1, i) = Key
Data(2, i) = Dictionary(Key)
Next Key
Else
For Each Key In Dictionary.Keys
i = i + 1
Data(1, i) = Dictionary(Key)
Data(2, i) = Key
Next Key
End If
Else
ReDim Data(1 To 1, 1 To NoE)
If Not Flip Then
For Each Key In Dictionary.Keys
i = i + 1
Data(1, i) = Key
Next Key
Else
For Each Key In Dictionary.Keys
i = i + 1
Data(1, i) = Dictionary(Key)
Next Key
End If
End If
End If
' Write result.
getDictionary = Data
ProcExit:
End Function
List of US Top 30 Names
James
John
Robert
Michael
William
Mary
David
Joseph
Richard
Charles
Thomas
Christopher
Daniel
Elizabeth
Matthew
Patricia
George
Jennifer
Linda
Anthony
Barbara
Donald
Paul
Mark
Andrew
Edward
Steven
Kenneth
Margaret
Joshua
I have a small VBA Loop but it takes over 2-3 minute to finish, any idea how I can speed up/rewrite it that it will be faster?
The Range "Replace Names" is a List of 500 names of named areas in "Data".
The for loop searches for the one that matches the name in "Data" and replaces the one with the name from "Source". This also works fine, but it takes a while. Is there a faster method?
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
For Each ID_name In wsSupport.Range("ReplaceNames")
wsCheck.Range("Data").Replace ID_name, wsSource.Range(ID_name), xlWhole
Next
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
You might benefit from this valuable piece of text:
https://www.microsoft.com/en-us/microsoft-365/blog/2009/03/12/excel-vba-performance-coding-best-practices/
So in your case the Code could somewhat look like this:
Dim arrData as Variant, arrSource as Variant, k as long
arrData = wsCheck.Range("Data").value2 'this creates a two-dimensional array with rows on the first and columns on the second index
arrSource = wsSource.Range(...).value2
'loop through rows I suppose
for k = LBound(arrData,1) to UBound(arrData,1)
if arrData(k, yourColumn) = ... then
arrData(k, yourColumn) = arrSource(rowhere, columnhere)
endif
next k
wscheck.range("Data") = arrData
Its working now!!!
If you see mistakes let me know!
Dim arrData As Variant, arrSource As Variant, arrNames As Variant, k As Long
arrData = wsChecklist.Range("Checklist").Value2 'this creates a two-dimensional array with rows on the first and columns on the second index
arrSource = wsSupport.Range("ReplaceNames").Value2
arrNames = wsNia.Range("D1:D1000").Value2
'loop through rows I suppose
For k = LBound(arrData, 1) To UBound(arrData, 1)
For j = LBound(arrData, 2) To UBound(arrData, 2)
' If UCase(arrData(k, j)) = UCase(arrSource(x, 1)) Then
If UCase(arrSource(x, 1)) = UCase(arrData(k, j)) Then
For i = 1 To 1000
Name1 = wsNia.Cells(i, 2)
Name2 = wsNia.Cells(i, 3)
Name = Name1 & "_" & Name2
If UCase(arrData(k, j)) = UCase(Name) Then
arrData(k, j) = arrNames(i, 1)
x = x + 1
k = 1
j = 1
i = 1
Exit For
End If
Next i
End If
If k > 2900 And x < 265 Then
x = x + 1
j = 1
k = 1
End If
Next j
Next k
wsChecklist.Range("Checklist").Value2 = arrData
I have about 15 Address that are formatted like, 1111 Really Cool Street, Sweet City, Awesome State I need to split them up into separate cells for their Street, City and State.
The problem I have currently is storing the Street,City and State. I've tried something along the lines..
Dim addressArray() As String
posRow = 1
Do
posRow = posRow + 1
addressFromCell= Sheet1.Cells(posRow , "C")
addressArray() = Split(addressFromCell, ",")
Sheet2.Cells(posRow , "A") = addressArray(0)
Sheet2.Cells(posRow , "B") = addressArray(1)
Sheet2.Cells(posRow , "C") = addressArray(2)
Loop Until posRow = 15
I thought addressArray would look like {"1111 Really Cool Street","Sweet City", "Awesome State"} and I could simply pass in the element I wanted. Instead I keep on getting
Run-time error '9': Subscript out of range
Any help would be great, thanks!
As Ken White pointed out, the reason for the error is that one of the values doesn't contain 2 commas.
Here is how I would do it:
Dim addressArray() As String
posRow = 1
Do
posRow = posRow + 1
addressArray() = Split(Sheet1.Cells(posRow, "C").Value, ",")
Sheet2.Cells(posRow, "A").Resize(1, UBound(addressArray) + 1).Value = addressArray()
Loop Until posRow = 15
This code uses the UBound function to retrieve the Upper Bound (number of the last element) for the Array and ensures that the Range is always the same size as the Array.
To avoid any such error, you should check if the cell is not blank and the address in the cell is not the complete address.
posRow = 1
Do
posRow = posRow + 1
addressFromCell = Sheet1.Cells(posRow, "C")
If addressFromCell <> "" Then
addressArray() = Split(addressFromCell, ",")
If UBound(addressArray) = 2 Then
Sheet2.Cells(posRow, "A") = addressArray(0)
Sheet2.Cells(posRow, "B") = addressArray(1)
Sheet2.Cells(posRow, "C") = addressArray(2)
End If
End If
Loop Until posRow = 15
Try,
Sub test()
Dim vArray
Dim vDB, vR()
Dim i As Long, r As Long, j As Integer
Dim k As Integer
Dim s, e
's = Timer
vDB = Range("c2", "c15")
r = UBound(vDB, 1)
ReDim vR(1 To r, 1 To 3)
For i = 1 To r
vArray = Split(vDB(i, 1), ",")
k = UBound(vArray)
If k > 0 And k < 3 Then
For j = 1 To k
vR(i, j) = vArray(j - 1)
Next j
End If
Next i
With Sheet2
.Range("a2").Resize(r, 3) = vR
End With
'e = Timer
'Debug.Print "Test() time : " & (e - s)
End Sub
I am trying to organise a list of data "a,b,c,d,e,...." into all possible combinations of 1,2,3....n elements.
For example:
a,b,c,d,e
a
b
c
d
e
a,b
a,c
a,d
a,e
b,c
b,d
d,e
...
and so on.
So far I have only come across people who have written code to find the combinations to two sets of data rather than one.
Would you know where to start?
In my head it would be similar to the following so it would run systematically, and stops any repeats of the permutations. So essentially I would be running a loop inside another loop 4 or 5 different times.
i
i+1
i+...n
i,j+1
.
.
.
i,j,k,l....
I asked a similar question about 10 years ago and got a great answer from John Coleman:
Gray Code
Here is his solution:
'If you run TestThis, then for example the second message box returns
'
'{}
'dog
'dog , cat
'cat
'cat , mouse
'dog , cat, mouse
'dog , mouse
'mouse
'mouse , zebra
'dog , mouse, zebra
'dog , cat, mouse, zebra
'cat , mouse, zebra
'cat , zebra
'dog , cat, zebra
'dog , zebra
'zebra
'
'Hope this helps,
'
'John Coleman
'p.s. The algorithm used to generate the Gray code comes from the
'excellent book "Combinatorial Algorithms: Generation, Enumeration and
'Search " by Kreher and Stinson."
and the code:
Sub TestThis()
Dim i As Integer
Dim A(3 To 7) As Integer
Dim B As Variant
For i = 3 To 7
A(i) = i
Next i
B = Array("dog", "cat", "mouse", "zebra")
MsgBox ListSubsets(A)
MsgBox ListSubsets(B)
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
I know this question is old, but I wrote the code before finding John Coleman's option.
In order to find different rank combinations (i.e. sets of 2,3,4 items) I put the list of items in ColumnA of a clean sheet, with a header, and call it with something like:
Sub call_listcombos()
Dim sht as Worksheet, outrn As Range
Dim n As Integer, r As Integer, rto As Integer
Dim poslist()
Application.ScreenUpdating = False
Set sht = ActiveSheet
n = sht.Range("A1").CurrentRegion.Rows.Count - 1
poslist() = Application.Transpose(sht.Range("A2").Resize(n).Value2)
rto = 2
Do While Application.Combin(n, rto + 1) < 250000
DoEvents
rto = rto + 1
Loop
For r = 2 To rto
Set outrn = sht.Range("A1").Offset(sht.Range("A1").CurrentRegion.Rows.Count)
Call list_combos(poslist(), r, outrn)
Next r
Application.ScreenUpdating = True
End Sub
The code:
Private Sub list_combos(items() As Variant, r As Integer, outrange As Range)
'receives a 1-D variant array and outputs a single column with nCr combinations
'selecting r items without replacement... n > r > 1 :: integers
Dim n As Integer, i As Integer, ri As Integer, outi As Long
Dim comboindex(), comboitems()
n = UBound(items) - LBound(items) + 1
outi = Application.Combin(n, r)
'test output range
If outrange.Row + outi > 1000000 Then
MsgBox "Too many combinations! Will not fit in output range."
Exit Sub
End If
If Application.CountA(outrange.Resize(outi)) > 0 Then
MsgBox "Output range is not empty!"
Exit Sub
End If
'initialize combinations
ReDim comboindex(1 To r)
ReDim comboitems(1 To r)
For ri = 1 To r
comboindex(ri) = LBound(items) + ri - 1 'sets comboindex's base to items' base
comboitems(ri) = items(comboindex(ri))
Next ri
'loop combinations
ri = r
outi = 0
Do While comboindex(ri) <= UBound(items)
DoEvents
For i = comboindex(ri) To UBound(items)
comboindex(ri) = i
comboitems(ri) = items(comboindex(ri))
outrange.Offset(outi).Value2 = Join(comboitems, ";")
outi = outi + 1
Next i
ri = ri - 1
Do While comboindex(ri) + 1 = comboindex(ri + 1)
DoEvents
If ri = 1 Then Exit Do
ri = ri - 1
Loop
comboindex(ri) = comboindex(ri) + 1
comboitems(ri) = items(comboindex(ri))
Do While ri < r
DoEvents
ri = ri + 1
comboindex(ri) = comboindex(ri - 1) + 1
If comboindex(ri) > UBound(items) Then Exit Do
comboitems(ri) = items(comboindex(ri))
Loop
Loop
End Sub