Converting input range to array in function - vba

I have been learning VBA and thought I was getting the hang of it, but for some reason this basic task is eluding me. I am trying to create a function where I select an input region of numbers (really only one row or column), and then output the summation of the numbers in a cell. Here is my code:
Function CashFlow(CashArray As Excel.Range)
Dim cashflows() As Variant
cashflows = CashArray.Value
amount = CashArray.Rows.Count
dim y()
redim y(amount)
Sum = 0
For i = 1 To amount
y(i) = cashflows(i)
Sum = Sum + y(i)
Next i
CashFlow = Sum
End Function
Despite me knowing how to do essentially this in a subroutine, the fact that I'm getting my data from the function's input is throwing me off. How do I accomplish this task?

Just to summarize the above comments, please give the following code a try:
Option Explicit
Function CashFlow(CashArray As Excel.Range)
Dim sum As Double
Dim x As Long, y As Long
Dim cashflows() As Variant
cashflows = CashArray.Value
sum = 0
For y = LBound(cashflows, 1) To UBound(cashflows, 1)
For x = LBound(cashflows, 2) To UBound(cashflows, 2)
sum = sum + cashflows(y, x)
Next x
Next y
CashFlow = sum
End Function
Note, that this code summarizes all cells in the given range (even if there are multiple rows and columns in that range). Basically, the range is stored in the array cashflows. As mentioned in the comments, this will yield a two-dimensional array. The function LBound(cashflows) will give you the Lower Boundary of that array.
By specifying LBound(cashflows, 1) I am specifically asking for the lower boundary of the first dimension (in this case the rows). UBound returns the Upper Boundary of that array for the specified dimension. Similarily UBound(cashflows, 2) returns the last column (second dimension) of the given array.
Remark: I am always using LBound() and UBound() in my code instead of starting with 0 or 1 to avoid coding errors. Some people prefer Option Base 0 and some prefer Option Base 1. While neither will have an impact on the above (as ranges always yield an array starting with 1) it is just good coding practice to prevent bugs in the future.

Related

Finding max value of a loop with VBA

I am trying to find max value of a loop. First, I have two random arrays and I want to find these two arrays correlation coefficient. Then, I want to calculate it multiple times as much as "I3" cell. After that, I want to write a code which finds max correlation coefficient from this calculation. I wrote the code below but it didn't work.
Sub Macro1()
Dim i As Long
For i = 1 To Range("I3")
Calculate
Next
DMax = Application.WorksheetFunction.Max("A2")
Range("I4").Value = DMax
End Sub
Any help is appreciated.
Your Max-Function needs a proper argument. Just typing "A2" doesn't work in VBA. Try:
DMax = Application.WorksheetFunction.Max(Range("A2"))
This will give you the max-Value of the Array A2. But keep in mind that the max-Value of a range consisting of a single cell is always the cell value.
If you want to calculate the maximum value of all iterations, you should use the max-function in each iteration (inside for-loop) and store it's value. In each following iteration you should then overwrite the max-Value if your new max value is larger than the old one. Just like this:
Sub Macro1()
Dim i As Long
DMax = 0
For i = 1 To Range("I3")
Calculate
DMax2 = Application.WorksheetFunction.Max(Range(...))
If DMax2 > DMax Then DMax = DMax2
Next i
Range("I4").Value = DMax
This will give you the max-Value of Range(...) of all iterations.
I barely understand your code, but the solution will be nasted loop. Suppose you have two sets of numbers: A2 (Cells(2, 1)) through I2 (Cells(2, 7)) and A3 (Cells(3, 1)) through I3 (Cells(3, 7)). You want to calculate partial correlation and find what was the maximum value of it.
For i = 1 To 7
For j = 1 To i
'Calculate the correlation
Next j
'here you have partial coefficient and you can compare it,
'if it is greater than previous one then save it and store
Next i
For i = 1 To Range("I3").value 'to calculate from 1 to the value in that cell
what i would recommend for your question.
For i = 1 To 10 ' loop 10 times
For j = 1 To i ' here it will allow you to check your stuff multiple times before moving on to the next value
arr1(i) = arr2(j) ' see if your array match
Next j
Next i

