function to use an array - vba

I am trying to write a function that uses the upper and lower bound of an array and returns the number of elements. It is confusing because of the lack of experience I have with functions but nonetheless I have come up with this code below:
Function numelement(LBound(array1), UBound(array2)) as Integer
numelement = UBound(array1) - LBound(array2)
End Function
Is this code correct, I know its simple but thats what the purpose of this code is to be.

If you realy want to have a function (you can do it easily without it), then try the code below:
Function numelement(array1 As Variant) As Long
numelement = (UBound(array1) - LBound(array1)) + 1 '<-- since array iz zero based
End Function
Sub Code to test the Function:
Sub TestFunc()
Dim Arr As Variant
Arr = Array("Shai", "Rado", "Tel-Aviv")
MsgBox numelement(Arr)
End Sub
Note: instead of the function, you can use:
Dim array1 As Variant
array1 = Array("Shai", "Rado", "Tel-Aviv")
MsgBox "number of elements in array are " & UBound(array1 ) + 1

You don't need a function for this. Ubound +1 works nicely for the total number of elements, if you initalize array like this Array(). It is a built-it function in VBA, it works great:
Option Explicit
Sub TestMe()
Dim myArr As Variant
myArr = Array("A", "B", "C")
Debug.Print LBound(myArr)
Debug.Print UBound(myArr)
End Sub
Unless you decide to initialize an array like this:
Dim arr(3 To 4)
arr(3) = 1
arr(4) = 2
Then you should use the following:
debug.print UBound(myArr)-LBound(myArr)+1

Related

In VBA, how to check if two arrays contain the same element?

I suspect I might need a function for that. I would like to return a Boolean. E.g., firstarray = ("haha", "ya", "test") and secondarray = ("haha", "no", "stop"), then return True. Thanks
Are There Any Matching Elements in Two Arrays? (One-Liner)
A safer yet less efficient way would probably be to loop through each element in one array and, in another (inner) loop, compare it to each element of the other array exiting the loops when a match is found.
Option Explicit
Sub Test()
Dim FirstArray() As Variant
FirstArray = Array("A", "ya", CVErr(xlErrNA), "test")
Dim SecondArray() As Variant
SecondArray = Array("sa", "aha", CVErr(xlErrNA), "stop")
If FoundMatchingElement(FirstArray, SecondArray) Then
Debug.Print "True"
Else
Debug.Print "False"
End If
' Result:
' False ' ... since error values are excluded from the comparison
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a boolean indicating whether two arrays ('Arr1', 'Arr2')
' have a same element.
' Remarks: 'Application.Match' is case-insensitive i.e. 'A = a'.
' Error values will not be considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function FoundMatchingElement( _
Arr1() As Variant, _
Arr2() As Variant) _
As Boolean
FoundMatchingElement _
= Application.Count(Application.Match(Arr1, Arr2, 0)) > 0
End Function
Please, use the next function. It does what you want without any iteration. It returns True if any of their elements are common to both of them:
Function MatchArrayElement(arr1, arr2) As Boolean
Dim arrMtch: arrMtch = Application.IfError(Application.match(arr1, arr2, 0), "x") 'place "x" instead of error (not matching elements)
MatchArrayElement = UBound(filter(arrMtch, "x", False)) > -1
End Function
It can be used in the next way:
Sub testMatchArrEl()
Dim arr1, arr2
arr1 = Array("hah", "ya", "test")
arr2 = Array("haha", "no", "stop")
Debug.Print MatchArrayElement(arr1, arr2)
End Sub
But if you need checking if a specific element/string exists in an array, you can use the following function:
Function arrElementMatch(El, arr) As Boolean
Dim mtch: mtch = Application.match(El, arr, 0)
arrElementMatch = Not IsError(mtch)
End Function
It can be checked in the next way:
Debug.Print arrElementMatch("haha", arr1), arrElementMatch("hah", arr1)
To check if the element exists in both array, try using:
Debug.Print arrElementMatch("haha", arr1) And arrElementMatch("haha", arr2)
Please, send some feedback after using it.
If something not clear enough, do not hesitate to ask for clarifications...

Converting VBA Collection to Array

