Nesting For loops in VBA - excel

Sub adress()
Dim s As Long
Dim h As Long
Dim n As Long
Dim i As Long
s = 1
n = 1
h = 1
For n = 1 To 1800
For i = 1 To 2000
If ActiveSheet.Cells(h + 1, 13) = ActiveSheet.Cells(s + 1, 32) Then
ActiveSheet.Cells(h + 1, 48) = ActiveSheet.Cells(s + 1, 36)
ActiveSheet.Cells(h + 1, 51) = ActiveSheet.Cells(s + 1, 37)
End If
s = s + 1
Next i
h = h + 1
i = 1
Next n
End Sub
This code is written to grab a value in a column of an excel spread sheet, then go to the next column and search the whole column for a matching value. Once that is found it will print the value that is in a cell in the same row of the value in the second column it found, into a cell in the same row as the original value it was trying to match.
While the inner loop works and my code will do the correct process when ran, it only does it for one value in the first column. I have tried using ranges in the For Loops, I have tried do while loops and do until loops. If i manually change the value of "h" and run the code it will progress down the column and print the correct information but i cannot get "h" to update on its own.

Nested For Next Loops
Although Exit For and turning off the two application settings are used, the first procedure still takes 45 seconds on my machine (without the 'improvements' it might take half an hour).
In the second code the inner loop is replaced with Application.Match and the operations are performed using arrays. It takes less than a second.
The Code
Option Explicit
Sub loopSlow()
Dim i As Long
Dim k As Long
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
With ActiveSheet
For i = 2 To 1801
For k = 2 To 2001
If .Cells(i, 13).Value = .Cells(k, 32).Value Then
.Cells(i, 48).Value = .Cells(k, 36).Value
.Cells(i, 51).Value = .Cells(k, 37).Value
Exit For
End If
Next k
Next i
End With
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Sub loopFast()
' Source
Const sName As String = "Sheet2"
Const sColsList As String = "AF,AJ,AK"
Const sFirstRow As Long = 2
' Destination (Lookup)
Const dName As String = "Sheet2"
Const dColsList As String = "M,AV,AY"
Const dFirstRow As Long = 2
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Declare variables.
Dim ws As Worksheet
Dim rg As Range
Dim Cols() As String
Dim cUpper As Long
Dim cOffset As Long
Dim n As Long
' Write values from Source Columns to arrays of Data Array.
Cols = Split(sColsList, ",")
cUpper = UBound(Cols)
Set ws = wb.Worksheets(sName)
Set rg = ws.Cells(ws.Rows.Count, Cols(0)).End(xlUp)
Set rg = ws.Range(ws.Cells(sFirstRow, Cols(0)), rg)
Dim Data As Variant: ReDim Data(0 To cUpper)
For n = 0 To cUpper
cOffset = ws.Columns(Cols(n)).Column - rg.Column
Data(n) = rg.Offset(, cOffset).Value
Next n
' Write values from Lookup Column to Lookup Array of Result Array.
Cols = Split(dColsList, ",")
Set ws = wb.Worksheets(dName)
Set rg = ws.Cells(ws.Rows.Count, Cols(0)).End(xlUp)
Set rg = ws.Range(ws.Cells(dFirstRow, Cols(0)), rg)
Dim Result As Variant: ReDim Result(0 To cUpper)
Result(0) = rg.Value
' Define the (remaining) Write Arrays of Result Array.
Dim ResultNew As Variant: ReDim ResultNew(1 To UBound(Result(0)), 1 To 1)
For n = 1 To cUpper
Result(n) = ResultNew
Next n
' Write values from Data Array to Write Arrays of Result Array.
Dim cIndex As Variant
Dim i As Long
For i = 1 To UBound(Result(0))
cIndex = Application.Match(Result(0)(i, 1), Data(0), 0)
If IsNumeric(cIndex) Then
For n = 1 To cUpper
Result(n)(i, 1) = Data(n)(cIndex, 1)
Next n
End If
Next i
' Write values from Write Arrays of Result Array to Destination Columns.
For n = 1 To cUpper
cOffset = ws.Columns(Cols(n)).Column - rg.Column
rg.Offset(, cOffset).Value = Result(n)
Next n
End Sub

Related

Repeat even numbers in an array VBA

