VBA 中的最长公共子序列给出 #VALUE!错误
Longest Common Subsequence in VBA Giving #VALUE! Error
我一直在 Excel (365) 中组合一个 UDF 来计算两个字符串之间的最长公共子序列(基于 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
错误是因为 ElseIf Mid(x, i - 1, 1) = Mid(x, i - 1, 1) Then
行上的 i= 0
的值导致 Mid(x, i - 1, 1)
失败,因此函数崩溃。
我的推荐:
- 用户始终正确处理错误。
- 使用
Line Numbers
为您的代码编号并使用 ERL
获取有问题的行号。
这是一个例子
Option Explicit
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
Dim i As Long, j As Long, k As Long
10 On Error GoTo Whoa
20 xLen = Len(x)
30 yLen = Len(y)
40 MsgBox "x = " & x & " y = " & y
'Create Zeroed Array of xLen+1 x yLen+1 dimensions (intentional extra space).
50 ReDim L((xLen + 1), (yLen + 1)) 'indexing starts at 0.
60 For i = 0 To (xLen + 1)
70 For j = 0 To (yLen + 1)
80 L(i, j) = 0
90 Next j
100 Next i
110 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]
120 For i = 0 To (xLen + 1)
130 For j = 0 To (yLen + 1)
140 If i = 0 Or j = 0 Then
150 L(i, j) = 0
160 ElseIf Mid(x, i - 1, 1) = Mid(x, i - 1, 1) Then
170 L(i, j) = L(i - 1, j - 1) + 1
180 Else
190 L(i, j) = WorksheetFunction.Max(L(i - 1, j), L(i, j - 1))
200 End If
210 Next j
220 Next i
'Length of LCS
Dim LCSlen As Integer
230 LCSlen = L(xLen, yLen)
240 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
250 LCS = ""
260 i = xLen
270 j = yLen
280 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
290 If Mid(x, i - 1, 1) = Mid(x, i - 1, 1) Then
300 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
310 ElseIf L(i - 1, j) > L(i, j - 1) Then
320 i = i + 1
330 Else
340 j = j + 1
350 End If
360 Wend
370 If return_String Then
380 ClosestMatch = LCS
390 Else
400 ClosestMatch = LCSlen
410 End If
LetsContinue:
420 Exit Function
Whoa:
430 ClosestMatch = Err.Description & " on line " & Erl
End Function
正在行动:
其他提示:
- 使用Option Explicit.
- 我用 MZ Tools 代替 Excel。如果你是一个认真的程序员,我绝对推荐使用它。
Excel “吞下”用户定义的函数错误并将它们包装到 Variant/Error
值中,这样任何抛出 VBA 运行 时间错误的函数return 调用工作表会出现 #VALUE!
错误。
诀窍是删除包装器并自己直接调用函数。
在VBIDE中,按Ctrl+G调出立即工具窗口,然后键入 ? 后跟函数名称及其参数:
?ClosestMatch("aabbaaaa", "aaaabbaa")
? 是 PRINT
的 shorthand 所以如果一切顺利的话,函数 returns 是一个打印在下面的值:
?ClosestMatch("aabbaaaa", "aaaabbaa")
aa
但是如果出现任何问题并且函数抛出错误,您将得到 VBA 运行 次错误提示,并将直接带到负责 [=13] 的指令=] 工作表出现错误,通过使用您可以随意使用的调试器工具,您将能够:
- 将鼠标悬停在任何变量上以查看其值
- 调出 locals 工具窗口以查看所有变量及其值
- 将当前语句(黄色箭头)设置为函数中的任何其他语句
- 逐步执行 (F8) 代码并一次执行一条语句
- 放置和删除断点 (F9) 以在特定语句处停止执行
- 恢复执行 (F5),即使在即时修改代码后
考虑使用 Debug.Print
语句而不是 MsgBox
,打印到 即时 工具窗口,而不是弹出一个破坏性的消息框。
然后考虑编写几个测试方法,用各种参数组合调用你的函数,并对结果断言:如果函数returns 预期输出,测试通过,否则测试失败。当所有测试都通过时,您就知道您的函数将按预期适用于所有涵盖的案例。 Rubberduck(我几年前开始的一个免费开源 VBIDE 插件项目)为您提供了轻松编写和 运行 此类单元测试的工具,及其 静态代码分析 可以帮助您避免许多陷阱、初学者陷阱和过时的代码结构(例如 While...Wend
可以写成更标准的 Do While...Loop
结构)。
在盯着手表看太多时间之后 window...我有很多错误,我在应该是 y 的地方复制 x,然后在应该是 j 的地方输入 i .
因为我没能找到 VBA 寻找最长公共子序列的例子,这里是...
Public Function LCSMatch(ByVal x As Range, ByVal y As Range, Optional ByVal return_String As Boolean = False) As Variant
Dim xLen As Integer
Dim yLen As Integer
xLen = Len(x)
yLen = Len(y)
'Create Zeroed Array of xLen+1 x yLen+1 dimensions (intentional extra space).
ReDim L((xLen), (yLen)) 'indexing starts at 0.
For i = 0 To (xLen)
For j = 0 To (yLen)
L(i, j) = 0
Next j
Next i
'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 j = 0 To (yLen)
For i = 0 To (xLen)
If i = 0 Or j = 0 Then
L(i, j) = 0
ElseIf Mid$(x, i, 1) = Mid$(y, j, 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 i
Next j
'Length of LCS
Dim LCSlen As Integer
LCSlen = L(xLen, yLen)
'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) = Mid$(y, j, 1) Then
LCSPart = Right$(LCS, Len(LCS))
LCS = Mid$(x, i, 1) & LCSPart
i = i - 1
j = j - 1
'GoTo Match
'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
'Match:
Wend
MsgBox "Length of the LCS is " & LCSlen
MsgBox "LCS is " & LCS
If return_String Then
LCSMatch = LCS
Else
LCSMatch = LCSlen
End If
End Function
我一直在 Excel (365) 中组合一个 UDF 来计算两个字符串之间的最长公共子序列(基于 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
错误是因为 ElseIf Mid(x, i - 1, 1) = Mid(x, i - 1, 1) Then
行上的 i= 0
的值导致 Mid(x, i - 1, 1)
失败,因此函数崩溃。
我的推荐:
- 用户始终正确处理错误。
- 使用
Line Numbers
为您的代码编号并使用ERL
获取有问题的行号。
这是一个例子
Option Explicit
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
Dim i As Long, j As Long, k As Long
10 On Error GoTo Whoa
20 xLen = Len(x)
30 yLen = Len(y)
40 MsgBox "x = " & x & " y = " & y
'Create Zeroed Array of xLen+1 x yLen+1 dimensions (intentional extra space).
50 ReDim L((xLen + 1), (yLen + 1)) 'indexing starts at 0.
60 For i = 0 To (xLen + 1)
70 For j = 0 To (yLen + 1)
80 L(i, j) = 0
90 Next j
100 Next i
110 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]
120 For i = 0 To (xLen + 1)
130 For j = 0 To (yLen + 1)
140 If i = 0 Or j = 0 Then
150 L(i, j) = 0
160 ElseIf Mid(x, i - 1, 1) = Mid(x, i - 1, 1) Then
170 L(i, j) = L(i - 1, j - 1) + 1
180 Else
190 L(i, j) = WorksheetFunction.Max(L(i - 1, j), L(i, j - 1))
200 End If
210 Next j
220 Next i
'Length of LCS
Dim LCSlen As Integer
230 LCSlen = L(xLen, yLen)
240 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
250 LCS = ""
260 i = xLen
270 j = yLen
280 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
290 If Mid(x, i - 1, 1) = Mid(x, i - 1, 1) Then
300 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
310 ElseIf L(i - 1, j) > L(i, j - 1) Then
320 i = i + 1
330 Else
340 j = j + 1
350 End If
360 Wend
370 If return_String Then
380 ClosestMatch = LCS
390 Else
400 ClosestMatch = LCSlen
410 End If
LetsContinue:
420 Exit Function
Whoa:
430 ClosestMatch = Err.Description & " on line " & Erl
End Function
正在行动:
其他提示:
- 使用Option Explicit.
- 我用 MZ Tools 代替 Excel。如果你是一个认真的程序员,我绝对推荐使用它。
Excel “吞下”用户定义的函数错误并将它们包装到 Variant/Error
值中,这样任何抛出 VBA 运行 时间错误的函数return 调用工作表会出现 #VALUE!
错误。
诀窍是删除包装器并自己直接调用函数。
在VBIDE中,按Ctrl+G调出立即工具窗口,然后键入 ? 后跟函数名称及其参数:
?ClosestMatch("aabbaaaa", "aaaabbaa")
? 是 PRINT
的 shorthand 所以如果一切顺利的话,函数 returns 是一个打印在下面的值:
?ClosestMatch("aabbaaaa", "aaaabbaa")
aa
但是如果出现任何问题并且函数抛出错误,您将得到 VBA 运行 次错误提示,并将直接带到负责 [=13] 的指令=] 工作表出现错误,通过使用您可以随意使用的调试器工具,您将能够:
- 将鼠标悬停在任何变量上以查看其值
- 调出 locals 工具窗口以查看所有变量及其值
- 将当前语句(黄色箭头)设置为函数中的任何其他语句
- 逐步执行 (F8) 代码并一次执行一条语句
- 放置和删除断点 (F9) 以在特定语句处停止执行
- 恢复执行 (F5),即使在即时修改代码后
考虑使用 Debug.Print
语句而不是 MsgBox
,打印到 即时 工具窗口,而不是弹出一个破坏性的消息框。
然后考虑编写几个测试方法,用各种参数组合调用你的函数,并对结果断言:如果函数returns 预期输出,测试通过,否则测试失败。当所有测试都通过时,您就知道您的函数将按预期适用于所有涵盖的案例。 Rubberduck(我几年前开始的一个免费开源 VBIDE 插件项目)为您提供了轻松编写和 运行 此类单元测试的工具,及其 静态代码分析 可以帮助您避免许多陷阱、初学者陷阱和过时的代码结构(例如 While...Wend
可以写成更标准的 Do While...Loop
结构)。
在盯着手表看太多时间之后 window...我有很多错误,我在应该是 y 的地方复制 x,然后在应该是 j 的地方输入 i .
因为我没能找到 VBA 寻找最长公共子序列的例子,这里是...
Public Function LCSMatch(ByVal x As Range, ByVal y As Range, Optional ByVal return_String As Boolean = False) As Variant
Dim xLen As Integer
Dim yLen As Integer
xLen = Len(x)
yLen = Len(y)
'Create Zeroed Array of xLen+1 x yLen+1 dimensions (intentional extra space).
ReDim L((xLen), (yLen)) 'indexing starts at 0.
For i = 0 To (xLen)
For j = 0 To (yLen)
L(i, j) = 0
Next j
Next i
'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 j = 0 To (yLen)
For i = 0 To (xLen)
If i = 0 Or j = 0 Then
L(i, j) = 0
ElseIf Mid$(x, i, 1) = Mid$(y, j, 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 i
Next j
'Length of LCS
Dim LCSlen As Integer
LCSlen = L(xLen, yLen)
'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) = Mid$(y, j, 1) Then
LCSPart = Right$(LCS, Len(LCS))
LCS = Mid$(x, i, 1) & LCSPart
i = i - 1
j = j - 1
'GoTo Match
'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
'Match:
Wend
MsgBox "Length of the LCS is " & LCSlen
MsgBox "LCS is " & LCS
If return_String Then
LCSMatch = LCS
Else
LCSMatch = LCSlen
End If
End Function