前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >VBA实战技巧04: 一个用于两个列表区域比较的自定义函数

VBA实战技巧04: 一个用于两个列表区域比较的自定义函数

作者头像
fanjy
发布2020-04-14 15:02:57
1.2K0
发布2020-04-14 15:02:57
举报
文章被收录于专栏:完美Excel

学习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过程代码:

代码语言:javascript
复制
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

二分搜索函数

代码如下:

代码语言:javascript
复制
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

精确匹配的线性搜索函数

代码如下:

代码语言:javascript
复制
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

部分匹配的线性搜索函数

代码如下:

代码语言:javascript
复制
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为部分匹配

代码语言:javascript
复制
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
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 完美Excel 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档