I'm trying to make a macro for where a user inputs a number and the even numbers are repeated in an array. I have got the code for repeating the numbers from 0-n (n being the number inputted). However, I don't know how to go about repeating the even numbers twice.
Sub Macro3()
For n = 1 To Worksheets("Sheet1").Cells(1, 2) + 1
Cells(2, 1 + n).Select
ActiveCell.FormulaR1C1 = (n - 1)
Next
End Sub
Below is the output
Current code vs what I really want
Write an Array of Integers
Writes an array of integers between 0 and the specified value in cell B1 to a row range starting from B2. Even numbers are written twice (one worksheet).
Initial Solution
This is a slow solution meant to be educational in understanding object variables (workbook-worksheet-range), ranges (Resize, Offset), loops,...
Option Explicit
Sub WriteArrayOfIntegersRange()
Const ProcTitle As String = "Write Array of Integers Range"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
' Create a reference to the source cell.
Dim sCell As Range: Set sCell = ws.Range("B1")
' Write the value of the source cell to a variable.
Dim sValue As Variant: sValue = sCell.Value
Dim LastInteger As Long
' Validate the source cell value.
If IsNumeric(sValue) Then ' is a number
LastInteger = Abs(CLng(sValue)) ' positive ('Abs'), whole ('CLng')
Else ' is not a number
MsgBox "The value in cell '" & sCell.Address(0, 0) & "' ('" _
& sValue & "' is not a number.", vbCritical, ProcTitle
Exit Sub
End If
' Create a reference to the first destination cell.
Dim dCell As Range: Set dCell = ws.Range("B2"): dCell.Value = 0
Dim Size As Long: Size = 1
Dim n As Long
' Loop through the numbers and apply alternating row size (1 or 2)
' and column offset (2 or 1) before writing.
For n = 1 To LastInteger
Set dCell = dCell.Offset(, Size) ' define next first cell
Size = 2 - n Mod 2 ' calculate the size (Odd = 1, Even = 2)
dCell.Resize(, Size).Value = n ' write to the resized row range
Next n
' Clear the range to the right of the last cell to remove any previous data.
Dim crrg As Range
With dCell.Offset(, Size) ' define next first cell
' Define the range from the next first to the last worksheet cell
' in the row.
Set crrg = .Resize(, ws.Columns.Count - .Column + 1)
End With
crrg.Clear ' or crrg.ClearContents
MsgBox "Array of numbers written.", vbInformation, ProcTitle
End Sub
Using Arrays
This is a more advanced solution that utilizes the multi-purpose GetArrayOfIntegers function. By modifying the related constants (Function Parameters) in the following procedure, you can easily change the output.
Note that it returns the results in another worksheet (Sheet2).
The last procedure is created for anyone to quickly get a flavor of the GetArrayOfIntegers function. Just add a new workbook, add a new module and copy the codes to it. Modify the function parameters in the last procedure to get different results in the Immediate window (Ctrl+G).
Sub WriteArrayOfIntegers()
' Needs the 'GetArrayOfIntegers' function.
Const ProcTitle As String = "Write Array of Numbers"
' Source
Const sName As String = "Sheet1"
Const sCellAddress As String = "B1"
' Destination
Const dName As String = "Sheet2"
Const dfCellAddress As String = "B2"
' Function Parameters ' experiment with these five parameters
Const EvensCount As Long = 2
Const OddsCount As Long = 1
Const DoReturnRow As Boolean = True
Const IncludeZero As Boolean = True
Const IsZeroOdd As Boolean = True
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Create a reference to the source cell.
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim sCell As Range: Set sCell = sws.Range(sCellAddress)
' Write the value of the source cell to a variable.
Dim sValue As Variant: sValue = sCell.Value
Dim LastInteger As Long
' Validate the source cell value.
If IsNumeric(sValue) Then ' is a number
LastInteger = Abs(CLng(sValue)) ' positive ('Abs'), whole ('CLng')
Else ' is not a number
MsgBox "The value in cell '" & sCell.Address(0, 0) & "' ('" _
& sValue & "' is not a number.", vbCritical, ProcTitle
Exit Sub
End If
' Return the result (an array) of the 'GetArrayOfIntegers' function.
Dim Data As Variant: Data = GetArrayOfIntegers( _
LastInteger, EvensCount, OddsCount, DoReturnRow, IncludeZero, IsZeroOdd)
' Without the constants it would be:
'Data = GetArrayOfIntegers(LastInteger, 2, 1, True, True, True)
If IsEmpty(Data) Then Exit Sub
Dim drCount As Long: drCount = UBound(Data, 1)
Dim dcCount As Long: dcCount = UBound(Data, 2)
' Create a reference to the first destination cell.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim dfCell As Range: Set dfCell = dws.Range(dfCellAddress)
' Clear all cells next to (to the right of) and below
' the first destination cell.
Dim dcrg As Range: Set dcrg = dfCell.Resize( _
dws.Rows.Count - dfCell.Row + 1, dws.Columns.Count - dfCell.Column + 1)
dcrg.Clear ' or dcrg.ClearContents
' Create a reference to the destination range.
Dim drg As Range: Set drg = dfCell.Resize(drCount, dcCount)
' Write the values from the array to the destination range.
drg.Value = Data
MsgBox "Array of numbers written.", vbInformation, ProcTitle
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Author: VBasic2008
' Dates: 20211101
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns an array of integers in a 2D one-based array.
' Remarks: The first element is always 0 or 1.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetArrayOfIntegers( _
ByVal LastInteger As Long, _
Optional ByVal EvensCount As Long = 1, _
Optional ByVal OddsCount As Long = 1, _
Optional ByVal DoReturnRow As Boolean = False, _
Optional ByVal IncludeZero As Boolean = False, _
Optional ByVal IsZeroOdd As Boolean = False) _
As Variant
Dim eoArr() As Long: ReDim eoArr(0 To 1)
eoArr(0) = EvensCount: eoArr(1) = OddsCount
Dim zCount As Long
If IncludeZero Then
If IsZeroOdd Then zCount = OddsCount Else zCount = EvensCount
End If
Dim iMod As Long: iMod = LastInteger Mod 2
Dim eCount As Long: eCount = Int(LastInteger / 2)
Dim oCount As Long: oCount = Int(LastInteger / 2) + iMod
Dim dtCount As Long
dtCount = eCount * EvensCount + oCount * OddsCount + zCount
Dim Data As Variant
Dim dt As Long: dt = 1
Dim n As Long
Dim r As Long
If DoReturnRow Then
ReDim Data(1 To 1, 1 To dtCount)
If zCount > 0 Then
For dt = 1 To zCount: Data(1, dt) = 0: Next dt
End If
For n = 1 To LastInteger
For r = 1 To eoArr(n Mod 2)
Data(1, dt) = n
dt = dt + 1
Next r
Next n
Else
ReDim Data(1 To dtCount, 1 To 1)
If zCount > 0 Then
For dt = 1 To zCount: Data(dt, 1) = 0: Next dt
End If
For n = 1 To LastInteger
For r = 1 To eoArr(n Mod 2)
Data(dt, 1) = n
dt = dt + 1
Next r
Next n
End If
GetArrayOfIntegers = Data
End Function
' This is an unrelated example to play with.
' Note that changing the fourth parameter will make no difference since
' the results are written to the Immediate window (Ctrl+G).
Sub GetArrayOfIntegersTEST()
' Needs the 'GetArrayOfIntegers' function.
Dim Data As Variant: Data = GetArrayOfIntegers(4, 3, 2, False, False, False)
Dim r As Long, c As Long
For r = 1 To UBound(Data, 1)
For c = 1 To UBound(Data, 2)
Debug.Print Data(r, c)
Next c
Next r
End Sub
Fast alternative via ArrayList
Working with an ArrayList (disposing btw of methods like .Sort,.Remove, .Insert, .Reverse) may be a convenient way to manipulate array data in a very readable way. It is not part of VBA, but can be accessed easily via late binding (referring to .Net library mscorlib.dll).
Option Explicit ' code module head
Sub DoubleEvenNumbersGreaterOne()
'a) define upper limit
Dim ws As Worksheet
Set ws = Sheet1 ' << change to project's sheet Code(Name)
Dim Limit As Long
Limit = ws.Range("B1")
'b) declare ArrayList
Dim arr As Object ' late bind .Net mscorlib.dll
Set arr = CreateObject("System.Collections.ArrayList")
'c) populate list array
arr.Add 0 ' start adding with zero
Dim i As Long
For i = 1 To Limit ' loop through sequence 1:Limit
arr.Add i ' add current number
If i Mod 2 = 0 Then arr.Add i ' additional even number
Next
'd) get array
Dim a As Variant: a = arr.ToArray ' change ArrayList object to VBA array
'Debug.Print Join(a, "|") ' optional check in VB Editor's immediate window
'e) write 0-based 1-dim array to ws (here: Sheet1) or declare another target worksheet (e.g. ws2)
With ws.Range("B2")
.EntireRow = vbNullString ' empty target row
.Resize(1, UBound(a) + 1) = a ' write values into correct number of cells
End With
End Sub
A formula oriented approach // late edit as of 11/1 2021
a) A first and very elementary way would be to
enter formula =COLUMN(A1)-INT((COLUMN(A1)+2)/3) into cell B2 and to
copy into the right neighbour cells as long as you get the wanted maximum
b) Refining this approach you can code the following udf accepting the wanted maximum as argument (note that I changed the flat Column reference to a vertical Row reference to simplify calculation of the actual maxima):
Function Sequ(ByVal maxNo As Long)
Dim myFormula As String
myFormula = Replace("=ROW(1:$)-INT((ROW(1:$)+2)/3)", "$", maxNo + maxNo \ 2 + 1)
Sequ = Application.Transpose(Evaluate(myFormula))
End Function
A direct formula entry of e.g. =Sequ(10) into B2 benefitting from the newer dynamic features of vers. 2019+/MS 365 would display all (row) elements automatically in a so called spill range without need of further inputs.
Using VBA to display results in VB Editor's immediate window could be coded as follows: Debug.Print Join(Sequ(10), "|") resulting in
0|1|2|2|3|4|4|5|6|6|7|8|8|9|10|10
or to assign the results to a variable that can be used in further code.
Your code is really ok, just add question is number even and one more variable to see where to write. Also just change n loop from 0:
Sub Macro3()
For n = 0 To Worksheets("Sheet1").Cells(1, 2)
a = a + 1
Cells(2, 2 + a).Select
ActiveCell.FormulaR1C1 = n
'check if number is even and check if a > 1 because we don't want to repeat 0
If n Mod 2 = 0 And a > 1 Then
a = a + 1
Cells(2, 2 + a).Select
ActiveCell.FormulaR1C1 = n
End If
Next
End Sub
Try this code
Sub Test()
Dim v, ws As Worksheet, i As Long, ii As Long, n As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
v = ws.Range("B1").Value
If Not IsNumeric(v) Or IsEmpty(v) Then MsgBox "Must Be Number", vbExclamation: Exit Sub
ReDim a(1 To (v / 2) + v)
For i = 1 To v
If i Mod 2 = 0 Then
For ii = 1 To 2
n = n + 1: a(n) = i
Next ii
Else
n = n + 1: a(n) = i
End If
Next i
Range("C2").Resize(, UBound(a)).Value = a
End Sub

