How to pass operators through a UDF

MrKowz

Well-known Member
Joined
Jun 30, 2008
Messages
6,653
Office Version
  1. 365
  2. 2016
Platform
  1. Windows
I am looking to expand my knowledge of UDFs, and am wanting to know how to write basic conditional statements as an argument in the UDF. Something similar to how SUMIF/COUNTIF works, where I can select a range, and then say it is ">10", "<>Joe", "="&A1, etc.

Thanks in advance!
 
Or:
Code:
operatortest = r.worksheet.Evaluate(r.Address & sCond)
 
Upvote 0

Excel Facts

Copy PDF to Excel
Select data in PDF. Paste to Microsoft Word. Copy from Word and paste to Excel.
These are working great for numerical values, but it is failing for when it has to do a string comparison.

Eg:

<b>Excel 2003</b><table cellpadding="2.5px" rules="all" style=";background-color: #FFFFFF;border: 1px solid;border-collapse: collapse; border-color: #A6AAB6"><colgroup><col width="25px" style="background-color: #E0E0F0" /><col /><col /><col /></colgroup><thead><tr style=" background-color: #E0E0F0;text-align: center;color: #161120"><th></th><th>A</th><th>B</th><th>C</th></tr></thead><tbody><tr ><td style="color: #161120;text-align: center;">1</td><td style=";">Value</td><td style=";">Condition Value</td><td style=";">Formula Return</td></tr><tr ><td style="color: #161120;text-align: center;">2</td><td style="text-align: right;;">1</td><td style="text-align: right;;">1</td><td style="text-align: right;;">TRUE</td></tr><tr ><td style="color: #161120;text-align: center;">3</td><td style="text-align: right;;">5</td><td style="text-align: right;;">3</td><td style="text-align: right;;">TRUE</td></tr><tr ><td style="color: #161120;text-align: center;">4</td><td style="text-align: right;;">10</td><td style="text-align: right;;">10</td><td style="text-align: right;;">FALSE</td></tr><tr ><td style="color: #161120;text-align: center;">5</td><td style=";">joe</td><td style="text-align: right;;">1</td><td style="text-align: right;;">FALSE</td></tr><tr ><td style="color: #161120;text-align: center;">6</td><td style=";">joe</td><td style=";">joe</td><td style="text-align: right;;">#NAME?</td></tr></tbody></table><table cellpadding="2.5px" rules="all" style=";background-color: #FFFFFF;border: 1px solid;border-collapse: collapse; border-color: #A6AAB6"><thead><tr style="background-color: #E0E0F0;text-align: center;color: #161120"><th><b>Sheet2</b></th></tr></td></thead></table><br /><br /><table width="85%" cellpadding="2.5px" rules="all" style=";border: 2px solid black;border-collapse:collapse;padding: 0.4em;background-color: #FFFFFF" ><tr><td style="padding:6px" ><b>Worksheet Formulas</b><table cellpadding="2.5px" width="100%" rules="all" style="border: 1px solid;text-align:center;background-color: #FFFFFF;border-collapse: collapse; border-color: #A6AAB6"><thead><tr style=" background-color: #E0E0F0;color: #161120"><th width="10px">Cell</th><th style="text-align:left;padding-left:5px;">Formula</th></tr></thead><tbody><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C2</th><td style="text-align:left">=operatortest(<font color="Blue">A2,"="&B2</font>)</td></tr><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C3</th><td style="text-align:left">=operatortest(<font color="Blue">A3,">"&B3</font>)</td></tr><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C4</th><td style="text-align:left">=operatortest(<font color="Blue">A4,"<>"&B4</font>)</td></tr><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C5</th><td style="text-align:left">=operatortest(<font color="Blue">A5,"="&B5</font>)</td></tr><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C6</th><td style="text-align:left">=operatortest(<font color="Blue">A6,"="&B6</font>)</td></tr></tbody></table></td></tr></table><br />

Code using to test:

Code:
Public Function operatortest(rng As Range, teststr As String)
    operatortest = rng.Worksheet.Evaluate(rng.Address & teststr)
End Function
 
Upvote 0
This is getting messy. You need quotes around an literal string so it isn't evaluated as a defined name.
Code:
      ---A---- ---B---- --C-- --D-- --E-- --F-- --G--
  1   Operand1 Operand2   <    <=     =    >=     >  
  2          5        1 FALSE FALSE FALSE  TRUE  TRUE
  3          1        3  TRUE  TRUE FALSE FALSE FALSE
  4         10       10 FALSE  TRUE  TRUE  TRUE FALSE
  5   joe             1 FALSE FALSE FALSE  TRUE  TRUE
  6   joe      joe      FALSE  TRUE  TRUE  TRUE FALSE
  7   bill     joe       TRUE  TRUE FALSE FALSE FALSE
