I have two different functions that require access to the same array (the array isn't a constant; it will be edited and appended to whenever the function is used within a cell in the sheet).
I want to make this array available to both of them. The array needs to be multi-dimensional (or be a UDT that can have multiple elements within it, like I tried in my code below), and it needs to be able to be dynamically resized. Here is some sample code (edited a bit) I have, but it doesn't seem to work properly.
Option Base 1
Private Type PathsArray
Nodes() As String
End Type
' Instantiate the global array
Dim Paths(1 To 1) As PathsArray
Function SETTWENTY()
' Increase size of the array, preserving the current elements already inside it
ReDim Preserve Paths(1 To UBound(Paths) + 1)
' Make the inner array be 20 elements long
ReDim Preserve Paths(UBound(Paths)).Nodes(1 to 20)
' Return something random
GETPATH = UBound(Paths)
End Function
Function SETTHIRTY()
' Increase size of the array, preserving the current elements already inside it
ReDim Preserve Paths(1 To UBound(Paths) + 1)
' Make the inner array be 30 elements long
ReDim Preserve Paths(UBound(Paths)).Nodes(1 to 30)
' Return something random
GETPATH = UBound(Paths)
End Function
Anyone know why this won't work?
The root of your problem is that you are trying to resize a "static" module-level array. Here is a good description (from Chip Pearson) of the difference between "static" and "dynamic" VBA arrays:
http://www.cpearson.com/excel/vbaarrays.htm
You have a secondary problem in that your functions will return the VBA value Empty instead of the number of paths. In VBA, you return a value from a function by assigning the value to the name of the function.
In the code below, I fixed those problems by:
making the module-level array "dynamic"
adding an "init" routine to get your initial element in there
returning the values you expect from your functions
You might not really need (2) if your original (1 To 1) declaration wasn't really what you wanted anyway.
Note the use of Option Explicit re: (3). If you'd had that there, your original code with the "GETPATH" assignments would fail to compile, even after fixing (1).
Option Explicit
Option Base 1
Private Type PathsArray
Nodes() As String
End Type
' Just declare the module-level array
Dim Paths() As PathsArray
Public Sub init()
ReDim Paths(1 To 1) As PathsArray
End Sub
Function SETTWENTY()
' Increase size of the array, preserving the current elements already inside it
ReDim Preserve Paths(1 To UBound(Paths) + 1)
' Make the inner array be 20 elements long
ReDim Preserve Paths(UBound(Paths)).Nodes(1 To 20)
' Return something random
SETTWENTY = UBound(Paths)
End Function
Function SETTHIRTY()
' Increase size of the array, preserving the current elements already inside it
ReDim Preserve Paths(1 To UBound(Paths) + 1)
' Make the inner array be 30 elements long
ReDim Preserve Paths(UBound(Paths)).Nodes(1 To 30)
' Return something random
SETTHIRTY = UBound(Paths)
End Function
Related
I have the following strings from which I need to extract 6 digit numbers. Since these strings are generated by another software, they occur interchangeably and I cannot control it. Is there any one method that would extract both 6-digit numbers from each of these strings?
Branch '100235 to 100236 Ckt 1' specified in table 'East Contingency' for record with primary key = 21733 was not found in branch or transformer data.
Loadflow branch ID '256574_701027_1' defined in supplemental branch table was not found in branch or transformer input.
Transmission element from bus number 135415 to bus number 157062 circuit ID = 1 defined for corridor 'IESO-NYISO' was not found in input data
I don't know VBA, but I can learn it if it means I can get the 6 digit numbers using a single method.
thanks
I have been using LEFT(), RIGHT() & MID() previously, but it means manually applying the appropriate formula for individual string.
If you have Microsoft 365, you can use this formula:
=LET(arr,TEXTSPLIT(SUBSTITUTE(SUBSTITUTE(A1,"'"," "),"_"," ")," "),
FILTER(arr,ISNUMBER(-arr)*(LEN(arr)=6)))
Thanks to #TomSharpe for this shorter version, using an array constant within TEXTSPLIT to add on possible delimiters.
=LET(arr,TEXTSPLIT(A1,{"_"," ",","," ","'"}),FILTER(arr,(LEN(arr)=6)*ISNUMBER(-arr)))
Data
Output
An alternative is:
=LET(ζ,MID(A1,SEQUENCE(,LEN(A1)-5),6),ξ,MID(ζ,SEQUENCE(6),1),FILTER(ζ,MMULT(SEQUENCE(,6,,0),1-ISERR(0+ξ))=6))
A couple more suggestions (if you need them):
(1) Replacing all non-digit characters with a space then splitting the resulting string:
=LET(numbers,TEXTSPLIT(TRIM(REDUCE("",MID(A1,SEQUENCE(1,LEN(A1)),1),LAMBDA(a,c,IF(is.digit(c),a&c,a&" "))))," "),FILTER(numbers,LEN(numbers)=6))
Here I've defined a function is.digit as
=LAMBDA(c, IF(c = "", FALSE, AND(CODE(c) > 47, CODE(c) < 58)))
(tl;dr I quite like doing it this way because it hides the implementation details of is.digit and creates a rudimentary form of encapsulation)
(2) A UDF - based on the example here and called as
=RegexTest(A1)
Option Explicit
Function RegexTest(s As String) As Double()
Dim regexOne As Object
Dim theNumbers As Object
Dim Number As Object
Dim result() As Double
Dim i As Integer
Set regexOne = New RegExp
' Not sure how you would extract numbers of length 6 only, so extract all numbers...
regexOne.Pattern = "\d+"
regexOne.Global = True
regexOne.IgnoreCase = True
Set theNumbers = regexOne.Execute(s)
i = 1
For Each Number In theNumbers
'...Check the length of each number here
If Len(Number) = 6 Then
ReDim Preserve result(1 To i)
result(i) = CDbl(Number)
i = i + 1
End If
Next
RegexTest = result
End Function
Note - if you wanted to preserve leading zeroes you would need to omit the Cdbl() and return the numbers as strings. Returns an error if no 6-digit numbers are found.
Why do I get a Type Mismatch error on the last line?
Dim LineArray()
Dim Line
Dim FilePath as String
FilePath = "X:\Path\To\File.txt"
Open FilePath For Input As #1
Line Input #1, Line
Close #1
LineArray = Split(Line, vbTab)
LineArray is a Variant/Variant()
Line is a Variant/String
If I Dim them both as Variants I still get the error
As written in the comments: Split returns an array of Strings.
So the correct syntax is
Dim LineArray() As String
LineArray = Split(Line, vbTab)
The VBA compiler knows that LineArray will be an array of Strings. However, as it is declared as dynamic array, it is not an array before you assign members to it. Usually, this is done using the ReDim-statement that reserves the necessary space for a certain number of variables of a specific type. When using the split-function, this ReDim (or something similar) is done inside the function.
Now Variant is a very special data type - it can hold any type of data. This can be an Integer, a String, a Date, an Object (reference), an Error, or the value Empty (which means nothing is assigned yet). And, it can even be an array of any datatype. You can get the information about what data it contains using the function VarType().
What you can`t do is assign the result to an static array:
Dim LineArray(0 to 3) As String
LineArray = Split(Line, vbTab)
This will throw a compile error: Can't assign to array. The variable is not only defined, the memory is already reserved (in this case 4 Strings).
What you also can't do is to assign the result to an array of Variant. Why? Because the Split creates an Array of Strings. Now if you would assign this to an array of Variant, it would be possible to assign, let's say, a Double to the first element. However, the memory was reserved by Split to hold an String, so horrible things would happen to the memory.
Dim LineArray(0) As Variant
LineArray = Split(Line, vbTab)
LineArray(0) = 3.1415926 ' Ouch!
What would be needed is an instance that
Reserve memory for n Variant variables and assign it to LineArray (n = number of strings returns by Split)
Loop over all elements of the string array and assign the strings one by one to the variant array.
Now while this is possible, no instance will do this for you. The Split-function creates and returns the String array. The assignment operator ("=") is capable to assign any value to a Variant, but is not capable to reserve an array and copy elements one by one.
The idea is passing the complete content of a listobject.databodyrange to an array to make operations in memory and not having to access the sheets cells values repeatedly which is very time consuming.
this is the code.
Dim theArray As Variant
theArray = mylistObject.DataBodyRange.value
MsgBox (theArray(1, 1)) '= column 1 row 1 = first element
It works, so far so good.
but!!! since theArray is dimensioned as Variant, their elements are NOT strings, So when passing every of the values of theArray into a function that requires a string an error appears.
what to do?
Note: I know I might change the data type of the function itself to variant, but this function is called from so many routines that i dont dare to touch it. I rather prefer try to look for the way to transform the content of that variant into a string
like theArray(i,j) to str(thearray(i,j)) (which does not work)
some help, some ideas?
EDIT 1:
this is the line of the error:
Dim theclaims As Variant
theclaims = rawClsTbl.DataBodyRange.value
For i = LBound(theclaims, 1) To UBound(theclaims, 1)
myText = deleteRefSigns(theclaims(i, 2))
etc
error: byref argument type missmatch
where:
Function deleteRefSigns(txT As String) As String
i will be trying the solutions proposed.
thx
Related questions:
I asked in overflow myself this question some time ago:
Passing Listobject Range to array and getting error "out of range"
and read also this one
Excel VBA Type Mismatch Error passing range to array
and several others.
The following should work:
Dim MyStr As String
MyStr = CStr(TheArray(1, 1))
Note: Always declare it as a forced array not just as Variant …
Dim TheArray() As Variant 'Variant array (can only be an array)
Dim TheArray As Variant 'Variant (can be a value or array)
… to ensure it contains an array. Otherwise it will contain only a value if you do
TheArray = Range("A1").Value
which might easily fail if your range is dynamic.
If you read a range into an array like
Dim TheArray() As Variant
TheArray = Range("A1:C20").Value
then there is no possibility to declare the array as String it is forced to be Variant by design.
It looks like you don't want deleteRefSigns to modify the argument passed by the calling procedure. If so, you can pass the argument by value...
Function deleteRefSigns(ByVal txT As String) As String
I'm trying to write the SWITCH function in VBA for my coworker who has Excel 2013. I feel that my VBA is strong enough to code this function once I set up all my function parameters. However, I'm not sure how to have an unlimited number of optional parameters in a function (similar to *args in Python). How can I set up a function so that it may have an unlimited number of optional arguments?
You need to use ParamArray, e.g.
Public Function TestSum(ParamArray a())
Dim i As Long
For i = LBound(a) To UBound(a)
TestSum = TestSum + a(i)
Next i
End Function
An interesting question, here's my attempt at replicating the Switch functionality.
You will need to use a ParamArray argument:
Optional. Used only as the last argument in arglist to indicate that the final argument is an Optional array of Variant elements. The ParamArray keyword allows you to provide an arbitrary number of arguments. It may not be used with ByVal, ByRef, or Optional. (source)
Revised, thanks to the comments with #TinMan, we no longer use a Dictionary so this will be compatible with Mac OS without further tweaks.
Function FSwitch2(ValueToMatch As Variant, ParamArray ValuesToMatchAndReturn())
' example of replicating the Switch function available in Office 365, etc.
' https://support.office.com/en-us/article/switch-function-47ab33c0-28ce-4530-8a45-d532ec4aa25e
Dim i As Integer
Dim retVal As Variant
Dim default As Variant
If (UBound(ValuesToMatchAndReturn) + 1) Mod 2 <> 0 Then
' if the array is not evenly sized, assume the last argument is the default value.
default = ValuesToMatchAndReturn(UBound(ValuesToMatchAndReturn))
Else
' Otherwise, default to #N/A error if no match.
default = CVErr(2042)
End If
For i = LBound(ValuesToMatchAndReturn) To UBound(ValuesToMatchAndReturn) Step 2
If ValueToMatch = ValuesToMatchAndReturn(i) Then
retVal = ValuesToMatchAndReturn(i + 1)
Exit For
End If
Next
FSwitch2 = IIf(IsEmpty(retVal), default, retVal)
End Function
I wondering how I can use a variable to call another variable. For example Apple1, Apple2, Apple3, Apple4, Apple5, Apple6 and AppleNum.
Let us say AppleNum is 4. How can I use AppleNum to call for the code to use Apple4?
Also, What if Apple1, Apple2...ect are objects?
My first thought on how to solve this was by using some sort of Array?
Note: Using Select Case here will work but doesn't simply the code I have and would require each case to be written out individually (a lot of work)
Just out of interest, ts there a way to define AppleNum number of variables in the code?
If you can help, Thanks!
It is possible to have an array of variable size in VBA, using the following (for example):
Dim myArray() as Double
Dim AppleNum as Integer, arraySize as Integer
AppleNum = 4
arraySize = 6
ReDim myArray(1 to arraySize)
this would create an array myArray, and change its size to be 6 elements long (with index starting at 1). You can declare the entire array in one statement (without redimensioning) with
myArray = Array(Apple1, Apple2, Apple3, Apple4, Apple5, Apple6)
Assuming that Apple1 etc. have been previously declared.
If you declare myArray as Variant rather than Integer (or don't give a type - defaults to Variant) then your array can contain anything you want.
You can also use
ReDim Preserve myArray(1 to arraySize * 2)
if at a later point you want the array to be twice as large, but you don't want to lose the first six elements you had assigned.
I hope these things will get you moving with your problem.