Multiple Excel SEARCH function criteria - excel-formula

I want to be able to return the position number of the text in a cell as you do with SEARCH function but is this possible with multiple criteria?
e.g. Text in cell is "Iphone 7 Sep 18 $20"
=SEARCH("Sep",F10,1)
I am looking for "Sep" and SEARCH has returned position 10 - ok
But what if that cell could be any of the 12 months? I want the position number returned if the cell has any of the 12 months. Is this possible using SEARCH or some other function?

Try the following User Defined Function:
Public Function Msearch(llist As Variant, s As String) As Long
Dim i As Long, sTemp As String
If TypeName(llist) = "Range" Then
sTemp = llist.Value
Else
sTemp = llist
End If
If InStr(sTemp, ",") = 0 Then
Msearch = InStr(s, llist)
Exit Function
End If
Msearch = 0
arr = Split(sTemp, ",")
For Each a In arr
i = InStr(s, a)
If i > 0 Then
Msearch = i
Exit Function
End If
Next a
End Function
It can be used like SEARCH(), except the first argument can be a comma-separated list:

Related

Nesting ParamArrays when declaring Excel VBA functions like SUMIFS?

Consider the following example: Lets say you want to make a function "JoinIfs" that works just like SUMIFS except instead of adding the values in the SumRange, it concatenates the values in "JoinRange". Is there a way to nest the ParamArray as it seems to be done in SUMIFS?
SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...)
I imagine the declaration should look something like this:
Function JoinIfs(JoinRange As Variant, _
Delim As String, _
IncludeNull As Boolean, _
ParamArray CritArray(CriteriaRange As Variant, Criteria As Variant)) As String
But nothing I try seems to compile and there might not be a way to nest ParamArrays. But the existence of functions like SUMIFS and COUNTIFS seems to suggest there might be a way to nest the ParamArrays.
This question duplicates AlexR's question Excel UDF with ParamArray constraint like SUMIFS. But that was posted a few years ago with no response so either the question didn't get enough attention or it was misunderstood.
Edit for clarification: This question is specifically about nesting ParamArrays. I'm not trying to find alternative methods of achieving the outcome of the example above. Imagine nesting ParamArrays on a completely different fictional function like "AverageIfs"
As per the documentation for the Function statement and Sub statement, a Function or Sub can only contain 1 ParamArray, and it must be the last argument.
However, you can pass an Array as an Argument to a ParamArray. Furthermore, you can then check how many elements are in the ParamArray, and throw an error if it isn't an even number. For example, this demonstration takes a list of Arrays, and which element in that array to take, and outputs another array with the results:
Sub DemonstrateParamArray()
Dim TestArray As Variant
TestArray = HasParamArray(Array("First", "Second"), 0)
MsgBox TestArray(0)
Dim AnotherArray As Variant
AnotherArray = Array("Hello", "World")
TestArray = HasParamArray(AnotherArray, 0, AnotherArray, 1)
MsgBox Join(TestArray, " ")
End Sub
Function HasParamArray(ParamArray ArgList() As Variant) As Variant
Dim ArgumentCount As Long, WhichPair As Long, Output() As Variant, WhatElement As Long
ArgumentCount = 1 + UBound(ArgList) - LBound(ArgList)
'Only allow Even Numbers!
If ArgumentCount Mod 2 = 1 Then
Err.Raise 450 '"Wrong number of arguments or invalid property assignment"
Exit Function
End If
ReDim Output(0 To Int(ArgumentCount / 1) - 1)
For WhichPair = LBound(ArgList) To ArgumentCount + LBound(ArgList) - 1 Step 2
WhatElement = ArgumentCount(WhichPair + 1)
Output(Int(WhichPair / 2)) = ArgumentCount(WhichPair)(WhatElement)
Next WhichPair
HasParameterArray = Output
End Function
(A list of built-in error codes for Err.Raise can be found here)
It seems like nesting a ParamArray is not possible.
I was hoping to get a function that looks like Excel's built in functions.
SUMIFS, for example seems to group pairs of parameters in a very neat way.
Based on the inputs of some users I made the following Function which seems to work quite well.
Function SJoinIfs(JoinRange As Variant, Sep As String, IncludeNull As Boolean, ParamArray CritArray() As Variant) As Variant
'Concatenates text based on multple criteria similar to SUMIFS.
'Sizes of ranges CritArray (0, 2, 4 ...) must match size of range JoinRange. CritArray must have an even amount of elements
'Elements of CritArray (1, 3, 5 ...) must be single values
Set JoinList = CreateObject("System.Collections.Arraylist")
'Set FinalList = CreateObject("System.Collections.Arraylist")
For Each DataPoint In JoinRange
JoinList.Add (CStr(DataPoint))
Next
JoinArray = JoinList.ToArray
CriteriaCount = UBound(CritArray) + 1
If CriteriaCount Mod 2 = 0 Then
CriteriaSetCount = Int(CriteriaCount / 2)
Set CriteriaLists = CreateObject("System.Collections.Arraylist")
Set CriteriaList = CreateObject("System.Collections.Arraylist")
Set MatchList = CreateObject("System.Collections.Arraylist")
For a = 0 To CriteriaSetCount - 1
CriteriaList.Clear
For Each CriteriaTest In CritArray(2 * a)
CriteriaList.Add (CStr(CriteriaTest))
Next
If CriteriaList.count <> JoinList.count Then 'Ranges are different sizes
SJoinIfs = CVErr(xlErrRef)
Exit Function
End If
MatchList.Add (CStr(CritArray((2 * a) + 1)))
CriteriaLists.Add (CriteriaList.ToArray)
Next
JoinList.Clear
For a = 0 To UBound(JoinArray)
AllMatch = True
For b = 0 To MatchList.count - 1
AllMatch = (MatchList(b) = CriteriaLists(b)(a)) And AllMatch
Next
If AllMatch Then JoinList.Add (JoinArray(a))
Next
SJoinIfs = SJoin(Sep, IncludeNull, JoinList)
Else 'Criteria Array Size is not even
SJoinIfs = CVErr(xlErrRef)
Exit Function
End If
End Function
This function makes use of another function SJoin() which I adapted some time ago based on the answer provided by Lun in his answer to How to replicate Excel's TEXTJOIN function in VBA UDF that allows array inputs.
I have adapted this Function to include the use of Numericals, VBA Arrays and Arraylists as well.
On Error Resume Next
'Sep is the separator, set to "" if you don't want any separator. Separator must be string or single cell, not cell range
'TxtRng is the content you want to join. TxtRng can be string, single cell, cell range or array returned from an array function. Empty content will be ignored
Dim OutStr As String 'the output string
Dim i, j, k, l As Integer 'counters
Dim FinArr(), element As Variant 'the final array and a temporary element when transfering between the two arrays
'Go through each item of TxtRng(), depending on the item type, transform and put it into FinArray()
i = 0 'the counter for TxtRng
j = 0 'the counter for FinArr
k = 0: l = 0 'the counters for the case of array from Excel array formula
Do While i < UBound(TxtRng) + 1
If TypeName(TxtRng(i)) = "String" Then 'specified string like "t"
ReDim Preserve FinArr(0 To j)
FinArr(j) = "blah"
FinArr(j) = TxtRng(i)
j = j + 1
ElseIf TypeName(TxtRng(i)) = "Range" Then 'single cell or range of cell like A1, A1:A2
For Each element In TxtRng(i)
ReDim Preserve FinArr(0 To j)
FinArr(j) = element
j = j + 1
Next
ElseIf TypeName(TxtRng(i)) = "Variant()" Then 'array returned from an Excel array formula
For k = LBound(TxtRng(0), 1) To UBound(TxtRng(0), 1)
For l = LBound(TxtRng(0), 2) To UBound(TxtRng(0), 2)
ReDim Preserve FinArr(0 To j)
FinArr(j) = TxtRng(0)(k, l)
j = j + 1
Next
Next
Else
TJoin = CVErr(xlErrValue)
Exit Function
End If
i = i + 1
Loop
'Put each element of the new array into the join string
For i = LBound(FinArr) To UBound(FinArr)
If FinArr(i) <> "" Then 'Remove this line if you want to include empty strings
OutStr = OutStr & FinArr(i) & Sep
End If
Next
TJoin = Left(OutStr, Len(OutStr) - Len(Sep)) 'remove the ending separator
End Function
Thanks to all who contributed to this question.

