【问题标题】:Return Index of an Element in an Array Excel VBA返回数组Excel VBA中元素的索引
【发布时间】:2011-10-25 06:37:09
【问题描述】:

我有一个数组 prLst,它是一个整数列表。整数未排序,因为它们在数组中的位置代表电子表格上的特定列。我想知道如何在数组中找到一个特定的整数,并返回它的索引。

似乎没有任何资源可以告诉我如何不将数组转换为工作表上的范围。这似乎有点复杂。这对 VBA 来说是不可能的吗?

【问题讨论】:

    标签: arrays excel vba indexing find


    【解决方案1】:
    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
    

    更新为显示使用 Match(带有 .Index)在二维数组的维度中查找值:

    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)
    

    编辑:这里值得说明@ARich 在 cmets 中指出的内容 - 如果您在循环中执行此操作,则使用 Index() 对数组进行切片具有可怕的性能。

    在测试中(下面的代码),Index() 方法比使用嵌套循环慢了近 2000 倍。

    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
    

    【讨论】:

    • 它工作! +1 我真的不知道可以在 VBA 数组上使用 Match 匹配方法!
    • 许多 Excel 工作表函数都有一个类似的表单,可通过 Application.WorksheetFunction.[FunctionName]使用 IsError()。如果您包含 WorksheetFunction 部分,那么(例如)如果 Match() 找不到匹配项,它将抛出一个错误,您需要使用错误处理程序来捕获该错误。
    • 整洁! match 也适用于多维数组吗?
    • @H3lue 很难说没有实际代码你的问题是什么。另外,您甚至没有提及您尝试使用哪个版本...
    • @TimWilliams 很抱歉在这么长时间之后拖累了这个......我只是想向未来的读者指出,在我所做的一些测试中,返回一个像 @987654325 这样的“行”引用上面的@ 方法确实如此,使用常规的Do While 循环要快得多(大约快530%)。上面概述的方法在某些情况下仍然非常有用,但如果时间很紧迫,我建议使用循环。
    【解决方案2】:

    变体数组:

        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
    

    最快的整数版本(如下所示)

        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)
    

    一个 sn-p,让我们测试通过引用作为参数传递的假设意味着什么。 (答案是否定的)使用它将 myList 和 myValue 替换为您的变量名:

      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
    

    为了证明这一点,我做了一些基准测试

    结果如下:

    ---------------------------
    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   
    ---------------------------
    

    一个模块:

    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
    

    一个名为 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
    

    【讨论】:

    • 我认为糟糕的表现是因为使用变体作为参数。因为预取器效应。即如果所有内存都可以提前读取。就像所有变量都是相同的并且按照它执行良好的顺序读取。如果它使用引用在内存位置跳跃可能会更慢。每次它跳过一个引用时,它都会降低 o(1) 的性能。对于许多参考,它就像 (o(1)+o(1)+o(1)+o(1))*nloop。
    • variant 是一种封装格式。像 bstr 和安全数组这样的 ole 对象通常是系统内存中进程外部的引用。并动态分配。在记忆中的不同位置。一个安全的数组很容易成为一个引用数组。和变体也可能是对引用的引用。所以根据定义它应该很慢。我猜 excel 函数正在破解系统并针对此类问题进行了优化,并且以某种方式更快地忽略了一些引用并在可能的情况下进行检查
    • 我想如果少用几个引用可能会更快。就像参数是简单类型的整数一样。并且变量不会是数组 byref 之类的引用。但是 byval 让它们成为本地副本而不是引用(将参数类型从变体更改为整数,并将 byref 更改为 byval)也可能不使用函数,而是在每个地方使用 sn-p
    • 我做了 pef 测试。实际上性能非常好。因为使用循环而不是使用excel函数。首选项测试还显示,数组的每个函数以及循环中的每个变体都有参考访问成本。似乎excel的匹配会复制一些数据并可能转换为范围。就性能而言,这是昂贵的
    【解决方案3】:

    这是另一种方式:

    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
    

    【讨论】:

    • 循环查找值?
    【解决方案4】:

    这是你要找的吗?

    public function GetIndex(byref iaList() as integer, byval iInteger as integer) as integer
    
    dim i as integer
    
     for i=lbound(ialist) to ubound(ialist)
      if iInteger=ialist(i) then
       GetIndex=i
       exit for
      end if
     next i
    
    end function
    

    【讨论】:

      【解决方案5】:

      注意数组是从零开始还是从一开始。 此外,当函数返回位置 0 或 1 时,请确保不会将其与函数返回的 True 或 False 混淆。

      Function array_return_index(arr As Variant, val As Variant, Optional array_start_at_zero As Boolean = True) As Variant
      
      Dim pos
      pos = Application.Match(val, arr, False)
      
      If Not IsError(pos) Then
          If array_start_at_zero = True Then
              pos = pos - 1
              'initializing array at 0
          End If
         array_return_index = pos
      Else
         array_return_index = False
      End If
      
      End Function
      
      Sub array_return_index_test()
      Dim pos, arr, val
      
      arr = Array(1, 2, 4, 5)
      val = 1
      
      'When array starts at zero
      pos = array_return_index(arr, val)
      If IsNumeric(pos) Then
      MsgBox "Array starting at 0; Value found at : " & pos
      Else
      MsgBox "Not found"
      End If
      
      'When array starts at one
      pos = array_return_index(arr, val, False)
      If IsNumeric(pos) Then
      MsgBox "Array starting at 1; Value found at : " & pos
      Else
      MsgBox "Not found"
      End If
      
      
      
      End Sub
      

      【讨论】:

        【解决方案6】:
        'To return the position of an element within any-dimension array  
        'Returns 0 if the element is not in the array, and -1 if there is an error  
        Public Function posInArray(ByVal itemSearched As Variant, ByVal aArray As Variant) As Long  
        Dim pos As Long, item As Variant  
        
        posInArray = -1  
        If IsArray(aArray) Then  
            If not IsEmpty(aArray) Then  
                pos = 1  
                For Each item In aArray  
                    If itemSearched = item Then  
                        posInArray = pos  
                        Exit Function  
                    End If  
                    pos = pos + 1  
                Next item  
                posInArray = 0  
            End If  
        End If
        
        End Function
        

        【讨论】:

          【解决方案7】:

          我可以做到这一点的唯一方法(尽管很麻烦但又方便/相对快速)是连接任意维数组,并将其减少为一维,使用 "/[column number]//\| "作为分隔符。

          & 在此一维列上使用单单元格结果多重查找宏函数。

          & 然后索引匹配以提取位置。 (使用多个查找匹配)

          这样您就可以在原始任意维度数组中获得您要查找的元素/字符串的所有匹配项及其位置。在一个单元格中。

          希望我可以为整个过程编写一个宏/函数。这会让我省得更多。

          【讨论】:

          • 我还没有编写一个宏代码来执行此操作:ni.com/example/27269/en 在 2D 数组中搜索多个匹配项 此示例说明如何在 2D 数组中搜索指定值。 VI 使用 For 循环遍历 2D 数组的每个元素。每当 LabVIEW 找到一个等于搜索值的元素时,LabVIEW 会将元素的索引添加到一个单独的数组中,然后将这些索引存储在一个移位寄存器中以供循环的剩余迭代使用。该 VI 查找数组中与搜索值匹配的每个元素。
          猜你喜欢
          • 1970-01-01
          • 2015-09-12
          • 2012-11-13
          • 1970-01-01
          • 2014-04-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多