HI,大家好,我是星光。在Excel基础教程里,咱们讲过函数+透视表是普通表格用户处理数据的最佳组合,函数用于数据清洗整理,透视表用于数据汇总分析。如果你接触过Power BI,会发现类似的情况,PowerQuery用于数据清洗整理,PowerPivot用于数据汇总分析。而VBA编程呢?也不例外,数组用于数据清洗整理,字典用于数据汇总分析……关于数组,可以看「零基础学VBA编程系列教程」中的数组四篇,关于字典——打个响指,今天就来给大家讲下什么是字典。 前方高能预警,本文共分8节,建议先收后看。 目录如下▼ 1 什么是字典 2 如何创建字典 3 如何将数据存入字典 4 如何将数据从字典取出 5 如何移除字典元素 6 如何遍历字典元素 7 如何释放字典 8 思考和其它 …… 1 丨 什么是字典 谈到字典,有些朋友可能会想到新华字典、康熙字典、英汉字典、有道字典等等。我今天说的字典,和这些家伙——都没关系。字典(Dictionary)是VBA编程中的一个对象,具有操作简单、运行高效的特点,常用于数据的条件查询、聚合汇总等。如果说数组是VBA处理数据的基础结构,那么字典就可以被称为核心。摊手,骗你娶你,不论男女。 我举个例子。 如上图所示的数据表,如果我们需要查询看见星光的特长,数组的解法步骤如下▼ 首先将数据源存入数组,然后遍历姓名,第1个人名不是,第2个人名不是,第3个人名不是,第4个还不是……一直到第7个人名才是,于是取特长,退出数组循环…… 数组循环是线性查找的方式,只能一个个元素找过去,如果查找值过多,效率就很不理想。 如果是用字典呢? 首先将数据源装入字典,然后用一句代码就可以查询相关人名的特长了。 比如查询看见星光的特长..▼ 字典('看见星光') 不用怀疑,不用循环,就是这么简单。 …… 还记得吗?在数组四篇之什么是数组里,咱们讲过数组处理数据的过程可以分为5步。创建数组->将数据存入数组->数组运算->将数组数据写入Excel->释放数组。 字典处理数据的过程与数组十分相似:创建字典->将数据装入字典->将数据从字典取出->释放字典。 ——那么如何创建字典? 2 丨 如何创建字典 如上文所讲,字典(Dictionary)是VBA编程的一个对象,但它并非VBA自带的妻妾,而是集成在动态链接库文件Scrrun.dll中,需要绑定文件后才能够使用。绑定文件有两种方式,前期绑定和后期绑定。 先来说前期绑定。 操作步骤如下▼ 在VBE编辑器的[工具]选项卡下,单击[引用],在打开的[引用]对话框中勾选'Microsoft Scripting Runtim'选项,单击[确定]按钮,关闭对话框即可。 通过'前期绑定'的方式引用Scrrun.dll文件后,即可在VBA代码中利用Dim语句声明变量为字典对象,然后使用字典处理数据。 示例代码如下..▼
第2句代码声明一个字典对象,其名为d。语法格式如下▼ Dim 变量名 As New Dictionary …… 然后再说下后期绑定。 所谓后期绑定是指通过CreateObject函数创建对字典对象的引用,示例代码如下▼
但是——前期绑定的代码不适合发送给其它用户使用,毕竟其它用户未必会去手动绑定字典对象;因此后期绑定的方式兼容性更强些。 3 丨 如何将数据装入字典 从表格角度,通俗而言,字典是有两个一维数组或者说两列数据构成的特殊表。第1列是关键字,被称为Key;第2列是每个关键字对应的条目,被称为Item。其中每个关键字在字典中都是唯一的,不会也不允许出现重复值。
那么如何将数据装入字典呢? 直接赋值就可以了。语法格式如下▼ 字典(关键字)=条目 举个例子。
第2行代码使用前期绑定的方式声明一个字典对象,其名为d。 第3行代码将关键字'看见星光'装入字典,对应的条目是99。此时字典有一行数据,如下图所示。 第4行代码将关键字'Excel星球'装入字典,对应的条目是98。此时字典就会有两行数据,如下图所示。 但是我们前面讲过,字典的关键字必须是唯一的,如果字典中已经存在了某个关键字,我们又往里面添加了同样的关键字,会怎么样呢? 举个例子。 Sub 字典添加重复数据() Dim d As New Dictionary '声明一个字典对象 d('看见星光') = 99 d('看见星光') = 59 End Sub 如果我不想字典更新已经存在的关键字记录呢?凭什么把我从99分改为59分?对不对?我凭本事抄的答案你凭啥给我改成不及格?作为一个人,难道我连抄答案的自由都没有了吗?多么糟糕的组织会干出这样无情的事?——摘自跨国出版物《星光日记》。 解决方案代码如下▼
字典.Exists(关键字)
第4行代码将数据源数据存入数组arr 第5至第7行代码遍历数组元素,将姓名作为key,特长作为item,分别存入字典。 …… 4 丨 如何将数据从字典取出 Sub 读取数据() Dim d As New Dictionary '声明一个字典对象 d('看见星光') = 99 d('Excel星球') = 98 MsgBox d('Excel星球') End Sub
![]() 如上图所示的数据为例,需要根据A:B列的数据源,查询D列人名对应的特长,这就是所谓的条件查询了。 Sub 读取数据2() Dim d As New Dictionary Dim arr, brr, i As Long arr = Range('a1').CurrentRegion '数据源 For i = 2 To UBound(arr) '遍历数组,数据装入字典 d(arr(i, 1)) = arr(i, 2) 'key是人名,item是特长 Next brr = Range('d1:e' & Cells(Rows.Count, 'd').End(xlUp).Row) '查询区域 For i = 2 To UBound(brr) '遍历查询值 If d.Exists(brr(i, 1)) Then '如果字典存在查询值 brr(i, 2) = d(brr(i, 1)) '获取人名对应的条目 Else brr(i, 2) = '查无此人' End If Next Range('d1:e' & Cells(Rows.Count, 'd').End(xlUp).Row) = brr Set d = Nothing End Sub ![]() 字典除了支持通过指定关键字查询对应Item外,也支持一次性将所有的关键字或条目转换为一维数组,这需要用到字典的Keys和Items属性。 获取字典所有的Key,语法格式如下▼
获取字典所有的Item,语法格式如下▼ 字典.Items …… 我用麒麟双臂给大家举个典型的例子:数据去重。 如下图所示的数据表,A列人名存在重复值,需要去重复,获取不重复的人员名单,结果如D列。
第4行至第9行代码将数据源的姓名作为关键字存入字典。很多新手朋友可能困惑于下面这句代码。 d(arr(i, 1)) = ''
需要重点说一下第11行代码,它的作用是将字典所有关键字存入当前工作表的D列。语句如下。 Range('d1').Resize(d.Count, 1) = Application.Transpose(d.Keys) Range('d1').Resize(d.Count, 1),以d1单元格为起点,向下扩展指定行数,作为存放字典所有关键字的单元格区域。 d.Keys是以一维数组的形式返回字典所有的关键字。在数组四篇里咱们讲过,一维数组就是一行数据,需要通过Transpose函数进行一次转置才能转换为二维,然后才能直接写入垂直单元格区域。 综上所述——这四个字老霸气了,但更霸气的是随后的四个字——您就懂了! …… 扩展一下,我再举个与之相似的例子。 如下图所示,需要在D:E列,获取A:B列不重复的人名及其特长数据。 ![]()
和上一段代码相比,所不同的有两句。 第11行代码放置数据的列数由1列改为了2列。 Range('d1').Resize(d.Count, 2) 代码使用以下语句一次性获取字典的Keys和Items,存入一个一维数组(在数组四篇里咱们讲过,Array函数结果为一维数组),最后再通过一次转置将一维数组修改为2维,直接写入指定单元格区域。
…… 5 丨 如何移除字典中的元素 如果移除指定关键字,可以使用方法Remove,语法格式如下▼ 字典.Remove 关键字 示例代码如下:
第3行和第4行代码分别添加两个关键字到字典中。此时字典有两行数据,如下图所示▼ ![]() ![]() 除了根据指定关键字移除数据外,还可以使用方法RemoveAll将字典数据一次性清空。语法格式如下▼ 字典.RemoveAll
![]() 示例代码如下▼
第6行代码在实际处理每行数据之前,先清空字典中的所有元素。 第7至第11行代码遍历当前行每列的元素,只将唯一值存入字典。 第12行代码使用Join函数,以逗号为分隔符,将当前字典所有的关键字合并成为一个字符串,并存入结果数组。 arr(i, UBound(arr, 2)) = VBA.Join(d.Keys, ',') 然后再遍历处理下一行数据…… …… 6 丨 如何遍历字典中的元素 有时候,为了筛选出符合条件的数据,我们需要像遍历数组一样,遍历字典中的每个元素。这通常需要先获取字典的Keys集合,再遍历每个Key去筛选字典中符合条件的数据。 什么意思呢?举个例子还是我。 如下图所示的数据表,需要筛选出人名重复出现次数大于2次的人员名单,以及相关出现次数,结果参考C:D列。
代码解析: 第4行代码将数据源数据存入数组arr。 第5行至第7行代码将数组arr中的人名存入字典,重点是下面这句代码。 d(arr(i, 1)) = d(arr(i, 1)) + 1 这句代码类似于咱们在什么是变量里讲过的计数器k=k+1。 作为赋值语句,它首先运算的是等号右侧的表达式:d(arr(i,1))+1。有趣的是,在代码运行这里的d(arr(i,1))的时候,我们还没有将arr(i,1)的关键字存入字典,所以正常理解,这句代码应该返回程序错误,但事实并没有。字典(关键字)语句的运算规则是,如果字典中存在指定关键字,则返回对应的Item,否则会将该关键字存入字典,同时将其对应的Item设置为Nothing。
等于▼ d(arr(i, 1)) = Nothing + 1 等于▼
也就是在字典中存入一个关键字arr(i,1),对应的条目为1。 当出现第2次出现同名的关键字时… d(arr(i, 1)) = d(arr(i, 1)) + 1 等于▼
等于▼ d(arr(i, 1)) = 2 也就是在字典将关键字arr(i,1)对应的条目更新为2. ……以此实现了相同值出现次数在字典中不断累加的效果。 你品品,是不是这个道理?品不出来?没事,不怕你堕落,我送你一瓶82年的雪碧,你慢慢品。 ![]() …… 第8行代码返回字典中所有的Key,结果是一个下标为0的一维数组,命名为aKey。 第9行代码声明一个结果数组,行数为字典的个数,列数是2列,一列放人名,一列放次数。 第10行至第16行代码采用索引的方式遍历数组aKey,查看每一个Key在字典中的Item是否大于2次,如果大于2次则将Key和Item分别存入结果数组。相似的套路咱们在数组4篇的数组运算里详细讲过了,这里就不再敲击键盘,免得浪费它所剩无几的生命力。 …… 7 丨 如何释放字典内存
在章节什么是变量里咱们讲过了,它的作用是释放对象变量所占用的内存,提高代码运行效率,虽然它未必是必须的,但聪明的您最好像优秀的我一样,养成使用它的好习惯。 有朋友可能会问,我用字典.RemoveAll语句清空字典,是不是可以代替Set 字典=Nothing?答案是否定的,你失恋了,伤感的把合租的房子清空,和你一怒之下把房子给烧没了,是两个概念好吧?前者房子虽然空了,但还在,还能住新欢和旧爱,后者是连房子都没了,再爱也都烟消云散了……。 8 丨 思考题和其它 …… 1丨前期绑定和后期绑定的不同 在本章第2节如何创建字典,咱们讲过,有些属性前期绑定是支持的,但后期绑定并不能使用。这个有些属性,其实指的就是Items和Keys。 在前期绑定的情况下,我们可以使用以下语句读取Keys集合的指定索引元素。 读取字典Keys的第2个元素,并赋值为变量strKey..▼ strKey = d.Keys(1) 但后期绑定并不支持运行该语句,必须先将Key或Item转换为数组才能够索引遍历。 读取字典Keys的第2个元素,并赋值为变量strKey..▼
…… 2丨如何让字典不区分字母大小写 一种是设置字典的CompareMode属性为TextCompar,示例代码如下▼ Sub 不区分字母大小写() Dim d As New Dictionary d.CompareMode = TextCompare d('a') = 1 MsgBox d('A') End Sub 一种是将所有的字母统一转换为大写(UCase)或小写(LCase),这种方式明显修改了原值,因此通常不建议使用。示例代码如下▼
…… 3丨字典对数据类型的态度是严格的 字典对数据类型的态度是严格的——这句话是什么意思呢?我们知道Excel是一款对数据类型要求很宽松的软件,数值可以分为文本型数值和纯数值两种,在VBA的逻辑判断中,文本型数值和纯数值是相等的,比如以下代码返回True。 Sub t() MsgBox '1' = 1 End Sub 但在字典中,纯数值和文本型数值并不相等。 举个例子,示例代码如下▼
第3行代码将纯数值1作为Key存入字典。 第4行代码判断文本型数值1在字典中是否存在,结果返回False。 知道这个知识点有什么用? 当你需要处理的Key有数值类型时,最好将源数据和查询值都统一转换为文本的形式,避免踩坑。如何统一转换为文本的形式?可以通过声明一个字符串类型的变量,强制进行转换。 示例代码如下▼ Sub 数据类型2() Dim d As New Dictionary Dim strKey As String '定义一个字符串类型的变量 strKey = '1' d(strKey) = '爱就一个字' strKey = '2' d(strKey) = '我要说两次' strKey = 1 '强制转换为字符串 MsgBox d.Exists(strKey) '结果返回True End Sub 4丨字典常用方法和属性的另外表达方式 如果你看的VBA代码多了,可能会看到有人使用以下方式往字典中添加关键字和对应条目:
其中夏天是Key,吃冰棍是Item。 但这种方式在常规VBA代码中我并不推荐使用,原因很简单,它不够灵活。如果字典中已存在相同关键字,该语句会返回错误值,而且它并不支持更新相关条目。当然,最重要的是,它打字太多了,你数数,相比下句代码它多打了几个字?别不把手指当亲骨肉啊同志们呐!! d('夏天')='吃冰棍' 与之相似的还有下面几种语句,同样因为不够灵活,打字偏多等不推荐使用。
5丨如何学习更多VBA编程知识和技巧? |
|
来自: 5jia5 > 《Excle & VBA》