Extract 5 Digit Number From a String with Excel-VBA

I'm facing a problem as a non dev. I have a column in Excel that contains info as such:
46843 xxxx xxx x
xxxx 65483 xxxx
xxxx xxx 65432 xxxxx 4 xx
"x" being normal caracters.
What I want is to be able to extract only the numbers of five digits only.
I started something like this but struggle to put a loop so that it scans all the string:
Function test()
val_in = "rue 4 qsdqsd CURIE 38320 EYBENS"
Filte = Left(val_in, 5)
If IsNumeric(Filte) Then
test = Left(val_in, 5)
Else
sp1 = InStr(1, val_in, " ")
sp2 = InStr(sp1 + 1, val_in, " ")
spt = sp2 + sp1
If spt > 5 Then
extr = Mid(val_in, spt, 5)
End If
End If
End Function
How could I turn the part after "Else" into a loop so that it would scan every space of the string and extract only the numbers that contains 5 digits?
Using regex
Option Explicit
Public Function GetNumbers(ByVal rng As Range) As Variant
Dim arr() As String, i As Long, matches As Object, re As Object
Set re = CreateObject("VBScript.RegExp")
With re
.Global = True
.MultiLine = True
.IgnoreCase = False
.Pattern = "\b\d{5}\b"
If .test(rng.Value) Then
Set matches = .Execute(rng.Value)
ReDim arr(0 To matches.Count - 1)
For i = LBound(arr) To UBound(arr)
arr(i) = matches(i)
Next i
Else
arr(i) = rng.Value
End If
End With
GetNumbers = Join(arr, ",")
End Function
Data:
If there is more than one match a comma separated list is returned.
Sub TestMe()
Dim valIn As String
valIn = "rue 4 qsdqsd CURIE 38320 EYBENS 43443"
Dim i As Long
Dim splitted As Variant
splitted = Split(valIn)
For i = LBound(splitted) To UBound(splitted)
If IsNumeric(splitted(i)) And Len(splitted(i)) = 5 Then
Debug.Print splitted(i)
End If
Next i
End Sub
Considering that in your example you mean that the 5 digit numbers are splitted by space, the above works. It splits the string by space to an array and loops through the elements of the array. If the element is with 5 chars and is numeric, it prints it.
If the rule for the spaces is not something that one can count on, here is a different implementation:
Sub TestMe()
Dim valIn As String
valIn = "44244rue4qsdqsdCURIE383201EYBENS43443"
Dim i As Long
For i = 1 To Len(valIn) - 4
If IsNumeric(Mid(valIn, i, 5)) Then
Debug.Print Mid(valIn, i, 5)
End If
Next i
End Sub
It starts looping through the string, checking whether each 5 chars are numeric. When you have numeric 6 chars, it gives two results - 1 to 5 and 2 to 6. Thus 383201 is "translated" as the following 2:
38320
83201
If you have always space between words/numbers then this should do
Sub test()
Dim TestStr As String
Dim Temp As Variant
Dim i As Long, FoundVal As Long
TestStr = "rue 4 qsdqsd CURIE 38320 EYBENS"
Temp = Split(TestStr, " ")
For i = 0 To UBound(Temp)
If Len(Trim(Temp(i))) = 5 And IsNumeric(Temp(i)) Then
FoundVal = Temp(i)
MsgBox FoundVal
End If
Next i
End Sub
From the solution you are trying to apply (creating custom function in VBA) I understand that you actually need to use it in a formula.
To find number with five digits from cell A1 you can use the following formula without VBA:
=IF(ISERROR(FIND("0"&REPT("1",5)&"0",CONCAT(0+(ISNUMBER(-MID(" "&A1&" ",ROW(INDIRECT("1:"&LEN(A1)+2)),1))+0)))),"",MID(A1,FIND("0"&REPT("1",5)&"0",CONCAT(0+(ISNUMBER(-MID(" "&A1&" ",ROW(INDIRECT("1:"&LEN(A1)+2)),1))+0))),5))
To search for other number of digits change the three occurrences of number 5 to your desired digits count in the formula.

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.

