VBA Greater Than Function Not Working - string

I have an issue where I am trying to compare a values that can be alphanumeric, only numeric, or only alphabetic.
The code originally worked fine for comparing anything within the same 100s group (IE 1-99 with alphabetic components). However when I included 100+ into it, it malfunctioned.
The current part of the code reads:
For j = 1 To thislength
If lennew < j Then
enteredval = Left("100A", lennew)
ElseIf lennew >= j Then
enteredval = Left("100A", j)
End If
If lenold < j Then
cellval = Left("67", lenold)
ElseIf lenold >= j Then
cellval = Left("67", j)
End If
'issue occurs here
If enteredval >= cellval Then
newrow = newrow+1
End If
Next j
The issue occurs in the last if statement.
When cycling through the 100 is greater than the 67 but still skips over. I tried to declare them both as strings (above this part of code) to see if that would help but it didn't.
What I am trying to accomplish is to sort through a bunch of rows and find where it should go. IE the 100A should go between 100 and 100B.
Sorry lennew=len("100A") and lennold=len("67"). And thislength=4or whatever is larger of the two lengths.

The problem is that you're trying to solve the comparison problem by attacking specific values, and that's going to be a problem to maintain. I'd make the problem more generic by creating a function that supplies takes two values returns -1 if the first operand is "before" the second, 0 if they are the same, and 1 if the first operand is "after" the second per your rules.
You could then restructure your code to eliminate the specific hardcoded prefix testing and then just call the comparison function directly, eg (and this is COMPLETELY untested, off-the-cuff, and my VBA is VERRRRRY stale :) but the idea is there: (it also assumes the existence of a simple string function called StripPrefix that just takes a string and strips off any leading digits, which I suspect you can spin up fairly readily yourself)
Function CompareCell(Cell1 as String, Cell2 as String) as Integer
Dim result as integer
Dim suffix1 as string
Dim suffix2 as string
if val(cell1)< val(cell2) Then
result = -1
else if val(cell1)>val(cell2) then
result = 1
else if val(cell1)=val(cell2) then
if len(cell1)=len(cell2) then
result =0
else
' write code to strip leading numeric prefixes
' You must supply StripPrefix, but it's pretty simple
' I just omitted it here for clarity
suffix1=StripPrefix(cell1) ' eg returns "ABC" for "1000ABC"
suffix2=StripPrefix(cell2)
if suffix1 < suffix2 then
result = -1
else if suffix1 > suffix2 then
result = 1
else
result = 0
end if
end if
return result
end function
A function like this then allows you to take any two cell references and compare them directly to make whatever decision you need:
if CompareCell(enteredval,newval)>=0 then
newrow=newrow+1
end if

Related

Suddenly runtime error 13 for no evident reason

