【问题标题】:Excel taking really long to calculate a UDF VBAExcel 需要很长时间才能计算 UDF VBA
【发布时间】:2022-01-12 22:08:27
【问题描述】:

example2 example1 The file name I'm trying to match is on Row A and I'm looking through Row I to see if there is a match 我找到了这段代码,我不记得在哪里,但我试图将一行零件号与其图像文件名的一行匹配。这段代码有效,但是,当我运行它时存在一个问题,即使只计算 1 列也需要很长时间,当我一次执行数百个时,我的 excel 只是停止响应,并且我需要匹配数千种产品。我对 VBA 很陌生,所以我什至无法找出问题所在。

请帮忙,谢谢。

'Name function and arguments
Function SearchChars(lookup_value As String, tbl_array As Range) As String
'Declare variables and types
Dim i As Integer, str As String, Value As String
Dim a As Integer, b As Integer, cell As Variant
'Iterste through each cell
For Each cell In tbl_array
  'Save cell value to variable
  str = cell
  'Iterate through characters
  For i = 1 To Len(lookup_value)
    'Same character?
    If InStr(cell, Mid(lookup_value, i, 1)) > 0 Then
      'Add 1 to number in array
      a = a + 1
      'Remove evaluated character from cell and contine with remaning characters
      cell = Mid(cell, 1, InStr(cell, Mid(lookup_value, i, 1)) - 1) & Mid(cell, InStr(cell, Mid(lookup_value, i, 1)) + 1, 9999)
    End If
  'Next character
  Next i
 
a = a - Len(cell)
'Save value if there are more matching characters than before  
If a > b Then
  b = a
  Value = str
End If
 
a = 0
Next cell
'Return value with the most matching characters
SearchChars = Value
End Function

【问题讨论】:

  • For Each cell In tbl_array - 这是逐个单元格的迭代,非常慢。相反,您想将tbl_array.Value 读入Variant 数组,然后循环该数组。换句话说,你认为你有一个数组,但实际上你没有。
  • 您好,感谢您的回复。那么我是否将 tbl_array 中的每个单元格更改为 tbl_array.Value 中的每个单元格?抱歉,我真的不知道自己在做什么。
  • 或许先阅读Arrays and Ranges
  • 尝试将 Excel 从自动计算切换到手动:文件->选项->公式。更新每个单元格后,Excel 可能会重新计算整个工作簿。如果将其设置为手动,则可以在脚本运行完成后按 F9 手动重新计算。这可能无法完全解决问题,但可能会产生很大的不同
  • @ANeonTetra 感谢您的帮助!

标签: excel vba user-defined-functions


【解决方案1】:

编辑(查看数据后):以下应该明显更快(并且明显更简单)

'Name function and arguments
Function SearchChars(lookup_value As String, tbl_array As Range) As String
    
    'Declare variables and types
    Dim inLenMatched%, vnVal, varLookupValues()
    
    'Puts lookup cell values into a array (to speed things up)
    varLookupValues = tbl_array.Value
    
    'Iterate through each lookup value
    For Each vnVal In varLookupValues
      
        'Ignore empty cells
        If vnVal <> "" Then
            
            'Does part number appear in filename?
            If InStr(lookup_value, vnVal) > 0 Then
                
                'Is this match the most complete match so far?
                If Len(vnVal) > inLenMatched Then
                    inLenMatched = Len(vnVal)
                    SearchChars = vnVal
                End If
             End If
        End If
        
    Next vnVal
    
    'Return match value (or 'No Match' if not matched)
    If SearchChars = "" Then SearchChars = "No Match"

End Function

以上只是一种现成的方法。
还有其他(并且可能更快)的方法来解决这个问题。

提高性能的最明显步骤(无论采用何种方法)是将tbl_array 限制为仅包含数据的行(而不是整个列)。

另外:如果不知道所有可能的情况,就不可能肯定地说。但是,这很可能可以使用本机 excel 函数来完成,并且(如果是的话)将提供最佳性能。

【讨论】:

  • 谢谢@spinner,我会试试你的代码。我在帖子中发布了我的数据样本的屏幕截图。
  • 这真的很棒,但是,它仍然部分匹配很多,而不是仅仅用“不匹配”填充列。
  • 1) 很高兴为您提供帮助。 2) 不确定我是否理解。你能举例说明你的意思吗? 3) 分别:所有的文件名都是??_PartNumber_??.jpg的形式吗?
  • 我在名为“example2”的帖子上放了一张截图。
  • so on "all_filenames" 是它的 .jpg 文件,一个零件号可以有多个图像。但有些零件号甚至可能没有 .jpg 可用。我已经尝试过 VLOOKUP 公式,但这样做的问题是因为一个零件号可以有 2 或 3 个图像,并且最后有一个额外的字母或单词,它不会匹配它。
【解决方案2】:

我试图修改您现有的代码,但我发现使用我认为更好的结构重写它更容易。并且在运行代码超过 26 列和 432 行后,只需 0.2 秒即可找到最接近的匹配字符串。

