VBA实用小程序79:统计不同值或唯一值的VBA自定义函数

excelperfect

在文章开始之前,解释一下什么是不同值?什么是唯一值?

例如,下面的一组数据:

a,a,b,b,c,d,e,e,f

我们说,这组数据有6个不同值:a,b,c,d,e,f;有3个唯一值:c,d,f,因为它们在列表中只出现了1次。

我们要求这组数据中不同值的数量,可以使用数组公式:

=SUM(--(FREQUENCY(IF(A1:A9<>'',MATCH('~'& A1:A9,A1:A9&'',0)),ROW(A1:A9)-ROW(A1)+1)>0))

结果如下图1所示。

图1

然而,这个公式不仅复杂,而且在处理混合数据时会很慢。因此,我们可以使用VBA来编写自定义函数。

使用Collection对象来统计不同值

代码如下:

    Public Function COUNTDISTINCTcol(ByRef rngToCheck As Range) As VariantDim colDistinct As CollectionDim varValues As VariantDim varValue As VariantDim lngCount As LongDim lngRow As LongDim lngCol As LongOn Error GoTo ErrorHandlervarValues= rngToCheck.Value'如果rngToCheck多于1个单元格'那么varValues是一个二维数组If IsArray(varValues) ThenSet colDistinct = New CollectionFor lngRow = LBound(varValues, 1) To UBound(varValues, 1)For lngCol = LBound(varValues, 2) To UBound(varValues, 2)varValue = varValues(lngRow, lngCol)'忽略空单元格'如果单元格包含错误值则触发错误If LenB(varValue) > 0 Then'如果该项已存在则会触发错误'忽略该错误On Error Resume NextcolDistinct.Add vbNullString, CStr(varValue)On Error GoTo ErrorHandlerEnd IfNext lngColNext lngRowlngCount = colDistinct.CountElseIf LenB(varValues) > 0 ThenlngCount = 1End IfEnd IfCOUNTDISTINCTcol = lngCountExit FunctionErrorHandler:COUNTDISTINCTcol = CVErr(xlErrValue)End Function

    集合中的每个项目都必须具有唯一键,并且该唯一键必须是字符串。如果代码尝试创建重复键,则会引发错误。由于OnError Resume Next语句,该错误被忽略。VBACollection 对象的一个特性是键不区分大小写。

    LenB函数用于检查单元格是否为空白。如果单元格包含错误值,则此时将引发错误并且自定义函数将返回#VALUE!。空单元格意味着:

    • 单元格中什么也没有

    • 有一个零长字符串

    • 仅仅有一个前缀符号(通常是’)

    使用Dictionary对象来统计不同值

    在编写代码前,先添加对MicrosoftScripting Runtime库的引用。在VBE中,单击“工具——引用”,找到并勾选“MicrosoftScripting Runtime”,如下图2所示。

    图2

    代码如下:

      Public Function COUNTDISTINCTdicNew(ByRef rngToCheck As Range) As Variant   '早期绑定   '需要引用Microsoft Scripting Runtime库    Dim dicDistinct As Scripting.Dictionary    Dim varValues As Variant    Dim varValue As Variant    Dim lngCount As Long    Dim lngRow As Long    Dim lngCol As Long    Dim strValue As String       On Error GoTo ErrorHandler       varValues= rngToCheck.Value      '如果rngToCheck多于1个单元格   '那么varValues是一个二维数组    If IsArray(varValues) Then        Set dicDistinct = CreateObject('Scripting.Dictionary')        dicDistinct.CompareMode = TextCompare               For lngRow = LBound(varValues, 1) To UBound(varValues, 1)           For lngCol = LBound(varValues, 2) To UBound(varValues, 2)               varValue = varValues(lngRow, lngCol)                '忽略空单元格                '如果单元格包含错误值则触发错误               If LenB(varValue) > 0 Then                    '将所有内容转换为字符串                    '字典对类型不敏感                   strValue = CStr(varValue)                   If Not dicDistinct.Exists(strValue) Then                        dicDistinct.Add strValue, vbNullString                   End If               End If           Next lngCol        Next lngRow              lngCount = dicDistinct.Count    Else        If LenB(varValues) > 0 Then           lngCount = 1        End If    End If   COUNTDISTINCTdicNew = lngCount    Exit FunctionErrorHandler:   COUNTDISTINCTdicNew = CVErr(xlErrValue)End Function

      这段代码在第一次调用后保留现有Dictionary对象并随后清除。使用Static关键字代替Dim,以便在函数调用之间保留Dictionary对象引用:

        Public Function COUNTDISTINCTdicStatic(ByRef rngToCheck As Range) As VariantStatic dicDistinct As Scripting.DictionaryDim varValues As VariantDim varValue As VariantDim lngCount As LongDim lngRow As LongDim lngCol As LongDim strValue As StringOn Error GoTo ErrorHandlervarValues= rngToCheck.Value'如果rngToCheck多于1个单元格'那么varValues是一个二维数组If IsArray(varValues) ThenIf dicDistinct Is Nothing ThenSet dicDistinct = CreateObject('Scripting.Dictionary')dicDistinct.CompareMode = TextCompareElsedicDistinct.RemoveAllEnd IfFor lngRow = LBound(varValues, 1) To UBound(varValues, 1)For lngCol = LBound(varValues, 2) To UBound(varValues, 2)varValue = varValues(lngRow, lngCol)'忽略空单元格'如果单元格包含错误值则触发错误If LenB(varValue) > 0 Then'将所有内容转换为字符串'字典对类型不敏感strValue = CStr(varValue)If Not dicDistinct.Exists(strValue) ThendicDistinct.Add strValue, vbNullStringEnd IfEnd IfNext lngColNext lngRowlngCount = dicDistinct.CountElseIf LenB(varValues) > 0 ThenlngCount = 1End IfEnd IfCOUNTDISTINCTdicStatic = lngCountExit FunctionErrorHandler:COUNTDISTINCTdicStatic = CVErr(xlErrValue)End Function

        下面是上述方式统计不同值的结果,如下图3所示。

        图3

        正如所看到的,这4个公式都对数据类型不敏感,也对大小写不敏感。

        扩展的不同值统计——Dictionary对象

        代码如下:

          Public Function COUNTDISTINCT(ByRef rngToCheck AsRange, _    Optional ByVal blnCaseSensitive As Boolean = True) As Variant    Static dicDistinctAs Scripting.Dictionary    Dim varValues As Variant    Dim varValue As Variant    Dim lngCount As Long    Dim lngRow As Long    Dim lngCol As Long           On Error GoTo ErrorHandler       Set rngToCheck = Intersect(rngToCheck.Worksheet.UsedRange, rngToCheck)       If No trngToCheck Is Nothing Then        '将单元格值分配到内存中        '以便更快地使用它们       varValues = rngToCheck.Value               '如果rngToCheck多于1个单元格        '那么varValues是一个二维数组        If IsArray(varValues) Then            If dicDistinct Is Nothing Then               Set dicDistinct = CreateObject('Scripting.Dictionary')               dicDistinct.CompareMode = BinaryCompare           Else               dicDistinct.RemoveAll           End If                   For lngRow = LBound(varValues, 1) ToUBound(varValues, 1)               For lngCol = LBound(varValues, 2) To UBound(varValues, 2)                   varValue = varValues(lngRow, lngCol)                    '忽略错误值                   If Not IsError(varValue) Then                        '忽略空单元格                        '包括公式返回的''                        If LenB(varValue) >0 Then                            '如果是字符串                            '那么允许区分大小写                            If VarType(varValue) = vbString Then                                If Not blnCaseSensitive Then                                    varValue =UCase(varValue)                                End If                            End If                             If Not dicDistinct.Exists(varValue)Then                                dicDistinct.AddvarValue, vbNullString                            End If                        End If                   End If               Next lngCol           Next lngRow                   lngCount = dicDistinct.Count        Else            '如果单元格包含错误或为空则忽略           If Not IsError(varValues) Then               If LenB(varValues) > 0 Then                   lngCount = 1               End If           End If        End If    End If   COUNTDISTINCT = lngCount    Exit FunctionErrorHandler:   COUNTDISTINCT = CVErr(xlErrValue)End Function

          注意以下几点:

          • 可以统计数字、文本和逻辑数据类型,但会忽略错误值,例如#N/A和DIV/0!。

          • 忽略空(和空白)单元格。

          • 默认情况下区分大小写。

          • 区分数据类型。例如,这两个公式将被认为是不同的:=TRUE()和='True',就像 ='1' 和 =1。

          • 需要对 MicrosoftScripting Runtime 库的引用。如果不包含引用,则需要将Dictionary 对象声明为 Object类型,并将BinaryCompare设置为等于0的常量。

          • 可以处理跨多列的数据。

          示例如下图4所示。

          图4

          扩展的唯一值统计——Dictionary对象

          与上面的代码相似:

            Public Function COUNTUNIQUE(ByRef rngToCheck AsRange, _Optional ByVal blnCaseSensitive As Boolean = True) As VariantStatic dicDistinct As ObjectDim varValues As VariantDim varValue As VariantDim varItems As VariantDim lngCount As LongDim lngItem As LongDim lngRow As LongDim lngCol As LongOn Error GoTo ErrorHandlerSet rngToCheck = Intersect(rngToCheck.Worksheet.UsedRange, rngToCheck)If Not rngToCheck Is Nothing Then'将单元格值分配到内存中'以便更快地使用它们varValues = rngToCheck.Value'如果rngToCheck多于1个单元格'那么varValues是一个二维数组If IsArray(varValues) ThenIf dicDistinct Is Nothing ThenSet dicDistinct = CreateObject('Scripting.Dictionary')dicDistinct.CompareMode = BinaryCompareElsedicDistinct.RemoveAllEnd IfFor lngRow = LBound(varValues, 1) To UBound(varValues, 1)For lngCol = LBound(varValues, 2) To UBound(varValues, 2)varValue = varValues(lngRow, lngCol)'忽略错误值If Not IsError(varValue) Then'忽略空单元格'包括公式返回的''If LenB(varValue) >0 Then'如果是字符串'那么允许区分大小写If VarType(varValue) = vbString ThenIf NotblnCaseSensitive ThenvarValue =UCase(varValue)End IfEnd If'如果已存在则统计其出现了多少次If dicDistinct.Exists(varValue) ThendicDistinct.Item(varValue) = dicDistinct.Item(varValue) + 1Else'添加其出现1次dicDistinct.AddvarValue, 1End IfEnd IfEnd IfNext lngColNext lngRow'仅对出现一次的项varItems = dicDistinct.ItemsFor lngItem = LBound(varItems, 1) To UBound(varItems, 1)If varItems(lngItem) = 1 ThenlngCount = lngCount + 1End IfNext lngItemElse'如果单元格包含错误或为空则忽略If Not IsError(varValues) ThenIf LenB(varValues) > 0 ThenlngCount = 1End IfEnd IfEnd IfEnd IfCOUNTUNIQUE = lngCountExit FunctionErrorHandler:COUNTUNIQUE = CVErr(xlErrValue)End Function

            结果如下图5所示。

            图5

            注:本文整理自colinlegg.wordpress.com,供有兴趣的朋友学习参考。

            (0)

            相关推荐

            • Excel巧设公式(字典+数字)

              有个网友提了这样的一个需求:A中有包含重复值的数据,现在需要将重复值所在单元格的值改为公式引用.例如:A6单元格值为3,第一个出现3的单元格为A5,所以将A6公式设置为=$A$5,其他单元格依次类推. ...

            • EXCEL VBA 合并与撤销合并单元格代码

              合并单元格代码 合并单元格的代码 Sub 合并单元格() Dim irow As Integer Dim i As Integer Application.ScreenUpdating = False ...

            • vba编程基础1

              在主要的编程语言中, 结构体是用大括号来表示 代码段的 范围 界定的. 但是在一些比较'老的'语言中,由于历史原因, 还是使用的 是: 关键字来进行界定代码 的 结构段, 如vba中的 语法结构: i ...

            • VBA--变量

              点击上方 蓝色 文字  关注我们吧! 送人玫瑰,手有余香,请将文章分享给更多朋友 动手操作是熟练掌握EXCEL的最快捷途径! 所谓变量是指用VBA处理数据时用来暂时保存数据的"容器" ...

            • VBA实用小程序78:统计工作簿内文本框和批注中的字符及单词数

              excelperfect 下面的程序可以统计工作簿所有工作表中文本框和批注内的字符和单词的数量. Sub CountCharWorBOXCMT() Dim wks As Worksheet Dim l ...

            • VBA实用小程序71:统计工作簿中的字符数

              excelperfect 引言:这是在ozgrid.com论坛中看到的一个VBA程序,特辑录于此,供有兴趣的朋友学习参考. 下面的程序统计工作簿中所有工作表的字符总数,包括其中的文本框中的字符数. S ...

            • VBA实用小程序77:生成字符的所有组合

              excelperfect 这是在stackoverflow.com中看到的一段VBA程序,生成所有7个字符中5个字符的任意组合,有兴趣的朋友可以试试. 程序代码如下: Public Function ...

            • VBA实用小程序73:厘米、英寸和像素、磅的转换

              excelperfect Excel.PowerPoint和Word使用了一种称为磅的测量方法.但是,标准应用程序菜单中的测量单位是厘米或英寸. 可以在Excel选项更改默认的测量单位.单击" ...

            • VBA实用小程序75:选择所有的合并单元格

              excelperfect 在使用VBA操作单元格时,合并的单元格可能会导致问题,甚至在工作表中进行一些操作时,合并的单元格也可能会导致错误.因此,如果我们能够识别合并的单元格,就可以确定对它们采取的操 ...

            • VBA实用小程序74:将合并单元格转换为跨列居中

              excelperfect "合并后居中"按钮是Excel界面中一个非常方便的功能,很多人都喜欢使用合并单元格.然而,对合并单元格进行一些操作会带来一些问题,Excel会给出下图1所 ...

            • VBA实用小程序72:遍历文件夹(和子文件夹)中的文件

              excelperfect 很多时候,我们都想要遍历文件夹中的每个文件,例如在工作表中列出所有文件名.对每个文件进行修改.VBA给我们提供了一些方式:(1)Dir函数:(2)File System Ob ...

            • VBA实用小程序20:保护含有公式的单元格

              很多时候,你都不想别人修改或者删除你的工作表中含有公式的单元格,因为这样会打乱你的工作表结构. 下面的代码锁定当前工作表中含有公式的所有单元格,你不能删除或者修改这些单元格,除非你解除工作表保护.当然 ...

            • VBA实用小程序13:记录单元格中存放过的数据

              如果我们能够记录单元格中曾经放置过的数据,就可以清楚地看到该单元格的编辑痕迹.如图1所示,在工作表Sheet1的单元格批注中,显示出该单元格中所有存放过的数据,包括当前正存放的数据. 图1 在工作表S ...