Excel VBA Listrow to Array - arrays

I have the below snippit for excel 2013 VBA
For Each r In rr
If Not r.Range.Height = 0 Then
FNum = FNum + 1
ReDim Preserve testArr(1 To FNum, 1 To 23)
testArr(FNum) = r
End If
Next r
My goal is to get all the visible rows from a filtered table into an array.
The table can be any number of rows, but always 23 columns.
I found that the height will be zero if it is hidden. But for the life of me, I cannot figure out how to get the entire row into the array.
r = listrow
rr = listrows
YES, I know a looping redim sucks.
SpecialCells(xlCellTypeVisible)
doesnt work either because it stops at the first hidden row/column.
I may just dump the entire table into the array and then filter the array. I havent figured out how to pull the active filter from the table to apply it, but I havent looked deeply into that yet. Thats what I will be doing now, because I am stuck for the other way.
Any and all advice is welcome.
DM

To avoid REDIM or double loops you can use something like Application.WorksheetFunction.Subtotal(3, Range("A2:A500000")) to quickly count the number of visible rows.
See this question

I define my Target range using .SpecialCells(xlCellTypeVisible). Target.Cells.Count / Target.Columns.Count will give you the row count. Finally I iterate over the cells in the Target range incrementing my counters based off of the Target.Columns.Count.
Public Sub FilteredArray()
Dim Data As Variant, r As Range, Target As Range
Dim rowCount As Long, x As Long, y As Long
Set Target = WorkSheets("Sheet1").ListObjects("Table1").DataBodyRange.SpecialCells(xlCellTypeVisible)
If Not Target Is Nothing Then
rowCount = Target.Cells.Count / Target.Columns.Count
ReDim Data(1 To rowCount, 1 To Target.Columns.Count)
x = 1
For Each r In Target
y = y + 1
If y > Target.Columns.Count Then
x = x + 1
y = 1
End If
Data(x, y) = r.Value
Next
End If
End Sub

The code below will create an array for all the rows and store each of these into another array that will store all info in sheet:
Function RowsToArray()
Dim lastRow: lastRow = ActiveWorkbook.ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
Dim lastCol: lastCol = ActiveWorkbook.ActiveSheet.Cells(1, Columns.Count).End(xlToLeft).Column
Dim newArr()
ReDim newArr(lastRow)
For r = 0 To lastRow - 1
Dim rowarr()
ReDim rowarr(lastCol)
For c = 0 To lastCol - 1
rowarr(c) = Cells(r + 1, c + 1).Value
Next c
newArr(r) = rowarr
Next r
End Function

Can you loop over the cells in rr rather than the rows? If so, as #SJR says, you can only Redim Preserve the final dimension, so we're going to have to switch your dimensions. You can then use r.EntireRow.Hidden to check if we're in a visible row and increase the bound of your array by one if we are.
The following assumes that your data starts in column A:
For Each r In rr
If Not r.EntireRow.Hidden Then
If r.Column = 1 Then
If UBound(testArr, 2) = 0 Then
ReDim testArr(1 To 23, 1 To 1)
Else
ReDim Preserve testArr(1 To 23, 1 To UBound(testArr, 2) + 1)
End If
End If
testArr(r.Column, UBound(testArr, 2)) = r
End If
Next r
Edit:
Alternatively, you can keep using ListRows, but loop through twice, once to set the bounds of your array, and once to fill the array (which will have its own internal loop to run through the row...):
For Each r In rr
If Not r.Range.Height = 0 Then
Fnum = Fnum + 1
ReDim testArr(1 To Fnum, 1 To 3)
End If
Next r
Fnum = 0
For Each r In rr
If Not r.Range.RowHeight = 0 Then
Fnum = Fnum + 1
dumarray = r.Range
For i = 1 To 3
testArr(Fnum, i) = dumarray(1, i)
Next i
End If
Next r

