I am trying to evaluate a logical expression by concatenating the operands and operator. Is there a formula to convert from text to logical in Excel 2016, similar to how VALUE() converts from text to number? I'm looking to a solution for this so I can dynamically change the condition without changing the actual Excel formula. I've searched and read through the Excel function descriptions, but nothing is jumping out as a solution.
'The operands and operator
A1: 1
A2: >
A3: 0
'Concatenation
B4: =CONCAT(A1:A3) 'This evaluates to 1>0
B5: =A1&A2&A3 'This also evaluates to 1>0
'Some checks
C4: =ISTEXT(B4) 'This evaluates to TRUE.
C5: =ISTEXT(B5) 'This also evaluates to TRUE
D4: =ISLOGICAL(B4) 'This evaluates to FALSE
D5: =ISLOGICAL(B5) 'This also evaluates to FALSE
'Vain attempts
E4: =AND(B4,TRUE) 'This ALWAYS is TRUE, even when my desired output is FALSE
E5: =OR(B5) 'This spits out a #VALUE! error
Since I'm looking for something dynamic, I want to avoid a solution such as
=IF(A2=">",A1>A3,FALSE). I also would prefer to avoid a UDF but am willing to go that route if no built in function exists to convert a logical expression in text and evaluate it as logical.
To write a UDF calling Evaluate, that can handle either a Range or Variant/String input, try the following:
Function L(exp As Variant) As Variant
Dim vExp As Variant
vExp = exp
L = Application.Evaluate(vExp)
End Function
Why does it work?
The line vExp = exp does the magic. If exp is a Range then the assigment uses the default .Value property and copies as a Variant/String (because we are not using Set). If it's a Variant/String, then it's a straight copy.
It does have the downside of using Application.Evaluate (downside as explained
here )
A version to also handle the possibility of being called from VBA, try
Function L(exp As Variant) As Variant
If TypeName(exp) = "Range" Then
L = exp.Worksheet.Evaluate(exp.Value)
Else
If IsError(Application.Caller) Then
'Calls from VBA are hanbled here.
' Note: Best to fully qualify any range references in exp
' to avoid unexpected references to the active sheet
L = ActiveSheet.Evaluate(exp)
Else
L = Application.Caller.Worksheet.Evaluate(exp)
End If
End If
End Function
Based on the comments that no built-in function takes care of this, I made a user-defined function ("UDF") called L() for logical, taking after the built-in functions N() and T() which are for numbers and text.
Function L(exp As Variant) As Variant
Dim limit As Integer
Dim counter As Integer
'Set an upper limit to how many cycles the loop may run
limit = 1000
'Assuming the possibility of nested functions, loop until the expression resolves to logical or to an error or until the loop limit has been reached
Do
exp = [exp] 'This avoids Error 2015 if exp is a Range reference. Comment if there's a better way!
exp = Application.Evaluate(exp) 'Evaluate the expression
counter = counter + 1 'Increment the loop counter
Loop Until Application.IsLogical(exp) Or Application.IsError(exp) Or counter >= limit
'Return the evaluated expression
L = exp
End Function
This function works even when I throw some silly things its way like =L(TRUE) or =L(CONCAT(A1:A2)&A3) or even =l(CONCAT(A1:A2)&"""foo"""). But it does not throw errors in cases where it probably should, such as =l(123) or =l("123"). In these cases, thank goodness for the counter limit because 123 and "123" will never evaluate to a logical or an error.
Related
I have the following strings from which I need to extract 6 digit numbers. Since these strings are generated by another software, they occur interchangeably and I cannot control it. Is there any one method that would extract both 6-digit numbers from each of these strings?
Branch '100235 to 100236 Ckt 1' specified in table 'East Contingency' for record with primary key = 21733 was not found in branch or transformer data.
Loadflow branch ID '256574_701027_1' defined in supplemental branch table was not found in branch or transformer input.
Transmission element from bus number 135415 to bus number 157062 circuit ID = 1 defined for corridor 'IESO-NYISO' was not found in input data
I don't know VBA, but I can learn it if it means I can get the 6 digit numbers using a single method.
thanks
I have been using LEFT(), RIGHT() & MID() previously, but it means manually applying the appropriate formula for individual string.
If you have Microsoft 365, you can use this formula:
=LET(arr,TEXTSPLIT(SUBSTITUTE(SUBSTITUTE(A1,"'"," "),"_"," ")," "),
FILTER(arr,ISNUMBER(-arr)*(LEN(arr)=6)))
Thanks to #TomSharpe for this shorter version, using an array constant within TEXTSPLIT to add on possible delimiters.
=LET(arr,TEXTSPLIT(A1,{"_"," ",","," ","'"}),FILTER(arr,(LEN(arr)=6)*ISNUMBER(-arr)))
Data
Output
An alternative is:
=LET(ζ,MID(A1,SEQUENCE(,LEN(A1)-5),6),ξ,MID(ζ,SEQUENCE(6),1),FILTER(ζ,MMULT(SEQUENCE(,6,,0),1-ISERR(0+ξ))=6))
A couple more suggestions (if you need them):
(1) Replacing all non-digit characters with a space then splitting the resulting string:
=LET(numbers,TEXTSPLIT(TRIM(REDUCE("",MID(A1,SEQUENCE(1,LEN(A1)),1),LAMBDA(a,c,IF(is.digit(c),a&c,a&" "))))," "),FILTER(numbers,LEN(numbers)=6))
Here I've defined a function is.digit as
=LAMBDA(c, IF(c = "", FALSE, AND(CODE(c) > 47, CODE(c) < 58)))
(tl;dr I quite like doing it this way because it hides the implementation details of is.digit and creates a rudimentary form of encapsulation)
(2) A UDF - based on the example here and called as
=RegexTest(A1)
Option Explicit
Function RegexTest(s As String) As Double()
Dim regexOne As Object
Dim theNumbers As Object
Dim Number As Object
Dim result() As Double
Dim i As Integer
Set regexOne = New RegExp
' Not sure how you would extract numbers of length 6 only, so extract all numbers...
regexOne.Pattern = "\d+"
regexOne.Global = True
regexOne.IgnoreCase = True
Set theNumbers = regexOne.Execute(s)
i = 1
For Each Number In theNumbers
'...Check the length of each number here
If Len(Number) = 6 Then
ReDim Preserve result(1 To i)
result(i) = CDbl(Number)
i = i + 1
End If
Next
RegexTest = result
End Function
Note - if you wanted to preserve leading zeroes you would need to omit the Cdbl() and return the numbers as strings. Returns an error if no 6-digit numbers are found.
I have a cell with 2 spaces as the only contents in the cell, when I use the len(trim(cell)) method, it returns a length of 0, but if I use IsEmpty(trim(cell)) it returns False. However, IsEmpty() on a blank cell returns a True. Why is this the case?
The code I used is:
MsgBox (IsEmpty(Application.WorksheetFunction.Trim(ThisWorkbook.Worksheets("Sheet1").Cells(1, 1))))
MsgBox (Len(Application.WorksheetFunction.Trim(ThisWorkbook.Worksheets("Sheet1").Cells(1, 1))))
As Microsoft points out, IsEmpty function "Returns a Boolean value indicating whether a variable has been initialized". So obviously a blank cell is considered uninitialized while a string - even an empty one - is initialized.
You could write your own function to test for empty strings:
Function IsStringEmpty(s As String) As Boolean
IsStringEmpty = Len(s) = 0
End Function
I'm trying to write the SWITCH function in VBA for my coworker who has Excel 2013. I feel that my VBA is strong enough to code this function once I set up all my function parameters. However, I'm not sure how to have an unlimited number of optional parameters in a function (similar to *args in Python). How can I set up a function so that it may have an unlimited number of optional arguments?
You need to use ParamArray, e.g.
Public Function TestSum(ParamArray a())
Dim i As Long
For i = LBound(a) To UBound(a)
TestSum = TestSum + a(i)
Next i
End Function
An interesting question, here's my attempt at replicating the Switch functionality.
You will need to use a ParamArray argument:
Optional. Used only as the last argument in arglist to indicate that the final argument is an Optional array of Variant elements. The ParamArray keyword allows you to provide an arbitrary number of arguments. It may not be used with ByVal, ByRef, or Optional. (source)
Revised, thanks to the comments with #TinMan, we no longer use a Dictionary so this will be compatible with Mac OS without further tweaks.
Function FSwitch2(ValueToMatch As Variant, ParamArray ValuesToMatchAndReturn())
' example of replicating the Switch function available in Office 365, etc.
' https://support.office.com/en-us/article/switch-function-47ab33c0-28ce-4530-8a45-d532ec4aa25e
Dim i As Integer
Dim retVal As Variant
Dim default As Variant
If (UBound(ValuesToMatchAndReturn) + 1) Mod 2 <> 0 Then
' if the array is not evenly sized, assume the last argument is the default value.
default = ValuesToMatchAndReturn(UBound(ValuesToMatchAndReturn))
Else
' Otherwise, default to #N/A error if no match.
default = CVErr(2042)
End If
For i = LBound(ValuesToMatchAndReturn) To UBound(ValuesToMatchAndReturn) Step 2
If ValueToMatch = ValuesToMatchAndReturn(i) Then
retVal = ValuesToMatchAndReturn(i + 1)
Exit For
End If
Next
FSwitch2 = IIf(IsEmpty(retVal), default, retVal)
End Function
I'm new to excel custom functions and trying to create a custom Excel function that outputs a "Y" or "N" and takes four arguments. The error I'm getting on the other end is #VALUE! with no further explanation. I tried looking at the Range object in the MSDN docs and can't understand why my function isn't reading the values.
My function takes four arguments and the datatypes in the cells are median: Double, base: Double, hours: Integer, and exemption: String (it takes a letter "E" or "N"). I get those arguments from four separate cells in my worksheet.
Function ToIncrease(median As Range, base As Range, hours As Range, exemption As Range)
ToIncrease = "N"
exempt = exemption.Value
If exempt.Equals("N") Then
baseSal = base.Value * hours.Value
medianSal = median.Value * hours.Value
If medianSal > baseSal Then
ToIncrease = "Y"
End If
End If
If exempt.Equals("E") Then
If median.Value > base.Value Then
ToIncrease = "Y"
End If
End If
End Function
I first tried exemption.Value.Equals("N") and it didn't work, and then I tried to declare the exempt variable as Dim exempt As String = exemption.Value and got an error about expecting the end of a statement.
First replace lines like:
If exempt.Equals("N") Then
with
If exempt = "N" Then
(there may be other problems as well)
It is MUCH easier to debug VBA code as a Sub rather than a Function() because the error messages are much better.
I have Excel 2007. I am trying to find the largest number in a cell that contains something like the following:
[[ E:\DATA\SQL\SY0\ , 19198 ],[ E:\ , 18872 ],[ E:\DATA\SQL\ST0\ , 26211 ],[ E:\DATA\SQL\ST1\ , 26211 ],[ E:\DATA\SQL\SD0\ , 9861 ],[ E:\DATA\SQL\SD1\ , 11220 ],[ E:\DATA\SQL\SL0\ , 3377 ],[ E:\DATA\SQL\SL1\ , 1707 ],[ E:\DATA\SQL_Support\SS0\ , 14375 ],[ E:\DATA\SQL_Support\SS1\ , 30711 ]]
I am not a coder but I can get by with some basic instructions. If there is a formula that can do this, great! If the best way to do this is some sort of backend code, just let me know. Thank you for your time.
I do have the following formula that almost gets me there:
=SUMPRODUCT(MID(0&A2,LARGE(INDEX(ISNUMBER(--MID(A2,ROW(INDIRECT("1:"&LEN($A$2))),1))*ROW(INDIRECT("1:"&LEN($A$2))),0),ROW(INDIRECT("1:"&LEN($A$2))))+1,1)*10^ROW(INDIRECT("1:"&LEN($A$2)))/10)
With a cell that contains a string like above, it will work. However, with a string that contains something like:
[[ E:\DATA\SQL\SY0\ , 19198.934678 ],[ E:\ , 18872.2567 ]]
I would end up with the value of 19198934678 as the largest value.
You can use this UDF:
Function MaxInString(rng As String) As Double
Dim splt() As String
Dim i&
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
Put this in a module attached to the workbook. NOT in the worksheet or ThisWorkbook code.
Then you can call it like any other formula:
=MaxInString(A1)
If there is always a space before and after, you can use this formula. The formula is an array formula and must be confirmed by holding down ctrl + shift while hitting enter
With your string in A1:
=MAX(IFERROR(--TRIM(MID(SUBSTITUTE(A1," ",REPT(" ",99)),IF(seq=1,1,(seq-1)*99),99)),0))
seq is a defined name that refers to:
=ROW(INDEX(Sheet1!$1:$65536,1,1):INDEX(Sheet1!$1:$65536,255,1))
If a VBA UDF is preferable, I suggest the following. The Regex will match anything that might be a number. The number is expected to be in the format of iiii.dddd The integer part and the decimal point are both optional.
Option Explicit
Function LargestNumberFromString(S As String) As Double
Dim RE As Object, MC As Object, M As Object
Dim D As Double
Set RE = CreateObject("vbscript.regexp")
With RE
.Global = True
.Pattern = "\b[0-9]*\.?[0-9]+\b"
If RE.test(S) = True Then
For Each M In MC
D = IIf(D > CDbl(M), D, CDbl(M))
Next M
End If
End With
End Function
This is going to be a rather complex answer for someone with no programming background, so be prepared to spend a lot of time covering and researching this topic if you really wish to achieve a function that finds the largest number in a string in excel.
The solution, requires the use of VBA and Regular expressions
VBA is used in excel when there is a need for more complex functionality that just can't be achieved with the use of built in spreadsheet functions.
Regular expressions are a language used to tell programs how to extract useful information from texts, in this case we can extract all the numbers in your text. with the following regular expression.
(\d+.?\d*)/g
Which roughly means: Match one or more digits with an optional period and subsequent optional digits.
The program that will interpret this will do the following: Look for digits, if you see one, then that's a match, grab all contiguous digits and add them to the match. Once you find a character that is not a digit, start looking for new matches. if at any point you find a dot, add it to the match, but just once, and keep on looking for digits. Rinse and repeat until the end of the text.
You can test it here. In this case, the regex matches 19 numbers.
http://www.regextester.com/
Once you have a collection with the 19 matches (See link to regular expressions), all you would need to do is to loop over each of the matches to find out which of the numbers is the highest:
for each number in matches
if number > highestNumber then
highestNumber = number
end if
next
And highestNumber will be the the result! In order to have this code run in a simple custom function, you can follow this microsoft tutorial ( https://support.office.com/en-us/article/Create-Custom-Functions-in-Excel-2007-2f06c10b-3622-40d6-a1b2-b6748ae8231f?ui=en-US&rs=en-US&ad=US&fromAR=1 )
Where c is your string to find max from
Dim qwe() As String
qwe = Split(c, ", ")
maxed = 0
For x = LBound(qwe) To UBound(qwe)
qwe(x) = Left(qwe(x), InStr(1, qwe(x), " ", vbBinaryCompare))
On Error Resume Next
If CLng(qwe(x)) > maxed Then maxed = CLng(qwe(x))
Next x
MsgBox maxed
The error line is there to ignore when qwe(x) cannot be converted to a LONG number.
I must say this is very specific to your string format, for a more comprehensive doodad you'd want to have the split delimiter as a variable and possibly use the "IsNumeric" function to scan the entire string.