Check for consecutive characters in an excel cell - vba

If you could help me I am in need to finding out if a character of the alphabet repeats consecutively 3 or more times in a cell, eg if a cell is "aronfff" or "aaaaaron" I want it to return true otherwise to return false eg "aaron".
Function InRowChars(cell As String) As Boolean
Dim repeats As Integer, char As String, i As Integer
repeats = 0
char = "abcdefghijklmnopqrstuvwxyz"
For i = 1 To Len(cell)
If cell.Value = " " Then
repeats = chars + 1
Else
chars = 0
End If
Next i
If chars = 3 Then
InRowChars = True
Else
InRowChars = False
End If
End Function
I don't know how to get the value of the cell to be checked against the alphabet.

This can be achieved with regular expressions. I've made a function example that also accept the number of minimum characters desired:
'Add a reference to Microsoft VBScript Regular Expressions 5.5
Function ContainsConsecutiveChars(ByRef CellRef As Range, Optional ConsecutiveCount As Long = 3) As Boolean
Dim chars() As String
chars = Split("a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z", ",")
With New RegExp
.Pattern = Join(chars, "{" & ConsecutiveCount & ",}|")
ContainsConsecutiveChars = .test(CellRef.Value2)
End With
End Function

Here is a another regex solution that returns TRUE or FALSE depending on whether or not there are three or more repeating alphabetic characters:
Option Explicit
Function TripleChars(S As String) As Boolean
Dim RE As Object
Set RE = CreateObject("vbscript.regexp")
With RE
.Global = True
.Pattern = "([a-z])\1\1"
.ignorecase = True 'edit as desired
TripleChars = .test(S)
End With
End Function
And here is an explanation of the Regex Pattern:
([a-z])\1\1
([a-z])\1\1
Options: Case insensitive; ^$ don’t match at line breaks
Match the regex below and capture its match into backreference number 1 ([a-z])
Match a single character in the range between “a” and “z” [a-z]
Match the same text that was most recently matched by capturing group number 1 \1
Match the same text that was most recently matched by capturing group number 1 \1
Created with RegexBuddy

I see you already have a RegEx answer now. Just finished my version so thought I'd post it to.
#Thunderframe - I liked the optional bit, so have blatantly taken it for my version to.
Public Function RepeatingChars(Target As Range, Optional ConsecutiveCount As Long = 3) As Variant
Dim RE As Object, REMatches As Object
Set RE = CreateObject("VBScript.RegExp")
With RE
.MultiLine = False
.Global = False
.IgnoreCase = True
.Pattern = "(.)\1{" & ConsecutiveCount - 1 & ",}"
End With
Set REMatches = RE.Execute(Target.Value)
If REMatches.Count = 0 Then
RepeatingChars = CVErr(xlErrNA)
Else
RepeatingChars = REMatches(0)
End If
End Function
The function will return duplicates of any character, or #NA if no matches found.
Edit
After a quick re-read of your question you can replace the whole If...End If block with RepeatingChars = REMatches.Count <> 0 to return TRUE/FALSE. Remember to change the return type of the function to Boolean in this case.

This is what I have came up with so far:
Option Explicit
Function checkChars(inputCell As String, Optional repeat As Long = 3) As Boolean
Dim cnt As Long
Dim previous As String
Dim countResult As Long
For cnt = 1 To Len(inputCell)
If previous = Mid(inputCell, cnt, 1) Then
countResult = countResult + 1
Else
countResult = 1
End If
If countResult = (repeat) Then
checkChars = True
Exit Function
End If
previous = Mid(inputCell, cnt, 1)
Next cnt
End Function
Public Sub TestMe()
Debug.Print checkChars("lalaaa")
Debug.Print checkChars("lalaala", 2)
Debug.Print checkChars("lalaala", 1)
Debug.Print checkChars("lflalajajala", 2)
End Sub
The idea is that you can also pass the repeat number as an optional value, if it is different than 3. This is what you get as an output from the example:
True
True
True
False

Related

Data validation matching with a list with VBA

