I am having troubles creating a small program to check if characters in a cell are straight under certain conditions. Let's say for instance that a cell contains the following characters "23465148", what I would like to know is whether 5 characters of this cell represent a straight chain of number (in this case yes, 12345).
The problem I have is the following one. I was only able to automate this verification on celles that contains 5 characters. But for cells that contain >5 characters, I am not sure how to proceed because I need to check every combinations of characeters in this cell.
Another problem I have is when the cell contain <5 or 5 or >5 characters. Whatever the number of characters, I would like to see how many of them are straight even if there are less than 5.
Below is the code I'm currently using. Note that in this case, the code is written for a card game (poker)
Do you have any suggestion I could include to complete my code?
Thank you
'This function will return TRUE if the 5 cards are a Straight
'sHand is a string corresponding to the 5 cards in hand, for example : 237TK
Function IsAStraight(sHand As String) As Boolean
Dim arCards(1 To 5, 1 To 2)
Dim i As Integer, sSortedHand As String
Const sStraight As String = "KQJT98765432A|KQJTA"
Const sCardsRanked As String = "A23456789TJQK"
'Get the cards values
For i = 1 To 5
arCards(i, 1) = Mid(sHand, i, 1)
arCards(i, 2) = InStr(1, sCardsRanked, Mid(sHand, i, 1))
Next i
'Sort by value descending
Sort2DVert arCards, 2, "D"
'Sorted hand
For i = 1 To 5
sSortedHand = sSortedHand & arCards(i, 1)
Next i
'Check if this is a straight
IsAStraight = InStr(1, sStraight, sSortedHand) > 0
End Function
'Sort a 2D Array
Public Sub Sort2DVert(avArray As Variant, iKey As Integer, sOrder As String, Optional iLow1, Optional iHigh1)
Dim iLow2 As Long, iHigh2 As Long, i As Long
Dim vItem1, vItem2 As Variant
On Error GoTo PtrExit
If IsMissing(iLow1) Then iLow1 = LBound(avArray)
If IsMissing(iHigh1) Then iHigh1 = UBound(avArray)
iLow2 = iLow1: iHigh2 = iHigh1
vItem1 = avArray((iLow1 + iHigh1) \ 2, iKey)
'Loop for all the items in the array between the extremes
Do While iLow2 < iHigh2
If sOrder = "A" Then
Do While avArray(iLow2, iKey) < vItem1 And iLow2 < iHigh1: iLow2 = iLow2 + 1: Loop
Do While avArray(iHigh2, iKey) > vItem1 And iHigh2 > iLow1: iHigh2 = iHigh2 - 1: Loop
Else
Do While avArray(iLow2, iKey) > vItem1 And iLow2 < iHigh1: iLow2 = iLow2 + 1: Loop
Do While avArray(iHigh2, iKey) < vItem1 And iHigh2 > iLow1: iHigh2 = iHigh2 - 1: Loop
End If
If iLow2 < iHigh2 Then
For i = LBound(avArray, 2) To UBound(avArray, 2)
vItem2 = avArray(iLow2, i)
avArray(iLow2, i) = avArray(iHigh2, i)
avArray(iHigh2, i) = vItem2
Next
End If
If iLow2 <= iHigh2 Then
iLow2 = iLow2 + 1
iHigh2 = iHigh2 - 1
End If
Loop
If iHigh2 > iLow1 Then Sort2DVert avArray, iKey, sOrder, iLow1, iHigh2
If iLow2 < iHigh1 Then Sort2DVert avArray, iKey, sOrder, iLow2, iHigh1
PtrExit:
End Sub
You could do something like this:
Sub tester()
Dim arr, runs As Collection, run, hand
arr = Array("7A596A8KQ", "787878", "A87", "A2QJ4K3", "468JK")
For Each hand In arr
Debug.Print "---------" & hand & "---------"
Set runs = Straights(CStr(hand))
For Each run In runs
Debug.Print " - " & run
Next run
Next hand
End Sub
'return a collection of all runs in `sHand`
Function Straights(sHand As String) As Collection 'of strings
Const ranked As String = "A23456789TJQK"
Dim i As Long, run As Boolean, rlen As Long
Dim rStart As Long, arr() As Long, p As Long
Dim hadRun As Boolean, runs As New Collection, last As Boolean
ReDim arr(1 To Len(ranked))
'first count all cards in the hand
For i = 1 To Len(sHand)
p = InStr(1, ranked, Mid(sHand, i, 1))
arr(p) = arr(p) + 1
Next i
'now check for runs: keep looping over `arr` until no more runs are found
Do
hadRun = False 'reset flag
For i = 2 To UBound(arr)
last = (i = UBound(arr)) 'last element?
run = arr(i) > 0 And arr(i - 1) > 0 'in a run?
If run Then
hadRun = True 'flag found a run in this go round
If rlen = 0 Then
rStart = i - 1 'new run: record start position
arr(i - 1) = arr(i - 1) - 1 'decrement count at i-1
rlen = 2 'set initial length
Else
arr(i) = arr(i) - 1 'decrement count at i
rlen = rlen + 1
End If
End If
If Not run Or (run And last) Then 'at end, or end of a run?
If rlen > 0 Then 'previously in a run?
runs.Add Mid(ranked, rStart, rlen) 'add run to output
rlen = 0 'reset run length
End If
End If
Next i
rlen = 0
If Not hadRun Then Exit Do 'no more runs found
Loop 'keep checking as long as there was a run in this iteration
Set Straights = runs
End Function
Related
I am trying to figure out a loop logic to get all possible permutations where I add a set value to each item in a set array iLoop number of times. I'm gonna try my best to explain what I am looking for.
I have a set value "StrokeValue" and a set array "DistanceMatesArray"
Dim StrokeValue as single
Dim DistanceMatesArray as variant
StrokeValue = 300
DistanceMatesArray = Array(300, 300, 300, 300)
Now I need to loop through each possible result where I add StrokeValue to each Item which in the first loop would result in possible DistanceMatesArrays:
The tricky part is when I want to add StrokeValue more than once and get every outcome where I added StrokeValue iLoop number of time "AllowedActions" resulting in a list such as:
I kind of suspect that I need a 2D array to store all the results from previous loop., that's why in the example the rows are coloured to indicate which one row was taken as a starting point to add the StrokeValue once
What I got so far looks like this:
Public StrokeValue As Single
Public DistanceMatesArray As Variant
Public iError As Long
Public NumberOfCombinations As Long
Public x As Long
Public y As Long
Public i As Long
Option Explicit
Sub Test()
'Declare variables
Dim PreviousLoopResultsArray As Variant
Dim NextLoopResultsArray As Variant
Dim iresults As Long
Dim iLoop As Long
Dim iPreviousResult As Long
'Set variables
StrokeValue = 300
'Array
DistanceMatesArray = Array(300, 300, 300, 300)
ReDim NextLoopResultsArray(0, UBound(DistanceMatesArray))
For i = LBound(DistanceMatesArray) To UBound(DistanceMatesArray)
NextLoopResultsArray(0, i) = DistanceMatesArray(i)
Next i
'------------------------------------------------------
'Loop
Do While iError = NumberOfCombinations
'Try DistanceMatesArray
For i = 0 To iresults
For x = 0 To UBound(DistanceMatesArray) 'Loop horizontally
DistanceMatesArray(x) = NextLoopResultsArray(i, x)
Next x
Debug.Print Join(DistanceMatesArray)
'TRY HERE
Next i
'Array
PreviousLoopResultsArray = NextLoopResultsArray
'Array
If iLoop <> 0 Then
For x = 0 To UBound(DistanceMatesArray) 'Loop horizontally
DistanceMatesArray(x) = PreviousLoopResultsArray(iPreviousResult, x)
Next x
End If
'Set variables
iLoop = iLoop + 1
iPreviousResult = 1
iresults = ((UBound(DistanceMatesArray) + 1) ^ iLoop) - 1
ReDim NextLoopResultsArray(iresults, UBound(DistanceMatesArray))
'Populate NextLoopResultsArray
For y = 0 To iresults 'Loop vertically
If y Mod (UBound(DistanceMatesArray) + 1) = 0 And y <> iresults And y <> 0 Then
For x = 0 To UBound(DistanceMatesArray) 'Loop horizontally
DistanceMatesArray(x) = PreviousLoopResultsArray(iPreviousResult, x)
Next x
iPreviousResult = iPreviousResult + 1
End If
For x = 0 To UBound(DistanceMatesArray) 'Loop horizontally
NextLoopResultsArray(y, x) = DistanceMatesArray(x)
With ThisWorkbook.Worksheets(1).Cells(y + 1, x + 1)
.Value = NextLoopResultsArray(y, x)
End With
Next x
Next y
'Modify NextLoopResultsArray
x = 0
For y = 0 To iresults 'Loop vertically
NextLoopResultsArray(y, x) = NextLoopResultsArray(y, x) + StrokeValue
With ThisWorkbook.Worksheets(1).Cells(y + 1, x + 1)
.Value = NextLoopResultsArray(y, x)
.Interior.Color = vbYellow
End With
If x + 1 > UBound(DistanceMatesArray) Then
x = 0
Else
x = x + 1
End If
Next y
'Set variables
iPreviousResult = 0
'Excel reset
For i = 1 To (UBound(DistanceMatesArray) + 1)
Columns(i).Clear
Next i
Loop
End Sub
At the end of the loop I am expecting to have each one row as DistanceMatesArray i.e. one of them would now be
DistanceMatesArray = array(300,600,600,300)
Where it added StrokeValue twice.
Would someone, please, help me figure out a shorter and simpler logic behind this?
EDIT:
Results expected after running it up to 3 loops looks like this:
And without duplicate outcomes
Continuing to try and figure out the logic of it, maybe now someone get's a betetr idea for what I am lookign for and can help
No need to mention that it's an infinite loop - I know that and That's the point, it needs to go on untill I validate the right array in which case iError <> NumberOfCombinations.
Been able to learn more about arrays, so I consider this a big win.
The code took in account the duplicates but for now your iterations are hardset (could easily ask how many iterations with an inputbox), not in the endless loop you had set up, hope that rework won't be too much.
Some variables are reworked, I tried to keep most of your original ones though.
Public StrokeValue As Single
Public DistanceMatesArray As Variant
Public iError As Long
Public iTerations As Long
Public i As Long
Public j As Long
Public k As Long
Option Explicit
Sub TestArrayfill()
Dim pArray As Variant, nArray As Variant, cArray As Variant 'pArray = previous array, nArray = next array, cArray = check array
Dim iresults As Long, iLoop As Long, nb As Long, actB As Long, addCounter As Long, Lastrow As Long
'Set variables
StrokeValue = 300
addCounter = 1
iTerations = 4
'Array
DistanceMatesArray = Array(300, 300, 300, 300)
nb = UBound(DistanceMatesArray) + 1
ReDim Preserve DistanceMatesArray(1 To nb)
cArray = DistanceMatesArray
ReDim pArray(1 To nb, 1 To nb)
For i = 1 To nb
pArray(1, i) = DistanceMatesArray(i)
Next i
actB = nb
For iLoop = 1 To iTerations 'I can't figure out the limitations with permutations so we'll just bruteforce it with nb*actB (maximum possibilities)
ReDim nArray(1 To nb * actB, 1 To nb) '(re)setting nArray
If iLoop = 1 Then actB = 1 'workaround to have pArray as a 2D-array
For i = 1 To actB 'loop through every row in pArray except for when iLoop = 1
For j = 1 To nb 'loop through every cell in pArray(i)
For k = 1 To nb 'setting the extra StrokeValue
If j = k Then
cArray(k) = pArray(i, k) + StrokeValue
Else
cArray(k) = pArray(i, k)
End If
Next k
If Not arrElemInArray(cArray, nArray) Then
For k = 1 To nb
nArray(addCounter, k) = cArray(k) 'add the "row" to our nArray
Next k
addCounter = addCounter + 1
End If
Next j
Next i
actB = addCounter - 1
ReDim pArray(1 To actB, 1 To nb) 'ReDim is possible on both dimensions, Redim Preserve is not so we use this to our advantage
For i = 1 To actB 'another loop is necessary however
For j = 1 To nb
pArray(i, j) = nArray(i, j)
Next j
Next i
' nArray = Application.Transpose(nArray)
' ReDim Preserve nArray(1 To nb, 1 To actB)
' nArray = Application.Transpose(nArray)
' pArray = Application.Transpose(pArray)
' ReDim pArray(1 To UBound(nArray, 2), UBound(nArray, 1))
' pArray = Application.Transpose(pArray)
' pArray = nArray
addCounter = 1
Lastrow = Range("A" & Rows.Count).End(xlUp).Row
If Lastrow = 1 Then
Cells(Lastrow, 1).Value = "Loop" & iLoop
Else
Cells(Lastrow + 1, 1).Value = "Loop " & iLoop
Lastrow = Lastrow + 1
End If
Cells(Lastrow + 1, 1).Resize(UBound(nArray, 1), UBound(nArray, 2)) = nArray
Next iLoop
End Sub
Function arrElemInArray(arr As Variant, arrX As Variant) As Boolean 'this is from one of your previous questions btw, just a bit modified to fit our needs
Dim i As Long, j As Long, boolFound As Boolean, mtch
If Not IsArray(arrX) Then
For j = LBound(arr) To UBound(arr)
If arr(j) = arrX Then arrElemInArray = True: Exit For
Next j
Exit Function
End If
For i = LBound(arrX) To UBound(arrX)
boolFound = True 'True at beginning so if any cells deviates from the corresponding check, it gets set to False, ergo it doesn't exist yet.
For j = LBound(arr) To UBound(arr)
If arr(j) <> arrX(i, j) Then
boolFound = False
End If
Next j
If boolFound Then arrElemInArray = True: Exit Function
Next i
arrElemInArray = False
End Function
Hope it's all clear and works for you :)
I understood your logic for the first table
but for the following ones I find it difficult to understand what you want especially in relation to the capture that you put in your message
for the first
Sub testing()
Dim StrokeValue As Single
Dim DistanceMatesArray As Variant
Dim i As Long 'variable row iteration
Dim c As Long 'variable column itération
Dim Table As Variant 'variable variant no dimention in the first
StrokeValue = 300
DistanceMatesArray = Array(300, 300, 300, 300) 'is an array in base 0
nb = UBound(DistanceMatesArray) + 1 'convert a ubound of DistanceMatesArray in count (in base 1)
ReDim Table(1 To nb, 1 To nb) 'table dimensioning (variant) in base 1
'loop for row
For i = 1 To UBound(Table) 'start at index 1
'loop for column
For c = 1 To UBound(Table, 2) 'start at index 1
'if index row and index column then item has multipled by (2)
If c <> i Then Table(i, c) = StrokeValue Else Table(i, c) = StrokeValue + StrokeValue
Next c
Next i
'just for see on sheet
Cells.Resize(UBound(Table), UBound(Table)) = Table
End Sub
I would like to ask you for help with creating a VBA code or python script that would combine all information in below screen like following:
There are 10 columns with in each there is one digit (or a dot). I would like to create a macro that would combine all possible combinations and created a list for me like:
KMFD...BAK
KMHD...BAK
KMJD...BAK
KMFD...CAK
KMFD...CAK
KMHD...CAK
KMJD...CAK
....
....
....
so in short to display me a concataned list whre every digit in each column is combined with every single digit from other columns.
Is that achievable with a macro?
Thank you in advance for any tips.
I would like to creata a VBA
From: VBA - Write all possible combinations of 4 columns of data
Sub ListCombinations()
Dim col As New Collection
Dim c As Range, sht As Worksheet, res
Dim i As Long, arr, numCols As Long
Set sht = ActiveSheet
'lists begin in A2, B2, C2, etc
For Each c In sht.Range("A2:J2").Cells
col.Add Application.Transpose(sht.Range(c, sht.Cells(Rows.Count, c.Column).End(xlUp)))
numCols = numCols + 1
Next c
res = Combine(col, "~~")
For i = 0 To UBound(res)
arr = Split(res(i), "~~")
sht.Range("L1").Offset(i, 0).Resize(1, numCols) = arr
Next i
End Sub
'create combinations from a collection of string arrays
Function Combine(col As Collection, SEP As String) As String()
Dim rv() As String
Dim pos() As Long, lengths() As Long, lbs() As Long, ubs() As Long
Dim t As Long, i As Long, n As Long, ub As Long
Dim numIn As Long, s As String, r As Long, v, tmp()
numIn = col.Count
ReDim pos(1 To numIn)
ReDim lbs(1 To numIn)
ReDim ubs(1 To numIn)
ReDim lengths(1 To numIn)
t = 0
For i = 1 To numIn 'calculate # of combinations, and cache bounds/lengths
'handle cases where only one value in a column (not passed in as array)
If Not TypeName(col(i)) Like "*()" Then
ReDim tmp(1 To 1)
tmp(1) = col(i)
col.Remove i
If i > col.Count Then
col.Add tmp
Else
col.Add tmp, Before:=i
End If
End If
lbs(i) = LBound(col(i))
ubs(i) = UBound(col(i))
lengths(i) = (ubs(i) - lbs(i)) + 1
pos(i) = lbs(i)
t = IIf(t = 0, lengths(i), t * lengths(i))
Next i
ReDim rv(0 To t - 1) 'resize destination array
For n = 0 To (t - 1)
s = ""
For i = 1 To numIn
s = s & IIf(Len(s) > 0, SEP, "") & col(i)(pos(i)) 'build the string
Next i
rv(n) = s
For i = numIn To 1 Step -1
If pos(i) <> ubs(i) Then 'Not done all of this array yet...
pos(i) = pos(i) + 1 'Increment array index
For r = i + 1 To numIn 'Reset all the indexes
pos(r) = lbs(r) ' of the later arrays
Next r
Exit For
End If
Next i
Next n
Combine = rv
End Function
I currently have an Excel spreadsheet with over 2000 rows of data. In one of the columns, i have an ID that is a string containing multiple decimal points. I need to sort the data in my Excel spreadsheet based off this ID. The column of ID's look like:
1.01.1.3.1
1.01.1.5.2
1.01.1.3.13
1.01.1.3.2
1.02.5.1.1.1.1
1.01.1.3.1.1
1.01.1.3.2.1
And the result needs to look like:
1.01.1.3.1
1.01.1.3.1.1
1.01.1.3.2
1.01.1.3.2.1
1.01.1.3.13
1.01.1.5.2
1.02.5.1.1.1.1
I am using VBA to pull the data from the spreadsheet and store in an array, but i am unsure how to approach sorting the string from left to right. I know i have to Split each entry by "." and sort the first index then the next index but i fear this method would take too long over the 2000+ entries. Im also unsure how to handle entries that have 5 indexes (Ex: 1.01.1.1.1) compared to an entry with 9 indexes (Ex: 1.01.1.1.2.5.1.1.1)
Another issue is that some entries contain letters. Ex: 1.01.1.4.1A
Note, I have this BubbleSort function:
Public Function BubbleSort(ByVal tempArray As Variant) As Variant
Dim Temp As Variant
Dim i As Integer
Dim NoExchanges As Integer
' Loop until no more "exchanges" are made.
Do
NoExchanges = True
' Loop through each element in the array.
For i = 0 To UBound(tempArray) - 1
' Substitution when element is greater than the element following int
If tempArray(i) > tempArray(i + 1) Then
NoExchanges = False
Temp = tempArray(i)
tempArray(i) = tempArray(i + 1)
tempArray(i + 1) = Temp
End If
Next i
Loop While Not (NoExchanges)
BubbleSort = tempArray
End Function
If anyone has any insight for a solution, your help is kindly appreciated.
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
If you are allowed to use additional columns do the following:
Copy the ID column to a new column
Check the maximum number of dots in every cell
Remove each non-numeric [^0-9] and non-dot [^.] from every cell
Amend each cell, including the maximal number of dots like this:
From:
1.01.1.3.13
To:
1.01.01.03.13.00
E.g. adding a zero, if it is consisting only of 1 value and adding additional points, to equalize to the maximal value with points.
In the new column remove the dots
Sort by the new column
Delete the new column
This is it!
If you are not allowed to use additional columns, then you should use some mapping techniques.
This code splits the range using the . delimiter.
It then adds a 0 to the blank cells in the split before sorting based on the split, but also including the original text.
The split cells are then cleared leaving just the sorted original values.
1.01.1.4.1A appears between 1.01.1.3.13 and 1.01.1.5.2.
Sub Test()
Dim wrkSht As Worksheet
Dim rng As Range
Dim rng_Split As Range
'Dim rng_Blanks As Range - EDIT: Not needed.
Dim lLastCol As Long
Dim rCol As Range
Set wrkSht = ThisWorkbook.Worksheets("Sheet1")
'Split the value and find the last column it splits to.
With wrkSht
'Adjust the range to yours.
Set rng = .Range("A31:A38")
rng.TextToColumns _
Destination:=rng.Offset(, 1), _
DataType:=xlDelimited, _
Other:=True, _
OtherChar:="."
lLastCol = rng.EntireRow.Cells.Find("*", , , , xlByColumns, xlPrevious).Column
End With
'Add a 0 to all blank cells.
Set rng_Split = rng.Offset(, 1).Resize(rng.Rows.Count, lLastCol - 1)
rng_Split.SpecialCells(xlCellTypeBlanks).Value = 0
With wrkSht
With .Sort
.SortFields.Clear
For Each rCol In rng_Split.Columns
.SortFields.Add Key:=rCol, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
Next rCol
'Adjust this range to include all columns to be sorted.
.SetRange rng_Split.Offset(, -1).Resize(, lLastCol)
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.Apply
End With
End With
rng_Split.ClearContents
End Sub
Edit: Using this method 01 and 1 are considered the same.
I want to create a function that is almost exactly like SumIfs, but I'm having a hard time figuring our how to handle the ParamArray portion. I'm looking for a solution that allows the same Range1,Criteria1,Range2,Criteria2,...,Rangen,Criterian as the sum ifs but in my "SumIfsContains" function. I've attached the code for the singular case, "SumIfContains" so you can see my starting point:
Function SumIfContains(PhraseRange As Range, Criteria As String, SumRange As Range)
Dim element As Range
ElementCount = 0
For Each element In PhraseRange
ElementCount = ElementCount + 1
Next element
Dim SumArray: ReDim SumArray(1 To 3, 1 To ElementCount)
ElementCount = 0
For Each element In SumRange
ElementCount = ElementCount + 1
SumArray(2, ElementCount) = element
Next element
ElementCount = 0
For Each element In PhraseRange
ElementCount = ElementCount + 1
SumArray(1, ElementCount) = element
If InString(CStr(element), Criteria) Then
SumArray(3, ElementCount) = SumArray(2, ElementCount)
Else
SumArray(3, ElementCount) = 0
End If
Next element
SumIfContains = 0
For Item = 1 To ElementCount
SumIfContains = SumIfContains + CDbl(SumArray(3, Item))
Next Item
End Function
Before I got an answer last night I came up with a working option as follows:
Function SumIfsContains(SumRange As Range, ParamArray Criteria() As Variant)
Dim element As Range
Dim cCriteria As String
Dim PhraseRange As Range
'Exit Function
Dim PhraseRangeArray(): ReDim PhraseRangeArray(LBound(Criteria()) To (((UBound(Criteria()) + 1) / 2) - 1))
Dim CriteriaArray(): ReDim CriteriaArray(LBound(Criteria()) To (((UBound(Criteria()) + 1) / 2) - 1))
CurrentPair = 0
For i = LBound(Criteria()) To UBound(Criteria())
If i Mod 2 = 0 Then
PhraseRangeArray(CurrentPair) = Criteria(i)
Else
CriteriaArray(CurrentPair) = Criteria(i)
CurrentPair = CurrentPair + 1
End If
Next i
ElementCount = UBound(PhraseRangeArray(0))
Dim SumRng: ReDim SumRng(1 To ElementCount)
i = 1
For Each element In SumRange
SumRng(i) = element
i = i + 1
Next element
Dim SumArray: ReDim SumArray(0 To 2 + UBound(PhraseRangeArray), 1 To ElementCount)
For i = 1 To ElementCount
SumArray(1, i) = SumRng(i)
For RC = 2 To 2 + UBound(PhraseRangeArray)
If InString(CStr(PhraseRangeArray(RC - 2)(i, 1)), CStr(CriteriaArray(RC - 2))) Then
SumArray(RC, i) = 1
Else
SumArray(RC, i) = 0
End If
Next RC
SumArray(0, i) = SumArray(1, i)
For Mult = 2 To 2 + UBound(PhraseRangeArray)
SumArray(0, i) = SumArray(0, i) * SumArray(Mult, i)
Next Mult
Next i
SumIfsContains = 0
For Item = 1 To ElementCount
SumIfsContains = SumIfsContains + CDbl(SumArray(0, Item))
Next Item
End Function
But I'm still curious how to make the Range/Criteria pair not simply be parced out of the "Criteria" array later.
If I understand correctly what you're trying to do, you just need to iterate over the ParamArray Step 2. Add a test to make sure than the passed parameters come in pairs, then just grab them as a set of Criteria and SumRange in a loop:
Public Function PairedParamArrayIe(PhraseRange As Range, ParamArray values())
Dim counter As Integer
Dim Criteria As String
Dim SumRange As Range
If UBound(values) Mod 2 <> 1 Then
Err.Raise -1, vbNullString, "Invalid ParamArray"
End If
For counter = LBound(values) + 1 To UBound(values) Step 2
Criteria = values(counter - 1)
Set SumRange = values(counter)
Debug.Print Criteria
Debug.Print SumRange.AddressLocal
Next counter
End Function
You'll note that for SUMIFS, unlike SUMIF, the data range comes first. That's key to your ParamArray:
Function SumIfContains(SumRange As Range, ParamArray criteria())
Dim x As Long
Dim n As Long
Dim dTotal As Double
Dim bMatch As Boolean
' check for criteria ranges
For n = LBound(criteria) To UBound(criteria) Step 2
If TypeName(criteria(n)) <> "Range" Then
SumIfContains = CVErr(xlErrNum)
End If
Next n
' loop through each cell in sum range
For x = 1 To SumRange.Cells.Count
bMatch = True
' loop through criteria
For n = LBound(criteria) To UBound(criteria) Step 2
' first item in pair is the range, second is the criterion
If InStr(1, criteria(n).Cells(x).Value2, criteria(n + 1), vbTextCompare) = 0 Then
' if one doesn't match, set a flag and exit the loop
bMatch = False
Exit For
End If
Next n
' only if all criteria matched is bMatch still True, and we add the sumrange cell
If bMatch And IsNumeric(SumRange.Cells(x).Value2) Then dTotal = dTotal + SumRange.Cells(x).Value2
Next x
SumIfContains = dTotal
End Function
I was wondering if anyone had any experience imposing time limits on sections of code. I have programmed a search engine into an excel spreadsheet in VBA and there is a section of the code that removes duplicate results. Now this part can sometimes stretch on for quite a long time if given the most vague search criteria. So I would like to impose a time limit for this operation. I have looked everywhere for a solution and tried using OnTime, but it doesnt seem to work in the way I need. Ideally, I'd like an imposed time limit and then when that is reached a GoTo statement, to move it further on in the code. From what I have read the OnTime will not interrupt an operation, but will wait for it to finish instead, this is not what I want.
Thanks for your help guys.
Amy
I've added my code:
Sub RemoveDuplicates()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
'Code called upon through the other macros which will remove duplicates from all the types of search.
Application.StatusBar = "Removing Duplicates...."
Dim k As Integer
Dim SuperArray As String
Dim CheckingArray As String
Dim Duplicate As Boolean
Dim i As Integer
Dim j As Integer
Dim m As Integer
Dim Endrow As Integer
Dim Endcolumn As Integer
Dim w As Integer
Dim x As Integer
Dim n As Integer
w = 1
x = 9
Endcolumn = Module6.Endcolumn(x)
Endrow = Module6.Endrow(w)
If Worksheets("Search Engine").Cells(9, Endrow) = "Percentage Similarity" Then
Endrow = Endrow - 1
End If
For i = 9 To Endcolumn
j = 1
k = i + 1
Do While j <> Endrow + 1
SuperArray = Cells(i, j) & Superstring
Superstring = SuperArray
j = j + 1
Loop
For k = k To Endcolumn
m = 1
Do While m <> Endrow
CheckingArray = Cells(k, m) & Uberstring
Uberstring = CheckingArray
m = m + 1
Loop
If Uberstring = Superstring Then
n = 1
Do While n <> Endrow + 1
If Worksheets("Search Engine").Cells(k, n).Interior.ColorIndex = 37 Then
Worksheets("Search Engine").Cells(i, n).Interior.ColorIndex = 37
End If
n = n + 1
Loop
Rows(k).Clear
End If
Uberstring = -1
Next k
Superstring = -1
Next i
Do While i > 9
If Cells(i, 1) = Empty Then
Rows(i).Delete
End If
i = i - 1
Loop
End Sub
I assume your code must have some kind of loop, e.g. For Each, While ... Wend, Do ... Loop Until, etc.
In theses cases, extend the condition by a comparison to the Timer. This returns you a Double between 0 and 86400, indicating how many seconds have passed since midnight. Thus, you also need to account for the day break. Here is some example code showing you implementations for three different loop constructs:
Sub ExampleLoops()
Dim dblStart As Double
Dim tmp As Long
Const cDblMaxTimeInSeconds As Double = 2.5
dblStart = Timer
'Example with For loop
For tmp = 1 To 1000
tmp = 1 'to fake a very long loop, replace with your code
DoEvents 'your code here
If TimerDiff(dblStart, Timer) > cDblMaxTimeInSeconds Then GoTo Finalize 'Alternative: Exit For
Next
'Alternative example for Do loop
Do
DoEvents 'your code here
Loop Until TimerDiff(dblStart, Timer) > cDblMaxTimeInSeconds And False 'your condition here
'Alternative example for While loop
While TimerDiff(dblStart, Timer) <= cDblMaxTimeInSeconds And True 'your condtion here
DoEvents 'your code here
Wend
Finalize:
'FinalizeCode here
Exit Sub
End Sub
Function TimerDiff(dblTimerStart As Double, dblTimerEnd As Double)
Dim dblTemp As Double
dblTemp = dblTimerEnd - dblTimerStart
If dblTemp < -43200 Then 'half a day
dblTemp = dblTemp + 86400
End If
TimerDiff = dblTemp
End Function