Array do not resize after ReDim VBA - arrays

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

Related

How do I get data in cells as an array up one row in the same column without selecting?

I am trying to write a process that compares strings and deletes the duplicate string within a given column using a selection as the top and bottom constraints.
Most of the process of checking and deleting works however I am having trouble with moving the cell contents up a cell after the duplicate string was deleted.
Image showing how the script should work
Red outline is the loop that selects the String to compare against.
Green outline is the loop that finds, deletes and moves the cells up one.
Blue outline is the Selection.
Stage 1 is to find and compare two strings that are the same.
Stage 2 is to delete the string that is the same as the first string.
Stage 3 is to move everything under the deleted cell with the deleted string up one row so that there is no empty cell.
I'm having problems with stage 3. I don't know how to move all data in those cells up one row without using a loop and I can't use the selection.
Here is the code so far:
Private Sub Tabeller()
Dim vRngMv As Variant
Dim iRowChsr1, iRowChsr2, iRowTtl, iI As Integer
Dim vRowIn, vRowComp As String
Dim oRngSlct, oRngMv As Range: Dim ws As Worksheet: Dim oBS As Object
'Newer Version will get rid of Selection as range determination
'Why does oRngSlct become a Variant/Object/Range here and oRngMv stays a Range object?
'I dont use it, kept it in to ask the question.
Set oRngMv = Selection: Set oRngSlct = Selection
iRowTtl = oRngSlct.Rows.Count
'First Loop For holding target cell data for comparison
For iRowChsr1 = 1 To iRowTtl
'Chooses target cell and string
vRowIn = oRngSlct(iRowChsr1, 1)
'Second loop for Seeking a matching String
For iRowChsr2 = 1 To iRowTtl
'Check to not pick itself
If iRowChsr1 = iRowChsr2 Then
'Offsets Counter by 1 if it enocunters itself
iRowChsr2 = iRowChsr2 + 1
Else
'Sets comparison string
vRowComp = oRngSlct(iRowChsr2, 1)
'String comparison
iI = StrComp(vRowIn, vRowComp, 1)
'If strings are equal
If iI = 0 Then
'Deletes; I know this is redundant but its here for clarity
oRngSlct(iRowChsr2, 1) = ""
'Offsets by iRowChsr by 1
iRowChsr2 = iRowChsr2 + 1
'Create Variant with proper range, it just has to be translated into something that excel can move.
vRngMv = Range((oRngSlct(iRowChsr2, 1)), (oRngSlct(iRowTtl, 1)))
Set oRngMv = Range 'I know this doesnt work
'Offsets back to original Position of Deleted cell
iRowChsr2 = iRowChsr2 - 1
'*******************************
'*Cuts and pastes or moves here*
'*******************************
End If
End If
'Next Comparison String
Next iRowChsr2
'Next target String
Next iRowChsr1
End Sub
Unique (Remove Duplicates)
You could rather use one of the following.
The first solution will leave error values and blanks as part of the resulting data, while the second one will remove them.
The Code
Option Explicit
Sub removeDupesColumnSelection()
' Validate Selection.
If TypeName(Selection) <> "Range" Then Exit Sub
' Remove duplicates.
Selection.Columns(1).RemoveDuplicates Array(1), xlNo
End Sub
Sub uniquifyColumnSelection()
' Validate Selection.
If TypeName(Selection) <> "Range" Then Exit Sub
' Write values from first column of Selection to Data Array.
Dim rg As Range: Set rg = Selection.Columns(1)
Dim rCount As Long: rCount = rg.Rows.Count
Dim Data As Variant
If rCount > 1 Then
Data = rg.Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
End If
' In Unique Dictionary...
With CreateObject("Scripting.Dictionary")
.CompareMode = vbTextCompare
' Write unique values from Data Array to Unique Dictionary.
Dim Key As Variant
Dim i As Long
For i = 1 To rCount
Key = Data(i, 1)
If Not IsError(Key) Then
If Len(Key) > 0 Then
.Item(Key) = Empty
End If
End If
Next i
ReDim Data(1 To rCount, 1 To 1)
If .Count > 1 Then
' Write values from Unique Dictionary to Data Array.
i = 0
For Each Key In .Keys
i = i + 1
Data(i, 1) = Key
Next Key
End If
End With
' Write values from Data Array to Destination Range.
rg.Value = Data
End Sub