I am trying to create an array with all the worksheet names in my workbook that have the word 'Template' in it. I thought the easiest way to do this would be to create a collection first and then convert it to an array but I am having trouble. Right now the error I am getting is on the
collectionToArray (col)
line. I am receiving an
Argument Not Optional error
Pretty stuck, any help is super appreciated. Thanks!!
Public col As New Collection
Public Sub Test()
For Each ws In ThisWorkbook.Worksheets
If InStr(ws.Name, "Template") <> 0 Then
col.Add ws.Name
End If
Next ws
collectionToArray (col)
End Sub
Function collectionToArray(c As Collection) As Variant()
Dim a() As Variant: ReDim a(0 To c.Count - 1)
Dim i As Integer
For i = 1 To c.Count
a(i - 1) = c.Item(i)
Next
collectionToArray = a
End Function
collectionToArray (col)
Notice that whitespace between the function's name and its argument list? That's the VBE telling you this:
I'll take that argument, evaluate it as a value, then pass it ByVal to that procedure you're calling, even if the signature for that procedure says ByRef, explicitly or not.
This "extraneous parentheses" habit is inevitably going to make you bump into weird "Object Required" runtime errors at one point or another: lose it.
The Function is overdoing it IMO: a Variant can perfectly well wrap an array, so I'd change its signature to return a Variant instead of a Variant().
Integer being a 16-bit signed integer type (i.e. short in some other languages), it's probably a better idea to use a Long instead (32-bit signed integer, i.e. int in some other languages) - that way you'll avoid running into "Overflow" issues when you need to deal with more than 32,767 values (especially common if a worksheet is involved).
Public col As New Collection
This makes col an auto-instantiated object variable, and it has potentially surprising side-effects. Consider this code:
Dim c As New Collection
c.Add 42
Set c = Nothing
c.Add 42
Debug.Print c.Count
What do you expect this code to do? If you thought "error 91, because the object reference is Nothing", you've been bitten by auto-instantiation. Best avoid it, and keep declaration and assignments as separate instructions.
Other than that, CLR's answer has your solution: a Function should return a value, that the calling code should consume.
result = MyFunction(args)
You'll notice the VBE clearing any whitespace you might be tempted to add between MyFunction and (args) here: that's the VBE telling you this:
I'll take that argument, pass it to MyFunction, and assign the function's return value to result.
It's all there, you're just not using the Function as a function. You need to store the result in something, like 'NewArray'..?
Public col As New Collection
Public Sub Test()
For Each ws In ThisWorkbook.Worksheets
If InStr(ws.Name, "Template") <> 0 Then
col.Add ws.Name
End If
Next ws
' Tweaked as per Vityata's comment
If col.Count > 0 Then
newarray = collectionToArray(col)
Else
' Do something else
End If
End Sub
Function collectionToArray(c As Collection) As Variant()
Dim a() As Variant: ReDim a(0 To c.Count - 1)
Dim i As Integer
For i = 1 To c.Count
a(i - 1) = c.Item(i)
Next
collectionToArray = a
End Function
This is my collectionToArray function:
Public Function CollectionToArray(myCol As Collection) As Variant
Dim result As Variant
Dim cnt As Long
If myCol.Count = 0 Then
CollectionToArray = Array()
Exit Function
End If
ReDim result(myCol.Count - 1)
For cnt = 0 To myCol.Count - 1
result(cnt) = myCol(cnt + 1)
Next cnt
CollectionToArray = result
End Function
It is better than the one you are using, because it will not give an error, if the collection is empty.
To avoid the error on an empty collection in your case, you may consider adding a check like this:
If col.Count > 0 Then k = CollectionToArray(col)
Instead of using a standard collection, you can use a dictionary because of the .keys method that can transfer all info to an array. Less coding, simple :)
Dim ws As Worksheet
Dim arr
Dim Dic As Scripting.Dictionary
Set Dic = New Dictionary
For Each ws In ActiveWorkbook.Worksheets
If ws.Name Like "Template" Then
Dic.Add ws.Name, "Awesome"
End If
Next ws
arr = Dic.Keys

VBA UBound Function

