VBA - Create empty array - arrays

I have a function that takes an array of strings and map each string to a Date instance. The function boils down to the following code.
Private Function ParseDates(dates() As String) As Date()
Dim res() As Date
Dim i As Integer
If UBound(dates) >= 0 Then
ReDim res(UBound(dates)) As Date
End If
For i = LBound(dates) To UBound(dates)
res(i) = #01/01/2000#
Next i
ParseDates = res
End Function
The function works just fine as long as the argument dates is nonempty. When dates is empty res is not given a dimension. As a result, the returned value is not enumerable causing to users of this function to crash if the result is enumerated in a loop.
parsedDates = ParseDates(input)
For i = 1 To UBound(parsedDates) ' Suscription out of range
...
How do I instantiate and return an empty array, when dates is empty?
If you call Split("",".") you receive an object with type String(0 to -1). I need my function to return a object of type Date(0 to -1) as Date() is not an actual array.
I have tried with ReDim res(-1) this causes an Subscript out of range error.

This seems to do the trick:
Private Declare Function EmptyDateArray Lib "oleaut32" Alias "SafeArrayCreateVector" (Optional ByVal vt As VbVarType = vbDate, Optional ByVal lLow As Long = 0, Optional ByVal lCount As Long = 0) As Date()
Function emptyDate() as Date()
emptyDate = EmptyDateArray()
End Function
Based on an answer by user wgweto for this question at VBForums.

I've used something like this in the past.
Public Function IS_ARRAY_EMPTY(arrInput As Variant) As Boolean
Dim lngTemp As Long
On Error GoTo eHandle
lngTemp = UBound(arrInput)
IS_ARRAY_EMPTY = False
Exit Function
eHandle:
IS_ARRAY_EMPTY = True
End Function

You specifically mentioned problems in which the calling code needs to iterate over the return value and that iterating over a non-dimensioned array throws an error. Collections don't have that problem. One possibility would be to refactor your code so that it returns a collection (which might or might not have zero elements):
Private Function ParseDates(dates() As String) As Collection
Dim res As New Collection
Dim i As Long
For i = LBound(dates) To UBound(dates)
res.Add #1/1/2000#
Next i
Set ParseDates = res
End Function
Say the calling code has the line:
Set C = ParseDates(dates)
Even if C.Count = 0, the following loop works:
Dim d As Variant
For Each d In C
'process d
Next d

Try this:
Private Function ParseDates(dates() As String) As Date()
Dim res() As Date
Dim i As Integer
Dim k%
k=0
If UBound(dates) >= 0 Then
ReDim res(UBound(dates)) As Date
End If
For i = LBound(dates) To UBound(dates)
if dates(i)<>"" then
k=k+1
redim preserve res(k)
end if
res(k) = #01/01/2000#
Next i
if k=0 then
redim res(ubound(dates))
end if
ParseDates = res
End Function

Related

Redim Preserve VBA Alternatives