Thanks all, a combo of answers led me to: (not very elegant, but quick)
For Each r In rr
If Not r.Range.Height = 0 Then
TNum = TNum + 1
End If
Next r
ReDim testArr(TNum, 23)
For Each r In rr
If Not r.Range.Height = 0 Then
FNum = FNum + 1
For i = 1 To 23
testArr(FNum, i) = r.Range.Cells(, i)
Next i
End If
Next r

Related

Looping through Unique Data Set to Return Matching Value on a Different Sheet

I have searched for something similar to what I am asking, and unfortunately there is nothing close to what I am looking for.
I have a unique data set here on Sheet(2): The goal is to return the values in the highlighted blue columns if it matches the same "Item#" for the box selected in a dropdown list of the box names on Sheet(1). Please see Sheet(1) here: Sheet(1) Set-Up.
The Item#'s on Sheet(1) are located in B3:B12 on Sheet(1). - I've added also another list where I would like my code to run In the column next to this is a blank where the matching items in blue would post.
I am trying to use For Loops to accomplish this. I understand that the data set is weird, but I want to keep it like that for the mere challenge of it (and also because I have a larger data set similar and am just using this as a test run)... My code so far is as follows:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
' In order to run code on sheet without a button or enabling in a module
Set KeyCells = Range("A1")
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
Dim i, j As Long
Dim n As Long
Dim box As String
Set sh2 = ThisWorkbook.Sheets(2)
Set rn2 = sh2.UsedRange
box = Sheets(1).Cells.Range("A1")
Dim k1 As Long
k1 = rn2.Rows.Count + rn2.Row - 1
n = 0
For i = 1 To k1
If Sheets(2).Cells(1, i) = box Then
If n = 0 Then
Sheets(1).Cells(3, 3).Value = Sheets(2).Cells(i, 2)
n = n + 1
End If
ElseIf n > 0 Then
For j = 3 To n + 2
If Sheets(2).Cells(2, i).Value = Sheets(1).Cells(j, 2).Value Then
If Sheets(2).Cells(2, i).Value <> Sheets(1).Cells(j, 2).Value Then
x = x
Else
x = x + 1
End If
End If
Next
If x = 0 Then
Sheets(1).Cells(3 + n, 3).Value = Sheets(2).Cells(2, i).Value
n = n + 1
End If
End If
x = 0
Next
End If
End Sub
Please let me know what you experts think!
Edit 2; the macro finds Sheet1.Range("A1").Value in Sheet2 row 1. It then loops through each cell below the found value in Sheet2. It then finds each cells value in Sheet1. It will then copy the cells value in Sheet2 from the next cell to the right, and place the value in the cell in Sheet1 to the next cell to the right. It then loops down to the next cell in sheet2, and performs the same task, etc.
Private Sub Worksheet_Change(ByVal target As Range) 'Works
Dim fndTrgt As Range, fndCel As Range
If target.Address = "$A$1" Then
Set fndTrgt = Sheets("Sheet2").Rows(1).Find(target.Value)
If Not fndTrgt Is Nothing Then
For i = 1 To 5
Set fndCel = Sheets("Sheet1").Range("A2:D12").Find(fndTrgt.Offset(i).Value)
If Not fndCel Is Nothing Then
fndCel.Offset(, 1).Value = fndTrgt.Offset(i, 1).Value
End If
Next i
End If
End If
End Sub

VBA - How to take a single column as input array then output the array removing all odd numbers