I'm using a modified match function since years which worked fine. But suddenly I get runtime error 13 for no reason. This function is called two times in the actual process before the error pops up. The first time everything works fine, the second time I get the error. Here's the code:
Public Function xMatch(ByRef Direction_Range As Range, ByVal Find_Value_Or_String, Occ_Number As Integer, Row_True_Or_Column_False As Boolean, RelativePosition_True_Or_AbsolutePosition_False As Boolean) As Integer
Dim xMTcell
toolVar1 = 0
xMatch = 0
occurrencesCount = 0
If RelativePosition_True_Or_AbsolutePosition_False = True Then
If Row_True_Or_Column_False = True Then
toolVar1 = Range(Split(Direction_Range.Address, ":")(0)).Row - 1
Else
toolVar1 = Range(Split(Direction_Range.Address, ":")(0)).Column - 1
End If
End If
For Each xMTcell In Direction_Range
If xMTcell.Value = Find_Value_Or_String Then
occurrencesCount = occurrencesCount + 1
If occurrencesCount = Occ_Number Then
If Row_True_Or_Column_False = True Then
xMatch = xMTcell.Row - toolVar1
Else
xMatch = xMTcell.Column - toolVar1
End If
Exit For
End If
End If
Next xMTcell
End Function
toolVar1 and occurrencesCount are declared in the module. The function can search in any ranges (Direction_Range) and find the Value (Find_Value_Or_String). In contrary to a regular match function you can decide (Occ_Number) which find will be the one you need if there are multiple ones in that range. Also, you can decide if you want the row or the column of that find and if you want that row/column absolute (position compared to worksheet) or relative (position compared to Direction_Range).
The error occurs in this line:
If xMTcell.Value = Find_Value_Or_String Then
Since xMTcell is part of Direction_Range I checked the Range and it is clearly the right one plus it IS a range and nothing else. I also checked the Value and it is a String he is looking for which can be found manually in that range. I can't understand why it works fine in other stages of the process with the exact same type of Direction_Range and Find_Value_Or_String and suddenly it doesn't. I already tried declaring xMTcell as Range but it made no difference.
Anyone an idea?
Regards
Carl
According to the comments i did following checks:
Debug.Print VarType(Direction_Range)
Debug.Print Direction_Range.Address
Debug.Print VarType(xMTcell.Value)
Debug.Print VarType(Find_Value_Or_String)
Debug.Print xMTcell.Address
and for the non-bugging process i get
8204
$A$4:$N$4
8
8
$B$4
and for the bugging process
8204
$A$5:$A$16
8204
8
$A$5:$A$16
so its a problem with the range, why does it behave differently?
TL;DR:
Change For Each xMTcell In Direction_Range to For Each xMTcell In Direction_Range.Cells.
Some debugging and general tips (summarizing the comments and your feedback in them):
Dim xMTcell - make that a Range.
Use Debug.Print (Control+G to bring up the Immediate Window and inspect the output).
Debug.Print VarType(xMTcell.Value) returns 8204: Per the VarType docs, that means that xMTcell.Value is a vbArray of vbVariants (8192 + 12 = 8204).
Debug.Print VarType(Find_Value_or_String) returns 8: Again per the VarType docs, this means Find_Value_or_String is a String.
The type mismatch is because you can't compare a String to an array.
The fact that xMTCell.Value is an array points to xMTCell being a multi-cell range, not a single cell.
... which is verified by the output of Debug.Print xMTcell.Address being a multi-cell range.
Most likely the issue is that you passed a Row or Column as your Direction_Range, i.e. you used Rows or Columns to return a range. When looping over a Row or Column, you need to specify that you're looping over the individual cells.

VBA - How to check if a string variable/cell contains another string Variable?

basically, I need to find a way to check if a String variable contains another String variable(or preferably an item from an array). Here's my case:
I have a list of 15k+ Strings in Excel, and another one with 5k+ Strings.The Second list is composed of 3 character long abbreviations, and the first one might contain from 0 to 15 of those, separated by ", ". My goal is to count each occurrence, of each of those abbreviations.
I've approached this by creating an array containing all the abbreviations. Then I was hoping to loop through the 15k+ long list 5k+ times, to count the occurrences of each of those abbreviations, changing the abbreviation sought every loop. However I'm stuck with comparing the strings...
I've tried to use InStr to see if I get 0 or not. Although it worked for me before when I referred to a String being searched by variable, but it seems that expression sought can't be placed in such a way. I've been trying to use Find or Application.Match instead but had the same issue... I wonder if there is a VBA function I don't know about? Or maybe someone has an idea for a custom function or an entirely different approach? I've hit a brick wall...
Thank you in advance for any advice! I highly appreciate it.
Unfortunately I can't show you the data since it's customer's sensitive data but here's my loop:
With ActiveWorkbook
For p = 1 To size
For j = 6 To lrow
a = .Sheets("Server View").Cells(j, 3).Value
tmp = Apps(p)
test = InStr(1, a, tmp)
If test = 0 Then
Else
b = .Sheets("Server View").Cells(j, 9).Value
If b = PROD Then
cval = .Sheets("temp").Cells(j, 3).Value
cval = cval + 1
.Sheets("temp").Cells(j, 3).Value = cval
Else
cval = .Sheets("temp").Cells(j, 2).Value
cval = cval + 1
.Sheets("temp").Cells(j, 2).Value = cval
End If
End If
Next j
Next p
End With

Finding indexes of strings in a string array in Matlab

