Find and Replace and Looping Though a Table - vba

I am trying to make a user friendly excel interface to replace values in a large data set using vba.
I have a small table with the Columns "Replace What" and "Replace With" and the large data set in a worksheet together.
My goal is to hit a button and have a macro use the Find function on the data to look up the number in the Replace What Column and then paste in the data from the corresponding row of the Replace With column.
Here's my code so far:
Sub ReplaceItems()
Dim replaceList As Range
Set replaceList = ListItems("Table4").ListColummns("Replace What").DataBodyRange
Dim item As Range
For Each Cell In replaceList.Cells
Cell.Offset(0, 1).Select.Copy
item = ActiveWorksheet.Find(Cell.Value)
item.Select.Paste
Next Cell
End Sub

You could use a dictionary to quickly map the Replace What key to the Replace With values. Then check if a key appears in the cell's value (you can use a combination of Index-match and InStr/RegEx, but I would probably just loop through the cells). Finally delete the key from the cell and copy in the value, you can do this in one line using Left() and Right() functions
Ex. Using a dictionary
Sub dictionary()
Dim key As String, value As String, var As Variant
Dim d As Object
Set d = CreateObject("Scripting.Dictionary")
key = "my key"
value = "my value"
d.Add key, value
d.Add key & 1, value & 1
For Each var In d.keys
MsgBox var & " : " & d.item(var)
Next var
End Sub
Ex. Replace key with value
Sub ReplaceItems()
Dim s As String, k As String, v As String, index As Integer
s = "this is my key, I think"
k = "key"
v = "value"
index = InStr(s, k)
MsgBox Left(s, index - 1) & v & Right(s, Len(s) - index - Len(k) + 1)
End Sub

I happen to have this kind of routine so I'll share.
Like what Alter posted, I used Dictionary.
Sub test()
Dim RepList As Range, RepItem As Range
Dim rng As Range, ldbase As Variant
Dim i As Long
With Sheet1 '~~> contains your table, change to suit
Set RepList = .Range("Table4[Replace What]")
End With
With Sheet2 '~~> contains your large database, change to suit
'~~> transfer your database in an array
'~~> I this example, my target is the whole column B with data.
Set rng = .Range("B2", .Range("B" & .Rows.Count).End(xlUp))
ldbase = Application.Transpose(rng) 'dumps range values to array
End With
With CreateObject("Scripting.Dictionary")
'~~> first transfer your list in Dictionary
For Each RepItem In RepList
If Not .Exists(RepItem.Value) Then
.Add RepItem.Value, RepItem.Offset(0, 1).Value
End If
Next
'~~> here is the actual find and replace
For i = LBound(ldbase) To UBound(ldbase)
If .Exists(ldbase(i)) Then ldbase(i) = .Item(ldbase(i))
Next
rng = Application.Transpose(ldbase) '~~> dumps array values to range
End With
End Sub
HTH.

Related

Replace cell references with string from a corresponding cell

