Passing arrays to UDF; UBound - vba

It's quite frustrating that I already seem to be doing something wrong in the first line of my first VBA project. In essence my project has already been stalled because I can't figure out why this works:
Function TestF(Values As Variant, Dates As Variant)
TestF = Values(2)
End Function
But this does:
Function TestF(Values As Variant, Dates As Variant)
TestF = UBound(Values)
End Function
(I just want to see that UBound works. So, I removed everything from the project that is not related.)
Where Values and Dates are each supposed to be a 1-dimensional array. (Or ranges? Or is that the issue?)
How do I fix the first bit of code?
I Googled and I Googled, but I haven't found my answer.
The (Excel) 'error' is get is #VALUE! from =testf(A2:A10,B2:B10), where the ranges are like:
-1000 31-Dec-13
-10 31-Dec-14
30 13-Mar-15
1200 17-Mar-15
-40 30-Jun-15
1300 30-Sep-16
1200 31-Oct-17
1250 30-Nov-18
1500 31-Dec-18

The problem in your passing of array to UDF is your understanding about array and ranges. In general, ranges are presented as a two dimensional array even when they are with single row or column.
To make your code work, try the "Double Transpose Trick" which is pretty much the following:
Public Sub TestMe()
Dim k As Variant
k = Range("A2:F2")
Debug.Print k(1)
With WorksheetFunction
k = .Transpose(.Transpose(k))
Debug.Print k(1)
End With
End Sub
The code above will hopefully result as an error in the Debug.Print k(1) part. This is because k is a two-dimensional array, although it is on one row only:
Comment this part or change it to Debug.Print k(1, 1) and continue with the .Transpose It will change your array to a single dimension and it will be working.
Thus, if you want your non-working formula to work, this is a possible way to do it:
Public Function TestFormula(myValues As Variant) As String
Dim myArr As Variant
With WorksheetFunction
myArr = .Transpose(.Transpose(myValues))
TestFormula = myArr(UBound(myArr))
End With
End Function
Some important notes: the "Double Transpose Trick" works on one row ranges, when these should be transfered to array. If you are having a one column range, use "Single Transpose":
myArr = .Transpose(myValues)
With more than 1 column or 1 row, do not use transpose as it cannot be mapped to a 1 dimensional array.
Furthermore, if you pass a range of once cell, it will not work. But you will find some way to go around it.
As mentioned by #cyboashu in the comments, the myArr() array would take values up to column 65536, because the Transpose() is made this way, probably for compatibility reasons with Excel 2003. To make a bigger array - Put entire column (each value in column) in an array?

You need to pass the right parameters to your UDF.
Both parameters are Range.
Function TestF(Rng1 As Range, DatesRng As Range) As Long
Dim C As Range
Dim x As Long
For Each C In Rng1
If Trim(C.Value) <> "" Then ' make sure there isn't a blank cell in the middle of your Range
x = x + 1
End If
Next C
TestF = x - 1
End Function
If you don't have empty cells in the middle of your Range, then the following code will be sufficient:
Function TestF(Rng1 As Range, DatesRng As Range) As Long
Dim C As Range
TestF = Rng1.Cells.Count - 1
End Function

You probably wanted:
TestF=Values(Ubound(Values))
also, you should declare what your function is suppose to return, like
Function TestF(Values As Variant, Dates As Variant) as String

Related

VBA Get Values from AutoFilter

Let's say I have very large set of data with over 100,000+ rows. In Column A, I want to find each unique number.
I understand this can be done using the .Find feature and Collections/Arrays but those seem to take a good bit of time - especially with 100,000+ rows.
However, after AutoFiltering Column A, when I hit the down arrow it displays only unique variables. Is it possible to simply extract those values out of the selections in this way?
'pseudocode
filter.Count
Dim X As Long
For x = 2 to filter.Count
Cells(x, 14) = filter(x)
Next x
You can use advanced filter, it's pretty darn quick. I tried it with 127k rows, the results were instant.
Columns("A:A").AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Range("D1"), Unique:=True
You can extract the visible cells in to an array. Say your total range (without filter) is A2:A10000. Run your filter, then you can run this macro:
Sub t()
Dim arr() As Variant
arr = Range("A2:A10000").SpecialCells(xlCellTypeVisible)
Dim i As Long
For i = LBound(arr) To UBound(arr)
Debug.Print (arr(i, 1))
' Do things with each entry in array
Next i
End Sub