I have a very basic question, but would love to know how to do this. I want to write a function in VBA where I can highlight a column as an input, and then spit out the result somewhere else.
Thanks in advance :)
e.g. column A
--------
10
8
5
6
1
3
2
becomes:
column A
--------
10
8
6
2
I just did it from column a to b, but you probably want range as the current selection and a different output column.
Option Explicit
Sub filterlist()
Dim rng As Range
Set rng = Range("a1:a5")
Dim celluse As Range
Dim arr As Variant
For Each celluse In rng
If celluse.Value Mod 2 = 0 Then
If IsEmpty(arr) Then
arr = Array(celluse.Value)
Else
ReDim Preserve arr(UBound(arr) + 1)
arr(UBound(arr)) = celluse.Value
End If
End If
Next celluse
Dim i As Long
For i = 0 To UBound(arr)
Range("b" & i + 1) = arr(i)
Next i
End Sub
This code should do the trick.
You can enter as an array-formula directly to a sheet: {=RemoveOdds(A1:A7)} or as part of another procedure:
Sub Test()
RemoveOdds Selection
End Sub
Public Function RemoveOdds(Target As Range) As Variant
Dim vFinal() As Variant
Dim rCell As Range
Dim x As Long
ReDim vFinal(1 To Target.Cells.Count)
x = 1
For Each rCell In Target
If rCell Mod 2 = 0 Then
vFinal(x) = rCell.Value
x = x + 1
End If
Next rCell
'So missing values do not show up as 0 at bottom of array.
' Do While x <= Target.Cells.Count
' vFinal(x) = ""
' x = x + 1
' Loop
ReDim Preserve vFinal(1 To x - 1)
'RemoveOdds = vFinal 'Basic array - will place values horizontally on sheet.
RemoveOdds = Application.Transpose(vFinal) 'Will place values vertically on sheet.
End Function

Array Comparision