Background
I just read a comment on this question that states that Redim Preserve is expensive and should be avoided. I use Redim Preserve in many scenarios, let us say for example to save field names from a PT that meet some specific criteria to use them later on with an API for Access/Selenium/XlWings,etc. where I need to access the elements in the array at different times, thus not looping in the original sheet(s) where PT(s) are; I use them to save data that came outside Excel too. This is to save the time to redo verification/processes and everything that was considered by saving the array in the first place.
Research/thoughts
I have seen that a similar question was asked at VB.net where they suggest List(Of Variable) but I do not think this may be achieved within Excel. I Erase them once they are not longer needed too. In addition, where it is possible, I try to use dictionaries instead of arrays, but it may not be always the case where it is easier to go by index numbers and there is a need for array and not dictionaries. I was thinking that I may be able to create a sheet with the specified items instead of saving them to an array, but I do not see the benefit of doing so in terms of memory saving.
Question
What would be the best alternative to Redim Preserve in VBA?
The intent of Ben's comment is that you should avoid excessive use of Preserve.
Where Arrays are a good design choice, you can and should use them. This is especially true when extracting data from an Excel sheet.
So, how to avoid excessive use of Preserve?
The need to Redim Preserve implies you are collecting data into an array, usually in a loop. Redim without Preserve is pretty fast.
If you have sufficient info, calculate the required array size and ReDim it as that size once
If you don't have that info, Redim it to an oversize dimension. Redim Preserve to the actual size once, after the loop
If you must Redim Preserve in the loop, do it infrequently in large chunks
Beware of premature optimisation. If it works fast enough for your needs, maybe just leave it as is
Update for 20 May 2022. An updated version of the class below can be found at
https://github.com/FullValueRider/WCollection
This update has a more extensive collection of methods and is also available as a 32 bit or 64 bit ActiveX.dll (thanks to twinBasic). There are currently 148 passing tests so the problems of things not working should hopefully be avoided.
Please leave any further comments or requests for updates as an issue on the github page.
===============================================
A collection is a good way to go but the default collection is a bit limited.
You may wish to use a wrapped collection which gives you more flexibility.
Class WCollection (but its easy to change the name to List if you prefer)
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "WCollection"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
'Rubberduck annotations
'#PredeclaredId
'#Exposed
Option Explicit
'#ModuleDescription("A wrapper for the collection object to add flexibility")
Private Type State
Coll As Collection
End Type
Private s As State
Private Sub Class_Initialize()
Set s.Coll = New Collection
End Sub
Public Function Deb() As WCollection
With New WCollection
Set Deb = .ReadyToUseInstance
End With
End Function
Friend Function ReadyToUseInstance() As WCollection
Set ReadyToUseInstance = Me
End Function
Public Function NewEnum() As IEnumVARIANT
Set NewEnum = s.Coll.[_NewEnum]
End Function
Public Function Add(ParamArray ipItems() As Variant) As WCollection
Dim myItem As Variant
For Each myItem In ipItems
s.Coll.Add myItem
Next
Set Add = Me
End Function
Public Function AddRange(ByVal ipIterable As Variant) As WCollection
Dim myitem As Variant
For Each myitem In ipIterable
s.Coll.Add myitem
Next
Set AddRange = Me
End Function
Public Function AddString(ByVal ipString As String) As WCollection
Dim myIndex As Long
For myIndex = 1 To Len(ipString)
s.Coll.Add VBA.Mid$(ipString, myIndex, 1)
Next
End Function
Public Function Clone() As WCollection
Set Clone = WCollection.Deb.AddRange(s.Coll)
End Function
'#DefaultMember
Public Property Get Item(ByVal ipIndex As Long) As Variant
If VBA.IsObject(s.Coll.Item(ipIndex)) Then
Set Item = s.Coll.Item(ipIndex)
Else
Item = s.Coll.Item(ipIndex)
End If
End Property
Public Property Let Item(ByVal ipIndex As Long, ByVal ipItem As Variant)
s.Coll.Add ipItem, after:=ipIndex
s.Coll.Remove ipIndex
End Property
Public Property Set Item(ByVal ipindex As Long, ByVal ipitem As Variant)
s.Coll.Add ipitem, after:=ipindex
s.Coll.Remove ipindex
End Property
Public Function HoldsItem(ByVal ipItem As Variant) As Boolean
HoldsItem = True
Dim myItem As Variant
For Each myItem In s.Coll
If myItem = ipItem Then Exit Function
Next
HoldsItem = False
End Function
Public Function Join(Optional ByVal ipSeparator As String) As String
If TypeName(s.Coll.Item(1)) <> "String" Then
Join = "Items are not string type"
Exit Function
End If
Dim myStr As String
Dim myItem As Variant
For Each myItem In s.Coll
If Len(myStr) = 0 Then
myStr = myItem
Else
myStr = myStr & ipSeparator
End If
Next
End Function
Public Function Reverse() As WCollection
Dim myW As WCollection
Set myW = WCollection.Deb
Dim myIndex As Long
For myIndex = LastIndex To FirstIndex Step -1
myW.Add s.Coll.Item(myIndex)
Next
Set Reverse = myW
End Function
Public Function HasItems() As Boolean
HasItems = s.Coll.Count > 0
End Function
Public Function HasNoItems() As Boolean
HasNoItems = Not HasItems
End Function
Public Function Indexof(ByVal ipItem As Variant, Optional ipIndex As Long = -1) As Long
Dim myIndex As Long
For myIndex = IIf(ipIndex = -1, 1, ipIndex) To s.Coll.Count
If ipItem = s.Coll.Item(myIndex) Then
Indexof = myIndex
Exit Function
End If
Next
End Function
Public Function LastIndexof(ByVal ipItem As Variant, Optional ipIndex As Long = -1) As Long
Dim myIndex As Long
For myIndex = LastIndex To IIf(ipIndex = -1, 1, ipIndex) Step -1
If ipItem = s.Coll.Item(myIndex) Then
LastIndexof = myIndex
Exit Function
End If
Next
LastIndexof = -1
End Function
Public Function LacksItem(ByVal ipItem As Variant) As Boolean
LacksItem = Not HoldsItem(ipItem)
End Function
Public Function Insert(ByVal ipIndex As Long, ByVal ipItem As Variant) As WCollection
s.Coll.Add ipItem, before:=ipIndex
Set Insert = Me
End Function
Public Function Remove(ByVal ipIndex As Long) As WCollection
s.Coll.Remove ipIndex
Set Remove = Me
End Function
Public Function FirstIndex() As Long
FirstIndex = 1
End Function
Public Function LastIndex() As Long
LastIndex = s.Coll.Count
End Function
Public Function RemoveAll() As WCollection
Dim myIndex As Long
For myIndex = s.Coll.Count To 1 Step -1
Remove myIndex
Next
Set RemoveAll = Me
End Function
Public Property Get Count() As Long
Count = s.Coll.Count
End Property
Public Function ToArray() As Variant
Dim myarray As Variant
ReDim myarray(0 To s.Coll.Count - 1)
Dim myItem As Variant
Dim myIndex As Long
myIndex = 0
For Each myItem In s.Coll
If VBA.IsObject(myItem) Then
Set myarray(myIndex) = myItem
Else
myarray(myIndex) = myItem
End If
myIndex = myIndex + 1
Next
ToArray = myarray
End Function
Public Function RemoveFirstOf(ByVal ipItem As Variant) As WCollection
Set RemoveFirstOf = Remove(Indexof(ipItem))
Set RemoveFirstOf = Me
End Function
Public Function RemoveLastOf(ByVal ipItem As Variant) As WCollection
Set RemoveLastOf = Remove(LastIndexof(ipItem))
Set RemoveLastOf = Me
End Function
Public Function RemoveAnyOf(ByVal ipItem As Variant) As WCollection
Dim myIndex As Long
For myIndex = LastIndex To FirstIndex Step -1
If s.Coll.Item(myIndex) = ipItem Then Remove myIndex
Next
Set RemoveAnyOf = Me
End Function
Public Function First() As Variant
If VBA.IsObject(s.Coll.Item(FirstIndex)) Then
Set First = s.Coll.Item(FirstIndex)
Else
First = s.Coll.Item(FirstIndex)
End If
End Function
Public Function Last() As Variant
If VBA.IsObject(s.Coll.Item(LastIndex)) Then
Set Last = s.Coll.Item(LastIndex)
Else
Last = s.Coll.Item(LastIndex)
End If
End Function
Public Function Enqueue(ByVal ipItem As Variant) As WCollection
Add ipItem
Set Enqueue = Me
End Function
Public Function Dequeue() As Variant
If VBA.IsObject(s.Coll.Item(FirstIndex)) Then
Set Dequeue = s.Coll.Item(FirstIndex)
Else
Dequeue = s.Coll.Item(FirstIndex)
End If
Remove 0
End Function
Public Function Push(ByVal ipitem As Variant) As WCollection
Add ipitem
Set Push = Me
End Function
Public Function Pop(ByVal ipitem As Variant) As Variant
If VBA.IsObject(s.Coll.Item(FirstIndex)) Then
Set Pop = s.Coll.Item(FirstIndex)
Else
Pop = s.Coll.Item(FirstIndex)
End If
Remove s.Coll.Count
End Function
Public Function Peek(ByVal ipIndex As Long) As Variant
If VBA.IsObject(s.Coll.Item(FirstIndex)) Then
Set Peek = s.Coll.Item(FirstIndex)
Else
Peek = s.Coll.Item(FirstIndex)
End If
End Function
The custom collection shown in another answer looks like a helpful tool. Another one I recently came across is the BetterArray class, found here. Rather than extending the built-in collection, it extends the built-in array. I posted an answer reviewing it and a couple of other options (the ArrayList, and expansion in chunks) here.
A Collection of Array Rows
One other approach is to use a 1d array for each row of data, and add rows into a collection. When done, the result can be dumped into a 2d array. With a function for making the conversion on hand, the process can be convenient and reasonably efficient.
Function ArrayFromRowCollection(source As Collection) As Variant
'Convert a collection of 1d array rows to a 2d array
'The return array will have the max number of columns found in any row (if inconsistent, a warning is printed)
'Any non-array values in the collection will be entered in the first column of the return array (with warning printed)
'Any objects or multidimensional arrays in the collection will cause an error
Dim sourceCount As Long: sourceCount = source.Count
If sourceCount > 0 Then
'Scan for the max column count across all rows; wrap non-arrays in an array with a warning
Dim itmRow As Variant, itmIndex As Long
Dim arrayBound As Long, tempBound As Long, inconsistentBounds As Boolean
For Each itmRow In source
itmIndex = itmIndex + 1
If VarType(itmRow) < vbArray Then 'An array has a vartype of at least the vbArray constant (8192)
source.Add Array(itmRow), , itmIndex
source.Remove itmIndex + 1 'Wrap non-array element in 1d array so it is in the expected format for later
Debug.Print "ArrayFromRowCollection Warning: Non-array item found and entered in first array column (item " & itmIndex & ")"
Else
tempBound = UBound(itmRow)
If arrayBound <> tempBound Then
If itmIndex > 1 Then inconsistentBounds = True 'This prompts a warning below
If tempBound > arrayBound Then arrayBound = tempBound 'Take the new larger bound, in search of the max
End If
End If
Next
If inconsistentBounds Then Debug.Print "ArrayFromRowCollection Warning: Inconsistent column counts found."
'Create 2d array
Dim i As Long, j As Long
Dim returnArray() As Variant
ReDim returnArray(sourceCount - 1, arrayBound)
For Each itmRow In source
For j = 0 To UBound(itmRow)
returnArray(i, j) = itmRow(j)
Next
i = i + 1
Next
ArrayFromRowCollection = returnArray
Else
ArrayFromRowCollection = Array() 'Empty array for empty collection
End If
End Function
A quick demo, creating an array of data from a directory.
Sub GatherDirectoryInfo()
'Gather directory info in a collection of 1d array rows
Dim tempDir As String, dirPath As String, tempFull As String
dirPath = "C:" & Application.PathSeparator
tempDir = Dir(dirPath, vbDirectory) 'This gets basic files and folders (just the first with this call)
Dim tempCollection As Collection: Set tempCollection = New Collection
tempCollection.Add Array("Parent Folder", "Name", "Type", "File Size", "Last Modified") 'Headers
Do While tempDir <> ""
tempFull = dirPath & tempDir
tempCollection.Add Array(dirPath, tempDir, IIf(GetAttr(tempFull) And vbDirectory, "Folder", ""), Round(FileLen(tempFull) / 1024, 0) & " kb", FileDateTime(tempFull))
tempDir = Dir()
Loop
'Transfer collection to 2d array
Dim DirArray As Variant
DirArray = ArrayFromRowCollection(tempCollection)
End Sub