I have cells with calculations.
Here is one simple example, which is in row 11.
=$V11*$AB11*AF11
I'm trying to get this:
=[EAD: On Balance Sheet]*[PD Low]*[Collateral LGD High]
These 3 strings all come from row 10, in Column V, AB, and AF.
Here is another example:
Change this:
=$V11*VLOOKUP($AA11,Rates!AQ:AU,5,FALSE)*AE11
To this:
'[EAD: On Balance Sheet]*VLOOKUP([Proposed Risk Rating],Rates!AQ:AU,5,FALSE)*[Collateral LGD Low]
All formulas are on row 11, and I want to get the corresponding headers, which are all strings, from row 10.
I'm thinking that there must be a way to do this, since Excel knows all the relevant cell references, and keeps track of everything.
I can't figure out how to replace the reference with the string (in this case the corresponding header in row 10).
I'm pretty new to this so don't have enough 'reputation' to comment and clarify your question.
If the cells V11, AB11 and AF11 have the text "EAD: On Balance Sheet", "PD Low " and "Collateral LGD High" and you want this cell to show those words.
Then the following code could work:
sub combine_words()
dim i as string
dim j as string
dim k as string
i = range("V11").value
j = range("AB11").value
k = range("AF11").value
range("A11").value = "[" & i & "]*[" & j & "]*[" & k & "]"
end sub
replace the cell A11 with whichever cell you wanted the text inputed into.
Let me know if I understood your question incorrectly and I will change the code to match your needs if I can.
Perhaps a simple find and replace in the formula would work well enough. I'm sure there are lots of edge cases I'm not thinking about. Hopefully this steers the conversation in the right direction.
Sub SOExample()
Dim mySheet As Worksheet: Set mySheet = ThisWorkbook.Sheets("Sheet1")
Dim headerRng As Range: Set headerRng = mySheet.Range("A1:J1") 'Specify where to do replacements
Dim mycell As Range
Dim vkey As Variant
Dim myDict As Object: Set myDict = CreateObject("Scripting.Dictionary")
'Iterate each header row add the address as the key, and the NEXT row's Text as the value
For Each mycell In headerRng
If Not myDict.exists(mycell.Offset(1, 0).Address) Then
myDict.Add mycell.Offset(1, 0).Address, mycell.Text
End If
Next
'Iterate each cells formula and replace it
For Each mycell In headerRng
For Each vkey In myDict.keys
mycell.Offset(1, 0).Formula = Replace(mycell.Offset(1, 0).Formula, vkey, myDict(vkey), , , vbTextCompare)
Next
Next
End Sub

Excel VBA - Grouping list of strings in one cell

I don't know how to best describe this but it's better that I explain my problem in pictures.
I have 2 worksheets:
In worksheet Array, there are certain periods with their corresponding 'Array' associated with them.
In Sheet1, there is a list of strings in the format: dd/mm/yyyy hh:mm:ss AM/PM - # ordered by ascending order of number, then by date and finally by time.
The code I have, generates those values in Sheet1 by extracting the data in Array and listing them out in one cell. The code I've used is.
Sub Filter()
Const Array_FirstRow As Integer = 2 'Indicates the first row (row 2) in Array sheet
Dim Array_RowIndex As Integer 'variable to loop through array values of col A
Dim Summary_PeriodMoment1 As String 'in worksheet Sheet 1
Array_RowIndex = Array_FirstRow
Array_LastRow = Array_RowIndex - 1
Summary_PeriodMoment1 = ""
For Array_RowIndex = Array_FirstRow To Array_LastRow
If Summary_PeriodMoment1 <> " " Then
Summary_PeriodMoment1 = Summary_PeriodMoment1 & ", " & Worksheets("Array").Cells(Array_RowIndex, Array_DateTime_Column).Value
End If
Next
Sheet1.Cells(1, 1).Value = Summary_PeriodMoment1
End Sub
This is slightly confusing and overly complicated to read. Is there any way to add code to :
Sort/group the values by # and consolidate by date (to make it less confusing)? Like so?
Have a separate cell for each value, again categorized by # (I would like to plot these values on a pivot graph later on using other code, so would like it to be in a friendly format
Essentially I would like to do some data reformatting/transposing with a VBA script. Any idea what functions I should use? thanks!
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Update: I have what I need for transposing a string of values in one cell. I wonder if this can be done for multiple cells. I tried using this code:
Sub TextToRows()
'Code for 1.2. section
Dim Arr As Variant
Dim Arr1 As Variant
Dim Arr2 As Variant
Dim InputRng As Range, InputRng2 As Range, InputRng3 As Range, OutputRng As Range, OutputRng1 As Range, OutputRng2 As Range
Set InputRng = Range("B1") 'Cell Containing all the text
Set InputRng1 = Range("B2")
Set InputRng2 = Range("B3")
Set OutputRng = Range("D1") 'First Cell of a column that you want the output there
Set OutputRng1 = Range("G1")
Set OutputRng2 = Range("J1")
Arr = Split(InputRng.Value, ",")
Arr1 = Split(InputRng.Value, ",")
Arr2 = Split(InputRng.Value, ",")
Set OutputRng = OutputRng.Resize(UBound(Arr) - LBound(Arr) + 1)
OutputRng.Value = Application.Transpose(Arr)
Set OutputRng1 = OutputRng1.Resize(UBound(Arr1) - LBound(Arr1) + 1)
OutputRng.Value = Application.Transpose(Arr1)
Set OutputRng2 = OutputRng2.Resize(UBound(Arr2) - LBound(Arr2) + 1)
OutputRng.Value = Application.Transpose(Arr2)
End Sub
Seems it only works for InputRng and not InputRng1 or InputRng2
1.
How to split comma-delimited data in one cell? (Look below)
1.1. If you don't have any other data, and number of records are not more than number of possible columns in excel then transposing within the worksheet is an option (Instead of using the code below).
1.2. (If you have more data than limit of excel columns): Otherwise, you need to use arrays. The code below answers first part of your question. It will split the cell for "," as delimiter.
2.
Then you can use Text to Columns in Data tab and delimiter ":" to get the numbers in one column and dates in another one.
3.
Use How To Transpose Cells In One Column Based On Unique Values In Another Column? to group them based on the numbers.
Sub TextToRows()
'Code for 1.2. section
Dim Arr As Variant
Dim InputRng As Range, OutputRng As Range
Set InputRng = Range("B1") 'Cell Containing all the text
Set OutputRng = Range("D1") 'First Cell of a column that you want the output there
Arr = Split(InputRng.Value, ",")
Set OutputRng = OutputRng.Resize(UBound(Arr) - LBound(Arr) + 1)
OutputRng.Value = Application.Transpose(Arr)
End Sub

