As you can see from the code below i m looping an array and if condition met i want to get the row number that includes the specific value in column A.
Images:
Option Explicit
Sub test()
Dim i As Long, arr As Variant
With ThisWorkbook.Worksheets("Sheet1")
arr = .Range("A1:A10")
For i = LBound(arr) To UBound(arr)
If arr(i, 1) = 4 Then
'Get the row that the array value apperas in Column A. The answer should be row number 8
End If
Next i
End With
End Sub
Your array relates to your row number by i although this is dependent on your array starting from the first row. If you started on the 5th row, it would be i + 4
For i = LBound(arr) To UBound(arr)
If arr(i, 1) = 4 Then
Debug.Print i
End If
Next i
Sub test()
Dim i As Long, arr As Variant, rng As Range
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1:A10")
arr = rng.Value
For i = LBound(arr) To UBound(arr)
If arr(i, 1) = 4 Then
Debug.Print rng(i).Row
End If
Next i
End Sub
Try with For each cells as below. it will return the exact match rows.
Option Explicit
Sub test()
Dim i As Long
Dim cells As Range
With ThisWorkbook.Worksheets("Sheet1")
For Each cells In .Range("A1:A10")
If cells.Value = 4 Then
MsgBox ("row Number is :" & cells.Row)
End If
Next
End With
End Sub
I've added a variable that stores the initial row number where the range starts.
Also, note that the index i of your array is related to position inside range. When you do arr = .Range("A1:A10") you are creating an BIDIMENSIONAL array of 10 cells (10x1). Index 1 would be Cell (1,1), index 2 would be Cell(2,1) and so on.
So the trick here would be to store the row number where range starts, and then sum up index.
Sub test()
Dim i As Long, arr As Variant
Dim rng As Range
Dim InitialRow As Long
With ThisWorkbook.Worksheets("Sheet1")
Set rng = .Range("A1:A10")
arr = rng.Value
InitialRow = Range(Left(rng.Address(False, False), Application.WorksheetFunction.Search(":", rng.Address(False, False)) - 1)).Row
For i = LBound(arr) To UBound(arr)
If arr(i, 1) = 4 Then
'Get the row that the array value apperas in Column A. The answer should be row number 8
Debug.Print InitialRow + i - 1 'this is the row number that matches the value
End If
Next i
Erase arr
End With
End Sub
If I test this with values on `Range("A1:A10"), I get as result 8.
But If I change position of values, I get another result with same code too, because code stores the initial row of range.
If your range is not going to change never ever the starting position, just with the index would work. But if range is not going to start always in same row, then you need to know the initial row and sum it up with the index.
Hope this code can help you out and be adapted to your needs.
Related
i have a multidimensional dynamic array with 11 columns.
how do i count how many elements in column 4 starts with "ZS" (there can be items with no value, other with "ZC973279473" and others with "ZS5367276". i care only about the one like "ZS773746".)
i tried with
XNSites = Application.Count(Application.Match(Left(myarray.Columns(4), 2), "ZS", 0))
and
XNSites = Application.WorksheetFunction.CountIf(Left(myarray.columns(4), 2), "=" & "ZS")
but it doesn't work
Since your data seems to be alphanumeric, with just the first two chars as letters, you could prevent a loop using a 1D-array extract from a 2D-array when you apply FILTER:
Sub Test()
Dim arr1 As Variant, arr2 As Variant, arr3 As Variant
Dim lr As Long
With Sheet1 'Change to your own sheets CodeName
'Fill dynamic 2D-array for testing (not sure how you get your array)
lr = .Cells(.Rows.Count, 1).End(xlUp).Row
arr1 = .Range("A1:K" & lr).Value2
'Get you 1D-array for the 4th column
With Application
arr2 = .Transpose(.Index(arr1, 0, 4))
End With
'Count elements starting with ZS
arr3 = Filter(arr2, "ZS")
Debug.Print UBound(arr3) + 1
End With
End Sub
I think a simple iteration will help you:
Dim Count As Long
Dim ColCell As Range
For Each ColCell In myarray.Columns(11).Cells
If Left(ColCell.Value, 2) = "ZS" Then
Count = Count + 1
End If
Next ColCell
Debug.Print "Total: " & Count
Please try below code( I believe all you care about values in column 4). This code will
copy the entire rows which have ZS values at the beginning in column 4 and paste it on sheet2
If data is not on sheet1,please change the sheetname :)
Sub you()
lfr = ThisWorkbook.Worksheets("sheet1").Cells(Rows.Count, 1).End(xlUp).Row
For x = 2 To lfr
If Worksheets("sheet1").Cells(x,4).Value Like "ZS*" Then
Worksheets("sheet1").Rows(x).Copy
Worksheets("sheet2").Activate
bfr = ThisWorkbook.Worksheets("sheet2").Cells(Rows.Count, 1).End(xlUp).Row
Worksheets("sheet2").Cells(bfr + 1, 1).Select
ActiveSheet.Paste
End If
Next
Application.CutCopyMode = False
Worksheets("sheet1").Activate
Worksheets("sheet1").Cells(1, 1).Select
End Sub
If you want just the count try below(You please change rahge from gi to wherever you want the count number) :-
Sub you()
lfr = ThisWorkbook.Worksheets("sheet1").Cells(Rows.Count, 1).End(xlUp).Row
For x = 2 To lfr
KLA = Application.WorksheetFunction.Count(Worksheets("sheet1").Cells(x, 4).Value Like "ZS")
Range("g1") = KLA
Next
End Sub
I was looking here for the answer to my problem but still, do not know how to solve it so I am refreshing the topic.
I have a primitive function that searches in the Worksheet (column A) the inputs to Userform.TextBox1, UserForm.Textbox2, etc. When a particular record is found, it should assign to an array the record itself and values from next 3 or 4 cells from the same row (each row ends with “End”). In this way, I will have the array of max 4 columns and as many rows as records will be found
The first Do loop goes perfect but increasing the size variable (found records), so increasing the array’s row as I wanted, gives me the subscript out of range error. I spent on this a whole day but I do not see what I am missing.
Here’s the code:
Sub test()
Dim arr() As Variant
Dim i, size As Integer
Dim back As String
Cells(1, 1).Select
i = 0
size = 0
Do Until ActiveCell.Value = UserForm1.TextBox1.Value
ActiveCell.Offset(1, 0).Select
Loop
back = ActiveCell.Address
Do Until ActiveCell = "End"
size = size + 1
ReDim Preserve arr(1 To size, 1 To 4)
Do Until ActiveCell.Value = "End"
i = i + 1
arr(size, i) = ActiveCell
ActiveCell.Offset(0, 1).Select
Loop
Loop
Range(back).Offset(1, 0).Select
Do Until ActiveCell.Value = UserForm1.TextBox2.Value
ActiveCell.Offset(1, 0).Select
Loop
back = ActiveCell.Address
i = 0
Do Until ActiveCell = "End"
size = size + 1
ReDim Preserve arr(1 To size, 1 To 4) '"Subscript out of range" error occurs here
Do Until ActiveCell.Value = "End"
i = i + 1
arr(size, i) = ActiveCell
ActiveCell.Offset(0, 1).Select
Loop
Loop
End Sub
If you use Preserve keyword in an array Redim declaration it will only redimension the last of array's column. You need to reorganize your arr() array.
To restate your algorithm:
Search within the first column for the text in UserForm1.TextBox. This is the start row of a block
Each block continues until "End" appears in the first column
For each row in the block, you want the values of the cells in that row.
A cell which contains "End" marks the end of the values of the cells in that row.
I would suggest the following general improvements:
Use a data structure where you don't have to manage the number of elements, as this is very error-prone. Use a Scripting.Dictionary, an ArrayList or a VBA Collection.
You should not need to manipulate the selected cell. Define a Range and iterate over the cells in the Range.
Like this:
Dim text1 As String
text1 = "Alfa"
Dim text2 As String
text2 = "Kilo"
Dim results As New ArrayList
Dim rng As Range
Set rng = Worksheets("Sheet1").UsedRange
Dim row As Integer
For row = 1 To rng.Rows.Count
Dim firstCellText As String
firstCellText = rng(row, 1)
'you might store the possible values in a Dictionary and use Dictionary.Exists for this check
If firstCellText = text1 Or firstCellText = text2 Then
Dim cellValues As ArrayList
Set cellValues = New ArrayList 'this has to be on a separate line
Dim cell As Range
For Each cell In rng.Rows(row).Cells
If cell = "End" Then Exit For
cellValues.Add cell.Value
Next
results.Add cellValues
End If
Next
I have a code for an array that saves all the data from my spreadsheet in columns D to I, however it also saves all of the blank cells from the sheet too which I don't want. All of the columns have the same number of rows, but ideally I want the array for every row from the second until it finds the last repetition of the max that it works out from column D. My code is:
Sub PopulatingArrayVariable()
Dim myArray() As Variant
Dim DataRange As Range
Dim cell As Range
Dim x As Long
Dim TotalTargets As Double
TotalTargets = WorksheetFunction.Max(Columns("D"))
Set DataRange = Sheets("Result").Range("D:I")
For Each cell In DataRange.Cells
ReDim Preserve myArray(x)
myArray(x) = cell.Value
x = x + 1
Next cell
End Sub
Here's an alternative approach which should skip ReDim Preserve altogether.
See if it helps your situation.
Sub BuildArray()
Dim lngLastRow As Long
Dim rng As Range
Dim arList As Object
Dim varOut As Variant
lngLastRow = Sheets("Result").Range("D:I").Find("*", Sheets("Result").Range("D1"), , , xlByRows, xlPrevious).Row
Set arList = CreateObject("System.Collections.ArrayList")
For Each rng In Sheets("Result").Range("D1:I" & lngLastRow)
If Len(Trim(rng.Value)) > 0 Then
arList.Add rng.Value
End If
Next
varOut = arList.ToArray
End Sub
Add a condition for the length of the cell before adding to the array:
For Each cell In DataRange.Cells
If Len(Trim(Cells)) > 0 Then
ReDim Preserve myArray(x)
myArray(x) = cell.Value
x = x + 1
End If
Next cell
The Trim() would remove the spaces from left and right, thus if there is a cell with just one space like this it would still give 0 and would not be taken into account.
Trim MSDN
I’m in a situation where I need to reproduce something in VBA and a bit stuck given my lack of understanding of object oriented language and VBA in general.
Problem:
I need to produce an array or vector based on a 2 column table.
The first range (column) contains unit counts.
The second range (column) contains numeric values.
I need to replicate the value based on the number of units.
For example,
if the first row contains 3 units with a value of $100
I need the array to contain $100, $100, $100.
This will need to be looped thru each row containing units.
So if row 2 contains 2 units with a value of $50
I need to complete array to be $100, $100, $100, $50, $50, and so on.
I understand this situation will require ReDim the array based on the total values. My struggle is I’ve been unable to figure out the nested for loops.
I get how to replicate the value based on the number of “units” like the below...
ReDim arr(0 To x - 1)
For i = 0 To x - 1
arr(i) = rng.Offset(0, 1).Value
Next
What is the best way to loop thru each row and replicate the values for each row in the range based on the unit count?
If anyone is familiar with R, I'm essentially looking for something that achieves the rep() function (e.g., rep(df$b, df$a)) and return the values in a single array.
Any help is greatly appreciated. Thanks
Or a one liner which uses the REPT function as you would have used in r :)
This assumes your data is in A1:B10 - the length can be made variable
s = Split(Join(Application.Transpose(Evaluate("=INDEx(REPT(B1:B10&"","",A1:A10),,1)"))), ",")
An an example, to dump the new to array to C1
s = Split(Join(Application.Transpose(Evaluate("=INDEx(REPT(B1:B10&"","",A1:A10),,1)"))), ",")
[c1].Resize(UBound(s), 1) = Application.Transpose(s)
When you say Row contains 3 units, do you mean the cell has value 3 or 3 Units? If it is 3 then you may not have to Redim the array in the loop. Simply find the sum of values in Col A which has units and Redim it in one go as shown below.
Sub Sample()
Dim ws As Worksheet
Dim Ar() As String
Dim n As Long, i As Long, lRow As Long
'~~> Change this to the relevant sheet
Set ws = Sheet6
With ws
n = Application.WorksheetFunction.Sum(.Columns(1))
ReDim Ar(t To n)
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
n = 1
For i = 1 To lRow
If Len(Trim(.Range("A" & i).Value)) <> 0 Then
For j = 1 To .Range("A" & i).Value
Ar(n) = .Range("B" & i).Value
n = n + 1
Next j
End If
Next i
For i = LBound(Ar) To UBound(Ar)
Debug.Print Ar(i)
Next i
End With
End Sub
Screenshot
And if the cell has 3 Units then you will have to store the values of Col A in an array, do a replace on Unit/Units, find the sum and finally use the above code. Here is an example
Sub Sample()
Dim ws As Worksheet
Dim Ar() As String, tmpAr As Variant
Dim n As Long, i As Long, j As Long, k As Long, lRow As Long
'~~> Change this to the relevant sheet
Set ws = Sheet6
With ws
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
tmpAr = .Range("A1:A" & lRow).Value
For i = LBound(tmpAr) To UBound(tmpAr)
tmpAr(i, 1) = Replace(tmpAr(i, 1), "Units", "")
tmpAr(i, 1) = Trim(Replace(tmpAr(i, 1), "Unit", ""))
n = n + Val(tmpAr(i, 1))
Next i
ReDim Ar(t To n)
n = 1
For i = 1 To lRow
If Len(Trim(.Range("A" & i).Value)) <> 0 Then
k = Val(Trim(Replace(Replace(.Range("A" & i).Value, "Units", ""), "Unit", "")))
For j = 1 To k
Ar(n) = .Range("B" & i).Value
n = n + 1
Next j
End If
Next i
For i = 1 To UBound(Ar)
Debug.Print Ar(i)
Next i
End With
End Sub
Screenshot
if your data is already in an array then ReDim'ing will delete it's contents. You can ReDim Preserve but it's an expensive operation, better to create a new array to put the results into.
I have assumed the data is contained within a Named Range called "Data" with Units being the first column and Values being the second column.
if your data changes regularly you can create a dynamic range using the OFFSET function i.e. =OFFSET(Sheet1!$A$1,0,0,COUNTA(Sheet1!$A:$A),2) assuming your data starts in cell A1 and there is no header row.
Sub ProcessData()
Dim DataArr() As Variant
Dim QtyColArr() As Variant
Dim ResultArr() As Variant
Dim TotalQty As Long
Dim i As Long, j As Long, k As Long
'store data into array
DataArr = Range("Data") 'assume data stored in named range called "Data"
'store Qty col into 1D array
QtyColArr = Range("Data").Resize(, 1)
'sum all qty vals
TotalQty = Application.Sum(QtyColArr)
're-size ResultsArray
ReDim ResultArr(1 To TotalQty)
'Initialize ResultsArr counter
k = LBound(ResultArr)
'loop DataArr
For i = LBound(DataArr) To UBound(DataArr)
'loop qty for current row
For j = 1 To DataArr(i, 1)
'copy value
ResultArr(k) = DataArr(i, 2)
'iterate ResultsArr counter
k = k + 1
Next j
Next i
'output to intermediate window
Debug.Print "{" & Join(ResultArr) & "}"
End Sub
I'm trying to get a 2D array of size [x][3] filled. X is just the size of the sheet (number of rows) and there are 3 columns which I am interested in. The columns are not near each other, for instance arr[i][0] should be filled from column AA, arr[i][1] should come from column K, and arr[i][2] needs to be from columns L.
I tried assigning it the following way, but got an error in the array value assignment.
Any help on this would be greatly appreciated!
Code:
Sub SOC_work()
'Trying to sort each of the disciplines further, by Stage of Construction
Dim ar_SOC() As Variant
Dim int_NumRows As Long
Dim i_counter As Long
Dim j_Counter As Long
Dim lite As Range
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Sheets("AVEVA_PBOM_PARTS").Select
'Redimension the array size to the amount of parts in the PBOM
int_NumRows = ActiveSheet.UsedRange.Rows.count - 1
ReDim ar_SOC(int_NumRows, 3)
'now assignt he range into the array space
lite = Range("AA2", Range("AA2").End(xlDown))
ar_SOC[][1]=lite
End Sub
Is there any way to do this without looping through the entire column?
As described in the comments, you can fill three 2-D arrays. You can then populate a fourth array from the three arrays, like below.
Sub populateArray()
Dim arrColOne() As Variant, arrColTwo() As Variant, arrColThree() As Variant
Dim arrAllData() As Variant
Dim i As Long
arrColOne = Range("A2:A" & lrow(1)) 'amend column number
arrColTwo = Range("D2:D" & lrow(4))
arrColThree = Range("G2:G" & lrow(7))
ReDim arrAllData(1 To UBound(arrColOne, 1), 2) As Variant
For i = 1 To UBound(arrColOne, 1)
arrAllData(i, 0) = arrColOne(i, 1)
arrAllData(i, 1) = arrColTwo(i, 1)
arrAllData(i, 2) = arrColThree(i, 1)
Next i
End Sub
Public Function lrow(colNum As Integer) As Long
lrow = Cells(Rows.Count, colNum).End(xlUp).Row
End Function
The above will require all 3 columns to be the same length (otherwise populating the last array will not work); this is due to the fourth array being redimensioned to contain the number of elements contained in the first array.
Testing with 250,000 rows of data, the fourth array populated in 0.43 seconds.
How lenient are you with the array you get in return? I can get you a Array(col)(row)-style array, without having to loop to get it, if that works. Note that's not Array(col, row), by the way. It's a single-dimensional array of columns, with each element containing a single-dimensional array of row values. If you're okay with that, you can do this:
Dim a(1 To 3)
a(1) = WorksheetFunction.Index(WorksheetFunction.Transpose(Range("AA2:AA10")), 1, 0)
a(2) = WorksheetFunction.Index(WorksheetFunction.Transpose(Range("K2:K10" )), 1, 0)
a(3) = WorksheetFunction.Index(WorksheetFunction.Transpose(Range("L2:L10" )), 1, 0)
Then you could access your array items like so:
Debug.Print UBound(a) ' Number of columns (3)
Debug.Print UBound(a(1)) ' Number of rows in column 1
Debug.Print a(1)(3) ' Value of column 1 (AA), row 3
The Index() function can return a 1D array but only in the rows direction. So, you need to combine it with Transpose() to return a 1D column array. That's all the code above is doing.
What about an array of arrays?
Sub NoLoop()
Dim R1 As Range, R2 As Range, R3 As Range
Dim Arr1() As Variant, Arr2() As Variant, Arr3() As Variant
Dim LR As Long
LR1 = Cells(Rows.Count, "AA").End(xlUp).Row
LR2 = Cells(Rows.Count, "K").End(xlUp).Row
LR3 = Cells(Rows.Count, "L").End(xlUp).Row
Set R1 = Range(Cells(1, "AA"), Cells(LR1, "AA"))
Set R2 = Range(Cells(1, "K"), Cells(LR2, "K"))
Set R3 = Range(Cells(1, "L"), Cells(LR3, "L"))
Arr1 = R1.Value
Arr2 = R2.Value
Arr3 = R3.Value
ArrArr = Array(Arr1, Arr2, Arr3)
End Sub
With this you can call your values using:
MyVal = ArrArr(0)(1,1)
MyVal = ArrArr(0)(2,1)
MyVal = ArrArr(1)(1,1)
Where the first number is for the array (starts from 0 and ends with 2) and the second number is for row/cell of the range used to fill array.
The third number is always 1 (because adding a range to an array returns a bidimensional array)
With this code you can also have different dimensions for each column so to save memory.