How to find the largest number in text string in Excel formula or VBA - vba

This is my first post here. I am looking to get the largest number out of this type of text. And here is the example.
Class 1 - $250,000 - PTD equal to principal sumClass 2 - $500,000 - PTD equal to principal sumClass 3 - $500,000 - PTD equal to principal sumClass 4 - $250,000 Class 5 - $250,000 Class 6 - $250,000
Everyone of the number will have dollar sign. I have tried Scott's solution here. But no luck.
Please let me know if and how it can be done.
Thank you.

I'd go this way:
Function GetMax(s As String)
Dim val As Variant
Dim num As Double
Dim pos As Long
For Each val In Split(s, "$")
pos = 0
Do While IsNumeric(Mid(val, 1, pos + 1))
pos = pos + 1
Loop
If pos > 0 Then
num = CDbl(Mid(val, 1, pos))
If num > GetMax Then GetMax = num
End If
Next
End Function

You can just adapt the answer you linked to by first removing all the "$" signs using VBAs Replace function:
Function MaxInString(rng As String) As Double
Dim splt() As String
Dim i&
'==================NEW LINE==================='
rng = Replace(rng, "$", "")
'============================================='
splt = Split(rng)
For i = LBound(splt) To UBound(splt)
If IsNumeric(splt(i)) Then
If splt(i) > MaxInString Then
MaxInString = splt(i)
End If
End If
Next i
End Function
Based on your new requirements, here is a possible regex based solution (based on this https://stackoverflow.com/a/44339803/1011724):
Public Function max_number(s As String) As Double
Static re As VBScript_RegExp_55.RegExp
s = Replace(s, ",", "")
If re Is Nothing Then
Set re = New RegExp
re.IgnoreCase = True: re.Global = True
re.Pattern = "-?\d*\.?\d+"
End If
max_number = 0
For Each elem In re.Execute(s)
If max_number < CDbl(elem) Then
max_number = CDbl(elem)
End If
Next
End Function
Just make sure to first follow Step 1 in this answer: https://stackoverflow.com/a/22542835/1011724 to add a reference to the regex library first.

Try this code (necessary comments in code):
Option Explicit
Sub GetMaxNumber()
Dim txt As String, idx As Long, idx2 As Long, maxValue As Long, extractedNumber As Long, char As String
maxValue = 0
'set variable in a code or use cell value
'txt = Range("A1").Value
txt = "Class 1 - $250,000 - PTD equal to principal sumClass 2 - $500,000 - PTD equal to principal sumClass 3 - $500,000 - PTD equal to principal sumClass 4 - $250,000 Class 5 - $250,000 Class 6 - $250,000"
idx = InStr(1, txt, "$")
'on each loop we will look for dollar sign (you mentioned, that every number starts with it)
'and then, look for first non-comma non-numeric characted, there the number will end
'at the end we extract the number from text
Do While idx > 0
idx2 = idx + 1
char = Mid(txt, idx2, 1)
'determine the end of a number
Do While IsNumeric(char) Or char = ","
char = Mid(txt, idx2, 1)
idx2 = idx2 + 1
Loop
'extract the number, also removing comma from it
extractedNumber = Replace(Mid(txt, idx + 1, idx2 - idx - 2), ",", "")
'if extracted number is greater than current max, replace it
If maxValue < extractedNumber Then maxValue = extractedNumber
idx = InStr(idx + 1, txt, "$")
Loop
MsgBox maxValue
End Sub

I would split on the spaces, then loop through looking for dollar sign and then once found paste the string into a cell to parse out commas etc.
Note the following code uses the cell parser to strip out commas and currency signs.
Sub Test2()
Sheet1.Cells(1, 1).Value = "3,000"
Debug.Assert Sheet1.Cells(1, 1).Value = 3000
Sheet1.Cells(1, 1).Value = "$250,000"
Debug.Assert Sheet1.Cells(1, 1).Value = 250000
End Sub
Here is full listing
Sub Test()
Dim s As String
s = "Class 1 - $250,000 - PTD equal to principal sumClass 2 - $500,000 - PTD equal to principal sumClass 3 - $500,000 - PTD equal to principal sumClass 4 - $250,000 Class 5 - $250,000 Class 6 - $250,000"
Dim vSplit As Variant
vSplit = Split(s, " ")
Dim ccyMax As Currency
ccyMax = -1
Dim vSplitLoop As Variant
For Each vSplitLoop In vSplit
If Left$(vSplitLoop, 1) = "$" Then
Sheet1.Cells(1, 1).Value = vSplitLoop
Dim ccyParsed As Currency
ccyParsed = Sheet1.Cells(1, 1).Value
If ccyParsed > ccyMax Then ccyMax = ccyParsed
End If
Next
Debug.Print ccyMax
End Sub

Related

Label a set of objects with (A->Z,AA->ZZ, AAA->ZZZ) in VBA

I have a set which has an unknown number of objects. I want to associate a label to each one of these objects. Instead of labeling each object with a number I want to label them with letters.
For example the first object would be labeled A the second B and so on.
When I get to Z, the next object would be labeled AA
AZ? then BA, BB, BC.
ZZ? then AAA, AAB, AAC and so on.
I'm working using Mapbasic (similar to VBA), but I can't seem to wrap my head around a dynamic solution. My solution assumes that there will be a max number of objects that the set may or may not exceed.
label = pos1 & pos2
Once pos2 reaches ASCII "Z" then pos1 will be "A" and pos2 will be "A". However, if there is another object after "ZZ" this will fail.
How do I overcome this static solution?
Basically what I needed was a Base 26 Counter. The function takes a parameter like "A" or "AAA" and determines the next letter in the sequence.
Function IncrementAlpha(ByVal alpha As String) As String
Dim N As Integer
Dim num As Integer
Dim str As String
Do While Len(alpha)
num = num * 26 + (Asc(alpha) - Asc("A") + 1)
alpha = Mid$(alpha, 2,1)
Loop
N = num + 1
Do While N > 0
str = Chr$(Asc("A") + (N - 1) Mod 26) & str
N = (N - 1) \ 26
Loop
IncrementAlpha = str
End Function
If we need to convert numbers to a "letter format" where:
1 = A
26 = Z
27 = AA
702 = ZZ
703 = AAA etc
...and it needs to be in Excel VBA, then we're in luck. Excel's columns are "numbered" the same way!
Function numToLetters(num As Integer) As String
numToLetters = Split(Cells(1, num).Address(, 0), "$")(0)
End Function
Pass this function a number between 1 and 16384 and it will return a string between A and XFD.
Edit:
I guess I misread; you're not using Excel. If you're using VBA you should still be able to do this will the help of an reference to an Excel Object Library.
This should get you going in terms of the logic. Haven't tested it completely, but you should be able to work from here.
Public Function GenerateLabel(ByVal Number As Long) As String
Const TOKENS As String = "ZABCDEFGHIJKLMNOPQRSTUVWXY"
Dim i As Long
Dim j As Long
Dim Prev As String
j = 1
Prev = ""
Do While Number > 0
i = (Number Mod 26) + 1
GenerateLabel = Prev & Mid(TOKENS, i, 1)
Number = Number - 26
If j > 0 Then Prev = Mid(TOKENS, j + 1, 1)
j = j + Abs(Number Mod 26 = 0)
Loop
End Function

Longest common substring large strings?

I need some help with this function. I am trying to find the longest common string between 2 strings. Here is the function that I am currently using:
Public Shared Function LCS(str1 As Char(), str2 As Char())
Dim l As Integer(,) = New Integer(str1.Length - 1, str2.Length - 1) {}
Dim lcs__1 As Integer = -1
Dim substr As String = String.Empty
Dim [end] As Integer = -1
For i As Integer = 0 To str1.Length - 1
For j As Integer = 0 To str2.Length - 1
If str1(i) = str2(j) Then
If i = 0 OrElse j = 0 Then
l(i, j) = 1
Else
l(i, j) = l(i - 1, j - 1) + 1
End If
If l(i, j) > lcs__1 Then
lcs__1 = l(i, j)
[end] = i
End If
Else
l(i, j) = 0
End If
Next
Next
For i As Integer = [end] - lcs__1 + 1 To [end]
substr += str1(i)
Next
Return substr
End Function
This works great on strings of up to around 600 words or so. If I try to compare strings with a larger word count than that it starts to throw system.outofmemoryexception. Obviously, this is hitting the memory pretty hard. Is there any way to fine tune this function or is there possibly another way of doing this that is more streamlined?

How can I extract the 'logical_test' from an if statement in excel?

I'm putting together an excel spreadsheet for calculations, and I need to be able to show the formulas to go with the decisions, for the most part its pretty straight forward, but When I come to an 'if' formula in an excel cell, I don't want to show the value_if_true and value_if_false... Just the logical_test value.
Example:
Formula is: =if(and(5<=A1, A1<=10),"Pass", "Fail");
Result will be: "and(5<=A1, A1<=10)"
I need to be able to work with complex logical tests which may include nested if statements, so just splitting at the commas won't work reliably. Similarly the value_if_true and value_if_false statements could also contain if statements.
Any ideas?
If have clear understanding of what you asking for, then you can use something like this (shall be used only with IF() statement :
Function extrIf(ByVal ifstatement As Range) As String
Dim S$, sRev$, x%, k
S = Replace(Replace(ifstatement.Formula, "IF(", "\"), "),", ")|")
sRev = StrReverse(S)
If InStr(1, sRev, "|") > InStr(1, sRev, "\") Or InStr(1, sRev, "|") = 0 Then
x = InStr(1, StrReverse(Left(sRev, InStr(1, sRev, "\"))), ",") - 1
S = Mid(S, 1, Len(S) - InStr(1, sRev, "\") + x) & "|"
End If
sRev = ""
For Each k In Split(S, "|")
If k <> "" Then
If k Like "*\*" Then
sRev = sRev & ", " & Mid(k, InStr(1, k, "\") + 1, 999)
End If
End If
Next
extrIf = Mid(sRev, 3, 999)
End Function
example:
test:
Maybe this is not complete solution for you, but I think it might give you right direction.
If the cell formula starts with an If statement then you can return the logic test (starting after the first open parenthesis) by determining the position of the first comma where the sum of the previous open parenthesis - the sum previous closed = 0.
Formulas
Function ExtractIfTest(Target As Range) As String
Dim ch As String, s As String
Dim openP As Long
Dim x As Long
s = Target.formula
For x = 5 To Len(s)
ch = Mid(s, x, 1)
If Mid(s, x, 1) = "(" Then
openP = openP + 1
ElseIf Mid(s, x, 1) = ")" Then
openP = openP - 1
ElseIf Mid(s, x, 1) = "," And openP = 0 Then
ExtractIfTest = Mid(s, 5, x - 12)
End If
Next
End Function
Results
There might be instances where the is a comma without parenthesis A1,B1. If this happens simple escape them with parenthesis (A1,B1)
I've written an UDF that extract any of the parameters of the target formula. It's close to the one in Thomas answer, but more global and takes into account strings that can enclose commas or parenthesis.
Function ExtractFormulaParameter(Target As Range, Optional Position As Long = 1) As Variant
Dim inString As Boolean
Dim formula As String
Dim st As Long, sp As Long, i As Long, c As String
Dim parenthesis As Long, comma As Long
formula = Target.formula
st = 0: sp = 0
If Position <= 0 Then ExtractFormulaParameter = CVErr(xlErrValue): Exit Function
For i = 1 To Len(formula)
c = Mid$(formula, i, 1)
If inString Then
If c = """" Then
inString = False
End If
Else
Select Case c
Case """"
inString = True
Case "("
parenthesis = parenthesis + 1
If parenthesis = 1 And Position = 1 Then
st = i + 1
End If
Case ")"
parenthesis = parenthesis - 1
If parenthesis = 0 And sp = 0 Then sp = i: Exit For
Case ","
If parenthesis = 1 Then
comma = comma + 1
If Position = 1 And comma = 1 Then sp = i: Exit For
If Position > 1 And comma = Position - 1 Then st = i + 1
If Position > 1 And comma = Position Then sp = i: Exit For
End If
Case Else
End Select
End If
Next i
If st = 0 Or sp = 0 Then
ExtractFormulaParameter = CVErr(xlErrNA)
Else
ExtractFormulaParameter = Mid$(formula, st, sp - st)
End If
End Function
By default it returns the first parameter, but you can also return the second or the third, and it should work with any formula.
Thanks for the replies all. I thought about this more, and ended up coming up with a similar solution to those posted above - essentially string manipulation to extract the text where we expect to find the logical test.
Works well enough, and I'm sure I could use it to extract further logical tests from substrings too.

How can I convert these inelegant formulae into VBA?

Good people of Stackland
I'm analysing strings comprised of 5 alpha chars which in their raw format look like this;
A2) BCDBE
A3) TLDPP
A4) FGGFC
A5) BBGBB
I need a way of evaluating each character to identify patterns within the strings themselves, eg repeating letters. I want to represent these patterns as follows, where the 1st letter is always given as "A", the 2nd "B"...;
A2) BCDBE --> ABCAD
A3) TLDPP --> ABCDD
A4) FGGFC --> ABBAC
A5) BBGBB --> AABAA
Now, I have achieved this with some pretty inelegant conditional formulae but had to do this to evaluate each character individually, as follows;
1) =IF(LEFT(A2,1)>0,"A")
2) =IF(MID(A2,2,1)=LEFT(A2,1),"A","B")
3) =IF(MID(A2,3,1)=LEFT(A2,1),"A",IF(MID(A2,3,1)=MID(A2,2,1),M2,CHAR(CODE(M2)+1)))
4) =IF(MID(A2,4,1)=LEFT(A2,1),"A",IF(MID(A2,4,1)=MID(A2,2,1),M2,IF(MID(A2,4,1)=MID(A2,3,1),N2,CHAR(MAX(CODE(L2:N2)+1)))))
5) =IF(MID(A2,5,1)=LEFT(A2,1),"A",IF(MID(A2,5,1)=MID(A2,2,1),M2,IF(MID(A2,5,1)=MID(A2,3,1),N2,IF(MID(A2,5,1)=MID(A2,4,1),O2,CHAR(MAX(CODE(L2:O2)+1))))))
Translated...
1) Call the first character "A"
2) If the 2nd character is the same as the same as the 1st call it "A", otherwise cause it "B"
3) If the 3rd character is the same as the 1st call it "A", if it's the same as the 2nd call it whatever the 2nd is, if not give it the value of the next letter, ie "C"
4) If the 4th character is the same as the 1st, call it "A", if it's the sames as the 2nd call it whatever the 2nd is, if it's the same as the 3rd call it whatever the 3rd is, if not then call it the next letter in the alphabet, ie "D"
5) If the 5th character is the same as the 1st, call it "A", if it's the same as the 2nd call it whatever the 2nd is, if it's the same as the 3rd call it whatever the 3rd is called, if it's the same as the 4th call it whatever the 4th is called, if not then call it the next letter in the alphabet, ie "E"
I'm doing this over 5 cols, one formula per col, and the concatenating the 5 results into one cell to get AABAA or whatever.
I just need to know if there's a nice, clean VBA solution to this.
Any ideas?
Here is the a Function to do the letter instead of numbers:
Function findPattern(inputStr As String) As String
Dim i As Integer
Dim t As Integer
t = 1
For i = 1 To 5 Step 1
If Asc(Mid(inputStr, i, 1)) > 54 Then
inputStr = Replace(inputStr, Mid(inputStr, i, 1), t)
t = t + 1
End If
Next i
For i = 1 To 5
inputStr = Replace(inputStr, i, Chr(i + 64))
Next i
findPattern = inputStr
End Function
Put it in a module attached to the workbook, and you can call it thus:
=findPattern(A2)
Driectly from the worksheet where A2 is the cell you want tested.
Or from vba:
Sub test()
Dim str as string
str = findPattern(Range("A2").value)
debug.print str
End Sub
Edit: By your Comment I assume you have more than just the first 5 characters that you want left original. If that is the case use this:
Function findPattern(Str As String) As String
Dim inputStr As String
Dim i As Integer
Dim t As Integer
inputStr = Left(Str, 5)
t = 1
For i = 1 To 5 Step 1
If Asc(Mid(inputStr, i, 1)) > 54 Then
inputStr = Replace(inputStr, Mid(inputStr, i, 1), t)
t = t + 1
End If
Next i
For i = 1 To 5
inputStr = Replace(inputStr, i, Chr(i + 64))
Next i
'This is the return line. As is it will only return 5 characters.
'If you want the whole string with only the first five as the pattern
'Remove the single quote in the middle of the string.
findPattern = inputStr '& Mid(Str, 6, (Len(Str)))
End Function
This seems like an easy approach:
's is the input string
dim pos, c, s_new, s_old
pos = 1 : c = 49
s_new = mid(s, 1, 5) ' take only first five characters
do while pos <= 5
s_old = s_new
s_new = replace(s_new, mid(s, pos, 1), chr(c))
if s_new <> s_old then c = c + 1
loop
s_new = replace(s_new, "1", "A")
s_new = replace(s_new, "2", "B")
s_new = replace(s_new, "3", "C")
s_new = replace(s_new, "4", "D")
s_new = replace(s_new, "5", "E")
'm assuming that you don't have any numeric characters in your input.
This has a certain elegance:
Function Pattern(r As Range)
Dim c&, i&, a
Const FORMULA = "iferror(find(mid(~,{2,3,4,5},1),left(~,{1,2,3,4})),)"
a = Evaluate(Replace(FORMULA, "~", r.Address))
c = 1: Pattern = "A"
For i = 1 To 4
If a(i) = 0 Then c = c + 1: a(i) = c
Pattern = Pattern & Chr$(64 + a(i))
Next
End Function
I had this for a while (it's handy for cryptograms), so I'll post it:
Function Pattern(ByVal sInp As String) As String
' shg 2012
' Returns the pattern of a string as a string of the same length
' First unique letter and all repeats is a, second is b, …
' E.g., Pattern("mississippi") returns "abccbccbddb"
Dim iChr As Long ' character index to sInp & Pattern
Dim sChr As String ' character in sInp
Dim iPos As Long ' position of first appearance of sChr in sInp
sInp = LCase(Trim(sInp))
If Len(sInp) Then
sChr = Chr(64)
Pattern = sInp
For iChr = 1 To Len(sInp)
iPos = InStr(sInp, Mid(sInp, iChr, 1))
If iPos = iChr Then ' it's new
sChr = Chr(Asc(sChr) + 1)
Mid(Pattern, iChr) = sChr
Else
Mid(Pattern, iChr) = Mid(Pattern, iPos, 1)
End If
Next iChr
End If
End Function

How to compare Strings for Percentage Match using vb.net?

I am banging my head against the wall for a while now trying different techniques.
None of them are working well.
I have two strings.
I need to compare them and get an exact percentage of match,
ie. "four score and seven years ago" TO "for scor and sevn yeres ago"
Well, I first started by comparing every word to every word, tracking every hit, and percentage = count \ numOfWords. Nope, didn't take into account misspelled words.
("four" <> "for" even though it is close)
Then I started by trying to compare every char in each char, incrementing the string char if not a match (to count for misspellings). But, I would get false hits because the first string could have every char in the second but not in the exact order of the second. ("stuff avail" <> "stu vail" (but it would come back as such, low percentage, but a hit. 9 \ 11 = 81%))
SO, I then tried comparing PAIRS of chars in each string. If string1[i] = string2[k] AND string1[i+1] = string2[k+1], increment the count, and increment the "k" when it doesn't match (to track mispellings. "for" and "four" should come back with a 75% hit.) That doesn't seem to work either. It is getting closer, but even with an exact match it is only returns 94%. And then it really gets screwed up when something is really misspelled. (Code at the bottom)
Any ideas or directions to go?
Code
count = 0
j = 0
k = 0
While j < strTempName.Length - 2 And k < strTempFile.Length - 2
' To ignore non letters or digits '
If Not strTempName(j).IsLetter(strTempName(j)) Then
j += 1
End If
' To ignore non letters or digits '
If Not strTempFile(k).IsLetter(strTempFile(k)) Then
k += 1
End If
' compare pair of chars '
While (strTempName(j) <> strTempFile(k) And _
strTempName(j + 1) <> strTempFile(k + 1) And _
k < strTempFile.Length - 2)
k += 1
End While
count += 1
j += 1
k += 1
End While
perc = count / (strTempName.Length - 1)
Edit: I have been doing some research and I think I initially found the code from here and translated it to vbnet years ago. It uses the Levenshtein string matching algorithm.
Here is the code I use for that, hope it helps:
Sub Main()
Dim string1 As String = "four score and seven years ago"
Dim string2 As String = "for scor and sevn yeres ago"
Dim similarity As Single =
GetSimilarity(string1, string2)
' RESULT : 0.8
End Sub
Public Function GetSimilarity(string1 As String, string2 As String) As Single
Dim dis As Single = ComputeDistance(string1, string2)
Dim maxLen As Single = string1.Length
If maxLen < string2.Length Then
maxLen = string2.Length
End If
If maxLen = 0.0F Then
Return 1.0F
Else
Return 1.0F - dis / maxLen
End If
End Function
Private Function ComputeDistance(s As String, t As String) As Integer
Dim n As Integer = s.Length
Dim m As Integer = t.Length
Dim distance As Integer(,) = New Integer(n, m) {}
' matrix
Dim cost As Integer = 0
If n = 0 Then
Return m
End If
If m = 0 Then
Return n
End If
'init1
Dim i As Integer = 0
While i <= n
distance(i, 0) = System.Math.Max(System.Threading.Interlocked.Increment(i), i - 1)
End While
Dim j As Integer = 0
While j <= m
distance(0, j) = System.Math.Max(System.Threading.Interlocked.Increment(j), j - 1)
End While
'find min distance
For i = 1 To n
For j = 1 To m
cost = (If(t.Substring(j - 1, 1) = s.Substring(i - 1, 1), 0, 1))
distance(i, j) = Math.Min(distance(i - 1, j) + 1, Math.Min(distance(i, j - 1) + 1, distance(i - 1, j - 1) + cost))
Next
Next
Return distance(n, m)
End Function
Did not work for me unless one (or both) of following are done:
1) use option compare statement "Option Compare Text" before any Import declarations and before Class definition (i.e. the very, very first line)
2) convert both strings to lowercase using .tolower
Xavier's code must be correct to:
While i <= n
distance(i, 0) = System.Math.Min(System.Threading.Interlocked.Increment(i), i - 1)
End While
Dim j As Integer = 0
While j <= m
distance(0, j) = System.Math.Min(System.Threading.Interlocked.Increment(j), j - 1)
End While