Code:
Function operatortest(r As Range, sCond As String) As Variant
    Dim sOper       As String
 
    Select Case Mid(sCond, 2, 1)
        Case "=", ">"
            sOper = Left(sCond, 2)
        Case Else
            sOper = Left(sCond, 1)
    End Select
 
    sCond = Mid(sCond, Len(sOper) + 1)
    If Not IsNumeric(sCond) Then sCond = """" & sCond & """"
    operatortest = r.Worksheet.Evaluate(r.Address & sOper & sCond)
End Function
 
Last edited:
Upvote 0
If you want to write a general purpose routine on par with native Excel functions, you have to invest a fair amount of resources in the issue. Posting each and every problem you run into means you want to develop a function not by yourself but rather through the collective brainpower of this forum. {grin} While there's nothing wrong with that you might want to more clearly define your goals.

As for your specific problem with strings, you have to do a lot more than just worry about adding quotes. You have to identify a number (and a number can be one of many different entities including in exponent form or a percentage value), a boolean, a defined name or formula, and then, of course, a string.

If I were to undertake a function such as this I'd address a weakness, or at least what I perceive as a weakness, in Microsoft's approach. Build the function with the signature

ConcatenateIf(Rng-to-concatenate, Condition, optional-delim=",")

Condition is not something applied to range-to-concatenate, but a standalone condition. This will allow you to include Excel functions or any other complex constructs.

For an example of how one would use this consider an array formula type construct
=ConcatenateIf(A1:A10,"(A1:A10>1)+(B1:B10<10)"),chr(13))

If all one wants is to see if A1:A10 is a literal starting with joe, use
=ConcatenateIf(A1:A10,"A1:A10='joe*'")

So, essentially, a string will be enclosed in single quotes (and a real single quote will be escaped, say as two single quotes).

In addition to providing more flexibility, this approach alleviates the need for the function to parse the Condition string to identify numbers, booleans, names, and strings.
These are working great for numerical values, but it is failing for when it has to do a string comparison.

Eg:

<b>Excel 2003</b><table cellpadding="2.5px" rules="all" style=";background-color: #FFFFFF;border: 1px solid;border-collapse: collapse; border-color: #A6AAB6"><colgroup><col width="25px" style="background-color: #E0E0F0" /><col /><col /><col /></colgroup><thead><tr style=" background-color: #E0E0F0;text-align: center;color: #161120"><th></th><th>A</th><th>B</th><th>C</th></tr></thead><tbody><tr ><td style="color: #161120;text-align: center;">1</td><td style=";">Value</td><td style=";">Condition Value</td><td style=";">Formula Return</td></tr><tr ><td style="color: #161120;text-align: center;">2</td><td style="text-align: right;;">1</td><td style="text-align: right;;">1</td><td style="text-align: right;;">TRUE</td></tr><tr ><td style="color: #161120;text-align: center;">3</td><td style="text-align: right;;">5</td><td style="text-align: right;;">3</td><td style="text-align: right;;">TRUE</td></tr><tr ><td style="color: #161120;text-align: center;">4</td><td style="text-align: right;;">10</td><td style="text-align: right;;">10</td><td style="text-align: right;;">FALSE</td></tr><tr ><td style="color: #161120;text-align: center;">5</td><td style=";">joe</td><td style="text-align: right;;">1</td><td style="text-align: right;;">FALSE</td></tr><tr ><td style="color: #161120;text-align: center;">6</td><td style=";">joe</td><td style=";">joe</td><td style="text-align: right;;">#NAME?</td></tr></tbody></table><table cellpadding="2.5px" rules="all" style=";background-color: #FFFFFF;border: 1px solid;border-collapse: collapse; border-color: #A6AAB6"><thead><tr style="background-color: #E0E0F0;text-align: center;color: #161120"><th><b>Sheet2</b></th></tr></td></thead></table><br /><br /><table width="85%" cellpadding="2.5px" rules="all" style=";border: 2px solid black;border-collapse:collapse;padding: 0.4em;background-color: #FFFFFF" ><tr><td style="padding:6px" ><b>Worksheet Formulas</b><table cellpadding="2.5px" width="100%" rules="all" style="border: 1px solid;text-align:center;background-color: #FFFFFF;border-collapse: collapse; border-color: #A6AAB6"><thead><tr style=" background-color: #E0E0F0;color: #161120"><th width="10px">Cell</th><th style="text-align:left;padding-left:5px;">Formula</th></tr></thead><tbody><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C2</th><td style="text-align:left">=operatortest(<font color="Blue">A2,"="&B2</font>)</td></tr><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C3</th><td style="text-align:left">=operatortest(<font color="Blue">A3,">"&B3</font>)</td></tr><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C4</th><td style="text-align:left">=operatortest(<font color="Blue">A4,"<>"&B4</font>)</td></tr><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C5</th><td style="text-align:left">=operatortest(<font color="Blue">A5,"="&B5</font>)</td></tr><tr><th width="10px" style=" background-color: #E0E0F0;color: #161120">C6</th><td style="text-align:left">=operatortest(<font color="Blue">A6,"="&B6</font>)</td></tr></tbody></table></td></tr></table><br />