Sometimes can't assign to array and sometimes can

I have the next code:
Function findRanges(keyword) As Variant()
Dim foundRanges(), rngSearch As Range
Dim i, foundCount As Integer
i = 0
foundCount = 0
ReDim foundRanges(0)
Set rngSearch = ActiveDocument.Range
Do While rngSearch.Find.Execute(FindText:=keyword, MatchWholeWord:=True, Forward:=True) = True
Set foundRanges(i) = rngSearch.Duplicate
i = i + 1
ReDim Preserve foundRanges(UBound(foundRanges) + 1)
rngSearch.Collapse Direction:=wdCollapseEnd
Loop
ReDim Preserve foundRanges(UBound(foundRanges) - 1)
findRanges = foundRanges
End Function
And:
Sub test()
Dim rngIAM_Code() As Range
...
Dim rngIAM_Title() As Range
rngIAM_Code = findRanges("IAM_Code")
...
rngIAM_Title = findRanges("IAM_Title")
End Sub
What is very confuding is that sometimes the compiler says "Can't assign to array" and sometimes it works fine. For example, when I only try to search one value and populate one array, the code works. When I try to populate both array, there is an error "Can't assign to an array". I can then switch lines of code like this:
rngIAM_Title = findRanges("IAM_Title")
...
rngIAM_Code = findRanges("IAM_Code")
And then the error happens with another array. The error can happen anywhere: on the first line, in the middle, or in the end, but it is consistent as long as I don't move lines. And again, if I leave only one-two lines of code with arrays in sub "test"everything works fine.
The following works for me.
In this code, every object variable is explicitly assigned a type. In VBA, every variable must be typed, else it's assigned the type Variant by default. In the following declaration line, for example, foundRanges() is of type Variant because it's not followed by As with a data type. The same with i in the next line of code in the question.
Dim foundRanges(), rngSearch As Range
And since the arrays in the calling procedure are of type Range the function should return the same type.
I also took the liberty of passing the Document object to the function as, conceivably, some day the document in question might not be ActiveDocument but a Document object assigned using Documents.Open or Documents.Add. If this is not desired it can be changed back, but not relying on ActiveDocument is more reliable...
Additionally, I added the Wrap parameter to Find.Execute - it's always a good idea to specify that when executing Find in a loop to prevent the search from starting again at the beginning of the document (wdFindContinue).
Sub testRangesInArrays()
Dim rngIAM_Code() As Range
Dim rngIAM_Title() As Range
rngIAM_Code = findRanges("You", ActiveDocument)
rngIAM_Title = findRanges("change", ActiveDocument)
End Sub
Function findRanges(keyword As String, doc As Word.Document) As Range()
Dim foundRanges() As Range, rngSearch As Range
Dim i As Integer, foundCount As Integer
i = 0
foundCount = 0
ReDim foundRanges(0)
Set rngSearch = doc.content
Do While rngSearch.Find.Execute(findText:=keyword, MatchWholeWord:=True, _
Forward:=True, wrap:=wdFindStop) = True
Set foundRanges(i) = rngSearch.Duplicate
ReDim Preserve foundRanges(UBound(foundRanges) + 1)
i = i + 1
rngSearch.Collapse Direction:=wdCollapseEnd
Loop
findRanges = foundRanges
End Function
Here is an alternative based on a Collection instead of an Array:
I used also included Cindys Input regarding passing the document and adding wrap.
I don't exactly know what the you use the return value for, but in general a collection is a bit more flexible than an Array.
I also removed the underscores since they indicate a function of an implemented Interface and may cause problems later down the line. are used when implementing an Interface (improves readability).
As explained here you can use wrap or collapse to prevent a continuous Loop.
Option Explicit
Sub test()
Dim rngIAMCode As Collection
Dim rngIAMTitle As Collection
Set rngIAMCode = findRanges("IAM_Code", ActiveDocument)
Set rngIAMTitle = findRanges("IAM_Title", ActiveDocument)
Debug.Print "Code found : " & rngIAMCode.Count & " Times."
Debug.Print "Title found : " & rngIAMTitle.Count & " Times."
End Sub
Function findRanges(ByVal keyword As String, doc As Document) As Collection
Set findRanges = New Collection
Dim rngSearch As Range
Set rngSearch = doc.Content
With rngSearch.Find
.Text = keyword
.MatchWholeWord = True
.Forward = True
.Wrap = wdFindStop
While .Execute
findRanges.Add rngSearch.Duplicate
rngSearch.Collapse Direction:=wdCollapseEnd
Wend
End With
End Function