Find range subset combinations from main range in Excel via VBA

Here are the particulars. Let's say you have a 10 X 10 range of cells- A1:J10 will work fine. What I'm trying to find via VBA is how many pairs of non intersecting ranges containing 5 cells and 4 cells can be derived from the main range of A1:J10. For example, A1:A5 (5 cell subset) and A6:A9 (4 cell subset) would be a valid pair. A1:E5 and A1:A4 would not be a valid pair due to the intersection of ranges at A1. I'm mostly just looking for the number of valid pairs, but if somebody can show how to physically list all the pairs on a worksheet as well that would help out.
Thanks in advance for the help!
Range Subset Combinations
Option Explicit
Sub writeRangePairs()
' Source
Const wsName As String = "Sheet1"
Const srcRange = "A1:J10"
Const RC1 As Long = 5
Const RC2 As Long = 4
' Target
Const tgtFirstCell As String = "L1"
' Other
Dim wb As Workbook
Set wb = ThisWorkbook
' Define Source Range.
Dim ws As Worksheet
Set ws = wb.Worksheets(wsName)
Dim rng As Range
Set rng = ws.Range(srcRange)
' Write all possible range addresses
' to Source Arrays ('Source1', 'Source2').
Dim Source1 As Variant
Source1 = getOneRowOneCol(rng, RC1)
Dim Source2 As Variant
Source2 = getOneRowOneCol(rng, RC2)
' Define Target Array. It is too big,
' but It cannot be bigger than UB1 * UB2.
Dim Target As Variant
ReDim Target(1 To UBound(Source1) * UBound(Source2), 1 To 3)
Dim i As Long
Dim k As Long
Dim m As Long
' Write values from Source Arrays to Target Array.
For i = 1 To UBound(Source1)
For k = 1 To UBound(Source2)
If Intersect(ws.Range(Source1(i, 1)), ws.Range(Source2(k, 1))) _
Is Nothing Then
m = m + 1
' Write first range address.
Target(m, 1) = Source1(i, 1)
' Write second range address.
Target(m, 2) = Source2(k, 1)
' Write both addresses as one range address. This column
' will contain duplicates which could be removed
' by using a dictionary.
Target(m, 3) = Union(ws.Range(Source1(i, 1)), _
ws.Range(Source2(k, 1))).Address(0, 0)
End If
Next k
Next i
' Write values from Target Array to Target Range. Since Target Array
' is too big, we are using 'm' instead of 'UBound(Target, 1)'.
ws.Range(tgtFirstCell).Resize(m, UBound(Target, 2)).Value = Target
End Sub
Function getOneRowOneCol(SourceRange As Range, _
ByVal NumberOfCells As Long) _
As Variant
Dim r As Long
r = SourceRange.Rows.Count
Dim c As Long
c = SourceRange.Columns.Count
Dim NoRs As Long
NoRs = r * (c - NumberOfCells + 1)
Dim NoCs As Long
NoCs = c * (r - NumberOfCells + 1)
Dim Data As Variant
ReDim Data(1 To NoRs + NoCs, 1 To 1)
Dim rng As Range
Dim i As Long
Dim j As Long
Dim k As Long
' Rows
For i = 1 To r
For j = 1 To c - NumberOfCells + 1
Set rng = SourceRange.Cells(i, j).Resize(, NumberOfCells)
k = k + 1
Data(k, 1) = rng.Address(0, 0)
Next j
Next i
' Columns
For j = 1 To c
For i = 1 To r - NumberOfCells + 1
Set rng = SourceRange.Cells(i, j).Resize(NumberOfCells)
k = k + 1
Data(k, 1) = rng.Address(0, 0)
Next i
Next j
getOneRowOneCol = Data
' Debug.Print UBound(Data)
' For i = 1 To UBound(Data)
' Debug.Print Data(i, 1)
' Next i
End Function