VBA: Filter table data based on drop down selection

I would like to filter a table Column C, based on a drop down list selection.
I have more lines, where I can select Country code form the drop down list.
I would like to filter my table based on the country code selection.
For example:
First line: "54" country code selected
Second line "24" country code selected
And so on....
The table on the other tab will be filtered by the selected country code "54","24".
Can you please help me how can I manage it?
Thank you :)
Sub FilterRangeCriteria()
Dim vCrit As Variant
Dim wsFiltered As Worksheet
Dim wsSelection As Worksheet
Dim rngCrit As Range
Dim rngOrders As Range
Dim Lastrow As Integer
'you need more variables to save the range in an array
Dim valArr As Variant
Dim cl As Range
Dim i As Integer
Set wsFiltered = Worksheets("S") ' I want to filter this tab with "Centre Information" selection
Set wsSelection = Worksheets("Centre Information")
Set rngOrders = wsFiltered.Range("b:b") 'I want to filter this column
Lastrow = Worksheets("Centre Information").Cells(Rows.Count, 2).End(xlUp).Row
myrange = ("b3:b" & Lastrow) ' the value from B3 until last row: this will be the filter data
Set rngCrit = wsSelection.Range(myrange)
vCrit = rngCrit.Value
'I get error here: Autofilter method of range class failed
'Correction: Fill array
ReDim valArr(Lastrow - 3) 'define array size (first two rows are empty + considering the first array position starts with 0)
i = 0
For Each cl In rngCrit 'loop through range
valArr(i) = "=" & cl 'filter for each value + operator
i = i + 1
Next cl
'Correction: use array als range of numbers which shall be matched
rngOrders.AutoFilter _
Field:=1, _
Criteria1:=valArr, _
Operator:=xlFilterValues
End Sub
Comment: If you want to filter for a range, using autofilter you have provide an array which contains all values in string format. Filtering numbers requires an operator: e.g. "=", "<=", etc. Best regards.

VBA Paste Cells Stored in Dictionary to Cells in Another Worksheet