Type mismatch when changing argument types passed to a function

Situation:
I have a function, RemoveEmptyArrRowCol, which accepts two arguments, one of which is an array (tempArr).
When the second argument was a Long everything was fine. When I changed the second argument to a String (and the associated call variable) I got:
Type mismatch (Error 13)
So in the below code examples:
Test1 runs fine
Test2 fails
Questions:
1) Why is the behaviour different between the two?
2) How do I fix the second version so it behaves as per the first?
3) How will I future proof this for when I pass a dictionary value (array) as the 1st parameter, rather than reading directly from the worksheet?
What I have tried:
This appears to be a common question on SO and I have looked at a number of these questions; some of which I have put as references at the bottom of this question. I still, however, have not resolved why the first of these two sub works, but the second does not?
I played around with different combinations of:
Adding extra brackets
Explicitly declaring the type of tempArr: Dim tempArr() As Variant
Changing part of the function signature: ByRef tempArr() As Variant
After looking at #Fionnuala's answer to this question, MS Access/VBA type mismatch when passing arrays to function/subroutine, I decided to try using Call:
Call RemoveEmptyArrRowCol2(ws.Range("C4:I129").Value, tempStr)
This compiled but means I would need to change other parts of my code to ensure tempArr is correctly populated. If I were to do it this way, I might as well convert the function to a procedure.
As is, the flow is that I populate tempArr, in the test example, direct from the sheet and then hand off to another sub i.e.
tempArr = RemoveEmptyArrRowCol(ws.Range("C4:I129").Value, tempStr)
ArrayToSheet wb.Worksheets("Test").Range("A1"), tempArr
Note re: Question 3:
In the final version, I will be passing an array, pulled from a dictionary, as the first parameter i.e.
tempArr = RemoveEmptyArrRowCol( ArrayDict(tempStr), tempStr)
Working version:
Option Explicit
Public Sub Test1()
Dim tempArr() 'variant
Init
Dim tempStr As String: tempStr = "Response Times"
tempArr = RemoveEmptyArrRowCol(ws.Range("C4:I129").Value, categoryDict(tempStr & "Cols"))
End Sub
Private Function RemoveEmptyArrRowCol(ByRef tempArr As Variant, ByVal nCols As Long) As Variant
End Function
Failing version:
Public Sub Test2()
Dim tempArr()
Init
Dim tempStr As String: tempStr = "Response Times"
tempArr = RemoveEmptyArrRowCol2(ws.Range("C4:I129").Value, tempStr)
End Sub
Private Function RemoveEmptyArrRowCol2(ByRef tempArr As Variant, ByVal tempStr As String) As Variant
End Function
Example of the current full function:
Private Function RemoveEmptyArrRowCol(ByRef tempArr As Variant, ByVal tempStr As String) As Variant
Dim i As Long
Dim j As Long
Dim counter As Long
counter = 0
Dim tempArr2()
Dim totCol As Long
Dim adjColTotal As Long
totCol = categoryDict(tempStr & "Cols")
adjColTotal = categoryDict(tempStr & "ColsAdj")
Select Case tempStr
Case "ResponseTimes", "NoCCPR"
ReDim tempArr2(1 To 1000, 1 To adjColTotal)
For i = 1 To UBound(tempArr, 1)
If tempArr(i, 2) <> vbNullString Then 'process row
counter = counter + 1 'load row to temp array (counter becomes row count)
For j = 1 To totCol
Select Case j
Case Is < 4
tempArr2(counter, j) = tempArr(i, j)
Case Is > 4
tempArr2(counter, j - 1) = tempArr(i, j)
End Select
Next j
End If
Next i
RemoveEmptyArrRowCol = RedimArrDimOne(tempArr2, adjColTotal, counter)
Case "Incidents"
End Select
End Function
Additional references:
1) Passing arrays to functions in vba
2) Passing array to function returns compile error
3) Type mismatch error when passing arrays to a function in excel vba
4) Should I use Call keyword in VBA
It really depends on your input and output, e.g. what is in the RemoveEmptyArrRowCol2 function. This is an option, in which tempStr as String is not failing:
Public Sub Test2()
Dim tempArr()
Dim tempStr As String: tempStr = "Response Times"
tempArr = RemoveEmptyArrRowCol2(Range("C4:I129").Value, tempStr)
End Sub
Private Function RemoveEmptyArrRowCol2(ByRef tempArr As Variant, _
ByVal tempStr As String) As Variant
RemoveEmptyArrRowCol2 = Array(1, 2)
End Function
E.g., if you remove the returning value (Array(1,2), it fails) but it should fail, because it does not return anything.
Define "Dim tempArr As Variant" not as Variant-Array with "()"
Please show us your function "categoryDict" and "ArrayDict"
"Call" is not nessesary!
You access Values as follows:
Dim r As Long
Dim c As Long
For r = 1 To UBound(tempArr, 1)
For c = 1 To UBound(tempArr, 2)
Debug.Print tempArr(r, c)
Next
Next