I have a certain range of cells in excel where I want to apply data validation on another data validation.
I want the user to only have 3 options for data input within range of cells:
either a number,
a range of numbers or by
choosing from a dropdown list that contains words and numbers.
I already implemented number 1 and 2 with the following function:
Function checkStr(ByVal str As String) As String
Dim objRegEx As Object, allMatches As Object
Set objRegEx = CreateObject("VBScript.RegExp")
With objRegEx
.MultiLine = False
.IgnoreCase = False
.Global = True
.Pattern = "^\d+(-\d+)?$"
End With
Set allMatches = objRegEx.Execute(str)
checkStr = (allMatches.Count > 0)
End Function
Since the above function will only allow numbers or a range of numbers to be inputted, any ideas on how to add a validation to allow values from a pre-defined list containing both words and numbers?
I suggest to change the return As Boolean and then just filter the str against an array of valid list entries.
Function checkStr(ByVal str As String) As Boolean
Dim objRegEx As Object, allMatches As Object
Set objRegEx = CreateObject("VBScript.RegExp")
With objRegEx
.MultiLine = False
.IgnoreCase = False
.Global = True
.Pattern = "^\d+(-\d+)?$"
End With
Set allMatches = objRegEx.Execute(str)
Dim ValidList As Variant
ValidList = Array("123", "456") 'your list of valid entries
'check if either str is in the ValidList OR matches the regex
If (UBound(Filter(ValidList, str)) > -1) Or (allMatches.Count > 0) Then
checkStr = True
End If
End Function
If the list of valid entries is in a range you can replace it with:
ValidList = WorksheetFunction.Transpose(Worksheets("SheetName").Range("A1:A10").Value)
The list is taking values from some range. Thus, take the range of the list and use the Application.Match() to check whether the str is there:
Public Function checkStr(str As String) As Boolean
Dim isItError As Variant
isItError = Application.Match(str, Worksheets(1).Range("A1:A5"), 0)
checkStr = Not IsError(isItError)
End Function
Application.Match() would return either error or true. Thus, your function can be fixed with Not IsError().
And if you want to compare Strings with Strings and Numbers as Numbers, try to pass the variable as Variant and let VBA decide what it is actually:
Public Function checkMe(someVar As Variant) As Boolean
Dim isItError As Variant
Dim formulaAddress As String
With Range("C1").Validation
formulaAddress = Right(.Formula1, Len(.Formula1) - 1)
End With
isItError = Application.Match(someVar, Range(formulaAddress))
checkMe = Not IsError(isItError)
End Function
If you explicitly define the variable as a numeric or string, the other option would be excluded in the Application.Match() comparison:
?Application.Match("1",Array(1,2,3))
Error 2042
?Application.Match(1,Array(1,2,3))
1
?Application.Match("1",Array("1","2","3"))
1
?Application.Match(1,Array("1","2","3"))
Error 2042

Function that returns exact string matches from multiple columns based off a word list