Getting the maximum value of a specific column in a 2d array [duplicate]

I use the code hereunder to calculate max values as described in this post (vba max value of group of values). The code works great but once I have more than 65k lines I get a data type mismatch when trying to pase the array:
sht.Range(Cells(1, lColumn), Cells(last.Row, lColumn)).Value = Application.Index(groupsArray, , lColumn)
Could somebody help me to slice the array in chunks. I have tried to get it working myself but without any luck.
Sub FillGroupsMax()
Dim lColumn As Long
Dim sht As Worksheet
Dim groupsArray As Variant 'array with all group infomation
Dim groupsSeen As Variant 'array with group infomation already seen
Application.ScreenUpdating = False 'stop screen updating makes vba perform better
Set sht = ThisWorkbook.Worksheets("import")
Set last = sht.Range("A:A").Find("*", Cells(1, 1), searchdirection:=xlPrevious) 'last cell with value in column A
lColumn = sht.Cells(1, Columns.Count).End(xlToLeft).Column
groupsArray = sht.Range(Cells(1, 1), Cells(last.Row, lColumn))
'collect all the information on the Sheet into an array
'Improves performance by not visiting the sheet
For dRow = 2 To last.Row 'for each of the rows skipping header
'check if group as already been seen
If inArrayValue(Cells(dRow, 1).Value, groupsSeen) > 0 Then
'if it has been seen/calculated attribute value
'Cells(dRow, 4).Value = inArrayValue(Cells(dRow, 1).Value, groupsSeen)
groupsArray(dRow, lColumn) = inArrayValue(Cells(dRow, 1).Value, groupsSeen)
Else
'if it hasn't been seen then find max
'Cells(dRow, 4).Value = getMax(Cells(dRow, 1).Value, groupsArray)
groupsArray(dRow, lColumn) = getMax(Cells(dRow, 1).Value, groupsArray, lColumn)
'array construction from empty
If IsEmpty(groupsSeen) Then
ReDim groupsSeen(0)
'groupsSeen(0) = Array(Cells(dRow, 1).Value, Cells(dRow, 4).Value)
groupsSeen(0) = Array(groupsArray(dRow, 1), groupsArray(dRow, lColumn))
'attribute value to array
Else
ReDim Preserve groupsSeen(0 To UBound(groupsSeen) + 1)
groupsSeen(UBound(groupsSeen)) = Array(groupsArray(dRow, 1), groupsArray(dRow, lColumn))
End If
End If
Next
sht.Range(Cells(1, lColumn), Cells(last.Row, lColumn)).Value = Application.Index(groupsArray, , lColumn)
'reactivate Screen updating
Application.ScreenUpdating = True
End Sub
Function getMax(group As String, groupsArray As Variant, lColumn As Long) As Double
'for each in array
For n = 1 To UBound(groupsArray)
'if its the same group the Max we seen so far the record
If groupsArray(n, 1) = group And groupsArray(n, lColumn - 1) > maxSoFar Then
maxSoFar = groupsArray(n, lColumn - 1)
End If
Next
'set function value
getMax = maxSoFar
End Function
Function inArrayValue(group As String, groupsSeen As Variant) As Double
'set function value
inArrayValue = 0
'if array is empty then exit
If IsEmpty(groupsSeen) Then Exit Function
'for each in array
For n = 0 To UBound(groupsSeen)
'if we find the group
If groupsSeen(n)(0) = group Then
'set function value to the Max value already seen
inArrayValue = groupsSeen(n)(1)
'exit function earlier
Exit Function
End If
Next
End Function
You can write a helper function to use instead of Application.Index
Bonus - it will be much faster than using Index (>5x)
Sub Tester()
Dim arr, arrCol
arr = Range("A2:J80000").Value
arrCol = GetColumn(arr, 5) '<< get the fifth column
Range("L2").Resize(UBound(arrCol, 1), 1).Value = arrCol
End Sub
'extract a single column from a 1-based 2-D array
Function GetColumn(arr, colNumber)
Dim arrRet, i As Long
ReDim arrRet(1 To UBound(arr, 1), 1 To 1)
For i = 1 To UBound(arr, 1)
arrRet(i, 1) = arr(i, colNumber)
Next i
GetColumn = arrRet
End Function
EDIT - since QHarr asked about timing here's a basic example
Sub Tester()
Dim arr, arrCol, t, i as long
arr = Range("A2:J80000").Value
t = Timer
For i = 1 to 100
arrCol = GetColumn(arr, 5) '<< get the fifth column
Next i
Debug.print Timer - t '<<# of seconds for execution
End Sub
Below, whilst not as tidy as could be, is a way to process an array in chunks and Index to access a column and write out to the sheet.
I populated two columns (A:B) with data. Both had 132,000 rows, populated incrementally, with values from 1 to 132,000 in each column for my test run.
You can fiddle with cutOff to get the chunk size just below the point where the fail happens.
The code below is simply to demonstrate the principle of looping in batches, upto the set cutoff in each batch, until all rows have been processed.
Option Explicit
Public Sub WriteArrayToSheet()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ThisWorkbook
Set sht = wb.Worksheets("Sheet1") 'change as appropriate
Dim myArr() 'dynamic array
myArr = sht.Range("A1").CurrentRegion.Value 'you may want a more robust method
Dim cutOff As Long 'the max value - what ever it is before error occurs
cutOff = 1000
Dim totalRows As Long 'total rows in array read in from sheet
totalRows = UBound(myArr, 1)
Dim totalArraysNeeded As Long
'Determine how many lots of cutOff chunks there are in the total number of array rows
totalArraysNeeded = Application.WorksheetFunction.Ceiling(totalRows / cutOff, 1)
Dim rotations As Long 'number of times to loop original array to handle all rows
Dim rowCountTotal As Long
Dim rowCount As Long
Dim tempArr() 'this will hold the chunk of the original array
Dim rowCounter As Long
Dim lastRow As Long
Dim nextRow As Long
Dim i As Long
Dim j As Long
Dim numRows As Long
rotations = 1
Do While rotations < totalArraysNeeded
If rotations < totalArraysNeeded - 1 Then
ReDim tempArr(1 To cutOff, 1 To UBound(myArr, 2)) 'size chunk array
numRows = cutOff
Else
numRows = totalRows - rowCountTotal
ReDim tempArr(1 To numRows, 1 To UBound(myArr, 2)) 'size chunk array
End If
For i = 1 To numRows
rowCount = 1 'rows in this chunk looped
rowCountTotal = rowCountTotal + 1 'rows in original array looped
For j = LBound(myArr, 2) To UBound(myArr, 2)
tempArr(i, j) = myArr(rowCountTotal, j)
Next j
rowCount = rowCount + 1
Next i
With sht
lastRow = .Cells(.Rows.Count, "E").End(xlUp).Row 'Column where I am writing the sliced column out to
End With
If lastRow = 1 Then
nextRow = 1
Else
nextRow = lastRow + 1
End If
sht.Range("E" & nextRow).Resize(UBound(tempArr, 1), 1) = Application.Index(tempArr, , 1) 'write out to sheet
rotations = rotations + 1
Loop
End Sub
As #Tim suggested, the best way to slice a large array is use a loop to copy the column.
Though in your case, most of the processing time is spent on computing the maximum since your code is using a nested loop.
If you want to reduce significantly the processing time, then use a dictionary:
Sub Usage
GetMaxByGroupTo _
sourceGroups := ThisWorkbook.Range("Sheet1!A2:A100"), _
sourceValues := ThisWorkbook.Range("Sheet1!B2:B100"), _
target := ThisWorkbook.Range("Sheet1!C2")
End Sub
Sub GetMaxByGroupTo(sourceGroups As Range, sourceValues As Range, target As Range)
Dim dict As Object, groups(), values(), r As Long, max
Set dict = CreateObject("Scripting.Dictionary")
groups = sourceGroups.Value2
values = sourceValues.Value2
' store the maximum value of each group in a dictionary for an efficient lookup '
For r = Lbound(groups) to Ubound(groups)
max = dict(groups(r, 1))
If VarType(max) And values(r, 1) <= max Then Else dict(groups(r, 1)) = values(r, 1)
Next
' build and copy the result array to the sheet '
For r = Lbound(groups) to Ubound(groups)
values(r, 1) = dict(groups(r, 1))
Next
target.Resize(Ubound(groups), 1).Value2 = values
End Sub