Assigning Range array from a returning function

I want to have an array of ranges to create charts from them.
Here's how I had it:
Dim infoR As Range
Dim aRng() As Range
Dim numLvls As Integer
Set infoR = Range("H1:H100");
numLvls = getLevels()
Set aRng() = getOnlyNumericCellToArrayRanges(infoR, numLvls)
The function is this:
Function getOnlyNumericCellsRangesArrays(ByVal actRange As Range, ByVal numLvls As Integer) As Range()
Dim aRng() As Range
Redim aRng(0 To numLvls - 1)
'Some code
Set getOnlyNumericCellToArrayRanges = aRng()
End Function
I've seen several arrays examples over the internet and they use variant as a data type for that means but it doesn't compile like that too.
I've found that works with some changes:
Dim aRng
'Some code
aRng = getOnlyNumericCellToArrayRanges(infoR)
I think passing the array by reference could work, however I want to know if there is a way to make the array declaration and assignment to Range data type explicitly from the beginning.
Or how can I cast the result array back into a Range array?
An array is not an object (even when it's an array of objects), so you don't need Set here...
Sub Tester()
Dim arrRng() As Range, x As Long
arrRng = GetRangeArray()
For x = LBound(arrRng) To UBound(arrRng)
Debug.Print arrRng(x).Address()
Next x
End Sub
Function GetRangeArray() As Range()
Dim arrRng() As Range
ReDim arrRng(1 To 3)
Set arrRng(1) = ActiveSheet.Range("A1")
Set arrRng(2) = ActiveSheet.Range("A3")
Set arrRng(3) = ActiveSheet.Range("A5")
GetRangeArray = arrRng
End Function

Function in function with arrays

I don't get what's false in my code. I searched the error the whole morning! So I hope you can help me.
First, here's the problem code (the names of the variables aren't their real names):
Sheets(sheet).Range(nameOfTheRange).FormulaR1C1 = _
functionReturningString(functionReturningStrArr( _
Range(nameOfAnotherRange).Value, AnInputWorkSheet, "colNameInInputSheet"))
So my description on that:
All functions work fine standing alone, but in combination there is always this error (Language: German):
Fehler beim Kompilieren:
Unverträglicher Typ: Datenfeld oder benutzerdefinierter Typ erwartet
functionReturningString is a function with the following parameters(strArr() as Variant) --> it returns a String like a bulletlist.
functionReturningStrArr(nameWhichISearchInSheet as String, dataSheet as Worksheet, dataColumn, as String) --> it returns a Variant() for the bulletListing
I'm not sure if the second method really works so here's the code of it.
Function functionReturningStrArr(ByVal nameWhichISearchInSheet As String, ByVal datasheet As Worksheet, ByVal datacolumn As String) As String()
Dim returnArray() As String
Dim rowindex As Integer
Dim ID As String
Sheets(rawdataOverall).Cells(1, getColNumFromColName("Project")).EntireColumn.Select
'search correct dataset
For Each cell In Selection
If cell.Value = nameWhichISearchInSheet Then
rowindex = cell.row
Exit For
End If
Next cell
'get ID
ID = Sheets(rawdataOverall).Cells(rowindex, getColNumFromColName("ID")).Value
'search data from file with this ID
datasheet.Cells(1, getColNumFromColName(datacolumn)).EntireColumn.Select
Selection.UsedRange.Select
For Each cell In Selection
rowindex = cell.row
'check if row contains to this project
If Cells(rowindex, getColNumFromColName("ID")) = ID Then
ReDim Preserve returnArray(UBound(returnArray) + 1)
returnArray(UBound(returnArray)) = cell.Value
End If
Next cell
functionReturningStrArr = returnArray()
If you are asking yourselves what is getColNumFromColName, it is a method which works really fine, I used it in other projects too.
You really have to start declaring everything explicitly using Dim -- and force yourself to do this by writing Option Explicit at the top of your module. That way you will identify errors much more quickly.
Here
'get ID
ID = Sheets(rawdataOverall).Cells(rowindex, getcolnumformcolname("ID")).Value
you call a function called getcolnumformcolname; presumably form is a typo and you meant From as in getColNumFromColName. Had you had Option Explicit, you would have detected that error immediately.
The following three variables/arrays are not declared: rawdataOverall, cell, getDataFromThisProject. You should declare them and assign them a type explicitly.
Try fixing those things and see where that brings you.
Is seems a small portion of the function code snippet is wrong. on the very last line, you should assign the value of returnArray() to your function name like so:
functionReturningStrArr = returnArray()
Otherwise, you would need to extract the array from a variable named "getDataFromActualProject", as is shown in the example.
EDIT:
Alter your function "functionReturningStrArr As Variant" to return "As String()" instead of Variant. It seems you cant cast a Variant to a string array as you would expect.
EDIT:
I created a function to test this. This compile error shows up when you try to cast Variant as Array of string. This also includes a fix, your function that returns Variant MUST return array of string instead.
Sub RunTest()
Debug.Print getStringFromArray(getArray())
Debug.Print getStringFromArray(getVariant()) ' compile error! you cannot cast variant to array of string
End Sub
Function getArray() As String()
Dim returnArray(2) As String
returnArray(0) = "A"
returnArray(1) = "B"
returnArray(2) = "C"
getArray = returnArray()
End Function
Function getVariant() As Variant()
Dim returnArray(2) As String
returnArray(0) = "A"
returnArray(1) = "B"
returnArray(2) = "C"
getArray = returnArray() ' Not a compile error, you can cast string array to variant
End Function
Function getStringFromArray(inputArray() As String) As String
Dim returnString As String
For i = LBound(inputArray()) To UBound(inputArray())
If returnString = "" Then
returnString = inputArray(i)
Else
returnString = returnString & "," & inputArray(i)
End If
Next i
getStringFromArray = returnString
End Function

Resources