Related
I am currently reading a range into an array to perform a few calculations before outputting into another worksheet. My reason for using the array is speed as I am often dealing with thousands of rows.
I have one particular calculation that I am struggling with for some reason.
This is the part I am struggling with (rest of sample of this code is further down):
For i = non_rev_rows To 2 Step -1.
**' Remove Blank Rows from array
If data_range(i, 2) = "No WBS/CC" Then
If Application.WorksheetFunction.CountA(Range("C" & i & ":M" & i)) = 0 Then
Rows(i).Delete
End If
So basically when a row in column 2 is equal to "No WBS/CC" then I need to run a CountA or any other method you can recommend to calcuate the total value of columns C to M on that row. I am essentially looking for any row that = "No WBS/CC" and where columns C:M have no value. If so, then delete the entire row. If there is a value in columns C:M then I would not wish to delete the row.
'Row Count
With Sheets("array")
non_rev_rows = .Range("E" & .Rows.Count).End(xlUp).Row
End With
' Remove Blank Rows from array
' Replace "NO WBS/CC" with Co Code Over-Ride if supplied
' Set Debit / Credit
' Round to 2 decimal places
Set data = array_sheet.Range("A1:M" & non_rev_rows)
data_range = data.Value
For i = non_rev_rows To 2 Step -1.
**' Remove Blank Rows from array
If data_range(i, 2) = "No WBS/CC" Then
If Application.WorksheetFunction.CountA(Range("C" & i & ":M" & i)) = 0 Then
Rows(i).Delete
End If
' Replace "NO WBS/CC" with Co Code Over-Ride if supplied
If data_range(i, 13) <> 0 Then
data_range(i, 2) = data_range(i, 13)
End If
End If**
' Set Debit / Credit
data_range(i, 3) = Replace(data_range(i, 3), "Debit", 41)
data_range(i, 3) = Replace(data_range(i, 3), "Credit", 51)
' Round to 2 decimal places
data_range(i, 5) = WorksheetFunction.Round(data_range(i, 5), 2)
' If data_range(i, 3) = "Debit" Then
' data_range(i, 3).Value = 41
' ElseIf data_range(i, 3) = "Credit" Then
' data_range(i, 3).Value = 51
' End If
'data_range(i, 5).Value = Application.WorksheetFunction.Round(Range(data_range(i, 5)).Value, 2)
'Range("E" & i).Value = Application.WorksheetFunction.Round(Range("E" & i).Value, 2)
Next i
**' Remove Blank Rows from array
If data_range(i, 2) = "No WBS/CC" Then
If Application.WorksheetFunction.CountA(Range("C" & i & ":M" & i)) = 0 Then
Rows(i).Delete
End If
This code does not result in an error but it also does not have the desired impact. I have several rows in my test data that contain "No WBS/CC" in column 2 and zero values in columns C:M but the code is not deleting those rows.
If you want learning/understanding how an array row can be deleted (adapted for your case), please test the next way. It will return the array without deleted rows starting from "O2" of the same sheet, so the range after M:M column must be empty. You can easily adapt last code line to return wherever you need (in other sheet, other workbook...):
Sub DeleteArrayRows()
Dim array_sheet As Worksheet, non_rev_rows As Long, Data As Range, count2 As Long, data_range, arrRow, i As Long
Set array_sheet = ActiveSheet 'worksheets("array")
non_rev_rows = array_sheet.Range("E" & array_sheet.rows.count).End(xlUp).row
Set Data = array_sheet.Range("A1:M" & non_rev_rows)
data_range = Data.Value
For i = 1 To UBound(data_range)
count2 = 0
If data_range(i, 2) = "No WBS/CC" Then
With Application
arrRow = .Transpose(.Transpose(.Index(data_range, i, 0))) 'extract a slice of the row array
End With
Debug.Print Join(arrRow, ""): Stop 'just to see the joinned respecitve slice In Immediate Window
'comment it after seeing what it represents and press F5
If data_range(i, 1) <> "" Then count2 = Len(data_range(i, 1))
If Len(Join(arrRow, "")) - count2 = Len(data_range(i, 2)) Then
data_range = DeleteArrayRow_(data_range, i): i = i - 1
End If
End If
If i = UBound(data_range) Then Exit For
Next i
'drop the array (without deleted rows) in a range:
array_sheet.Range("O1").Resize(UBound(data_range), UBound(data_range, 2)).Value = data_range
End Sub
Private Function DeleteArrayRow_(arr As Variant, RowToDelete As Long) As Variant 'interesting...
'It does not work to eliminate the first array row...
Dim Rws As Long, cols As String
Rws = UBound(arr) - LBound(arr)
cols = "A:" & Split(Columns(UBound(arr, 2) - LBound(arr, 2) + 1).address(, 0), ":")(0)
DeleteArrayRow_ = Application.Index(arr, Application.Transpose(Split(Join(Application.Transpose(Evaluate("Row(1:" & _
(RowToDelete - 1) & ")"))) & " " & Join(Application.Transpose(Evaluate("Row(" & _
(RowToDelete + 1) & ":" & UBound(arr) & ")"))))), Evaluate("COLUMN(" & cols & ")"))
End Function
It is not extremely fast, I tried showing it only for didactic purpose. To see that it is and how it is possible...
Note: I did not pay attention to all at the code lines after deletion. It can be easily adapted to include that part...
You can do both tests on the array rather than partially in array and partially in the worksheet.
Only delete the row in the worksheet when you find a full match.
Public Sub Test2()
Dim data_range As Variant
Dim lRows As Long
Dim lColumns As Long
Dim lCounter As Long
data_range = Sheet1.Range("A1:M6")
' Add the data to an array
For lRows = UBound(data_range) To LBound(data_range) Step -1
'Step through the array in reverse
If data_range(lRows, 2) = "No WBS/CC" Then
'Check for the "No WBS/CC" value in the second column of the array
lCounter = 0
'Reset the counter
For lColumns = 3 To 13
If Not IsEmpty(data_range(lRows, lColumns)) Then
lCounter = lCounter + 1
End If
Next lColumns
'Check columns in the array row to see if they have data
'Add to the counter for each cell having value
If lCounter = 0 Then
Sheet1.Rows(lRows).EntireRow.Delete
End If
'If the counter is zero delete the current row in the Workbook
End If
Next lRows
End Sub
Sample data before the macro is run. The row we expected to be removed highlighted in green.
Sample data after the macro is run. The expected row has been removed.
An alternate option is to write the valid rows to a new array.
Clear the data on the worksheet, then write the new array to the worksheet.
Remove Rows
Sub DoStuff()
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets("Array")
Dim LastRow As Long: LastRow = ws.Cells(ws.Rows.Count, "E").End(xlUp).Row
Dim rg As Range: Set rg = ws.Range("A2", ws.Cells(LastRow, "M"))
Dim rCount As Long: rCount = rg.Rows.Count
Dim cCount As Long: cCount = rg.Columns.Count
Dim Data() As Variant: Data = rg.Value
Dim sr As Long
Dim dr As Long
Dim c As Long
For sr = 1 To rCount
If Not IsRowBlank(Data, sr, 3, 13) Then ' is not blank
' Replace "NO WBS/CC" with Co Code Over-Ride if supplied
If CStr(Data(sr, 1)) = "No WBS/CC" Then
If Data(sr, 13) <> 0 Then
Data(sr, 2) = Data(sr, 13)
End If
End If
' Set Debit / Credit
Data(sr, 3) = Replace(Data(sr, 3), "Debit", 41)
Data(sr, 3) = Replace(Data(sr, 3), "Credit", 51)
' Round to 2 decimal places
Data(sr, 5) = Application.Round(Data(sr, 5), 2)
' Copy source row to destination row.
dr = dr + 1
For c = 1 To cCount
Data(dr, c) = Data(sr, c)
Next c
'Else ' is blank; do nothing
End If
Next sr
' Clear bottom source data.
If dr < rCount Then
For sr = dr + 1 To rCount
For c = 1 To cCount
Data(sr, c) = Empty
Next c
Next sr
End If
rg.Value = dData
End Sub
Function IsRowBlank( _
Data() As Variant, _
ByVal DataRow As Long, _
ByVal StartColumn As Long, _
ByVal EndColumn As Long) _
As Boolean
Dim c As Long
For c = StartColumn To EndColumn
If Len(CStr(Data(DataRow, c))) > 0 Then Exit For
Next c
IsRowBlank = c > EndColumn
End Function
I managed to sync selected data from three sheets into a fourth sheet. But the data doesn't align properly after empty cells beginning with the 14th row.
Now I'm trying to use arrays to align my data better. I have 3 sheets with columns Area, Zone, Employee and 6 numeric columns for each employee.
The data in Area, Zone & Employee is repeating itself in multiple rows so I need to add the numbers for every employee to have the Employee Name displayed only once with added data in other 6 columns.
I don't really have problem with filtering the names and adding data, but I'm not sure how to do it using arrays.
Or if anyone could help me find a mistake in my code that's causing the data to not align properly, I would also appreciate it. Below is my code so far, hopefully it would help.
Private Sub cmd_button1_Click()
Dim WS1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet, ws4 As Worksheet
Set WS1 = Sheets("Sheet2")
Set ws2 = Sheets("Distribution")
Set ws3 = Sheets("Sheet3")
Set ws4 = Sheets("Sheet4")
Dim LastRow As Long
Dim R As Long, LR As Long, n As Long
Application.ScreenUpdating = False
'Getting the row number of last cell
LastRow = ws2.Range("A" & Rows.Count).End(xlUp).Row
'Deleting any previous data from destination sheet
ws2.Range("A2:AX10000").ClearContents
For i = 1 To 10
'If value in V column of the row is "" then copy the row to destination sheet
If WS1.Cells(i, "V").Value = "" Then
WS1.Range("E:E").Copy Destination:=ws2.Range("A1")
WS1.Range("F:F").Copy Destination:=ws2.Range("B1")
WS1.Range("G:G").Copy Destination:=ws2.Range("C1")
WS1.Range("A:A").Copy Destination:=ws2.Range("E1")
WS1.Range("O:O").Copy Destination:=ws2.Range("F1")
WS1.Range("P:P").Copy Destination:=ws2.Range("G1")
WS1.Range("R:R").Copy Destination:=ws2.Range("H1")
WS1.Range("S:S").Copy Destination:=ws2.Range("I1")
WS1.Range("Q:Q").Copy Destination:=ws2.Range("J1")
WS1.Range("T:T").Copy Destination:=ws2.Range("K1")
ws3.Range("E:E").Copy Destination:=ws2.Range("L1")
ws3.Range("F:F").Copy Destination:=ws2.Range("M1")
ws3.Range("G:G").Copy Destination:=ws2.Range("N1")
ws3.Range("A:A").Copy Destination:=ws2.Range("O1")
ws3.Range("S:S").Copy Destination:=ws2.Range("P1")
ws3.Range("T:T").Copy Destination:=ws2.Range("Q1")
ws3.Range("V:V").Copy Destination:=ws2.Range("R1")
ws3.Range("W:W").Copy Destination:=ws2.Range("S1")
ws3.Range("X:X").Copy Destination:=ws2.Range("T1")
ws4.Range("F:F").Copy Destination:=ws2.Range("U1")
ws4.Range("G:G").Copy Destination:=ws2.Range("V1")
ws4.Range("H:H").Copy Destination:=ws2.Range("W1")
ws4.Range("A:A").Copy Destination:=ws2.Range("X1")
ws4.Range("L:L").Copy Destination:=ws2.Range("Y1")
ws4.Range("M:M").Copy Destination:=ws2.Range("Z1")
ws4.Range("N:N").Copy Destination:=ws2.Range("AA1")
ws4.Range("O:O").Copy Destination:=ws2.Range("AB1")
ws4.Range("P:P").Copy Destination:=ws2.Range("AC1")
ws4.Range("Q:Q").Copy Destination:=ws2.Range("AD1")
End If
Next i
LR = Cells(Rows.Count, "C").End(xlUp).Row
Range("A2:AX" & LR).Sort Key1:=Range("A2"), Order1:=xlAscending
For R = 2 To LR
'Count the number of duplicates for third row
n = Application.CountIf(Columns(3), Cells(R, 3).Value)
'Sum up the values for every duplicate
Range("F" & R).Value = Evaluate("=Sum(F" & R & ":F" & R + n - 1 & ")")
Range("G" & R).Value = Evaluate("=Sum(G" & R & ":G" & R + n - 1 & ")")
Range("H" & R).Value = Evaluate("=Sum(H" & R & ":H" & R + n - 1 & ")")
Range("I" & R).Value = Evaluate("=Sum(I" & R & ":I" & R + n - 1 & ")")
Range("J" & R).Value = Evaluate("=Sum(J" & R & ":J" & R + n - 1 & ")")
Range("K" & R).Value = Evaluate("=Sum(K" & R & ":K" & R + n - 1 & ")")
Range("E" & R).Value = Evaluate("=Count(E" & R & ":E" & R + n - 1 & ")")
'Go to next value in third column
R = R + n - 1
Next R
On Error Resume Next
'Remove all duplicates
ws2.Range("$A$1:$K$7979").RemoveDuplicates Columns:=3, Header:=xlYes
On Error GoTo 0
'Fill out the table with values
Columns("A:K").AutoFit
Application.ScreenUpdating = True
Range("A1").Select
End Sub
The code above is for synchronizing the sheets with Distribution and filter the data from Sheet2, and I have 2 more buttons made to filter the other 2 sheets.
The code below is my attempt to align the data but it's not working correctly.
Sub LineEmUp()
Dim i As Long, j As Long, LR As Long
Application.ScreenUpdating = False
LR = Range("C" & Rows.Count).End(xlUp).Row
Columns("A:K").Sort Key1:=Range("A2"), _
Header:=xlYes, OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal
Columns("L:T").Sort Key1:=Range("L2"), _
Header:=xlYes, OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal
Columns("U:AD").Sort Key1:=Range("U2"), _
Header:=xlYes, OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal
i = 2
Do
If Cells(i, "C") > Cells(i, "N") And Cells(i, "C") > "" Then
Cells(i, "A").Resize(1, 10).Insert xlShiftDown
ElseIf Cells(i, "N") > Cells(i, "W") And Cells(i, "N") > "" Then
Cells(i, "L").Resize(1, 10).Insert xlShiftDown
ElseIf Cells(i, "W") > Cells(i, "C") And Cells(i, "W") > "" Then
Cells(i, "U").Resize(1, 10).Insert xlShiftDown
ElseIf Cells(i, "C") < Cells(i, "N") And Cells(i, "C") > "" Then
Cells(i, "L").Resize(1, 10).Insert xlShiftDown
ElseIf Cells(i, "N") < Cells(i, "W") And Cells(i, "N") > "" Then
Cells(i, "U").Resize(1, 10).Insert xlShiftDown
ElseIf Cells(i, "W") < Cells(i, "C") And Cells(i, "W") > "" Then
Cells(i, "A").Resize(1, 10).Insert xlShiftDown
End If
i = i + 1
Loop Until Cells(i, "C") = "" And Cells(i, "W") = ""
Application.ScreenUpdating = True
End Sub
Hope I explained it properly. Thanks
Organization (without unnecessary repetition) is always important in coding, and especially key when troubleshooting. For example, your 29 copy-paste statements can be tidied up considerably - which shows some inconsistencies.
...I sorted them by source worksheet and then by source column, and grouped them together, also pasting into columns instead of single cells.
Edit:
There's a number of "weird things" going on here that require some explanation so I know whether they're designed this way intentionally.
**See my "'<<<<<<" notes below (There are some specific questions, starting with *what happens if you don't disable screen updating, and don't ignore the errors with On Error Resume Next...?
Option Explicit
Private Sub cmd_button1_Click()
Dim WS1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet, ws4 As Worksheet
Set WS1 = Sheets("Sheet2")
Set ws2 = Sheets("Distribution")
Set ws3 = Sheets("Sheet3")
Set ws4 = Sheets("Sheet4")
'Dim LastRow As Long
Dim R As Long, LR As Long, n As Long, i As Integer
' <<<<< always ALLOW screen updating during troubleshooting, until your code
' <<<<< is functioning perfectly: It may give a clue to the problem.
'Application.ScreenUpdating = False
'Getting the row number of last cell '<<<<< variable [LastRow] is not being used.
'LastRow = ws2.Range("A" & Rows.Count).End(xlUp).Row
'Deleting any previous data from destination sheet
'ws2.Range("A2:AX10000").ClearContents
ws2.UsedRange.ClearContents ' <<<<<< instead of specifying a range, just clear what's used
For i = 1 To 10
'If value in V column of the row is "" then copy the row to destination sheet
If WS1.Cells(i, "V").Value = "" Then
WS1.Range("A:A").Copy Destination:=ws2.Range("E:E") '<<< there's no pattern to what's being copied,
WS1.Range("E:G").Copy Destination:=ws2.Range("A:C") '<<< (and in a strange criss-cross),
WS1.Range("O:S").Copy Destination:=ws2.Range("F:I") '<<< are you sure nothing's being missed?
WS1.Range("T:T").Copy Destination:=ws2.Range("K:K")
ws3.Range("A:A").Copy Destination:=ws2.Range("O:O")
ws3.Range("E:G").Copy Destination:=ws2.Range("L:N")
ws3.Range("S:T").Copy Destination:=ws2.Range("P:Q")
ws3.Range("V:X").Copy Destination:=ws2.Range("R:T")
ws4.Range("A:A").Copy Destination:=ws2.Range("X1")
ws4.Range("F:H").Copy Destination:=ws2.Range("U:W")
ws4.Range("L:Q").Copy Destination:=ws2.Range("Y:AD")
End If
Next i
LR = Cells(Rows.Count, "C").End(xlUp).Row
Range("A2:AX" & LR).Sort Key1:=Range("A2"), Order1:=xlAscending '<<<<< this could be a problem??
For R = 2 To LR
'Count the number of duplicates for third row
n = Application.CountIf(Columns(3), Cells(R, 3).Value)
'Sum up the values for every duplicate
Range("F" & R).Value = Evaluate("=Sum(F" & R & ":F" & R + n - 1 & ")") '<<<<<< this is a strange way to do this...,
Range("G" & R).Value = Evaluate("=Sum(G" & R & ":G" & R + n - 1 & ")") '<<<<<< can you explain the purpose of these lines?
Range("H" & R).Value = Evaluate("=Sum(H" & R & ":H" & R + n - 1 & ")") '<<<<<< why not just add the cells normally instead like this?
Range("I" & R).Value = Evaluate("=Sum(I" & R & ":I" & R + n - 1 & ")")
Range("J" & R).Value = Evaluate("=Sum(J" & R & ":J" & R + n - 1 & ")")
Range("K" & R).Value = Evaluate("=Sum(K" & R & ":K" & R + n - 1 & ")")
Range("E" & R).Value = Evaluate("=Count(E" & R & ":E" & R + n - 1 & ")")
'Go to next value in third column
R = R + n - 1 '<<<<< WOAH! don't change the value of R when it's being used inside a loop!!!
Next R
'On Error Resume Next '<<<<< Errors mean something - Don't ignore them! (especially during troubleshooting)
'Remove all duplicates
ws2.Range("$A$1:$K$7979").RemoveDuplicates Columns:=3, Header:=xlYes '<<< this shifts cells around, might be a problem
On Error GoTo 0
'Fill out the table with values
Columns("A:K").AutoFit
Application.ScreenUpdating = True
Range("A1").Select
End Sub
I have to massive Excel sheets (rows 7500 and 16000). I need to see what items that are in list one are NOT in list two... and what items are in list two that are NOT in list one, and then paste those results on a third sheet.
I decided to store both lists in two Collections. So far that works well. When I try to loop through the Collections to find what doesn't match my computer freezes as the file is too big.
How can I change my code so that it is quicker? I feel like there must be a better way to do this instead of looping through every i in list one and every z in list two.
Thanks!
Sub FullListCompareFSvDF()
Worksheets("FundserveFL").Activate
'Open New Collection and define every variable
Dim FSTrades As New Collection
Dim c As Long
Dim i As Long
Dim z As Long
Dim searchFor As String
'enter the items into the list. There are blank rows and so the first IF Statement is to ignore these.
' The Else Statement shows an account number as the item and an account number & balance (FS.Offset(0,6).Value) as the key
Dim FS As Range
For Each FS In Sheet1.Range("L:L")
If FS = "" Then
Else: FSTrades.Add CStr(FS.Value & " " & FS.Offset(0, 6).Value)
End If
Next
Worksheets("DatafileFL").Activate
Dim DFTrades As New Collection
'enter the items into the list. There are blank rows as well as random numbers and so the first IF Statement is to ignore these (all account numbers are greater than 10000
'"Matching" is displayed for all errors - during an error read the account number from two columns over.
' The Else Statement shows an account number as the item and an account number & balance (FS.Offset(0,6).Value) as the key
Dim DF As Range
For Each DF In Sheet2.Range("H:H")
If DF = "" Or Not IsNumeric(DF.Offset(0, 2)) Or DF < 10000 Then
ElseIf DF.Offset(0, -4) = "MATCHING" Then
DFTrades.Add CStr(DF.Offset(0, 2).Value & " " & DF.Value)
Else:
DFTrades.Add CStr(DF.Value & " " & DF.Offset(0, -2).Value)
End If
Next
'loop through the first collection. Find the first item and try to match it with the items in the second collection.
'Collection 1 Item 1... is it in Collection 2 Item 1? No - then is it in Collection 2 Item 2? When a match is found, move on to Collection 1 Item 2... If no match is found send the item to "ForInvestigation" worksheet
For i = 1 To FSTrades.Count
searchFor = FSTrades(i)
z = 0
Do
z = z + 1
If z > DFTrades.Count Then
c = c + 1
Worksheets("ForInvestigation").Activate
Cells(c, 1).Value = DFTrades(i)
Exit Do
Else:
If DFTrades(z) = searchFor Then
Exit Do
End If
End If
Loop
Next
'Clear Collections
Set FSTrades = Nothing
Set DFTrades = Nothing
End Sub
Don't Activate
Read all the relevant cells into a variant array in one step. eg:
Dim V As Variant
With Worksheets("FundserveFL")
V = .Range("L1", .Cells(.Rows.Count, "L").End(xlUp)).Resize(columnsize:=6)
End With
Create a key for your collection that can be used to see if there is a duplicate.
On Error Resume Next
For i = 1 To UBound(V, 1)
If V(i, 1) <> "" Then
FSTrades.Add Item:=CStr(V(i, 1) & " " & V(i, 6)), Key:=CStr(V(i, 1) & " " & V(i, 6))
End If
Next i
On Error Resume Next
If you similarly handle the data on your second worksheet, creating an array, adding it to the same collection after creating a key which will "error" if you try to add a duplicate, you will wind up with a collection that contains no duplicates. Populate an array with that collection, and write it to your third worksheet.
I would guess that using the above technique will increase your speed by at least a factor of ten, if not more.
EDIT:
If you want to do something other than a unique list, it is merely a matter of understanding the logic. For example, if, as in your comment, you have two arrays 1,2,3,4 and 1,3,4,5, you could do something like the following. Understand, of course, that one assumption is that there are no duplicates within either array: (If there are, that can be handled also, would just require a different logic)
Sub foo()
Dim V1, V2
Dim COL As Collection
Dim I As Long
V1 = Array(1, 2, 3, 4)
V2 = Array(1, 3, 4, 5)
Set COL = New Collection
For I = 0 To UBound(V1)
COL.Add V1(I), CStr(V1(I))
Next I
On Error Resume Next
For I = 0 To UBound(V2)
COL.Add V2(I), CStr(V2(I))
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I))
Case Is <> 0
MsgBox "Error No. " & Err.Number & vbTab & Err.Description
End Select
Next I
Stop
End Sub
When the routine stops, if you examine COL you will see it only contains 2 and 5
I have a similarly sized list of stuff, and I frequently need to create a unique list of values. I'm not sure why you want to work with two collections at once though. It is much simpler to load the data from one sheet into the collection, then loop through the other sheet to see if it already exists in the collection. Here's some of my code to help you write yours.
Dim colUniqueSNs As New Collection
On Error Resume Next
For r = 2 To Sheets("Inventory").UsedRange.Rows.Count
strSN = Sheets("Inventory").Cells(r, 6).Text
strHost = Sheets("Inventory").Cells(r, 2).Text
If Not InCollection(colUniqueSNs, strSN) Then colUniqueSNs.Add strHost, strSN
Next
On Error GoTo 0
Public Function InCollection(col As Collection, key As Variant) As Boolean
Dim obj As Variant
On Error GoTo err
InCollection = True
obj = col(key)
Exit Function
err:
InCollection = False
End Function
You are starting with ranges and you are ending with them. How about skipping the Collections at all?
Pls try this:
Sub FullListCompareFSvDF()
Dim Ran1Val As Variant, Ran1ValOffset As Variant, Ran2Val As Variant
Ran1Val = Intersect(Sheet1.Columns(12), Sheet1.UsedRange).Value
Ran2Val = Intersect(Sheet1.Columns(18), Sheet1.UsedRange).Value
Dim i As Long, j As Long
For i = 1 To UBound(ranval1)
If Len(Ran1Val(i, 1)) Then Ran1Val(i, 1) = Ran1Val(i, 1) & " " & Ran2Val(i, 1)
Next
Ran2Val = Intersect(Sheet2.Range("D:J"), Sheet2.UsedRange).Value
Dim OutputVal() As Variant
ReDim OutputVal(1 To UBound(Ran1Val) + UBound(Ran2Val), 1 To 1)
For i = 1 To UBound(Ran2Val)
If Ran2Val(i, 5) <> "" And IsNumeric(Ran2Val(i, 7)) And Ran2Val(i, 5) > 10000 Then
If Ran2Val(i, 1) = "MATCHING" Then
Ran2Val(i, 1) = CStr(Ran2Val(i, 7) & " " & Ran2Val(i, 5))
Else
Ran2Val(i, 1) = CStr(Ran2Val(i, 5) & " " & Ran2Val(i, 3))
End If
If IsNumeric(Application.Match(Ran2Val(i, 1), Ran1Val, 0)) Then
j = j + 1
OutputVal(j, 1) = Ran2Val(i, 1)
End If
Else
Ran2Val(i, 1) = ""
End If
Next
ReDim Preserve Ran2Val(1 To UBound(Ran2Val), 1 To 1)
Dim runNer As Variant
For Each runNer In Ran1Val
If Len(runNer) Then
If IsNumeric(Application.Match(runNer, Ran2Val, 0)) Then
j = j + 1
OutputVal(j, 1) = runNer
End If
End If
Next
If j > 0 Then
Worksheets("ForInvestigation").Range("A1:A" & j).Value = OutputVal
End If
End Sub
I simply gets the Range.Value inside an array. Deleting all unused values and having one dimension as (1 To 1) allowes us to use Application.Match which is one of the fastest functions in excel.
when building up the second array, we already can check for the first one and push uniques directly to the output-array.
resizing the second array (with preserve) allowes us to use this with Match too.
Finally checking the entrys of the first array against the second one and push them also inside our output-array.
now we can directly copy the values to your destination (in one step)
Note:
- You may delete the "output-range" first (a smaller list later on will not overwrite oler values.)
- I'm not able to run real checks (you may need to report errors via comment I missed out)
- this code does not check for doubles inside one list (having 1 item 2 times in list 1 but not in list 2, will print it 2 times at the end / if you need this check, then just write a comment)
Thanks for all of your help! Here is my answer. It is mostly coming from Ron's answer - I have of course added some tweaks to it.
Sub MatchFSTradesDFTrades2()
Dim V1 As Variant
Dim V2 As Variant
Dim COL As New Collection
Dim I As Long
Worksheets("DatafileFL").Activate
With Worksheets("FundserveFL")
V1 = .Range("L1", .Cells(.Rows.Count, "L").End(xlUp)).Resize(columnsize:=7)
End With
With Worksheets("DatafileFL")
V2 = .Range("F1", .Cells(.Rows.Count, "D").End(xlUp)).Resize(columnsize:=12)
End With
For I = 1 To UBound(V1)
If V1(I, 1) = " " Or Not IsNumeric(V1(I, 1)) Or V1(I, 1) < 10000 Or V1(I, 1) = "***" Or Not IsNumeric(V1(I, 3)) Or (V1(I, 5)) = "Buy-EC" Or (V1(I, 5)) = "Sell-EC" Then
Else:
COL.Add (V1(I, 1)) & " " & (V1(I, 7)), CStr(V1(I, 1)) & " " & (V1(I, 7))
End If
Next I
For I = 1 To COL.Count
Sheet3.Cells(I + 1, 1).Value = COL.Item(I)
Next
On Error Resume Next
For I = 1 To UBound(V2)
If V2(I, 1) = "MATCHING" Then
If IsNumeric(V2(I, 5)) Then
COL.Add (V2(I, 7)) & " " & V2(I, 5), CStr(V2(I, 7)) & " " & V2(I, 5)
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I, 7)) & " " & V2(I, 5)
End Select
Else: V2(I, 12) = Right(V2(I, 5), Len(V2(I, 5)) - 1)
V2(I, 12) = Format(V2(I, 12), "General Number")
COL.Add (V2(I, 7)) & " " & V2(I, 12), CStr(V2(I, 7)) & " " & V2(I, 12)
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I, 7)) & " " & V2(I, 12)
End Select
End If
ElseIf V2(I, 5) = " " Or Not IsNumeric(V2(I, 5)) Or V2(I, 5) < 10000 Or V2(I, 5) = "***" Or V2(I, 1) = "BULK" Then
Else:
If IsNumeric(V2(I, 3)) Then
COL.Add (V2(I, 5)) & " " & V2(I, 3), CStr(V2(I, 5)) & " " & V2(I, 3)
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I, 5)) & " " & V2(I, 3)
End Select
Else: V2(I, 12) = Right(V2(I, 3), Len(V2(I, 3)) - 1)
V2(I, 12) = Format(V2(I, 12), "General Number")
COL.Add (V2(I, 5)) & " " & V2(I, 12), CStr(V2(I, 5)) & " " & V2(I, 12)
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I, 5)) & " " & V2(I, 12)
End Select
End If
End If
Next
Worksheets("ForInvestigation").Activate
Cells.Clear
For I = 1 To COL.Count
Sheet3.Cells(I + 1, 1).Value = COL.Item(I)
Next
Range("A:A").Select
Selection.TextToColumns DataType:=xlDelimited, Space:=True, Other:=True
Range("A1") = "Trade ID Number"
Range("A1").Font.Bold = True
Range("B1") = "Net Balanace On Trade"
Range("B1").Font.Bold = True
End Sub
I have the following code below,
I want to get the entire row not just column 1 of the original array, how would i do this?
Sub Example1()
Dim arrValues() As Variant
Dim lastRow As Long
Dim filteredArray()
Dim lRow As Long
Dim lCount As Long
Dim tempArray()
lastRow = Sheets("Raw Data").UsedRange.Rows(Sheets("Raw Data").UsedRange.Rows.Count).Row
arrValues = Sheets("Raw Data").Range(Cells(2, 1), Cells(lastRow, 21)).Value
' First use a temporary array with just one dimension
ReDim tempArray(1 To UBound(arrValues))
For lCount = 1 To UBound(arrValues)
If arrValues(lCount, 3) = "phone" Then
lRow = lRow + 1
tempArray(lRow) = arrValues(lCount, 1)
End If
Next
' Now we know how large the filteredArray needs to be: copy the found values into it
ReDim filteredArray(1 To lRow, 1 To 1)
For lCount = 1 To lRow
filteredArray(lCount, 1) = tempArray(lCount)
Next
Sheets("L").Range("A2:U" & 1 + lRow) = filteredArray
End Sub
The ReDim statement can add records on-the-fly with the PRESERVE parameter but only into the last rank. This is a problem as the second rank of a two dimensioned array is typically considered the 'columns' while the first are the 'rows'.
The Application.Transpose can flip rows into columns and vise-versa but it has limitations. (see here and here)
A simple function to transpose without these limitations is actually very easy to build. All you really need are two arrays and two nested loops to flip them.
Sub Example1()
Dim arrVALs() As Variant, arrPHONs() As Variant
Dim v As Long, w As Long
With Sheets("Raw Data").Cells(1, 1).CurrentRegion
With .Resize(.Rows.Count - 1, 21).Offset(1, 0)
arrVALs = .Cells.Value
'array dimension check
'Debug.Print LBound(arrVALs, 1) & ":" & UBound(arrVALs, 1)
'Debug.Print LBound(arrVALs, 2) & ":" & UBound(arrVALs, 2)
'Debug.Print Application.CountIf(.Columns(3), "phone") & " phones"
End With
End With
ReDim arrPHONs(1 To UBound(arrVALs, 2), 1 To 1)
For v = LBound(arrVALs, 1) To UBound(arrVALs, 1)
If LCase(arrVALs(v, 3)) = "phone" Then
For w = LBound(arrVALs, 2) To UBound(arrVALs, 2)
arrPHONs(w, UBound(arrPHONs, 2)) = arrVALs(v, w)
Next w
ReDim Preserve arrPHONs(1 To UBound(arrPHONs, 1), _
1 To UBound(arrPHONs, 2) + 1)
End If
Next v
'there is 1 too many in the filtered array
ReDim Preserve arrPHONs(1 To UBound(arrPHONs, 1), _
1 To UBound(arrPHONs, 2) - 1)
'array dimension check
'Debug.Print LBound(arrPHONs, 1) & ":" & UBound(arrPHONs, 1)
'Debug.Print LBound(arrPHONs, 2) & ":" & UBound(arrPHONs, 2)
'Option 1: use built-in Transpose
'Worksheets("L").Range("A2:U" & UBound(arrPHONs, 2) + 1) = Application.Transpose(arrPHONs)
'Option 2: use custom my_2D_Transpose
Worksheets("L").Range("A2:U" & UBound(arrPHONs, 2) + 1) = my_2D_Transpose(arrPHONs)
End Sub
Function my_2D_Transpose(arr As Variant)
Dim a As Long, b As Long, tmp() As Variant
ReDim tmp(1 To UBound(arr, 2), 1 To UBound(arr, 1))
For a = LBound(arr, 1) To UBound(arr, 1)
For b = LBound(arr, 2) To UBound(arr, 2)
tmp(b, a) = Trim(arr(a, b))
Next b
Next a
my_2D_Transpose = tmp
End Function
So if you are in a hurry and the scope of your arrays is such that you will never reach the limits of Application.Transpose then by all means use it. If you cannot safely use transpose then use a custom function.
Ubound can return the max index value of an array, but in a multidimensional array, how would I specify WHICH dimension I want the max index of?
For example
Dim arr(1 to 4, 1 to 3) As Variant
In this 4x3 array, how would I have Ubound return 4, and how would I have Ubound return 3?
ubound(arr, 1)
and
ubound(arr, 2)
You need to deal with the optional Rank parameter of UBound.
Dim arr(1 To 4, 1 To 3) As Variant
Debug.Print UBound(arr, 1) '◄ returns 4
Debug.Print UBound(arr, 2) '◄ returns 3
More at: UBound Function (Visual Basic)
[This is a late answer addressing the title of the question (since that is what people would encounter when searching) rather than the specifics of OP's question which has already been answered adequately]
Ubound is a bit fragile in that it provides no way to know how many dimensions an array has. You can use error trapping to determine the full layout of an array. The following returns a collection of arrays, one for each dimension. The count property can be used to determine the number of dimensions and their lower and upper bounds can be extracted as needed:
Function Bounds(A As Variant) As Collection
Dim C As New Collection
Dim v As Variant, i As Long
On Error GoTo exit_function
i = 1
Do While True
v = Array(LBound(A, i), UBound(A, i))
C.Add v
i = i + 1
Loop
exit_function:
Set Bounds = C
End Function
Used like this:
Sub test()
Dim i As Long
Dim A(1 To 10, 1 To 5, 4 To 10) As Integer
Dim B(1 To 5) As Variant
Dim C As Variant
Dim sizes As Collection
Set sizes = Bounds(A)
Debug.Print "A has " & sizes.Count & " dimensions:"
For i = 1 To sizes.Count
Debug.Print sizes(i)(0) & " to " & sizes(i)(1)
Next i
Set sizes = Bounds(B)
Debug.Print vbCrLf & "B has " & sizes.Count & " dimensions:"
For i = 1 To sizes.Count
Debug.Print sizes(i)(0) & " to " & sizes(i)(1)
Next i
Set sizes = Bounds(C)
Debug.Print vbCrLf & "C has " & sizes.Count & " dimensions:"
For i = 1 To sizes.Count
Debug.Print sizes(i)(0) & " to " & sizes(i)(1)
Next i
End Sub
Output:
A has 3 dimensions:
1 to 10
1 to 5
4 to 10
B has 1 dimensions:
1 to 5
C has 0 dimensions:
UBound(myArray, 1) returns the number of rows in 2d array
UBound(myArray, 2) returns the number of columns in 2d array
However, let's go 1 step further and assume that you need the last row and last column of range, that has been written as a 2d array. That row (or column) should be converted to a 1d array. E.g. if our 2d array looks like this:
Then running the code below, will give you 2 1D arrays, that are the last column and last row:
Sub PrintMultidimensionalArrayExample()
Dim myRange As Range: Set myRange = Range("a1").CurrentRegion
Dim myArray As Variant: myArray = myRange
Dim lastRowArray As Variant: lastRowArray = GetRowFromMdArray(myArray, UBound(myArray, 1))
Dim lastColumnArray As Variant
lastColumnArray = GetColumnFromMdArray(myArray, UBound(myArray, 2))
End Sub
Function GetColumnFromMdArray(myArray As Variant, myCol As Long) As Variant
'returning a column from multidimensional array
'the returned array is 0-based, but the 0th element is Empty.
Dim i As Long
Dim result As Variant
Dim size As Long: size = UBound(myArray, 1)
ReDim result(size)
For i = LBound(myArray, 1) To UBound(myArray, 1)
result(i) = myArray(i, myCol)
Next
GetColumnFromMdArray = result
End Function
Function GetRowFromMdArray(myArray As Variant, myRow As Long) As Variant
'returning a row from multidimensional array
'the returned array is 0-based, but the 0th element is Empty.
Dim i As Long
Dim result As Variant
Dim size As Long: size = UBound(myArray, 2)
ReDim result(size)
For i = LBound(myArray, 2) To UBound(myArray, 2)
result(i) = myArray(myRow, i)
Next
GetRowFromMdArray = result
End Function
In addition to the already excellent answers, also consider this function to retrieve both the number of dimensions and their bounds, which is similar to John's answer, but works and looks a little differently:
Function sizeOfArray(arr As Variant) As String
Dim str As String
Dim numDim As Integer
numDim = NumberOfArrayDimensions(arr)
str = "Array"
For i = 1 To numDim
str = str & "(" & LBound(arr, i) & " To " & UBound(arr, i)
If Not i = numDim Then
str = str & ", "
Else
str = str & ")"
End If
Next i
sizeOfArray = str
End Function
Private Function NumberOfArrayDimensions(arr As Variant) As Integer
' By Chip Pearson
' http://www.cpearson.com/excel/vbaarrays.htm
Dim Ndx As Integer
Dim Res As Integer
On Error Resume Next
' Loop, increasing the dimension index Ndx, until an error occurs.
' An error will occur when Ndx exceeds the number of dimension
' in the array. Return Ndx - 1.
Do
Ndx = Ndx + 1
Res = UBound(arr, Ndx)
Loop Until Err.Number <> 0
NumberOfArrayDimensions = Ndx - 1
End Function
Example usage:
Sub arrSizeTester()
Dim arr(1 To 2, 3 To 22, 2 To 9, 12 To 18) As Variant
Debug.Print sizeOfArray(arr())
End Sub
And its output:
Array(1 To 2, 3 To 22, 2 To 9, 12 To 18)
Looping D3 ways;
Sub SearchArray()
Dim arr(3, 2) As Variant
arr(0, 0) = "A"
arr(0, 1) = "1"
arr(0, 2) = "w"
arr(1, 0) = "B"
arr(1, 1) = "2"
arr(1, 2) = "x"
arr(2, 0) = "C"
arr(2, 1) = "3"
arr(2, 2) = "y"
arr(3, 0) = "D"
arr(3, 1) = "4"
arr(3, 2) = "z"
Debug.Print "Loop Dimension 1"
For i = 0 To UBound(arr, 1)
Debug.Print "arr(" & i & ", 0) is " & arr(i, 0)
Next i
Debug.Print ""
Debug.Print "Loop Dimension 2"
For j = 0 To UBound(arr, 2)
Debug.Print "arr(0, " & j & ") is " & arr(0, j)
Next j
Debug.Print ""
Debug.Print "Loop Dimension 1 and 2"
For i = 0 To UBound(arr, 1)
For j = 0 To UBound(arr, 2)
Debug.Print "arr(" & i & ", " & j & ") is " & arr(i, j)
Next j
Next i
Debug.Print ""
End Sub