I have two string arrays and I want to find where each string from the first array is in the second array, so i tried this:
for i = 1:length(array1);
cmp(i) = strfind(array2,array1(i,:));
end
This doesn't seem to work and I get an error: "must be one row".
Just for the sake of completeness, an array of strings is nothing but a char matrix. This can be quite restrictive because all of your strings must have the same number of elements. And that's what #neerad29 solution is all about.
However, instead of an array of strings you might want to consider a cell array of strings, in which every string can be arbitrarily long. I will report the very same #neerad29 solution, but with cell arrays. The code will also look a little bit smarter:
a = {'abcd'; 'efgh'; 'ijkl'};
b = {'efgh'; 'abcd'; 'ijkl'};
pos=[];
for i=1:size(a,1)
AreStringFound=cellfun(#(x) strcmp(x,a(i,:)),b);
pos=[pos find(AreStringFound)];
end
But some additional words might be needed:
pos will contain the indices, 2 1 3 in our case, just like #neerad29 's solution
cellfun() is a function which applies a given function, the strcmp() in our case, to every cell of a given cell array. x will be the generic cell from array b which will be compared with a(i,:)
the cellfun() returns a boolean array (AreStringFound) with true in position j if a(i,:) is found in the j-th cell of b and the find() will indeed return the value of j, our proper index. This code is more robust and works also if a given string is found in more than one position in b.
strfind won't work, because it is used to find a string within another string, not within an array of strings. So, how about this:
a = ['abcd'; 'efgh'; 'ijkl'];
b = ['efgh'; 'abcd'; 'ijkl'];
cmp = zeros(1, size(a, 1));
for i = 1:size(a, 1)
for j = 1:size(b, 1)
if strcmp(a(i, :), b(j, :))
cmp(i) = j;
break;
end
end
end
cmp =
2 1 3

Excel if function

I've made this large excel sheet and at the time i didn't know i'd need to sort this table through categories.
I have in a column (J here ) the description of the line and the category joint. (example: "Shipment of tires for usin'ss")
The only way i was able to sort the table the way i wanted was to build a category column using this :
=IF(COUNTIF(J3;"*usi*");"Usins";IF(COUNTIF(J3;"*remis*");"Remise";IF(COUNTIF(J3;"*oe*");"Oenols";IF(COUNTIF(J3;"*KDB*");"KDB";IF(COUNTIF(J3;"*vis*");"cvis";IF(COUNTIF(J3;"*amc*");"AMC";0))))))
usi for instance is a segment of a category name, that i sometimes wrote as
usin'ss
usin
usin's
usins
'cause you know smart.
Anyway, how do i translate =If(If(If...))) into something readable in VBA like:
If...then
If... then
Example of "IF ... ELSE" in EVBA
IF condition_1 THEN
'Instructions inside First IF Block
ELSEIF condition_2 Then
'Instructions inside ELSEIF Block
...
ELSEIF condition_n Then
'Instructions inside nth ELSEIF Block
ELSE
'Instructions inside Else Block
END IF
Example of Case Switch in EVBA
Select Case score
Case Is >= 90
result = "A"
Case Is >= 80
result = "B"
Case Is >= 70
result = "C"
Case Else
result = "Fail"
End Select
Both cases work off a waterfall type logic where if the first condition is met, then it does not continue, but if condition 1 is not met then it checks the next, etc.
Example usage:
Function makeASelectAction(vI_Score As Integer) As String
Select Case vI_Score
Case Is >= 90
makeASelectAction = "A, fantastic!"
Case Is >= 80
makeASelectAction = "B, not to shabby."
Case Is >= 70
makeASelectAction = "C... least your average"
Case Else
makeASelectAction = "Fail, nuff said."
End Select
End Function
Function makeAnIfAction(vS_Destination As String, vS_WhatToSay As String, Optional ovR_WhereToStick As Range, Optional ovI_TheScore As Integer)
If vS_Destination = "popup" Then
MsgBox (vS_WhatToSay)
ElseIf vS_Destination = "cell" Then
ovR_WhereToStick.value = vS_WhatToSay
ElseIf vS_Destination = "select" Then
MsgBox makeASelectAction(ovI_TheScore)
End If
End Function
Sub PopMeUp()
Call makeAnIfAction("popup", "Heyo!")
End Sub
Sub PopMeIn()
Call makeAnIfAction("cell", "Heyo!", Range("A4"))
End Sub
Sub MakeADescision()
Call makeAnIfAction(vS_Destination:="select" _
, vS_WhatToSay:="Heyo!" _
, ovI_TheScore:=80 _
)
End Sub
It will show you how to send variables to functions and how to call said function, it will show you how use optional parameters, how a function and interact with another function or sub, how do write a value to a sheet or spit out a messagebox. The possabilities are endless. Let me know if you need anything else cleared up or coded out.
You seem to be using CountIf just to see if the contents of the cell matches a certain pattern and, if so, give a replacement string. In VBA you can use the Like operator for pattern matching. In any event -- here is a function I wrote which, when passed a string and a series of pattern/substitution strings, loops through the patterns until it finds a match and then returns the corresponding substitution. If no match is found, it returns an optional default value (the last argument supplied). If no default is supplied, it returns #N/A.
The code illustrates that sometimes complicated nested ifs can be replaced by a loop which iterates through the various cases. This is helpful when you don't know the number of cases before hand.
Function ReplacePattern(s As String, ParamArray patterns()) As Variant
Dim i As Long, n As Long
n = UBound(patterns)
If n Mod 2 = 0 Then n = n - 1
For i = 0 To n Step 2
If s Like patterns(i) Then
ReplacePattern = patterns(i + 1)
Exit Function
End If
Next i
If UBound(patterns) Mod 2 = 0 Then
ReplacePattern = patterns(n + 1)
Else
ReplacePattern = CVErr(xlErrNA)
End If
End Function
Your spreadsheet formula is equivalent to
=ReplacePattern(J3,"*usi*","Usins","*remis*","Remise","*oe*","Oenols","*KDB*","KDB","*vis*","cvis","*amc*","AMC",0)