Remove rows from a 2d array if value in column is empty

I have a large table of lab measurement logs, which I work with using arrays.
(Im a chemist, a lab technician and Ive started to learn VBA only last week, please bear with me.)
Im trying to figure out, how to load the table into an array and then remove rows with an empty value in the 5th column so that I can "export" the table without blanks in the 5th column via an array into a different sheet.
I first tested this with some code I found for a 1D array, where I would make 2 arrays, one placeholder array which Id loop through adding only non-blanks to a second array.
For Counter = LBound(TestArr) To UBound(TestArr)
If TestArr(Counter, 1) <> "" Then
NoBlankSize = NoBlankSize + 1
NoBlanksArr(UBound(NoBlanksArr)) = TestArr(Counter, 1)
ReDim Preserve NoBlanksArr(0 To UBound(NoBlanksArr) + 1)
End If
Next Counter
It works in 1D, but I cant seem to get it two work with 2 dimensions.
Heres the array Im using for reading and outputting the data
Sub ArrayTest()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim TestArray() As Variant
Dim Dimension1 As Long, Dimension2 As Long
Sheets("Tracker").Activate
Dimension1 = Range("A3", Range("A2").End(xlDown)).Cells.Count - 1
Dimension2 = Range("A2", Range("A2").End(xlToRight)).Cells.Count - 1
ReDim TestArray(0 To Dimension1, 0 To Dimension2)
'load into array
For Dimension1 = LBound(TestArray, 1) To UBound(TestArray, 1)
For Dimension2 = LBound(TestArray, 2) To UBound(TestArray, 2)
TestArray(Dimension1, Dimension2) = Range("A4").Offset(Dimension1, Dimension2).Value
Next Dimension2
Next Dimension1
Sheets("Output").Activate
ActiveSheet.Range("A2").Select
'read from array
For Dimension1 = LBound(TestArray, 1) To UBound(TestArray, 1)
For Dimension2 = LBound(TestArray, 2) To UBound(TestArray, 2)
ActiveCell.Offset(Dimension1, Dimension2).Value = TestArray(Dimension1, Dimension2)
Next Dimension2
Next Dimension1
Erase TestArray
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Thank you for any help in advance.
The Redim Preserve statement does not work for two-dimensional arrays if you want to change the number of records (rows).
You could load the range into an array, and then when you want to export the array to another range, loop through that array while skipping blank records.
An example:
Option Explicit
Sub ArrayTest()
Dim wb As Workbook, wsInput As Worksheet, wsOutput As Worksheet
Dim myArr As Variant
Dim i As Long, k As Long, LRow As Long
Set wb = ThisWorkbook
Set wsInput = wb.Sheets("Tracker")
Set wsOutput = wb.Sheets("Output")
LRow = wsOutput.Cells(wsOutput.Rows.Count, "A").End(xlUp).Row + 1
'Load a range into the array (example range)
myArr = wsInput.Range("A1:Z100")
'Fill another range with the array
For i = LBound(myArr) To UBound(myArr)
'Check if the first field of the current record is empty
If Not Len(myArr(i, 1)) = 0 Then
'Loop through the record and fill the row
For k = LBound(myArr, 2) To UBound(myArr, 2)
wsOutput.Cells(LRow, k) = myArr(i, k)
Next k
LRow = LRow + 1
End If
Next i
End Sub
From your code, it appears you want to
test a column of data on a worksheet to see if there are blanks.
if there are blanks in the particular column, exclude that row
copy the data with the excluded rows to a new area
You can probably do that easier (and quicker) with a filter: code below checking for blanks in column2
Option Explicit
Sub removeCol2BlankRows()
Dim wsSrc As Worksheet, wsRes As Worksheet
Dim rSrc As Range, rRes As Range
Set wsSrc = ThisWorkbook.Worksheets("sheet1")
Set rSrc = wsSrc.Cells(1, 1).CurrentRegion 'many ways to do this
Set wsRes = ThisWorkbook.Worksheets("sheet1")
Set rRes = wsRes.Cells(1, 10)
If wsSrc.AutoFilterMode = True Then wsSrc.AutoFilterMode = False
rSrc.AutoFilter field:=2, Criteria1:="<>"
rSrc.SpecialCells(xlCellTypeVisible).Copy rRes
wsRes.AutoFilterMode = False
End Sub
If you really just want to filter the VBA arrays in code, I'd store the non-blank rows in a dictionary, and then write it back to the new array:
Option Explicit
Sub removeCol2BlankRows()
Dim testArr As Variant
Dim noBlanksArr As Variant
Dim myDict As Object
Dim I As Long, J As Long, V
Dim rwData(1 To 4) As Variant
With ThisWorkbook.Worksheets("sheet1")
testArr = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=4)
End With
Set myDict = CreateObject("Scripting.Dictionary")
For I = 1 To UBound(testArr, 1)
If testArr(I, 2) <> "" Then
For J = 1 To UBound(testArr, 2)
rwData(J) = testArr(I, J)
Next J
myDict.Add Key:=I, Item:=rwData
End If
Next I
ReDim noBlanksArr(1 To myDict.Count, 1 To 4)
I = 0
For Each V In myDict.keys
I = I + 1
For J = 1 To 4
noBlanksArr(I, J) = myDict(V)(J)
Next J
Next V
End Sub

