学习Excel技术,关注微信公众号:
excelperfect
本文整理自https://fastexcel.wordpress.com/,有兴趣的朋友可以研究一下。
目的
在Excel中,经常会碰到比较两个列表的问题,以查看列表中不同的项目。
实现
下面的VBA用户自定义函数(UDF)——IsInList2调用了6个方法:
1.对LookIn列表进行排序并使用二分搜索来比较LookFor列表中的项目
2.在LookIn列表中使用线性搜索LookFor列表中的每个项目
3.创建一个包含LookIn列表的集合,并检查其每个项目是否在LookFor列表中
4.创建一个包含LookIn列表的字典,并检查其每个项目是否在LookFor列表中
5.使用已排序的LookIn列表和二分搜索
6.使用InStr查找部分匹配
IsInList2函数是返回True/False数组的数组函数。它被设计作为多单元格数组函数,在LookFor列表旁边的列中输入,可以查找在LookFor列表中存在而在LookIn列表中不存在的所有项目。
为简单起见,该函数假设两个列表都是至少包含2个项目的区域,因此,第一个任务是从区域中获取值到变体数组。然后,创建的输出数组为调用单元格和LookFor列表的较小者。接着,如果完全匹配,则数据被排序,添加到集合或字典。随后,该函数使用适当的过程方法遍历LookFor列表,并将结果存储到输出数组中。
代码
QuickSort过程
下面是QuickSort过程代码:
Sub QSortVar(InputValues As Variant, jStart As Long, jEnd As Long)
Dim jStart2 As Long
Dim jEnd2 As Long
Dim v1 As Variant
Dim v2 As Variant
jStart2 = jStart
jEnd2 = jEnd
v1 = InputValues((jStart + (jEnd - jStart)* Rnd()), 1)
While jStart2 < jEnd2
While InputValues(jStart2, 1) < v1And jStart2 < jEnd
jStart2 = jStart2 + 1
Wend
While InputValues(jEnd2, 1) > v1 AndjEnd2 > jStart
jEnd2 = jEnd2 - 1
Wend
If jStart2 < jEnd2 Then
v2 = InputValues(jStart2, 1)
InputValues(jStart2, 1) =InputValues(jEnd2, 1)
InputValues(jEnd2, 1) = v2
End If
If jStart2 <= jEnd2 Then
jStart2 = jStart2 + 1
jEnd2 = jEnd2 - 1
End If
Wend
If jEnd2 > jStart Then QSortVarInputValues, jStart, jEnd2
If jStart2 < jEnd Then QSortVarInputValues, jStart2, jEnd
End Sub
二分搜索函数
代码如下:
Function BSearchMatch(LookupValue As Variant, LookupArray As Variant) As Boolean
Dim low As Long
Dim high As Long
Dim middle As Long
Dim varMiddle As Variant
Dim jRow As Long
jRow = 1
BSearchMatch = False
low = 0
high = UBound(LookupArray)
Do While high - low > 1
middle = (low + high) \ 2
varMiddle = LookupArray(middle, 1)
If varMiddle >= LookupValue Then
high = middle
Else
low = middle
End If
Loop
If (low > 0 And high <=UBound(LookupArray)) Then
If LookupArray(high, 1) >LookupValue Then
jRow = low
Else
jRow = high
End If
End If
If LookupValue = LookupArray(jRow, 1) Then BSearchMatch = True
End Function
精确匹配的线性搜索函数
代码如下:
Function LMatchExactV(LookupValue As Variant, LookupArray As Variant) As Boolean
'使用线性搜索查找是否LookupArray中存在LookupValue
'LookupArray必须是N行和1列的二维变体数组
Dim j As Long
LMatchExactV = False
For j = 1 To UBound(LookupArray)
If LookupValue = LookupArray(j, 1) Then
LMatchExactV = True
Exit For
End If
Next j
End Function
部分匹配的线性搜索函数
代码如下:
Function LMatchInV(LookupValue As Variant, LookupArray As Variant) As Boolean
'使用线性搜索和Instr查找是否LookupValue在LookupArray中的任意值里
'LookupArray必须是N行和1列的二维变体数组
Dim j As Long
Dim strLook As String
LMatchInV = False
strLook = CStr(LookupValue)
For j = 1 To UBound(LookupArray)
If InStr(LookupArray(j, 1), strLook)> 0 Then
LMatchInV = True
Exit For
End If
Next j
End Function
IsInList2数组函数
由于该函数使用了Dictionary对象,因此需要先添加对Microsoft Scripting Runtime库的引用。
该函数有2个可选参数,用来控制使用的方法:
1.jSorted:使用哪个排序/查找方法
2.FindExact:指定为True则进行精确匹配,False为部分匹配
Public Function IsInList2(LookFor As Variant, _
LookIn As Variant, _
Optional jSorted As Long = 0, _
Optional FindExact As Boolean = True)
'jSorted
'=0 数据未排序,但进行排序
'=1 数据已排序 - 使用二分搜索
'=-1 使用线性搜索
'=2 使用集合
'=3 使用字典
Dim nLookFor As Long
Dim nLookIn As Long
Dim nOut As Long
Dim vOut() As Variant
Dim j As Long
Dim coll As New Collection
Dim dict As New dictionary
'强制将区域转换为值
LookFor = LookFor.Value2
LookIn = LookIn.Value2
'获取行数
nLookFor = UBound(LookFor)
nLookIn = UBound(LookIn)
nOut = Application.Caller.Rows.Count
If nLookFor < nOut Then nOut = nLookFor
ReDim vOut(nOut, 1) '创建输出数组
If FindExact Then
If jSorted = 0 Then
'快速排序
QSortVar LookIn, LBound(LookIn),UBound(LookIn)
jSorted = 1
ElseIf jSorted = 2 Then
On Error Resume Next
For j = 1 To nLookIn
'集合
coll.Add LookIn(j, 1), CStr(LookIn(j,1))
Next j
ElseIf jSorted = 3 Then
On Error Resume Next
For j = 1 To nLookIn
'字典
dict.Add LookIn(j, 1),LookIn(j, 1)
Next j
End If
On Error GoTo 0
End If
For j = 1 To nOut
If Not FindExact Then
'instr线性搜索
vOut(j, 1) = LMatchInV(LookFor(j,1), LookIn)
ElseIf jSorted = 1 Then
vOut(j, 1) =BSearchMatch(LookFor(j, 1), LookIn) ' 二分搜索
ElseIf jSorted = 2 Then
'使用集合
On Error Resume Next
Err.Clear
vOut(j, 1) =coll.Item(CStr(LookFor(j, 1)))
If CLng(Err.Number) = 5 Then
vOut(j, 1) = False
Else
vOut(j, 1) = True
End If
ElseIf jSorted = 3 Then
'字典
vOut(j, 1) = dict.Exists(LookFor(j,1))
ElseIf jSorted = -1 Then
'精确匹配
vOut(j, 1) = LMatchExactV(LookFor(j,1), LookIn)
Else
vOut(j, 1) = CVErr(xlErrValue)
End If
Next j
IsInList2 = vOut
End Function