I'm trying to search a column of cells from one worksheet, find all of the unique values, and then paste those values to a column in another worksheet. So far I have code that creates a dictionary, searches through the desired column, and selects all of the unique values in that column.
Function UniqueRequest() As Long
myReqIDCol = ColSearch("id")
'Creates a dictionary filled with each unique value in the "TaskIDList" column and counts them to determine how many unique keys are in the document
Set dic = CreateObject("Scripting.Dictionary")
For i = 1 To LastRow
tmp = Cells(i, myReqIDCol).Value
If Not dic.exists(tmp) Then
dic.Add tmp, 1
End If
Next i
End Function
I also have a function that selects the sheet I want to paste the cells to and sets it up so it pastes values into each successive blank cell in the desired column.
Function ReqSheet(input_column As Integer, input_value As Long) As Long
Dim rv As Long
rv = 1
Sheets("Request Results").Activate
Do While Cells(rv, input_column).Value <> ""
rv = rv + 1
Loop
Cells(rv, input_column).Value = input_value
ReqSheet = input_value
End Function
The issue I have is that I'm not entirely sure how to relate these two. I want to call the ReqSheet function with each value of the dictionary, but everything I've tried has failed. Sorry if this is an easy fix, but I can't really find a good solution from the internet and I'm fairly new to VBA.
One of the nice things about dictionaries is that you can pull their values and keys out into an array and write it all at once to a range without looping.
Sub GetUnique()
Dim dc As Scripting.Dictionary
Dim rCell As Range
Set dc = New Scripting.Dictionary
For Each rCell In Selection.Cells
If Not dc.Exists(rCell.Value) Then
dc.Add rCell.Value, rCell.Value
End If
Next rCell
ThisWorkbook.Worksheets("Request Results").Range("A1").Resize(UBound(dc.Keys), 1).Value = _
Application.Transpose(dc.Keys)
End Sub
Use This code and change the column to whatever you want to use.
Function UniqueRequest() As Long
myReqIDCol = ColSearch("id")
'Creates a dictionary filled with each unique value in the "TaskIDList" column and counts them to determine how many unique keys are in the document
Set dic = CreateObject("Scripting.Dictionary")
For i = 1 To LastRow
tmp = Cells(i, myReqIDCol).Value
If Not dic.exists(tmp) Then
dic.Add tmp, 1
End If
Next i
For Each value in dic.keys
ReqSheet(4,value) 'I have taken column 4,you can change it to any no you want.
End Function
Something along those lines should work. You just need to replace the input_column with the proper variable or method to find the column.
Function UniqueRequest() As Long
myReqIDCol = ColSearch("id")
'Creates a dictionary filled with each unique value in the "TaskIDList" column and counts them to determine how many unique keys are in the document
Set dic = CreateObject("Scripting.Dictionary")
For i = 1 To LastRow
tmp = Cells(i, myReqIDCol).Value
If Not dic.exists(tmp) Then
dic.Add tmp, 1
End If
Next i
For each _Value in dic
ReqSheet(input_column, _Value)
Next
End Function

Make a new column without duplicates VBA?