I have a array with 8 columns and 42 rows B2 TO I43. I have to compare this array with other arrays in the same sheet so that every array have same values. I defined array1 Rang('B2;I43") and need to compare every other array of same size. how to that in VBA.
My code is
Sub driver()
Dim array1, array2, m, n
Set array1 = Range("B2,I43")
total_rows = 42
total_cols = 8
Set array2 = Range("B44:I85")
For i = 1 To total_rows
For j = 1 To total_cols
If array1(i, j) = array2(i, j) Then
array2.Cells(i, j).Interior.ColorIndex = 0
ElseIf array1(i, j) <> array2(i, j) Then
array2.Cells(i, j).Interior.ColorIndex = 3
End If
Next j
Next i
End Sub
I want array2 to point to other set of values. Every array start after 42 rows.
Have you tried to use Conditional formatting instead? Your suggested VBA code can easily be solved with conditional formatting by comparing each cell in array2 with the same cell in array1 and use colours to mark if the cells are equal or not
Edit
I have modified your code. Instead of using two ranges I have used an "row offset" for each array you have on your sheet. It then compares the cells from your source array (array1 in your code) with the cells that are found next_array_offset rows down. When the comparison has been made, the offset is increased with 42. The loop ends when there are no more values to be found.
Is this what you was looking for?
Sub driver()
Dim r As Integer
Dim c As Integer
Dim source_row As Integer
Dim source_col As Integer
Dim total_rows As Integer
Dim total_cols As Integer
Dim next_array_offset As Integer
source_row = 2 ' row B
source_col = 2 ' col 2
total_rows = 42
total_cols = 8
next_array_offset = 42 ' distance in rows to next array
Do Until IsEmpty(Cells(source_row + next_array_offset, source_col).Value)
For r = 0 To total_rows - 1
For c = 0 To total_cols - 1
If Cells(source_row + r, source_col + c) = Cells(source_row + next_array_offset + r, source_col + c) Then
Cells(source_row + next_array_offset + r, source_col + c).Interior.ColorIndex = 0
Else
Cells(source_row + next_array_offset + r, source_col + c).Interior.ColorIndex = 3
End If
Next
Next
next_array_offset = next_array_offset + 42
Loop
End Sub
Your main task is to define the ranges accurately. In the code below I've assumed it's every 42 rows until end of data. You simply iterate your 42-row test arrays and compare against the reference array. To do this you basically need two row variables: one for your test array and one for your reference array.
The quickest way would be to read the test data just once into one big array and to create two ranges (one with matches and one with mis-matches) and then colour them at the end of the routine.
I don't know your colour palette (and therefore the color indexes) so I've used the .Color property. You can adjust this to suit.
Const ROW_COUNT As Long = 42
Const COL_COUNT As Long = 8
Const START_ROW As Long = 2
Dim refArray As Variant, testArray As Variant
Dim rowSize As Long, r As Long, c As Long, i As Long
Dim cell As Range, yesRng As Range, noRng As Range
'Read data into arrays
With Sheet1
'Find last row of data
rowSize = .Cells(.Rows.Count, "B").End(xlUp).Row
'Adjust last row to be multiple of 42
rowSize = Int((rowSize - START_ROW) / ROW_COUNT) * ROW_COUNT
refArray = .Cells(START_ROW, "B").Resize(ROW_COUNT, COL_COUNT).Value2
testArray = .Cells(START_ROW + ROW_COUNT, "B").Resize(rowSize, COL_COUNT).Value2
End With
'Compare test array with reference array
i = 1 'refArray row index
For r = 1 To UBound(testArray, 1)
For c = 1 To UBound(testArray, 2)
Set cell = Sheet1.Cells(r + START_ROW + ROW_COUNT - 1, c + 1)
If testArray(r, c) = refArray(i, c) Then
'It's a match so add to yes range
If yesRng Is Nothing Then
Set yesRng = cell
Else
Set yesRng = Union(yesRng, cell)
End If
Else
'It's a miss so add to no range
If noRng Is Nothing Then
Set noRng = cell
Else
Set noRng = Union(noRng, cell)
End If
End If
Next
'Increment ref row index or set back to 1 if at 42
i = IIf(i < ROW_COUNT, i + 1, 1)
Next
'Colour the ranges
If Not yesRng Is Nothing Then yesRng.Interior.Color = vbGreen
If Not noRng Is Nothing Then noRng.Interior.Color = vbRed

How to loop through columns and calculate averages?

data series
I want to find the average for each segment of ten values down a column. (See data series picture)
Continuously all the way to the bottom of the data set. The data set can vary in length, and the code has to be "generic" of sorts.
Based on other code segments I have tried to do this:
Sub tenthavg()
Dim currentIndex As Long
Dim myArray() As Variant
Dim rng As Range
ReDim myArray(1 To 10)
Range("b1", Range("b1").End(xlDown)).Select
Set myArray = Selection
currentIndex = 1
Do Until currentIndex + 1 > UBound(myArray)
ActiveSheet.Cells(currentIndex, "T") = AverageOfSubArray(myArray, currentIndex, 10)
currentIndex = currentIndex + 1
Loop
End Sub
'=================================================================
Function AverageOfSubArray(myArray As Variant, startIndex As Long, elementCount As Long) As Double
Dim runningTotal As Double
Dim i As Long
For i = startIndex To (startIndex + elementCount - 1)
runningTotal = runningTotal + val(myArray(i))
Next i
AverageOfSubArray = runningTotal / elementCount
End Function
Unfortunately I can't make it work. Am I approaching this the right way?
If so, what am I doing wrong?
IMHO it's not quite the successful approach ... instead of Selecting EndDown and other concepts borrowed from interactive working make use of VBA's own mechanisms.
A "generic" approch takes Range start address, batch size and offsets where to put the result as arguments ...
Sub AvgX(MyR As Range, S As Integer, ORow As Integer, OCol As Integer)
' MyR = start of range
' S = batch size
' OCol, ORow = Offsets to place result in relation to last batch value
Dim Idx As Integer, Jdx As Integer, RSum As Variant
Idx = 1
RSum = 0
Do
For Jdx = 1 To S
RSum = RSum + MyR(Idx, 1)
Idx = Idx + 1
If MyR(Idx, 1) = "" Then Exit Do
Next Jdx
MyR(Idx - 1, 1).Offset(ORow, OCol) = RSum / (Jdx - 1)
RSum = 0
Loop
End Sub
and is called by
Sub Test()
AvgX [C4], 10, 0, 1
End Sub
to give you this result ...
You can get your result in a simpler way:
Sub tenthavg()
Dim LastRow As Long
LastRow = ThisWorkbook.Sheets("Your Sheet Name").Columns(2).Find("*", SearchOrder:=xlByRows, LookIn:=xlValues, SearchDirection:=xlPrevious).Row
Dim myArray(1 To 10) As Double
If LastRow < 10 Then
MsgBox "There's not enough data!"
Else
On Error Resume Next
For x = 1 To LastRow - 9
For y = 1 To 10
myArray(y) = ThisWorkbook.Sheets("Your Sheet Name").Cells(y + x - 1, 2).Value
Next y
ThisWorkbook.Sheets("Your Sheet Name").Cells(x, 20).FormulaR1C1 = 0
ThisWorkbook.Sheets("Your Sheet Name").Cells(x, 20).FormulaR1C1 = Application.Average(myArray)
Next x
End If
End Sub
Please note: I'm assuming you're data starts at B1 and you want the output on column T.

Dynamically dimension/populate a 2D array

I have an interesting problem. I need to fill in a 2D array with data, but I don't know how many data points there are until after the array is populated.
Dim finalArray(0 to 500000, 0 to 3)
R=0
For Each index in someDictionary
If Not someDictionary.item(index)(1) = 0
finalArray(R,0) = someDictionary.item(index)(1)
finalArray(R,1) = someDictionary.item(index)(2)
finalArray(R,2) = someDictionary.item(index)(3)
R = R + 1
End If
Next index
The issue is that I don't know how many items will be in the dictionary, nor how many will be nonzero. The only way I know is after I run the loop and have a count R.
Currently I'm printing the entire 500k row array to Excel, which is usually 100-400k rows of data with the rest blank. This is ungainly, and I would like to re-dimension the array to be the correct size. I can't use ReDim because I can't delete the data, and I can't use ReDim Preserve because it's 2-dimensional and I need to reduce the rows, not columns.
How about this?
Sub Sample()
Dim finalArray()
Dim R As Long
For Each Index In someDictionary
If Not someDictionary.Item(Index)(1) = 0 Then R = R + 1
Next Index
ReDim finalArray(0 To R, 0 To 3)
R = 0
For Each Index In someDictionary
If Not someDictionary.Item(Index)(1) = 0 Then
finalArray(R, 0) = someDictionary.Item(Index)(1)
finalArray(R, 1) = someDictionary.Item(Index)(2)
finalArray(R, 2) = someDictionary.Item(Index)(3)
R = R + 1
End If
Next Index
End Sub
You have a few options
Redim the array as needed rather then create up front (which I think is better than looping through the array twice :)). I have added this given your comment to tigeravatar
Transpose the array to redim the first dimension
Ignore the redundant data as per tigeravatar
Option 1
note I have flipped the data to make the point around testing for ReDim as you go rather than define an array size up front - it could well argue this changes the nature of the question but I thought the technique worth pointing out. Option2 shows how to transpose the array regardless
Sub SloaneDog()
Dim finalArray()
Dim R As Long
Dim lngCnt As Long
Dim lngCnt2 As Long
lngCnt = 100
ReDim finalArray(1 To 3, 1 To lngCnt)
somedictionary = Range("A1:C3001")
For lngCnt2 = 1 To UBound(somedictionary, 1)
finalArray(1, lngCnt2) = "data " & lngCnt2
If lngCnt2 Mod lngCnt = 0 Then ReDim Preserve finalArray(1 To 3, 1 To lngCnt2 + lngCnt)
Next
End Sub
Option 2
Sub EddieBetts()
Dim X()
Dim Y()
Dim LngCnt As Long
ReDim X(1 To 1000, 1 To 3)
Debug.Print UBound(X, 1)
LngCnt = 100
Y = Application.Transpose(X)
ReDim Preserve Y(1 To UBound(Y, 1), 1 To LngCnt)
X = Application.Transpose(Y)
Debug.Print UBound(X, 1)
End Sub

Resources