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
Related
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
I have a function that returns a Dictionary with pairs key-value. Then I proceed to use on such pair to create an array: I get a value for key "DATA_ITEMS_NUMBER" to determine arrays' max length. However it results in an error...
Function getGlobalVariables()
Dim resultDict As Object
Set resultDict = CreateObject("Scripting.Dictionary")
resultDict.Add "DATA_ITEMS_NUMBER", _
ThisWorkbook.Worksheets("setup").Cells(25, 5).value
Set getGlobalVariables = resultDict
End Function
Function getBudgetItemInfos(infoType As String, year As Integer)
Dim globals As Object
Set globals = getGlobalVariables()
Dim DATA_ITEMS_NUMBER As Integer
DATA_ITEMS_NUMBER = globals("DATA_ITEMS_NUMBER")
Dim resultArray(1 To DATA_ITEMS_NUMBER) As String
...
End Function
The Dim statement isn't executable; you can't put a breakpoint on a Dim statement, it "runs" as soon as the local scope is entered, in "static context", i.e. it doesn't (and can't) know about anything that lives in "execution context", like other local variables' values.
Hence, Dim foo(1 To SomeVariable) is illegal, because SomeVariable is not a constant expression that's known at compile-time: without the execution context, SomeVariable has no value and the array can't be statically sized.
If you want a dynamically-sized array, you need to declare a dynamic array - the ReDim statement is executable:
ReDim resultArray(1 To DATA_ITEMS_NUMBER) As String
Note that a Dim resultArray() statement isn't necessary, since ReDim is going to perform the allocation anyway: you won't get a "variable not declared" compile-time error with a ReDim foo(...) without a preceding Dim foo and Option Explicit specified.
For good form your Function procedures should have an explicit return type though:
'returns a Scripting.Dictionary instance
Function getGlobalVariables() As Object
And
'returns a Variant array
Function getBudgetItemInfos(infoType As String, year As Integer) As Variant
Otherwise (especially for the Object-returning function), you're wrapping your functions' return values in a Variant, and VBA needs to work harder than it should, at the call sites.
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.
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.
Closed. This question needs to be more focused. It is not currently accepting answers.
Closed 4 years ago.
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
Is there a way to convert all elements of an array from string to double without having to convert each element one by one. I want to avoid to use a loop if possible. I'd like to know that for VBA not VB.Net
In some scenarios you could use CopyMem to move data between arrays of different types. (For instance Strings to Integer Arrays.) But this won't work with String and Doubles as equivilant values are stored differently at a byte level. So a String Binary "1" is not the same set of 1s and 0s as Double Binary and vice versa.
Generally speaking you will need to do it with a conversion function:
Public Sub Test()
Const clUprBnd As Long = 9&
Dim asTest(clUprBnd) As String
Dim adTest() As Double
Dim lIndx As Long
For lIndx = 0& To clUprBnd
asTest(lIndx) = CStr(lIndx)
Next
adTest = StringArrayToDoubleArray(asTest)
MsgBox adTest(5)
End Sub
Private Function StringArrayToDoubleArray(ByRef values() As String) As Double()
Dim lIndx As Long, lLwrBnd As Long, lUprBnd As Long
Dim adRtnVals() As Double
lLwrBnd = LBound(values)
lUprBnd = UBound(values)
ReDim adRtnVals(lLwrBnd To lUprBnd) As Double
For lIndx = lLwrBnd To lUprBnd
adRtnVals(lIndx) = CDbl(values(lIndx))
Next
StringArrayToDoubleArray = adRtnVals
End Function
I'm trying to think conceptually how it is possible, at any layer of abstraction, to "do something" on each (the keyword here is each) item in an array without processing it one at a time.
At the lowest levels of abstraction concerning a single CPU, each item in an array is always going to be processed one at a time. The CPU can't take a collection and magically transform each element without iterating through each item in the collection. The words iteration (and consequently, loop) and each enjoy each other's company very much.
Now, is it possible, at higher layers of abstraction, to present to the programmer a method/function/procedure that looks like it's acting on an entire collection? Yes, it's very possible. LINQ (in .NET) does this a lot. However, all LINQ does is provide a way for a programmer to act on each item in a collection using only one statement.
Even if VBA had a way to convert the elements in an array from one type to another (which I don't believe it does), at some level of abstraction, the program will have to iterate through each item in the list to perform the change.
That being said, I think you're stuck doing a loop. The best thing you could do is wrap this functionality within a Function. Here's a sample function with some test code:
Function ConvertArray(arrStr() As String) As Double()
Dim strS As String
Dim intL As Integer
Dim intU As Integer
Dim intCounter As Integer
Dim intLen As Integer
Dim arrDbl() As Double
intL = LBound(arrStr)
intU = UBound(arrStr)
ReDim arrDbl(intL To intU)
intCounter = intL
Do While intCounter <= UBound(arrDbl)
arrDbl(intCounter) = CDbl(arrStr(intCounter))
intCounter = intCounter + 1
Loop
ConvertArray = arrDbl
End Function
Sub Test()
Dim strS(0 To 2) As String
Dim dblD() As Double
Dim dbl As Variant
strS(0) = "15.5"
strS(1) = "12"
strS(2) = "4.543"
dblD = ConvertArray(strS)
For Each dbl In dblD
Debug.Print dbl
Next dbl
End Sub
The answer to that exact question is "no". There is no built in VBA operator that works on typed arrays like that.
However, you can have an array of variants, and that can contain elements that are strings or doubles (or other things of course). So if your concern is being able to pass arrays around or use individual elements without having to do explicit conversions, you can do something like:
Public Sub passesStuff()
Call expectsNumericStuff(Array("1", "2", "3"))
Call expectsNumericStuff(Array(1, 2, 3))
End Sub
Public Sub expectsNumericStuff(arr)
Debug.Assert IsArray(arr)
Debug.Assert IsNumeric(arr(1))
Debug.Print arr(1) * 42
End Sub
Obviously all of the advantages and disadvantages of variants then apply, and should be kept in mind.