How to pass a string with parameters for evaluation in Excel? - excel

In VBA, is there a way to create a function which receives one of it's parameters as a string with a condition to be evaluated by an IF block?
This should give an idea of what I am looking for, but I now it's not that simple:
Function StringAsCondition(a As String) As Boolean
Dim result As Boolean
Dim b As Long
Dim c As Long
b = 4
c = 2
If a Then
result = True
End If
StringAsCondition = result
End Function
Sub Test()
Dim a As String
a = "b >= c"
Dim functionresult As Boolean
functionresult = StringAsCondition(a)
MsgBox functionresult
End Sub

The evaluation of the string is actually a rather easy task in the "fancy" programming languages (all but VBA). There, you would simply use some type of string formatting, replace and evaluate.
In VBA, you may build your own string formatting (credits Is there an equivalent of printf or String.Format in Excel) and use it:
Sub Test()
Dim condition As String
Dim b As Long, c As Long
b = 4
c = 2
condition = "{0} >= {1}"
Debug.Print Application.Evaluate(StringFormat(condition, b, c))
End Sub
Public Function StringFormat(mask As String, ParamArray tokens()) As String
Dim i As Long
For i = LBound(tokens) To UBound(tokens)
mask = Replace$(mask, "{" & i & "}", tokens(i))
Next
StringFormat = mask
End Function

