How to bypass 255 character limit of VBA mass replace function? - vba

Sub MultiFindNReplace()
'Update 20140722
Dim Rng As Range
Dim InputRng As Range, ReplaceRng As Range
xTitleId = "KutoolsforExcel"
Set InputRng = Application.Selection
Set InputRng = Application.InputBox("Original Range ", xTitleId, InputRng.Address, Type:=8)
Set ReplaceRng = Application.InputBox("Replace Range :", xTitleId, Type:=8)
Application.ScreenUpdating = False
For Each Rng In ReplaceRng.Columns(1).Cells
    InputRng.Replace what:=Rng.Value, replacement:=Rng.Offset(0, 1).Value
Next
Application.ScreenUpdating = True
End Sub
Source:
Extend Office - How To Find And Replace Multiple Values At Once In Excel?
Data type:
Using the Excel Application.InputBox method
I tried to replace Type:=8 with Type:=2 for text instead of range, but it didn't work. Please help me by pass the 255 character limit.
Example Data:
Google Spreadsheet

I'm not 100% clear on what data you have and what you're trying to do, but I think you will have more success if you use the:
MSDN : Replace Function (VBA)
...instead of:
Office Support: Range.Replace Method (Excel)
The second one is basically a worksheet function, therefore subject to various limits that the first one doesn't have.
Your code should require only minor changes to adapt to the Replace function.

Replace Whole Cell Content
So my idea to replace whole cell contents is:
Read replacements into a dictionary
Read data into an array
Replace in array
Write array back to cells
I choose the array because using arrays is much faster than working with cells. So we have only one slow cell reading and one slow cell writing and working with the array is fast.
Option Explicit
Public Sub MultiReplaceWholeCells()
Const xTitleId As String = "KutoolsforExcel"
Dim InputRange As Range
Set InputRange = Range("A2:F10") 'Application.InputBox("Original Range ", xTitleId, InputRng.Address, Type:=8)
Dim ReplaceRange As Range
Set ReplaceRange = Range("A12:B14") 'Application.InputBox("Original Range ", xTitleId, InputRng.Address, Type:=8)
Dim Replacements As Object
Set Replacements = CreateObject("Scripting.Dictionary")
'read replacements into an array
Dim ReplaceValues As Variant
ReplaceValues = ReplaceRange.Value
'read replacements into a dictionary
Dim iRow As Long
For iRow = 1 To ReplaceRange.Rows.Count
Replacements.Add ReplaceValues(iRow, 1), ReplaceValues(iRow, 2)
Next iRow
'read values into an array
Dim Data As Variant
Data = InputRange.Value
'loop through array data and replace whole data
Dim r As Long, c As Long
For r = 1 To InputRange.Rows.Count
For c = 1 To InputRange.Columns.Count
If Replacements.Exists(Data(r, c)) Then
Data(r, c) = Replacements(Data(r, c))
End If
Next c
Next r
'write data from array back to range
InputRange.Value = Data
End Sub
Replace Part Of Cell Content
For replacing a part of a cell this would be slower:
Option Explicit
Public Sub MultiReplaceWholeCells()
Const xTitleId As String = "KutoolsforExcel"
Dim InputRange As Range
Set InputRange = Range("A2:F10") 'Application.InputBox("Original Range ", xTitleId, InputRng.Address, Type:=8)
Dim ReplaceRange As Range
Set ReplaceRange = Range("A12:B14") 'Application.InputBox("Original Range ", xTitleId, InputRng.Address, Type:=8)
'read replacements into an array
Dim ReplaceValues As Variant
ReplaceValues = ReplaceRange.Value
'read values into an array
Dim Data As Variant
Data = InputRange.Value
'loop through array data and replace PARTS of data
Dim r As Long, c As Long
For r = 1 To InputRange.Rows.Count
For c = 1 To InputRange.Columns.Count
Dim iRow As Long
For iRow = 1 To ReplaceRange.Rows.Count
Data(r, c) = Replace(Data(r, c), ReplaceValues(iRow, 1), ReplaceValues(iRow, 2))
Next iRow
Next c
Next r
'write data from array back to range
InputRange.Value = Data
End Sub
If you only need to replace whole cell contents use the first one which should be faster.
Procedure to Do Both Replacement Types
Or if you need both write a procedure so you can chose if you need to replace xlWhole or xlPart. Even a different output range would be possible.
Option Explicit
Public Sub TestReplace()
Const xTitleId As String = "KutoolsforExcel"
Dim InputRange As Range
Set InputRange = Range("A2:F10") 'Application.InputBox("Original Range ", xTitleId, InputRng.Address, Type:=8)
Dim ReplaceRange As Range
Set ReplaceRange = Range("A12:B14") 'Application.InputBox("Original Range ", xTitleId, InputRng.Address, Type:=8)
MultiReplaceInCells InputRange, ReplaceRange, xlWhole, Range("A20") 'replace whole to output range
MultiReplaceInCells InputRange, ReplaceRange, xlPart, Range("A30") 'replace parts to output range
MultiReplaceInCells InputRange, ReplaceRange, xlWhole 'replace whole in place
End Sub
Public Sub MultiReplaceInCells(InputRange As Range, ReplaceRange As Range, Optional LookAt As XlLookAt = xlWhole, Optional OutputRange As Range)
'read replacements into an array
Dim ReplaceValues As Variant
ReplaceValues = ReplaceRange.Value
'read values into an array
Dim Data As Variant
Data = InputRange.Value
Dim r As Long, c As Long, iRow As Long
If LookAt = xlPart Then
'loop through array data and replace PARTS of data
For r = 1 To InputRange.Rows.Count
For c = 1 To InputRange.Columns.Count
For iRow = 1 To ReplaceRange.Rows.Count
Data(r, c) = Replace(Data(r, c), ReplaceValues(iRow, 1), ReplaceValues(iRow, 2))
Next iRow
Next c
Next r
Else
'read replacements into a dictionary
Dim Replacements As Object
Set Replacements = CreateObject("Scripting.Dictionary")
For iRow = 1 To ReplaceRange.Rows.Count
Replacements.Add ReplaceValues(iRow, 1), ReplaceValues(iRow, 2)
Next iRow
'loop through array data and replace WHOLE data
For r = 1 To InputRange.Rows.Count
For c = 1 To InputRange.Columns.Count
If Replacements.Exists(Data(r, c)) Then
Data(r, c) = Replacements(Data(r, c))
End If
Next c
Next r
End If
'write data from array back to range
If OutputRange Is Nothing Then
InputRange.Value = Data
Else
OutputRange.Resize(InputRange.Rows.Count, InputRange.Columns.Count).Value = Data
End If
End Sub

