Sum numbers in cell containing text without spaces - excel

I have done extensive research on SO and google but not coming up with a solution.
While this question may have been asked earlier, my situation is slightly different
The function works fine if there is a space between the text and numbers. However, does not work if there aren't any spaces.
Function SumNumbers(rngS As Range, Optional strDelim As String = " ") As Double
Dim xNums As Variant, lngNum As Long
xNums = Split(rngS, strDelim)
For lngNum = LBound(xNums) To UBound(xNums) Step 1
SumNumbers = SumNumbers + Val(xNums(lngNum))
Next lngNum
End Function
My cell data looks like this: su9m11w11.5th8
I tried adding an alphabet array, but had no luck. Help is welcome.
strDelim = Array("F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "U")

Well, depening on your data, you could try:
Sub Test()
Dim StrIn As String: StrIn = "su9m11w11.5th8"
Debug.Print SumDigits(StrIn)
End Sub
Function SumDigits(str As String) As Double
With CreateObject("vbscript.regexp")
.Global = True
.Pattern = "[a-z]+"
SumDigits = Application.Evaluate(.Replace(str, "+") & ".0")
End With
End Function
Here I used a regular expression to substitute all the lowercase characters ranging a-z for a "+". The resulting string then can be evaluated to return the sum.
EDIT:
Another way could be to use a regular expression that will return all the possible (negative) numbers, and sum those:
Function SumDigits(str As String) As Double
With CreateObject("vbscript.regexp")
.Global = True
.Pattern = "-?\d+(?:\.\d+)?"
If .Test(str) Then
For Each Match In .Execute(str)
SumDigits = SumDigits + Match
Next
End If
End With
EDIT 2:
To return an average we should capture the amount of matching substrings and devide the sum by that count:
Function SumDigits(str As String, Optional avg As Boolean) As Double
With CreateObject("vbscript.regexp")
.Global = True
.Pattern = "-?\d+(?:\.\d+)?"
If .Test(str) Then
Set Matches = .Execute(str)
For Each Match In Matches
SumDigits = SumDigits + Match
Next
If avg Then SumDigits = SumDigits / Matches.Count
End If
End With
End Function
Call using Debug.Print SumDigits(StrIn, True) instead.

Here is a VBA function to produce either the sum or the average of your embedded numbers... and it handles negatives as well. And this is pure Excel VBA with no references to outside libraries:
Function SumEmbeddedNumbers#(s$, Optional bAverage As Boolean)
Dim i&, p&, max&, t&, pluses&
Dim b() As Byte, res() As Byte
Static keep() As Boolean
Const VALS$ = "0123456789.-"
If (Not Not keep) = 0 Then
ReDim keep(0 To 255)
For i = 1 To Len(VALS)
keep(Asc(Mid$(VALS, i, 1))) = 1
Next
End If
max = LenB(s)
ReDim res(0 To max)
b = StrConv(s, vbFromUnicode)
For i = 0 To Len(s) - 1
t = b(i)
If keep(t) Then
res(p) = t
p = p + 1
Else
If p Then
If res(p - 1) <> 43 Then
res(p) = 43
pluses = pluses + 1
p = p + 1
End If
End If
End If
Next
SumEmbeddedNumbers = Evaluate(Left$(StrConv(res, vbUnicode), p))
If bAverage Then SumEmbeddedNumbers = SumEmbeddedNumbers / (pluses + 1)
End Function
MsgBox SumEmbeddedNumbers("su9m11w11.5th8") '<---displays: 39.5
MsgBox SumEmbeddedNumbers("su9m11w11.5th8", True) '<---displays: 9.875

Related

Extract only numbers with 8 digits and does not followed by contain characters (. , #)

I am using a function to extract numbers from string with conditions that number with 8 digits and does not contain characters (. , #).
It works with 8 digits , but if the number is followed by characters (. , #) ,it also extract that number and that not required.
This my string 11111111 12345678.1 11111112 11111113 and the expected output is 11111111 11111112 11111113 without 12345678.1.
I tried to use negative Lookahead \d{8}(?!.,#) but it is useless.
Thanks all for your help.
Function Find8Numbers(st As String) As Variant
Dim regex As New RegExp
Dim matches As MatchCollection, mch As match
regex.Pattern = "\d{8}" 'Look for variable length numbers only
regex.IgnoreCase = True
regex.Global = True
regex.MultiLine = True
If (regex.Test(st) = True) Then
Set matches = regex.Execute(st) 'Execute search
For Each mch In matches
Find8Numbers = LTrim(Find8Numbers) & " " & mch.value
Next
End If
End Function
In line with your question and current attempt, you could indeed use regex:
Function Find8Numbers(st As String) As String
With CreateObject("vbscript.regexp")
.Pattern = "(?:^|\s)(\d{8})(?![.,#\d])"
.Global = True
If .Test(st) Then
Set Matches = .Execute(st)
For Each mch In Matches
Find8Numbers = LTrim(Find8Numbers & " " & mch.submatches(0))
Next
End If
End With
End Function
Invoke through:
Sub Test()
Dim s As String: s = "11111111 12345678.1 11111112 11111113"
Debug.Print Find8Numbers(s)
End Sub
Prints:
11111111 11111112 11111113
Pattern used:
(?:^|\s)(\d{8})(?![.,#\d])
See an online demo
(?:^|\s) - No lookbehind in VBA thus used a non-capture group to match start-line anchor or whitespace;
(\d{8}) - Exactly 8 digits in capture group;
(?![.,#\d]) - Negative lookahead to assert position isn't followed by any of given characters including digits.
I'm not sure you need Regex for what is a reasonably simple pattern. You could just go with a VBA solution:
Public Function Find8Numbers(str As String) As String
Dim c As String, c1 As String
Dim i As Long, numStart As Long
Dim isNumSeq As Boolean
Dim result As String
If Len(str) < 8 Then Exit Function
For i = 1 To Len(str)
c = Mid(str, i, 1)
If i = Len(str) Then
c1 = ""
Else
c1 = Mid(str, i + 1, 1)
End If
If c >= "0" And c <= "9" Then
If isNumSeq Then
If i - numStart + 1 = 8 Then
If c1 <> "." And c1 <> "," And c1 <> "#" Then
If result <> "" Then result = result & " "
result = result & Mid(str, numStart, 8)
isNumSeq = False
End If
End If
Else
If i > Len(str) - 8 + 1 Then Exit For
isNumSeq = True
numStart = i
End If
Else
isNumSeq = False
End If
Next
Find8Numbers = result
End Function

Sort alphabets in a word/string

Does excel vba have a function to sort a given word or string alphabetically? Also, what is this kind of a string manipulation called in technical/programming terms?
For e.g. Word = "Somestring"
Output = "egimnorSst"
Thanks.
If you have Excel O365 with the functions I've used below, you can use this formula:
=TEXTJOIN(,,SORT(MID(A1,SEQUENCE(LEN(A1)),1)))
or as indicated by #JvdV, instead of TEXTJOIN we can use the simpler:
=CONCAT(SORT(MID(A1,SEQUENCE(LEN(A1)),1)))
If y0u don't have those functions, you would need a UDF written in VBA.
Here is one that, since the sort strings should be relatively short, uses a simple Bubblesort to sort the string elements.
Option Explicit
Option Compare Text
Function sortString(S As String) As String
Dim str() As String
Dim I As Long
ReDim str(1 To Len(S))
For I = 1 To Len(S)
str(I) = Mid(S, I, 1)
Next I
BubbleSort str
sortString = Join(str, "")
End Function
Sub BubbleSort(TempArray)
'copied directly from support.microsoft.com
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 = LBound(TempArray) To UBound(TempArray) - 1
' If the element is greater than the element
' following it, exchange the two elements.
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)
End Sub
Though the question itself is very minimal I would like to answer nonetheless. If you not bothered having S and s reversed than:
Sub Test()
Dim x As Long
Dim str As String: str = "Somestring"
With CreateObject("System.Collections.ArrayList")
For x = 1 To Len(str)
.Add Mid(str, x, 1)
.Sort
Next
Debug.Print Join(.Toarray, "")
End With
End Sub
Results in:
egimnorsSt
If that is not what you want it becomes a bit more complicated I think since we cannot use ASCII codes (S = 83 and way lower than the other characters).
It may not be super pretty but try:
Sub Test()
Dim x As Long
Dim str As String, str_new As String
str = "abcdABCD"
With CreateObject("System.Collections.ArrayList")
For x = 1 To Len(str)
.Add Mid(str, x, 1)
.Sort
Next
str_new = Join(.Toarray, "")
End With
With CreateObject("vbscript.regexp")
.Global = True
.IgnoreCase = True
.Pattern = "([a-z])\1+"
If .Test(str_new) Then
For Each Match In .Execute(str_new)
str_new = Replace(str_new, Match, Application.Proper(Match)) 'Assuming no more than 1 of the same uppercase letters.
Next
End If
End With
Debug.Print str_new
End Sub
Results in:
AaBbCcDd
Another option if you have ExcelO365 with new DA-functions and value in A1:
=CONCAT(SORT(MID(A1,ROW(A1:INDEX(A:A,LEN(A1))),1)))
This would actually return egimnorSst

How to remove the LAST set Parentheses from a Excel text string that contains only numeric s

I have an excel spreadsheet with over 50,000 entries. The entries have a name and address and sometimes a phone number ALL in the same string. I am concentrating on the phone number part of the string which is always at the end and enclosed in parentheses. I have been trying to use VBA code to address this.
How to remove the LAST set Parentheses from a Excel text string that contains only numeric s between the parentheses. In any given string there may be either NO parentheses or multiple parentheses but I only want to remove that LAST set and leave the numbers contained there in the string
Example string "Toone Carkeet J., agt.,Alliance Assurance Co. Ltd. (Provident Life branch), 3 St. Andrew st. (1936)" I have tried using VBScript.RegExp to define "(1936)" but I cannot get the RegExp to match the string and replace the parentheses () with "".
For Each Cell In Range
If strPattern<> "" Then
strInput = Cell
With regEx
.Pattern="\(([0-9]))*)"
.Global=False
End With
If .Pattern= True Then
Replace(Cell.Value, "(","")
End If
Here are two quick user defined functions that do not rely on regular expressions. The first uses VBA's StrReverse and the second InStrRev.
Function RemoveParens1(str As String)
str = StrReverse(str)
str = Replace(str, "(", vbNullString, 1, 1)
str = Replace(str, ")", vbNullString, 1, 1)
RemoveParens1 = StrReverse(str)
End Function
Function RemoveParens2(str As String)
Dim o As Integer, c As Integer
o = InStrRev(str, "(")
c = InStrRev(str, ")")
str = Left(str, c - 1) & Mid(str, c + 1)
str = Left(str, o - 1) & Mid(str, o + 1)
RemoveParens2 = str
End Function
If you don't want to use UDFs, just pick the logic method you prefer and adapt it for your own purposes.
Here's one more using regular expression's Replace.
Function RemoveParens3(str As String)
Static rgx As Object, cmat As Object, tmp As String
If rgx Is Nothing Then Set rgx = CreateObject("vbscript.regexp")
With rgx
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = "\([0-9]*\)"
If .test(str) Then
Set cmat = .Execute(str)
tmp = cmat.Item(cmat.Count - 1)
tmp = Mid(tmp, 2, Len(tmp) - 2)
str = .Replace(str, tmp)
End If
End With
RemoveParens3 = str
End Function
Here's an example using similar logic to yours.
I changed the names of the range variables as it is not a good idea to use keywords for named variables, even if the editor will allow that.
Instead of just deleting the parentheses, we match the entire (nnnn) substring with the numbers inside a capturing group, and then replace that match with just the captured group.
Since Replace won't do anything if there is no match, there is no need to test.
Also, note that we set up the regEx OUTSIDE the loop.
With regEx
.Pattern = "\((\d+)\)"
.Global = False
End With
For Each myCell In myRange
myCell = regEx.Replace(myCell, "$1")
Next myCell
If necessary due to other substrings with the same pattern, you could change the pattern to ensure the match is at the end of the line, or that it is the last pattern of that type in the string.
For example:
Substring at end of the line
\((\d+)\)$
Substring the last one
\((\d+)\)(?!.*\(\d+\))
And there may be other modifications necessary if your string is in multiple lines within the cell.
Dim x, y, z As Long
x = 2 'ASSUMING YOUR DATA START AT RANGE A2
With Sheet1
Do While .Cells(x, 1).Value <> ""
If Right(.Cells(x, 1).Value, 1) = ")" Then
.Cells(x, 1).Value = Replace(.Cells(x, 1).Value, ")", "")
z = VBA.Len(.Cells(x, 1).Value)
For y = z To 1 Step -1
If Mid(.Cells(x, 1).Value, y, 1) = "(" Then
.Cells(x, 1).Value = Replace(.Cells(x, 1).Value, "(", "")
Exit For
End If
Next y
x = x + 1
End If
Loop
End With

To get appropriate maximum number from a string

My string is su=45, nita = 30.8, raj = 60, gita = 40.8 . This has reference to SO question Extract maximum number from a string
I am utilizing maxNums function and getting result as 40.8 whereas I would like it to be 60. Where an amendment in code line would get me the desired result.Code reproduced below to avoid cross reference.If this string contains all numbers with decimal point then I get the correct result but the data in consideration from external sources could have whole numbers.
Option Explicit
Option Base 0 '<~~this is the default but I've included it because it has to be 0
Function maxNums(str As String)
Dim n As Long, nums() As Variant
Static rgx As Object, cmat As Object
'with rgx as static, it only has to be created once; beneficial when filling a long column with this UDF
If rgx Is Nothing Then
Set rgx = CreateObject("VBScript.RegExp")
End If
maxNums = vbNullString
With rgx
.Global = True
.MultiLine = False
.Pattern = "\d*\.\d*"
If .Test(str) Then
Set cmat = .Execute(str)
'resize the nums array to accept the matches
ReDim nums(cmat.Count - 1)
'populate the nums array with the matches
For n = LBound(nums) To UBound(nums)
nums(n) = CDbl(cmat.Item(n))
Next n
'test array
'Debug.Print Join(nums, ", ")
'return the maximum value found
maxNums = Application.Max(nums)
End If
End With
End Function
There are one or two issues with your code. The first one is that the regular expression isn't looking for decimal numbers. If you change it to
.Pattern = "\d+\.?(\d?)+"
it will work better. In short:
\d+ = At least one digit
.? = An optional dot
(\d?)+ = Optional numbers
This is not a waterproof expression, but it works to some extent at least.
The second issue is the potential problem of differing decimal symbols, in which case you will need to do some search and replace before processing.
If its always x=number I think it's simpler to loop over each delimited value then read past the = for the value:
Function MaxValue(data As String)
Dim i As Long, value As Double
Dim tokens() As String: tokens = Split(data, ",")
For i = 0 To UBound(tokens)
'// get the value after = as a double
value = CDbl(Trim$(Mid$(tokens(i), InStr(tokens(i), "=") + 1)))
If (value > MaxValue) Then MaxValue = value
Next
End Function
Without Regex:
Public Function maxNums(str As String) As Double
Dim i As Long, L As Long, s As String, wf As WorksheetFunction, brr()
Set wf = Application.WorksheetFunction
L = Len(str)
For i = 1 To L
s = Mid(str, i, 1)
If s Like "[0-9]" Or s = "." Then
Else
Mid(str, i, 1) = " "
End If
Next i
str = wf.Trim(str)
arr = Split(str, " ")
ReDim brr(LBound(arr) To UBound(arr))
For i = LBound(arr) To UBound(arr)
brr(i) = CDbl(arr(i))
Next i
maxNums = wf.Max(brr)
End Function

Excel VBA loop through a string of numbers until a letter is found

I have a string in a cell, lets say it says "Client Ref: F123456PassPlus".
It's possible the string not have a letter before the numbers, it's possible there is a symbol in the numbers and it's possible there is a space between the letter and the numbers.
I need to extract only the numbers as a variable. I have the code to do it, but it doesn't know when to stop looping through the string. It should stop when there is something other than a number or symbol but it carries on instead.
IsNumber = 1
ref = ""
If branch = "" Then
e = b
Else
e = b + 1
End If
f = 1
While IsNumber = 1
For intpos = 1 To 15
ref = Mid(x, e, f)
f = f + 1
Select Case Asc(ref)
Case 45 To 57
IsNumber = 1
Case Else
IsNumber = 0
Exit For
End Select
Next
IsNumber = 0
Wend
Any variable letters there that don't have definitions have been previously defined, e tells the code where to start copying and x is the cell that contains the string. For now, it all works fine, it starts at the number and copies them and builds them into a bigger and bigger string, but it will only stop when intpos reaches 15.
There is nothing wrong with how your trying to accomplish this task but I can't help myself from suggesting regex :-)
This example will strip all non-digits from the string located in A1 and present the result in a message box. The pattern used is [^0-9]
Sub StripDigits()
Dim strPattern As String: strPattern = "[^0-9]"
Dim strReplace As String: strReplace = vbnullstring
Dim regEx As New RegExp
Dim strInput As String
Dim Myrange As Range
Set Myrange = ActiveSheet.Range("A1")
If strPattern <> "" Then
strInput = Myrange.Value
strReplace = ""
With regEx
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = strPattern
End With
If regEx.test(strInput) Then
MsgBox (regEx.Replace(strInput, strReplace))
Else
MsgBox ("Not matched")
End If
End If
End Sub
Make sure you add a reference to "Microsoft VBScript Regular Expressions 5.5"
For more information on how to use regex in Excel, including examples of looping through ranges check out this post.
Results:
I got rid of the Asc check and added a check against each character as you pass it before building the numerical "string".
IsNumber = 1
ref = ""
If branch = "" Then
e = b
Else
e = b + 1
End If
f = 1
While IsNumber = 1
For intpos = 1 To 15
char = Mid(x, e + intpos, 1)
f = f + 1
If IsNumeric(char) Then
ref = Mid(x, e, f)
IsNumber = 1
Else
IsNumber = 0
Exit For
End If
Next
IsNumber = 0
Wend
This code, loosely based on your, works (produces "12345"). For large strings or more complex extraction needs, I would consider learning about the regex COM object.
Function ExtractNumber(ByVal text As String) As String
ExtractNumber = ""
foundnumber = False
For e = 1 To Len(text)
ref = Mid(text, e, 1)
Select Case Asc(ref)
Case 45 To 57 'this includes - . and /, if you want only digits, should be 48 to 57
foundnumber = True
ExtractNumber = ExtractNumber & ref
Case Else
If foundnumber = True Then Exit For
End Select
Next
End Function

Resources