I was hoping to receive help editing this helpful piece of code:
Function ListSearchB(text As String, wordlist As String, Optional caseSensitive As Boolean = False)
Dim strMatches As String
Dim res As Variant
Dim arrWords() As String
arrWords = Split(wordlist)
On Error Resume Next
Err.Clear
For Each word In arrWords
If caseSensitive = False Then
res = InStr(LCase(text), LCase(word))
Else
res = InStr(text, word)
End If
If res > 0 Then
strMatches = strMatches & word
End If
Next word
If Len(strMatches) <> 0 Then
strMatches = Right(strMatches, Len(strMatches))
End If
ListSearchB = strMatches
End Function
This code works excellent for what it does currently. First it selects the text string to compare, then selects the array of comma separated words its looking to find matches to. If any of the words in the text string match to a word in the array it will return that match.
What I'm looking to add to it is to be able to select the first cell with text, then a second cell with text, and then the array itself and return any and all matches from both selected cells that match.
I tried all day trying to get it to work but I've been getting errors each time.
example would look like:
A1: apples taste better than the oranges
B1: strawberries are the best berry
C1(array): apples, strawberries, blueberries, peaches, oranges
D1(output): apples oranges strawberries
Some functions to help you:
'To check if an element is within a specific Array, Object, Range, String, etc.
Public Function isInArray(ByVal itemSearched As Variant, ByVal aArray As Variant) As Boolean
Dim item As Variant
If VarType(aArray) >= vbArray Or VarType(aArray) = vbObject Or VarType(aArray) = vbDataObject Or TypeName(aArray) = "Range" Then
For Each item In aArray
If itemSearched = item Then
isInArray = True
Exit Function
End If
Next item
isInArray = False
ElseIf VarType(aArray) = vbString Then
isInArray = InStr(1, aArray, itemSearched, vbBinaryCompare) > 0 'Comparing character by character
Else
On Error Resume Next
isInArray = Not IsError(Application.Match(itemSearched, aArray, False)) 'Slow on large arrays
Err.Clear: On Error GoTo 0
End If
End Function
'To check if a word is within a sentence-like string
Public Function isInStrArray(ByVal itemSearched As Variant, ByVal strSource As Variant) As Boolean
Dim strArr As Variant
isInStrArray = False
If VarType(itemSearched) = vbString And VarType(strSource) = vbString Then
itemSearched = Trim(itemSearched): strSource = Trim(strSource)
If Len(itemSearched) > 0 And Len(strSource) > 0 Then
strArr = Split(strSource) 'Splitting at each space
isInStrArray = isInArray(itemSearched, strArr)
Erase strArr
End If
End If
End Function

VBA starts with or ends with a special character