Get row number using array VBA

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.

How to insert data in the array (inside a loop)?

I have a problem with inserting data to the array. In the program I search all cells with "Data:" value. If this value appear I jump to the cell on the right and mark it. I want to collect all marked values (all of them are dates) in the array but with my code (enclosed below) I have an error. I have tried ReDim and setting an exact number of objects in the array. I would be grateful for a help.
Sub CheckData()
Dim FindIt As Range
Dim EndIt As Range
Dim StartAddress As String
With Range("A1:A100")
Set EndIt = .Cells(.Cells.Count)
End With
Set FindIt = Range("A1:A100").Find(what:="Data:", after:=EndIt)
If Not FindIt Is Nothing Then
StartAddress = FindIt.Address
End If
Dim Tabel() As Variant
Tabel = Array()
i = 0
Do Until FindIt Is Nothing
Set FindIt = Range("A1:A100").FindNext(after:=FindIt)
Data = FindIt.Address
Range(Data).Select
ActiveCell.Offset(0, 1).Select
ActiveCell.Interior.ColorIndex = 6
'Debug.Print ActiveCell.Value
Tabel(i) = ActiveCell.Value
i = i + 1
'Debug.Print i
If FindIt.Address = StartAddress Then
Exit Do
End If
Loop
End Sub
You never sized your array.
Dim Tabel() As Variant
Use ReDim to resize a dynamically-sized array.
ReDim Preserve Tabel(0 To UBound(Tabel)+1)
However that's a terribly inefficient thing to do in a loop (you're copying the same elements over and over and over and over again, at every single iteration).
Rule of thumb, if you don't know from the start how many elements you're going to need, it's probably best to use a Collection instead of an array.
Dim items As Collection
Set items = New Collection
'...
items.Add ActiveCell.Value
You could also use a for loop instead of a find (also using Mat's Mug idea about collections)
Sub CheckData1()
Dim ws As Worksheet
Dim searchRng As Range
Dim cell As Range
Dim tabel As Collection 'incorrectly spelt table?
Set ws = ActiveSheet
Set tabel = New Collection
Set searchRng = ws.Range("A1:A100")
For Each cell In searchRng.Cells
If cell.Value = "Data:" Then
tabel.Add cell.Offset(, 1)
cell.Offset(, 1).Interior.ColorIndex = 6 'If you still need it highlighted
End If
Next
End Sub

Trouble filtering out rows on one worksheet based on an array with values from another worksheet in VBA

My intention was to have the following code compile data from my "Low CPM 1" worksheet into an array and then filter my active worksheet based on this array. While the macro does seem to affect the filters, none of the values get filtered out. Any help on this matter would be greatly appreciated
Sub Macro1()
Dim CPM1Array(0 To 300) As Variant
For i = 2 To UBound(CPM1Array)
CPM1Array(i) = Sheets("Low CPM 1").Cells(i, 2).Value
Next i
ActiveSheet.Range("$A$1:$H$251").AutoFilter Field:=3, Criteria1:=("<>1 to Ubound(CPM1Array)"), Operator:=xlFilterValues
End Sub
There is no simple way with autofilter to achieve what you want. You cannot use Criteria1:="<>MyArray"
Alternative
We know which values we do not want. We can find out what are the values of the relevant column
Simply store the values of the relevant column in an array and then remove the unnecessary values from it by comparing it with the array which has values we do not want.
Remove blank cells from the array
Pass the final array to the autofilter.
In Action
Let's say our worksheet looks like as shown in the below image. I am taking an example of only 15 rows.
Code
Sub Sample()
Dim ws As Worksheet
Dim MyAr(1 To 5) As String
Dim tmpAr As Variant, ArFinal() As String
Dim LRow As Long
ReDim ArFinal(0 To 0)
Set ws = ActiveSheet
'~~> Creating an array of values which we do not want
For i = 1 To 5
MyAr(i) = i
Next i
With ws
'~~> Last Row of Col C sice you will filter on 3rd column
LRow = .Range("C" & .Rows.Count).End(xlUp).Row
'~~> Storing the values form C in the array
tmpAr = .Range("C2:C" & LRow).Value
'~~> Compare and remove values which we do not want
For i = 1 To LRow - 1
For j = 1 To UBound(MyAr)
If tmpAr(i, 1) = MyAr(j) Then tmpAr(i, 1) = ""
Next j
Next i
'~~> Remove blank cells from the array by copying them to a new array
For i = LBound(tmpAr) To UBound(tmpAr)
If tmpAr(i, 1) <> "" Then
ArFinal(UBound(ArFinal)) = tmpAr(i, 1)
ReDim Preserve ArFinal(0 To UBound(ArFinal) + 1)
End If
Next i
'~~> Filter on values which you want. Change range as applicable
.Range("$A$1:$H$15").AutoFilter Field:=3, Criteria1:=ArFinal, Operator:=xlFilterValues
End With
End Sub
Output

Dynamic row and static column array for 'write line' loop

I need to generate an array for a 'write line' custom function. This function writes a single row of cells (the array) to a text file during a loop.
This array is static in size.
The row to be written to the text file is 'detected' by the Rng variable below:
Set Rng = Worksheets("Sheet1").Columns(5).Find(userform1.ComboBox1.Value)
i.e. the Rng variable only indexes a single cell in column 5 - as matched by the value in ComboBox1. The array to be written by writeLine will be column 6:49 of the row matched by Rng.
The arguments for the 'write line' function are
the value of the combobox i.e. userform1.combobox1.value, and
the array to be written in the 'write line' function
So far, I have this:
Private Sub CommandButton18_Click()
Dim Range As Range
Dim Array As Range
Set Range = Worksheets("Sheet1").Columns(5).Find(userform1.ComboBox1.Value) ''anticipated to set the row index of the array???
Array = ????
Function.writeLine(userform1.ComboBox1.Value,????)
End Sub
The trouble I am having is to create an array that incorporates the changing row whilst having the number of columns fixed?
Here is part of the writeLine function:
Do Until objTF.AtEndOfStream
readString = objTF.readline
data = Split(readString, vbTab)
foundID = data(0)
If StrComp(**foundID, ID**, 1) <> 0 Then
objTF2.writeLine (readString)
ElseIf StrComp(**foundID, ID**, 1) = 0 Then
'write initial value outside the loop
strTmp = Split(readString, strDelim)
'Modify the data array to include the data provided by writeArray
For argPos = 5 To UBound(data)
'check for index out of bounds, stop writing if it is!
If (argPos - 5) > UBound(writeArray) Then Exit For 'need to check this will exit if the value is index out of bounds.
data(argPos) = writeArray(dataPos)
dataPos = dataPos + 1
Next argPos
'Take each entry from data and build a string delimited by strDelim
Do Until counter > UBound(data)
resultStr = IIf(counter <= UBound(data), resultStr & data(counter) & strDelim, resultStr & data(counter))
counter = counter + 1
Loop
'output to temp file
objTF2.writeLine (resultStr)...
Basically, the "foundID" and "ID" are the variables to be matched in order to write the array to the text file. The "ID" is the ComboBox1.value. "foundID" is the value in column 5 of the spreadsheet. Column 6:49 (the array) are to be written to the text file.
Use Ubound to get the range as array.
Private Sub CommandButton18_Click()
Dim rRange As Range
Dim colno As Long
Dim rownumber As Long
Dim sString As String
lastrow = 10
For colno = 6 To 43
For rownumber = 1 To lastrow
sString = Worksheets("sheet1").Cells(rownumber, colno)
Next rownumber
Next
End Sub

Resources