Excel/VBA: Cannot get Variant/Boolean to return (should be trivial)

I cannot get this function to return my values to the output column in excel:
To overcome some intense lookup tables and speed up computation, I am using a pivot table with slicers to output row numbers from filtering. These rows then need to be converted into a column of true/false cells for a large table from which I then want to perform more calculations. To avoid lookups or matching I simply need to step through the list of rows and turn those cells to "true" in the output vector.
Function IncludedinSlicer(input_range As Variant) As Variant
Dim n As Long, j As Long, r As Long
n = input_range.Height ' Height of the column of reference values
' every row in the input_range contains a row number which in the output should be TRUE
' all other rows should be false
Dim output_range As Variant
ReDim output_range(1 To 300000)
' This covers the maximum number of rows
' Initialise all rows to FALSE
For j = 1 To 300000
output_range(j) = False
Next j
' Set only those rows listed in the reference to TRUE
For j = 1 To n
r = input_range(j).Value
If r = 0 Then ' If r=0 then we are beyond the end of the reference table and have captured some blank rows
Exit For ' Exit, to avoid outside-of-array errors
Else
output_range(r) = True
End If
'End If
Next j
' Return results to Excel
' THIS LAST BIT DOES NOT RETURN VALUES TO EXCEL
IncludedinSlicer = output_range
End Function
I know this should be trivial but somehow it has vexed me for literally hours. Please help! Thank you in advance!
EDIT: Found the issue!
First, thank you for pointing out the difference between Height and Rows.Count as I was not aware of this.
Unfortunately, that still left me with the same error in the final cell output (#Value). Luckily in the meantime I tried processing this via Matlab instead and when passing back the results I got the same error. This allowed me to narrow down the problem and I tracked the error to ... (drum roll) ... VBA's 2^16 limit for array size.
My table has close to 2^18 rows so this causes the error.
input_range.Height refers to the literal height in pixels of the range. Try instead input_range.Rows.Count to get the number of rows in the range.
For your function, you should be passing in (and potentially returning) a Range type instead of the ambiguous Variant type.
This will give you direct access to all of the properties of the Range type (i.e. Rows, Columns, Cells) and would probably make it easier to catch your issue that bobajob pointed out.

Need to add diagonal cells with an additional cell for each subsequent diagonal

I need help getting the sum of specific cells in a diagonal format. My issue is that each subsequent diagonal grows by 1 cell. I was thinking I use a For loop, but I don't know how to add an additional cell to the summation for each iteration of the loop. The picture below shows an example of the format the data will be in that I need to sum. Any ideas? Thanks for the help.
Here's a function that asks for the range and the count of values to add. You can probably adjust the code to get rid of the need to add count altogether, but it's a start.
Just for clarity, you'd enter into your sheet like this:
=AddDiagonal(D4,0)
=AddDiagonal(D7,1)
=AddDiagonal(D10,2)
...
Function
Function AddDiagonal(rng As Range, cnt As Long) As Double
Dim x As Long
Dim answer As Double
For x = 0 To cnt
If x = 0 Then
answer = answer + rng.Offset(x, x)
Else
answer = answer + rng.Offset(x * -3, x * 1)
End If
Next
AddDiagonal = answer
End Function

Find Lowest Sum of "x" Consecutive Numbers in A List

I am looking to find the lowest sum of x consecutive numbers in a column of 91 numbers (column A, for example). For example, if x is 30, I want to find the lowest sum of 30 consecutive numbers in the 91 number column. I will need to do this calculation for each value of x between 30 and 78.
The brute force, non-VBA approach would look something like this for the case where x = 30:
=MIN(SUM(A1:A30),SUM(A2:A31),SUM(A3:32),...,SUM(A62:A91))
For the case where x = 78, this non-VBA approach would look like this:
=MIN(SUM(A1:A78),SUM(A2:A79),SUM(A3:A80),...,SUM(A14:A91))
I have 20 columns (each with 91 numbers) that I need to apply this function to, so the column needs to be a variable as well. My guess is that we would define a function with a range length input (i.e. x) and a column input.
I don't have much experience with VBA syntax. Can you guys/gals help me formulate something?
I would normally try to solve this myself (I tried the Excel brute force way then tried making my own UDF but I didn't know where to start).
Consider the following UDF:
Public Function consec(rng As Range, x As Long) As Double
Dim r As Range, i As Long, mymin As Double, temp As Double
Dim wf As WorksheetFunction
Set wf = Application.WorksheetFunction
i = 1
Set r = rng(i).Resize(x, 1)
mymin = wf.Sum(r)
For i = 2 To rng.Count - x + 1
Set r = rng(i).Resize(x, 1)
temp = wf.Sum(r)
If temp < mymin Then
mymin = temp
End If
Next i
consec = mymin
End Function
For example:
Do you require VBA?
It's possible to do this with an "array formula", e.g. if your "x" value is in cell Z1 you can use this formula
=MIN(SUBTOTAL(9,OFFSET(A1:A91,ROW(INDIRECT("1:"&COUNT(A1:A91)-Z1+1)),0,Z1)))
confirmed with CTRL+SHIFT+ENTER

#VALUE error with Excel VBA Function

In my Excel spreadsheet I have two columns.
A contains strings with the values 'Yes', 'No' or 'Maybe'.
B contains strings with a year in.
I need a function to determine the number of occurrences of a year in column B, where the equivalent value in column A is 'Yes'.
I currently have the following code:
Function CountIfYearAndValue(Rng As Range, YNM As String, Year As String) As Integer
Dim count As Integer
count = 0
For Each c In Rng.Cells
If (StrComp(Abs(c.Value), Year, vbTextCompare) = 0) And (StrComp(Cells(c.Row, A), YMN, vbTextCompare) = 0) Then count = count + 1
Next
CountIfYearAndValue = count
End Function
The idea of this code is that we iterate through every cell in the range given (a range on column B) and check if the year is equal to the Year parameter. And if the equivalent cell on column A is equal to the YNM parameter we increment the count variable.
For some reason this code does not work when I use the following parameter:
=CountIfYearAndValue('Years'!B1:B7,"Yes","Year 7")
It just does the #VALUE error and refuses to display any outcome.
Any help would be much appreciated.
Edit: All of the values in both cells are on of an unformatted datatype ('General') and no cells are blank.
It sounds like you are reinventing the wheel... There already is a built in function (advantage: being much faster than a UDF) that does exactly what you are after. It is called COUNTIFS()
All YESes for Year 7 in rows 1 to 10.
=COUNTIFS(B1:B10, "Year 7",A1:A10, "Yes")
I just had a quick look at your code and I think there are possibly a few reasons why your original code is not working as expected.
YNM is a valid column name therefore it should not be used as a variable name. You should avoid naming your variables like that - give it a more meaningful name
YNM != YMN as you had it in your code (see function definition and then the misspelled version in the StrComp() function)
Year is a valid VBA built in function, therefore once again you should avoid using it as a variable name as you're exposing yourself to a naming collision.
Add Option Explicit at the top of your module. This requires you to Dimension all you variables. It's always recommended for many many reasons.
rng variable is of Range type therefore you do not need to explicitly add the .Cells property to it. Even though it may help in some cases - at a bit more advanced level you may face some runtime type compatibility issues. ( runtime may convert your rng Range variable to a 2D array etc )
Added an explicit conversion in the second StrComp() function around the c.Offset(0, -1) as you don't want the runtime to (rare but still possible) convert your Yes to a Boolean data type. Explicit conversion to a String just gives you that extra protection ;p (lol)
therefore, something like this returns the correct value
Function CountIfYearAndValue(rng As Range, choice As String, myYear As String) As Long
Dim count As Long
count = 0
Dim c As Range
For Each c In rng
If (StrComp(c, myYear, vbTextCompare) = 0) And (StrComp(CStr(c.Offset(0, -1)), choice, vbTextCompare) = 0) Then
count = count + 1
End If
Next c
CountIfYearAndValue = count
End Function
Right, I hope this helps you understand bits and pieces :) any questions please leave a comment