I want to see if a string starts with or ends with a special character
testString("#Testing") Returns: true
testString("Testing\") Returns: true
testString("#Testing)") Returns: true
testString("Tes#ting~") Returns: true
testString("Tes#ting") Returns: false
testString("Testing") Returns: false
The idea is to use a regular expression
Dim rg As Variant
Set rg = CreateObject("VBScript.RegExp")
rg.Pattern = ""
returnFunc = rg.test(paramString)
However, I am not sure how to create a regular expression to check symbols.
All alternative solutions are welcome
So if it starts or ends with anything other than [a-Z][0-9]
Function test(x)
Dim rg As Variant
Set rg = CreateObject("VBScript.RegExp")
rg.Pattern = "^([^A-Za-z0-9].*|.*[^A-Za-z0-9])$"
test = rg.test(x)
End Function
Sub hoi()
Debug.Print test("#Testing")
Debug.Print test("Testing\")
Debug.Print test("#Testing)")
Debug.Print test("Tes#ting~")
Debug.Print test("Tes#ting")
Debug.Print test("Testing")
End Sub
If you don’t need to change your definition of special characters for different languages or other reasons then you can simply checking the first and last character against a list of valid characters would work.
Public Function testString(text As String)
testString = isCharAlphaNumeric(Left(text, 1)) Or isCharAlphaNumeric(Right(text, 1))
End Function
Public Function isCharAlphaNumeric(char)
Const valid As String = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
isCharAlphaNumeric = InStr(valid, char) = 0
End Function
Public Sub test()
Debug.Print testString("#Testing") ' Returns: true
Debug.Print testString("Testing\") ' Returns: true
Debug.Print testString("#Testing)") ' Returns: true
Debug.Print testString("Tes#ting~") ' Returns: true
Debug.Print testString("Tes#ting") ' Returns: false
Debug.Print testString("Testing") ' Returns: false
End Sub
To check if string does not start and end with alphanumeric characters using the VB Like operator:
If Not "#Testing" Like "[0-9A-Za-z]*[0-9A-Za-z]" Then MsgBox True
If the string might be less than 2 characters:
If string Like "[!0-9A-Za-z]*" Or string Like "*[!0-9A-Za-z]" Then MsgBox True

Excel VBA Version number from Sequential number

All,
I have an Excel spreadsheet which gathers files from various locations which have version numbers at the end of their name.
eg
Filea_v1.1.xlsm
Filea_v1.1.1.xlsm
Filea_v9.1.xlsm
Filea_v11.1.1.xlsm
I have already stripped the details of the file and left with the version number, but I'm finding it hard to code to get the latest version. When using a simple
If LatestVersion > LatestVersion_Last
' 11.1.1 > 9.1
I get a FALSE output as it thinks the 9.1 is greater than 11.1.1 (as its held in a string). I can't convert to a number that I'm aware of as it contains more than one decimal place.
Does anyone have any suggestions?
Thanks
This function returns 1 if the version of the first input string is bigger, -1 if the second one is bigger and 0 if both are equal:
Function Compare(a As String, b As String) As Integer
Dim n As Integer
Dim aarr() As String
Dim barr() As String
Dim av As Integer
Dim bv As Integer
aarr = Split(a, ".")
barr = Split(b, ".")
n = LBound(aarr)
Do
av = -1
If UBound(aarr) > i - 1 Then av = CInt(aarr(i))
bv = -1
If UBound(barr) > i - 1 Then bv = CInt(barr(i))
If av = -1 And bv = -1 Then
Compare = 0
Exit Function
End If
If av > bv Then
Compare = 1
Exit Function
End If
If av < bv Then
Compare = -1
Exit Function
End If
i = i + 1
Loop
End Function
Public Fucntion CompareVersion( strVersion1 as String, strVersion2 as String)
strVersion1 = "Filea_v11.1.1.xlsm"
strVersion2 = "Filea_v9.1.xlsm"
strVersion1 = Replace(strVersion1, "Filea_v", "") 'remove prefix
strVersion1 = Replace(strVersion1, ".xlsm", "") ' remove suffix
strVersion1 = Replace(strVersion1, ".", "") 'remove dots
strVersion2 = Replace(strVersion1, "Filea_v", "") 'remove prefix
strVersion2 = Replace(strVersion1, ".xlsm", "") ' remove suffix
strVersion2 = Replace(strVersion1, ".", "") 'remove dots
Dim strVersionArray1 as String()
Dim strVersionArray2 as String()
strVersionArray1 = Split(strVersion1,".")
strVersionArray2 = Split(strVersion2,".")
Dim i as Integer
For i=LBound(strVersionArray1) To UBound(strVersionArray2)
If (Cint(strVersionArray1(i))>Cint(strVersionArray2(i))) Then
'strVerion1 is greater than strVersion2
GoTo EXIT_FUNC:
Else If (Cint(strVersionArray1(i))<Cint(strVersionArray2(i))) Then
'strVerion is greater than strVersion2
GoTo EXIT_FUNC:
Else If (Cint(strVersionArray1(i))=Cint(strVersionArray2(i))) Then
'we need to examine the next segment of the array
End if
Next i
EXIT_FUNC:
End Function
I think you could try something like this:
Function FctLatest(ByVal LatestVersion As String, ByVal LatestVersion_Last As String) As Boolean
Dim comparedNew, comparedOld As String
'Loop to remove the versions one by one
Do
'For the new version
comparedNew = CutString(comparedNew, LatestVersion)
'For the previous version
comparedOld = CutString(comparedOld, LatestVersion_Last)
'we eliminate the case of equal versions
If CInt(comparedNew) > CInt(comparedOld) Then
FctLatest = True
GoTo endFunction
ElseIf CInt(comparedNew) < CInt(comparedOld) Then
FctLatest = False
GoTo endFunction
End If
Loop While InStr(LatestVersion_Last, ".") <> 0 Or InStr(LatestVersion, ".") <> 0
'For the new version
comparedNew = CutString(comparedNew, LatestVersion)
'For the previous version
comparedOld = CutString(comparedOld, LatestVersion_Last)
'we eliminate the case of equal versions, and choose the first one input
If CInt(comparedNew) > CInt(comparedOld) Then
FctLatest = True
ElseIf CInt(comparedNew) < CInt(comparedOld) Then
FctLatest = False
End If
endFunction:
End Function
Private Function CutString(ByVal ReturnedString, ByRef InputString As String) As String
'For latest version
If InStr(InputString, ".") = 0 Then
ReturnedString = InputString
Else
ReturnedString = Left(InputString, InStr(InputString, ".") - 1) 'Adding the first part of the version
InputString = Right(InputString, Len(InputString) - InStr(InputString, ".")) 'Removing the first part of the version
End If
CutString = ReturnedString
End Function
It returns a boolean comparing the versions you're interested in.
Regards,
Pierre.
Edit: just added cases, as it wasn't functional in the first place.

VBA. How to find position of first digit in string

I have string "ololo123".
I need get position of first digit - 1.
How to set mask of search ?
Here is a lightweight and fast method that avoids regex/reference additions, thus helping with overhead and transportability should that be an advantage.
Public Function GetNumLoc(xValue As String) As Integer
For GetNumLoc = 1 To Len(xValue)
If Mid(xValue, GetNumLoc, 1) Like "#" Then Exit Function
Next
GetNumLoc = 0
End Function
Something like this should do the trick for you:
Public Function GetPositionOfFirstNumericCharacter(ByVal s As String) As Integer
For i = 1 To Len(s)
Dim currentCharacter As String
currentCharacter = Mid(s, i, 1)
If IsNumeric(currentCharacter) = True Then
GetPositionOfFirstNumericCharacter = i
Exit Function
End If
Next i
End Function
You can then call it like this:
Dim iPosition as Integer
iPosition = GetPositionOfFirstNumericCharacter("ololo123")
Not sure on your environment, but this worked in Excel 2010
'Added reference for Microsoft VBScript Regular Expressions 5.5
Const myString As String = "ololo123"
Dim regex As New RegExp
Dim regmatch As MatchCollection
regex.Pattern = "\d"
Set regmatch = regex.Execute(myString)
MsgBox (regmatch.Item(0).FirstIndex) ' Outputs 5
I actually have that function:
Public Function GetNumericPosition(ByVal s As String) As Integer
Dim result As Integer
Dim i As Integer
Dim ii As Integer
result = -1
ii = Len(s)
For i = 1 To ii
If IsNumeric(Mid$(s, i, 1)) Then
result = i
Exit For
End If
Next
GetNumericPosition = result
End Function
You could try regex, and then you'd have two problems. My VBAfu is not up to snuff, but I'll give it a go:
Function FirstDigit(strData As String) As Integer
Dim RE As Object REMatches As Object
Set RE = CreateObject("vbscript.regexp")
With RE
.Pattern = "[0-9]"
End With
Set REMatches = RE.Execute(strData)
FirstDigit = REMatches(0).FirstIndex
End Function
Then you just call it with FirstDigit("ololo123").
If speed is an issue, this will run a bit faster than Robs (noi Rob):
Public Sub Example()
Const myString As String = "ololo123"
Dim position As Long
position = GetFirstNumeric(myString)
If position > 0 Then
MsgBox "Found numeric at postion " & position & "."
Else
MsgBox "Numeric not found."
End If
End Sub
Public Function GetFirstNumeric(ByVal value As String) As Long
Dim i As Long
Dim bytValue() As Byte
Dim lngRtnVal As Long
bytValue = value
For i = 0 To UBound(bytValue) Step 2
Select Case bytValue(i)
Case vbKey0 To vbKey9
If bytValue(i + 1) = 0 Then
lngRtnVal = (i \ 2) + 1
Exit For
End If
End Select
Next
GetFirstNumeric = lngRtnVal
End Function
An improved version of spere's answer (can't edit his answer), which works for any pattern
Private Function GetNumLoc(textValue As String, pattern As String) As Integer
For GetNumLoc = 1 To (Len(textValue) - Len(pattern) + 1)
If Mid(textValue, GetNumLoc, Len(pattern)) Like pattern Then Exit Function
Next
GetNumLoc = 0
End Function
To get the pattern value you can use this:
Private Function GetTextByPattern(textValue As String, pattern As String) As String
Dim NumLoc As Integer
For NumLoc = 1 To (Len(textValue) - Len(pattern) + 1)
If Mid(textValue, NumLoc, Len(pattern)) Like pattern Then
GetTextByPattern = Mid(textValue, NumLoc, Len(pattern))
Exit Function
End If
Next
GetTextByPattern = ""
End Function
Example use:
dim bill as String
bill = "BILLNUMBER 2202/1132/1 PT2200136"
Debug.Print GetNumLoc(bill , "PT#######")
'Printed result:
'24
Debug.Print GetTextByPattern(bill , "PT#######")
'Printed result:
'PT2200136