Check length of Variant object - vba

I want to find out if there are any elements in a list after applying filter to it. The list is of type variant which seems to be a problem. Most suggestions are in line with this post: but it does not work for me. What am i doing wrong?
Dim message As Variant
Dim tempMessage As Variant
Dim a As Long
message = Split("AACP;CP;sBcp;ccffcp", ";")
tempMessage = Filter(message, "CP", False, vbTextCompare)
If IsNull(tempMessage) = True Then
Debug.Print "EMPTY"
Else
Debug.Print tempMessage(0)
End If

You have a couple of ways:
VarType(tempMessage) will be vbEmpty if it is completely blank.
VarType(tempMessage) And vbArray will be non-zero if it's an array type (even with zero elements. Zero length arrays are allowed in VBA). Here I'm using And in the logical context.
If you've established it's an array, then you can take a guess at the dimensionality. (VBA provides no function for this, but luckily you should know from the documentation of Filter).
If you've established the variant is an array then use UBound(tempMessage) - LBound(tempMessage) + 1 to get the number of elements.

Related

Declaring Array() in VBA-ACCESS does not work without upper limit

I am learning about declaring arrays. It works when I declare it by giving an upper limit using following code:
Dim arrayA(5) as String
I check it by assigning a random value:
arrayA(0) = 1
MsgBox arrayA(0)
MsgBox responds by giving a value of 1.
However, my actual intention is to create a dynamic array that I defined as below:
Dim arrayA() as String
I test it in the same way
arrayA(0) = 1
MsgBox arrayA(0)
But this time it does not work and MsgBox pops up empty. Would someone tell me if I need to load some libraries to work with dynamic array?
Arrays in VBA need to be initialized before they are used.
You can initialize an array with a Redim statement:
Dim arrayA() as String
Dim i As Integer
i = 0
Redim ArrayA (0 To i)
arrayA(0) = "1" 'String
MsgBox arrayA(0)
Alternatively, there are functions that return an initialized array. In that case, Redim is not needed as the initialization happens in the external function. You do need to make sure you match type with the array being returned, though, and the overhead is the same or more.
Dim arrayA() as Variant
arrayA = Array(1)
MsgBox arrayA(0)
You can declare an array without a limit, but must redim the array to the desired limit prior to using it:
Dim myArray() As Long
Redim myArray(0)
myArray(0) = 0 'etc...
So, you cannot use a "dynamic" array in VBA like this.
The best reference I've ever known for arrays (and much other VB/A related information), comes from the late Chip Pearson: http://www.cpearson.com/Excel/VBAArrays.htm

Creating a new String Array from another

I have an array of integers (dfArray) and would like to create a new second String array which consists of the integers and appending "G" to the beginning. What's the best way to go about this? I was thinking of For Each but couldn't get it to work. Thanks in advance.
Set dfArray = [dff]
Set dfArray2 = ["G" & dff] 'incorrect but you get the idea?
Dim dfArray() As Variant
Dim dfArray2() As String
dfArray = [dff].Value
ReDim dfArray2(UBound(dfArray)) As String
Dim i As Double
For i = 1 To UBound(dfArray) Step 1
dfArray2(i) = "G" & dfArray(i, 1)
Next i
Anyways, from my personal point of view, I don't like to asign a complete Range into Array, only if needed. I prefer to loop using Lbound or Ubound and control the array all the time. To asign a range into an Array, you need the Array variable to be Variant type, and also, you can't use Preserve easily. Check this question for more info
Issues about Variant Arrays

VBA Variant - IsNull and IsEmpty both false for empty array

