arrays - VBA 中的最长公共(public)子序列给出#VALUE!错误

标签 arrays excel vba debugging lcs

我一直在 Excel (365) 中组合一个 UDF 来计算两个字符串之间的最长公共(public)子序列(基于 python https://www.geeksforgeeks.org/printing-longest-common-subsequence/ 中的此实现)。

当我运行 UDF 时,我得到一个#Value!工作表上的错误。我已经完成了一些基本的调试,但我是 VBA 新手,遇到了困难。代码中的消息框语句只是为了进行粗略的调试。

我相信问题出在我对 L 数组的操作上。它似乎到达第一组 for 循环中的第一种情况,然后在评估 L(i, j,) = 0 时退出。有任何关于我哪里出错的指示吗?

在工作表中,我使用 =ClosestMatch("aabbaaaa", "aaaabbaa") 并得到 #VALUE! 结果。

这是我正在尝试的 UDF 的 VBA 代码:

Function ClosestMatch(ByVal x As String, ByVal y As String, Optional ByVal return_String As Boolean = False) As Variant
    Dim xLen As Integer
    Dim yLen As Integer
    
    xLen = Len(x)
    yLen = Len(y)
    
    MsgBox "x = " & x & " y = " & y
    
    'Create Zeroed Array of xLen+1 x yLen+1 dimensions (intentional extra space).
    ReDim L((xLen + 1), (yLen + 1)) 'indexing starts at 0.
    For i = 0 To (xLen + 1)
        For j = 0 To (yLen + 1)
            L(i, j) = 0
        Next j
    Next i
    
    MsgBox "Created 0'ed array L"
    
    'Build dynamic programming table from the bottom up.
    'Note that L[xLen][yLen] will contain an integer equal to the length
    'of the complete LCS.
    'Note that L[i][j] contains the length of the lcs of x[0..i] and y[0..j]
    For i = 0 To (xLen + 1)
        For j = 0 To (yLen + 1)
            If i = 0 Or j = 0 Then
                L(i, j) = 0
            ElseIf Mid(x, i - 1, 1) = Mid(x, i - 1, 1) Then
                L(i, j) = L(i - 1, j - 1) + 1
            Else
                L(i, j) = WorksheetFunction.Max(L(i - 1, j), L(i, j - 1))
            End If
        Next j
    Next i
    
    'Length of LCS
    Dim LCSlen As Integer
    LCSlen = L(xLen, yLen)
    
    MsgBox "Length of the LCS is " & LCSlen
    
    'Start from the right-most-bottom-most corner and store chars
    'one by on in LCS
    Dim LCS As String
    
    LCS = ""
    i = xLen
    j = yLen
    
    While i > 0 And j > 0
            'If current character in x and y are same, then current char
            'is part of the LCS. The L[xLen][yLen] is the location of the
            'fist charachter we will PUSH onto the front of the LCS string
            If Mid(x, i - 1, 1) = Mid(x, i - 1, 1) Then
                LCS = Mid(x, i - 1, 1) & Right(LCS, Len(LCS))
            
            'If not same, then find the larger of the two lengths in L[][]
            'then go in the direction of the larger value
            ElseIf L(i - 1, j) > L(i, j - 1) Then
                i = i + 1
            Else
                j = j + 1
            End If
    Wend
    
    If return_String Then
        ClosestMatch = LCS
    Else
        ClosestMatch = LCSlen
    End If
    
End Function

最佳答案

Excel“吞掉”用户定义的函数错误并将其包装到 Variant/Error 值中,这样任何引发 VBA 运行时错误的函数都会返回 #VALUE! 调用工作表时出错。

诀窍是删除包装器并直接自己调用该函数。

在 VBIDE 中,按 Ctrl+G 调出立即工具窗口,然后键入 ?通过函数名称及其参数:

?ClosestMatch("aabbaaaa", "aaaabbaa")

?PRINT 的简写,因此如果一切顺利,该函数将返回一个在正下方打印的值:

?ClosestMatch("aabbaaaa", "aaaabbaa")
aa

但是,如果出现任何问题并且函数引发错误,您将收到 VBA 运行时错误提示,并将直接转至工作表中导致 #VALUE! 错误的指令正在看到,通过使用您可以使用的调试器工具,您将能够:

  • 将鼠标悬停在任意变量上即可查看其值
  • 调出locals工具窗口以查看所有变量及其值
  • 当前语句(黄色箭头)设置为函数中的任何其他语句
  • 单步执行 (F8) 代码并一次执行一条语句
  • 放置和删除断点 (F9) 以在特定语句处停止执行
  • 恢复执行 (F5),即使在动态修改代码后也是如此

考虑使用Debug.Print语句而不是MsgBox,打印到立即工具窗口,而不是弹出破坏性消息框。

然后考虑编写几个测试方法,使用各种参数组合调用您的函数,并对结果进行断言:如果函数返回预期输出,则测试通过,否则测试失败。当所有测试通过后,您就知道您的函数将按预期在所有涵盖的情况下工作。 Rubberduck (我几年前开始的一个免费开源 VBIDE 插件项目)为您提供了轻松编写和运行此类单元测试的工具,其静态代码分析可以帮助您避免许多陷阱,初学者陷阱和过时的代码结构(例如,While...Wend 可以编写为更标准的 Do While...Loop 结构)。

关于arrays - VBA 中的最长公共(public)子序列给出#VALUE!错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70241004/

相关文章:

arrays - 如何从元胞数组调用函数句柄?

python - 制作列表子集总和的列表

excel - 更新用户表单标签 Worksheet_Calculate 和每次打开用户表单

performance - 在单精度和 double 之间轻松切换

vba - 使用 Excel 2016 将范围保存为图片

c++ - Malloc vs New for Primitives

java - 如何测试 Java String 中使用的支持数组?

excel - excel文件中的警告信息

email - 跟进使用VBA发送电子邮件

vba - 在 VBA (Visio) 中选择分组形状