Code using to test:

Code:
Public Function operatortest(rng As Range, teststr As String)
    operatortest = rng.Worksheet.Evaluate(rng.Address & teststr)
End Function
 
Upvote 0
Point well taken, Tusharm. Thanks! :)
 
Upvote 0
If you let Excel build the condition array (here numeric rather than Boolean), we're kind of back to where we started:

=CatIf(A1:A10, (A1:A10>1)+(B1:B10<10), CHAR(10))
 
Upvote 0
If you let Excel build the condition array (here numeric rather than Boolean), we're kind of back to where we started:

=CatIf(A1:A10, (A1:A10>1)+(B1:B10<10), CHAR(10))

Aye, but it seems that where we started would be the best solution...

Going forward, Here is what I have come up with so far, but I get #Value! when I try it. I am sure it has to do with my usage of the Evaluate, as I'm not too familiar with how to test each element properly. Any advice is appreciated!

Code:
Public Function CatIf(CatRange As Range, CatCond As String, Optional delimiter As String = "") As String
Dim rng As Range
 
If Len(delimiter) = 0 Then
    delimiter = ""
End If
For Each rng In CatRange
    If rng.Value <> "" And rng.Evaluate(CatCond) Then
        CatIf = CatIf & rng.Value & delimiter
    End If
Next rng
If Len(CatIf) > 0 Then
    CatIf = Left(CatIf, Len(CatIf) - Len(delimiter))
Else
    CatIf = ""
End If
 
End Function

And testing it in this manner:

Excel 2003<TABLE style="BORDER-RIGHT: #a6aab6 1px solid; BORDER-TOP: #a6aab6 1px solid; BORDER-LEFT: #a6aab6 1px solid; BORDER-BOTTOM: #a6aab6 1px solid; BORDER-COLLAPSE: collapse; BACKGROUND-COLOR: #ffffff" cellPadding=2 rules=all><COLGROUP><COL style="BACKGROUND-COLOR: #e0e0f0" width=25><COL><COL><COL></COLGROUP><THEAD><TR style="COLOR: #161120; BACKGROUND-COLOR: #e0e0f0; TEXT-ALIGN: center"><TH></TH><TH>A</TH><TH>B</TH><TH>C</TH></TR></THEAD><TBODY><TR><TD style="COLOR: #161120; TEXT-ALIGN: center">1</TD><TD>Jane</TD><TD style="TEXT-ALIGN: right">1</TD><TD style="TEXT-ALIGN: right">#VALUE!</TD></TR><TR><TD style="COLOR: #161120; TEXT-ALIGN: center">2</TD><TD>Joe</TD><TD style="TEXT-ALIGN: right">2</TD><TD style="TEXT-ALIGN: right"></TD></TR><TR><TD style="COLOR: #161120; TEXT-ALIGN: center">3</TD><TD>Joe</TD><TD style="TEXT-ALIGN: right">3</TD><TD style="TEXT-ALIGN: right"></TD></TR><TR><TD style="COLOR: #161120; TEXT-ALIGN: center">4</TD><TD>Joe</TD><TD style="TEXT-ALIGN: right">4</TD><TD style="TEXT-ALIGN: right"></TD></TR><TR><TD style="COLOR: #161120; TEXT-ALIGN: center">5</TD><TD>Jane</TD><TD style="TEXT-ALIGN: right">5</TD><TD style="TEXT-ALIGN: right"></TD></TR><TR><TD style="COLOR: #161120; TEXT-ALIGN: center">6</TD><TD>Joe</TD><TD style="TEXT-ALIGN: right">6</TD><TD style="TEXT-ALIGN: right"></TD></TR></TBODY></TABLE><TABLE style="BORDER-RIGHT: #a6aab6 1px solid; BORDER-TOP: #a6aab6 1px solid; BORDER-LEFT: #a6aab6 1px solid; BORDER-BOTTOM: #a6aab6 1px solid; BORDER-COLLAPSE: collapse; BACKGROUND-COLOR: #ffffff" cellPadding=2 rules=all><THEAD><TR style="COLOR: #161120; BACKGROUND-COLOR: #e0e0f0; TEXT-ALIGN: center"><TH>Sheet1</TH></TR></TD></THEAD><TBODY></TBODY></TABLE>