Write array to the worksheet and repeat it n times

I am working on the code where I want to write 2 arrays (assigned in 'Input sheet) to 'Output' sheet n times, i.e. specifically 2 times in the loop. I want to use arrays because the range of the ids and its names can change (it can be much more).
To start with a simple example (with a small amount of data), the arrays are assigned acc. to data in 'Input' sheet:
These 2 arrays should be written to 'Output' sheet n times i.e.; They should be written once and then again in the loop i.e. 2 times. I want to do it in the loop to give it the flexibility of writing in the future e.g. 3, 4, n times. In this example, I do it 2 times. Before each written array, there should be written a heading 'Title' and at the end of the written array should written text 'Total', therefore this is my desired outcome:
My code works only to write the 2 arrays for the first time but it does not write these 2 arrays for 2nd time. Instead, I am getting something else which is wrong:
This is my code:
Sub Write1()
Dim r As Long
Dim c As Long
Dim Start_Row As Long
Dim End_Row As Long
Dim main As Integer
Dim lngRowCount As Long
Dim w_Output As Worksheet
Dim w1 As Worksheet
Dim intLastRow As Integer
Const RowStart As Integer = 3
Const ColumnID As Integer = 1
Const Column_Desc As Integer = 3
Dim arrID() As Variant
Dim arrDesc() As Variant
With ThisWorkbook
Set w1 = .Sheets("Input")
Set w_Output = .Sheets("Output")
End With
'***********************************
'arrays
With w1
intLastRow = .Cells(Rows.Count, 1).End(xlUp).Row
'IntLastCol = .Cells(4, Columns.Count).End(xlToLeft).Column
arrID = .Range(.Cells(RowStart, ColumnID), .Cells(intLastRow, ColumnID))
arrDesc = .Range(.Cells(RowStart, Column_Desc), .Cells(intLastRow, Column_Desc))
'******************************************
main = 1
End_Row = 2 'this is the 2nd iteration to write arrays
For Start_Row = 1 To End_Row
w_Output.Cells(main, 3) = "Title"
main = main + 1
For r = 1 To UBound(arrID, 1)
If Len(arrID(r, 1)) > 0 Then
'Write
w_Output.Cells(r + 1, 3) = arrID(r, 1)
w_Output.Cells(r + 1, 4) = arrDesc(r, 1)
End If
main = main + 1
w_Output.Cells(main, 3) = "Total "
Next r
main = main + 4
Next Start_Row
End With
MsgBox "Done", vbInformation
End Sub
Does anybody know what I do wrong in my loop to make it work?
I have figured it out, it turns out the I was simply supposed to use 'main' as the row to write to the sheet and not 'r' which is used for the arrays - this is part of the code where arrays are written to the sheet.
Sub Write1()
Dim r As Long
Dim c As Long
Dim d As Long
Dim Start_Row As Long
Dim End_Row As Long
Dim main As Integer
Dim lngRowCount As Long
Dim w_Output As Worksheet
Dim w1 As Worksheet
Dim intLastRow As Integer
Dim IntLastCol As Integer
Const RowStart As Integer = 3
Const ColumnID As Integer = 1
Const Column_Desc As Integer = 3
Dim arrID() As Variant
Dim arrDesc() As Variant
With ThisWorkbook
Set w1 = .Sheets("Input")
Set w_Output = .Sheets("Output")
End With
'***********************************
'arrays
With w1
intLastRow = .Cells(Rows.Count, 1).End(xlUp).Row
arrID = .Range(.Cells(RowStart, ColumnID), .Cells(intLastRow, ColumnID))
arrDesc = .Range(.Cells(RowStart, Column_Desc), .Cells(intLastRow, Column_Desc))
'******************************************
main = 1
End_Row = 2
For Start_Row = 1 To End_Row
w_Output.Cells(main, 3) = "Title"
main = main + 1
For r = 1 To UBound(arrID, 1)
If Len(arrID(r, 1)) > 0 Then
'Write
w_Output.Cells(main, 3) = arrID(r, 1)
w_Output.Cells(main, 4) = arrDesc(r, 1)
End If
main = main + 1
Next r
w_Output.Cells(main, 3) = "Total "
main = main + 4
Next Start_Row
End With
MsgBox "Done", vbInformation
End Sub
It works perfectly.

Resources