I have a VBA class that contains a number of variants. These variants are there to hold Y x Z size arrays, but they're optional inputs (and so often the variants will never get used or initialized).
A function within the same class calls on each of these variants to perform a calculation. BUT, I need it to skip the variants that are blank. When I try to use IsNull(xyz) and IsEmpty(xyz), both return false. Looking at the watch on xyz, it has:
Expression: xyz
Value:
Type: Variant/Variant()
Context: className
Totally blank under value.
Any ideas how I can test/return a boolean if these things are empty? Or even a boolean if they're full, that would work too.
Thanks
Edit: I should add, that IsMissing just completely crashes excel...
Very dirty workaround but something like this might do the trick:
Function IsEmptyArray(testArr As Variant) As Boolean
Dim test As Long
Dim ret As Boolean
ret = False
On Error Resume Next
test = UBound(testArr)
If Err.Number = 9 Then
ret = True
End If
Err.Clear
On Error GoTo 0
IsEmptyArray = ret
End Function
One method I've used in the past is to test whether or not the array is filled using a helper function. I join the array using an empty string delimiter and test that the length is greater than zero. It has worked in my circumstances, but I'm not sure if there are any flaws in the logic.
Below code returns true if the array is empty and false if it is not:
Function TestIfArrayIsEmpty(vArray As Variant) As Boolean
TestIfArrayIsEmpty = (Len(Join(vArray, "")) = 0)
End Function
You can use vartype(variable_name. You can check
vartype(varriable_name) = vbArray or VbEmpty or VbNull
Then check LBound and UBound of eac dimension
for example
LBound(variable, 1) and UBound(variable_name, 1) to check the lower and higher indexes of the array.

passing range on another sheet to vlookup

I can't reference a range with a sheet for my function like I can with =vlookup.
This works: =MVLOOKUP(a2,B:C,2,",",", ")
This isn't: =MVLOOKUP(a2,Sheet3!B:C,2,",",", ")
The code:
Public Function MVLookup(Lookup_Values, Table_Array As Range, Col_Index_Num As Long, Input_Separator As String, Output_Separator As String) As String
Dim in0, out0, i
in0 = Split(Lookup_Values, Input_Separator)
ReDim out0(UBound(in0, 1))
For i = LBound(in0, 1) To UBound(in0, 1)
out0(i) = Application.WorksheetFunction.VLookup(in0(i), Table_Array, Col_Index_Num, False)
Next i
MVLookup = Join(out0, Output_Separator)
End Function
I don't know basic and I'm not planning to learn it, I rarely even use excel, so sorry for the lame question. I guess basic is really "basic" it took me 30 minutes to get to this point from the reference(reading included), but other 60 minutes in frustration because the above problem.
Help me so I can go back to my vba free life!
EDIT: Although the code above worked after an excel restart, Jeeped gave me a safer solution and more universal functionality. Thanks for that.
I was not planning to use it on other than strings but thanks for the addition, I wrongly assumed there is a check for data type every time and type passed along in the background and vlookup acting accordingly. I have also learned how to set default values to function input variables.
See solution.
Thanks again, Jeeped!
You are confusing 1 with "1" and regardless of your personal distaste for VBA, I really don't know of any programming language that treats them as identical values (with the possible exception of a worksheet's COUNTIF function).
Public Function MVLookup(Lookup_Values, table_Array As Range, col_Index_Num As Long, _
Optional Input_Separator As String = ",", _
Optional output_Separator As String = ", ") As String
Dim in0 As Variant, out0 As Variant, i As Long
in0 = Split(Lookup_Values, Input_Separator)
ReDim out0(UBound(in0))
For i = LBound(in0) To UBound(in0)
If IsNumeric(in0(i)) Then
If Not IsError(Application.Match(Val(in0(i)), Application.Index(table_Array, 0, 1), 0)) Then _
out0(i) = Application.VLookup(Val(in0(i)), table_Array, col_Index_Num, False)
Else
If Not IsError(Application.Match(in0(i), Application.Index(table_Array, 0, 1), 0)) Then _
out0(i) = Application.VLookup(in0(i), table_Array, col_Index_Num, False)
End If
Next i
MVLookup = Join(out0, output_Separator)
End Function
When you Split a string into a variant array, you end up with an array of string elements. Granted, they look like numbers but they are not true numbers; merely textual representational facsimiles of true numbers. The VLOOKUP function does not treat them as numbers when the first column in your table_array parameter is filled with true numbers.
The IsNumeric function can reconize a string that looks like a number and then the Val function can convert that text-that-looks-like-a-number into a true number.
I've also added a quick check to ensure what you are looking for is actually there before you attempt to stuff the return value into an array.
Your split strings are one-dimensioned variant arrays. There is no need to supply the rank in the LBound / UBound functions.
                    Sample data on Sheet3                                  Results from MVLOOKUP
This is not a valid range reference ANYWHERE in Excel B:Sheet3!C.
Either use B:C or Sheet3!B:C
Edit. Corrected as per Jeeped's comment.

How to check whether a variant array is unallocated?

