I am trying to search for special characters in a string.
If the special character exists in the string then the code would return a false to the adjacent cell.
Dim arr(5)
arr(1) = "19"
arr(2) = "26"
arr(3) = "29"
arr(4) = "32"
arr(5) = "50"
'control characters check
For n = 1 To 5
For x = 1 To 41
If InStr(1, Range("b" & arr(n)), Chr(x)) = 0 Then
For y = 123 To 255
If InStr(Range("b" & arr(n)).Value, Chr(y)) > 0 Then
Range("e" & arr(n)).Value = "FALSE"
Exit For
Else
Range("e" & arr(n)).Value = "TRUE"
End If
Next y
Else
Range("e" & arr(n)).Value = "FALSE"
Exit For
End If
Next x
Next n
My problem is, just looping a few times took quite a long time, is there a faster way to loop through all the data saving more time?
An example of the string data in the cell is : TY56D-CAT131BP342AC46-eL-W-00
Try this:
Sub Test()
Dim arr(5) As String
Dim iLen As Integer, strV As String
Dim Found As Boolean: Found = False
Dim Test As Variant
arr(1) = "19"
arr(2) = "26"
arr(3) = "29"
arr(4) = "32"
arr(5) = "50"
For x = 1 To 5
iLen = Len(Range("B" & arr(x)).Value)
strV = Range("B" & arr(x)).Value
For i = 1 To iLen
Select Case Asc(Mid$(strV, i, 1))
Case 1 To 41, 123 To 255
Found = True
Exit For
End Select
Next i
If Found = False Then
Range("E" & arr(x)).Value = "TRUE"
Else
Found = False
Range("E" & arr(x)).Value = "FALSE"
End If
Next x
End Sub
Calculation time is nearly instant. What differs to your method is, that i go through every characters and then check if its allowed or not. In this case the Select Case can do this much quicker than a for loop for every unallowed char.
It looks like you're being slowed down a lot by referring to
Range("b" & arr(n)).Value
potentially up to 120+ times in the loop (which is itself nested in other loops)
What should immediately improve the speed of your macro is to transfer the Range value to a variable before this loop, for example:
dim search_string as string
search_string = Range("b" & arr(n)).Value
For y = 123 To 255
If InStr(search_string, Chr(y)) > 0 Then
Range("e" & arr(n)).Value = "FALSE"
You'd also save a bit of time converting your whole search range to an array and working on that, but that would take more work, whereas this is a quick change you can make which should drastically improve performance
Try this:
Sub CheckCharacters()
Dim cl(5) As Integer, n As Integer
cls = Array(19, 26, 29, 32, 50)
For n = 0 To 4
If IsValidString(Range("B" & cls(n))) Then
Range("B" & cls(n)).Offset(0, 3) = "TRUE"
Else
Range("B" & cls(n)).Offset(0, 3) = "FALSE"
End if
Next n
End Sub
Function IsValidString(str As String) As Boolean
Dim objRegEx As Object
Set objRegEx = CreateObject("vbscript.regexp")
objRegEx.Pattern = "^[\x2a-\x7a]+"
objRegEx.Global = True
objRegEx.IgnoreCase = True
IsValidString = objRegEx.test(str)
End Function
The important bit is:
"^[\x2a-\x7a]+"
This is a Regex that is only TRUE if the string only contains characters between ASCII values 42 and 122 (which is what you want).
Related
I have a nested for loop that first runs through 10-15k rows, compares a cell in that row to another table that is 40k+ rows, if it finds a match, it returns that match, otherwise "no record" is written in a cell. the code works fine, just investigating an alternative approach to make it run faster. currently, 13000 lines takes about 50 min to an hour to run. I've looked into arrays, but loading an array with 40k+ items seems like the wrong route to take. the report is often run bits at a time, so when it is first created it may have 2k rows, then 3k rows may be added to it later, the code below will skip over rows it has already checked and pick up where it left off. any help is appreciated
For i = 2 To lastRow
If Cells(i, 83).Value <> "" Then GoTo NextIteration:
Sheets("mft Rpt").Cells(i, 83) = "No Record"
model = Sheets("MFT RPT").Cells(i, 11).Value
trimModel = Replace(Replace(model, " ", ""), "-", "")
For j = 1 To lastCollateralRow
If trimModel = Sheets("Promosheet Table").Cells(j, 1).Value Then
Sheets("MFT RPT").Cells(i, 83) = Sheets("promosheet Table").Cells(j, 3).Value
End If
Next j
NextIteration:
Next i
This ia just a proof of concept, you will need to adjust variables and ranges to suit your needs.
Sub ProofOfConcept()
Dim rngList As Range
Dim rngMatch As Range
Dim arrList As Variant
Dim arrMatch As Variant
Set rngList = Range("A1:A50000")
arrList = Application.Transpose(rngList.Value)
Set rngMatch = Range("C1:D15000")
arrMatch = Application.Transpose(rngMatch.Value)
For a = 1 To 15000
For b = 1 To 50000
If arrMatch(1, a) = arrList(b) Then
arrMatch(2, a) = "Match found"
GoTo skip
End If
Next
skip:
Next
rngMatch = WorksheetFunction.Transpose(arrMatch)
End Sub
thanks #Michal
played with it a bit. I trimmed down the run time from almost an hour to about 7 or 8 min using this code. works beautifully!!
Dim promoList As Range
Dim rngMatch As Range
Dim arrList As Variant
Dim arrMatch As Variant
Dim z
Set promoList = Sheets("promosheet table").Range("A1:A" & lastcollateralRow)
arrList = Application.Transpose(promoList.Value)
Set rngMatch = Sheets("Mft rpt").Range("K2:K" & lastRow)
arrMatch = Application.Transpose(rngMatch.Value)
For z = LBound(arrMatch) To UBound(arrMatch)
arrMatch(z) = Replace(Replace(arrMatch(z), " ", ""), "-", "")
Next
For A = 1 To lastRow
If Cells(A + 1, 83).Value <> "" Then GoTo skip:
Sheets("mft rpt").Cells(A + 1, 83) = "No Record"
For b = 1 To lastcollateralRow + 1
If arrMatch(A) = promoList(b) Then
Sheets("mft rpt").Cells(A + 1, 83) = promoList(b, 3)
GoTo skip
End If
Next
skip:
Next
I have a column containing formulas as "strings", i.e. "=+I11+I192+I245+I280"
I need to replace the cells (I11, I192,I245andI280`) ID with the content (strings) contained in the cells themselves.
Example:
Cell X --> "=+I11+I192+I245+I280"
Cell I11 = 'A'
Cell I192 = 'B'
Cell I245 = 'C'
Cell I280 = 'D'
The formula should generate "=+A+B+C+D".
This?
="=+" & I11 &"+" & I192 &"+" & I245 & "+" & I280
Well, how about :
=I11 & I192 & I245 & I280
Or you can include spaces
=I11 & " " & I192
But straight quotes - my phone is being funny...
The formula should generate --> "=+A+B+C+D"
Try,
="=+"&textjoin("+", true, I11, I192, I245, I280)
Don't know what you will be doing with empty cells so here is draft
Public Sub test()
[I11] = "A": [I192] = "B": [I245] = "C": [I280] = "D"
Debug.Print ConvertedString("=+I11+I192+I245+I280")
End Sub
Public Function ConvertedString(ByVal inputString As String) As Variant
Dim arr() As String, i As Long
On Error GoTo errHand
If Not InStr(inputString, Chr$(43)) > 0 Then
ConvertedString = CVErr(xlErrNA)
Exit Function
End If
arr = Split(inputString, Chr$(43))
For i = 1 To UBound(arr)
arr(i) = Range(arr(i))
Next i
ConvertedString = Join(arr, Chr$(43))
Exit Function
errHand:
ConvertedString = CVErr(xlErrNA)
End Function
I think you mean something like
=INDIRECT(I11,TRUE)+INDIRECT(I192,TRUE)+INDIRECT(I245,TRUE)+INDIRECT(I280,TRUE)
but please note that Indirect is a volatile function, and can slow your calculations down if used extensively.
Using VBA (with only single delimiter):
Function ReplaceAddr(sInput As String, Optional sDelimiter As String = "+") As String
Dim sArr
Dim i As Long
sArr = Split(sInput, sDelimiter)
For i = 1 To UBound(sArr)
sArr = Range(sArr(i))
Next i
ReplaceAddr = Join(sArr, sDelimiter)
End Function
From OP's comment:
The problem is that formulas changes, so I can't only change manually. The one I gave you is only an example, but I have so many different ones with all math operators.
You can try finding cell addresses with regular expression and replace with cell's value:
Function ReplaceAddr2(sInput As String) As String
Dim oRegEx As Object
Dim oMatches As Object
Dim i As Long, lStart As Long, lLength As Long
Set oRegEx = CreateObject("vbscript.regexp")
oRegEx.Pattern = "[A-Za-z]{1,3}\d{1,7}"
oRegEx.Global = True
oRegEx.MultiLine = True
Set oMatches = oRegEx.Execute(sInput)
lStart = 0
For i = 0 To oMatches.Count - 1
lLength = oMatches(i).FirstIndex - lStart
ReplaceAddr2 = ReplaceAddr2 & Mid$(sInput, lStart + 1, lLength) & Range(oMatches(i).Value)
lStart = lStart + lLength + oMatches(i).length
Next
ReplaceAddr2 = ReplaceAddr2 & Mid(sInput, lStart + 1, Len(sInput) - lStart)
End Function
Pattern is 1-3 letters followed by 1-7 digits.
Both functions are not volatile - will be recalculated only when input string changes, but not when cells addressed there change. Adding this line:
Application.Volatile True
will make it recalculate on every change, but it may affect performance.
I was wondering how to remove duplicate names/text's in a cell. For example
Jean Donea Jean Doneasee
R.L. Foye R.L. Foyesee
J.E. Zimmer J.E. Zimmersee
R.P. Reed R.P. Reedsee D.E. Munson D.E. Munsonsee
While googling, I stumbled upon a macro/code, it's like:
Function RemoveDupes1(pWorkRng As Range) As String
'Updateby20140924
Dim xValue As String
Dim xChar As String
Dim xOutValue As String
Set xDic = CreateObject("Scripting.Dictionary")
xValue = pWorkRng.Value
For i = 1 To VBA.Len(xValue)
xChar = VBA.Mid(xValue, i, 1)
If xDic.exists(xChar) Then
Else
xDic(xChar) = ""
xOutValue = xOutValue & xChar
End If
Next
RemoveDupes1 = xOutValue
End Function
The macro is working, but it is comparing every letter, and if it finds any repeated letters, it's removing that.
When I use the code over those names, the result is somewhat like this:
Jean Dos
R.L Foyes
J.E Zimers
R.P edsDEMuno
By looking at the result I can make out it is not what I want, yet I got no clue how to correct the code.
The desired output should look like:
Jean Donea
R.L. Foye
J.E. Zimmer
R.P. Reed
Any suggestions?
Thanks in Advance.
Input
With the input on the image:
Result
The Debug.Print output
Regex
A regex can be used dynamically iterating on the cell, to work as a Find tool. So it will extract only the shortest match. \w*( OUTPUT_OF_EXTRACTELEMENT )\w*, e.g.: \w*(Jean)\w*
The Regex's reference must be enabled.
Code
Function EXTRACTELEMENT(Txt As String, n, Separator As String) As String
On Error GoTo ErrHandler:
EXTRACTELEMENT = Split(Application.Trim(Mid(Txt, 1)), Separator)(n - 1)
Exit Function
ErrHandler:
' error handling code
EXTRACTELEMENT = 0
On Error GoTo 0
End Function
Sub test()
Dim str As String
Dim objMatches As Object
Set objRegExp = CreateObject("VBScript.RegExp") 'New regexp
lastrow = ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlUp).Row
For Row = 1 To lastrow
str = Range("A" & Row)
F_str = ""
N_Elements = UBound(Split(str, " "))
If N_Elements > 0 Then
For k = 1 To N_Elements + 1
strPattern = "\w*(" & EXTRACTELEMENT(CStr(str), k, " ") & ")\w*"
With objRegExp
.Pattern = strPattern
.Global = True
End With
If objRegExp.test(strPattern) Then
Set objMatches = objRegExp.Execute(str)
If objMatches.Count > 1 Then
If objRegExp.test(F_str) = False Then
F_str = F_str & " " & objMatches(0).Submatches(0)
End If
ElseIf k <= 2 And objMatches.Count = 1 Then
F_str = F_str & " " & objMatches(0).Submatches(0)
End If
End If
Next k
Else
F_str = str
End If
Debug.Print Trim(F_str)
Next Row
End Sub
Note that you can Replace the Debug.Print to write on the target
cell, if it is column B to Cells(Row,2)=Trim(F_str)
Explanation
Function
You can use this UDF, that uses the Split Function to obtain the element separated by spaces (" "). So it can get every element to compare on the cell.
Loops
It will loop from 1 to the number of elements k in each cell and from row 1 to lastrow.
Regex
The Regex is used to find the matches on the cell and Join a new string with the shortest element of each match.
This solution operates on the assumption that 'see' (or some other three-letter string) will always be on the end of the cell value. If that isn't the case then this won't work.
Function RemoveDupeInCell(dString As String) As String
Dim x As Long, ct As Long
Dim str As String
'define str as half the length of the cell, minus the right three characters
str = Trim(Left(dString, WorksheetFunction.RoundUp((Len(dString) - 3) / 2, 0)))
'loop through the entire cell and count the number of instances of str
For x = 1 To Len(dString)
If Mid(dString, x, Len(str)) = str Then ct = ct + 1
Next x
'if it's more than one, set to str, otherwise error
If ct > 1 Then
RemoveDupeInCell = str
Else
RemoveDupeInCell = "#N/A"
End If
End Function
How can I remove all characters from inputbox, leaving just numbers?
I have a macro that runs down a column removing white space, shortening to 13 digits but I also need it to remove any text characters.
I still think a regexp is the way to go.
Function removeAlpha(strData As String) As String
strData = Replace(strData, " ", "")
With CreateObject("vbscript.regexp")
.Pattern = "[A-Za-z]"
.Global = True
removeAlpha = .Replace(strData, "")
End With
End Function
And to test:
Sub TestClean()
Const strTest As String = "qwerty123 456 uiops"
MsgBox removeAlpha(strTest)
End Sub
An alternate method to using a regular expression is:
Public Sub removeCharacters()
For Each RANGE_UNASSIGNED In Worksheets(1).Range("A1:A" & Worksheets(1).Range("A1").End(xlDown).Row)
STRING_OUTPUT = ""
For INTEGER_STEP = 1 To Len(RANGE_UNASSIGNED.Value)
STRING_TEMPORARY = Mid(RANGE_UNASSIGNED.Value, INTEGER_STEP, 1)
If STRING_TEMPORARY Like "[a-z.]" Or STRING_TEMPORARY Like "[A-Z.]" Then
STRING_xOUTPUT = ""
Else
STRING_xOUTPUT = STRING_TEMPORARY
End If
STRING_OUTPUT = STRING_OUTPUT & STRING_xOUTPUT
Next INTEGER_STEP
RANGE_UNASSIGNED.Value = STRING_OUTPUT
Next RANGE_UNASSIGNED
End Sub
This should remove all alpha characters from your cell(s). You can remove additional characters if required.
An approach based on IsNumeric.
Sub Keep_If_IsNumeric()
For j = 1 To Cells(Rows.Count, "A").End(xlUp).Row
Set c = Cells(j, 1)
strc = ""
For i = 1 To Len(c.Value)
n = Mid(c.Value, i, 1)
If Not IsNumeric(strc & n & "0") Then
Else
strc = strc & n
End If
Next
c.Offset(, 1) = strc
c.Offset(, 2) = Val(Replace(strc, ",", "."))
Next
End Sub
I have a set of passwords and I need to verify for each one if it contains 1 number, 1 upper case, 1 lower case and if the length is 8 characters.
I made this code but it isn't working for the characters, only for the length.
Can you help me, please? Thanks!!
Sub Password()
Dim b As Integer
Dim i As Integer, j As Integer, k As Integer
Dim psw As String
Dim LengthOFPasswordsList As Long
LengthOFPasswordsList = Range("D" & Rows.Count).End(xlUp).Row
For b = 3 To LengthOFPasswordsList
psw = Range("D" & b)
If i >= 65 Or i <= 90 Then
If j >= 97 Or j <= 122 Then
If k > 48 Or k <= 57 Then
If psw <> (Chr(i) & Chr(j) & Chr(k) & Chr(i Or j Or k) _
& Chr(i Or j Or k) & Chr(i Or j Or k) & Chr(i Or j Or k)) _
And Len(psw) <> 8 Then
Range("F" & b) = "Password Inválida"
End If
End If
End If
End If
Next b
End Sub
It looked like you were trying to do too much at once. There is nothing terribly wrong with splitting up the code a bit. Many times it makes it easier to read and understand what is going on.
Sub Password()
Dim b As Integer
Dim i As Integer, j As Integer, k As Integer
Dim psw As String
Dim hasNum As Boolean, hasUpper As Boolean, hasLower As Boolean
Dim LengthOFPasswordsList As Long
LengthOFPasswordsList = Range("D" & Rows.Count).End(xlUp).Row
For b = 3 To LengthOFPasswordsList
'assume the password is no good.
hasNum = False
hasUpper = False
hasLower = False
'capture the psw in question
psw = Range("D" & b)
'see if there is a number in the password
'NOTE: the following For loops uses the ASCII values for numbers and letters.
For k = 48 To 57
If (InStr(1, psw, Chr(k))) Then
hasNum = True
Exit For
End If
Next k
'See if there is an upper case
For i = 65 To 90
If (InStr(1, psw, Chr(i))) Then
hasUpper = True
Exit For
End If
Next i
'See if there is a lower case
For j = 97 To 122
If (InStr(1, psw, Chr(j))) Then
hasLower = True
Exit For
End If
Next j
'See if all criteria was met
If Not hasLower Or Not hasUpper Or Not hasNum Or (Len(psw) <> 8) Then
Range("F" & b) = "Password Inválida"
End If
Next b
End Sub
How about something like this and use the built in functionality:
Sub PasswordCheck ()
Dim bNum as Boolean, bUpper as Boolean, bLower as Boolean
Dim lRow as Long, x as Long
Dim i as Integer
Dim sPWD as String
lRow = Range("D" & Rows.Count).End(xlUp).Row
For x = 1 to lRow 'Go through each Password
bNum = False
bUpper = False
bLower = False
sPWD = Range("D" & x)
For i = 1 to Len(sPWD) 'Go through each letter
If IsNumeric(Mid(sPWD, i, 1) Then
bNum = True
ElseIf Mid(sPWD, i, 1) Like "[A-Z]" Then
bUpper = True
ElseIf Mid(sPWD, i, 1) Like "[a-z]" Then
bLower = True
End If
Next i
If bNum And bUpper And bLower Then
'Password is valid
Else
'Password is Invalid
Range("F" & x) = "Password Inválida"
End If
Next x
End Sub
I'd personally use regular expressions for this. Just check for each of your criteria and fail the password if any of them don't match. The validation function can be as simple as this:
'Requires a reference to Microsoft VBScript Regular Expressions x.x
Private Function ValidPassword(inValue As String) As Boolean
Dim criteria As Variant
With New RegExp
For Each criteria In Split(".{8},[A-Z],[a-z],[0-9]", ",")
.Pattern = criteria
If Not .Test(inValue) Then Exit Function
Next
End With
ValidPassword = True
End Function
This also hugely simplifies the calling code:
Sub Password()
Dim b As Integer
For b = 3 To Range("D" & Rows.Count).End(xlUp).Row
If Not ValidPassword(Range("D" & b)) Then
Range("F" & b) = "Password Inválida"
End If
Next
End Sub