When I paste my array as such,
Set rngPaste = wksSkillsDist.Cells(iStartRow, iFirstColTotal)
rngPaste.Resize(UBound(arrTotals, 1), UBound(arrTotals, 2)) = arrTotals
I get #N/A values that are outside the bounds of my array. In my array, there are no #N/A values.
This is how I declare my Arrray
With wksSkillsDist
'get last Column
iColLastCategory = .Cells(iStartRow - 1, 2).End(xlToRight).Column
'Create array which the indicies match the cells where values will go
ReDim arrTotals(iStartRow To .Cells(iStartRow, iSkillCodeColumn).End(xlDown).Row, 2 To iColLastCategory) As Variant
End With
Here is an example of how items are added to arrTotals. Basically, check to see if certain strings match. If they match then I increment the corresponding spot in the array:
For iColumn = iFirstColPrimary To iLastColPrimary
If szLevel = "Mastered" Then
If InStr(1, wksSkillsDist.Cells(iHeaderRow - 1, iColumn), "Mastered", vbTextCompare) <> 0 And _
StrComp(wksSkillsDist.Cells(iHeaderRow - 2, iColumn).Text, szELM) = 0 And bMasterMatch = False Then
iHeaderCol = iColumn
bMasterMatch = True
iTotal = iTotal + 1
End If
ElseIf szLevel = "Developing" Then
If InStr(1, wksSkillsDist.Cells(iHeaderRow - 1, iColumn), "Developing", vbTextCompare) <> 0 And _
StrComp(wksSkillsDist.Cells(iHeaderRow - 2, iColumn).Text, szELM) = 0 And bMasterMatch = False Then
iHeaderCol = iColumn
bDevelopingMatch = True
iTotal = iTotal + 1
End If
End If
Next iColumn
If bMasterMatch = True Or bPerformingMatch = True Or bDevelopingMatch = True Then
If iTotal > 1 Then
Debug.Print "ERROR"
End If
arrTotals(iSkillRow, iHeaderCol) = arrTotals(iSkillRow, iHeaderCol) + 1
End If
When I paste my values on the sheet using a Loop like such, I get no #N/A Values
'for first y coordinate to last y coordinate in array
For iRow = LBound(arrTotals, 1) To UBound(arrTotals, 1)
'for first x coordinate to last x coordinate in array
For iColumn = LBound(arrTotals, 2) To UBound(arrTotals, 2)
'Add items to SkillDist worksheet
wksSkillsDist.Cells(iRow, iColumn).Value = arrTotals(iRow, iColumn)
Next iColumn
Next iRow
Why is this happening?
Range Size Larger Than Array Size (#N/A)
A Quick Fix
Your array is not a one-based array i.e. its 'LBounds' are not 1 but iStartRow and 2.
Your code is trying to fit the values of the array into a larger range a number of times i.e. e.g. assuming the number of columns is equal, if you're trying to fit an array of 3 rows into a range of 8 rows, it can't be done. If it were 6 or 9 rows, the array would have been written two or three times respectively.
Of course, you want to fit it one time into the correct range. Study the material following this solution.
rngPaste.Resize(UBound(arrTotals, 1) - LBound(arrTotals, 1) + 1, _
UBound(arrTotals, 2) - LBound(arrTotals, 2) + 1) = arrTotals
Any-Based
The number of rows (1st dimension) of any 2D array is calculated in the following way:
Dim rCount as long: rCount = UBound(Data, 1) - LBound(Data, 1) + 1
Similarly, the number of columns (2nd dimension) of any 2D array is calculated in the following way:
Dim cCount as long: cCount = UBound(Data, 2) - LBound(Data, 2) + 1
One-Based
Conveniently, to write the values of a range to a 2D one-based array, if there are at least two cells, one can simply do:
Dim rg As Range: Set rg = Sheet1.Range("A1:J10")
Dim Data As Variant: Data = rg.Value
Conveniently, using what we learned at the beginning, the number of rows in this 2D one-based array is equal to its UBound (since LBound = 1):
Dim rCount As Long: rCount = Ubound(Data, 1) - 1 + 1 ' or...
rCount = Ubound(Data, 1)
Similarly, the number of columns in this 2D one-based array is equal to its UBound (since LBound = 1):
Dim cCount As Long: cCount = Ubound(Data, 2) - 1 + 1 ' or...
cCount = Ubound(Data, 2)
A Small Study
Copy the following code into a standard module, e.g. Module1, of a new workbook. Play with (modify) the constants.
Note that if you set rStart and cStart to 1, the correct result will show in any case. This isn't covered because it would too much complicate the code.
Option Explicit
Sub RangeVsArray()
Const ProcName As String = "RangeVsArray"
' Imagine these are the results of you 'Range.End property' business.
Const rStart As Long = 6
Const rEnd As Long = 8
Const cStart As Long = 2
Const cEnd As Long = 14
Dim Data As Variant: ReDim Data(rStart To rEnd, cStart To cEnd)
' ... i.e.
'ReDim Data(6 To 8, 2 To 14)
' Populate the array (not relevant).
Dim r As Long, c As Long
For r = LBound(Data, 1) To UBound(Data, 1)
For c = LBound(Data, 2) To UBound(Data, 2)
Data(r, c) = r * c
Next c
Next r
Sheet1.Cells.Clear
Dim dCell As Range: Set dCell = Sheet1.Range("A1")
Dim drg As Range
Dim rCount As Long
Dim cCount As Long
' Wrong:
Set drg = dCell.Resize(UBound(Data, 1), UBound(Data, 2))
drg.Value = Data
Dim msg As Long
msg = MsgBox("This is wrong. Do you want to see the correct result?", _
vbYesNo + vbExclamation, ProcName)
If msg = vbYes Then
drg.Clear
' Correct...
rCount = rEnd - rStart + 1 ' it's not rEnd (Ubound(Data, 1))
cCount = cEnd - cStart + 1 ' it's not cEnd (Ubound(Data, 2))
' ... i.e.:
'Dim rCount As Long: rCount = UBound(Data, 1) - LBound(Data, 1) + 1
'Dim cCount As Long: cCount = UBound(Data, 2) - LBound(Data, 2) + 1
Set drg = dCell.Resize(rCount, cCount)
drg.Value = Data
MsgBox "You are watching the correct result.", vbInformation, ProcName
Else
MsgBox "You are watching the wrong result.", vbInformation, ProcName
End If
End Sub
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
There are some questions about splitting arrays but in my case something goes wrong when I want to output the arrays onto the Worksheet. Also, my solution seems a bit complicated.
My goal is to split a 1-dimensional array with 2.5 mln elements in 4 parts to be able to easily output it to a Worksheet (625,000 rows, 4 columns).
SomeSub() is where the data originates, in this case "i Mod 99" generates some "random" numbers to see some output on the Worksheet. SomeSub() calls the sub SplitArray() which is very the splitting happens. I only have data in a 1-dimensional array but I thought I need to use a 2-dimensional one so that I can get the values from columns into rows by transposing them. Not sure this is actually needed but it works to some degree.
Sub SomeSub()
Dim i As Long
Dim bigarr(1, 2500000) As Integer
Dim timing As Single
timing = Timer
For i = 1 To 2500000
bigarr(1, i) = i Mod 99
Next i
Call SplitArray(bigarr)
Debug.Print Format(Timer - timing, "0.0") & " seconds"
End Sub
Sub SplitArray(ByRef arr0() As Integer)
Dim i As Long
Dim arr1(1, 625000) As Integer
Dim arr2(1, 625000) As Integer
Dim arr3(1, 625000) As Integer
Dim arr4(1, 625000) As Integer
For i = 1 To 625000: arr1(1, i) = arr0(1, i): Next i
For i = 625001 To 1250000: arr2(1, i - 625000) = arr0(1, i): Next i
For i = 1250001 To 1875000: arr3(1, i - 1250000) = arr0(1, i): Next i
For i = 1875001 To 2500000: arr4(1, i - 1875000) = arr0(1, i): Next i
Dim vektor As Variant
Worksheets("Output").Select
vektor = Application.WorksheetFunction.Transpose(arr1)
Range(Cells(11, 1), Cells(625010, 1)).Value = vektor
vektor = Application.WorksheetFunction.Transpose(arr2)
Range(Cells(11, 2), Cells(625010, 2)).Value = vektor
vektor = Application.WorksheetFunction.Transpose(arr3)
Range(Cells(11, 3), Cells(625010, 3)).Value = vektor
vektor = Application.WorksheetFunction.Transpose(arr4)
Range(Cells(11, 4), Cells(625010, 4)).Value = vektor
End Sub
The problem is that my approach works only until row 35186 but not until row 625,010.
Currently the whole procedure takes about 1.9 seconds using 1 thread. This is usually fast enough but a quicker or simpler solution to splitting a "long array" would also be appreciated.
Change your arrays to be vertical instead of horizontal and you can avoid Application.Transpose which has a limit to the number of items it allows:
Sub SplitArray(ByRef arr0() As Integer)
Dim i As Long
Dim arr1(625000, 1) As Integer
Dim arr2(625000, 1) As Integer
Dim arr3(625000, 1) As Integer
Dim arr4(625000, 1) As Integer
For i = 1 To 625000: arr1(i, 1) = arr0(1, i): Next i
For i = 625001 To 1250000: arr2(i - 625000, 1) = arr0(1, i): Next i
For i = 1250001 To 1875000: arr3(i - 1250000, 1) = arr0(1, i): Next i
For i = 1875001 To 2500000: arr4(i - 1875000, 1) = arr0(1, i): Next i
With Worksheets("Output")
.Range(.Cells(11, 1), .Cells(625010, 1)).Value = arr1
.Range(.Cells(11, 2), .Cells(625010, 2)).Value = arr2
.Range(.Cells(11, 3), .Cells(625010, 3)).Value = arr3
.Range(.Cells(11, 4), .Cells(625010, 4)).Value = arr4
End With
End Sub
But you really only need one output array with 4 columns:
Sub SplitArray(ByRef arr0() As Integer)
Dim i As Long
Dim arr1(625000, 4) As Integer
For i = 1 To 625000: arr1(i, 1) = arr0(1, i): Next i
For i = 625001 To 1250000: arr1(i - 625000, 2) = arr0(1, i): Next i
For i = 1250001 To 1875000: arr1(i - 1250000, 3) = arr0(1, i): Next i
For i = 1875001 To 2500000: arr1(i - 1875000, 4) = arr0(1, i): Next i
With Worksheets("Output")
.Range(.Cells(11, 1), .Cells(625010, 4)).Value = arr1
End With
End Sub
Split a 1D Array
Simple
The GetSplitOneD function will return the columns in a 2D one-based array whose values can easily be written (copied) to a range e.g.:
rg.Resize(UBound(Data, 1), UBound(Data, 2)).Value = Data
Sub GetSplitOneDtest()
Dim Timing As Double: Timing = Timer
Const nCount As Long = 2500000
Const ColumnsCount As Long = 4
Const dName As String = "Output"
Const dFirstCellAddress As String = "A2"
' Reference the workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim n As Long
' Create the source (sample) array i.e. return the numbers
' from 1 to 'nCount' in a 1D one-based array.
Dim sArr() As Variant: ReDim sArr(1 To nCount)
For n = 1 To nCount
sArr(n) = n
Next n
' Using the 'GetSplitOneD' function, return the split values
' from the source array in the destination array ('dData'),
' a 2D one-based array.
Dim dData() As Variant: dData = GetSplitOneD(sArr, ColumnsCount)
' Write the destination rows count to a variable ('drCount').
Dim drCount As Long: drCount = UBound(dData, 1)
' Reference the destination worksheet ('dws').
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
' Reference the destination first cell ('dfCell').
Dim dfCell As Range: Set dfCell = dws.Range(dFirstCellAddress)
' Calculate the destination clear rows count ('dcrCount'),
' the number of rows to be cleared below the destination ranges.
Dim dcrCount As Long: dcrCount = dws.Rows.Count - dfCell.Row - drCount + 1
' Write the values from the destination array to the destination ranges.
With dfCell.Resize(, ColumnsCount) ' reference the first row
.Resize(drCount).Value = dData ' write
.Resize(dcrCount).Offset(drCount).Clear ' clear below
End With
Debug.Print Format(Timer - Timing, "0.000") & " seconds"
' Inform.
MsgBox "Data split.", vbInformation
End Sub
Function GetSplitOneD( _
SourceOneD() As Variant, _
ByVal ColumnsCount As Long) _
As Variant()
Dim sCount As Long: sCount = UBound(SourceOneD) - LBound(SourceOneD) + 1
Dim drCounts() As Long: ReDim drCounts(1 To ColumnsCount)
Dim drCount As Long: drCount = Int(sCount / ColumnsCount)
Dim Remainder As Long: Remainder = sCount Mod ColumnsCount
If Remainder > 0 Then
drCount = drCount + 1
drCounts(ColumnsCount) = drCount - ColumnsCount + Remainder
Else
drCounts(ColumnsCount) = drCount
End If
Dim c As Long
For c = 1 To ColumnsCount - 1: drCounts(c) = drCount: Next c
Dim dData() As Variant: ReDim dData(1 To drCount, 1 To ColumnsCount)
Dim s As Long: s = LBound(SourceOneD)
Dim dr As Long
For c = 1 To ColumnsCount
For dr = 1 To drCounts(c)
dData(dr, c) = SourceOneD(s)
s = s + 1
Next dr
Next c
GetSplitOneD = dData
End Function
More Flexible
The GetJaggedSplitOneD function will return the columns in a jagged array containing as many 2D one-based one-column arrays as there are columns. Then you could write each column to another place instead of writing them next to each other. In the test sub, you could change the value of the dcGap constant determining how many empty columns should be in-between. If you don't need this additional functionality, use the first function since it's a little bit faster.
Sub GetJaggedSplitOneDtest()
Dim Timing As Double: Timing = Timer
' Define constants.
Const nCount As Long = 2500000
Const ColumnsCount As Long = 4
Const dName As String = "Output"
Const dFirstCellAddress As String = "A2"
Const dcGap As Long = 2 ' empty columns in-between
' Reference the workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim n As Long
' Create the source (sample) array i.e. return the numbers
' from 1 to 'nCount' in a 1D one-based array.
Dim sArr() As Variant: ReDim sArr(1 To nCount)
For n = 1 To nCount
sArr(n) = n
Next n
' Using the 'GetJaggedSplitOneD' function, return the split values
' from the source array in the destination array ('dJAG'), a jagged array
' containing 4 ('ColumnsCount') same-sized 2D one-based one-column arrays.
Dim dJag() As Variant: dJag = GetJaggedSplitOneD(sArr, ColumnsCount)
' Write the destination rows count to a variable ('drCount').
Dim drCount As Long: drCount = UBound(dJag(1), 1)
' Reference the destination worksheet ('dws').
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
' Reference the destination first cell ('dfCell').
Dim dfCell As Range: Set dfCell = dws.Range(dFirstCellAddress)
' Calculate the destination clear rows count ('dcrCount'),
' the number of rows to be cleared below the destination ranges.
Dim dcrCount As Long: dcrCount = dws.Rows.Count - dfCell.Row - drCount + 1
' Write the values from the destination array to the destination ranges.
For n = 1 To ColumnsCount
With dfCell
.Resize(drCount).Value = dJag(n) ' write
.Resize(dcrCount).Offset(drCount).Clear ' clear below
End With
Set dfCell = dfCell.Offset(, dcGap + 1)
Next n
Debug.Print Format(Timer - Timing, "0.000") & " seconds"
' Inform.
MsgBox "Data split.", vbInformation
End Sub
Function GetJaggedSplitOneD( _
SourceOneD() As Variant, _
ByVal ColumnsCount As Long) _
As Variant()
Dim sCount As Long: sCount = UBound(SourceOneD) - LBound(SourceOneD) + 1
Dim drCounts() As Long: ReDim drCounts(1 To ColumnsCount)
Dim drCount As Long: drCount = Int(sCount / ColumnsCount)
Dim Remainder As Long: Remainder = sCount Mod ColumnsCount
If Remainder > 0 Then
drCount = drCount + 1
drCounts(ColumnsCount) = drCount - ColumnsCount + Remainder
Else
drCounts(ColumnsCount) = drCount
End If
Dim c As Long
For c = 1 To ColumnsCount - 1: drCounts(c) = drCount: Next c
Dim dJag() As Variant: ReDim dJag(1 To ColumnsCount)
Dim dData() As Variant: ReDim dData(1 To drCount, 1 To 1)
Dim s As Long: s = LBound(SourceOneD)
Dim dr As Long
For c = 1 To ColumnsCount
dJag(c) = dData
For dr = 1 To drCounts(c)
dJag(c)(dr, 1) = SourceOneD(s)
s = s + 1
Next dr
Next c
GetJaggedSplitOneD = dJag
End Function
I have 2-dimensional array and I would like to inspect each element in a specific row with If-Then statements and assign assign values to the next row depending on the outcome of the If-Then statements? What is the correct syntax for looping through the elements of a row in a 2-d array?
Please, try using the next Sub:
Sub changeRow(arr As Variant, iR As Long, strTxt As String)
Dim i As Long
For i = LBound(arr, 2) To UBound(arr, 2) '(arr, 2) to determine the number of columns
arr(iR, i) = arr(iR, i) & strTxt
Next i
End Sub
Of course, it can be designed to do whatever you need on the respective row. Even extending parameters to be used.
It can easily be tested in the next way:
Sub testIterate2DArrayRow()
Dim sh As Worksheet, arr, arrR, iRow As Long, strAdd As String
Set sh = ActiveSheet
iRow = 2 'the array row to be iterated
strAdd = " - XX" 'string to be added to each row element (instructional example)
arr = sh.Range("A2:D6").value 'the easiest way to create a 2D array
arrR = Application.Index(arr, iRow, 0) 'create a 1D slice of the row to be iterated/modified
'if you need only iterating to extract something, you may stop here
'and iterate between its elements...
Debug.Print Join(arrR, "|") 'just to visually see the row content
changeRow arr, iRow, strAdd 'iterate on the iRow row (and modify something)
Debug.Print Join(Application.Index(arr, iRow, 0), "|") 'visual evidence of the modification...
End Sub
Edited:
I will let the above code for other people liking to learn the general concept.
Please, test the next code, which should process the array as (I understood) you need.
Its first lines only create the opportunity to easily check the concept. So, you should place the necessary bays on an Excel sheet, from "A1" to "J1" and run the above code. It will return the processed array starting from "L1":
Sub analizeBays()
Dim sh As Worksheet, BayRay(), i As Long
Set sh = ActiveSheet
BayRay = sh.Range("A1:J4").value 'only to easily test the concept
For i = LBound(BayRay, 2) To UBound(BayRay, 2)
If BayRay(1, i) <= 10 Then
BayRay(2, i) = 2035
BayRay(3, i) = 2005
BayRay(4, i) = 1005
ElseIf BayRay(1, i) > 10 And BayRay(1, i) <= 12 Then
BayRay(2, i) = 2022
BayRay(3, i) = 1032
BayRay(4, i) = 4344
End If
Next i
'drop the processed array content starting from "L1")
sh.Range("L1").Resize(UBound(BayRay), UBound(BayRay, 2)).value = BayRay
End Sub
Loop Through a Row of a 2D Array
Option Explicit
Sub LoopThroughRow()
Const RowIndex As Long = 2
Const Criteria As Double = 3
Const MinNum As Long = 1
Const MaxNum As Long = 5
' Populate with random integers.
Dim Data As Variant: ReDim Data(1 To 5, 1 To 5)
Dim r As Long, c As Long
For r = LBound(Data, 1) To UBound(Data, 1)
For c = LBound(Data, 2) To UBound(Data, 2)
Data(r, c) = Int((MaxNum - MinNum + 1) * Rnd + MinNum)
Next c
Next r
' Write criteria row.
For c = LBound(Data, 2) To UBound(Data, 2)
If Data(RowIndex, c) > Criteria Then
Data(RowIndex + 1, c) = "Yes"
Else
Data(RowIndex + 1, c) = "No"
End If
Next c
' Print result.
Debug.Print "Column", "Row " & RowIndex, "Row " & RowIndex + 1
For c = LBound(Data, 2) To UBound(Data, 2)
Debug.Print c, Data(RowIndex, c), Data(RowIndex + 1, c)
Next c
End Sub
Im looking for some help with a VBA problem I'm having. Basically, I'm collecting information from a source file on sheet1 into static arrays. From those static arrays I'm creating a dynamic array with account numbers, and a calculated value. What I'm trying to do next is create a second dynamic array with only unique account numbers and summing the calculated values in the previous dynamic array. But I have no idea how to do that...
The following is what I have so far.
Dim ClosingCash() As Variant, MarginExcess() As Variant, VarMarg() As Variant, Acct() As Variant, FX() As Variant, UniqueAcct() As Variant, Answers() As Variant
Dim Dim1 As Long, Counter As Long, W_Sum As Long
Sheet1.Activate
Acct = Range("b2", Range("b2").End(xlDown))
ClosingCash = Range("f2", Range("f2").End(xlDown))
MarginExcess = Range("j2", Range("J2").End(xlDown))
FX = Range("n2", Range("n2").End(xlDown))
VarMarg = Range("o2", Range("o2").End(xlDown))
Dim1 = UBound(ClosingCash, 1)
ReDim Answers(1 To Dim1, 1 To 2)
For Counter = 1 To Dim1
Answers(Counter, 1) = Acct(Counter, 1)
Answers(Counter, 2) = (WorksheetFunction.Min(ClosingCash(Counter, 1) + VarMarg(Counter, 1), MarginExcess(Counter, 1)) * FX(Counter, 1))
Next Counter
Sheet3.Activate
Range("a2", Range("a2").Offset(Dim1 - 1, 1)).Value = Answers
What I would like to print out are the unique account numbers, and the sum of Answers(counter, 2) that correspond to that account number, similar to a SumIf.
Any advise would be greatly appreciated!
Sum Unique
In your code you could use it like this:
Dim Data As Variant: Data = getUniqueSum(Answers)
If Not IsEmpty(Data) Then
Sheet3.Range("E2").Resize(UBound(Data, 1), UBound(Data, 2)).Value = Data
End If
The Code
Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes the unique values from the first column of a 2D array
' and the sum of the corresponding values in its second column,
' to a 2D one-based two-columns array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function getUniqueSum( _
Data As Variant) _
As Variant
If IsEmpty(Data) Then Exit Function
With CreateObject("Scripting.Dictionary")
.CompareMode = vbTextCompare
Dim Key As Variant
Dim i As Long
Dim c1 As Long: c1 = LBound(Data, 2)
Dim c2 As Long: c2 = c1 + 1
For i = LBound(Data, 1) To UBound(Data, 1)
Key = Data(i, c1)
If Not IsError(Key) Then
If Len(Key) > 0 Then
.Item(Key) = .Item(Key) + Data(i, c2)
End If
End If
Next i
If .Count = 0 Then Exit Function
Dim Result As Variant: ReDim Result(1 To .Count, 1 To 2)
i = 0
For Each Key In .Keys
i = i + 1
Result(i, 1) = Key
Result(i, 2) = .Item(Key)
Next Key
getUniqueSum = Result
End With
End Function
Try This
Sub GetUniqueSum()
Dim Rng As Range
Dim numRows As Long, endRow As Long, outputRow As Long, i As Long
Dim rangeText As String
Dim acct As Variant
Dim Sum As Double, ClosingCash As Double, MarginExcess As Double
Dim FX As Double, VarMarg As Double
Dim Value As Double, Value2 As Double
'Get the last row as a string
numRows = Range("B2", Range("b2").End(xlDown)).Rows.Count
endRow = CStr(numRows + 1)
rangeText = "B2:O" & endRow
'Sort the range
Set Rng = Range("Sheet2!" & rangeText)
Rng.Sort (Rng.Columns(1))
'Initialize variables
acct = Rng.Cells(2, 1)
outputRow = 1
Sum = 0
'Calculate Sums
For i = 1 To Rng.Rows.Count
If Rng.Cells(i, 1) <> acct Then
'No longer same acct, print out results
outputRow = outputRow + 1
Worksheets("Sheet3").Cells(outputRow, 1) = acct
Worksheets("Sheet3").Cells(outputRow, 2) = Sum
acct = Rng.Cells(i, 1)
Sum = 0
End If
ClosingCash = Rng(i, 5).Value
MarginExcess = Rng(i, 9).Value
FX = Rng(i, 13).Value
VarMarg = Rng(i, 14).Value
Value = ClosingCash + VarMarg
Value2 = MarginExcess * FX
If Value > Value2 Then Value = Value2
Sum = Sum + Value
Next
'Print out last result
Worksheets("Sheet3").Cells(outputRow + 1, 1) = acct
Worksheets("Sheet3").Cells(outputRow + 1, 2) = Sum
End Sub
I need to do the following:
lift the range C2:AU264 into an 2D array
create another 1D array, (1 To 11880)
fill second array with values from the first one ("transpose")
write array 2 back to the sheet
Here is the code I am using:
Private Ws As Worksheet
Private budgets() As Variant
Private arrayToWrite() As Variant
Private lastrow As Long
Private lastcol As Long
Private Sub procedure()
Application.ScreenUpdating = False
Set Ws = Sheet19
Ws.Activate
lastrow = Ws.Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).row
lastcol = Ws.Cells.Find("*", searchorder:=xlByColumns, searchdirection:=xlPrevious).Column
ReDim budgets(1 To lastrow - 1, 1 To lastcol - 2)
budgets= Ws.Range("C2:AU265")
ReDim arrayToWrite(1 To (lastCol - 2) * (lastRow - 1))
k = 0
For j = 1 To UBound(budgets, 2)
For i = 1 To UBound(budgets, 1)
arrayToWrite(i + k) = budgets(i, j)
Next i
k = k + lastrow - 1
Next j
Set Ws = Sheet6
Ws.Activate
Ws.Range("E2").Resize(UBound(arrayToWrite)).Value = arrayToWrite
'For i = 1 To UBound(arrayToWrite)
'Ws.Range(Cells(i + 1, 5).Address).Value = arrayToWrite(i)
'Next i
Application.ScreenUpdating = True
End Sub
This just writes the first value from the range C2:AU264 (the first element of the first array) through the whole range E2:E11881. If however, I un-comment the For loop just before the end of my script and do it that way, it does work, but is slow. How can I write the array correctly using the first statement?
If you want to write an array to a range, the array must have two dimensions. Even if you only wish to write a single column.
Change
ReDim arrayToWrite(1 To (lastCol - 2) * (lastRow - 1))
to
ReDim arrayToWrite(1 To (lastCol - 2) * (lastRow - 1), 1 To 1)
and
arrayToWrite(i + k) = budgets(i, j)
to
arrayToWrite(i + k, 1) = budgets(i, j)
simply use transpose... change
Ws.Range("E2").Resize(UBound(arrayToWrite)).Value = arrayToWrite
to
Ws.Range("E2").Resize(UBound(arrayToWrite)).Value = Application.Transpose(arrayToWrite)
Hint: there is no need for ReDim budgets(1 To lastrow - 1, 1 To lastcol - 2).
If budgets is a variant then budgets = Ws.Range("C2:AU265") will automatically set the ranges (upper left cell (in this case C2) will be (1, 1)).
EDIT
Assuming you only want to write down all columns (one after another) below each other, you can shorten the macro a bit like that:
Private Sub procedure()
Dim inArr As Variant, outArr() As Variant
Dim i As Long, j As Long, k As Long
With Sheet19
.Activate
inArr = .Range(, .Cells(2, 3), .Cells(.Cells.Find("*", , , , 1, 2).Row, .Cells.Find("*", , , , 2, 2).Column)).Value
End With
ReDim outArr(1 To UBound(inArr) * UBound(inArr, 2))
k = 1
For j = 1 To UBound(inArr, 2)
For i = 1 To UBound(inArr)
k = k + 1
arrayToWrite(k) = budgets(i, j)
Next i
Next j
Sheet6.Range("E2:E" & UBound(arrayToWrite)).Value = Application.Transpose(arrayToWrite)
End Sub
And if you want each row transposed and below each other than simply switch the two For...-lines. (Still the code does basically the same like before)