I'm stuck for hours solving my case. Code included below, I'll explain my case first for better understanding and to be easier to follow.
I have created a two dimensional array that has multiple compounds and corresponding heating values for them at two temperatures- it is contained in code and the user does not have a view of it.
The user types in the compounds and percentages of the mixture into the cells, and I want the selected cells that make up the array of multiple rows and two columns to be added to the two-dimensional array and then used in the function created to calculate a certain value (which is shown in the attached screenshot).
Ultimately, I want the program to search the user's entered and selected table to match the union name with the array, which is "hidden "in the code to properly perform the algebraic operation.
Code:
Function LoopThroughArray(T, x)
Dim arr() As Variant
ReDim arr(2, 4)
arr(0, 0) = "CH4"
arr(0, 1) = 35.818
arr(0, 2) = 35.808
arr(1, 0) = "C2H6"
arr(1, 1) = 63.76
arr(1, 2) = 63.74
arr(2, 0) = "C3H8"
arr(2, 1) = 91.18
arr(2, 2) = 91.15
Dim arrUser() As Variant
ReDim arrUser(2, 4)
arrUser(0, 0) = "CH4"
arrUser(0, 1) = 0.7
arrUser(1, 0) = "C2H6"
arrUser(1, 1) = 0.3
'declare variables for the loop
Dim i As Long, j As Long
'loop for the first dimension
For i = LBound(arr, 1) To UBound(arr, 1)
'loop for the second dimension
For j = LBound(arr, 2) To UBound(arr, 2)
If T = 0 And arr(i, j) = "CH4" And arrUser(i, j) = "CH4" Then
LoopThroughArray = arr(i, j + 1) * x 'the X is concentration of CH4 selected by user
Else
If T = 25 And arr(i, j) = "CH4" And arrUser(i, j) = "CH4" Then
LoopThroughArray = arr(i, j + 2) * x 'the X is concentration of CH4 selected by user
End If
End If
Next j
Next i
End Function
Screenshot from Excel:
I am also attaching a screenshot showing the values of the table that is embedded in the code, and how the function would ultimately work.
Problem:
Currently, my code that I have written only works when the function is for a CH4 compound and the user manually select cell containing value of concentration (x in my code).
How should I modify the code so that the function/loop will search the table entered by the user, match the compound names from it with those in the built-in table in the code and calculate a value in the form: concentration (user defined, currently the x value in my code) * LHV for specific compound in desired temperatures (0 or 25 deg).
1. Edit:
What would I need to change to make the function independent of whether compounds/concentrations are entered not in columns but in rows?
2. Edit:
I changed the code a bit, where in the built-in array for compounds, the values are calculated by a polynomial that takes the temperature "T" set in the function.
a. I have created "if" conditions that inform about incorrectly entered data. I used WorksheetFunction.[...] for this (I wonder if using this option is the correct approach to the problem). When I use the function even in another worksheet, one message activates the messages from the other cells when they meet the conditions. Even when I launch the excel file the message pops up.
Question 1: how should I change the code below so that the message pops up only once, when the formula is entered (when the assumed condition is met)?
b. Question 2: how to create conditions that:
-when the temperature entered in the function is below 25 degrees for the selected compounds, a message will pop up to inform you of this (this applies to all compounds, including air),
-when the temperature entered in the function will be above 2200 degrees for the selected compounds where air will be, a message will appear informing that the temperature for air is out of range,
-if the temperature entered in the function is above 3000 degrees for the selected compounds, a message appears, informing that the temperature is out of range.
Example:
Code:
Public Function Cp_mix_t(T, compounds As Range, concentrations As Range) As Double
Dim arr() As Variant
Dim i As Long, j As Long, k As Long
Dim curRow As Range
Dim ret As Double, x As Double
TN=273
'Array of compounds and polynomials defining Cp
ReDim arr(29, 2)
arr(0, 0) = "CH4"
arr(0, 1) = -1.9E-14 * (T + TN) ^ 5 + 2.1E-10 * (T + TN) ^ 4 - 7.1E-07 * (T + TN) ^ 3 + 7.8E-04 * (T + TN) ^ 2 + 1.4 * (T + TN) ^ 1 + 1709.8
arr(20, 0) = "N2"
arr(20, 1) = -3.5E-15 * (T + TN) ^ 5 + 3.9E-11 * (T + TN) ^ 4 - 1.61E-07 * (T + TN) ^ 3 + 2.87E-04 * (T + TN) ^ 2 - 0.17 * (T + TN) ^ 1 + 1054.5
arr(28, 0) = "AIR"
arr(28, 1) = -9.8E-15 * (T + TN) ^ 5 + 8.4E-11 * (T + TN) ^ 4 - 2.7E-07 * (T + TN) ^ 3 + 4.1E-04 * (T + TN) ^ 2 - 0.16 * (T + TN) ^ 1 + 1027.9
concentrationHasRows = True
If concentrations.Columns.Count > 1 Then
concentrationHasRows = False
End If
For Each Cell In concentrations 'It checks if negative values of percentages in selected cells have been entered. If so, a warning appears and the program does not count Cp - it gives a value of 0
If Cell.Value < 0 Then
MsgBox ("A negative value was entered!")
Cp_mix_t = 0
Exit Function
End If
Next Cell
If compounds.Count <> concentrations.Count Then 'It checks if the number of entered compounds matches the number of entered percentages. If not, a message appears and the program does not count Cp - it gives a value of 0
MsgBox ("Wrong selection! Check the selected range of compounds/percentages.")
Cp_mix_t = 0
Exit Function
ElseIf WorksheetFunction.Sum(concentrations) > 1 Then 'It checks if the sum of percentages >100%. If so, a warning appears and the program does not count Cp - it gives a value of 0
MsgBox ("Sum of percentages greater than 100%!")
Cp_mix_t = 0
Exit Function
ElseIf WorksheetFunction.Sum(concentrations) > 0 And WorksheetFunction.Sum(concentrations) < 1 Then 'It checks if the sum of the percentages =100%. If yes, then only the message
MsgBox ("The sum of the percentages is not equal to 100%!")
End If
' Loop through user input rows:
k = 1
For Each m In compounds
arraycompound = Trim(UCase(m.Value2))
For i = 0 To UBound(arr, 1)
If arr(i, 0) = arraycompound Then
' x retrieves user's input of concentration:
If concentrationHasRows Then
x = concentrations.Cells(k, 1).Value2
Else
x = concentrations.Cells(1, k).Value2
End If
If T < 25 Then
MsgBox ("Temperature below 25 deg")
ElseIf T > 2200 And arr(i, 0) = "AIR" Then
MsgBox ("Temperature for air above 2200 deg ")
' ElseIf T > 3000 And arr(i, 0) = Not "AIR" Then
' MsgBox ("Temperature for compounds above 3000 deg")
End If
ret = ret + arr(i, j + 1) * x
Exit For
End If
Next
k = k + 1
Next
Cp_mix_t = ret
End Function
(Re-edited)
I have found this possible solution. The code in Data module would be:
' call the function like in =LHV(25;A9:B10;E5:E6)
Function LHV(T, compounds As Range, concentrations As Range) As Double
Dim rng1 As Range, rng2 As Range
If ThisWorkbook.done = False Then
ThisWorkbook.numMsg = 0
ThisWorkbook.done = True
For Each cell In compounds.Application.ActiveSheet.UsedRange
If cell.HasFormula Then
' change down here "LHV(" by your formula's name:
If InStr(Replace(UCase(cell.Formula), " ", ""), "LHV(") Then
v = Split(cell.Formula, ",") ' code arrives here?
pos = InStr(v(0), "(")
v(0) = Mid(v(0), pos + 1)
Set rng1 = Range(v(1))
v(2) = Replace(v(2), ")", "")
Set rng2 = Range(v(2))
cellLHV v(0), rng1, rng2
End If
End If
Next
End If
If ThisWorkbook.numMsg And 1 Then
MsgBox ("Temperature below 25 deg")
End If
If ThisWorkbook.numMsg And 2 Then
MsgBox ("Temperature for air above 2200 deg ")
End If
If ThisWorkbook.numMsg And 4 Then
' MsgBox ("Temperature for compounds above 3000 deg")
End If
If ThisWorkbook.numMsg And 8 Then
' MsgBox ("Some other message")
End If
ThisWorkbook.numMsg = 0
LHV = cellLHV(T, compounds, concentrations)
ThisWorkbook.done = False
End Function
Function cellLHV(T, compounds As Range, concentrations As Range) As Double
Dim arr() As Variant
Dim strFind As String
Dim i As Long, j As Long, k As Long
Dim curRow As Range
Dim ret As Double, x As Double
ReDim arr(2, 4)
arr(0, 0) = "CH4"
arr(0, 1) = 35.818
arr(0, 2) = 35.808
arr(1, 0) = "C2H6"
arr(1, 1) = 63.76
arr(1, 2) = 63.74
arr(2, 0) = "C3H8"
arr(2, 1) = 91.18
arr(2, 2) = 91.15
concentrationHasRows = True
If concentrations.Columns.Count > 1 Then
concentrationHasRows = False
End If
' Loop through user input rows:
k = 1
For Each m In compounds
arraycompound = Trim(UCase(m.Value2))
For i = 0 To UBound(arr, 1)
If arr(i, 0) = arraycompound Then
' x retrieves user's input of concentration:
If concentrationHasRows Then
x = concentrations.Cells(k, 1).Value2
Else
x = concentrations.Cells(1, k).Value2
End If
If T < 25 Then
ThisWorkbook.numMsg = ThisWorkbook.numMsg Or 1
ElseIf T > 2200 And arr(i, 0) = "AIR" Then
ThisWorkbook.numMsg = ThisWorkbook.numMsg Or 2
'ElseIf T > 3000 And arr(i, 0) = Not "AIR" Then
'ThisWorkbook.numMsg = ThisWorkbook.numMsg Or 4
' ElseIf ..... then
'ThisWorkbook.numMsg = ThisWorkbook.numMsg Or 8
End If
ret = ret + arr(i, j + 1) * x
Exit For
End If
Next
k = k + 1
Next
cellLHV = ret
End Function
...and the code from Data's ThisWorkbook:
Public numMsg As Long
Public done As Boolean
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
ThisWorkbook.done = False
End Sub
Also converting arr() into a global Public variable speed can be improved.
If temperature T is going to be a cell range then change the function into:
Public Function Cp_mix_t(T, compounds As Range, concentrations As Range) As Double
Dim rng1 As Range, rng2 As Range
Dim rng0 As Range
If ThisWorkbook.done = False Then
ThisWorkbook.numMsg = 0
ThisWorkbook.done = True
For Each cell In compounds.Application.ActiveSheet.UsedRange
If cell.HasFormula Then
If InStr(Replace(cell.Formula, " ", ""), "Cp_mix_t(") Then
v = Split(cell.Formula, ",")
pos = InStr(v(0), "(")
v(0) = Mid(v(0), pos + 1)
Set rng0 = Range(v(0))
Set rng1 = Range(v(1))
v(2) = Replace(v(2), ")", "")
Set rng2 = Range(v(2))
' code arrives here ?
cellCp_mix_t rng0, rng1, rng2
End If
End If
Next
End If
If ThisWorkbook.numMsg And 1 Then
MsgBox ("Temperature below 25 deg")
End If
If ThisWorkbook.numMsg And 2 Then
MsgBox ("Temperature for air above 2200 deg ")
End If
If ThisWorkbook.numMsg And 4 Then
' MsgBox ("Temperature for compounds above 3000 deg")
End If
If ThisWorkbook.numMsg And 8 Then
' MsgBox ("Some other message")
End If
ThisWorkbook.numMsg = 0
Cp_mix_t = cellCp_mix_t(T, compounds, concentrations)
ThisWorkbook.done = False
End Function
Related
Thanks for your helps,
I have two arrays: A (100k row, 10 col) and B (100k row, 12 col)
The following code (thanks to BSALV) loop through A and B => It takes really long to finish. Is there any way to speedup.
ReDim Preserve B(1 To UBound(B), 1 To UBound(B, 2) + 4)
ReDim arr(1 To UBound(B), 1 To 2)
For i = 1 To UBound(B)
iSell = B(i, 3): mysold = 0
r = Application.Match(B(i, 2), Application.Index(A, 0, 2), 0)
If IsNumeric(r) Then
For i1 = r To UBound(A)
If A(i1, 2) = B(i, 2) And A(i1, 1) <= B(i, 1) Then
x = Application.Max(0, Application.Min(A(i1, 3), iSell))
If x > 0 Then
mysold = mysold + x
iSell = iSell - x
MyValueSold = MyValueSold + x * A(i1, 4)
A(i1, 3) = A(i1, 3) - x
If A(i1, 3) <= 0 Then A(i1, 2) = "~"
End If
If A(i1, 3) > 0 Then Exit For
End If
Next
End If
arr(i, 1) = mysold: arr(i, 2) = MyValueSold
Next
This operation is really slow when using larger arrays:
r = Application.Match(B(i, 2), Application.Index(A, 0, 2), 0)
You can get much better performance just by replacing the Index/Match line with a dictionary lookup.
To illustrate:
Sub Tester()
Const NROWS As Long = 100000
Dim i As Long, r, t
Dim dict As Object
Set dict = CreateObject("scripting.dictionary")
Dim A(1 To NROWS, 1 To 10)
'populate some dummy data
For i = 1 To UBound(A, 1)
A(i, 2) = Application.RandBetween(1, NROWS)
A(i, 3) = i
Next i
'First your existing row lookup...
t = Timer
For i = 1 To 100 'only testing 100 lookups (too slow for more!)
r = Application.Match(i, Application.Index(A, 0, 2), 0)
Next i
Debug.Print "Index/Match lookup", Timer - t, "*100* lookups"
'populate a dictionary for lookups...
t = Timer
For i = 1 To NROWS
dict(A(i, 2)) = i 'mapping second column first occurences to row #
Next i
Debug.Print "Mapping done", Timer - t
'Now the dictionary lookup
t = Timer
For i = 1 To NROWS
If dict.Exists(i) Then
r = dict(i)
End If
Next i
Debug.Print "Dictionary lookup", Timer - t, NROWS & " lookups"
End Sub
Output:
Index/Match lookup 9.62 *100* lookups '<<< slow slow!
Mapping done 0.12
Dictionary lookup 0.26 100000 lookups
EDIT: changes in your existing code
Dim rngMatch As Range '<<< added
'...
'...
Set lo = Sheets("Exc").ListObjects("TBL_Buy")
Set rngMatch = lo.DataBodyRange.Columns(2) '<<< lookup range
With lo.Range
.Sort .Range("B1"), xlAscending, , .Range("A1"), xlAscending, Header:=xlYes
aBuy = lo.DataBodyRange.Value2
.Sort .Range("A1"), xlAscending, , .Range("B1"), xlAscending, Header:=xlYes
End With
'...
For i = 1 To UBound(aResult)
'...
r = Application.Match(aResult(i, 2), rngMatch, 0) '<<<
'...
'...
I am an accountant and I need to match every customer payment against the outstanding invoices every day, I found a very nice and elegant VBA code published by Michael Schwimmer in this website. https://berndplumhoff.gitbook.io/sulprobil/excel/excel-vba-solutions/accounts-receivable-problem
The code works perfect, it can automatically calculate and list the results that are added up to a specific sum. However, I would like the VBA code to returns the invoice numbers as well. The code passed an array of the values to a function for calculation and then returns the possible solution to Column E, I don't have knowledge in array so don't know how to pass the array of the invoice numbers to the function and return the results. Could anyone help? The code is as below, you can also download the excel workbook from the link I provided. Thanks in advance!
Private Sub cmbCalculate_Click()
Dim dGoal As Double
Dim dTolerance As Double
Dim dAmounts() As Double
Dim vResult As Variant
Dim m As Long
Dim n As Long
With Me
dGoal = .Range("B2")
dTolerance = .Range("C2")
ReDim dAmounts(1 To 100)
For m = 2 To 101
If (.Cells(m, 1) <> "") And (IsNumeric(.Cells(m, 1))) Then
dAmounts(m - 1) = .Cells(m, 1)
Else
ReDim Preserve dAmounts(1 To m - 1)
Exit For
End If
Next
ReDim Preserve dAmounts(1 To UBound(dAmounts) - 1)
vResult = Combinations(dAmounts, dGoal, dTolerance)
Application.ScreenUpdating = False
.Range("D3:D65536").ClearContents
.Range(.Cells(3, 4), .Cells(UBound(vResult) + 3, 4)) = vResult
Application.ScreenUpdating = True
End With
End Sub
Function Combinations( _
Elements As Variant, _
Goal As Double, _
Optional Tolerance As Double, _
Optional SoFar As Variant, _
Optional Position As Long) As Variant
Dim i As Long
Dim k As Long
Dim dCompare As Double
Dim dDummy As Double
Dim vDummy As Variant
Dim vResult As Variant
If Not IsMissing(SoFar) Then
'Sum of elements so far
For Each vDummy In SoFar
dCompare = dCompare + vDummy
Next
Else
'Start elements sorted by amount
For i = 1 To UBound(Elements)
For k = i + 1 To UBound(Elements)
If Elements(k) < Elements(i) Then
dDummy = Elements(i)
Elements(i) = Elements(k)
Elements(k) = dDummy
End If
Next
Next
Set SoFar = New Collection
End If
If Position = 0 Then Position = LBound(Elements)
For i = Position To UBound(Elements)
'Add current element
SoFar.Add Elements(i)
dCompare = dCompare + Elements(i)
If Abs(Goal - dCompare) < (0.001 + Tolerance) Then
'Goal achieved
k = 0
ReDim vResult(0 To SoFar.Count - 1, 0)
For Each vDummy In SoFar
vResult(k, 0) = vDummy
k = k + 1
Next
Combinations = vResult
Exit For
ElseIf dCompare < (Goal + 0.001 + Tolerance) Then
'Enough room for another element
'Call recursively starting with next higher amount
vResult = Combinations(Elements, Goal, Tolerance, SoFar, i + 1)
If IsArray(vResult) Then
Combinations = vResult
Exit For
Else
SoFar.Remove SoFar.Count
dCompare = dCompare - Elements(i)
End If
Else
'Amount too high
SoFar.Remove SoFar.Count
Exit For
End If
Next 'Try next higher amount
End Function
You could probably get the invoice numbers simply with a VLOOKUP but here is a VBA solution. I have changed the values in the Sofar collection from invoice amounts to the index number for that amount. That index number then gives the corresponding invoice number from a new array InvNo.
Update - Sorted by due date
Sub cmbCalculate_Click()
Dim ws As Worksheet, dAmounts() As Double, sInvno() As String
Dim i As Long, dSum As Double
Dim dtDue() As Date
Set ws = Me
i = ws.Cells(Rows.Count, "A").End(xlUp).Row
ReDim dAmounts(1 To i - 1)
ReDim sInvno(1 To i - 1)
ReDim dtDue(1 To i - 1)
' fill array
For i = 1 To UBound(dAmounts)
dAmounts(i) = ws.Cells(i + 1, "A")
sInvno(i) = ws.Cells(i + 1, "B")
dtDue(i) = ws.Cells(i + 1, "C")
dSum = dSum + dAmounts(i)
Next
' sort array
Call BubbleSort(dAmounts, sInvno, dtDue)
Dim n: For n = LBound(dAmounts) To UBound(dAmounts): Debug.Print n, dAmounts(n), sInvno(n), dtDue(n): Next
Dim dGoal As Double, dTolerance As Double, vResult As Variant
dGoal = ws.Range("D2")
dTolerance = ws.Range("E2")
' check possible
If dGoal > dSum Then
MsgBox "Error : Total for Invoices " & Format(dSum, "#,##0.00") & _
" is less than Goal " & Format(dGoal, "#,##0.00")
Else
' solve and write to sheet
vResult = Combinations2(dAmounts, sInvno, dtDue, dGoal, dTolerance)
If IsArray(vResult) Then
With ws
.Range("F3:H" & Rows.Count).ClearContents
.Range("F3").Resize(UBound(vResult), 3) = vResult
End With
MsgBox "Done"
Else
MsgBox "Cannot find suitable combination", vbCritical
End If
End If
End Sub
Function Combinations2( _
Elements As Variant, _
Invno As Variant, _
Due As Variant, _
Goal As Double, _
Optional Tolerance As Double, _
Optional SoFar As Variant, _
Optional Position As Long) As Variant
Dim i As Long, n As Long, dCompare As Double
' summate so far
If IsMissing(SoFar) Then
Set SoFar = New Collection
Else
For i = 1 To SoFar.Count
dCompare = dCompare + Elements(SoFar(i))
Next
End If
If Position = 0 Then Position = LBound(Elements)
For i = Position To UBound(Elements)
SoFar.Add CStr(i)
dCompare = dCompare + Elements(i)
' check if target achieved
If Abs(Goal - dCompare) < (0.001 + Tolerance) Then
'Goal achieved
Dim vResult As Variant
ReDim vResult(1 To SoFar.Count, 1 To 3)
For n = 1 To SoFar.Count
vResult(n, 1) = Elements(SoFar(n))
vResult(n, 2) = Invno(SoFar(n))
vResult(n, 3) = Due(SoFar(n))
Next
Combinations2 = vResult
ElseIf dCompare < (Goal + 0.001 + Tolerance) Then
'Enough room for another element
'Call recursively starting with next higher amount
vResult = Combinations2(Elements, Invno, Due, Goal, Tolerance, SoFar, i + 1)
If IsArray(vResult) Then
Combinations2 = vResult
Exit For
Else
SoFar.Remove SoFar.Count
dCompare = dCompare - Elements(i)
End If
Else
'Amount too high
SoFar.Remove SoFar.Count
Exit For
End If
Next
End Function
Sub BubbleSort(ByRef ar1 As Variant, ByRef ar2 As Variant, ByRef ar3 As Variant)
' sort both arrays
Dim d, s, i As Long, k As Long, dt As Date
For i = 1 To UBound(ar1)
For k = i + 1 To UBound(ar1)
If (ar1(k) < ar1(i)) Or _
(ar1(k) = ar1(i) _
And ar3(k) < ar3(i)) Then
d = ar1(i)
ar1(i) = ar1(k)
ar1(k) = d
s = ar2(i)
ar2(i) = ar2(k)
ar2(k) = s
dt = ar3(i)
ar3(i) = ar3(k)
ar3(k) = dt
End If
Next
Next
End Sub
Get nth match in Index
Please refer this exceljet page for function for getting nth match which is used in index function for finding the match for the nth position given by countif function as last argument of small function. Range in the countif function need to be fixed at the first cell only. So, when we copy the formula below we get relative increment in the 'n' in case of duplicate matches. So, Index function will give the incremental nth position value.
Array CSE(Control+Shift+Enter) Formula for in F3 and copy down
=INDEX(ColEResultRangeFixed,SMALL(IF(ColAValuesRangeFixed=ColEResultCriteria,ROW(ColAValuesRangeFixed)-MIN(ROW(ColAValuesRangeFixed))+1),COUNTIF($ColAValuesRangeFixedFirst,ColEResultCriteria)))
In this case.. CSE Formula in F3 and then copy down
=INDEX($B$2:$B$11,SMALL(IF($A$2:$A$11=E3,ROW($A$2:$A$11)-MIN(ROW($A$2:$A$11))+1),COUNTIF($E$3:E3,E3)))
I have a ListBox1 in UserForm1. When I send a multi-row array to a .List, everything works. But when I send only a one-row array, the values in the ListBox1 are arranged one below the other in first column. Independent of use Application.Transpose.
I tried to write a condition and a for loop, but it doesn't work.
Run-time error 381
Could not set the List property. Invalid property array index.
.AddItem cannot be used because there are more than 10 columns
Do you have any other solution?
Dim sumItem As Integer: sumItem = 0 'later between 1 and 5000
.
.
ReDim Preserve arrSort(0 To (columnCount - 1 + 2), 0 To sumItem - 1)
.
.
Call Load(UserForm1) 'to be able to manipulate components
If sumItem = 1 Then 'if only one ROW is loaded in the array
Dim qq As Byte
For qq = 0 To (columnCount - 1)
UserForm1.ListBox1.List(0, qq) = arrSort(qq, 0) 'need to fill the LISTBOX ROW here
Next qq
ElseIf sumItem > 1 Then
UserForm1.ListBox1.List = Application.Transpose(arrSort) 'if more than one ROW is filled, this works
Else
End If
UserForm1.Show
Thank's to #Tim Williams
This work for me:
Dim sumItem As Integer: sumItem = 0 'later between 1 and 5000
.
.
ReDim Preserve arrSort(0 To (columnCount - 1 + 2), 0 To sumItem - 1) 'the dimensions are reversed
.
.
Call Load(UserForm1) 'to be able to manipulate components
If sumItem = 1 Then 'if only one ROW is loaded in the array
Dim qq As Byte
Dim arrTmp(0 To 0, 0 To (columnCount - 1)) As Variant 'auxiliary array for dimension exchange
For qq = 0 To (columnCount - 1)
arrTmp(0, qq) = arrSort(qq, 0)
Next qq
UserForm1.ListBox1.List = arrSort
ElseIf sumItem > 1 Then
UserForm1.ListBox1.List = Application.Transpose(arrSort) 'if more than one ROW is filled, this works
Else
End If
UserForm1.Show
You can do something like this:
Const NUM_COLS As Long = 20
Private Sub UserForm_Activate()
Dim lstInit(0 To 0, 0 To NUM_COLS - 1), r As Long, c As Long
Me.ListBox1.ColumnCount = NUM_COLS
'fill a row of dummy data....
For c = 0 To NUM_COLS - 1
lstInit(0, c) = "R1:C" & (c + 1)
Next c
Me.ListBox1.List = lstInit
End Sub
Private Sub CommandButton1_Click()
Dim arr, c As Long, ub As Long
arr = AddARow(Me.ListBox1.List) 'get the existing listbox data and add a row
ub = UBound(arr, 1)
For c = 0 To UBound(arr, 2)
arr(ub, c) = "R" & (ub + 1) & ":C" & (c + 1) 'populate the added row
Next c
Me.ListBox1.List = arr 'refresh the listbox
End Sub
'add one "row" to a 2D array and return the new array
Function AddARow(lst)
Dim lstNew, r As Long, c As Long
ReDim lstNew(0 To UBound(lst, 1) + 1, 0 To UBound(lst, 2))
'copy existing data
For r = 0 To UBound(lst, 1)
For c = 0 To UBound(lst, 2)
lstNew(r, c) = lst(r, c)
Next c
Next r
AddARow = lstNew
End Function
This compares a Customer Name and Part Number on sheet Temp (about 50 rows) to Customer Name and Part Number on sheet Data (about 20,000 rows). If the name and number are found in Data, then the associated information from that same row in Temp is added to Data.
This works great unless a name and number in Temp are not found in Data. When that occurs, a "Subscript out of range" error is generated. To me, it seems like the code is trying to find that value from Temp, and when it cannot find it, it just gives us and throws the error.
Can the code be revised to say, "Hey, if you cannot match a value, it's okay, just skip it and keep going"?
Sub MergeRMAArray()
'##############################################################################
' Creates arrays from "Temp RMA" & "Data" sheets, then compares rows on RMA and when a match occurs,
' pastes values in temp array. After loops, temp array values paste to "Data" sheet.
'##############################################################################
' If when processed there is an error, and the highlighted section states "Subscript out of range", with i+j
' being larger than the rows shown, then one potential error could be that a part on the RMA tab is not
' present in the Data tab, so the macro keeps searching. Will need to try and fix this on the next revision.
'##############################################################################
'##############################################################################
Set Data = Worksheets("Data")
Set Temp = Sheets("Temp RMA")
Data.Activate
Dim arrA, arrB, arrC As Variant
Dim i, j, k, LastRow2 As Long
LastRow = Data.Cells(Cells.Rows.Count, "A").End(xlUp).Row
LastRow2 = Temp.Cells(Cells.Rows.Count, "A").End(xlUp).Row
arrA = Data.Range("A2:B" & LastRow)
arrB = Temp.Range("A2:H" & LastRow2)
ReDim arrC(1 To LastRow - 1, 1 To 4)
For i = LBound(arrB) To UBound(arrB)
j = 0
For k = LBound(arrA) To UBound(arrA)
If (arrB(i, 1) = arrA(i + j, 1) _
And arrB(i, 2) = arrA(i + j, 2) _
And arrC(i + j, 1) = "") Then
arrC(i + j, 1) = arrB(i, 5)
arrC(i + j, 2) = arrB(i, 6)
arrC(i + j, 3) = arrB(i, 7)
arrC(i + j, 4) = arrB(i, 8)
Exit For
End If
j = j + 1
Next k
Next i
Range("W2").Resize(UBound(arrC, 1), UBound(arrC, 2)).Value = arrC
Erase arrA, arrB, arrC
End Sub
You are copying from "Temp" but arrC size is that of "Data". Need to change the Redim Statement. Also, J will reach 20000 each time (Data rows) at that point it will also add i which and then it will go beyond 20000 (Size of arrC) and hence the "Subscript out of Range" error.
Replace redim an loop with following.
m = 0
ReDim arrC(1 To LastRow2 - 1, 1 To 4)
For i = LBound(arrB, 1) To UBound(arrB, 1)
For j = LBound(arrA, 1) To UBound(arrA, 1)
If arrB(i, 1) = arrA(j, 1) _
And arrB(i, 2) = arrA(j, 2) Then
m = m + 1
arrC(m, 1) = arrB(i, 5)
arrC(m, 2) = arrB(i, 6)
arrC(m, 3) = arrB(i, 7)
arrC(m, 4) = arrB(i, 8)
End If
Next j
Next i
Looking at the number of loops in question (50*20,000 = 1000,000) and comparisons in arrays, suggesting following procedure using Range.Find method along with Range.Offset. This will cause much less loops and comparisons compared to those in the question.
Sub MergeRMAArray()
'##############################################################################
' "Temp RMA" & "Data" sheets, compares rows on RMA and when a match occurs,
' pastes values in temp sheet Columns E:H to "Data" sheet.
'##############################################################################
Dim Data As Worksheet: Set Data = Worksheets("Data")
Dim Temp As Worksheet: Set Temp = Sheets("Temp RMA")
Dim i As Long, j As Long, x As Long, y As Long, k As Long
Dim fRG As Range, outPut As Range, rw As Range
Dim dLR As Long: dLR = Data.Cells(Cells.Rows.Count, "A").End(xlUp).Row - 1
Dim tLR As Long: tLR = Temp.Cells(Cells.Rows.Count, "A").End(xlUp).Row - 1
Dim dRG As Range: Set dRG = Data.Range("A2:B" & dLR)
Dim tRG As Range: Set tRG = Temp.Range("A2:H" & tLR)
'Find the values in Temp sheet col A & B in Data sheet Col A & B
'If found union the range from corresponding row in Temp col E to H
For i = 1 To tLR '50 loops
On Error Resume Next
Set fRG = dRG.Columns(1).Find(What:=tRG(i, 1), After:=dRG(dRG.Rows.Count, 1), _
LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not fRG Is Nothing Then
If tRG(i, 2) = fRG.Offset(0, 1) Then
If outPut Is Nothing Then
Set outPut = tRG(i, 1).Offset(0, 4).Resize(1, 4)
Else
Set outPut = Union(outPut, tRG(i, 1).Offset(0, 4).Resize(1, 4))
End If
End If
End If
Next
'Put all the outPut range values in arrC
Dim arrC
For Each Area In outPut.Areas 'max 50 loops
x = x + Area.Rows.Count
Next
y = outPut.Columns.Count
ReDim arrC(1 To x, 1 To y)
i = 0
For k = 1 To outPut.Areas.Count 'max 50*50 = 2500 loops
For Each rw In outPut.Areas(k).Rows
i = i + 1
arr = rw.Value
For j = 1 To y
arrC(i, j) = Split(Join(Application.Index(arr, 1, 0), "|"), "|")(j - 1)
Next
Next
Next
'Copy outPut values (stored in arrC) to Range("W2") in Data sheet
Data.Range("W2").Resize(x, y).Value = arrC
End Sub
I'm a student currently studying VBA in one of my classes, where the current assignment is to pull data from a .txt file and display it, as well as total it, and then grade the total. using arrays I've been successful in the first two parts using arrays, but when trying to factor in the total for a grade the array only takes the starting numbers into account. Any thoughts? Code below
Sub Categories()
Dim Locale As String, State(1 To 50) As Variant
Dim Serial(1 To 50) As Single, i As Single
Dim path As String, j As Single
Dim Score(1 To 50, 1 To 7) As Single
Dim IndexGrade(1 To 50) As Single
Dim Total(1 To 50) As Single
Locale = ActiveWorkbook.path
path = Locale & "US_States.txt"
Open path For Input As #1
For i = 1 To 50 Step 1
Input #1, Serial(i), State(i)
Sheet1.Cells(1 + i, 1).Value = Serial(i)
Sheet1.Cells(1 + i, 2).Value = State(i)
For j = 1 To 7 Step 1
Input #1, Score(i, j)
Total(i) = Total(i) + Score(i, j)
Sheet1.Cells(1 + i, 3).Value = Total(i)
Next j
Total(i) = Sheet1.Cells(1 + i, 3).Value
If 0 <= Total(i) < 100 Then
Sheet1.Cells(1 + i, 4).Value = "A"
ElseIf 100 <= Total(i) < 200 Then
Sheet1.Cells(1 + i, 4).Value = "B"
ElseIf 200 <= Total(i) < 300 Then
Sheet1.Cells(1 + i, 4).Value = "C"
ElseIf 300 <= Total(i) Then
Sheet1.Cells(1 + i, 4).Value = "D"
End If
Next i
Close #1
End Sub
Problem is with your If condition. In VBA 1 < 2 < 1 evaluates to true. That's why even if your total(i) is more than 100, it always evaluates to true and your elseif is not coming into play.
In VBA/VB6, type conversion is simply evil.
You nee to rewrite your If and elseif conditions
Example:
Sub test()
Dim x As Long
Dim y As Long
x = 101
y = 99
'/ What you are doing
If 0 <= x < 1 Then
MsgBox "This is not python."
End If
'/ How you should do it.
If y >= 0 And y < 100 Then
MsgBox "This is how you do it in VBA."
End If
End Sub