Dim Result() As Variant
In my watch window, this appears as
Expression | Value | Type
Result | | Variant/Variant()
How do I check the following:
if Result is nothing then
or
if Result is Not Set then
This is basically what I am trying to accomplish, but the first one does not work and the second does not exist.
To avoid error handling, I used this, seen on a forum long time ago and used sucessfully since then:
If (Not Not Result) <> 0 Then 'Means it is allocated
or alternatively
If (Not Not Result) = 0 Then 'Means it is not allocated
I used this mainly to extend array size from unset array this way
'Declare array
Dim arrIndex() As Variant
'Extend array
If (Not Not Result) = 0 Then
ReDim Preserve Result(0 To 0)
Else
ReDim Preserve Result(0 To UBound(Result) + 1)
End If
Chip Pearson made a useful module called modArraySupport that contains a bunch of functions to test for things like this. In your case, you would want to use IsArrayAllocated.
Public Function IsArrayAllocated(Arr As Variant) As Boolean
This function returns TRUE or FALSE indicating whether the specified array is allocated (not empty). Returns TRUE of the
array is a static array or a dynamic that has been allocated with a Redim statement. Returns FALSE if the array is a dynamic array that
has not yet been sized with ReDim or that has been deallocated with the Erase statement. This function is basically the opposite of
ArrayIsEmpty. For example,
Dim V() As Variant
Dim R As Boolean
R = IsArrayAllocated(V) ' returns false
ReDim V(1 To 10)
R = IsArrayAllocated(V) ' returns true
The technique used is basically to test the array bounds (as suggested by #Tim Williams) BUT with an extra gotcha.
To test in your immediate window:
?IsArrayAllocated(Result)
Testing in Watch window: there are may ways to do this; for example, add a watch on R and under "Watch Type" select "Break When Value Changes".
You can use the following in the immediate window:
?Result Is Nothing
?IsNull( Result )
?IsEmpty( Result )
?IsMissing( Result )
The first is simply for completeness. Since Result is not an object, Result Is Nothing will throw an error. Empty is for variants that have not been initialized including arrays which have not been dimensioned..
(Update) In doing some additional checking, I have discovered that IsEmpty will never return true on a declared array (whether Redim'd or not) with only one exception. The only exception I found is when the array is declared at the module level and not as Public and then only when you check it in the immediate window.
Missing if for optional values passed to a function or sub. While you cannot declare Optional Foo() As Variant, you could have something like ParamArray Foo() As Variant in which case if nothing is passed, IsMissing would return true.
Thus, the only way to determine if the array is initialized is to write a procedure that would check:
Public Function IsDimensioned(vValue As Variant) As Boolean
On Error Resume Next
If Not IsArray(vValue) Then Exit Function
Dim i As Integer
i = UBound(Bar)
IsDimensioned = Err.Number = 0
End Function
Btw, it should be noted that this routine (or the library posted by Jean-François Corbett) will return false if the array is dimensioned and then erased.
I recommend a slightly different approach because I think using language artifacts like (Not Array) = -1 to check for initialization is difficult to read and causes maintenance headaches.
If you are needing to check for array allocation, most likely it's because you're trying to make your own "vector" type: an array that grows during runtime to accommodate data as it is being added. VBA makes it fairly easy to implement a vector type, if you take advantage of the type system.
Type Vector
VectorData() As Variant
VectorCount As Long
End Type
Dim MyData As Vector
Sub AddData(NewData As Variant)
With MyData
' If .VectorData hasn't been allocated yet, allocate it with an
' initial size of 16 elements.
If .VectorCount = 0 Then ReDim .VectorData(1 To 16)
.VectorCount = .VectorCount + 1
' If there is not enough storage for the new element, double the
' storage of the vector.
If .VectorCount > UBound(.VectorData) Then
ReDim Preserve .VectorData(1 To UBound(.VectorData) * 2)
End If
.VectorData(.VectorCount) = NewData
End With
End Sub
' Example of looping through the vector:
For I = 1 To MyData.VectorCount
' Process MyData.VectorData(I)
Next
Notice how there's no need to check for array allocation in this code, because we can just check the VectorCount variable. If it's 0, we know that nothing has been added to the vector yet and therefore the array is unallocated.
Not only is this code simple and straightforward, vectors also have all the performance advantages of an array, and the amortized cost for adding elements is actually O(1), which is very efficient. The only tradeoff is that, due to how the storage is doubled every time the vector runs out of space, in the worst case 50% of the vector's storage is wasted.
Check the LBound of the array. If you get an error then it's uninitialized.