Concatenate strings and remove excess comma - regex

I'm trying to concatenate multiple strings and separate them by comma,
and then subsequently to remove excess, leading and trailing commata.
For example, with an input of TEST("", "b", "c", "", ""), I would like to get
b, c
However, my regex ,$| ,+|^, does not really take repeated commas into account:
Function TEST(a, b, c, d, e)
res = a & ", " & b & ", " & c & ", " & d & ", " & e
Debug.Print (res)
Dim regex As Object, str, result As String
Set regex = CreateObject("VBScript.RegExp")
With regex
.Pattern = ",$| ,+|^,"
End With
Dim ReplacePattern As String
ReplacePattern = ""
res = regex.Replace(res, ReplacePattern)
TEST = res
End Function
How can I do this?

Most elegant is #ScottCraner's suggestion of TEXTJOIN (will remove this part of answer, if he wishes to post this as his own)
Private Function nonEmptyFields(ParamArray strings() As Variant) As String
nonEmptyFields = WorksheetFunction.TextJoin(",", True, Array(strings))
End Function
Note: This will only work for Office 365+, but you can always create your own version of
TEXTJOIN
Another option would be to loop over the ParamArray of strings and add them together, depending on their content (whether they are populated or empty)
Private Function nonEmptyFields(ParamArray strings() As Variant) As String
Dim result As String
Dim i As Byte
For i = LBound(strings) To UBound(strings)
If Len(strings(i)) <> 0 Then
If result = vbNullString Then
result = strings(i)
Else
result = result & "," & strings(i)
End If
End If
Next i
nonEmptyFields = result
End Function
Both would yield desired result with set up of
Debug.Print nonEmptyFields(a, b, c, d, e, f) ' "", "b", "c", "", "", ""

My ugly solution maintaining the same parameters:
Function TEST(a, b, c, d, e)
If a <> "" Then res = a
If b <> "" Then
If res <> "" Then
res = res & ", " & b
Else
res = b
End If
End If
If c <> "" Then
If res <> "" Then
res = res & ", " & c
Else
res = c
End If
End If
If d <> "" Then
If res <> "" Then
res = res & ", " & d
Else
res = d
End If
End If
If e <> "" Then
If res <> "" Then
res = res & ", " & e
Else
res = e
End If
End If
TEST = res
End Function

Related

Tokenise mathematical (infix) expression in VBA