get array position in string array when value = 1

I have a column that contain a binary string as this
11110010
i need to return position in another cell if found 1
like this
12347
i try to use index and match function but it's doesn't work problaly
Put this in a module on your worksheet:
Function GetInstances(MyString As String, FindChar As String)
Dim X As Long, MyResult As String
MyResult = ""
For X = 1 To Len(MyString)
If Mid(MyString, X, 1) = FindChar Then MyResult = MyResult & X
Next
GetInstances = MyResult
End Function
In Cell A1: 11110010
In Cell B1 I used the new formula like so: =GetInstances(A1,1)
The result it gave me was 12347
A1 contains the string to evaluate and the 1 in there is the number to find.
InStr method can shown the position of a character but index start from 1.
So, in 1234, if we find 1, it will return 1. One thing is that, it will shown for the first matches.
I tested about it as:
MsgBox InStr("1234", "1")
I give me 1 in message box. But, when I tried as follow:
MsgBox InStr("12341", "1")
It don't give two message box for position 1 and 5. It just show message box for position 1. If it is OK, try with this.
An alternative function that uses array for speed below:
Function StrOut(strIn As String)
Dim buff() As String
Dim lngCnt As Long
buff = Split(StrConv(strIn, vbUnicode), Chr$(0))
For lngCnt = 0 To UBound(buff) - 1
StrOut = StrOut & (lngCnt + 1) * buff(lngCnt)
Next
StrOut = Replace(StrOut, "0", vbNullString)
End Function
test code
Sub Test()
MsgBox StrOut("11110010")
End Sub
Tinkered with a formula approach that I intended to try with Evaluate, got as far as
=IF(MID(A1,ROW(INDIRECT("1:" & LEN(A1))),1)="1",ROW(INDIRECT("1:" & LEN(A1))),"X")
which gives
={1;2;3;4;"X";"X";7;"X"}
but not progressed to completion yet.