Building Variable Names with Concatenate

I have a function that takes optional arguments in pairs: firstRange_1, secondRange_2; firstRange_2, secondRange_2; etc.
For each optional argument I need to execute a series of statements if the argument is passed to the function.
For example
dim firstRange_1 as range
dim secondRange_1 as range
dim firstRange_2 as range
dim secondRange_2 as range
etc.
dim firstCell_1 as string
dim lastCell_1 as string
dim firstCell_2 as string
dim lastCell_2 as string
etc.
If IsMissing(firstRange_1) = False Then
firstCell_1 = secondRange_1.Cells(1,1).Address
lastCell_1 = secondRange_1.Cells(secondRange_1.Rows.Count, secondRange_1.Rows.Count)
End if
if IsMissing(firstRange_2) = False Then
firstCell_2 = secondRange_2.Cells(1,1).Address
lastCell_2 = secondRange_2.Cells(secondRange_2.Rows.Count, secondRange_2.Rows.Count)
End If
Is it possible to "build" (sorry if the terminology isn't correct, I'm not yet experienced in programming or vba) the variables on the fly?
for example a loop like
For n=1 to 100
If IsMissing(firstRange_ & "n") = False Then
firstCell_ & "n" = secondRange_ & "n".Cells(1,1).Address
lastCell_ & "n" = secondRange_ & "n".Cells(secondRange_ & "n".Rows.Count, secondRange_ & "n".Rows.Count)
End If
Next
Edit:
See my comments to Branislav Kollár for updates.
I think what you need to rewrite the function to use ParamArrays (see the "Using an Indefinite Number of Arguments" section). Something like this:
myFunction(ParamArray userRanges()) As Range'or whatever Data Types you need
This way, you could use the LBound and UBound functions to see how many arguments were passed into function, leaving the necessity to check if they are missing.
For example you can create a 2 new arrays inside the function (not the argument array) for determining the first and last cells of each argument range. This is not the only way, you can use 2D arrays or put everything into one array. This is just one way.
Function myFunction(ParamArray userRanges()) As Range
Dim firstCell() As Range
Dim lastCell() As Range
ReDim firstCell(UBound(userRanges))
ReDim lastCell(UBound(userRanges))
For x = 0 To UBound(userRanges)
Set firstCell(x) = userRanges(x).Range("A1")
Set lastCell(x) = firstCell_1(x).Offset(userRanges(x).Rows.Count - 1, userRanges(x).Columns.Count - 1)
Next x
'other code to actually do something with the cells
'...
End Function
Try this, if you have any trouble, please let us know.
One more link to learn about this Understanding the ParamArray
Edit 1
Based on comment from OP, I rewritten the code, so that now each input range userRanges will have firstCell and lastCell stored in appropriate arrays. I didn't realize the limitation of my previous post before.
The only think to keed in mind now, is that the index 0 is first range; 1 is second range; 2 is third range; etc.
Or you can use Option Base 1 to make it more naturally indexed, but that is not recommended for some reason.
You can't dynamically name variables, but you can use Arrays. They are stupid powerful, so it's worth learning about them.
Essentially you will make 2 arrays. One for your input (variable1_n) and one of your outputs (output_1_n).
Dim inputArray(1 to 100) as String 'or whatever type these are supposed to be
Dim outputArray(1 to 100) as Range 'perhaps these are ranges?
For i = 1 to 100
Set outputArray(i) = function(inputArray(i))
Next i
Now you have an array full of ranges!

Resources