My answer just tries to present the real solution (of #Vitiata) in a way to directly answer the request:
Function StringAsCondition(cond As String) As Boolean
Dim result As Boolean
Dim b As Long
Dim c As Long
b = 4
c = 2
result = CBool(Application.Evaluate(StringFormat(cond, b, c)))
StringAsCondition = result
End Function
Sub TestEV()
Dim a As String
Dim functionresult As Boolean
a = "{0} >= {1}" 'instead of a = "b >= c"
functionresult = StringAsCondition(a)
MsgBox functionresult
End Sub
Private Function StringFormat(mask As String, ParamArray tokens()) As String
Dim i As Long
For i = 0 To UBound(tokens)
mask = Replace$(mask, "{" & i & "}", tokens(i))
Next
StringFormat = mask
End Function

Allow placeholding variables in any order
Just in addition to #Vityata 's clever solution, a similar solution with a mask definition closer to common mathematical formulae with variables in any order.
So an expression (condition) of e.g.
y >= x*c would be transformed easily to a mask string like
expression = "{y} >= {x}*{c}"
Sub TestStringFormat()
' Purp: evaluate mask with place holding variables in any order
Dim expression As String
expression = "{y} >= {x}*{c}" ' "17 >= 4*2" ~~> True
Dim myVars As String: myVars = "c,x,y" ' "c,x,y", c x y
Debug.Print Application.Evaluate(StrFrm(expression, myVars, 2, 4, 17))
End Sub
Public Function StrFrm(mask As String, myVars As String, ParamArray tokens()) As String
' Purpose: mask with place holding variables in any order ' e.g. y >= x*c
' Note: modified by T.M.; credit to https://stackoverflow.com/users/246342/alex-k
' Site: https://stackoverflow.com/questions/17233701/is-there-an-equivalent-of-printf-or-string-format-in-excel
Dim vars: vars = Split(myVars, ",") ' variables c,x,y
Dim i As Long
For i = LBound(vars) To UBound(vars)
mask = Replace$(mask, "{" & vars(i) & "}", tokens(i))
Next
StrFrm = mask
Debug.Print StrFrm
End Function

Related

In excel cell there must be some string values with comma separated ex: apple,orange,grapes,pine apple,Maa

Example:
if I have in A1: apple,Orange, Grapes I would like to have missing names in B1:Pineapple,Maa.
If A2: Grapes,Pineapple then I would have in B2:Apple,Orange,Maa
I searched for a solution online, but I find same type for missing numbers only. Please help on this
I'd be glad if I can have a solution here. Thanks.
I tired the below:
Public Function MissingWords As String
Dim temp As String
Temp = Replace (stringList, "")
temp = Replace(temp, "")
Dim arr As Variant
Arr = Split (temp, ", ")
Dim newstrings As String
Newstrings = " Apple,Orange,grapes,pineapple, maa"
Dim i As Long
For i = LBound (arr) To UBound(arr)
Newstrings = Replace (newstrings, arr(I) & ", ", "")
Next
Newstrings = Left$(newstrings, Lena(newstrings) - 1)
Missingstrings = newstrings
End function
If I applied this in excel getting value error
Use the following function in a worksheet like
=GetMissingWordsFromList(A1,"Apple,Orange,Grapes,Pineapple,Maa")
And if A1 is Pineapple,Maa you will get Apple,Orange,Grapes in return.
Note that this is case sensitive so Pineapple and pineapple is not considered the same. Also there must not be any spaces after your commas , because it cannot handle that.
Option Explicit
Public Function GetMissingWordsFromList(ByVal Words As String, ByVal WordsList As String, Optional ByVal Delimiter As String = ",") As String
Dim WordsArr() As String
WordsArr = Split(Words, Delimiter)
Dim WordsListArr() As String
WordsListArr = Split(WordsList, Delimiter)
Dim RetVal As String
Dim Word As Variant
For Each Word In WordsListArr
If Not IsInArray(Word, WordsArr) Then
RetVal = RetVal & IIf(RetVal = vbNullString, "", ",") & Word
End If
Next Word
GetMissingWordsFromList = RetVal
End Function
Private Function IsInArray(ByVal What As String, ByVal InArray As Variant) As Boolean
IsInArray = IsNumeric(Application.Match(What, InArray, 0))
End Function
to make it case insensitive (so it will find Pineapple,Maa in a list like apple,orange,grapes,pineapple,maa) you need to use
WordsArr = Split(LCase(Words), Delimiter)
and
If Not IsInArray(LCase(Word), WordsArr) Then
instead.
the last line for your function should be
MissingWords=NewStrings
Now, your function always returns nothing.
Missingstrings is not defined in the 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 convert a numeric value to words Bangladeshi Currency in Excel using VBA

enter image description here
Please see the image:
https://support.microsoft.com/en-us/help/213360/how-to-convert-a-numeric-value-into-english-words-in-excel
This may be more than you bargained for, but that may be better than if it were less. Try it. But first, please understand the setup. The idea is that you have a cell - in a worksheet, of course - in which you enter an amount. Then you have another cell - presumed to be on the same worksheet, but not necessarily so - in which to display the amount in words. Paste the calling procedure which follows here in the code sheet of the worksheet on which you have the cell to contain the amount.
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Const AmountCell As String = "B5" ' read the amount from here
Const TargetCell As String = "D5" ' write the words here
Const Indian As Boolean = True
If Target.Address = Range(AmountCell).Address Then
Call WriteAmountInWords(Target.Value, Range(TargetCell), Indian)
End If
End Sub
This code has a lot parameters you can set. They start in the above procedure. The amount will be written in cell B5. You can specify any other cell. You can add code to specify several cells. If you want the same action on another sheet you need to paste a copy of the code in that other sheet's code sheet as well.
The above code specifies the words to be written to cell D5. You can specify any other cell implicitly or relative to the AmountCell. That is another task of programming not covered here.
Finally, you can specify Indian as either True or False. If you specify it as True you get lakhs and crores. False will produce millions and billions. If such is your need you can set this property programmatically, too. Note, however, that the present structure doesn't lend itself to dynamic changes. You would have to replace the constants with variables.
The above code call the procedure WriteAmountInWords which has a lot of supporting code it needs. All of that must be on a new, normal (not class and not form) code module. Its name isn't important, but you might call it SpellNum. Paste all of the following code in that module.
Option Explicit
Option Base 0
Enum Ncr ' Index to Array Curr()
NcrCurr
NcrOnly ' word used when there are no cents
NcrAnd ' word used between dollars and cents
NcrFraction
End Enum
Enum Nct ' CaseType
NctLower ' = all lower case
NctFirst ' = Only first character in upper case
NctProper ' = Each word's first character capitalised (Default)
NctUpper ' = all caps
End Enum
Enum Ngp ' Number groups: Powers of 1000
NgpN
NgpM ' = 1000's
NgpMM ' = millions
NgpBn ' = billions
NgpDec ' decimals
End Enum
Const SpellCurr As String = "dollar,only,and,cent"
Const Ones As String = "zero one two three four five six seven eight nine"
Const Teens As String = "teen eleven twelve thir four fif six seven eigh nine"
Const Tens As String = "null ten twenty thirty fourty fifty sixty seventy eighty ninety"
Const Powers_En As String = "hundred thousand million billion"
Const Powers_In As String = "hundred thousand lakh crore"
Dim Powers As String
Public Sub WriteAmountInWords(ByVal Amt As Variant, _
ByRef TargetCell As Range, _
ByVal Indian As Boolean)
Const WithCurr As Boolean = False
Const NoDecs As Boolean = False
Const SpellDecs As Boolean = False
Const CaseType As Long = NctProper
TargetCell.Value = SpellAmount(Amt, Indian, WithCurr, NoDecs, SpellDecs, CaseType)
End Sub
Private Function SpellAmount(ByVal Amt As Variant, _
ByVal Indian As Boolean, _
ByVal WithCurr As Boolean, _
ByVal NoDecs As Boolean, _
ByVal SpellDecs As Boolean, _
ByVal CaseType As Long) As String
' return the amount Amt in words
' include the currency, if WithCurr = True
' True to suppress zero fractions in integers,
' also ignore fractions existing in Amt
' write out fractions, if SpellDecs = True
' specify any Nct value for CaseType (Proper by default)
Dim Num As Double ' = Amt
Dim Spa As String ' result
Dim S As String ' partial result
Dim Sp() As String ' groups of numbers
Dim G As Ngp
Powers = IIf(Indian, Powers_In, Powers_En)
Num = SetGroups(Amt, Sp, Indian)
For G = NgpBn To NgpN Step -1
If Val(Sp(G)) > 0 Then
S = Spell999(Sp(G))
If G > NgpN Then
S = WithBreak(S, True) & Split(Powers)(G)
End If
Spa = WithBreak(Spa, True) & S
End If
Next G
If Len(Spa) = 0 Then Spa = Split(Ones)(0)
If NoDecs Then
If WithCurr Then Call AddCurrency(Spa, Int(Num))
Else
Call AddDecimals(Spa, Sp(NgpDec), SpellDecs, WithCurr, Num)
End If
SpellAmount = WriteProper(Spa, CaseType)
End Function
Private Function Spell999(G3 As String) As String
' return the amount in words of a G3 of 3 numbers
Dim Sp As String ' result
Dim S As String ' partial result
Dim n(1 To 3) As Integer ' value of each character
Dim IsTeen As Boolean
Dim i As Long
For i = 1 To 3
n(i) = Val(Mid(Right("000" & G3, 3), i, 1))
Next i
If n(1) > 0 Then Sp = WithBreak((Split(Ones)(n(1)))) & _
Split(Powers)(NgpN)
If n(2) = 1 And n(3) > 0 Then
IsTeen = True
ElseIf n(2) Then
Sp = WithBreak(Sp) & Split(Tens)(n(2))
End If
If n(3) Then
If IsTeen Then
S = Split(Teens)(n(3))
If n(3) > 2 Then
S = WithBreak(S) & Split(Teens)(0)
End If
Else
S = Split(Ones)(n(3))
End If
Sp = WithBreak(Sp) & S
End If
Spell999 = Sp
End Function
Private Sub AddDecimals(ByRef Spa As String, _
ByVal Decs As String, _
ByVal SpellDecs As Boolean, _
ByVal WithCurr As Boolean, _
ByVal Num As Double)
Dim S As String
If WithCurr And SpellDecs Then Call AddCurrency(S, Int(Num))
S = WithBreak(S, True) & Split(SpellCurr, ",") _
(NcrOnly - CBool(Val(Decs)))
If SpellDecs Then
If Val(Decs) Then
S = WithBreak(S, True) & Spell999(Decs)
If WithCurr Then
Call AddCurrency(S, Val(Decs), True)
Else
S = WithBreak(S, True) & Split(Powers)(0) & "th"
End If
End If
Else
S = WithBreak(S, True) & Decs & "/100"
If WithCurr Then Call AddCurrency(S, Num)
End If
Spa = WithBreak(Spa, True) & S
End Sub
Private Function SetGroups(ByVal Amt As Variant, _
ByRef Sp() As String, _
ByVal Indian As Boolean) As Double
' Sp() is a return array
Dim Grps() As Variant
Dim A As String
Dim n As Integer
Dim i As Integer
If Indian Then
Grps = Array(5, 2, 2, 3) ' from left to right
Else
Grps = Array(3, 3, 3, 3)
End If
ReDim Sp(NgpDec)
A = Format(Unformat(Amt), String(12, "0") & ".00")
For i = NgpN To (NgpDec - 1)
Sp(NgpDec - i - 1) = Mid(A, n + 1, Grps(i))
n = n + Grps(i)
Next i
Sp(NgpDec) = Right(A, 2)
SetGroups = Val(A)
End Function
Private Function Unformat(ByVal Amt As Variant) As String
Dim Uf As String
Dim S As String
Dim i As Integer
For i = 1 To Len(Amt)
S = Mid(Amt, i, 1)
If IsNumeric(S) Or S = "." Then
Uf = Uf & S
End If
Next i
Unformat = Uf
End Function
Private Function WithBreak(ByVal S As String, _
Optional ByVal AddSpace As Boolean) _
As String
' append a conditional line break or space to S
Dim BreakChar As Integer
BreakChar = IIf(AddSpace, 32, 31)
WithBreak = S
If Len(S) > 1 Then
If Asc(Right(S, 1)) <> BreakChar Then
WithBreak = S + Chr(BreakChar)
End If
End If
End Function
Private Function WriteProper(ByVal S As String, _
ByVal CaseType As Nct) As String
Dim Wp As String
Dim Sp() As String
Dim n As Long
If Len(S) Then
Wp = LCase(S)
Select Case CaseType
Case NctFirst
Wp = UCase(Left(S, 1)) & Mid(S, 2)
Case NctProper
Sp = Split(Wp)
For n = LBound(Sp) To UBound(Sp)
Sp(n) = UCase(Left(Sp(n), 1)) & Mid(Sp(n), 2)
Next n
Wp = Join(Sp)
Case NctUpper
Wp = UCase(S)
End Select
End If
WriteProper = Wp
End Function
Private Sub AddCurrency(ByRef Spa As String, _
ByVal Num As Double, _
Optional IsFraction As Boolean)
Dim S As String
Dim i As Ncr
i = IIf(IsFraction, NcrFraction, NcrCurr)
S = Split(SpellCurr, ",")(i) & IIf(Num = 1, "", "s")
Spa = WithBreak(Spa, True) & S
End Sub
Look for this line of code Const SpellCurr As String = "dollar,only,and,cent". Change the dollars to the name of your currency. Same for the "cents". However, by default the words will be written without naming the currency. You have to enable that by changing Const WithCurr As Boolean = False to True.
This setting excludes decimals from the written amount. Const NoDecs As Boolean = False. You can change it to True. Once it is True you can specify how to write the decimals, in words or numbers. Const SpellDecs As Boolean = False The default is False, meaning written as numbers, like 00/100.
The last constant in the WriteAmountInWords procedure determines capitalisation of the spelled out amount. Const CaseType As Long = NctProper. To set this constant, use one of the enumerations at the top of the code (here repeated).
Enum Nct ' CaseType
NctLower ' = all lower case
NctFirst ' = Only first character in upper case
NctProper ' = Each word's first character capitalised (Default)
NctUpper ' = all caps
End Enum
Note that the capitalisation of the enuration names will adapt itself to your preference. Once you capitalise a name differently, VBA will remember and follow your guidance. Type responsibly.
I have updated the module for bangla. I have done it for my personal need. Now you can use too. Below is the download link. Right click and select save as.
https://github.com/masudpce/number_to_bangla_word/raw/main/number_to_bangla%20_word_any_font.bas

IF two cells have at least one common word, true, else false: Excel formula

I have have two columns populated with text. I want to compare row-wise for any identical words between the two cells. How can this be accomplished with an Excel formula or vba function?
Best regards,
Try the following UDF():
Public Function Kompare(s1 As String, s2 As String) As Boolean
ary = Split(s1, " ")
bry = Split(s2, " ")
Kompare = False
For Each a In ary
For Each b In bry
If a = b Then
Kompare = True
Exit Function
End If
Next b
Next a
End Function
A third column would be needed. IE:
A..........B..........C
Text,1,another...Text,2,another......'=CommonWords(A1,B1,",") (Result another,Text)
In order to be able to use the UDF paste the following:
Function CommonWords(Text1 As Variant, Text2 As Variant, Character As Variant)
Dim ArrayText1 As Variant: ArrayText1 = Split(Text1, Character)
Dim ItemArrayText1 As Variant
Dim ArrayText2 As Variant: ArrayText2 = Split(Text2, Character)
Dim ItemArrayText2 As Variant
Dim SummaryCommonWords As Variant
For Each ItemArrayText1 In ArrayText1
If InStr(Text2, ItemArrayText1) > 0 And InStr(SummaryCommonWords, ItemArrayText1) = 0 Then SummaryCommonWords = IIf(SummaryCommonWords = "", ItemArrayText1, ItemArrayText1 & Character & SummaryCommonWords)
Next ItemArrayText1
For Each ItemArrayText2 In ArrayText2
If InStr(Text1, ItemArrayText2) > 0 And InStr(SummaryCommonWords, ItemArrayText2) = 0 Then SummaryCommonWords = IIf(SummaryCommonWords = "", ItemArrayText2, ItemArrayText2 & Character & SummaryCommonWords)
Next ItemArrayText2
CommonWords = IIf(CStr(SummaryCommonWords) <> "", SummaryCommonWords, "No common words!")
End Function
As an OT:
Wouldn't be better to know which words are repeated to analyze instead of a true, false statement?
You would need to work it to ignore spaces in the words, caps if needed.

remove unwanted characters and evaluate remaining in excel

I want to be able to evaluate a formula in a cell (in excel) that has descriptions inbetween the values.
For example, I'll have 3hrs*2ppl + 4 in the cell, and I'll want to evaluate it in another cell to return the numerical answer (eg. 10) in this case.
Can anyone help me?
Ps. I don't know much about Visual Basic but can create the macro if the code is already written for me.
Try this vba function. It will only work for the four basic operations.
Function sEvalFormula(sInput As String) As String
Dim lLoop As Long, sSpecialChars As String, sTemp As String
sSpecialChars = "0123456789-+*/()=."
For lLoop = 1 To Len(sInput)
If InStr(sSpecialChars, Mid(sInput, lLoop, 1)) > 0 Then sTemp = sTemp & Mid(sInput, lLoop, 1)
Next
sEvalFormula = Evaluate(sTemp)
End Function
First, enter the following UDF in a standard module:
Public Function evall(s As String) As Variant
l = Len(s)
s2 = ""
For i = 1 To l
ch = Mid(s, i, 1)
If ch Like "[a-z]" Then
Else
s2 = s2 & ch
End If
Next i
evall = Evaluate(s2)
End Function
So if A1 contains:
3hrs*2ppl + 4
then
=evall(A1)
will display 10
This UDF discards lower case letters and evaluates the remaining expression.
Another way to do this is by using regular expressions (Regex). See this link for more information on what the patterns mean.
Also, make sure you add ”Microsoft VBScript Regular Expressions 5.5″ as reference to your VBA module (see this link for how to do that).
I'm passing in a cell address, stripping out any letters, and then evaluating the function.
Function RegExpReplace(cell As Range, _
Optional ByVal IsGlobal As Boolean = True, _
Optional ByVal IsCaseSensitive As Boolean = True) As Variant
'Declaring the object
Dim objRegExp As Object
'Initializing an Instance
Set objRegExp = CreateObject("vbscript.regexp")
'Setting the Properties
objRegExp.Global = IsGlobal
objRegExp.pattern = "[a-zA-Z]"
objRegExp.IgnoreCase = Not IsCaseSensitive
'Execute the Replace Method
RegExpReplace = Evaluate(objRegExp.Replace(cell.Text, ""))
End Function
In Cell B1 enter this formula: =RegExpReplace(A1)
Results:

Resources