Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return Index of an Element in an Array Excel VBA

I have an array prLst that is a list of integers. The integers are not sorted, because their position in the array represents a particular column on a spreadsheet. I want to know how I find a particular integer in the array, and return its index.

There does not seem to be any resource on showing me how without turning the array into a range on the worksheet. This seems a bit complicated. Is this just not possible with VBA?

like image 763
H3lue Avatar asked Aug 11 '11 19:08

H3lue


People also ask

How do you return the index of an element in an array?

To find the position of an element in an array, you use the indexOf() method. This method returns the index of the first occurrence the element that you want to find, or -1 if the element is not found.

How do I return the number of elements in an array in VBA?

In VBA, to get the length of an array means to count the number of elements you have in that array. For this, you need to know the lowest element and the highest element. So, to get this you can use the UBOUND and LBOUND functions that return the upper bound and lower bound, respectively.

How are arrays indexed in VBA?

By default, an array is indexed beginning with zero, so the upper bound of the array is 364 rather than 365. To set the value of an individual element, you specify the element's index. The following example assigns an initial value of 20 to each element in the array.

Can an excel VBA function return an array?

A function in a normal module (but not a Class module) can return an array by putting () after the data type. Note that what is returned is actually a copy of the array inside the function, not a reference. So if the function returns the contents of a Static array its data can't be changed by the calling procedure.


3 Answers

Dim pos, arr, val

arr=Array(1,2,4,5)
val = 4

pos=Application.Match(val, arr, False)

if not iserror(pos) then
   Msgbox val & " is at position " & pos
else
   Msgbox val & " not found!"
end if

Updated to show using Match (with .Index) to find a value in a dimension of a two-dimensional array:

Dim arr(1 To 10, 1 To 2)
Dim x

For x = 1 To 10
    arr(x, 1) = x
    arr(x, 2) = 11 - x
Next x

Debug.Print Application.Match(3, Application.Index(arr, 0, 1), 0)
Debug.Print Application.Match(3, Application.Index(arr, 0, 2), 0)

EDIT: it's worth illustrating here what @ARich pointed out in the comments - that using Index() to slice an array has horrible performance if you're doing it in a loop.

In testing (code below) the Index() approach is almost 2000-fold slower than using a nested loop.

Sub PerfTest()

    Const VAL_TO_FIND As String = "R1800:C8"
    Dim a(1 To 2000, 1 To 10)
    Dim r As Long, c As Long, t

    For r = 1 To 2000
        For c = 1 To 10
            a(r, c) = "R" & r & ":C" & c
        Next c
    Next r

    t = Timer
    Debug.Print FindLoop(a, VAL_TO_FIND), Timer - t
    ' >> 0.00781 sec

     t = Timer
    Debug.Print FindIndex(a, VAL_TO_FIND), Timer - t
    ' >> 14.18 sec

End Sub

Function FindLoop(arr, val) As Boolean
    Dim r As Long, c As Long
    For r = 1 To UBound(arr, 1)
    For c = 1 To UBound(arr, 2)
        If arr(r, c) = val Then
            FindLoop = True
            Exit Function
        End If
    Next c
    Next r
End Function

Function FindIndex(arr, val)
    Dim r As Long
    For r = 1 To UBound(arr, 1)
        If Not IsError(Application.Match(val, Application.Index(arr, r, 0), 0)) Then
            FindIndex = True
            Exit Function
        End If
    Next r
End Function
like image 139
Tim Williams Avatar answered Oct 02 '22 18:10

Tim Williams


array of variants:

    Public Function GetIndex(ByRef iaList() As Variant, ByVal value As Variant) As Long

    Dim i As Long

     For i = LBound(iaList) To UBound(iaList)
      If value = iaList(i) Then
       GetIndex = i
       Exit For
      End If
     Next i

    End Function

a fastest version for integers (as pref tested below)

    Public Function GetIndex(ByRef iaList() As Integer, ByVal value As Integer) As Integer
     Dim i As Integer

     For i = LBound(iaList) To UBound(iaList)
      If iaList(i) = value Then: GetIndex = i: Exit For:
     Next i

    End Function

' a snippet, replace myList and myValue to your varible names: (also have not tested)

a snippet, lets test the assumption the passing by reference as argument means something. (the answer is no) to use it replace myList and myValue to your variable names:

  Dim found As Integer, foundi As Integer ' put only once
  found = -1
  For foundi = LBound(myList) To UBound(myList):
   If myList(foundi) = myValue Then
    found = foundi: Exit For
   End If
  Next
  result = found

to prove the point I have made some benchmarks

here are the results:

---------------------------
Milliseconds
---------------------------
result0: 5 ' just empty loop

result1: 2702  ' function variant array

result2: 1498  ' function integer array

result3: 2511 ' snippet variant array

result4: 1508 ' snippet integer array

result5: 58493 ' excel function Application.Match on variant array

result6: 136128 ' excel function Application.Match on integer array
---------------------------
OK   
---------------------------

a module:

Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
#If VBA7 Then
    Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr) 'For 64 Bit Systems
#Else
    Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 'For 32 Bit Systems
#End If

    Public Function GetIndex1(ByRef iaList() As Variant, ByVal value As Variant) As Long

    Dim i As Long

     For i = LBound(iaList) To UBound(iaList)
      If value = iaList(i) Then
       GetIndex = i
       Exit For
      End If
     Next i

    End Function