Pass Excel Range in VBA Function, Process as Array, and Return Result

I have an Excel worksheet with some strings in a column. Sometimes all of the entries are the same, and sometimes not:
I wrote a function to pass the range as a parameter:
=Dent_WG(A1:A6)
The VBA function should determine which case is true (all entries = "Al", or at least one entry = "Ag"), then return 0 or 12 respectively:
Function DentWG(WG_Mat As Range) As Single
Dim dat As Variant, rw As Variant, temp As Single
dat = WG_Mat
temp = 0
For rw = LBound(dat, 1) To UBound(dat, 1)
If dat(rw, 1) = "Ag" Then
temp = 12
End If
Next
If temp = 12 Then
DentWG = 12
Else
DentWG = 0
End If
End Function
However, the function always returns 0, even for the 2nd case where "Ag" occurs in the range. I'm sure I'm failing to correctly convert the range into an array or correctly apply the intended logic to that array.
From your question...
The VBA function should determine which case is true (all entries = "Al", or at least one entry = "Ag"), then return 0 or 12 respectively:
This is what you need.
Function DentWG(WG_Mat As Range) As Long
Dim ClCount As Long
ClCount = WG_Mat.Cells.Count
If Application.WorksheetFunction.CountIf(WG_Mat, "Al") = ClCount Then
DentWG = 0
ElseIf Application.WorksheetFunction.CountIf(WG_Mat, "Ag") > 0 Then
DentWG = 12
End If
End Function
The same can be achieved using a formula
=IF(COUNTIF(A1:A6,"Al")=(ROWS(A1:A6)*COLUMNS(A1:A6)),0,IF(COUNTIF(A1:A6,"Ag") > 0‌​,12,""))
In case it will always be 1 Column then you don't need *COLUMNS(A1:A6). This will do.
=IF(COUNTIF(A1:A6,"Al")=ROWS(A1:A6),0,IF(COUNTIF(A1:A6,"Ag") > 0,12,""))
ScreenShot
You don't really need a UDF for this. You could just say:
=IF(COUNTIF(A1:A6,"Ag")>=1,12,0)
This works for me:
Function DentWG(WG_Mat As Range) As Single
Dim result As Single, cl as Range
result = 0
For Each cl In WG_Mat
If cl = "Ag" Then
DentWG = 12
Exit Function
End If
Next cl
DentWG = result
End Function

Resources