I need to tokenize a mathematical expression using VBA. I have a working solution but am looking for a more efficient way of doing it (possibly RegExp).
My current solution:
Function TokeniseTheString(str As String) As String()
Dim Operators() As String
' Array of Operators:
Operators = Split("+,-,/,*,^,<=,>=,<,>,=", ",")
' add special characters around all "(", ")" and ","
str = Replace(str, "(", Chr(1) & "(" & Chr(1))
str = Replace(str, ")", Chr(1) & ")" & Chr(1))
str = Replace(str, ",", Chr(1) & "," & Chr(1))
Dim i As Long
' add special characters around all operators
For i = LBound(Operators) To UBound(Operators)
str = Replace(str, Operators(i), Chr(1) & Operators(i) & Chr(1))
Next i
' for <= and >=, there will now be two special characters between them instead of being one token
' to change < = back to <=, for example
For i = LBound(Operators) To UBound(Operators)
If Len(Operators(i)) = 2 Then
str = Replace(str, Left(Operators(i), 1) & Chr(1) & Chr(1) & Right(Operators(i), 1), Operators(i))
End If
Next i
' if there was a "(", ")", "," or operator next to each other, there will be two special characters next to each other
Do While InStr(str, Chr(1) & Chr(1)) > 0
str = Replace(str, Chr(1) & Chr(1), Chr(1))
Loop
' Remove special character at the end of the string:
If Right(str, 1) = Chr(1) Then str = Left(str, Len(str) - 1)
TokeniseTheString = Split(str, Chr(1))
End Function
Test using this string IF(TestValue>=0,TestValue,-TestValue) gives me the desired solution.
Sub test()
Dim TokenArray() As String
TokenArray = TokeniseTheString("IF(TestValue>=0,TestValue,-TestValue)")
End Sub
I have never seen regular expressions before and tried to implement this into VBA. The problem I am having is that the RegExp object in VBA doesn't allow positive lookbehind.
I will appreciate any more efficient solution than mine above.
As suggested by #Florent B, the following function gives the same results using RegExp:
Function TokenRegex(str As String) As String()
Dim objRegEx As New RegExp
Dim strPattern As String
strPattern = "(""(?:""""|[^""])*""|[^\s()+\-\/*^<>=,]+|<=|>=|\S)\s*"
With objRegEx
.Global = True
.MultiLine = False
.IgnoreCase = True
.Pattern = strPattern
End With
str = objRegEx.Replace(str, "$1" & ChrW(-1))
If Right(str, 1) = ChrW(-1) Then str = Left(str, Len(str) - 1)
TokenRegex = Split(str, ChrW(-1))
End Function

VBA or PostgreSQL: remove unneeded parentheses from a mathematical equation string

I'm looking to remove mathematically unneeded parentheses from a mathematical equation string. I need to do this either, and preferably, in PostgreSQL 6 or VBA.
For example, I have the following string value in a PostgreSQL database:
PercentileRank((([bp47244]+([bp47229][ttm]))/(AvgAeTe([bp48918]))))
And I need it to look like this (edited/corrected):
PercentileRank(([bp47244]+[bp47229][ttm])/AvgAeTe([bp48918]))
I'd prefer a function or query in PostgreSQL, but a VBA solution could work.
Note PercentileRank() and AvgAeTe() are functions. This [bp47244] and [bp47229][ttm] each represent single numbers/variables, but they could be expressed in any way, like [abc123] and [xyz321][ttm]. I see a lot of examples out there, but I don't see one using PostgreSQL or VBA that works for me, so I thought it would be a good question.
Of course I am looking for a general solution that can be applied to any equation.
I'm working on this now, so if I find an answer before one is posted here, I'll share; however, I am not good at regex (not that the solution has to use regex).
Thanks!
UPDATE:
I'm working off this logic:
Let L be operator immediately left of the left parenthesis, or nil
Let R be operator immediately right of the right parenthesis, or nil
If L is nil and R is nil:
Redundant
Else:
Scan the unparenthesized operators between the parentheses
Let X be the lowest priority operator
If X has lower priority than L or R:
Not redundant
Else:
Redundant
from this link:
Remove redundant parentheses from an arithmetic expression
I'll code something up in VBA that follows this logic and post an answer.
This seems to work for my situation:
Function RemoveParens(s As String) As String
'remove unecessary parentheses
'exponents not implemented
'mathematical brackets are not implmented (it is assumed that only parentheses are used to create mathematical order)
'brakets are assumed to identify a variable or calculation on a variable
'[bp47229][ttm] -> one value/variable; [xyz123] -> one value/variable
'logic based on Antti Huima's answer:
'https://stackoverflow.com/questions/44203517/vba-or-postgresql-remove-unneeded-parentheses-from-a-mathematical-equation-stri
's = "PercentileRank((([bp47244]+([bp47229][ttm]))/(AvgAeTe([bp48918]))))"
's = "PercentileRank(2*(1+3)(5*4))"
If InStr(1, s, "^") > 0 Then
msgbox "Exponents are not implemented in RemoveParens"
End If
ReDim arS(1 To Len(s)) As String
Dim i As Integer
For i = 1 To Len(s)
arS(i) = Mid(s, i, 1)
Next i
Dim iCnt As Integer
iCnt = 0
Dim iLen As Integer
iLen = Len(s)
Dim sTmp As String
Dim bRemove As Boolean
bRemove = False
Dim sLfOpr As String
Dim sRtOpr As String
Dim iCntBtwn As Integer
Dim sLast As String
'loop through chars
Do
iCnt = iCnt + 1
sTmp = Mid(s, iCnt, 1)
If sTmp = "(" Then
if iCnt - 1 <= 0 then
sLfOpr = ""
else
sLfOpr = Mid(s, iCnt - 1, 1)
end if
'in case we have "5(...) or (...)(...)
If IsNumeric(sLfOpr) Or sLfOpr = ")" Then
sLfOpr = "*"
End If
'if it isn't an oper then clear it
If sLfOpr <> "+" _
And sLfOpr <> "-" _
And sLfOpr <> "/" _
And ((Not IsAlpha(sLfOpr) = True) Or (Not Mid(s, iCnt, 1) = "(")) _
And sLfOpr <> "*" _
Then
sLfOpr = ""
End If
'find the matching paren to the right of LfOpr
Dim iCntR As Integer
iCntR = iCnt
Dim iCntParen As Integer
iCntParen = 1
Dim sTmpR As String
sTmpR = ""
Do
iCntR = iCntR + 1
sTmpR = Mid(s, iCntR, 1)
If sTmpR = "(" Then
iCntParen = iCntParen + 1
ElseIf sTmpR = ")" Then
iCntParen = iCntParen - 1
End If
'we found the close paren that matches the open paren
If iCntParen = 0 Then
sRtOpr = Mid(s, iCntR + 1, 1)
'in case we have "(...)5 or (...)(...)
If IsNumeric(sRtOpr) Or sRtOpr = "(" Then
sRtOpr = "*"
End If
If sRtOpr <> "+" _
And sRtOpr <> "-" _
And sRtOpr <> "/" _
And ((Not IsAlpha(sRtOpr) = True) Or (Not Mid(s, iCntR, 1) = "(")) _
And sRtOpr <> "*" _
Then
sRtOpr = ""
End If
If sRtOpr = "" And sLfOpr = "" Then
arS(iCnt) = ""
arS(iCntR) = ""
'go to the next overall open paren
Exit Do
Else
' ------------ search btwn parens -------------------
Dim iCntParenOp As Integer
Dim iCntParenCl As Integer
iCntParenOp = 0
iCntParenCl = 0
Dim sTmpB As String
sTmpB = ""
Dim sLowOpr As String
sLowOpr = ""
Dim iCntRLw As Integer
iCntRLw = iCnt
Dim bInSub As Boolean
bInSub = False
Dim bNoOpr As Boolean
bNoOpr = True
'loop through chars between the two parens
For i = iCnt + 1 To iCntR
iCntRLw = iCntRLw + 1
sTmpR = Mid(s, iCntRLw, 1)
If sTmpR = "(" Then
iCntParenOp = iCntParenOp + 1
bInSub = True
ElseIf sTmpR = ")" Then
iCntParenCl = iCntParenCl + 1
If bInSub = True And iCntParenCl = iCntParenOp Then
bInSub = False
End If
End If
'we found the close paren that matches the open paren
'and we are not in a nested/sub paren
If bInSub = False Then
'in case we have "(...)5 or (...)(...)
If (IsNumeric(sTmpR) And Mid(s, iCntRLw + 1, 1) = "(") Or (sTmpR = "(" And Mid(s, iCntRLw + 1, 1) = "(") Then
sTmp = "*"
End If
'it is an operator
If sTmpR = "+" _
Or sTmpR = "-" _
Or sTmpR = "/" _
Or ((IsAlpha(sTmpR) = True) And (Mid(s, iCntRLw + 1, 1) = "(")) _
Or sTmpR = "*" _
Or bNoOpr = True _
Then
'see if sLowROpr operater has lower priority than sLfOpr and sRtOpr
If Not IsLowerPri(sTmpR, sRtOpr, sLfOpr) Then
arS(iCnt) = ""
arS(iCntR) = ""
Exit For
End If
bNoOpr = False
End If
End If
Next i
End If
Exit Do 'always stop loop if iCntParen = 0
End If
Loop While iCntR <> iLen
End If
Loop While iCnt <> iLen
Dim sOut As String
For i = LBound(arS) To UBound(arS)
sOut = sOut & arS(i)
Next i
'Debug.Print s
RemoveParens = sOut
End Function
Function IsLowerPri(sTestOpr As String, sRtOpr As String, sLfOpr As String) As Boolean
'exponents not implemented yet
Dim iTestOpr As Integer
Dim iRtOpr As Integer
Dim iLfOpr As Integer
iTestOpr = 1
If sTestOpr = "+" Or sTestOpr = "-" Then
iTestOpr = 1
ElseIf sTestOpr = "*" Or sTestOpr = "/" Then
iTestOpr = 2
ElseIf IsAlpha(sTestOpr) And sTestOpr <> "" Then
iTestOpr = 3
End If
If sRtOpr = "+" Or sRtOpr = "-" Then
iRtOpr = 1
ElseIf sRtOpr = "*" Or sRtOpr = "/" Then
iRtOpr = 2
ElseIf IsAlpha(sRtOpr) And sRtOpr <> "" Then
iRtOpr = 3
End If
If sLfOpr = "+" Or sLfOpr = "-" Then
iLfOpr = 1
ElseIf sLfOpr = "*" Or sLfOpr = "/" Then
iLfOpr = 2
ElseIf IsAlpha(sLfOpr) And sLfOpr <> "" Then
iLfOpr = 3
End If
If iTestOpr < iRtOpr Or iTestOpr < iLfOpr Then
IsLowerPri = True
Else
IsLowerPri = False
End If
End Function
It needs a lot of clean-up and probably some testing. I will give answer credit to whomever posts the best improvement or a different solution all together that is better.
UPDATE:
Forgot this function:
Public Function IsAlpha(strValue As String) As Boolean
IsAlpha = strValue Like WorksheetFunction.Rept("[a-zA-Z]", Len(strValue))
End Function

Regular Expression to Test Date VBA

I am looking for a code to test date Format, the date should be in one of these formats
year : 13xx - 20xx
month: xx,x
day: xx,x
the hole date would be on of the following
2012/1/1
2012/01/01
2012/1/01
2012/01/1
I tried the following
Option Explicit
Sub ttt()
MsgBox (testDate("2012/01/01"))
End Sub
Function testDate(strDateToBeTested As String) As Boolean
Dim regularExpression, match
Set regularExpression = CreateObject("vbscript.regexp")
testDate = False
'regularExpression.Pattern = "(14|13|19|20)[0-9]{2}[- /.]([0-9]{1,2})[- /.]([0-9]{1,2})"
'regularExpression.Pattern = "(\d\d\d\d)/(\d|\d\d)/(\d|/dd)"
regularExpression.Pattern = "([0-9]{4}[ /](0[1-9]|[12][0-9]|3[01])[ /](0[1-9]|1[012]))"
regularExpression.Global = True
regularExpression.MultiLine = True
If regularExpression.Test(strDateToBeTested) Then
' For Each match In regularExpression.Execute(strDateToBeTested)
If Len(strDateToBeTested) < 10 Then
testDate = True
' Exit For
End If
'End If
End If
Set regularExpression = Nothing
End Function
The more and more I thought about this (and some research), the more I figured that regex is not the best solution to this format problem. Combining a couple of other ideas (with the ReplaceAndSplit function attributed to the owner), this is what I came up with.
Option Explicit
Sub ttt()
Dim dateStr() As String
Dim i As Integer
dateStr = Split("2012/1/1,2012/01/01,2012/1/01,2012/01/1,1435/2/2," & _
"1435/02/02,1900/07/07,1435/02/02222222,2015/Jan/03", ",")
For i = 1 To UBound(dateStr)
Debug.Print "trying '" & dateStr(i) & "' ... " & testDate(dateStr(i))
Next i
End Sub
Function testDate(strDateToBeTested As String) As Boolean
Dim dateParts() As String
Dim y, m, d As Long
dateParts = ReplaceAndSplit(strDateToBeTested, "/.-")
testDate = False
If IsNumeric(dateParts(0)) Then
y = Int(dateParts(0))
Else
Exit Function
End If
If IsNumeric(dateParts(1)) Then
m = Int(dateParts(1))
Else
Exit Function
End If
If IsNumeric(dateParts(2)) Then
d = Int(dateParts(2))
Else
Exit Function
End If
If (y >= 1435) And (y < 2020) Then 'change or remove the upper limit as needed
If (m >= 1) And (m <= 12) Then
If (d >= 1) And (d <= 30) Then
testDate = True
End If
End If
End If
End Function
'=======================================================
'ReplaceAndSplit by alainbryden, optimized by aikimark
'Uses the native REPLACE() function to replace all delimiters with a common
'delimiter, and then splits them based on that.
'=======================================================
Function ReplaceAndSplit(ByRef Text As String, ByRef DelimChars As String) As String()
Dim DelimLen As Long, Delim As Long
Dim strTemp As String, Delim1 As String, Arr() As String, ThisDelim As String
strTemp = Text
Delim1 = Left$(DelimChars, 1)
DelimLen = Len(DelimChars)
For Delim = 2 To DelimLen
ThisDelim = Mid$(DelimChars, Delim, 1)
If InStr(strTemp, ThisDelim) <> 0 Then _
strTemp = Replace(strTemp, ThisDelim, Delim1)
Next
ReplaceAndSplit = Split(strTemp, Delim1)
End Function

Excel - Extract all occurrences of a String Pattern + the subsequent 4 characters after the pattern match from a cell

I am struggling with a huge Excel sheet where I need to extract from a certain cell (A1),
all occurrences of a string pattern e.g. "TCS" + the following 4 characters after the pattern match e.g. TCS1234 comma-separated into another cell (B1).
Example:
Cell A1 contains the following string:
HRS164, SRS3439(s), SRS3440(s), SRS3441(s), SRS3442(s), SRS3443(s), SRS3444(s), SRS3445(s), SRS3449(s), SRS3450(s), SRS3451(s), SRS3452(s), SYSBASE.SSS300(s), TCS3715(s), TCS3716(s), TCS3717(s), TCS4037(s), TCS1234
All TCS-Numbers shall be comma-separated in B1:
TCS3715, TCS3716, TCS3717, TCS4037, TCS1234
It is not necessary to also extract the followed "(s)".
Could someone please help me (excel rookie) with this challenge?
TIA Erika
Here is what I would use for something like that: also a user defined function:
Function GetTCS(TheString)
For Each TItem In Split(TheString, ", ")
If Left(TItem, 3) = "TCS" Then GetTCS = GetTCS & TItem & " "
Next
GetTCS = Replace(Trim(GetTCS), " ", ", ")
End Function
This returns "TCS3715(s), TCS3716(s), TCS3717(s), TCS4037(s), TCS1234" out of your string. If you don't know how to create a user defined function, just ask, it's pretty straight forward and I'd be happy to show you. Hope this helps.
Try the following User Defined Function:
Public Function Xtract(r As Range) As String
Dim s As String, L As Long, U As Long
Dim msg As String, i As Long
s = Replace(r(1).Text, " ", "")
ary = Split(s, ",")
L = LBound(ary)
U = UBound(ary)
Xtract = ""
msg = ""
For i = L To U
If Left(ary(i), 3) = "TCS" Then
If msg = "" Then
msg = Left(ary(i), 7)
Else
msg = msg & "," & Left(ary(i), 7)
End If
End If
Next i
Xtract = msg
End Function
If the TCS-parts are always at the end of the string as in your example, I would use (in B1):
=REPLACE(A1,1,FIND("TCS",A1)-1,"")

Find and Replace with ASP Classic

I have an function in ASP VB. and I need to replace the exact word in it. For example I have an string like "wool|silk/wool|silk". I want to replace just silk and not silk/wool.
' "|" is a devider
cur_val = "wool|silk/wool|silk"
cur_val_spl = Split("wool|silk/wool|silk", "|")
key_val = "silk"
For Each i In cur_val_spl
If i = key_val Then
cur_val = Replace(cur_val, ("|" & i), "")
cur_val = Replace(cur_val, i, "")
End If
Next
Response.Write(cur_val)
In this case my result would be "wool/wool" but what I really want is this "wool|silk/wool".
I really appreciate any help.
You should build a new string as you go
' "|" is a devider
cur_val = "wool|silk/wool|silk"
cur_val_spl = Split("wool|silk/wool|silk", "|")
result = ""
key_val = "silk"
addPipe = false
For Each i In cur_val_spl
If i <> key_val Then
if addPipe then
result = result & "|"
else
addPipe = true
end if
result = result & i
End If
Next
Response.Write(result)
you could do it with a regular expression but this is shorter
cur_val = "wool|silk/wool|silk"
Response.Write left(mid(replace("|"&cur_val&"|","|wool|","|silk|"),2),len(cur_val))
'=>silk|silk/wool|silk
Too bad you allready accepted the other answer 8>)