<TABLE style="BORDER-RIGHT: black 2px solid; PADDING-RIGHT: 0.4em; BORDER-TOP: black 2px solid; PADDING-LEFT: 0.4em; PADDING-BOTTOM: 0.4em; BORDER-LEFT: black 2px solid; PADDING-TOP: 0.4em; BORDER-BOTTOM: black 2px solid; BORDER-COLLAPSE: collapse; BACKGROUND-COLOR: #ffffff" cellPadding=2 rules=all width="85%"><TBODY><TR><TD style="PADDING-RIGHT: 6px; PADDING-LEFT: 6px; PADDING-BOTTOM: 6px; PADDING-TOP: 6px">Worksheet Formulas<TABLE style="BORDER-RIGHT: #a6aab6 1px solid; BORDER-TOP: #a6aab6 1px solid; BORDER-LEFT: #a6aab6 1px solid; BORDER-BOTTOM: #a6aab6 1px solid; BORDER-COLLAPSE: collapse; BACKGROUND-COLOR: #ffffff; TEXT-ALIGN: center" cellPadding=2 rules=all width="100%"><THEAD><TR style="COLOR: #161120; BACKGROUND-COLOR: #e0e0f0"><TH width=10>Cell</TH><TH style="PADDING-LEFT: 5px; TEXT-ALIGN: left">Formula</TH></TR></THEAD><TBODY><TR><TH style="COLOR: #161120; BACKGROUND-COLOR: #e0e0f0" width=10>C1</TH><TD style="TEXT-ALIGN: left">=catif(B1:B6,"A1:A6='joe'",", ")</TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
 
Last edited:
Upvote 0
You're trying to implement TM's suggestion, not mine.
 
Upvote 0
Something like this?
Rich (BB code):

' ZVI:2011-03-09 http://www.mrexcel.com/forum/showthread.php?t=534231
' Function to return concatenated values of RngToConcat for which Condition happens
' Optional Delim argument (comma) is the delimiter of the resulting list
' Note1:   It's faster if performed as array formula via Ctrl-Shift-Enter
' Note2:   Decimal separator of numbers is the dot char for all localizations
' Example: =ConcatIf(A1:A10, (B1:B10>1)*(C1:C10<10), CHAR(10))
Function ConcatIf(RngToConcat As Range, Condition, Optional Delim$ = ",") As String
  Dim vt As VbVarType, a, b, i&, r&, rs&, c&, cs&, s$, x
  a = RngToConcat.Value
  If Not IsArray(a) Then
    ' There is only single cell in RngToConcat - process it and exit
    If Condition Then ConcatIf = a: Exit Function
  End If
  b = Condition
  If Not IsArray(b) Then
    ' Ctrl-Shift-Enter was not performed - do evaluation of Condition formula(s)
    With Application.ThisCell
      b = Split(.Formula, ",")
      s = b(1)
      If UBound(b) = 1 Then s = Left$(s, Len(s) - 1)
      b = .Parent.Evaluate(s)
      s = ""
    End With
  End If
  rs = UBound(b, 1)
  cs = UBound(b, 2)
  For r = 1 To rs
    For c = 1 To cs
      x = b(r, c)
      If VarType(x) <> vbError Then
        If x Then
          x = a(r, c)
          vt = VarType(x)
          If vt <> vbError Then
            If Len(x) Then
              ' Provide dot char as the decimal separator for all localizations
              If IsNumeric(x) And vt <> vbString Then x = Trim(Str(x))
              ' Concatenate result
              s = s & x & Delim
            End If
          End If
        End If
      End If
    Next
  Next
  ' Set result
  i = Len(s)
  If i > 1 Then ConcatIf = Left$(s, i - 1)
End Function

Can be performed as array formula (it's faster) by CSE as well.
Speed of concatenating is more improvable if required by complicating of code.
 
Upvote 0

Forum statistics

Threads
1,215,694
Messages
6,126,254
Members
449,305
Latest member
Dalyb2

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top