This should be easy and I think I am almost there. I would like to count how many times an entry repeats itself within a certain array. The array will be populated from a range. Eventually if the number of the count is more than 4, I would like to insert "Excess", otherwise if less than 4, I would like to insert "Insufficient", else is "complete". Unfortunately, even though I have learnt to do these calculations without using Arrays, I find some difficulties when switching to Arrays.
How the code should look like
Sub test()
Dim MyArray() As Variant, Countarrays() As Variant, Result() As Variant
Dim r As Range
Dim rows As Integer
Worksheets("Sheet1").Activate
Set r = Range("B2", Range("B1").End(xlDown))
MyArray = Range("B2", Range("B1").End(xlDown))
rows = Range("B2", Range("B1").End(xlDown)).Count
For i = 0 To rows
For j = 0 To rows
Countarrays(i, 1) = WorksheetFunction.CountIf(r, MyArray(i))
If (Countarrays(i, 1).value) > 4 Then Result(j, 1) = "Excess"
ElseIf (Countarrays(i, 1).value) < 4 Then Result(j, 1) = "Insufficient"
ElseIf (Countarrays(i, 1).value) = 4 Then Result(j, 1) = "Complete"
Next j
Next i
End Sub
This should do the trick:
Option Explicit
Sub Test()
Dim MyArray, DictDuplicates As New Scripting.Dictionary, i As Long
With ThisWorkbook.Sheets("Sheet1") 'change if needed
MyArray = .Range(.Cells(2, 1), .Cells(2, 2).End(xlDown))
For i = LBound(MyArray) To UBound(MyArray) 'loop to store all the items and how many times do they repeat
If Not DictDuplicates.Exists(MyArray(i, 2)) Then 'if doesn't exists will store it
DictDuplicates.Add MyArray(i, 2), 1
Else 'if it does exists will increment its item value
DictDuplicates(MyArray(i, 2)) = DictDuplicates(MyArray(i, 2)) + 1
End If
Next i
For i = LBound(MyArray) To UBound(MyArray) 'loop to give back the result
Select Case DictDuplicates(MyArray(i, 2))
Case Is > 4
MyArray(i, 1) = "Excess"
Case Is = 4
MyArray(i, 1) = "Complete"
Case Is < 4
MyArray(i, 1) = "Insufficient"
End Select
Next i
.Range(.Cells(2, 1), .Cells(2, 2).End(xlDown)) = MyArray
End With
End Sub
Note that for the DictDuplicates to work, you need to check the Microsoft Scripting Runtime library.
Related
I'm trying to have a for loop that takes my weeks and then another for loop that looks at my product to calculate the sales for this year, last year and then the difference. I am getting 0's for all of the weeks except for the current week, any idea what is wrong with my code? Thanks
Sub Weekly_Recap()
Dim h, d As Worksheet
Dim myarray(), answers() As Variant
'Dim week, datarange As Range
Dim D1, i As Long
Set h = Worksheets("Helper")
Set d = Worksheets("Data")
myarray = d.Range("P2:P51")
D1 = UBound(myarray, 1)
ReDim answers(1 To D1, 1 To 3)
For i = 1 To D1
If myarray(i, 1) <= h.Range("A1") Then
For j = 1 To 17
answers(i, 1) = Application.WorksheetFunction.SumIfs(d.Range("G:G"), d.Range("B:B"), myarray(i, 1), d.Range("F:F"), h.Cells(j, 4))
answers(i, 2) = Application.WorksheetFunction.SumIfs(d.Range("H:H"), d.Range("B:B"), myarray(i, 1), d.Range("F:F"), h.Cells(j, 4))
answers(i, 3) = (answers(i, 1) - answers(1, 2)) / answers(i, 2)
If h.Cells(j, 4) = "FLAVORED/FUNCTIONAL WATER" Then
h.Range(h.Range("F2"), h.Range("F2").Offset(D1, 2)).Value = answers
ElseIf h.Cells(j, 4) = "SALTY BAGGED/CANISTER SNACKS" Then
h.Range(h.Range("K2"), h.Range("K2").Offset(D1, 2)).Value = answers
End If
Next j
End If
Next i
End Sub
Kinda hard to understand what you are trying to do. When I tried to run your code in debug mode I noticed that line with offset is overwriting data in previous lines, and that is where you are getting all 0's. In lets say loop i = 1 and j = 1 you get some value, but in i = 1 and j = 2 you don't (sumif returns 0) and then you overwrite it in variable answers and then paste it in worksheet, at the end only last one has not been overwritten.
You need swap the loops to calculate all the weeks for each product in turn otherwise the figure for week1/product1 will be overwritten by week1/product2 then week1/product3 etc.
Option Explicit
Sub Sales()
Dim arWeeks, NoOfWeeks As Long, iLastWeek As Long
Dim rngSales1 As Range, rngSales2 As Range
Dim rngWeek As Range, rngProduct As Range
Dim iWeek As Long, sProduct As String
Dim h As Worksheet, d As Worksheet
Dim j As Long, i As Long
Set h = Worksheets("Helper")
Set d = Worksheets("Data")
Set rngSales1 = d.Range("G:G")
Set rngSales2 = d.Range("H:H")
Set rngWeek = d.Range("B:B")
Set rngProduct = d.Range("F:F")
arWeeks = d.Range("P2:P51")
iLastWeek = h.Range("A1").Value
NoOfWeeks = UBound(arWeeks)
ReDim answers(1 To NoOfWeeks, 1 To 3) 'yr1,yr2,diff
For j = 1 To 17 ' products
sProduct = h.Cells(j, 4)
For i = 1 To NoOfWeeks ' weeks
iWeek = arWeeks(i, 1)
If iWeek <= iLastWeek Then
With Application.WorksheetFunction
answers(i, 1) = .SumIfs(rngSales1, rngWeek, iWeek, rngProduct, sProduct)
answers(i, 2) = .SumIfs(rngSales2, rngWeek, iWeek, rngProduct, sProduct)
End With
If answers(i, 2) <> 0 Then
answers(i, 3) = (answers(i, 1) - answers(1, 2)) / answers(i, 2)
End If
End If
Next i
If sProduct = "FLAVORED/FUNCTIONAL WATER" Then
h.Range("F2").Resize(NoOfWeeks, 3).Value = answers
ElseIf sProduct = "SALTY BAGGED/CANISTER SNACKS" Then
h.Range("k2").Resize(NoOfWeeks, 3).Value = answers
End If
Next j
MsgBox "Done ", vbInformation
End Sub
New to VBA. I'm trying to create an array of rows.
Basically, I have an entire sheet and want to take all the rows that start with a certain value ("MA") in column 8.
I eventually want to manipulate that array (as if it were a range), and paste it somewhere else in the sheet. Can anyone help? Here's my code so far:
Dim top0M As Variant
ReDim top0M(1 To 1) As Variant
For i = 4 To Rows.Count
If Cells(i, 8).Value Like "MA*" Then
top0M(UBound(top0M)) = Rows(i)
ReDim Preserve top0M(1 To UBound(top0M) + 1) As Variant
End If
Next i
This code runs but I'm not sure how to debug it to know if I even have the right rows inside. Can I paste these rows as if they were a range?
This sets the range and loads the whole into an array then it loads a different array with the lines that you want:
With ActiveSheet 'This should be changed to the name of the worksheet: Worksheets("MySheet")
Dim rng As Range
Set rng = .Range(.Cells(4, 1), .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, .Cells(4, .Columns.Count).End(xlToLeft).Column))
Dim tot As Variant
tot = rng.Value
Dim top0M As Variant
ReDim top0M(1 To Application.CountIf(.Range("H:H"), "MA*"), 1 To UBound(tot, 2)) As Variant
Dim k As Long
k = 1
Dim i As Long
For i = LBound(tot, 1) To UBound(tot, 1)
If tot(i, 8) Like "MA*" Then
Dim j As Long
For j = LBound(tot, 2) To UBound(tot, 2)
top0M(k, j) = tot(i, j)
Next j
k = k + 1
End If
Next i
End With
'to print to a sheet just assign the values:
Worksheets("sheet1").Range("A1").Resize(UBound(top0M, 1), UBound(top0M, 2)).Value = top0M
Try this code
Sub Test()
Dim x As Variant
x = ActiveSheet.Range("A4").CurrentRegion.Value
x = FilterArray(x, 8, "MA*", True)
ActiveSheet.Range("K14").Resize(UBound(x, 1), UBound(x, 2)).Value = x
End Sub
Function FilterArray(ByVal myRefArr As Variant, ByVal col As Integer, ByVal refValue As String, ByVal equal As Boolean) As Variant
Dim a As Variant
Dim i As Long
Dim j As Long
Dim n As Long
On Error Resume Next
n = 1
If refValue = "" Then
FilterArray = myRefArr
Else
ReDim a(1 To UBound(myRefArr, 1), 1 To UBound(myRefArr, 2))
For i = 1 To UBound(a, 1)
If IIf(equal, UCase(myRefArr(i, col)) Like UCase(refValue), Not (UCase(myRefArr(i, col)) Like UCase(refValue))) Then
For j = 1 To UBound(a, 2)
a(n, j) = myRefArr(i, j)
Next j
n = n + 1
End If
Next i
a = Application.Transpose(a)
ReDim Preserve a(1 To UBound(a, 1), 1 To n - 1)
a = Application.Transpose(a)
FilterArray = a
End If
On Error GoTo 0
End Function
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
The code works when I bite off a couple hundred rows at a time, but always hangs somewhere in the middle when I try to run it on 10,000.
What the code does: Looks for duplicate entries in column A, adds the values in columns c, d and e between the two rows, then deletes the original row.
Can anybody think of a more stable way to do this, or point me towards why it might be locking up?
Sub combineDelete ()
Const TEST_COLUMN As String = "A"
Dim i As Long
Dim iLastRow As Long
With ActiveSheet
iLastRow = .Cells(.Rows.Count, TEST_COLUMN).End(xlUp).Row
For i = iLastRow To 2 Step -1
If Cells(i, 1) = Cells(i - 1, 1) Then
s = Cells(i, 3).Value
t = Cells(i - 1, 3).Value
Cells(i - 1, 3) = s + t
u = Cells(i, 4).Value
v = Cells(i - 1, 4).Value
Cells(i - 1, 4) = u + v
w = Cells(i, 5).Value
y = Cells(i - 1, 5).Value
Cells(i - 1, 5) = w + y
Cells(i, 1).EntireRow.Delete
End If
Next i
End With
End Sub
Edit: Here's a link to a sample subset of the data.
Post-edit: Every one of these ideas is effective. Ron Rosenberg's solution below manages to handle it orders of magnitude faster than any solution I tinkered with. Thanks!
Start with this and let us know how things are going afterwards:
Option Explicit
Sub combineDelete()
Const TEST_COLUMN As String = "A"
Dim i As Long
Dim iLastRow As Long
Dim s As Double, t As Double, u As Double
Dim v As Double, w As Double, y As Double
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
With ActiveSheet
iLastRow = .Cells(.Rows.Count, TEST_COLUMN).End(xlUp).Row
For i = iLastRow To 2 Step -1
If .Cells(i, 1).Value2 = .Cells(i - 1, 1).Value2 Then
s = .Cells(i, 3).Value2
t = .Cells(i - 1, 3).Value2
.Cells(i - 1, 3).Value2 = s + t
u = .Cells(i, 4).Value2
v = .Cells(i - 1, 4).Value2
.Cells(i - 1, 4).Value2 = u + v
w = .Cells(i, 5).Value2
y = .Cells(i - 1, 5).Value2
.Cells(i - 1, 5).Value2 = w + y
.Rows(i).EntireRow.Delete
End If
Next i
End With
With Application
.EnableEvents = True
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Notes:
Disable screenupdating, calculations and events
Use .Value2 instead of .Value
Explicit coding
Missing references to ActiveSheet added by adding dots .
Dim all variables to avoid variants
Here is a routine that should run quite rapidly. You will note near the top of the code where to change the source and results worksheets if you want.
The work is done within VBA arrays, which will be much faster than working on the worksheet.
I create a User defined object whose properties are the contents of the TestColumn; the Maximum amount in Column B; and an array of the Sum of Columns C, D and E.
These are placed into a Collection object with the Key being the TestColumn. If there is a duplicate, the Collection object will return a 457 error, which we test for and use to combine the rows.
Finally, we write the collection object back to an array, and write that array to the worksheet.
You will use both a Class Module and a Regular Module
The original data does not need to be sorted, but you can if you want, either before or after running this macro.
Enjoy.
Class Module
Be sure to rename this module cCombo after inserting it
Rename this module **cCombo**
Option Explicit
Private pTestColumn As String
Private pMaxColumn As Double
Private pSumColumns(3 To 5) As Variant
Public Property Get TestColumn() As String
TestColumn = pTestColumn
End Property
Public Property Let TestColumn(Value As String)
pTestColumn = Value
End Property
Public Property Get MaxColumn() As Double
MaxColumn = pMaxColumn
End Property
Public Property Let MaxColumn(Value As Double)
pMaxColumn = IIf(pMaxColumn > Value, pMaxColumn, Value)
End Property
Public Property Get SumColumns() As Variant
SumColumns = pSumColumns
End Property
Public Property Let SumColumns(Value As Variant)
Dim I As Long
For I = LBound(Value) To UBound(Value)
pSumColumns(I) = pSumColumns(I) + Value(I)
Next I
End Property
Regular Module
Option Explicit
Sub combineDelete()
Const TEST_COLUMN As String = "A"
Dim vSrc As Variant, vRes As Variant, rRes As Range
Dim wsSrc As Worksheet, wsRes As Worksheet
Dim cC As cCombo, colC As Collection
Dim I As Long, J As Long, V As Variant, S As String
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2") 'could be same sheet if you want to overwrite
Set rRes = wsRes.Cells(2, 1)
'Get original data
With wsSrc
vSrc = Range(.Cells(2, TEST_COLUMN), .Cells(.Rows.Count, TEST_COLUMN).End(xlUp)).Resize(columnsize:=5)
End With
ReDim V(3 To UBound(vSrc, 2)) 'for storing rows
'Collect the data, eliminating duplicates
Set colC = New Collection
On Error Resume Next
For I = 1 To UBound(vSrc, 1)
Set cC = New cCombo
With cC
.TestColumn = vSrc(I, 1)
.MaxColumn = vSrc(I, 2)
For J = 3 To UBound(vSrc, 2)
V(J) = vSrc(I, J)
Next J
.SumColumns = V
colC.Add Item:=cC, Key:=.TestColumn
Select Case Err.Number
Case 457
Err.Clear
colC(.TestColumn).MaxColumn = .MaxColumn
colC(.TestColumn).SumColumns = .SumColumns
Case Is <> 0
Debug.Print Err.Number, Err.Description
Stop
End Select
End With
Next I
On Error GoTo 0
'Create results array
ReDim vRes(1 To colC.Count, 1 To 5)
For I = 1 To colC.Count
With colC(I)
vRes(I, 1) = .TestColumn
vRes(I, 2) = .MaxColumn
V = .SumColumns
For J = LBound(V) To UBound(V)
vRes(I, J) = V(J)
Next J
End With
Next I
'Write the results
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
.EntireColumn.ColumnWidth = 5
End With
End Sub
Working with ~10K rows would benefit immensely from a variant array but you can also make significant improvements by deleting all of the rows at once. While you could gather a Union of the rows to delete, a Range.RemoveDuplicates method is also appropriate in this case.
It is unclear on whether your data is sorted on a primary key of column A. Your current code depends upon this but I've changed the criteria check to the Excel Application object's MATCH function to accommodate unsorted data.
Your code appears to avoid text column header labels in row 1. I've used the Range.CurrentRegion property to localize the cells to be processed.
Sub combineDelete()
Const TEST_COLUMN As String = "A"
Dim i As Long, mtch As Long
'appTGGL bTGGL:=False 'uncomment this line once you have completed debugging
With ActiveSheet
With .Cells(1, 1).CurrentRegion
For i = .Rows.Count To 2 Step -1
mtch = Application.Match(.Cells(i, 1).Value, .Columns(1), 0)
If mtch < i Then
.Cells(mtch, 3) = Application.Sum(.Cells(mtch, 3), .Cells(i, 3))
.Cells(mtch, 4) = Application.Sum(.Cells(mtch, 4), .Cells(i, 4))
.Cells(mtch, 5) = Application.Sum(.Cells(mtch, 5), .Cells(i, 5))
End If
Next i
.RemoveDuplicates Columns:=1, Header:=xlYes
End With
End With
appTGGL
End Sub
Public Sub appTGGL(Optional bTGGL As Boolean = True)
With Application
.ScreenUpdating = bTGGL
.EnableEvents = bTGGL
.DisplayAlerts = bTGGL
.Calculation = IIf(bTGGL, xlCalculationAutomatic, xlCalculationManual)
.StatusBar = vbNullString
End With
Debug.Print Timer
End Sub
The use of Application.Sum(..., ...) is a trifle slower than straight addition but it has the benefit of providing error control over text values. This may or may not be a desired behavior; i.e. you might want to know when you are trying to add text to a number instead of skipping over it.
There were many places inside your With ... End With statement where you used Cells(i, 3) and not .Cells(i, 3) (note the prefix . ). If you are going to take the time to reference the Range.Parent property (and you should always do so!) then it seems a shame not to use it.
I've included a reusable 'helper' sub that 'turns off' many application environment states but left it commented. Uncomment it once you havew completed debugging for additional speed and stability.
Addendum for lookup strings with length > 255
Sub combineDelete()
Dim i As Long, mtch As Long
Dim vCOLAs As Variant, dCOLAs As Object
'appTGGL bTGGL:=False 'uncomment this line once you have completed debugging
Set dCOLAs = CreateObject("Scripting.Dictionary")
dCOLAs.CompareMode = vbTextCompare
With ActiveSheet
With .Cells(1, 1).CurrentRegion
'strings in column A may exceed 255 chars; build array and and a dictionary from array
vCOLAs = .Resize(.Rows.Count, 1).Value2
For i = UBound(vCOLAs, 1) To LBound(vCOLAs, 1) Step -1
'fast overwrite method
dCOLAs.Item(vCOLAs(i, 1)) = i
Next i
For i = .Rows.Count To 2 Step -1
mtch = dCOLAs.Item(vCOLAs(i, 1))
If mtch < i Then
.Cells(mtch, 3) = Application.Sum(.Cells(mtch, 3), .Cells(i, 3))
.Cells(mtch, 4) = Application.Sum(.Cells(mtch, 4), .Cells(i, 4))
.Cells(mtch, 5) = Application.Sum(.Cells(mtch, 5), .Cells(i, 5))
End If
Next i
.RemoveDuplicates Columns:=1, Header:=xlYes
End With
End With
Erase vCOLAs
dCOLAs.RemoveAll: Set dCOLAs = Nothing
appTGGL
End Sub
A dictionary object provides lightning fast lookups due to its unique keys. Since these are a variant type, there is no 255 character limit.
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)