Excel 2013 here - and am attempting to match the value in cell D to the value in cell C. The part that is leaving me pulling my hair out, is the fact that if a single word exists in column C it should be removed from column D.
For example
Column C Column D
Red Hairy Hats Hairy Cowpies
Since both fields contain the word Hairy it should be updated to read like so
Column C Column D
Red Hairy Hats Cowpies
I can not uncover how to do a wildcard match on string comparison in Excel VBA. I have this syntax which does an Exact match, but how could I do single words from the string like in my example above?
Dim i As Long
Dim resArry
dataArry = Cells(1).CurrentRegion
ReDim resArry(UBound(dataArry, 1) - 1, 1)
For i = 2 To UBound(dataArry, 1)
If InStr(1, dataArry(i, 3), dataArry(i, 4), vbBinaryCompare) Then
resArry(i - 2, 0) = ""
Else
resArry(i - 2, 0) = dataArry(i, 4)
End If
Next
Range("D2").Resize(UBound(resArry, 1)) = resArry
A RegExp option with variant arrays.
Create a pattern for each C string against each D string for a whole word only replacement
\b(Red|Hairy|Hats)\b
etc
Sub Interesting()
Dim rng1 As Range
Dim X, Y
Dim lngCnt As Long
Dim ObjRegex As Object
Set rng1 = Range([c1], Cells(Rows.Count, "c").End(xlUp))
X = rng1.Value2
Y = rng1.Offset(0, 1).Value2
Set ObjRegex = CreateObject("vbscript.regexp")
With ObjRegex
.Global = True
For lngCnt = 1 To UBound(X, 1)
.Pattern = "\b(" & Join(Split(X(lngCnt, 1), Chr(32)), "|") & ")\b"
Y(lngCnt, 1) = .Replace(Y(lngCnt, 1), vbNullString)
Next
End With
rng1.Offset(0, 1).Value2 = Y
End Sub
This is not a complete answer, since I’m a bit rusty with VBA, but rather than use instr to look for matches, you might have more success splitting both strings into arrays.
The process would be something like this:
split both strings using space
for each element in the second array
test whether it’s in the first array
if it is, remove the element
Join the second array back into a string using spaces
Repeat and rinse
Private Sub Test()
Dim C As String, D As String
C = "Red Hairy Hats"
D = "hairy cowpies"
Debug.Print RemoveMatches(C, D)
End Sub
Private Function RemoveMatches(C As String, D As String) As String
Dim Sp() As String
Dim i As Integer
Sp = Split(C)
For i = 0 To UBound(Sp)
If InStr(1, D, Sp(i), vbTextCompare) Then
D = Trim(Replace(D, Sp(i), "", Compare:=vbTextCompare))
End If
Next i
RemoveMatches = D
End Function
Related
I am a VBA beginner, who cannot seem to find a solution to what seemed to be a very easy comparison to me at first.
Basically, I have 2 columns where the values in the cells are split by a delimiter, however, not in the same order.
eg.
Range("A1").value = "1234|5678"
Range("B1").value = "5678|1234"
B1 should then be highlighted as a duplicate
I am searching for some vba code which I can use to loop through the used range's in Columns A & B, to compare and highlight cells in column B that are duplicated, as per example above.
Apologies if I missed any similar questions asked and answered previously, I have indeed conducted a search but perhaps my search criteria may have been out of bounds, and I simply did not come across the VBA solution.
Regards,
Enjay
Based on the little information given you could try the following code
Sub Highlight()
Const DELIMITER = "|"
Dim rg As Range
Dim a As Variant
Dim b As Variant
Dim sngCell As Range
Set rg = Range("A1:A3")
For Each sngCell In rg
a = Split(sngCell.Value2, DELIMITER)
b = Split(sngCell.Offset(, 1).Value2, DELIMITER)
If isEqual(a, b) Then
With sngCell.Offset(, 1).Interior
.ThemeColor = xlThemeColorAccent6
End With
End If
Next sngCell
End Sub
with the following functions
Function isEqual(a As Variant, b As Variant) As Boolean
a = BubbleSort(a)
b = BubbleSort(b)
isEqual = True
Dim i As Long
For i = LBound(a) To UBound(a)
If a(i) <> b(i) Then
isEqual = False
Exit For
End If
Next i
End Function
Function BubbleSort(ByRef strArray As Variant) As Variant
'sortieren von String Array
'eindimensionale Array
'Bubble-Sortier-Verfahren
Dim z As Long
Dim i As Long
Dim strWert As Variant
For z = UBound(strArray) - 1 To LBound(strArray) Step -1
For i = LBound(strArray) To z
If LCase(strArray(i)) > LCase(strArray(i + 1)) Then
strWert = strArray(i)
strArray(i) = strArray(i + 1)
strArray(i + 1) = strWert
End If
Next i
Next z
BubbleSort = strArray
End Function
This will answer your question as-is. If the solution needs to be adjusted, I trust that you can fix it :)
This uses StrComp to (in memory only) re-order the two string parts so that it can easily detect duplicate values.
Option Explicit
Sub DuplicateCheck()
Dim delimiter As String
delimiter = "|"
Dim lastCol As Long
lastCol = Cells(1, Columns.count).End(xlToLeft).Column
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim i As Long
For i = 1 To lastCol
Dim theSplit As Variant
theSplit = Split(Cells(1, i), delimiter)
Dim temp As String
If StrComp(theSplit(0), theSplit(1), vbTextCompare) = 1 Then
temp = theSplit(1)
theSplit(1) = theSplit(0)
theSplit(0) = temp
End If
temp = theSplit(0) & delimiter & theSplit(1)
If Not dict.exists(temp) Then
dict.Add (temp), 1
Else
Cells(1, i).Interior.color = 65535
End If
Next i
End Sub
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
In the column A, I have a list of strings. In the next column, I'd like to have all possible pairs (concatenated), such as:
|Column A | Column B|
|A| |AB|
|B| |AC|
|C| |BC|
|...| |...|
I have over 150 strings in my column A. I guess that I'll need a double loop, but I'm not sure how to proceed.
Here's one approach.
Option Explicit
' Modify if you want to delimit the concatenated values
Const delimiter As String = vbNullString
' If you want to concatenate a cell with itself, set this to True
Const compareSelf As Boolean = False
Sub pairs_mem()
'The pairs procedure calls on ConcatValues to write out data to sheet
' this procedures create pairwise combinations of each cell
' this does not omit duplicates (items nor pairs) or any other special considerations
Dim rng As Range
Dim cl1 As Range, cl2 As Range, dest As Range
Dim i As Long, length As Long
'Range of values to be concatenated, Modify as needed
Set rng = Range("A1:A7")
length = rng.Cells.Count
'Begin putting output in B1, Modify as needed
Set dest = Range("B1")
'Get the size of the output array
' output() is array container for the output values
If compareSelf Then
ReDim output(1 To length * (length - 1))
Else
ReDim output(1 To length ^ 2)
End If
i = 1
For Each cl1 In rng.Cells
For Each cl2 In rng.Cells
If cl1.Address = cl2.Address Then
If compareSelf Then
output(i) = ConcatValues(cl1, cl2)
i = i + 1
End If
Else
output(i) = ConcatValues(cl1, cl2)
i = i + 1
End If
Next
Next
dest.Resize(UBound(output)).Value = Application.Transpose(output)
End Sub
Function ConcatValues(ParamArray vals() As Variant)
'Call this function to do the concatenation and returns the "i" value to caller
Dim s$
Dim itm
For Each itm In vals
s = s & itm & delimiter
Next
If delimiter <> vbNullString Then
s = Left(s, Len(s) - 1)
End If
ConcatValues = s
End Function
I have an Excel Worksheet consisting of two columns, one of which is filled with strings and the other is emtpy. I would like to use VBA to assign the value of the cells in the empty column based on the value of the adjacent string in the other column.
I have the following code:
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
Dim j As Integer
For j = 1 To 2
If regexAdmin.test(Cells(i, j).Value) Then
Cells(i, j + 1).Value = "Exploitation"
End If
Next j
Next i
The problem is that when using this loop for a big amount of data, it takes way too long to work and, most of the time, it simply crashes Excel.
Anyone knows a better way to this?
You have an unnecessary loop, where you test the just completed column (j) too. Dropping that should improve the speed by 10-50%
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
If regexAdmin.test(Cells(i, 1).Value) Then
Cells(i, 1).offset(0,1).Value = "Exploitation"
End If
Next i
If the regex pattern really is simply "Admin", then you could also just use a worksheet formula for this, instead of writing a macro. The formula, which you'd place next to the text column (assuming your string/num col is A) would be:
=IF(NOT(ISERR(FIND("Admin",A1))),"Exploitation","")
In general, if it can be done with a formula, then you'd be better off doing it so. it's easier to maintain.
Try this:
Public Sub ProcessUsers()
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim r As Range, N As Integer, i As Integer
Set r = Range("A1") '1st row is headers
N = CountRows(r) - 1 'Count data rows
Dim inputs() As Variant, outputs() As Variant
inputs = r.Offset(1, 0).Resize(N, 1) ' Get all rows and 1 columns
ReDim outputs(1 To N, 1 To 1)
For i = 1 To N
If regexAdmin.test(inputs(i, 1)) Then
outputs(i, 1) = "Exploitation"
End If
Next i
'Output values
r.Offset(1, 1).Resize(N, 1).Value = outputs
End Sub
Public Function CountRows(ByRef r As Range) As Long
If IsEmpty(r) Then
CountRows = 0
ElseIf IsEmpty(r.Offset(1, 0)) Then
CountRows = 1
Else
CountRows = r.Worksheet.Range(r, r.End(xlDown)).Rows.Count
End If
End Function
when we are going to do a loop in the rows, we can use code like the following:
i = 1
Do
Range("E" & i & ":D" & i).Select
i = i + 1
Loop Until i > 10
but what if we want to do a loop on a column?
Can we use the same method as above?
while the columns in Excel is a complex such as A, B, C, ..., Y, Z, AA, AB, AC, ..., etc.
problems will arise between loop from the "Z" to the "AA".
how we do looping alphabet column from "A" to "Z" and then continued into "AA", "AB" and so on
is there anything that can help?
Yes, let's use Select as an example
sample code: Columns("A").select
How to loop through Columns:
Method 1: (You can use index to replace the Excel Address)
For i = 1 to 100
Columns(i).Select
next i
Method 2: (Using the address)
For i = 1 To 100
Columns(Columns(i).Address).Select
Next i
EDIT:
Strip the Column for OP
columnString = Replace(Split(Columns(27).Address, ":")(0), "$", "")
e.g. you want to get the 27th Column --> AA, you can get it this way
Another method to try out.
Also select could be replaced when you set the initial column into a Range object. Performance wise it helps.
Dim rng as Range
Set rng = WorkSheets(1).Range("A1") '-- you may change the sheet name according to yours.
'-- here is your loop
i = 1
Do
'-- do something: e.g. show the address of the column that you are currently in
Msgbox rng.offset(0,i).Address
i = i + 1
Loop Until i > 10
** Two methods to get the column name using column number**
Split()
code
colName = Split(Range.Offset(0,i).Address, "$")(1)
String manipulation:
code
Function myColName(colNum as Long) as String
myColName = Left(Range(0, colNum).Address(False, False), _
1 - (colNum > 10))
End Function
If you want to stick with the same sort of loop then this will work:
Option Explicit
Sub selectColumns()
Dim topSelection As Integer
Dim endSelection As Integer
topSelection = 2
endSelection = 10
Dim columnSelected As Integer
columnSelected = 1
Do
With Excel.ThisWorkbook.ActiveSheet
.Range(.Cells(columnSelected, columnSelected), .Cells(endSelection, columnSelected)).Select
End With
columnSelected = columnSelected + 1
Loop Until columnSelected > 10
End Sub
EDIT
If in reality you just want to loop through every cell in an area of the spreadsheet then use something like this:
Sub loopThroughCells()
'=============
'this is the starting point
Dim rwMin As Integer
Dim colMin As Integer
rwMin = 2
colMin = 2
'=============
'=============
'this is the ending point
Dim rwMax As Integer
Dim colMax As Integer
rwMax = 10
colMax = 5
'=============
'=============
'iterator
Dim rwIndex As Integer
Dim colIndex As Integer
'=============
For rwIndex = rwMin To rwMax
For colIndex = colMin To colMax
Cells(rwIndex, colIndex).Select
Next colIndex
Next rwIndex
End Sub
Just use the Cells function and loop thru columns.
Cells(Row,Column)