I have a column of cells whose values are something like this:
a
a
b
b
c
c
c
c
d
e
f
f
etc.
I'm looking to take the non-duplicated values and paste them into a new column. My pseudocode for this is as follows:
ActiveSheet.Range("a1").End(xlDown).Select
aend = Selection.Row
for acol= 1 to aend
ActiveSheet.Range("b1").End(xlDown).Select
bend = Selection.Row
'if Cells(1,acol).Value <> any of the values in the range Cells(2,1).Value
'to Cells(2,bend).Value, then add the value of Cells(1,acol) to the end of
'column b.
Does my logic in this make sense? I'm not sure how to code the commented portion. If this isn't the most efficient way to do it, could someone suggest a better way? Thanks so much!
Depending on which version of Excel you are using, you can use some built-in Excel functionality to obtain what you want- the whole solution depends on your level of skill with VBA.
Excel 2003:
You can use the Advancedfilter method (documentation) of your range to obtain the unique values and copy them to your target area. Example:
With ActiveSheet
.Range("A1", .Range("A1").End(xlDown)).AdvancedFilter Action:=xlFilterCopy, CopyToRange:=.Range("B1"), Unique:=True
End With
Where B1 is the first cell of the column you wish to copy the unique values to. The only problem with this method is that the first row of the source column ("A1") will be copied to the target range even if it is duplicated. This is because the AdvancedFilter method assumes that the first row is a header.
Therefore, adding an additional code line we have:
With ActiveSheet
.Range("A1", .Range("A1").End(xlDown)).AdvancedFilter Action:=xlFilterCopy, CopyToRange:=.Range("B1"), Unique:=True
.Range("B1").Delete Shift:=xlShiftUp
End With
Excel 2007 / 2010:
You can use the same method as above, or use the RemoveDuplicates method (documentation). This is similar to the AdvancedFilter method, except that RemoveDuplicates works in-place, which means you need to make a duplicate of your source column and then perform the filtering, for example:
With ActiveSheet
.Range("A1", .Range("A1").End(xlDown)).Copy Destination:=.Range("B1")
.Range("B1", .Range("B1").End(xlDown)).RemoveDuplicates Columns:=1, Header:=xlNo
End With
The final parameter Header controls whether the first cell of the source data is copied to the destination (if it's set to true then the method similarly to the AdvancedFilter method).
If you're after a "purer" method, then you can use a VBA Collection or dictionary - I am sure that someone else will offer a solution with this.
I use a collection, which can't have duplicate keys, to get the unique items from a list. Try to add each item to a collection and ignore the errors when there's a duplicate key. Then you'll have a collection with a subset of unique values
Sub MakeUnique()
Dim vaData As Variant
Dim colUnique As Collection
Dim aOutput() As Variant
Dim i As Long
'Put the data in an array
vaData = Sheet1.Range("A1:A12").Value
'Create a new collection
Set colUnique = New Collection
'Loop through the data
For i = LBound(vaData, 1) To UBound(vaData, 1)
'Collections can't have duplicate keys, so try to
'add each item to the collection ignoring errors.
'Only unique items will be added
On Error Resume Next
colUnique.Add vaData(i, 1), CStr(vaData(i, 1))
On Error GoTo 0
Next i
'size an array to write out to the sheet
ReDim aOutput(1 To colUnique.Count, 1 To 1)
'Loop through the collection and fill the output array
For i = 1 To colUnique.Count
aOutput(i, 1) = colUnique.Item(i)
Next i
'Write the unique values to column B
Sheet1.Range("B1").Resize(UBound(aOutput, 1), UBound(aOutput, 2)).Value = aOutput
End Sub
For completeness, I'm posting the Scripting.Dictionary method: it's the commonest alternative to using a VBA.Collection and it avoids the need to rely on error-handling in normal operation.
A VBA Function using the Scripting.Dictionary Object to Return Unique Values from an Excel Range Containing Duplicates:
Option Explicit
' Author: Nigel Heffernan
' May 2012 http://excellerando.blogspot.com
' **** THIS CODE IS IN THE PUBLIC DOMAIN ****
'
' You are advised to segregate this code from
' any proprietary or commercially-confidential
' source code, and to label it clearly. If you
' fail do do so, there is a risk that you will
' impair your right to assert ownership of any
' intellectual property embedded in your work,
' or impair your employers or clients' ability
' to do so if the intellectual property rights
' in your work have been assigned to them.
'
Public Function UniqueValues(SourceData As Excel.Range, _
Optional Compare As VbCompareMethod = vbBinaryCompare _
) As Variant
Application.Volatile False
' Takes a range of values and returns a single-column array of unique items.
' The returned array is the expected data structure for Excel.Range.Value():
' a 1-based 2-Dimensional Array with dimensions 1 to RowCount, 1 to ColCount
' All values in the source are treated as text, and uniqueness is determined
' by case-sensitive comparison. To change this, set the Compare parameter to
' to 1, the value of the VbCompareMethod enumerated constant 'VbTextCompare'
' Error values in cells are returned as "#ERROR" with no further comparison.
' Empty or null cells are ignored: they do not appear in the returned array.
Dim i As Long, j As Long, k As Long
Dim oSubRange As Excel.Range
Dim arrSubRng As Variant
Dim arrOutput As Variant
Dim strKey As String
Dim arrKeys As Variant
Dim dicUnique As Object
' Note the late-binding as 'object' - best practice is to create a reference
' to the Windows Scripting Runtime: this allows you to declare dictUnique as
' Dim dictUnique As Scripting.Dictionary and instantiate it using the 'NEW'
' keyword instead of CreateObject, giving slightly better speed & stability.
If SourceData Is Nothing Then
Exit Function
End If
If IsEmpty(SourceData) Then
Exit Function
End If
Set dicUnique = CreateObject("Scripting.Dictionary")
dicUnique.CompareMode = Compare
For Each oSubRange In SourceData.Areas ' handles noncontiguous ranges
'Use Worksheetfunction.countA(oSubRange) > 0 to ignore empty ranges
If oSubRange.Cells.Count = 1 Then
ReDim arrSubRng(1 To 1, 1 To 1)
arrSubRng(1, 1) = oSubRange.Cells(1, 1).Value
Else
arrSubRng = oSubRange.Value
End If
For i = LBound(arrSubRng, 1) To UBound(arrSubRng, 1)
For j = LBound(arrSubRng, 2) To UBound(arrSubRng, 2)
If IsError(arrSubRng(i, j)) Then
dicUnique("#ERROR") = vbNullString
ElseIf IsEmpty(arrSubRng(i, j)) Then
' no action: empty cells are ignored
Else
' We use the error-tolerant behaviour of the Dictionary:
' If you query a key that doesn't exist, it adds the key
dicUnique(CStr(arrSubRng(i, j))) = vbNullString
End If
Next j
Next i
Erase arrSubRng
Next oSubRange
If dicUnique.Count = 0 Then
UniqueValues = Empty
Else
arrKeys = dicUnique.keys
dicUnique.RemoveAll
ReDim arrOutput(1 To UBound(arrKeys) + 1, 1 To 1)
For k = LBound(arrKeys) To UBound(arrKeys)
arrOutput(k + 1, 1) = arrKeys(k)
Next k
Erase arrKeys
UniqueValues = arrOutput
Erase arrOutput
End If
Set dicUnique = Nothing
End Function
A couple of notes:
This is code for any Excel range, not just the single-column range you asked for.This function tolerates cells with errors, which are difficult to handle in VBA.This isn't Reddit: you can read the comments, they are an aid to understanding and generally beneficial to your sanity.
I would use a simple array, go through all the letters and check if the letter you are on is in the array:
Sub unique_column()
Dim data() As Variant 'array that will store all of the unique letters
c = 1
Range("A1").Select
Do While ActiveCell.Value <> ""
ReDim Preserve data(1 To c) As Variant
If IsInArray(ActiveCell.Value, data()) = False Then 'we are on a new unique letter and will add it to the array
data(c) = ActiveCell.Value
c = c + 1
End If
ActiveCell.Offset(1, 0).Select
Loop
'now we can spit out the letters in the array into a new column
Range("B1").Value = "Unique letters:"
Dim x As Variant
Range("B2").Select
For Each x In data()
ActiveCell.Value = x
ActiveCell.Offset(1, 0).Select
Next x
Range("A1").Select
c = c - 1
killer = MsgBox("Processing complete!" & vbNewLine & c & "unique letters applied.", vbOKOnly)
End Sub
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = (UBound(Filter(arr, stringToBeFound)) > -1)
End Function