I have a list of lists in Excel. There is some specifications (name, age, country, etc) in the first column and values in the second column. I don't want to repeat the same specifications over and over again. What I want to show in the picture. I tried =VLOOKUP() but it did not work perfectly because the lists do not include the same specifications. How can I achieve this?
A VBA macro can generate the results, and also the list of parameters for the first column of results.
To enter this Macro (Sub), alt-F11 opens the Visual Basic Editor.
Ensure your project is highlighted in the Project Explorer window.
Then, from the top menu, select Insert/Module and
paste the code below into the window that opens.
Be sure to set the Reference as stated in the Note in the macro
To use this Macro (Sub), alt-F8 opens the macro dialog box. Select the macro by name, and RUN.
This macro generates the list with the parameter list in the first column. It could be easily rewritten to have the parameter list in the first row, if that is preferable.
Option Explicit
'Set Reference to Microsoft Scripting Runtime
Sub GroupLists()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dictParams As Dictionary
Dim sParam As String
Dim I As Long, J As Long, K As Long
Dim V As Variant
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet1")
Set rRes = wsRes.Cells(1, 5)
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=2)
End With
'Get unique list of Parameters with row number
'Also count the number of entries for number of columns in final result
J = 0
Set dictParams = New Dictionary
K = 0 'row number for parameter
For I = 1 To UBound(vSrc, 1)
J = J + 1 'column count
Do
If Not dictParams.Exists(vSrc(I, 1)) Then
K = K + 1
dictParams.Add Key:=vSrc(I, 1), Item:=K
End If
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = ""
If I > UBound(vSrc) Then Exit For
Next I
'Create results array
ReDim vRes(1 To dictParams.Count, 1 To J + 1)
'Populate Column 1
For Each V In dictParams.Keys
vRes(dictParams(V), 1) = V
Next V
'Populate the data
J = 1 'column number
For I = 1 To UBound(vSrc, 1)
J = J + 1
Do
sParam = vSrc(I, 1)
vRes(dictParams(sParam), J) = vSrc(I, 2)
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = ""
If I > UBound(vSrc) Then Exit For
Next I
'Write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
rRes.EntireColumn.Clear
rRes = vRes
End Sub
EDIT: Macro modified to reflect the "real data"
Please note: You will need to add a second worksheet for the results. I named it "Sheet2"
Option Explicit
'Set Reference to Microsoft Scripting Runtime
Sub GroupLists()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dictParams As Dictionary
Dim sParam As String
Dim I As Long, J As Long, K As Long
Dim V As Variant
Dim sDelim As String 'Differentiates each record
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2")
Set rRes = wsRes.Cells(1, 1)
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=2)
sDelim = vSrc(1, 1)
End With
'Get unique list of Parameters with row number
'Also count the number of entries for number of columns in final result
J = 0
Set dictParams = New Dictionary
K = 0 'row number for parameter
For I = 1 To UBound(vSrc, 1)
J = J + 1 'column count
Do
If Not dictParams.Exists(vSrc(I, 1)) Then
K = K + 1
dictParams.Add Key:=vSrc(I, 1), Item:=K
End If
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = sDelim
If I > UBound(vSrc) Then
Exit For
Else
I = I - 1
End If
Next I
'Create results array
ReDim vRes(1 To dictParams.Count, 1 To J + 1)
'Populate Column 1
For Each V In dictParams.Keys
vRes(dictParams(V), 1) = V
Next V
'Populate the data
J = 1 'column number
For I = 1 To UBound(vSrc, 1)
J = J + 1
Do
sParam = vSrc(I, 1)
vRes(dictParams(sParam), J) = vSrc(I, 2)
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = sDelim
If I > UBound(vSrc) Then
Exit For
Else
I = I - 1
End If
Next I
'Write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
rRes.EntireColumn.Clear
rRes = vRes
End Sub
EDIT2: This macro is a modification of the above, which lists the results in the opposite orientation. It may be more useful.
Option Explicit
'Set Reference to Microsoft Scripting Runtime
Sub GroupListsVertical()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dictParams As Dictionary
Dim sParam As String
Dim I As Long, J As Long, K As Long
Dim V As Variant
Dim sDelim As String 'Differentiates each record
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet3")
Set rRes = wsRes.Cells(1, 1)
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=2)
sDelim = vSrc(1, 1)
End With
'Get unique list of Parameters with row number
'Also count the number of entries for number of columns in final result
J = 0
Set dictParams = New Dictionary
K = 0 'column number for parameter
For I = 1 To UBound(vSrc, 1)
J = J + 1 'row count
Do
If Not dictParams.Exists(vSrc(I, 1)) Then
K = K + 1
dictParams.Add Key:=vSrc(I, 1), Item:=K
End If
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = sDelim
If I > UBound(vSrc) Then
Exit For
Else
I = I - 1
End If
Next I
'Create results array
ReDim vRes(1 To J + 1, 1 To dictParams.Count)
'Populate row 1
For Each V In dictParams.Keys
vRes(1, dictParams(V)) = V
Next V
'Populate the data
J = 1 'row number
For I = 1 To UBound(vSrc, 1)
J = J + 1
Do
sParam = vSrc(I, 1)
vRes(J, dictParams(sParam)) = vSrc(I, 2)
I = I + 1
If I > UBound(vSrc) Then Exit Do
Loop Until vSrc(I, 1) = sDelim
If I > UBound(vSrc) Then
Exit For
Else
I = I - 1
End If
Next I
'Write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
rRes.EntireColumn.Clear
rRes = vRes
rRes.EntireColumn.AutoFit
End Sub
Use following ARRAY formulas.
Cell F2 formula
=IFERROR(INDEX($B$1:$B$20,SMALL(IF($A$1:$A$20=$E2,ROW($B$1:$B$20),""),COLUMN(A:A))),"")
Cell E19 formula
=IFERROR(INDEX($B$1:$B$20,SMALL(IF($A$1:$A$20=$E2,ROW($B$1:$B$20),""),COLUMN(A:A))),"")
Press CTRL+SHIFT+ENTER to evaluate the formula as it is an array formula.
Related
I'm new to VBA.
I have this formula in Excel in a new column (U) for each row, but takes too long and crashes:
=IF(COUNTIFS($E:$E,E2,$A:$A,"<>"&A2)>0,"Yes","No")
Is there a way to make this in VBA?
Thanks
Based on my understanding of your Excel formula. You are trying to put "Yes" in column U for each row where its value in column E is found elsewhere in Column E, but only if the Column A value is different.
Here is how you would do that in VBA:
Sub Example()
Dim Sh As Worksheet
Set Sh = ActiveSheet
Dim LastRow As Integer
LastRow = Sh.Cells(Sh.Rows.Count, "E").End(xlUp).Row
Dim TargetRange As Range
Set TargetRange = Sh.Range("A2:E" & LastRow)
Dim vArr() As Variant
vArr = TargetRange.Value
Dim ColU() As Variant
ReDim ColU(1 To UBound(vArr, 1), 1 To 1)
Dim i As Long
For i = 1 To UBound(vArr, 1)
ColU(i, 1) = "No"
Dim j As Long
For j = 1 To UBound(vArr, 1)
If vArr(i, 5) = vArr(j, 5) And vArr(i, 1) <> vArr(j, 1) Then
ColU(i, 1) = "Yes"
Exit For
End If
Next
Next
Sh.Range("U2").Resize(UBound(vArr, 1)).Value = ColU
End Sub
I first take the values of range A:E into an array. Then I loop through the array checking if my statement is true. If true, "Yes", otherwise default to "No". And then I output the answers to column U.
The downside to this approach is that it is n^2 number of iterations, as I loop through the array for each row of the array. It will be inevitably slow with a very large dataset.
An improvement suggested by #ChrisNeilsen was to start the inner loop from i, cutting the iterations by half. To encorporate this idea, I set up the ColU default values in its own loop first, and then when finding duplicates, I can set both of the duplicates to "Yes" at the same time.
Sub Example()
Dim Sh As Worksheet
Set Sh = ActiveSheet
Dim LastRow As Integer
LastRow = Sh.Cells(Sh.Rows.Count, "E").End(xlUp).Row
Dim TargetRange As Range
Set TargetRange = Sh.Range("A2:E" & LastRow)
Dim vArr() As Variant
vArr = TargetRange.Value
Dim ColU() As Variant
ReDim ColU(1 To UBound(vArr, 1), 1 To 1)
Dim i As Long
For i = 1 To UBound(vArr, 1)
ColU(i, 1) = "No"
Next
For i = 1 To UBound(vArr, 1)
Dim j As Long
For j = i To UBound(vArr, 1)
If vArr(i, 5) = vArr(j, 5) And vArr(i, 1) <> vArr(j, 1) Then
ColU(i, 1) = "Yes"
ColU(j, 1) = "Yes"
Exit For
End If
Next
Next
Sh.Range("U2").Resize(UBound(vArr, 1)).Value = ColU
End Sub
Rather than a double loop (which runs in order n^2) another approach that uses a single loop would be to use a lookup instead of the inner loop (this runs in order n, although a little more complex on each iteration).
Something like
Sub Example2()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, 5).End(xlUp).Row
Dim TargetRange As Range
Set TargetRange = ws.Range(ws.Cells(2, 1), ws.Cells(LastRow, 5))
Dim vArr() As Variant
vArr = TargetRange.Value2
Dim ColU() As Variant
ReDim ColU(1 To UBound(vArr, 1), 1 To 1)
Dim i As Long
Dim j As Long
Dim rE As Range
Set rE = ws.Range(ws.Cells(2, 5), ws.Cells(LastRow, 5))
ColU(UBound(vArr, 1), 1) = "No"
For i = 1 To UBound(vArr, 1) - 1
j = 0
On Error Resume Next
j = Application.WorksheetFunction.XMatch(vArr(i, 5), rE.Offset(i, 0), 0, 1)
On Error GoTo 0
ColU(i, 1) = "No"
If j > 0 Then
If vArr(i, 1) <> vArr(j + i, 1) Then
ColU(i, 1) = "Yes"
ColU(j + i, 1) = "Yes"
End If
End If
Next
ws.Range("U2").Resize(UBound(vArr, 1)).Value = ColU
End Sub
On my hardware, a arbitary sample data set ran
Rows
double loop
this code
100
0.015
0.01
1000
0.17
0.03
10000
11.9
0.33
50000
285.0
2.0
I have an Excel Sheet where some rows may contain the same data as other rows. I need a macro to sum all the values in that column and delete all the duplicates rows, except for the first one, which contains the sum of the rest.
I have tried multiple versions of code and the code that produces the results closest to what I need looks like this, but this code contains one problem is: infinite loop.
Sub delet()
Dim b As Integer
Dim y As Worksheet
Dim j As Double
Dim k As Double
Set y = ThisWorkbook.Worksheets("Sheet1")
b = y.Cells(Rows.Count, 2).End(xlUp).Row
For j = 1 To b
For k = j + 1 To b
If Cells(j, 2).Value = Cells(k, 2).Value Then
Cells(j, 3).Value = (Cells(j, 3).Value + Cells(k, 3).Value)
Rows(k).EntireRow.Delete
k = k - 1
ElseIf Cells(j, 2).Value <> Cells(k, 2).Value Then
k = k
End If
Next
Next
End Sub
I would recommend getting the data in an array and then do the relevant operation. This is a small range and it may not affect the performance but for a larger dataset it will matter.
Is this what you are trying?
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long, i As Long, j As Long
Dim MyAr As Variant, outputAr As Variant
Dim col As New Collection
Dim itm As Variant
Dim totQty As Double
'~~> Change this to the relevant sheet
Set ws = Sheet1
With ws
'~~> Find last row of col A
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Get those value in an array
MyAr = .Range("A2:C" & lRow).Value2
'~~> Get unique collection of Fam.
For i = LBound(MyAr) To UBound(MyAr)
If Len(Trim(MyAr(i, 2))) <> 0 Then
On Error Resume Next
col.Add MyAr(i, 2), CStr(MyAr(i, 2))
On Error GoTo 0
End If
Next i
'~~> Prepare array for output
ReDim outputAr(1 To col.Count, 1 To 3)
i = 1
For Each itm In col
'~~> Get Product
For j = LBound(MyAr) To UBound(MyAr)
If MyAr(i, 2) = itm Then
outputAr(i, 1) = MyAr(i, 1)
Exit For
End If
Next j
'~~> Fam.
outputAr(i, 2) = itm
totQty = 0
'~~> Qty
For j = LBound(MyAr) To UBound(MyAr)
If MyAr(j, 2) = itm Then
totQty = totQty + Val(MyAr(j, 3))
End If
Next j
outputAr(i, 3) = totQty
i = i + 1
Next itm
'~~> Copy headers
.Range("A1:C1").Copy .Range("G1")
'~~> Write array to relevant range
.Range("G2").Resize(UBound(outputAr), 3).Value = outputAr
End With
End Sub
Output
If VBA isn't essential and you've got 365:
In cell G2 enter the formula =UNIQUE(A2:B11)
In cell I2 enter the formula =SUMIFS(C2:C11,A2:A11,INDEX(G2#,,1),B2:B11,INDEX(G2#,,2))
Remove Duplicates with Sum
Adjust the values in the constants section.
Note that if you choose the same worksheets and "A1", you will overwrite.
The Code
Option Explicit
Sub removeDupesSum()
Const sName As String = "Sheet1"
Const dName As String = "Sheet1"
Const dFirst As String = "G1"
Dim wb As Workbook: Set wb = ThisWorkbook
' Write values from Source Range to Data Array.
Dim Data As Variant
Data = wb.Worksheets(sName).Cells(1).CurrentRegion.Value
' Write unique values from Data Array to Unique Sum Dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim arr As Variant: ReDim arr(2 To UBound(Data, 1)) ' for first column
Dim n As Long: n = 1
Dim i As Long
For i = 2 To UBound(Data, 1)
If dict.Exists(Data(i, 2)) Then
dict(Data(i, 2)) = dict(Data(i, 2)) + Data(i, 3)
Else
n = n + 1
arr(n) = Data(i, 1)
dict(Data(i, 2)) = Data(i, 3)
End If
Next i
Dim Result As Variant: ReDim Result(1 To dict.Count + 1, 1 To 3)
' Write headers.
For i = 1 To 3
Result(1, i) = Data(1, i)
Next i
Erase Data
' Write 'body'.
Dim Key As Variant
i = 1
For Each Key In dict.Keys
i = i + 1
Result(i, 1) = arr(i)
Result(i, 2) = Key
Result(i, 3) = dict(Key)
Next Key
' Write values from Result Array to Destination Range.
With wb.Worksheets(dName).Range(dFirst).Resize(, 3)
.Resize(i).Value = Result
.Resize(.Worksheet.Rows.Count - .Row - i + 1).Offset(i).ClearContents
End With
End Sub
Dim pos As Range, range1 As Range, range2 As Range
Dim x As Variant, Y As Variant
Set pos = Sheets("Sheet1").Cells(5, 6)
For Each y In range1
Set pos = pos.Offset(3, 0) 'Currently setting 3 spaces between each change of Y
For Each x In range2
If y = x Then
x.Cells(, 5).Copy pos
Set pos = pos.Offset(1, 0)
End If
Next x
Next y
Instead of putting spaces of 3, I want to know how many times X is copied for each time that Y changes, how would you do this?
I was thinking a counter, but how would you reset it?
Counting Using an Array or a Dictionary
All solutions are case-insensitive, i.e. A=a.
These are 3 pairs of solutions where each first is using a second loop, while each second is using Application.CountIf as a more efficient way instead.
The second pair (solutions 3 and 4) should be the most efficient, but can only be used if the values in range1 are unique, while the others can be used in any case, the first pair (solutions 1 and 2) probably being more efficient.
Also consider the differences between retrieving the values from the array and retrieving the values from the dictionary.
The Code
Option Explicit
Sub testUniqueDictionaryLoop()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
For Each y In range1.Cells
If Not dict.Exists(y.Value) Then
dict(y.Value) = 0
For Each x In range2.Cells
If StrComp(x.Value, y.Value, vbTextCompare) = 0 Then
dict(y.Value) = dict(y.Value) + 1
End If
Next x
End If
Next y
' Write to the Immediate window.
Dim Key As Variant
For Each Key In dict.keys
Debug.Print Key, dict(Key)
Next Key
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Columns(1).Resize(dict.Count).Value = Application.Transpose(dict.keys)
.Columns(2).Resize(dict.Count).Value = Application.Transpose(dict.Items)
End With
End Sub
Sub testUniqueDictionaryCountIf()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
For Each y In range1.Cells
If Not dict.Exists(y.Value) Then
dict(y.Value) = Application.CountIf(range2, y.Value)
End If
Next y
' Write to the Immediate window.
Dim Key As Variant
For Each Key In dict.keys
Debug.Print Key, dict(Key)
Next Key
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Columns(1).Resize(dict.Count).Value = Application.Transpose(dict.keys)
.Columns(2).Resize(dict.Count).Value = Application.Transpose(dict.Items)
End With
End Sub
Sub testArrayLoop()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim Data As Variant: ReDim Data(1 To range1.Cells.CountLarge, 1 To 2)
Dim i As Long
For Each y In range1.Cells
i = i + 1
Data(i, 1) = y.Value
Data(i, 2) = 0
For Each x In range2.Cells
If StrComp(x.Value, y.Value, vbTextCompare) = 0 Then
Data(i, 2) = Data(i, 2) + 1
End If
Next x
Next y
' Write to the Immediate window.
For i = 1 To UBound(Data, 1)
Debug.Print Data(i, 1), Data(i, 2)
Next i
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Resize(UBound(Data, 1)).Value = Data
End With
End Sub
Sub testArrayCountIf()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim Data As Variant: ReDim Data(1 To range1.Cells.CountLarge, 1 To 2)
Dim i As Long
For Each y In range1.Cells
i = i + 1
Data(i, 1) = y.Value
Data(i, 2) = Application.CountIf(range2, Data(i, 1))
Next y
' Write to the Immediate window.
For i = 1 To UBound(Data, 1)
Debug.Print Data(i, 1), Data(i, 2)
Next i
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Resize(UBound(Data, 1)).Value = Data
End With
End Sub
Sub testUniqueArrayLoop()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim Data As Variant: ReDim Data(1 To range1.Cells.CountLarge, 1 To 2)
Dim i As Long
For Each y In range1.Cells
If IsError(Application.Match(y.Value, Data, 0)) Then
i = i + 1
Data(i, 1) = y.Value
Data(i, 2) = 0
For Each x In range2.Cells
If StrComp(x.Value, y.Value, vbTextCompare) = 0 Then
Data(i, 2) = Data(i, 2) + 1
End If
Next x
End If
Next y
Dim k As Long: k = i
' Write to the Immediate window.
For i = 1 To k
Debug.Print Data(i, 1), Data(i, 2)
Next i
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Resize(k).Value = Data
End With
End Sub
Sub testUniqueArrayCountIf()
Dim range1 As Range: Set range1 = Range("D2:D11")
Dim range2 As Range: Set range2 = Range("F2:H11")
Dim y As Range
Dim x As Range
Dim Data As Variant: ReDim Data(1 To range1.Cells.CountLarge, 1 To 2)
Dim i As Long
For Each y In range1.Cells
If IsError(Application.Match(y.Value, Data, 0)) Then
i = i + 1
Data(i, 1) = y.Value
Data(i, 2) = Application.CountIf(range2, Data(i, 1))
End If
Next y
Dim k As Long: k = i
' Write to the Immediate window.
For i = 1 To k
Debug.Print Data(i, 1), Data(i, 2)
Next i
' Write to a two-column range.
With Range("A2").Resize(, 2)
.Resize(Rows.Count - .Row + 1).ClearContents
.Resize(k).Value = Data
End With
End Sub
I made a collection object that will add a new counter for every y. In the second loop it increments the last element of the collection by one. At the end if you look at each element in the collection it should contain the number of times x=y per y element.
Dim pos As Range
Dim range1 As Range
Dim range2 As Range
Dim x As Range
Dim y As Range
Dim countXinY As New Collection
Set pos = Sheets("Sheet1").Cells(5, 6)
For Each y In range1
countXinY.Add 0
Set pos = pos.Offset(3, 0) 'Currently setting 3 spaces between each change of Y
For Each x In range2
If y = x Then
countXinY(countXinY.Count) = countXinY(countXinY.Count) + 1
x.Cells(, 5).Copy pos
Set pos = pos.Offset(1, 0)
End If
Next x
Next y
I am trying to iterate through an array using:-
For i = LBound(arr, 1) To UBound(arr, 1)
Debug.Print arr(i, 1)
Next i
but receive a Subscript out of range error at Debug.Print arr(i, 1) which I do not understand. The code works fine if I take out the above lines.
Sub Summarise()
Dim dict
Dim i As Long
Dim arr() As Variant
Dim n As Long
Dim ws As Worksheet
Dim ws2 As Worksheet
Set ws2 = Worksheets("Plan")
Set ws = Worksheets("Data")
dict = ws.[A1].CurrentRegion.Value
With CreateObject("Scripting.Dictionary")
For i = 1 To UBound(dict, 1)
.Item(dict(i, 1)) = .Item(dict(i, 1)) + dict(i, 5)
Next
arr = Array(.Keys, .items)
n = .Count
End With
For i = LBound(arr, 1) To UBound(arr, 1)
Debug.Print arr(i, 1)
Next i
ws2.[A1].CurrentRegion.ClearContents
ws2.[A1].Resize(n, 2).Value = Application.Transpose(arr)
End Sub
Your line arr = Array(.Keys, .items) is creating an array of arrays and not an array of those items.
i.e. Array(Array(1,2,3), Array(4,5,6))
To loop through this you would need to do something like
For i = LBound(arr, 1) To UBound(arr, 1)
For j = LBound(arr(i)) to UBound(arr(i))
Debug.Print arr(i)(j)
Next j
Next i
To avoid doing this and loop through as you're currently you could add to your array as you add to your dictionary
I have to loop search multiple ranges and find match to 100k + records. Problem is I get mismatch error when assigning value to variant Arr2(i, 1).
Dim Arr1, Arr2 As Variant
Dim Wks0, Wks1 As Worksheet
Dim i As Integer
Dim Row0, Row1 As Long
Dim C As Object
Set Wks0 = Sheets("HOST")
Set Wks1 = Sheets("OFICI_BANC_USA")
'-- Create array of range -------------------------------------------*
Row0 = Wks0.Cells(Rows.Count, "A").End(xlUp).Row
Row1 = Wks1.Cells(Rows.Count, "A").End(xlUp).Row
Arr1 = Wks1.Range("A2:A" & Row1)
'-- Loop create value on sheet OFIC_BANC_USA found value in sheet HOST -----*
For i = 1 To 5 'UBound(Arr1)
With Wks0.Range("A2:A" & Row0)
Set C = .Find(Arr1(i, 1), LookAt:=xlPart,SearchOrder:=xlByRows, SearchDirection:=xlNext)
If Not C Is Nothing Then
'ReDim Preserve Arr2(i, 1)
Arr2(i, 1) = "OK"
Else
Arr2(i, 1) = "NO"
End If
End With
Next
' Transpose new array onto worksheet -------------------------------*
Wks1.Range("B2:B6") = WorksheetFunction.Transpose(Arr2)
'Arr1 = Nothing
'Arr2 = Nothing
I think you want to deal with a two-dimensioned array for the values coming in from wks1 (since you have no choice in the matter) and a single dimensioned array to hold the OK / NO values before stuffing them back into the worksheet.
Sub t()
Dim Arr0() As Variant, Arr1() As Variant, Arr2() As Variant
Dim Wks0 As Worksheet, Wks1 As Worksheet
Dim i As Long
Dim Row0 As Long, Row1 As Long
Dim C As Range
Set Wks0 = Sheets("HOST")
Set Wks1 = Sheets("OFICI_BANC_USA")
'-- Create array of range -------------------------------------------*
Row0 = Wks0.Cells(Wks0.Rows.Count, "A").End(xlUp).Row
Row1 = Wks1.Cells(Wks1.Rows.Count, "A").End(xlUp).Row
Arr1 = Wks1.Range("A2:A" & Row1)
'-- Loop create value on sheet OFIC_BANC_USA found value in sheet HOST -----*
For i = 1 To UBound(Arr1, 1)
With Wks0.Range("A2:A" & Row0)
Set C = .Find(Arr1(i, 1), LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext)
ReDim Preserve Arr2(i) '<~~ NOTE ReDim single dimensioned array here!
If Not C Is Nothing Then
Arr2(i) = "OK"
Else
Arr2(i) = "NO"
End If
End With
Next
' Transpose new array onto worksheet -------------------------------*
Wks1.Range("B2").Resize(UBound(Arr2), 1) = WorksheetFunction.Transpose(Arr2)
End Sub
Note where I've redimmed arr2. It's going to get a value either way so you need to extend its size in preparation to receive an OK / NO.
Scripting.Dictionary
Sub tt()
Dim arr As Variant, dHOST As Object
Dim Wks0 As Worksheet, Wks1 As Worksheet
Dim i As Long, j As Long
Dim Row0 As Long, Row1 As Long
Dim c As Range, rHOST As Range
Debug.Print Timer
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Set Wks0 = Worksheets("HOST")
Set Wks1 = Sheets("OFICI_BANC_USA")
Set dHOST = CreateObject("Scripting.Dictionary")
dHOST.CompareMode = vbTextCompare
'-- Create dictionary of HOST range --------------------------
Row0 = Wks0.Cells(Wks0.Rows.Count, "A").End(xlUp).Row
arr = Wks0.Range("A2:D" & Row0).Value2
For i = LBound(arr, 1) To UBound(arr, 1)
For j = LBound(arr, 2) To UBound(arr, 2)
'If Not dHOST.Exists(arr(i, j)) Then _
dHOST.Item(arr(i, j)) = j '<~~ for first match (adds 1½ seconds)
dHOST.Item(arr(i, j)) = j '<~~ for overwrite match
Next j
Next i
'-- Create array of OFICI_BANC_USA range ----------------------
Row1 = Wks1.Cells(Wks1.Rows.Count, "A").End(xlUp).Row
arr = Wks1.Range("A2:E" & Row1).Value2
For i = LBound(arr, 1) To UBound(arr, 1)
For j = LBound(arr, 2) + 1 To UBound(arr, 2)
arr(i, j) = "NO" '<~~ seed all NO matches
Next j
Next i
'-- Loop arrayed values from sheet OFIC_BANC_USA found value in dictionary HOST values --
For i = LBound(arr, 1) To UBound(arr, 1)
If dHOST.Exists(arr(i, 1)) Then _
arr(i, dHOST.Item(arr(i, 1)) + 1) = "OK"
Next i
' Stuff it all back into worksheet -------------------------------*
With Wks1.Range("A2:E" & Row1)
.Cells = arr
End With
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
Debug.Print Timer
End Sub
200K records in column A of OFICI_BANC_USA worksheet
4 columns # 50K rows each in HOSTS worksheet
~76% match rate
14.73 seconds start-to-finish
In addition to #VincentG's comment, you need to explicitly state which Rows you're using. Also, I uncommented the ReDim, and it seems to be working now:
Sub t()
Dim Arr0() As Variant, Arr1() As Variant, Arr2() As Variant
Dim Wks0 As Worksheet, Wks1 As Worksheet
Dim i As Integer
Dim Row0 As Long, Row1 As Long
Dim C As Object
Set Wks0 = Sheets("HOST")
Set Wks1 = Sheets("OFICI_BANC_USA")
'-- Create array of range -------------------------------------------*
Row0 = Wks0.Cells(Wks0.Rows.Count, "A").End(xlUp).Row
'Arr0 = Wks0.Range("A2:A" & Row0)
Row1 = Wks1.Cells(Wks1.Rows.Count, "A").End(xlUp).Row
Arr1 = Wks1.Range("A2:A" & Row1)
'-- Loop create value on sheet OFIC_BANC_USA found value in sheet HOST -----*
For i = 1 To 5 'UBound(Arr1)
With Wks0.Range("A2:A" & Row0)
Set C = .Find(Arr1(i, 1), LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext)
If Not C Is Nothing Then
ReDim Preserve Arr2(i, 1)
Arr2(i, 1) = "OK"
Else
Arr2(i, 1) = "NO"
End If
End With
Next
' Transpose new array onto worksheet -------------------------------*
Wks1.Range("B2:B6") = WorksheetFunction.Transpose(Arr2)
'Arr0 = Nothing
'Arr1 = Nothing
'Arr2 = Nothing
End Sub
I think I am understanding what you are trying to do. I set my two sheets up like this:
Then using the following code:
Sub jorge()
Application.ScreenUpdating = False
Dim Arr1 As Variant, Arr2 As Variant, Arr3 As Variant
Dim Wks0 As Worksheet, Wks1 As Worksheet
Dim i As Long, j As Long, k As Long
Dim Row0 As Long, Row1 As Long
Set Wks0 = Sheets("HOST")
Set Wks1 = Sheets("OFICI_BANC_USA")
'-- Create array of range -------------------------------------------*
Row0 = Wks0.Cells(Rows.Count, "A").End(xlUp).Row
Row1 = Wks1.Cells(Rows.Count, "A").End(xlUp).Row
Arr1 = Wks1.Range("A2:A" & Row1)
ReDim Arr2(1 To Row1, 1 To 4)
Arr3 = Wks0.Range("A2:D" & Row0)
'-- Loop create value on sheet OFIC_BANC_USA found value in sheet HOST -----*
For i = 1 To UBound(Arr1, 1)
For j = 1 To UBound(Arr3, 2)
Arr2(i, j) = "NO"
For k = 1 To UBound(Arr3, 1)
If Arr3(k, j) = Arr1(i, 1) Then
Arr2(i, j) = "OK"
Exit For
End If
Next k
Next j
Next i
Wks1.Range("B2").Resize(Row1, 4).value = Arr2
Application.ScreenUpdating = true
End Sub
I get this:
This formula will do the same thing, put this in B2:
=IF(ISNUMBER(MATCH($A2,HOST!A:A,0)),"OK","NO")
Copy across and down. This may be prohibitive with the sheer number of calculations, but it is here if you want to try it.