Related

How can I compare and change cells in Excel

I have two sheets. In both sheets I have a column (B) where each row is a different name:
Sheet 'A' has names in the column, these names are 'leading'.
Sheet 'B' also has names in this column, these names CAN BE outdated.
What I want: A macro which can check the names.
delete the names in Sheet B which are not (anymore) in Sheet A (delete entire affecting row)
Add the names from sheet A which are not (yet) in sheet B in new rows
What I have so far:
Sub DeleteRowsThatDoNotMatch()
Dim rng As Range
Dim Rng1 As Range, Rng2 As Range
Dim thisRow As Long
Dim arr1 As Variant
Dim arr2 As Variant
Dim dic2 As Variant
Dim OutArr As Variant
xTitleId = "Test"
Set Rng1 = Application.Selection
Set Rng1 = Application.InputBox("Welke data moet gewijzigd worden? :", xTitleId, Rng1.Address, Type:=8)
Set Rng2 = Application.InputBox("Selecteer de nieuwe waardes:", xTitleId, Type:=8)
Set Rng1 = Rng1.Columns(1)
Set dic2 = CreateObject("Scripting.Dictionary")
arr2 = Rng2.Value
For i = 1 To UBound(arr2, 1)
xKey = arr2(i, 1)
dic2(xKey) = ""
Next
thisRow = Rng1.Rows.Count
Do While thisRow > 0
If Not dic2.Exists(Rng1.Cells(thisRow, 1).Value) Then
Rng1.Cells(thisRow, 1).EntireRow.Delete
End If
thisRow = thisRow - 1
Loop
End Sub
This works as expected; the problem is adding the names which ARE in Sheet A but not (yet) in Sheet B.

Visual Basic for Applications (VBA) Count Unique Cells

