【问题标题】:better performance in an Excel UDF with IF or Select Case使用 IF 或 Select Case 在 Excel UDF 中获得更好的性能
【发布时间】:2017-08-29 11:45:39
【问题描述】:

我经常需要在 Excel 中使用公式搜索单元格中的某些特殊文本。我需要搜索的行数是 100.000 到 500.000,在极少数情况下高达 1.000.000。为了避免长公式,我编写了自己的 UDF 来搜索单元格中的多个文本字符串。新公式很容易处理。我尽我所能优化这个公式的运行时间。 500.000 行需要 11 到 12 秒。

我以两种方式制作这个公式:一种使用 IF 语句 (SuchenSIF),另一种 (SuchenSSELCASE) 使用 SELECT CASE 语句。布斯公式具有相同的速度。你能给我一些提示如何获得更好的性能吗?

这个公式的语法是:
SuchenSIF(要搜索的单元格,要搜索的文本 1,要搜索的文本 6)
SuchenSSELCASE(cell to search, text to search 1, ... text to search 6)

Public Function SuchenSIF(Zelle As Range, such1 As String, Optional such2 As String, Optional such3 As String, Optional such4 As String, Optional such5 As String, Optional such6 As String) As Integer
Application.Volatile

' this code, based on IF-statements need 11-12 seconds for 500.000 rows
' Start of IF-Section
'
ZelleWert = Zelle.Value
SuchenS = InStr(1, ZelleWert, such1, vbTextCompare)
If SuchenS > 0 Then Exit Function
SuchenS = InStr(1, ZelleWert, such2, vbTextCompare)
If SuchenS <> vbFalse Then Exit Function
If Len(such3) > 0 Then
    SuchenS = InStr(1, ZelleWert, such3, vbTextCompare)
    If SuchenS > 0 Then Exit Function
    If Len(such4) > 0 Then
        SuchenS = InStr(1, ZelleWert, such4, vbTextCompare)
        If SuchenS > 0 Then Exit Function
        If Len(such5) > 0 Then
            SuchenS = InStr(1, ZelleWert, such5, vbTextCompare)
            If SuchenS > 0 Then Exit Function
            If Len(such6) > 0 Then
                SuchenS = InStr(1, ZelleWert, such6, vbTextCompare)
                If SuchenS > 0 Then Exit Function
            End If
        End If
    End If
End If
'
' End of IF-Section
If SuchenS = 0 Then SuchenS = False
End Function

Public Function SuchenSSELCASE(Zelle As Range, such1 As String, Optional such2 As String, Optional such3 As String, Optional such4 As String, Optional such5 As String, Optional such6 As String) As Integer
Application.Volatile
' this code, based on SELECT-CASE-statements need 11-12 seconds for 500.000 rows
' Start of SELECT-CASE -Section
'
ZelleWert = Zelle.Value
SuchenS = InStr(1, ZelleWert, such1, vbTextCompare) * Len(such1)
Select Case SuchenS
    Case 0
        SuchenS = InStr(1, ZelleWert, such2, vbTextCompare) * Len(such2)
        Select Case SuchenS
            Case 0
                SuchenS = InStr(1, ZelleWert, such3, vbTextCompare) * Len (such3)
                Select Case SuchenS
                    Case 0
                        SuchenS = InStr(1, ZelleWert, such4, vbTextCompare) * Len(such4)
                        Select Case SuchenS
                            Case 0
                                SuchenS = InStr(1, ZelleWert, such5, vbTextCompare) * Len(such5)
                                Select Case SuchenS
                                    Case 0
                                        SuchenS = InStr(1, ZelleWert, such6, vbTextCompare) * Len(such6)
                                        Select Case SuchenS
                                            Case 0
                                            Case Else
                                                SuchenS = SuchenS / Len(such6)
                                                Exit Function
                                        End Select
                                    Case Else
                                        SuchenS = SuchenS / Len(such5)
                                        Exit Function
                                End Select
                            Case Else
                                SuchenS = SuchenS / Len(such4)
                                Exit Function
                        End Select
                    Case Else
                        SuchenS = SuchenS / Len(such3)
                        Exit Function
                End Select
            Case Else
                SuchenS = SuchenS / Len(such2)
                Exit Function
        End Select
    Case Else
        SuchenS = SuchenS / Len(such1)
        Exit Function
End Select
'
' End of SELECT-CASE -Section
If SuchenS = 0 Then SuchenS = False
End Function

【问题讨论】:

  • 你是从 VBS Sub 调用这个 Function 吗?还是来自工作表中的单元格?
  • 我从工作表中的单元格调用它
  • @Enrico,如果这是有效的代码,您应该将此问题提交给代码审查 - 他们在那里很棒
  • 为什么要让函数变得易失?它似乎不需要它。 vbTextCompare 比将值转换为相同大小写要慢。
  • 我建议改用 Excel 公式。 VBA仅限一个线程,但Excel公式可以并行计算msdn.microsoft.com/en-us/library/bb687899.aspx