Count characters between two empty space to dashes() in vba

How do I get the length of character between beginning with space and ending with * Here is the image. Column B shows the total len before dasher(-) and my code
Sub xn()
Dim x As Integer
x = 1
If Worksheet("Sheet1").Range("A"& x).len(Right," ") Or _
Worksheet("Sheet1").Range("A"&x)len(Left,"-") Then
len(totallen)
End If
x = x + 1
End Sub
The code posted has multiple issues:
Worksheet is not a valid object - you need to use Worksheets.
.len is not a property of a Range object.
Even in .len was a property of a Range, you would need a
de-reference operator (aka '.') in here: Range("A"&x)len(Left,"-")
If you intend to use the function Len(), it only takes one argument.
You apparently are trying to loop, but you need to use either a For
or For Each loop - it won't loop automatically when you increment x
at the bottom of the sub.
Right is a function, but you're calling it without arguments and they are not optional.
Similarly, Left is a function, but you're also calling it without
the required arguments.
totallen is not declared anywhere, so Len(totallen) will assume
that totallen is a Variant (default for undeclared variables), then
cast it to a String, and then always return 0 because it has never
been given a value.
Anything else I may have missed.
The solution is to use the InStr function. It returns the location in a string of a given sub-string.
Sub xn()
Dim x As Long
Dim sheet As Worksheet
Set sheet = ActiveWorkbook.Worksheets("Sheet1")
For x = 1 To sheet.Range("A" & sheet.Rows.Count).End(xlUp).Row
sheet.Cells(x, 2) = InStr(1, sheet.Cells(x, 1), "-") - 1
Next x
End Sub
I'd also recommend taking a look at the MSDN article on Looping Through a Range of Cells (2003 vintage, but still valid), and Error Finding Last Used cell In VBA.

Excel SUMPRODUCT with VBA based conditions

