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
Related
Could someone explain my why my code display values at columns instead of rows ? Thanks
Function ShiftVector(rng As Range, n As Integer)
Dim i As Integer
Dim j As Integer
Dim B As Variant
Dim A() As Variant
Dim nr As Integer
nr = rng.Rows.Count
ReDim B(nr)
ReDim A(nr)
For i = 1 To nr - n
B(i) = rng(i + n)
Next i
For i = nr - n + 1 To nr
B(i) = rng(i - nr + n)
Next i
ShiftVector = B
End Function
Your code loads a 1D array, which does not have rows by definition... Then, you do not explain what n means and I will make abstraction of its 'contribution' in the function... The next interpretation assumes that rng is a range containing only a column.
There are two ways of solving it:
Let the code as it is, but finally use:
ShiftVector = Application.Transpose(B)
ReDim and load a 2D array:
ReDim B(1 to nr, 1 to 1)
'and load it in the next way:
B(i, 1) = rng(i + n)
You can place a range directly in a (2D) array:
B = rng.value
If you will explain what n wants to be, I can adapt the answer to somehow take it in consideration...
Edited:
Please, play with the next function, able to make slices from a 2D array and 'mount' them in a different order:
Function ShiftVector2D(rng As Range, n As Integer) As Variant
Dim nr As Long, arr, arrSl1, arrSl2
nr = rng.rows.count - rng.row + 1 'the number of the range rows, even if it does not start from the first row...
arr = rng.Value 'place the range in a 2D array
With Application
arrSl1 = .Index(arr, Evaluate("row(1:" & n & ")"), 1) 'obtain an array slice of the first n rows
arrSl2 = .Index(arr, Evaluate("row(" & n + 1 & ":" & nr & ")"), 1) 'obtain an array slice of the rows after n up to the last row
arr = Split(Join(.Transpose(arrSl2), "|") & "|" & Join(.Transpose(arrSl1), "|"), "|") 'created a 1 D array by joinning the two arrays and split them by "|"
ShiftVector2D = .Transpose(arr) 'return the 2D necessary shifted array
End With
End Function
You can test it placing some strings in the range "A1:A10" and run the next code:
Sub testShiftVector2D()
Dim rng As Range, arr
Set rng = Range("A1:A10")
arr = ShiftVector2D(rng, 4)
Debug.Print Join(Application.Transpose(arr), "|")
End Sub
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
I am trying to build a subroutine which checks a certain condition in a client list, separates out the entries which meets the condition, and then average the remaining entries for each name. It is like a doing a pivot table with VBA. I did not want to use the pivot table, since writing the data into a new sheet, refresh it and do something with it adds an unnecessary burden on the speed of the tool. Furthermore, all the arrays are to be kept within the code, rather than written on the sheets. I am almost done with the code, but it is giving me an error in the very end, where I am using the sumif condition.
A point of clarification: The argument 'Number' is a global variable declared in the main tool, which comes from the count of names from the main list, which is in sheet5. I hope that the code is self explanatory beyond that.
What I am getting while running the code is the Error- Run-time error '1004':
Method 'Range' of object '_Global' failed on the line TaskArray(k, 1) = Application.WorksheetFunction.SumIf(Range(Names), NewList(k), Range(ParameterB))
The Code-
Sub Task()
Dim Names() As Variant 'Declare Names
ReDim Names(0 To Number) As Variant 'Declare Names as a vector
Dim ParameterA() As Variant 'Declare Parameter A
ReDim ParameterA(0 To Number) As Variant 'Declare Parameter A as a vector
Dim ParameterB() As Variant 'Declare Parameter B
ReDim ParameterB(0 To Number) As Variant 'Declare Parameter B as a vector
Dim i As Integer
For i = 1 To Number
Select Case Sheet5.Range("BO" & i + 1) - Sheet5.Range("BN" & i + 1)
Case 0
Names(i) = ""
ParameterA(i) = Sheet5.Range("BN" & i + 1) - Sheet5.Range("BL" & i + 1)
ParameterB(i) = ""
Case Else
Names(i) = Sheet5.Range("F" & i + 1)
ParameterA(i) = Sheet5.Range("BN" & i + 1) - Sheet5.Range("BL" & i + 1)
ParameterB(i) = Sheet5.Range("BO" & i + 1) - Sheet5.Range("BN" & i + 1)
End Select
Next i
Sheet3.Range("T159") = Application.WorksheetFunction.Sum(ParameterA()) 'Write the total of Parameter A
Sheet3.Range("T160") = Application.WorksheetFunction.Sum(ParameterB()) 'Write the total of Parameter B
'________________________ To isolate the list of Names (Unique) with existent Parameter B
Dim NewList() As Variant
Dim j As Long
Dim d As Scripting.Dictionary
Set d = New Scripting.Dictionary
With d
For j = LBound(Names) To UBound(Names)
If IsMissing(Names(j)) = False Then
.item(Names(j)) = 1
End If
Next
NewList = .Keys
End With
'________________________To create an array of sums of Parameter B
For k = 1 To Application.WorksheetFunction.CountA(NewList) - 1
Dim TaskArray() As Variant
ReDim TaskArray(1 To k, 0 To 1) As Variant
ReDim Names(0 To Number) As Variant
ReDim ParameterB(0 To Number) As Variant
TaskArray(k, 0) = NewList(k)
TaskArray(k, 1) = Application.WorksheetFunction.SumIf(Range(Names), NewList(k), Range(ParameterB))
Sheet19.Range("H" & k + 1) = TaskArray(k, 0)
Sheet19.Range("I" & k + 1) = TaskArray(k, 1)
Next k
End Sub
I am trying to write Column A to an array and while passing into the array or when writing the array to the sheet, I would like to multiple each value stored by a set number (specifically .01). I will be writing the array back over the same column it was set from.
Ex.
Sheet before macro:
Col A Col B Col C
Header Header Header
100
50
50
40
100
Sheet after macro:
Col A Col B Col C
Header Header Header
1
.5
.5
.4
1
So far I have been working off a basic array portion of code from a tutorial I saw online shown below:
Sub ArrayTest
Dim Arr() As Variant
Arr = Range("A1:A6")
Dim R As Long
Dim C As Long
For R = 1 To UBound(Arr, 1) ' First array dimension is rows.
For C = 1 To UBound(Arr, 2) ' Second array dimension is columns.
Debug.Print Arr(R, C)
Next C
Next R
'resize range array will be written to
Dim Destination As Range
Set Destination = Range("K1")
Destination.Resize(UBound(Arr, 1), UBound(Arr, 2)).Value = Arr
'transpose / write array to range
Set Destination = Range("A1")
Destination.Resize(UBound(Arr, 2), UBound(Arr, 1)).Value = Application.Transpose(Arr)
End Sub
This code has no errors, but I'm unsure of where / how I can "manipulate" the values (either on the way into the array or on the way back to the sheet).
An array may not even be the best way to achieve this overall goal of overwriting a columns values with itself multiplied by a another number. I know I could write the column to a dummy sheet, do the calculation then move back over the original sheet and column, but I was trying to find something cleaner and potentially faster than that. This is also a simplified example, my actual data set is much larger and more variable, but for the ease of discussion I created this example.
Any advice is much appreciated!
Here's a "no loop" approach:
Sub Tester()
Dim arr, rngSrc As Range, sht As Worksheet
Set sht = ActiveSheet
Set rngSrc = sht.Range("A2:A6")
arr = rngSrc.Parent.Evaluate(rngSrc.Address() & " * 10") '<< returns an array
sht.Range("B2").Resize(UBound(arr, 1), UBound(arr, 2)).Value = arr
End Sub
For your specific case:
With Range("A2:A6")
.Value = .Parent.Evaluate(.Address & " * 0.01")
End With
You can do it like this, but easier to use pastespecial (multiply or divide).
Sub x()
Dim v As Variant, i As Long
v = Range("A2:A6").Value
For i = LBound(v) To UBound(v)
v(i, 1) = v(i, 1) * 0.01
Debug.Print v(i, 1)
Next i
Range("A2:A6").Value = v
End Sub
Was working on this as I saw Tim post... similar use of evaluate, but doesn't need an additional array or loop:
Dim rng As Range, lr As Long
lr = Cells(Rows.Count, 1).End(xlUp).Row
Set rng = Range(Cells(1, 1), Cells(lr, 1))
rng = Evaluate(rng.Address & "*0.01")
Perhaps you should collect the values first and process the adjustment(s) in memory.
dim i as long, arr as variant
with worksheets("sheet1")
arr = .range(.cells(2, "A"), .cells(.rows.count, "A").end(xlup)).value2
for i=lbound(arr, 1) to ubound(arr, 1)
arr(i, 1) = arr(i, 1)/100
next i
for i=lbound(arr, 1) to ubound(arr, 1)
debug.print arr(i, 1)
next i
.cells(1, "K").resize(ubound(arr, 1), ubound(arr, 2)) = arr
.cells(1, "L").resize(ubound(arr, 2), ubound(arr, 1)) = application.transpose(arr)
end with
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)