标签: vba excel excel-formula user-defined-functions


【解决方案1】:

您可以通过在所有 instr 调用之前将单元格值转换为字符串一次来提高速度,而不是在每次调用时强制将变体转换为字符串。

Dim ZelleWert as string
ZelleWert=Cstr(Zelle.Value2)

如果您对 UDF 有大量调用,则需要避免 VBE 刷新错误:请参阅 https://fastexcel.wordpress.com/2011/06/13/writing-efficient-vba-udfs-part-3-avoiding-the-vbe-refresh-bug/

如果您将 UDF 转换为处理单元格范围并返回结果数组,您可能会制作更快的 UDF:请参阅 https://fastexcel.wordpress.com/2011/06/20/writing-efiicient-vba-udfs-part5-udf-array-formulas-go-faster/

【讨论】:

  • 谢谢查尔斯,特别是这些链接非常有用和有趣。
【解决方案2】:

您可以创建一个仅包含已传递给函数的参数的数组,并通过该数组循环以获得一点速度增益(...我认为)

Public Function SuchenSIF(Zelle As Range, such1 As String, Optional such2 As String, Optional such3 As String, Optional such4 As String, Optional such5 As String, Optional such6 As String) As Integer
    Application.Volatile

    Dim possibleInputs As Variant, v As Variant, inputs As Variant
    Dim i As Integer
    Dim ZelleWert As String

    possibleInputs = Array(such2, such3, such4, such5, such6)

    'create an array of non-empty parameters
    ReDim inputs(0 To 0)
    inputs(0) = such1
    For i = 0 To 4
        If possibleInputs(i) <> vbNullString Then
            ReDim Preserve inputs(0 To UBound(inputs) + 1)
            inputs(UBound(inputs)) = possibleInputs(i)
        End If
    Next i

    ZelleWert = CStr(Zelle.Value)

    'loop through given parameters and exit if found
    For Each v In inputs
        SuchenS = InStr(1, ZelleWert, v, vbTextCompare)
        If SuchenS > 0 Then
            Exit Function
        End If
    Next v
End Function

【讨论】:

  • 您可以使用Application.Match 来消除循环,而不是循环inputs 并消除循环
  • @ShaiRado 不确定我是否理解正确,但如果 OP 正在从 a string like this 中寻找 string,那么匹配将不起作用。你的解决方案需要通配符,但我可能弄错了:/
  • 在下面查看我的 EDITED 答案,使用Application.Match 而不是循环
  • @ShaiRado,我还是不明白。如果 Zella.Value = "abcde" 并且数组包含 {"b","z"} 匹配不会返回任何内容?但 InStr 会。
  • 你是对的,我没有想到 PO 想要在单元格中查找部分文本的可能性。如果他正在寻找完全匹配,它会工作;)
【解决方案3】:

您尚未提供任何数据,您如何使用此Function 以及您想要实现的目标。或许我们可以用更短、更快的方式替换您的整个 Function 概念。

编辑 1:删除了之前的概念,并决定将此版本与 Application.Match 一起使用。

Public Function SuchenSIF(Zelle As Range, such1 As String, Optional such2 As String, Optional such3 As String, Optional such4 As String, Optional such5 As String, Optional such6 As String) As Integer

    Dim suchArr() As String, Elem As Variant

    ReDim suchArr(0 To 5)

    ' create suchArr with only Such arguements that are none-blank
    For Each Elem In Array(such1, such2, such3, such4, such5, such6)
        If Elem <> vbNullString Then
            suchArr(i) = Elem
            i = i + 1
        End If
    Next Elem

    ReDim Preserve suchArr(0 To i - 1) ' resize to actual populated array size

    ' use Match to get the index of the array that is matched
    SuchenSIF = Application.Match(Zelle.Value, suchArr, 0) - 1

    If IsError(SuchenSIF) Then SuchenSIF = -10000  ' Just to Raise some kind of error "NOT found!"

End Function

【讨论】:

  • 我认为代码会运行得更慢,因为它多次调用 Range.Value 并且每次调用都很昂贵。
  • @CharlesWilliams 谢谢,已更新。我写得太快了(我的大脑没有跟上):)
  • @ShaiRado,你在哪里将值返回给函数?
  • @CallumDA 我只是添加了可以替换的部分以缩短运行时间,而不是替换 PO 整个代码
  • @ShaiRado,我的意思是你不能互相替换这两段代码,因为 OP 每次都明确设置函数 SuchenS 的值,所以Exit Function 将返回该值,而您的 Exit Functions 将导致 0 每次都返回
猜你喜欢
  • 1970-01-01
  • 2011-11-29
  • 2021-02-05
  • 1970-01-01
  • 2012-06-02
  • 1970-01-01
  • 2010-09-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多