Access 2013 32 bit: Win 7 64bit
Tried: Is it possible to pass a multidimensional array as a parameter in EXCEL VBA? to no avail
(If you can answer this you can probably answer that, but they're a little different)
Sub CreateArray()
Dim myArray(1 to 10, 1 to 5)
'Code that assigns values to (1 to 10, 1 to 4)
myArray() = CalculateLastColofArray(myArray())
'Do stuff with full array
End sub
Function CalculateLastColofArray(calcArray)
For i = LBound(calcArray()) to UBound(calcArray())
calcArray(i,5) = calcArray(i,1) + calcArray(i,3)
Next i
CalculateLastColofArray = calcArray()
End Function
My calculation is actually much more complex than the simple addition and my array is dynamically large (x, 5)
Doing it the way I have shown above fills myArray, but debugging has it shown as when I hover over it wrapped in the function call and it errors before entering the function
You can pass multidimensional array as a parameter.
You can also assign the result of the function to the array variable, however this variable must be declared as dynamic array (so its dimensions cannot be specified in declaring line).
Furthermore, you have some other errors in your code. Below is the correct version:
Sub CreateArray()
'Dim myArray(1 To 10, 1 To 5) As Variant
Dim myArray() As Variant
ReDim myArray(1 To 10, 1 To 5)
'Code that assigns values to (1 to 10, 1 to 4)
myArray = CalculateLastColofArray(myArray)
'Do stuff with full array
End Sub
Function CalculateLastColofArray(calcArray() As Variant) As Variant()
For i = LBound(calcArray) To UBound(calcArray)
calcArray(i, 5) = calcArray(i, 1) + calcArray(i, 3)
Next i
CalculateLastColofArray = calcArray
End Function
The key was the CalculateLastColofArray(myArray()) because I had included the parenthesis in the myArray() argument it was looking for a single value, not taking the array as a whole.
changing this line to CalculateLastColofArray(myArray) solved my problem
it's always something small...
Related
Is an array stored as a string ?
Example :
Arr[0]="A"
Arr[1]="B"
Arr[2]="C"
Is this array stored like this "A,B,C" if yes, it is "," or ";" separator ?
I've built the following function :
Function test(Ref As String) As Variant
Dim creationdetableau() As String
Dim cpt As Integer
cpt = 0
For i = 1 To 35500
If Ref = ThisWorkbook.Worksheets("CashFlows").Cells(i, "A").Value Then
ReDim Preserve creationdetableau(cpt)
creationdetableau(cpt) = ThisWorkbook.Worksheets("CashFlows").Cells(i, "B").Value
cpt = cpt + 1
Debug.Print i
End If
Next i
test = creationdetableau
End Function
If I call
=test(A2)
In an excel sheet, would it print the string in ? If No how would you do ?
What Am I trying to do :
I try to use this formula
=CfDur(E2;P2;CashFlows!B2:B100;CashFlows!J2:J100)
But my goal is to replace this CashFlows!B2:B100 by a custom vba function called test
=CfDur(E2;P2;test(A2);CashFlows!J2:J100)
CashFlows!B2:B100 contains date :
to conclude :
=CfDur(E2;P2;test(A2);CashFlows!J2:J100)
does not work
They are not "stored" as a String, but as distinct elements that can be indexed or retrieved by iteration. For example:
Sub Dorian()
Dim Arr(0 To 2) As String, i As Long
Arr(0) = "A"
Arr(1) = "B"
Arr(2) = "C"
For i = 0 To 2
MsgBox Arr(i)
Next i
For Each a In Arr
MsgBox a
Next a
End Sub
NOTE:
The elements of an array can be numbers or Pictures or Ranges or most other types of Objects.
The receiving function will read the passed variable according to the data type of the array used to pass it:
Dim arrInt() as Integer
Dim arrStr() As String
Note: Dim arrVar as Variant and Dim arrVar() as Variant will yield the same result.
The built in Array method expects variant data.
A variant parameter will accept almost any data type passed to it, provided it can be parsed, so for max compatibility you could use variant in your function parameters.
For performance or safety use specific data types instead and ensure the arrays used are of the appropriate type.
You can make an array from all basic data types and many object types too. If you can't find an array type to suit use System.Collections.ArrayList,
I have an excel table called AnimeList, where I have listed all the anime I have finished watching along with their info. The table has the following headers:
Name, Main Genre, Genre2, Genre3, Rating, Seasons, Episodes, Mins/Episode, Status.
I have written some VBA code that can count the distinct genres from the 3 columns as well as the number of them present.
Function CountAndSortGenre()
Dim size As Integer: size = Range("AnimeList[Main Genre]").Rows.Count
ReDim genreExtract((size * 3) - 1) As String
Dim i As Integer: i = 0
Dim cell As Range
For Each cell In Range("AnimeList[Main Genre]")
genreExtract(i) = cell.Value
i = i + 1
Next
For Each cell In Range("AnimeList[Genre - 2]")
genreExtract(i) = cell.Value
i = i + 1
Next
For Each cell In Range("AnimeList[Genre - 3]")
genreExtract(i) = cell.Value
i = i + 1
Next
Dim distinctGenres As New Dictionary
Dim genre As Variant
For Each genre In genreExtract
If distinctGenres.exists(genre) Then
distinctGenres(genre) = distinctGenres(genre) + 1
Else
distinctGenres.Add genre, 1
End If
Next
size = distinctGenres.Count
Erase genreExtract
ReDim sortedGenres(size - 1, 1) As Variant
For i = 0 To distinctGenres.Count - 1
sortedGenres(i, 0) = distinctGenres.Keys(i)
sortedGenres(i, 1) = distinctGenres.Items(i)
Next i
distinctGenres.RemoveAll
QuickSort sortedGenres, 0, size - 1 'This is done in a separate function
End Function
At the end I have what I need, i.e. the sorted genre counts in my sortedGenre array.
But I need to output it to the excel sheet now which is proving to be rather difficult task.
I tried calling the function after adding return type "As Variant" in the declaration and adding the statement CountAndSortGenre = sortedGenres at the end like so:
=CountAndSortGenre()
but the array which is returned is not spilled across multiple cells. Instead only the first element of the array is displayed on the cell where I input the formula.
I tried using Ctrl+Shift+Enter which changed the formula to:
{=CountAndSortGenre()}
but that did not change the output. It was still the first element of the array
I tried putting it in the index formula like so:
INDEX(CountAndSortGenre(), 1, 2)
trying to at least get something other than the first value of the array but that still kept returning the first value only.
Afterwards I tried using a manual approach to push the values into the cells by removing the As Variant return type and the return value in the end and adding the following code:
For i = 0 To size - 1
Application.ActiveCell.Offset(i + 1, 1) = sortedGenres(i, 0)
Application.ActiveCell.Offset(i + 1, 2) = sortedGenres(i, 1)
Next i
This approach worked when I ran the code but when I tried using the function like:
= CountAndSortGenre()
Excel gave me circular reference warning and thus it did not work.
The reason I dont want to use the macro and want to use it as a function is that I want these values to get updated as I update my source table. I am not sure that using a function will be dynamic, but it is the best bet. But right now I just want this function to start working.
I used an Array List because I'm too lazy to go look for my QuickSort routine; and I only created a single dimension output for horizontal output.
I used the range as an argument for the function so it would update dynamically when a cell in the called range is changed.
If your range may change in size, I'd suggest using either a dynamic named range, or using a Table with structured references, either of which can auto adjust the size.
If you require a vertical output, you can either Transpose before setting the output of the function; or loop into a 2D array.
Option Explicit
Option Compare Text
Function CountAndSortGenre(rg As Range) As Variant()
Dim v As Variant, w As Variant
Dim distinctGenres As Object
v = rg
Set distinctGenres = CreateObject("System.Collections.ArrayList")
With distinctGenres
For Each w In v
If w <> "" Then
If Not .contains(w) Then .Add w
End If
Next w
.Sort
CountAndSortGenre = .toarray
End With
End Function
I'm refactoring a VBA project with about 100 modules. Some of the modules are Option Base 1, others Option Base 0 (the default). I'd like all modules to be Option Base 0 as that simplifies moving functions between modules so that they are more sensibly located.
I think I'm safe to delete all Option Base 1 statements in the project if:
All Dim and ReDim statements that set the dimensionality of arrays have explicit lower bounds.
I pay close attention to the use of the Array() function since this will return an array with lower bound determined by the Option Base of the module it's in.
Is there anything else I need to worry about?
Option Base does not affect row counting
Note that Option Base does not affect row counting and therefore does not affect arrays that were filled out of range values like
Dim MyArr() As Variant
MyArr = Range("A1:A10").Value
will always result in an array MyArr(1 To 10) no matter if Option Base is 1 or 0.
Watch out functions that return arrays
You might also need to watch out if a function returns an array that was created with Array() or with no explicit lower bounds. In that case you need also to check the parent functions/procedures array handling which might not be that obvious.
This is especialy tricky if different Option Base were used in different modules.
Option Base 1 'if you plan to change this to 0
Function MyArrayReturningFunction() As Variant
MyArrayReturningFunction = Array(1, 2, 3)
End Function
'in another module
Option Base 0 'this was already 0
'so you might assume no changes are needed in this module BUT WRONG!
Sub ParentProcedure()
Dim MyArr As Variant
MyArr = MyArrayReturningFunction
Dim iItem As Long
For iItem = 1 To 3 'You need to take care here even if this module was already Base 0
'because the array was recieved from a Base 1 function.
'Especially in combination with cells (see next paragraph)
Debug.Print MyArr(iItem)
Next iItem
End Sub
Array numbering vs. row numbering
Also if loops were used for array looping in combination with cells and ranges you need to take special attention to the counters.
Option Base 1
Sub Test()
Dim MyArr() As Variant
MyArr = Array(1, 2, 3, 4, 5) 'MyArr(1 To 5)
Dim iRow As Long
For iRow = LBound(MyArr) To UBound(MyArr) 'It looks like safe to change because
'LBound/UBound is used BUT see below …
Cells(iRow, A) = MyArr(iRow)
Next iRow
End Sub
Needs to change to
Option Base 0
Sub Test()
Dim MyArr() As Variant
MyArr = Array(1, 2, 3, 4, 5) 'MyArr(0 To 4)
Dim iRow As Long
For iRow = LBound(MyArr) To UBound(MyArr) 'even if LBound/UBound is already used,
Cells(iRow + 1, A) = MyArr(iRow) 'the counter for the cells needs to be changed,
'but NOT for the MyArr
Next iRow
End Sub
Conclusion
In general I would consider that approach as safe but you will need to have a decent look into your code. There might be traps that are not obvious to see.
All that PEH writes is correct, particularly that making this kind of change requires careful analysis.
This is the current state of my knowledge:
Code sensitive to changing the Option Base statement at the top of the module:
Dim MyArr(3) lower bound set by Option Base.
ReDim MyArr(5)lower bound set by Option Base.
Private MyArr(3) lower bound set by Option Base.
Public MyArr(3) lower bound set by Option Base.
MyArr = Array(1,2,3) Unless written as VBA.Array the Array function returns an array with lower bound set by Option Base of the module.
Code not sensitive to changing Option Base:
Dim MyArr(1 To 3) lower bound is 1.
ReDim MyArr(0 To 5) lower bound is 0.
Private MyArr(0 To 5) lower bound is 0.
Public MyArr(0 To 5) lower bound is 0.
MyArr = VBA.Array(1,2,3) lower bound is 0.
MyArr = Split("1,2,3",",") lower bound is 0.
MyArr = VBA.Split("1,2,3",",") lower bound is 0. So unlike Array, Split's behaviour is the same whether or not prefixed by VBA.. It always returns an array with lower bound zero.
The following statements are true whatever the Option Base of a module:
Indexing into a Range.Cells collection starts from 1.
The .Value and .Value2 property of a Range (with more than one cell) is a two-dimensional Variant() array whose lower bounds are 1.
I don't think that there are other "gotchas" to worry about, but I would like to know if there are.
I have a spread sheet with multiple dynamic named ranges such as HR_B1, to HR_B10 etc.
I am trying to create a function that will find the minimum value from whichever ranges are inserted into the function, i.e. the user function will input two values into the function say 3 and 6 and it will find the minimum value over ranges HR_B3 to HR_B6.
I have created a array and for loop that stores the names of the named ranges in the array.
However I cannot get the WorksheetFunction.Min code to read the contents of the array as named ranges and output the min value.
My code is:
Public Function HR_Min_Range(minval As Integer, maxval As Integer) As Variant
Dim fullrange() As Variant
Dim total_birds As Integer
Dim i As Long
total_birds = (maxval - minval)
ReDim fullrange(total_birds)
For i = 0 To total_birds
fullrange(i) = "HR_B" & (i + minval)
Next i
HR_Min_Range = WorksheetFunction.Min(Sheets("HR_Depths").Range(fullrange))
End Function
Try it as,
Option Explicit
Public Function HR_Min_Range(minval As Integer, maxval As Integer) As Variant
Dim i As Long
HR_Min_Range = Worksheets("HR_Depths").Range("HR_B" & minval).Cells(1)
For i = minval To maxval
HR_Min_Range = Application.Min(HR_Min_Range, Worksheets("HR_Depths").Range("HR_B" & i))
Next i
End Function
The correct syntax is like this:
Worksheetfunction.min([named])
or
WorksheetFunction.min(activesheet.range("named"))
or
WorksheetFunction.min(tabelle1.Range("named"))
or
Worksheetfunction.Min(worksheets("Tabelle1").range("named"))
or
Worksheetfunction.Min(tabelle1.[named])
or
Worksheetfunction.Min(worksheets("Tabelle1").[named])
Feel free to pick anything :) That's in case that the WorkSheet is with caption and code name Tabelle1 and the named range is named named.
In you case, you are not putting fullrange in parenthesis or in [. Any of these two options should work.
I have a global array named myArray.
In this array is stored information taken from another file.
The procedure handles the data stored in myArray, and it works fine.
However, when I launch the procudre a 2nd time, I have a condition where myArray is tested:
IsEmpty(myArray) returns false
So apparantly it's not empty. So when I type in the immediate window this:
UBound(myArray), I get an error message.
So it's not empty, but it doesn't have any values.
Also, IsNull(myArray) returns false.
This makes my code not work properly, because it's supposed to see if myArray is empty, and if it isn't then it accesses one by one the values of myArray. Obviously this causes an error the 2nd time around.
I tired 'Erase myArray' at the end of the procedure, that doesn't help.
Any clues?
Thanks,
Edit: Sample code:
Global myArray As Variant
Sub tryMe()
For i = 0 To 10
If IsEmpty(myArray) Then
ReDim myArray(0)
myArray(0) = i
Else
tempVal = UBound(myArray)
ReDim Preserve myArray(tempVal + 1)
myArray(tempVal + 1) = i
End If
Next i
MsgBox UBound(myArray)
End Sub