I'm trying to explore UBound applications for my code in Visual Basic for Applications (VBA). Let's say I have a 4 by 2 array...(A1:B4) and I want to count the number of rows. I would think my code would be...
Function test(list) As Double
test = UBound(list)
End Function
My input is =test(A1:B4)but so far I get "#value!" error. I thought the return would be 4.
What am I doing wrong? I know how to get the number of rows using the row command but I simply want to go through the coding exercise.
List is range object not an array. Range.Value will return an array of values from a range and Range.Formula will return an array of formulas from a range.
Function test(list As Range) As Double
test = UBound(list.Value)
End Function
It seems that you have 2-dimensional array, therefore you have to provide additional parameter for UBound like:
UBound(array, dimension)
Please remember, if you get array from Excel Range than it is 1-based array.
Complete solution can look like this one:
Function testArrray(List, Dimmension)
Dim ListConverted As Variant
ListConverted = List
testArrray = UBound(ListConverted, Dimmension)
End Function
Sample call: =testArrray(G15:H20,1) produces 6 as a result which is correct.
Since you do not Dim list explicitly, it is Variant. So if you call =TEST(A1:B4) it will be a Range and not an Array. A Range does not have a UBound but does have Rows.Count.
So:
Function test(list As Range) As Double
test = list.Rows.Count
End Function
will work.
Or if you really need an Array, you could do:
Function test(list As Variant) As Double
list = list
test = UBound(list)
End Function
The list = list does the following: If list is a Range-Object then it will be implicit converted to Array since Set is not used to set an object. If list is an Array already, then it will be also an Array after that.
This is how you get the number of rows in a function.
Option Explicit
Function l_number_of_rows(rng_range As Range) As Long
l_number_of_rows = rng_range.Rows.Count
End Function
if you want the one from the dimension, this is a possible solution.
Sub test()
Dim MyArray(1 To 2, 0 To 3) As Long
MyArray(1, 0) = 10
MyArray(1, 1) = 11
MyArray(1, 2) = 12
MyArray(1, 3) = 13
MyArray(2, 0) = 20
MyArray(2, 1) = 21
MyArray(2, 2) = 22
MyArray(2, 3) = 23
Debug.Print MyArray(UBound(MyArray), 3)
End Sub
The UBound function is to give you elements in a variant array, you're passing it a range. You could try this:
Sub mains()
Dim myRange As Range, myArray As Variant
Set myRange = Range("A1:B4")
myArray = myRange.Value
Debug.Print test(myArray)
End Sub
Function test(list) As Double
test = UBound(list)
End Function
EDIT With a two dimensional range (as mentioned by KazimierzJawor) the default ubound will be the vertical, as you wanted, if you want to specify, you can add the optional perameter UBound(list, 1), but with the data you specified this defults to 4 as you wished
Ubound is a function that works on arrays you are passing it a range
try this
dim i(3)
i(0) = 1
i(1) =2
i(2) = 3
msgbox ubound(i)

Count all Comma In Selection or selected text

I want to count all Commas "," that occur only in selected text after that I will use Count as Integer to run the loop
My question is how do i Count , as following Image shows:
I Don't know how to use split and ubound. what is wrong with following code?
Sub CountComma()
Dim x As String, count As Integer, myRange As Range
Set myRange = ActiveDocument.Range(Selection.Range.Start, Selection.Range.End)
x = Split(myRange, ",")
count = UBound(x)
Debug.Print count
End Sub
A simple split will work.
x = Split("XXX,XXX,XXX,XXX,XX,XX", ",")
Count = UBound(x)
Debug.Print Count
B/c the array starts at zero you can take to Ubound number as is.
EDIT:
To use a range .
x = Split(Range("A1").Value, ",")
To break down the code.
Split("A string value","Delimiter to split the string by")
And if you want a single line of code than,
x = UBound(Split(myRange, ","))
your code is wrong in the initial declaration statement of x variable as of string type , since in the subsequent statement
with x = Split(myRange, ",")
you'd want x hold the return value of Split() function which is an array (see here), thus of Variant type
so you have to use
Dim x As Variant
But you can simplify your code as follows
Option Explicit
Sub CountComma()
Dim count As Integer
count = UBound(Split(Selection, ","))
Debug.Print count
End Sub
since:
you don't need any Range type variable to store Selection object into, being Selection the selected range already (see here)
you don't need the x Variant variable neither, feeding UBound()function (which expects an array as its first argument) directly with the Split() function which, as we saw above, returns just an array!
Finally I'd give out an alternative method of counting commas in a range
Sub CountComma()
Dim countAs Integer
count = Len(Selection) - Len(Replace(Selection, ",", ""))
Debug.Print count
End Sub
Thanks to KyloRen and Cindy Meister, Now I can use split and Ubound for Counting , in selection.text.
Following is working Code:
Sub Count_Words()
Dim WrdArray() As String, myRange As String
myRange = ActiveDocument.Range(Selection.Range.Start, Selection.Range.End)
WrdArray() = Split(myRange, ", ")
MsgBox ("Total , in the string : " & UBound(WrdArray()))
End Sub