我将每个值都移到了一个数组中。 我将lookup_value 和“单元格值”转换为字节数组。 我比较了字节数组来计算匹配的“字符”。 然后我返回匹配“字符”数量最多的字符串。

Sub Example()
    Dim StartTime As Double
    StartTime = Timer * 1000
    Debug.Print SearchChars3("Toddleson", Range("A1:Z432"))
    Debug.Print "Time Elapsed: " & Timer * 1000 - StartTime & " ms"
    'Time Elapsed: 171.875 ms
End Sub

Function SearchChars3(lookup_value As String, tbl_array As Range) As String
    Dim ClosestMatch As String, HighestMatchCount As Integer
    
    Dim tbl_values() As Variant
    tbl_values = tbl_array.Value
    
    Dim LkUpVal_Bytes() As Byte
    LkUpVal_Bytes = ToBytes(lookup_value)
    
    Dim Val As Variant
    For Each Val In tbl_values
        If Val = "" Then GoTo nextVal
        
        Dim Val_Bytes() As Byte
        Val_Bytes = ToBytes(CStr(Val))
        
        Dim MatchCount As Integer
        MatchCount = CountMatchingElements(LkUpVal_Bytes, Val_Bytes)
        
        If MatchCount > HighestMatchCount Then
            HighestMatchCount = MatchCount
            ClosestMatch = Val
        End If
nextVal:
    Next
    SearchChars3 = ClosestMatch
End Function

Function ToBytes(InputStr As String) As Byte()
    Dim ByteArr() As Byte
    ReDim ByteArr(Len(InputStr) - 1)
    Dim i As Long
    For i = 0 To Len(InputStr) - 1
        ByteArr(i) = AscW(Mid(InputStr, i + 1, 1))
    Next
    ToBytes = ByteArr
End Function

Function CountMatchingElements(Arr1 As Variant, Arr2 As Variant) As Integer
    'As elements from Arr1 are found in Arr2, those elements are removed from Arr2, to prevent re-matching with the same elements
    'To enable this feature, Arr2 is turned into a Collection
    Dim Col2 As New Collection
    Dim v As Variant
    For Each v In Arr2
        Col2.Add v
    Next
    
    Dim MatchCount As Integer, i As Long
    For Each v In Arr1
        For i = 1 To Col2.Count
            If Col2.Item(i) = v Then
                MatchCount = MatchCount + 1
                Col2.Remove (i)
                Exit For
            End If
        Next
    Next
    CountMatchingElements = MatchCount
End Function

进一步的优化可能是有第二个版本的ToBytes 函数,它直接将值输出到Collection。然后,您可以将CountMatchingElements 更改为接受集合,并且不需要将第二个数组转换为集合。

我将把它留作你试验的想法。

【讨论】:

  • 非常感谢@Toddleson,我会试试你的代码!
【解决方案3】:

如前所述,通过将范围分配给数组来最小化与工作表的交互将在结构上使您的宏更快。 未经测试,但您的代码中的这些细微更改应该可以帮助您走上正轨:

    Option Explicit
    'Name function and arguments
    Function SearchChars2(lookup_value As String, tbl_array As Range) As String
    'Declare variables and types
    Dim i As Integer, str As String, Value As String
    Dim a As Integer, b As Integer, cell As Variant
    'Iterste through each cell => replace with array
    'adapt to correct sheet
    Dim arr
    arr = tbl_array
    
    For Each cell In arr 'tbl_array
        'Save cell value to variable
        str = cell
        'Iterate through characters
        For i = 1 To Len(lookup_value)
          'Same character?
          If InStr(cell, Mid(lookup_value, i, 1)) > 0 Then
            'Add 1 to number in array
            a = a + 1
            'Remove evaluated character from cell and contine with remaning characters
            cell = Mid(cell, 1, InStr(cell, Mid(lookup_value, i, 1)) - 1) & Mid(cell, InStr(cell, Mid(lookup_value, i, 1)) + 1, 9999)
          End If
        'Next character
        Next i
     
        a = a - Len(cell)
        'Save value if there are more matching characters than before
        If a > b Then
          b = a
          Value = str
        End If
         
        a = 0
        Next cell
    'Return value with the most matching characters
    SearchChars2 = Value
    End Function

【讨论】:

  • 嗨@ceci,我将您的代码复制并粘贴到我的 VBA 中。我目前正在尝试,它已经处理了很长一段时间了。
  • 我还是不喜欢 Instr(cell,... -> cell = 循环。这似乎是计算匹配字符效率最低的方法之一。
  • 嗨@ceci,如果我按增量来做会快一点。
  • 好的,只是改变了数组部分,你能举一个源数据的例子吗?
  • 这里也是新的,我应该把其中一个excel文件放在这里吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-11
  • 2012-12-04
  • 2012-12-03
  • 2014-07-21
相关资源
最近更新 更多