I want to check if each particular value (text&numbers) from a range 1 exists in range 2. If not, this value has to be added to the range 2.
For Each loop takes too much time. I want to try with arrays:
create an array with all values from range 1
create an array with all values from range 2
check if the element of array 1 is not empty
3.1 if not, check if the element exists in array 2
3.1.1 if yes, go to next element of array 1
3.1.2 if no:
3.1.2.1 add the element to array 2
3.1.2.1 add the element to the range 2 (in workbook)
3.2 if yes, go to next element of array 1
go to next element of array 1 and repeat third step
Public Sub Table_And_Layout()
Dim wsRoadmap As Worksheet
Dim wsBacklog As Worksheet
Dim bList As Range
Dim Arr() As Variant
Dim rListLastCol As Long
Dim TempRng As Variant
Dim element As Variant 'Range
Set wsBacklog = Sheets("Backlog")
Set wsRoadmap = Sheets("Roadmap")
Set bList = wsBacklog.Range("C7", wsBacklog.Cells(bListLast, 3))
bListLast = wsBacklog.Cells(wsBacklog.Rows.Count, "C").End(xlUp).Row
Arr = wsRoadmap.Range("C6", wsRoadmap.Cells(rListLastRow, rListLastCol))
For Each element In Arr
If Not IsEmpty(element) Then
Set TempRng = bList.Find(element.Value)
If TempRng Is Nothing Then
wsBacklog.Cells(bListLast + 1, 3).Value = wsRoadmap.Cells(element.Row, element.Column).Value
bListLast = wsBacklog.Cells(wsBacklog.Rows.Count, "C").End(xlUp).Row
End If
End If
Next element
End Sub
The OPTION 2 is the fastst one:
Public Sub Table_And_Layout()
Dim wsRoadmap As Worksheet
Dim wsBacklog As Worksheet
Dim bList As Range
Dim bListLast As Long
Dim rList As Range
Dim rListLastCol As Long
Dim TempRng As Variant
Dim element As Variant
'****************************
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
'****************************
'Remember time when macro starts
StartTime = Timer
Set wsBacklog = Sheets("Backlog")
Set wsRoadmap = Sheets("Roadmap")
' unlock sheet
wsBacklog.Unprotect
bListLast = wsBacklog.Cells(wsBacklog.Rows.Count, "C").End(xlUp).Row
Set bList = wsBacklog.Range("C7", wsBacklog.Cells(bListLast, 3))
Set rList = wsRoadmap.Range("C6:BB100")
' find last not empty column
rListLastCol = wsRoadmap.Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column
'MsgBox "Last Col: " & rListLastCol
' find last not empty row
rListLastRow = wsRoadmap.Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
'MsgBox "Last Row: " & rListLastRow
Set rList = wsRoadmap.Range("C6", wsRoadmap.Cells(rListLastRow, rListLastCol))
'MsgBox rList.Address
'OPTION 1 (works but very slow)
' ' filling backlog
' For Each element In rList
' If Not element Is Nothing Then
' Set TempRng = bList.Find(element.Value)
' If TempRng Is Nothing Then
' wsBacklog.Cells(bListLast + 1, 3).Value = wsRoadmap.Cells(element.Row, element.Column).Value
' bListLast = wsBacklog.Cells(wsBacklog.Rows.Count, "C").End(xlUp).Row
' End If
' End If
' Next element
'OPTION 2 (works fast)
' declare array for roadmap
Dim Arr() As Variant ' declare an unallocated array.
Arr = wsRoadmap.Range("C6", wsRoadmap.Cells(rListLastRow, rListLastCol)) ' Arr is now an allocated array
' Dim NumRows As Long
' Dim NumCols As Long
' MsgBox NumRows = UBound(Arr, 1) - LBound(Arr, 1) + 1
' MsgBox NumCols = UBound(Arr, 2) - LBound(Arr, 2) + 1
' declare array for backlog
Dim ArrB() As Variant
ArrB = wsBacklog.Range("C6", wsBacklog.Cells(bListLast, 3))
'filling backloga
For Each element In Arr
If Not IsEmpty(element) Then
Set TempRng = bList.Find(element)
If TempRng Is Nothing Then
wsBacklog.Cells(bListLast + 1, 3).Value = element
bListLast = wsBacklog.Cells(wsBacklog.Rows.Count, "C").End(xlUp).Row
End If
End If
Next element
'OPTION 3 (does not work)
' For i = LBound(Arr) To UBound(Arr)
' For j = LBound(ArrB) To UBound(ArrB)
' If Not IsEmpty(i) Then
' If Arr(i) = ArrB(j) Then
' wsBacklog.Cells(bListLast + 1, 3).Value = Arr(i)
' End If
' End If
' Next
' Next
'*************************************************************************************
'Determine how many seconds code took to run
SecondsElapsed = Round(Timer - StartTime, 2)
'Notify user in seconds
MsgBox "This code ran successfully in " & SecondsElapsed & " seconds", vbInformation
'*************************************************************************************
End Sub
PS. Thank you SJR!
Related
I have a dict made up of multiple items (dates) per key, looks like this
Key Items dates()
23359720 03/12/2020 , 04/12/2020, 05/12/2020
23293711 26/01/2021
How can I transfer each key & item into an array (including repeating keys for multiple items)? so that array:
23359720 03/12/2020
23359720 04/12/2020
... ....
This is the code I have:
Sub Test_Dates()
'
Dim TESTWB As Workbook
Dim TESTWS As Worksheet
Set TESTWB = ThisWorkbook
Set TESTWS = TESTWB.Worksheets("TEST")
Dim Dict As New Scripting.Dictionary
For i = 2 To TESTWS.Cells(1, 1).End(xlDown).Row
Dict.Add TESTWS.Cells(i, 1).Value, getDates(TESTWS.Cells(i, 2), TESTWS.Cells(i, 3))
Next i
For i = 0 To Dict.Count - 1
Dim DateItem As Variant
For Each DateItem In Dict.Items(i)
Debug.Print Dict.Keys(i), DateItem
Next DateItem
Next i
'Dict to Array
Dim PricingDatesArr As Variant
PricingDatesArr = Dict.Keys
PricingDatesArr = Dict.Items
End Sub
This is to get dates between 2 sets of dates (i,2) (i,3) in the code
Function getDates(ByVal StartDate As Date, ByVal EndDate As Date) As Variant
Dim varDates() As Date
Dim lngDateCounter As Long
ReDim varDates(0 To CLng(EndDate) - CLng(StartDate))
For lngDateCounter = LBound(varDates) To UBound(varDates)
varDates(lngDateCounter) = CDate(StartDate)
StartDate = CDate(CDbl(StartDate) + 1)
Next lngDateCounter
getDates = varDates
End Function
This is the third question you have posted in as many days regarding Dictionaries. You might want to take a little time out a read up on Dictionary data structures a bit more.
The code below will return a 1 to n,by 1 to 2 array from a dictionary with arrays of dates.
Option Explicit
Public Function UnpackDictionaryOfArrays(ByRef ipDict As Scripting.DIctionary) As Variant
Dim myKeys As Collection
Set myKeys = New Collection
Dim myItems As Collection
Set myItems = New Collection
Dim myKey As Variant
Dim myItem As Variant
For Each myKey In ipDict
myItem = ipDict(myKey)
If IsArray(myItem) Then
Dim myElement As Variant
For Each myElement In myItems
myKeys.Add myKey
myItems.Add myElement
Next
Else
myKeys.Add myKey
myItems.Add myItem
End If
Next
' Now compile into a single array
Dim myKeyItem As Variant
ReDim myKeyItem(1 To myKeys.Count, 1 To 2)
Dim myIndex As Long
For myIndex = 1 To myKeys.Count
myKeyItem(myIndex, 1) = myKeys(myIndex)
myKeyItem(myIndex, 2) = myItems(myIndex)
Next
UnpackDictionaryOfArrays = myKeyIndex
End Function
Dictionary With Arrays to 2D One-Based Array
' Dict to Array
' Count the number of dates.
Dim Arr As Variant
Dim pCount As Long
For Each Arr In Dict.Items
pCount = pCount + UBound(Arr) - LBound(Arr) + 1
Next Arr
' Resize the array to the number of dates.
Dim PricingDatesArr As Variant: ReDim PricingDatesArr(1 To pCount, 1 To 2)
' Write the data to the array.
Dim Key As Variant
Dim n As Long, r As Long
For Each Key In Dict.Keys
For n = LBound(Dict(Key)) To UBound(Dict(Key))
r = r + 1
PricingDatesArr(r, 1) = Key
PricingDatesArr(r, 2) = Dict(Key)(n)
Next n
Next Key
' Print the elements of the array.
For r = 1 To pCount
Debug.Print PricingDatesArr(r, 1), PricingDatesArr(r, 2)
Next r
' Copy to a range.
'SheetX.Range("A1").Resize(pCount, 2).Value = PricingDatesArr
EDIT: The Function Edition
Usage in Your Code
' Dict to Array
Dim PricingDatesArr As Variant
PricingDatesArr = UnpivotDictionaryArrays(dict)
If IsEmpty(PricingDatesArr) Then Exit Sub
' Print the elements of the array.
For r = 1 To pCount
Debug.Print PricingDatesArr(r, 1), PricingDatesArr(r, 2)
Next r
' Copy to a range.
'SheetX.Range("A1").Resize(pCount, 2).Value = PricingDatesArr
The Function
Function UnpivotDictionaryArrays( _
ByVal dict As Object) _
As Variant
Const ProcName As String = "UnpivotDictionaryArrays"
On Error GoTo ClearError
' Count the items of the source (dictionary) arrays.
Dim Item As Variant
Dim n As Long
For Each Item In dict.Items
n = n + UBound(Item) - LBound(Item) + 1
Next Item
' Write the values from the dictionary to the destination array.
Dim Data As Variant: ReDim Data(1 To n, 1 To 2)
Dim r As Long
For Each Item In dict.Keys
For n = LBound(dict(Item)) To UBound(dict(Item))
r = r + 1: Data(r, 1) = Item: Data(r, 2) = dict(Item)(n)
Next n
Next Item
UnpivotDictionaryArrays = Data
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
I'm trying to go through a list with two columns and replace some of the text in the second column. I want to search for values using wildcards in combination with a value inside a 2D Array.
I've a file with all Pokemon cards separated in different worksheets by the set they're in. There are two columns that are called "Name" and "German Name".
I created another worksheet that contains all cards and their corresponding name and German name. Out of that worksheet, I create a 2 dimensional Array. This works.
Then I've loops going on and inside that I've got this line of code.
Worksheets(table).Cells(otherI, 2).Value = Replace(Worksheets(table).Cells(otherI, 2).Value, " * " & allArray(i, 0) & " * ", " * " & allArray(i, 1) & " * ")
Somewhere there is the problem.
E.g. I've the entry "Bulbasaur Lv.5" in both columns and now I want to replace "Bulbasaur" in the second column with its German equivalent "Bisasam" but the "Lv.5" mustn't be touched.
The whole script.
Option Explicit
Sub firstMakro()
'Variables
Dim allSize As Integer
Dim allArray()
Dim allI As Integer
allI = 1
Dim otherSize As Integer
Dim otherI As Integer
otherI = 1
Dim i As Integer
Dim table As Integer
table = 2
'Create Array
allSize = WorksheetFunction.CountA(Worksheets("All_Pokemons").Columns(1))
ReDim allArray(allI To allSize, 1)
Do
allArray(allI, 0) = Worksheets("All_Pokemons").Cells(allI, 1).Value
allArray(allI, 1) = Worksheets("All_Pokemons").Cells(allI, 2).Value
allI = allI + 1
Loop Until allI > allSize
MsgBox ("Array created")
'Replace Entries
For i = LBound(allArray, 1) To UBound(allArray, 1)
MsgBox (allArray(i, 0))
otherSize = WorksheetFunction.CountA(Worksheets(table).Columns(1))
Do
Worksheets(table).Cells(otherI, 2).Value = Replace(Worksheets(table).Cells(otherI, 2).Value, " * " & allArray(i, 0) & " * ", " * " & allArray(i, 1) & " * ")
otherI = otherI + 1
Loop Until otherI > otherSize
otherI = 1
Next i
End Sub
Replace doesn't use, or in this case even need, wildcards. Use
Replace(Worksheets(table).Cells(otherI, 2).Value, allArray(i, 0), allArray(i, 1))
Range Replace
Range.Replace (Microsoft Docs)
Tested only on a small dataset (feedback on efficiency (speed) is appreciated).
It will replace each occurrence of an English name with the associated German name in the whole destination range.
Adjust the values in the constants section.
Option Explicit
Sub Germanize()
Const sName As String = "All_Pokemons"
Const sfRow As Long = 2 ' ??? First Row
Const seCol As String = "A" ' ENG
Const sgCol As String = "B" ' GER
Const dName As String = "Sheet2" ' ??? Worksheet Tab Name
Const dfRow As Long = 2 ' ??? First Row
Const deCol As String = "A" ' ENG
Const dgCol As String = "B" ' GER
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Source (All)
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim serg As Range: Set serg = RefColumn(sws.Cells(sfRow, seCol)) ' ENG
If serg Is Nothing Then Exit Sub ' no data
Dim seData As Variant: seData = GetRange(serg) ' ENG
Dim sgrg As Range: Set sgrg = serg.EntireRow.Columns(sgCol) ' GER
Dim sgData As Variant: sgData = GetRange(sgrg) ' GER
' Destination
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim derg As Range: Set derg = RefColumn(dws.Cells(dfRow, deCol)) ' ENG
If derg Is Nothing Then Exit Sub ' no data
Dim dgrg As Range: Set dgrg = derg.EntireRow.Columns(dgCol) ' GER
Application.ScreenUpdating = False
dgrg.Value = derg.Value ' write ENG column to GER column
Dim seValue As Variant
Dim r As Long
' Replace in GER column.
For r = 1 To UBound(seData, 1)
seValue = seData(r, 1)
If Not IsError(seValue) Then
If Len(seValue) > 0 Then
dgrg.Replace seValue, CStr(sgData(r, 1)), xlPart, , False
End If
End If
Next r
Application.ScreenUpdating = True
MsgBox "German pokemon names updated.", vbInformation
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the one-column range from the first cell
' of a range ('FirstCell') to the bottom-most non-empty cell
' of the first cell's worksheet column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumn( _
ByVal FirstCell As Range) _
As Range
Const ProcName As String = "RefColumn"
On Error GoTo ClearError
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set RefColumn = .Resize(lCell.Row - .Row + 1)
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
I want a cod that will find duplicates and return it in separate array.
So I found a code that would be perfect for me, but the thing is that this code is removing duplicates. I thought that it will be a simple job to change it, but somehow I cannot manage to do it....
I was thinking that it will be in this part of code If Err.Number <> 0 Then coll.Remove txt but have no idea how to change it. I have tried changing <> with = but it seems not to work.
Can someone tell me where and how should I change the code to get duplicates from 2 arrays.
Sub test()
Dim arr1 As Variant
Dim arr2 As Variant
Dim arr3 As Variant
Dim coll As Collection
Dim I As Long, j As Long, ii As Long, txt As String, x
With Worksheets("Sheet1")
LastRowColumnA = .Cells(.Rows.Count, 1).End(xlUp).Row
arr1 = .Range("A2:C" & LastRowColumnA).Value
End With
With Worksheets("Sheet2")
LastRowColumnA = .Cells(.Rows.Count, 1).End(xlUp).Row
arr2 = .Range("A2:C" & LastRowColumnA).Value
End With
Set coll = New Collection
On Error Resume Next
For I = LBound(arr1, 1) To UBound(arr1, 1)
txt = Join(Array(arr1(I, 1), arr1(I, 2), arr1(I, 3)), Chr(2))
coll.Add txt, txt
Next I
For I = LBound(arr2, 1) To UBound(arr2, 1)
txt = Join(Array(arr2(I, 1), arr2(I, 2), arr2(I, 3)), Chr(2))
Err.Clear
coll.Add txt, txt
If Err.Number <> 0 Then coll.Remove txt
Next I
ReDim arr3(1 To coll.Count, 1 To 3)
For I = 1 To coll.Count
x = Split(coll(I), Chr(2))
For ii = 0 To 2
arr3(I, ii + 1) = x(ii)
Next
Next I
Worksheets("test").Range("A2").Resize(UBound(arr3, 1), 3).Value = arr3
Columns("A:C").EntireColumn.AutoFit
End Sub
Regards,
Timonek
Extract Duplicates
If you set CountSameWorksheetDuplicates to True, it will return the duplicates of each worksheet even if they are not found in the other worksheet.
Option Explicit
Sub ExtractDuplicates()
Const sName1 As String = "Sheet1"
Const sCols1 As String = "A:C"
Const sfRow1 As Long = 2
Const sName2 As String = "Sheet2"
Const sCols2 As String = "A:C"
Const sfRow2 As Long = 2
Const dName As String = "Test"
Const dfCellAddress As String = "A2"
Const CountSameWorksheetDuplicates As Boolean = False
Dim Delimiter As String: Delimiter = Chr(2)
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sData As Variant
sData = RefColumns(wb.Worksheets(sName1).Rows(sfRow1).Columns(sCols1))
Dim cCount As Long: cCount = UBound(sData, 2)
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim r As Long
Dim sKey As Variant
For r = 1 To UBound(sData, 1)
sKey = StrJoinedDataRow(sData, r, Delimiter)
If CountSameWorksheetDuplicates Then
DictAddCount dict, sKey
Else
DictAdd dict, sKey, 1
End If
Next r
sData = RefColumns(wb.Worksheets(sName2).Rows(sfRow2).Columns(sCols2))
If CountSameWorksheetDuplicates Then
For r = 1 To UBound(sData, 1)
sKey = StrJoinedDataRow(sData, r, Delimiter)
DictAddCount dict, sKey
Next r
Else
Dim dict2 As Object: Set dict2 = CreateObject("Scripting.Dictionary")
dict2.CompareMode = vbTextCompare
For r = 1 To UBound(sData, 1)
sKey = StrJoinedDataRow(sData, r, Delimiter)
DictAdd dict2, sKey
Next r
For Each sKey In dict2.Keys
DictAddCount dict, sKey
Next sKey
Set dict2 = Nothing
End If
Erase sData
For Each sKey In dict.Keys
If dict(sKey) = 1 Then dict.Remove sKey
Next sKey
Dim drCount As Long: drCount = dict.Count
If drCount = 0 Then Exit Sub
Dim dData As Variant: ReDim dData(1 To drCount, 1 To cCount)
r = 0
Dim c As Long
For Each sKey In dict.Keys
sData = Split(sKey, Delimiter)
r = r + 1
For c = 1 To cCount
dData(r, c) = sData(c - 1)
Next c
Next sKey
Dim drg As Range
Set drg = wb.Worksheets(dName).Range(dfCellAddress).Resize(drCount, cCount)
drg.Value = dData
drg.Resize(drg.Worksheet.Rows.Count - drg.Row - drCount + 1) _
.Offset(drCount).Clear ' clear below
drg.EntireColumn.AutoFit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the range from the first row of a range
' ('FirstRowRange') to the row range containing
' the bottom-most non-empty cell in the row's columns.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumns( _
ByVal FirstRowRange As Range) _
As Range
If FirstRowRange Is Nothing Then Exit Function
With FirstRowRange.Rows(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Function ' empty range
Set RefColumns = .Resize(lCell.Row - .Row + 1)
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a row of a 2D array in a delimited string.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function StrJoinedDataRow( _
ByVal Data As Variant, _
ByVal RowIndex As Long, _
Optional ByVal Delimiter As String = " ") _
As String
Const ProcName As String = "StrJoinedDataRow"
On Error GoTo ClearError
Dim c As Long
Dim cString As String
For c = LBound(Data, 2) To UBound(Data, 2)
cString = cString & CStr(Data(RowIndex, c)) & Delimiter
Next c
StrJoinedDataRow = Left(cString, Len(cString) - Len(Delimiter))
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Adds a value ('Key') to a key of an existing ('ByRef')
' dictionary ('dict') adding another value ('Item')
' to the key's associated item.
' Remarks: Error and blank values are excluded.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub DictAdd( _
ByRef dict As Object, _
ByVal Key As Variant, _
Optional ByVal Item As Variant = Empty)
If Not IsError(Key) Then
If Len(Key) > 0 Then
dict(Key) = Item
End If
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Adds a value ('Key') to a key of an existing ('ByRef')
' dictionary ('dict') increasing its count being held
' in the key's associated item.
' Remarks: Error and blank values are excluded.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub DictAddCount( _
ByRef dict As Object, _
ByVal Key As Variant)
If Not IsError(Key) Then
If Len(Key) > 0 Then
dict(Key) = dict(Key) + 1
End If
End If
End Sub
Dim Dict as Object
Dict = CreateObject("Scripting.Dictionary")
Dim Line As Object
For Each line in MyArray
On Error Resume Next
Dict.Add(Line, "")
On Error Goto 0
Next
Dictionaries don't allow duplicate keys. We are only setting keys and ignoring the value by not setting it. The dictionary raises an error if the key exists.
I have several excel sheets with tables with a similar format to below:
Where I'd like to copy all the rows where the number under the "Value" header exceeds 10 into a blank tab.
Sub Copy_Criteria()
With Range("A5:P1000")
.AutoFilter Field:=15, Criteria1:=">10"
End With
End Sub
After that I would like to select all the values filtered here and copy them into a blank sheet. Next I'd like to repeat the whole process, but copying the rows based on another header/criteria into a second blank tab.
Thanks!
You can do something like this:
With Range("A5:P1000")
.AutoFilter Field:=15, Criteria1:=">30"
On Error Resume Next 'in case no visible cells
.SpecialCells(xlCellTypeVisible).Copy Sheet2.Range("a1")
On Error Goto 0
.Parent.ShowAllData 'clear filter
End With
Copy By Criteria
Adjust the values in the constants section.
Option Explicit
Sub CopyByCriteria()
' Needs 'RefCurrentRegionBottomRight', 'GetFilteredRange' and 'GetRange'.
Const ProcTitle As String = "CopyByCriteria"
Const sFirst As String = "A5"
Dim swsNames As Variant: swsNames = Array("Sheet1", "Sheet2", "Sheet3")
Const dFirst As String = "A1"
' These three arrays need to have the same number of elements.
Dim dwsNames As Variant: dwsNames = Array("15gt10", "12gt15")
Dim dFields As Variant: dFields = Array(15, 12)
Dim dCriteria As Variant: dCriteria = Array(">10", ">15")
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet
Dim srg As Range
Dim dws As Worksheet
Dim drg As Range
Dim dCell As Range
Dim dData As Variant
Dim n As Long
Dim IncludeHeaders As Boolean
For n = LBound(dwsNames) To UBound(dwsNames)
On Error Resume Next
Application.DisplayAlerts = False
wb.Sheets(dwsNames(n)).Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set dws = wb.Worksheets.Add(After:=wb.Sheets(wb.Sheets.Count))
dws.Name = dwsNames(n)
Set dCell = dws.Range(dFirst)
IncludeHeaders = True
For Each sws In wb.Worksheets(swsNames)
Set srg = RefCurrentRegionBottomRight(sws.Range(sFirst))
dData = GetFilteredRange( _
srg, dFields(n), dCriteria(n), IncludeHeaders, IncludeHeaders)
If Not IsEmpty(dData) Then
IncludeHeaders = False ' include only the first time
Set drg = dCell.Resize(UBound(dData, 1), UBound(dData, 2))
drg.Value = dData
Set dCell = dCell.Offset(UBound(dData, 1))
End If
Next sws
Next n
MsgBox "Worksheets created. Values copied.", vbInformation, ProcTitle
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the filtered values of a range ('rg')
' in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetFilteredRange( _
ByVal rg As Range, _
ByVal fField As Long, _
ByVal fCriteria As String, _
Optional ByVal IncludeHeaders As Boolean = True, _
Optional ByVal AllowHeadersOnly As Boolean = False) _
As Variant
' Needs the 'GetRange' function.
Const ProcName As String = "GetFilteredRange"
On Error GoTo ClearError
Dim ws As Worksheet: Set ws = rg.Worksheet
If ws.AutoFilterMode Then
ws.AutoFilterMode = False
End If
rg.AutoFilter fField, fCriteria
Dim frShift As Long: frShift = IIf(IncludeHeaders, 0, 1)
Dim frg As Range
On Error Resume Next
Set frg = rg.Resize(rg.Rows.Count - frShift) _
.Offset(frShift).SpecialCells(xlCellTypeVisible)
On Error GoTo ClearError
ws.AutoFilterMode = False
If Not frg Is Nothing Then
Dim frCount As Long
frCount = Intersect(frg, ws.Columns(frg.Column)).Cells.Count
Dim doContinue As Boolean: doContinue = True
If frShift = 0 Then
If frCount = 1 Then
If Not AllowHeadersOnly Then
doContinue = False
End If
End If
End If
If doContinue Then
Dim fcCount As Long: fcCount = frg.Columns.Count
Dim dData As Variant: ReDim dData(1 To frCount, 1 To fcCount)
Dim tData As Variant
Dim arg As Range
Dim ar As Long
Dim c As Long
Dim dr As Long
Dim trCount As Long
For Each arg In frg.Areas
tData = GetRange(arg)
For ar = 1 To arg.Rows.Count
dr = dr + 1
For c = 1 To fcCount
dData(dr, c) = tData(ar, c)
Next c
Next ar
Next arg
GetFilteredRange = dData
'Else ' frShift = 0: frCount = 1: AllowHeadersOnly = False
End If
'Else ' no filtered range
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
If rg Is Nothing Then Exit Function
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a reference to the range starting with a given cell
' and ending with the last cell of its Current Region.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefCurrentRegionBottomRight( _
ByVal FirstCellRange As Range) _
As Range
If FirstCellRange Is Nothing Then Exit Function
With FirstCellRange.Cells(1).CurrentRegion
Set RefCurrentRegionBottomRight = _
FirstCellRange.Resize(.Row + .Rows.Count - FirstCellRange.Row, _
.Column + .Columns.Count - FirstCellRange.Column)
End With
End Function
Got an array which gets records the color of cells plus the ID associated with that row, which is specific to that row.
Now I want to run through the array picking up the ID and then using that to compare to another sheet (using the ID) to see if the color of the cell has changed.
I have tried to do this in a "hack" kind of way but I don't know how to step through each array record and pickup the ID extra to check.
Sub FindColourChange()
'this first bit is getting the data and putting in array
Dim newSheet As Worksheet
Dim r As Integer
Dim c As Integer
Set newSheet = ThisWorkbook.Worksheets("Combine")
intRowsNew = newSheet.UsedRange.Rows.Count
Dim newColourArray()
ReDim Preserve newColourArray(2 To intRowsNew, 7 To 19)
For r = 2 To intRowsNew ' this is the number of rows in your range
newColourArray(r, 7) = newSheet.Cells(r, 1).Value
Debug.Print "New is " & newColourArray(r, 7) & ", "
For c = 8 To 19
newColourArray(r, c) = newSheet.Cells(r, c).Interior.ColorIndex
Debug.Print "Colour of new is " & newColourArray(r, c) & ", "
Next
Next
'HERE IS WHERE I AM HAVING ISSUES - TRYING TO GET THE DATA FROM ARRAY TO COMPARE TO THE "Old Data" SHEET but cant figure a way out to go through each individual array record and get the first column value...
Dim result As String
Dim sheet As Worksheet
Set sheet = ActiveWorkbook.Sheets("Old Data")
Dim currentRow As Integer
'result = Application.VLookup(newColourArray(r, 1), sheet.Range("A:S"), 8, False)
Sheets("Combine").Select
For r = 2 To newColourArray
Columns("A:A").Select
Selection.Find(What:=newColourArray(r, 7), After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
currentRow = ActiveCell.Row
For c = 8 To 19
If newColourArray(r, c) <> oldSheet.Cells(currentRow, c).Interior.ColorIndex Then
Sheets("Combine").Select
End If
Next
Next
End Sub
Thanks for posting as a new question. I was thinking about it yesterday and the code below might do the trick for you:
Private Const ID_COLUMN As Integer = 1
Private Const FIRST_VALUE_COLUMN As Integer = 8
Private Const LAST_VALUE_COLUMN As Integer = 19
Private Type RowFields
ItemID As Variant
ColourOfValues(LAST_VALUE_COLUMN - _
FIRST_VALUE_COLUMN) As Variant
SheetRow As Long
End Type
Private mOldSheet As Worksheet
Private mNewSheet As Worksheet
Private mOldRowFields() As RowFields
Private mNewRowFields() As RowFields
Sub RunMe()
Set mOldSheet = ThisWorkbook.Worksheets("Old Data")
Set mNewSheet = ThisWorkbook.Worksheets("Combine")
' Read the desired values
ReadIDsColoursAndValues
' Acquire the cells where there's a colour change
AcquireColourChanges
End Sub
Private Sub ReadIDsColoursAndValues()
Dim firstRow As Integer
Dim lastRow As Integer
Dim r As Long
Dim c As Integer
Dim rowIndex As Long
Dim valueIndex As Integer
' ------------------
' Read the old sheet
' ------------------
' Define the row range
firstRow = 2 ' change this if different
lastRow = mOldSheet.Cells(mOldSheet.Rows.Count, 1).End(xlUp).Row
' Redimension the RowFields array
ReDim mOldRowFields(lastRow - firstRow) ' adjust if not zero-based
' Iterate through the rows to acquire data
For r = firstRow To lastRow
' Populate the row fields object
rowIndex = r - firstRow ' adjust if not zero-based
With mOldRowFields(rowIndex)
.ItemID = mOldSheet.Cells(r, ID_COLUMN).Value2
.SheetRow = r
' Iterate through the columns to acquire the colours
For c = FIRST_VALUE_COLUMN To LAST_VALUE_COLUMN
valueIndex = c - FIRST_VALUE_COLUMN ' adjust if not zero-based
.ColourOfValues(valueIndex) = _
mOldSheet.Cells(r, c).Interior.ColorIndex
Next
End With
Next
' ------------------
' Read the new sheet
' ------------------
' Define the row range
firstRow = 2 ' change this if different
lastRow = mNewSheet.Cells(mNewSheet.Rows.Count, 1).End(xlUp).Row
' Redimension the RowFields array
ReDim mNewRowFields(lastRow - firstRow) ' adjust if not zero-based
' Iterate through the rows to acquire data
For r = firstRow To lastRow
' Populate the row fields object
rowIndex = r - firstRow ' adjust if not zero-based
With mNewRowFields(rowIndex)
.ItemID = mNewSheet.Cells(r, ID_COLUMN).Value2
.SheetRow = r
' Iterate through the columns to acquire the colours
For c = FIRST_VALUE_COLUMN To LAST_VALUE_COLUMN
valueIndex = c - FIRST_VALUE_COLUMN ' adjust if not zero-based
.ColourOfValues(valueIndex) = _
mNewSheet.Cells(r, c).Interior.ColorIndex
Next
End With
Next
End Sub
Private Sub AcquireColourChanges()
Dim rowIndex As Long
Dim refIndex As Long
Dim rowItem As RowFields
Dim refItem As RowFields
Dim valueIndex As Integer
Dim sheetColumn As Integer
Dim highlightCells As Range
Dim cell As Range
For rowIndex = LBound(mNewRowFields, 1) To UBound(mNewRowFields, 1)
rowItem = mNewRowFields(rowIndex)
' Find the matching ID RowFields from old sheet
For refIndex = LBound(mOldRowFields, 1) To UBound(mOldRowFields, 1)
refItem = mOldRowFields(refIndex)
If rowItem.ItemID = refItem.ItemID Then
' Check each value colour against the old row
For valueIndex = LBound(rowItem.ColourOfValues, 1) To _
UBound(rowItem.ColourOfValues, 1)
If rowItem.ColourOfValues(valueIndex) <> _
refItem.ColourOfValues(valueIndex) Then
' Small piece of code to highligh the cells.
' You can do anything you like at this point.
sheetColumn = valueIndex + FIRST_VALUE_COLUMN ' adjust if not zero-based
Set cell = mNewSheet.Cells(rowItem.SheetRow, sheetColumn)
If highlightCells Is Nothing Then
Set highlightCells = cell
Else
Set highlightCells = Union(highlightCells, cell)
End If
End If
Next
' ID was found so we can break the search loop
Exit For
End If
Next
Next
mNewSheet.Activate
If highlightCells Is Nothing Then
MsgBox "No values have different colours."
Else
highlightCells.Select
MsgBox "The different coloured values have been highlighted." & vbCrLf & vbCrLf & _
highlightCells.Address(False, False)
End If
End Sub