'maybe a faster variant for integers

    Public Function GetIndex2(ByRef iaList() As Integer, ByVal value As Integer) As Integer
     Dim i As Integer

     For i = LBound(iaList) To UBound(iaList)
      If iaList(i) = value Then: GetIndex = i: Exit For:
     Next i

    End Function

' a snippet, replace myList and myValue to your varible names: (also have not tested)



    Public Sub test1()
     Dim i As Integer

     For i = LBound(iaList) To UBound(iaList)
      If iaList(i) = value Then: GetIndex = i: Exit For:
     Next i

    End Sub


Sub testTimer()

Dim myList(500) As Variant, myValue As Variant
Dim myList2(500) As Integer, myValue2 As Integer
Dim n

For n = 1 To 500
myList(n) = n
Next

For n = 1 To 500
myList2(n) = n
Next

myValue = 100
myValue2 = 100


Dim oPM
Set oPM = New PerformanceMonitor
Dim result0 As Long
Dim result1 As Long
Dim result2 As Long
Dim result3 As Long
Dim result4 As Long
Dim result5 As Long
Dim result6 As Long

Dim t As Long

Dim a As Long

a = 0
Dim i
't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000

Next
result0 = oPM.TimeElapsed() '  GetTickCount - t

a = 0

't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
a = GetIndex1(myList, myValue)
Next
result1 = oPM.TimeElapsed()
'result1 = GetTickCount - t


a = 0

't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
a = GetIndex2(myList2, myValue2)
Next
result2 = oPM.TimeElapsed()
'result2 = GetTickCount - t



a = 0

't = GetTickCount

oPM.StartCounter
Dim found As Integer, foundi As Integer ' put only once
For i = 1 To 1000000
found = -1
For foundi = LBound(myList) To UBound(myList):
 If myList(foundi) = myValue Then
  found = foundi: Exit For
 End If
Next
a = found
Next
result3 = oPM.TimeElapsed()
'result3 = GetTickCount - t



a = 0

't = GetTickCount

oPM.StartCounter
For i = 1 To 1000000
found = -1
For foundi = LBound(myList2) To UBound(myList2):
 If myList2(foundi) = myValue2 Then
  found = foundi: Exit For
 End If
Next
a = found
Next
result4 = oPM.TimeElapsed()
'result4 = GetTickCount - t


a = 0

't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
a = pos = Application.Match(myValue, myList, False)
Next
result5 = oPM.TimeElapsed()
'result5 = GetTickCount - t



a = 0

't = GetTickCount
oPM.StartCounter
For i = 1 To 1000000
a = pos = Application.Match(myValue2, myList2, False)
Next
result6 = oPM.TimeElapsed()
'result6 = GetTickCount - t


MsgBox "result0: " & result0 & vbCrLf & "result1: " & result1 & vbCrLf & "result2: " & result2 & vbCrLf & "result3: " & result3 & vbCrLf & "result4: " & result4 & vbCrLf & "result5: " & result5 & vbCrLf & "result6: " & result6, , "Milliseconds"
End Sub

a class named PerformanceMonitor

Option Explicit

Private Type LARGE_INTEGER
    lowpart As Long
    highpart As Long
End Type

Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As LARGE_INTEGER) As Long
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As LARGE_INTEGER) As Long

Private m_CounterStart As LARGE_INTEGER
Private m_CounterEnd As LARGE_INTEGER
Private m_crFrequency As Double

Private Const TWO_32 = 4294967296# ' = 256# * 256# * 256# * 256#

Private Function LI2Double(LI As LARGE_INTEGER) As Double
Dim Low As Double
    Low = LI.lowpart
    If Low < 0 Then
        Low = Low + TWO_32
    End If
    LI2Double = LI.highpart * TWO_32 + Low
End Function

Private Sub Class_Initialize()
Dim PerfFrequency As LARGE_INTEGER
    QueryPerformanceFrequency PerfFrequency
    m_crFrequency = LI2Double(PerfFrequency)
End Sub

Public Sub StartCounter()
    QueryPerformanceCounter m_CounterStart
End Sub

Property Get TimeElapsed() As Double
Dim crStart As Double
Dim crStop As Double
    QueryPerformanceCounter m_CounterEnd
    crStart = LI2Double(m_CounterStart)
    crStop = LI2Double(m_CounterEnd)
    TimeElapsed = 1000# * (crStop - crStart) / m_crFrequency
End Property
like image 29
Shimon Doodkin Avatar answered Oct 02 '22 18:10

Shimon Doodkin


Here's another way:

Option Explicit

' Just a little test stub. 
Sub Tester()

    Dim pList(500) As Integer
    Dim i As Integer

    For i = 0 To UBound(pList)

        pList(i) = 500 - i

    Next i

    MsgBox "Value 18 is at array position " & FindInArray(pList, 18) & "."
    MsgBox "Value 217 is at array position " & FindInArray(pList, 217) & "."
    MsgBox "Value 1001 is at array position " & FindInArray(pList, 1001) & "."

End Sub

Function FindInArray(pList() As Integer, value As Integer)

    Dim i As Integer
    Dim FoundValueLocation As Integer

    FoundValueLocation = -1

    For i = 0 To UBound(pList)

        If pList(i) = value Then

            FoundValueLocation = i
            Exit For

        End If

    Next i

    FindInArray = FoundValueLocation

End Function
like image 20
Paul McLain Avatar answered Oct 02 '22 20:10

Paul McLain