I'm attempting to make a Visual Basic Macro to count unique items in a row without doing the copy and pasting and data remove duplicates.
For some reason I'm having issues with my syntax. When I run the script it outputs with the number of rows.
This is my first time programming in Visual Basic for Applications (VBA).
Private Sub FrequencyCount_Click()
Dim rng As Range
Dim outcell As Range
Dim outnum As Integer
Dim MyArray() As Variant
Dim ArrayLength As Integer
Dim unique As Boolean
Dim i
outnum = 0
ArrayLength = 1
unique = False
Set rng = Application.InputBox("Select a Range to Count Uniques", "Obtain Range Object", Type:=8)
Set outcell = Application.InputBox("Select an Output Box for Uniques", "Obtain Range Object", Type:=8)
For Each B In rng.Rows
If outnum = 0 Then
ReDim MyArray(1)
MyArray(ArrayLength) = B.Value
outnum = outnum + 1
ArrayLength = ArrayLength + 1
Else
i = 0
unique = True
Do Until i < ArrayLength
If MyArray(i) = B.Value Then
unique = False
End If
i = i + 1
Loop
MsgBox unique
If unique = True Then
ReDim Preserve MyArray(0 To ArrayLength + 1)
MyArray(ArrayLength) = B.Value
ArrayLength = ArrayLength + 1
outnum = outnum + 1
End If
End If
Next
End
outcell.Value = outnum
End Sub
It is generally considered bad practice to ReDim Arrays in Loop and not recommended. If you search internet then many discussions like this will come up
ReDim in Loop
You can use built-in functionality to get where you want. Example code which should work for you.
Sub FrequencyCount_Click()
Dim rng As Range
Dim outcell As Range
Set rng = Application.InputBox("Select a Range to Count Uniques", "Obtain Range Object", Type:=8)
Set outcell = Application.InputBox("Select an Output Box for Uniques", "Obtain Range Object", Type:=8)
rng.Copy outcell.Cells(1, 1)
outcell.Resize(rng.Cells.Count, 1).RemoveDuplicates 1, xlNo
End Sub
As #RyanWildry suggests, you can use the Dictionary object for this.
The code to call the procedure will also define the range containing the duplicates and the start range to paste the unique values to:
Sub Test()
'This will take values in the first range and paste the uniques starting at the second range cell.
'NB: Search for With...End With.
With ThisWorkbook.Worksheets("Sheet1")
FrequencyCount .Range("B2:B48"), .Range("D2")
End With
End Sub
This code will then place the values into a dictionary, which also removes any duplicates and then uses a couple of techniques to paste back into rows or columns.
I've added lots of comments and this link may help for further reading on the Dictionary object: https://excelmacromastery.com/vba-dictionary/
Public Sub FrequencyCount(SourceRange As Range, TargetRange As Range)
Dim oDict As Object
Dim rCell As Range
Dim vKey As Variant
Dim vArr As Variant
Dim x As Long
Set oDict = CreateObject("Scripting.Dictionary")
oDict.comparemode = vbTextCompare 'Non-case sensitive. Use vbBinaryCompare to make case sensitive.
'Go through each cell in the source range and copy the value to the dictionary.
For Each rCell In SourceRange
'Change the value in the dictionary referenced by key value.
'If key value doesn't exist create it.
oDict(rCell.Value) = rCell.Value
Next rCell
'Paste in rows.
x = 1
ReDim vArr(1 To oDict.Count)
For Each vKey In oDict.Keys
vArr(x) = oDict(vKey)
x = x + 1
Next vKey
TargetRange.Resize(UBound(vArr)) = WorksheetFunction.Transpose(vArr)
'Paste in columns.
TargetRange.Resize(1, UBound(Application.Transpose(oDict.Keys))) = oDict.Keys
End Sub
The problem is you are setting i = 0 then saying
Do until i < arraylength`
Well if i = 0 then it will always be less than arraylength, this probably should be
Do until i > arraylength
Hope this helps :)
This is an more compact solution, I cobbled together from other solutions.
Sub UniqueCountinSelection()
Dim outcell As Range
Dim itms As Object, c As Range, k, tmp As String
Set rng = Application.InputBox("Select a Range to Count Uniques", "Obtain Range Object", Type:=8)
Set outcell = Application.InputBox("Select an Output Box for Uniques", "Obtain Range Object", Type:=8)
Set itms = CreateObject("scripting.dictionary")
For Each c In rng
tmp = Trim(c.Value) 'removes leading and trailing spaces
If Len(tmp) > 0 Then itms(tmp) = itms(tmp) + 1
Next c
outcell.Value = UBound(itms.Keys) + 1
End Sub

Excel VBA array of Selected Range

I know how to make two functions on each column (in this case TRIM and STRCONV to ProperCase
Dim arrData() As Variant
Dim arrReturnData() As Variant
Dim rng As Excel.Range
Dim lRows As Long
Dim lCols As Long
Dim i As Long, j As Long
Range("H2", Range("H2").End(xlDown)).Select
lRows = Selection.Rows.Count
lCols = Selection.Columns.Count
ReDim arrData(1 To lRows, 1 To lCols)
ReDim arrReturnData(1 To lRows, 1 To lCols)
Set rng = Selection
arrData = rng.Value
For j = 1 To lCols
For i = 1 To lRow
arrReturnData(i, j) = StrConv(Trim(arrData(i, j)), vbProperCase)
Next i
Next j
rng.Value = arrReturnData
Set rng = Nothing
Currently I'm trying to figure out how to add one more FOR which where I could gather more than one selection ranges for example:
Set myAnotherArray(0) = Range("H2", Range("H2").End(xlDown)).Select
Set myAnotherArray(1) = Range("J2", Range("J2").End(xlDown)).Select
For k = 1 To myAnotherArray.lenght
Because I'm copying and pasting whole script to make aciton on three columns. Tried already:
Dim Rng As Range
Dim Area As Range
Set Rng = Range("Range("H2", Range("H2").End(xlDown)).Select,Range("J2", Range("J2").End(xlDown)).Select")
For Each Area In Rng.Areas
Area.Font.Bold = True
Next Area
Even tried to Union range but I failed. Any sugesstions?
And as always... Thank you for your time!
I found a way you could use to perform work on those ranges, refer to the code below:
Sub DoSomethingWithRanges()
Dim m_Worksheet As Excel.Worksheet
Dim m_Columns() As Variant
Set m_Worksheet = ActiveSheet
' fill all your columns in here
m_Columns = Array(2, 3, 4)
For Each m_Column In m_Columns
' the area being used ranges from the second until the last row of your column
With m_Worksheet.Range(m_Worksheet.Cells(2, m_Column), m_Worksheet.Cells(m_Worksheet.UsedRange.Rows.Count, m_Column))
' do things with range
.Font.Bold = True
End With
Next m_Column
End Sub
In the variant array m_Columns you can add all the columns you want. Only downside is that in my example you have to use numbers to specify columns instead of "H". However, you don't have to worry about the row-indexes, since the area automatically ranges from the second to the last used row.

Find column with name "x" and count word occurrences in that column

I have a worksheet of data with headers. I am trying, in VBA, to find the column with the header "type" and then in that column count the amount of times string "x" appears, i.e count the number of times "add" appears in column with header "type".
I know you can create a scripting dictionary to count the amount of times each word appears, I am having issues with searching through the headers to find the column "type".
My code so far looks at every cell in the sheet however i just want to limit it to the column "type":
Dim shtSheet1 As String
Dim dict As Object
Dim mycell As Range
shtSheet1 = "Test"
Set dict = CreateObject("Scripting.Dictionary")
dict.Add "Add", 0
dict.Add "Delete", 0
dict.Add "Update", 0
For Each mycell In ActiveWorkbook.Worksheets(shtSheet1).UsedRange
If dict.Exists(ActiveWorkbook.Worksheets(shtSheet1).Cells(mycell.Row, mycell.Column).Value) Then
dict(ActiveWorkbook.Worksheets(shtSheet1).Cells(mycell.Row, mycell.Column).Value) = dict(ActiveWorkbook.Worksheets(shtSheet1).Cells(mycell.Row, mycell.Column).Value) + 1
End If
Next
Thanks for any help!
if I understood correctly, then you can use this:
Sub test()
Dim Dict As Object: Set Dict = CreateObject("Scripting.Dictionary")
Dim shtSheet1 As Worksheet: Set shtSheet1 = Sheets("Test")
Dim mycell As Range, n&, z&
Dim Fx As Object, Key As Variant
Set Fx = WorksheetFunction
Dict.CompareMode = vbTextCompare
With shtSheet1
n = .Rows(1).Find("Type").Column
z = .Cells(.Rows.Count, n).End(xlUp).Row
For Each mycell In .Range(.Cells(2, n), Cells(z, n))
If Not Dict.Exists(Fx.Trim(mycell)) Then Dict.Add Fx.Trim(mycell), 0
Next
For Each mycell In .Range(.Cells(2, n), Cells(z, n))
If Dict.Exists(Fx.Trim(mycell)) Then
Dict(Fx.Trim(mycell)) = CLng(Dict(Fx.Trim(mycell))) + 1
End If
Next
End With
For Each Key In Dict
Debug.Print Key, Dict(Key)
Next Key
End Sub
output with data example is below:
update
variant using worksheetfunction.countif with dictionary
Sub test2()
Dim Dict As Object: Set Dict = CreateObject("Scripting.Dictionary")
Dim shtSheet1 As Worksheet: Set shtSheet1 = Sheets("Test")
Dim mycell As Range, n&, Data As Range
Dim Fx As Object, Key As Variant
Set Fx = WorksheetFunction
Dict.CompareMode = vbTextCompare
With shtSheet1
n = .Rows(1).Find("Type").Column
Set Data = .Range(.Cells(2, n), Cells(.Cells(.Rows.Count, n).End(xlUp).Row, n))
For Each mycell In Data
If Not Dict.Exists(Fx.Trim(mycell)) Then Dict.Add Fx.Trim(mycell), Fx.CountIf(Data, "*" & Fx.Trim(mycell) & "*")
Next
End With
For Each Key In Dict
Debug.Print Key, Dict(Key)
Next Key
End Sub
I would use this code block to iterate through your column headers. Further, I would use the Worksheet Function COUNTIF so you only have to iterate through the column headers rather than every cell in your range.
Dim shtSheet1 As String
Dim dict As Object
Dim myCell As Range
Dim firstHeaderCell As Range
shtSheet1 = "Test"
Set dict = CreateObject("Scripting.Dictionary")
Set firstHeaderCell = Range("A1")
'Iterate across column headers only
For Each myCell In Range(firstHeaderCell, _
Cells(firstHeaderCell.Row, _
firstHeaderCell.Column + firstHeaderCell.CurrentRegion.Columns.Count - 1))
'Add it to the dictionary if it isn't there (this future proofs the code)
If Not dict.Exists(myCell.Value) Then
dict.Add myCell.Value, 0
End If
'Use worksheet function COUNTIF to count number of instances of column header value in the column
dict(myCell.Value) = WorksheetFunction.CountIf(Range(Cells(firstHeaderCell.Row + 1, myCell.Column), _
Cells(firstHeaderCell.CurrentRegion.Rows.Count - 1, firstHeaderCell.Column)), _
myCell.Value)
Next

Speed up loop in excel

I had some great help to get this search tool working in excel but I was wondering if there is room for speed improvement. I did some research and with what little I understand about VB for i = LBOUND(array) To UBOUND(array) seems most optimal. Would 'For Each' be faster? I am wondering if there is a way to isolate the records currently in the worksheet, or if it is already doing this with L/UBOUND? If it is, is there a way to do 'ignore special characters' similar to SQL? After adding screenupdating and calculation, I was able to shave about 10 seconds off of the total run time. And further I was using FormulaR1C1 for my search before this new loop and it would limit the amount of columns to search while being super fast.
Range("W2:W" & LastRow).FormulaR1C1 = _
"=IF(ISERR(SEARCH(R1C23,RC[-22]&RC[-21]&RC[-20]&RC[-19]&RC[-18]&RC[-17]&RC[-16]&RC[-15]&RC[-15]&RC[-14]&RC[-13]&RC[-12]&RC[-11]&RC[-10]&RC[-9]&RC[-8]&RC[-7]&RC[-6]&RC[-5]&RC[-4]&RC[-3]&RC[-2]&RC[-1])),0,1)"
If WorksheetFunction.CountIf(Columns(23), 1) = 0 Then
Columns(23).Delete
Any help or recommendations are greatly appreciated.
Sub FindFeature()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim shResults As Worksheet
Dim vaData As Variant
Dim i As Long, j As Long
Dim sSearchTerm As String
Dim sData As String
Dim rNext As Range
Dim v As Variant
Dim vaDataCopy As Variant
Dim uRange As Range
Dim findRange As Range
Dim nxtRange As Range
Dim ws As Range
'Put all the data into an array
vaData = ActiveSheet.UsedRange.Value
'Get the search term
sSearchTerm = Application.InputBox("What are you looking for?")
'Define and clear the results sheet
Set shResults = ActiveWorkbook.Worksheets("Results")
shResults.Range("A3").Resize(shResults.UsedRange.Rows.Count, 1).EntireRow.Delete
Set uRange = ActiveSheet.UsedRange
vaData = uRange.Value
vaDataCopy = vaData
For Each v In vaDataCopy
v = Anglicize(v)
Next
Application.WorksheetFunction.Transpose (vaDataCopy)
ActiveSheet.UsedRange.Value = vaDataCopy
'Loop through the data
Set ws = Cells.Find(What:=uRange, After:="ActiveCell", LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not ws Is Nothing Then
Set findRange = ws
Do
Set nxtRange = Cells.FindNext(After:=ws)
Set findRange = nxtRange
Loop Until ws.Address = findRange.Address
ActiveSheet.UsedRange.Value = vaData
'Write the row to the next available row on Results
Set rNext = shResults.Cells(shResults.Rows.Count, 1).End(xlUp).Offset(1, 0)
rNext.Resize(1, uRange(vaData, 2)).Value = Application.Index(vaData, i, 0)
'Stop looking in that row after one match
End If
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Ultimately, the execution speed here is severely hampered by the apparent requirement to operate on every cell in the range, and because you're asking about performance, I suspect this range may contain many thousands of cells. There are two things I can think of:
1. Save your results in an array and write to the Results worksheet in one statement
Try replacing this:
'Write the row to the next available row on Results
Set rNext = shResults.Cells(shResults.Rows.Count, 1).End(xlUp).Offset(1, 0)
rNext.Resize(1, UBound(vaData, 2)).Value = Application.Index(vaData, i, 0)
'Stop looking in that row after one match
Exit For
With a statement that assigns the value Application.Index(vaData, i, 0) to an array variable, and then when you're completed the For i loop, you can write the results in one pass to the results worksheet.
NOTE This may be noticeably faster if and only if there are many thousands of results. If there are only a few results expected, then exeuction speed is primarily affected by the need to iterate over every cell, not the operation of writing the results to another sheet.
2. Use another method than cell iteration
If you can implement this method, I would use it in conjunction with the above.
Ordinarily I would recommend using the .Find and .FindNext methods as considerably more efficient than using the i,j iteration. But since you need to use the Anglicize UDF on every cell in the range, you would need to make some restructure your code to accommodate. Might require multiple loops, for example, first Anglicize the vaData and preserve a copy of the non-Anglicized data, like:
Dim r as Long, c as Long
Dim vaDataCopy as Variant
Dim uRange as Range
Set uRange = ActiveSheet.UsedRange
vaData = uRange.Value
vaDataCopy = vaData
For r = 1 to Ubound(varDataCopy,1)
For c = 1 to Ubound(varDataCopy,2)
varDataCopy(r,c) = Anglicize(varDataCopy(r,c))
Next
Next
Then, put the Anglicize version on to the worksheet.
ActiveSheet.UsedRange.Value = vaDataCopy
Then, instead of the For i =... For j =... loop, use the .Find and .FindNext method on the uRange object.
Here is an example of how I implement Find/FindNext.
Finally, put the non-Anglicized version back on the worksheet, again with the caveat that it might require use of Transpose function:
ActiveSheet.UsedRange.Value = vaData
Whil this still iterates over every value to perform the Anglicize function, it does not operate on every value a second time (Instr function). So, you're essentially operating on the values only once, rather than twice. I suspect this should be much faster, especially if you combine it with the #1 above.
UPDATE BASED ON OP REVISION EFFORTS
After some comment discussion & emails back and forth, we arrive at this solution:
Option Explicit
Sub FindFeature()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim shSearch As Worksheet:
Dim shResults As Worksheet
Dim vaData As Variant
Dim i As Long, j As Long, r As Long, c As Long
Dim sSearchTerm As String
Dim sData As String
Dim rNext As Range
Dim v As Variant
Dim vaDataCopy As Variant
Dim uRange As Range
Dim findRange As Range
Dim nxtRange As Range
Dim rng As Range
Dim foundRows As Object
Dim k As Variant
Set shSearch = Sheets("City")
shSearch.Activate
'Define and clear the results sheet
Set shResults = ActiveWorkbook.Worksheets("Results")
shResults.Range("A3").Resize(shResults.UsedRange.Rows.Count, 1).EntireRow.Delete
'# Create a dictionary to store our result rows
Set foundRows = CreateObject("Scripting.Dictionary")
'Get the search term
sSearchTerm = Application.InputBox("What are you looking for?")
'# set and fill our range/array variables
Set uRange = shSearch.UsedRange
vaData = uRange.Value
vaDataCopy = Application.Transpose(vaData)
For r = 1 To UBound(vaDataCopy, 1)
For c = 1 To UBound(vaDataCopy, 2)
'MsgBox uRange.Address
vaDataCopy(r, c) = Anglicize(vaDataCopy(r, c))
Next
Next
'# Temporarily put the anglicized text on the worksheet
uRange.Value = Application.Transpose(vaDataCopy)
'# Loop through the data, finding instances of the sSearchTerm
With uRange
.Cells(1, 1).Activate
Set rng = .Cells.Find(What:=sSearchTerm, After:=ActiveCell, _
LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not rng Is Nothing Then
Set findRange = rng
Do
Set nxtRange = .Cells.FindNext(After:=findRange)
Debug.Print sSearchTerm & " found at " & nxtRange.Address
If Not foundRows.Exists(nxtRange.Row) Then
'# Make sure we're not storing the same row# multiple times.
'# store the row# in a Dictionary
foundRows.Add nxtRange.Row, nxtRange.Column
End If
Set findRange = nxtRange
'# iterate over all matches, but stop when the FindNext brings us back to the first match
Loop Until findRange.Address = rng.Address
'# Iterate over the keys in the Dictionary. This contains the ROW# where a match was found
For Each k In foundRows.Keys
'# Find the next empty row on results page:
With shResults
Set rNext = .Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0). _
Resize(1, UBound(Application.Transpose(vaData), 1))
End With
'# Write the row to the next available row on Results
rNext.Value = Application.Index(vaData, k, 0)
Next
Else:
MsgBox sSearchTerm & " was not found"
End If
End With
'# Put the non-Anglicized values back on the sheet
uRange.Value = vaData
'# Restore application properties
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
'# Display the results
shResults.Activate
End Sub
Public Function Anglicize(ByVal sInput As String) As String
Dim vaGood As Variant
Dim vaBad As Variant
Dim i As Long
Dim sReturn As String
Dim c As Range
'Replace any 'bad' characters with 'good' characters
vaGood = Split("S,Z,s,z,Y,A,A,A,A,A,A,C,E,E,E,E,I,I,I,I,D,N,O,O,O,O,O,U,U,U,U,Y,a,a,a,a,a,a,c,e,e,e,e,i,i,i,i,d,n,o,o,o,o,o,u,u,u,u,y,y", ",")
vaBad = Split("Š,Ž,š,ž,Ÿ,À,Á,Â,Ã,Ä,Å,Ç,È,É,Ê,Ë,Ì,Í,Î,Ï,Ð,Ñ,Ò,Ó,Ô,Õ,Ö,Ù,Ú,Û,Ü,Ý,à,á,â,ã,ä,å,ç,è,é,ê,ë,ì,í,î,ï,ð,ñ,ò,ó,ô,õ,ö,ù,ú,û,ü,ý,ÿ", ",")
sReturn = sInput
Set c = Range("D1:G1")
For i = LBound(vaBad) To UBound(vaBad)
sReturn = Replace$(sReturn, vaBad(i), vaGood(i))
Next i
Anglicize = sReturn
'Sheets("Results").Activate
End Function