Function will not return array when range contains only one value

I have a function meant to return an array which is created out of a single-column list of data. I have been using this function's return value essentially as a pseudo-global variable (LINENAMES_ARRAY) which I pass to many functions. Those functions than do checks on it such as If Len(Join(LINENAMES_ARRAY)) = 0 Then or go through items with For Each statements. Here is the code:
Function LINENAMES_ARRAY() As Variant
'returns an array of all items in the main sheet linenames column
LINENAMES_ARRAY = Application.Transpose(MAIN.Range( _
MAIN.Cells(MAIN_HEAD_COUNT + 1, MAIN_LINENAMES_COLUMN), _
MAIN.Cells(LINENAMES_COUNT + 1, MAIN_LINENAMES_COLUMN)))
End Function
I recently stumbled on one of those you-don't-see-it-till-you-see-it problems while using this workbook for a new project, where if the array happens to be only 1 element, everything fails. Apparently in that case, this returns a single value so Join() will fail For Each __ in LINENAMES_ARRAY will too. Why won't it treat this as a 1x1 array rather than a free value? I have started to mitigate the problem by rewriting functions where this is called, to check whether it is an array, then do some other procedure. Things like:
For j = 1 To LINENAMES_COUNT
LINES_BOX.AddItem lineNames(j)
Next j
is changed to:
If Not IsArray(LINENAMES_ARRAY) Then
myListBox.AddItem CStr(LINENAMES_ARRAY)
Else
For j = 1 To LINENAMES_COUNT
LINES_BOX.AddItem LINENAMES_ARRAY(j)
Next j
End If
However this becomes messy and is adding many extra checks to my code that I would prefer to handle in the LINENAMES_ARRAY function. Is there a way to return a 1x1 array? Or any other workaround?
An array can have a single element if you create it as a single element array and populate it in an array manner.
Option Explicit
Dim MAIN_HEAD_COUNT As Long
Dim LINENAMES_COUNT As Long
Dim MAIN_LINENAMES_COLUMN As Long
Dim MAIN As Worksheet
Sub stuff()
Dim arr As Variant
Set MAIN = Worksheets("Sheet1")
MAIN_LINENAMES_COLUMN = 2
MAIN_HEAD_COUNT = 2
LINENAMES_COUNT = 2
arr = LINENAMES_ARRAY()
Debug.Print IsArray(arr)
Debug.Print LBound(arr) & ":" & UBound(arr)
End Sub
Function LINENAMES_ARRAY() As Variant
Dim a As Long, tmp() As Variant
ReDim tmp(0 To LINENAMES_COUNT - MAIN_HEAD_COUNT)
For a = 0 To LINENAMES_COUNT - MAIN_HEAD_COUNT
tmp(a) = MAIN.Range(MAIN.Cells(MAIN_HEAD_COUNT + 1, MAIN_LINENAMES_COLUMN), _
MAIN.Cells(LINENAMES_COUNT + 1, MAIN_LINENAMES_COLUMN)).Cells(a).Value2
Next a
'returns an array of all items in the main sheet linenames column
LINENAMES_ARRAY = tmp
End Function
Results from the VBE's Immediate window:
True
0:0