Does anyone know how to use a VBA function within a worksheet based call to SUMPRODUCT?
This works fine, summing the values in column N where column L contains "Y" and col A contains a different value to col K...
=SUMPRODUCT(--(Input!L1:L100="Y"), --(Input!A1:A100<>Input!K1:K100), Input!N1:N100)
But I want to be able to apply more logic than just A<>K in my second criteria, something like this...
=SUMPRODUCT(--(Input!L1:L100="Y"), --(MatchNames(Input!A1:A100,Input!K1:K100)), Input!N1:N100)
I have a function called MatchNames in my VBA, but I can't work out what it needs to return in order to work. I've tried returning an array of boolean, integer (set to 0 or 1), I've tried transposing the results before returning them, but nothing is working. I've debugged through the MatchNames function, and it does return something "useful" (i.e. an array of booleans), so it's not that the function is bombing out part way through, but I get #VALUE! when I try to use it in a SUMPRODUCT.
This is my VBA function...
Public Function MatchNames(ByVal rng1 As Range, rng2 As Range) As Boolean()
Dim blnOut() As Boolean
Dim k As Long
ReDim blnOut(rng1.Rows.Count - 1)
For k = 1 To rng1.Rows.Count
If rng1.Cells(k, 1).Value <> "" Then
If rng1.Cells(k, 1).Value <> rng2.Cells(k, 1).Value Then
blnOut(k - 1) = True
End If
End If
Next
MatchNames = blnOut
End Function
I think your MatchNames array needs to be transposed as you suggest (because it appears to be returning the equivalent of a row of values - which doesn't work with the other columns of values in SUMPRODUCT).
I don't know how you'd transpose that in VBA but if you can't do that then transpose in SUMPRODUCT like
=SUMPRODUCT(--(input!L1:L100="Y"),--TRANSPOSE(MatchNames(input!A1:A100,input!K1:K100)), input!N1:N100)
but use of TRANSPOSE means that formula now needs to be "array-entered" with CTRL+SHIFT+ENTER
...or you can use MMULT which will multiply a 1x100 range by a 100x1, i.e.
=MMULT(MatchNames(input!A1:A100,input!K1:K100)+0,input!N1:N100*(input!L1:L100="Y"))
for that latter formula to work the sum range - input!N1:N100 - must be all numeric

Functions not actualizing

I execute a VBA code that takes a database, treats it and export it into a sheet. This is working fine. However, I have a sheet that produces graphs depending on the data in the particular sheet. The datas does not actualize. I have to enter the cell and click enter to actualize it. I'm pretty sure there is an easier way to do this. Calculation is set to automatic but that doesn't seem to change anything.
In my cell, I have my own vba function that needs to be updated once the report is done. When I click the cell and then enter, the result is updated but I would like this to be done automatically. I hope this is clearer !
Thanks in advance,
Etienne NOEL
HEre is the code of my function
Public Function number_of_appearances(term As String, sheet As String, column As Integer) As Integer
Application.Volatile
Dim number_of_rows As Integer
Dim appearances As Integer
Dim row As Integer
appearances = 0
row = 1
number_of_rows = Worksheets(sheet).UsedRange.Rows.Count
Do While row <= number_of_rows
If Worksheets(sheet).Cells(row, column).Value = term Then
appearances = appearances + 1
End If
row = row + 1
Loop
number_of_appearances = appearances
End Function
A cell example of a user of the function
=number_of_appearances('test';'sheet1'; 3)
Sounds like your UDF might not depend on any cells that change value when your DB is processed.
See This MSDN Link
Post your UDF (or just its header if you prefer) and an example of its use...
EDIT:
Yes, none of the parameters to the UDF are cell references, therefore the UDF is not triggered to recalculate when data on the shet changes.
You have two choices:
1. rewrite your UDF to include parameter(s) that reference cells that change value when the DB is processed
2. make your UDF volitile (include Application.Volatile in the UDF code) WARNING: this can be very inefficient, depending on how many time the UDF is used and how intensive its calculation is
EDIT 2:
Heres a refactor of your udf using the first option mentioned:
Public Function number_of_appearances(term As String, rng As Range) As Integer
Dim v As Variant
Dim i As Long, j As Long
Dim appearances As Long
v = Intersect(rng, rng.Worksheet.UsedRange)
For j = LBound(v, 2) To UBound(v, 2)
For i = LBound(v, 1) To UBound(v, 1)
If v(i, j) = term Then
appearances = appearances + 1
End If
Next i, j
number_of_appearances = appearances
End Function
use like
=number_of_appearances("test";Sheet1!C:C)
EDIT 3:
If all you are doing is counting number of occurances of a string in a range, consider using
=COUNTIF(Sheet1!C:C;"test")

Counting Rows/Columns of Selected Range Error

I am trying to determine if a selected range is within a set area... This toggles Copy/Paste restrictions in the spreadsheet. I have figured it out, I think, but I'm getting a run-time error 6 (Overflow) if you select an entire row or column. This is what I've got..
Function BETWEENROWS(ByVal Selected As Range, ByVal Min As Double, ByVal Max As Double) As Boolean
Dim LastRow As Integer
LastRow = Selected.Row + Selected.Rows.Count - 1
If BETWEEN(Min, Selected.Row, Max) = True And BETWEEN(Min, LastRow, Max) = True Then
BETWEENROWS = True
Else
BETWEENROWS = False
End If
End Function
There is one for columns BETWEENCOLUMNS as well and the function BETWEEN just returns True/False if a given number is between a min and max value.
This is working great, however, if an entire row/column is selected it's throwing an error and I'm not too familiar with VBA and the only way that I know of bypassing the error is with On Error Resume Next but that seems like I'm putting a bandaid on it and would like to figure out how to fix it another way.
Your LastRow variable is not the correct type for a number as large as the max columns/rows of the spreadsheet. Change the type to Long:
Dim LastRow As Long
You are getting an overflow error because you have made the LastRow variable an integer. Since there are more rows in an entire column then can fit in an integer variable, it triggers the overflow. You could fix this by changing the LastRow variable to be type Long
However, rather then comparing row values you may want to look into the Intersect() function. Given two (or more) ranges it will return the range object that represents the intersection of the two ranges. You could then check that intersection. If they don't intersect the range object will be Nothing. There is a good tutorial for this function at ozgrid.com
UPDATE
Here is the code to ensure range intersects fully using the Intersect() function
'// Run a test here to make sure Intersect does not return Nothing
If (TestRNG.Count <= ISectRNG.Count) And (Intersect(TestRNG, ISectRNG).Count = TestRNG.Count) Then
'// All of TestRNG falls within ISectRNG
End If