VBA编程技巧 之 字典对象使用经验谈要点.docx
- 文档编号:16667509
- 上传时间:2023-07-16
- 格式:DOCX
- 页数:40
- 大小:107.77KB
VBA编程技巧 之 字典对象使用经验谈要点.docx
《VBA编程技巧 之 字典对象使用经验谈要点.docx》由会员分享,可在线阅读,更多相关《VBA编程技巧 之 字典对象使用经验谈要点.docx(40页珍藏版)》请在冰点文库上搜索。
VBA编程技巧之字典对象使用经验谈要点
VBA编程技巧之字典对象使用经验谈(更新至四之一)
字典对象使用经验谈
目录
前言_______________________________________01楼
一、字典的基本功能___________________________01楼
二、TheHardcoreofDictionary__________________02楼
二之一、多层字典对象应用案例分析1_____________16楼
二之二、多层字典对象应用案例分析2_____________30楼
三之一、动态的树形数据结构的构建________________35楼
三之二、动态树形结构的遍历____________________54楼
三之三、送你把漂亮的解牛小刀___________________64楼
三之四、上帝的归上帝,凯撒的归凯撒______________66楼
四之一、利用字典动态的构建自定义数据类型___________68楼
前言
相信大家对字典对象已经是耳熟能详了。
现在帮人写个代码如果不来个字典,出门都不好意思和人打招呼。
EH里也有大量的帖子详尽的介绍了字典对象的功能和各种使用方法,我大致翻过这些帖子,感觉很有必要和大家交流一下最近一段时间泡坛子、帮人家写代码得到的一些心得体会。
可能更多的会聊一些编程思路的东西,所以我想这篇文章应该是给有一定基础的朋友看的,起码应该能不需要注释就能看懂代码,起码应该看过置顶贴里提到的那些帖子。
一、字典的基本功能
相信字典对象最为出名的是它的关键字不重复特性,我们经常会看到这样的语句:
Fori=0toUBound(arr)
dic(arr(i,1))=""
Next
这段语句唯一的作用就是将数组的第一列数据去掉了重复项。
但值得强调的是既然我们叫它字典对象,那么它就理所应当的具有翻译功能。
以一个典型的EXCEL数据表为例,很多情况下会是类似于一个数据库中表这样的一个结构,即具有第一行的表头部分定义了每一列的内容是什么,其下每一行都是一条单独的纪录。
那么这种情况下,我们完全可以用字典对象来创建由表头来翻译索引列号。
这至少带来两个好处,1、使得你的代码更具有可看性,或则说更像自然语言;2、使得你的代码不会依赖于表格的地理位置,也就是说即便出于某种原因列的顺序有了变动,你也不需要去找出你的代码里涉及到相应列号并逐一改正。
其实,更重要的一点,是你的代码会具有更大的适用性。
让我们来比较两段代码,设想我们需要读取一个月工资表并统计各班组的绩效奖金,其包含姓名、班组、工位、基本工资、绩效奖金等等信息,那么可能的代码会是这样的:
复制内容到剪贴板
代码:
Dimdic,arr,i&,lRow&
lRow=Sheet1.[a65536].End(xlUp).Row
arr=Sheet1.Range("a2:
e"&lRow)
Setdic=CreateObject("Scripting.Dictionary")
Fori=1ToUBound(arr)
dic(arr(i,2))=dic(arr(i,2))+dic(arr(i,5))
Next
'=================
DimdTitle,arr,i&,dic
arr=Sheet1.[a1].CurrentRegion
SetdTitle=CreateObject("Scripting.Dictionary")
Fori=1ToUBound(arr,2)
dTitle(arr(1,i))=i
Next
Setdic=CreateObject("Scripting.Dictionary")
Fori=2ToUBound(arr)
dic(arr(i,dTitle("班组")))=dic(arr(i,dTitle("班组")))+arr(i,dTitle("绩效奖金"))
Next
第二段代码我们使用了一个名为dTitle的字典对象来记录表头名称和对应列号,这样当我们需要使用某列数据的时候,我们可以使用这个对象来将表头名翻译成列号。
很明显的是第一段代码完全依赖于表格内容的地理位置,而且如果不去看数据表的话,你根本不知道它在干什么。
而相应的,对于第二段代码而言,我们完全可以不用去了解数据表是什么样的,只需要知道它有这样的两个表头就可以了。
并且你不觉得它很接近自然语言了吗?
哦,不吗,你确定?
那我再稍微改一下:
复制内容到剪贴板
代码:
数据=Sheet1.[a1].CurrentRegion
Set表头之列号=CreateObject("Scripting.Dictionary")
Fori=1ToUBound(数据,2)
表头之列号(数据(1,i))=i
Next
Set班组绩效奖金=CreateObject("Scripting.Dictionary")
Fori=2ToUBound(数据)
班组名=数据(i,表头之列号("班组"))
成员绩效奖金=数据(i,表头之列号("绩效奖金"))
班组绩效奖金(班组名)=班组绩效奖金(班组名)+成员绩效奖金
Next
如果出于某些原因,原来的那个工资表在绩效奖金之前增加了一列,比如说老板大发善心为大家增发了住房津贴,显然作为劳资统计的你不会希望把它给漏了。
那么这时,如果你的代码是前面第一种方法,那么你必须仔细检查你的代码,确保每一个数字对应的列是你需要的内容。
但是如果你非常幸运的看过了这篇文章,并且使用了第二种方法,恭喜你,你不用像前者那样心惊胆颤的一个个数列数了,开开心心的在一边数钱吧!
可能有看官说了:
嘿,我们老板才烦呢,他不会加发工资的,他会把那个绩效奖金的名字改成工作表现奖!
你瞧,这下你要去改代码了吧。
那么这里我想说的是,养成良好的编程习惯,使用常量设置。
如果你经常写代码的话,你肯定会碰到前面这位看官提到的情况,那么你就会知道使用常量设置是多么方便的事情。
千万不要为了少敲键盘而省略这个过程,我们要牢记我军的优良训练传统:
训练多流汗,战时少流血!
编写多常量,更改不挠头!
想想还是把代码写出来看看效果吧:
复制内容到剪贴板
代码:
PublicConstPR_SALARY_GROUP="班组"
PublicConstPR_SALARY_BONUS="绩效奖金"
....
DimdTitle,arr,i&,dic
arr=Sheet1.[a1].CurrentRegion
SetdTitle=CreateObject("Scripting.Dictionary")
Fori=1ToUBound(arr,2)
dTitle(arr(1,i))=i
Next
Setdic=CreateObject("Scripting.Dictionary")
Fori=2ToUBound(arr)
dic(arr(i,dTitle(PR_SALARY_GROUP)))=_
dic(arr(i,dTitle(PR_SALARY_GROUP)))+arr(i,dTitle(PR_SALARY_BONUS))
Next
<未完待续>
二、TheHardcoreofDictionary
琢磨了半天,还真没想出什么中文词来表达Hardcore比较合适。
(题外话,不建议去Google搜索这个关键字,但相信我这个词本身没有任何相关的含义,真的是个好词。
)
我们知道字典对象由关键字Key和数据项Item构成。
通常情况下Key是字符串,实际上也可以是其它数据类型,比如整数、小数等。
而数据项则可以是任何数据类型,包括字典对象本身。
这样我们就可以创建多层的字典对象了。
利用多层字典对象,我们可以实现诸如级联菜单、联动数据有效性序列、联动下拉框等等应用,这也常见于坛子里各个帖子。
这里我不想重复谈这些应用,而是想着重强调其背后隐藏的一个概念。
我们到底用字典作了什么?
一言以蔽之,所谓的多层字典,实际上你利用它构造了一个树型数据结构!
坛子里也有很多帖子在介绍TreeView这个控件,它和我们的多层字典何其相似。
让我们还是以上面那个工资表来作为例子,我们可能希望把它处理成这样的一个形式:
复制内容到剪贴板
代码:
PublicConstPR_SALARY_GROUP="班组"
PublicConstPR_SALARY_POSITION="工位"
PublicConstPR_SALARY_NAME="姓名"
PublicConstPR_SALARY_BASE="基本工资"
PublicConstPR_SALARY_BONUS="绩效奖金"
PublicFunctionParseData()
DimdTitle,arr,i&,dic,dTemp
arr=Sheet1.[a1].CurrentRegion
SetdTitle=CreateObject("Scripting.Dictionary")
Fori=1ToUBound(arr,2)
dTitle(arr(1,i))=i
Next
Setdic=CreateObject("Scripting.Dictionary")
Fori=2ToUBound(arr)
IfNotdic.Exists(arr(i,dTitle(PR_SALARY_GROUP)))Then_
Setdic(arr(i,dTitle(PR_SALARY_GROUP)))=CreateObject("Scripting.Dictionary")
SetdTemp=dic(arr(i,dTitle(PR_SALARY_GROUP)))
IfNotdTemp.Exists(arr(i,dTitle(PR_SALARY_POSITION)))Then_
SetdTemp(arr(i,dTitle(PR_SALARY_POSITION)))=CreateObject("Scripting.Dictionary")
SetdTemp=dTemp(arr(i,dTitle(PR_SALARY_POSITION)))
IfNotdTemp.Exists(arr(i,dTitle(PR_SALARY_NAME)))Then_
SetdTemp(arr(i,dTitle(PR_SALARY_NAME)))=CreateObject("Scripting.Dictionary")
SetdTemp=dTemp(arr(i,dTitle(PR_SALARY_NAME)))
dTemp(PR_SALARY_BASE)=arr(i,dTitle(PR_SALARY_BASE))
dTemp(PR_SALARY_BONUS)=arr(i,dTitle(PR_SALARY_BONUS))
Next
SetParseData=dic
SetdTitle=Nothing
EndFunction
如果我们使用类似这样的语句SetdicSalary=ParseData()调用上面这个程序,那么我们可能得到的一个数据结构,会是如下这样子的:
复制内容到剪贴板
代码:
dicSalary
├─甲班
│ ├─拼装
│ │ ├─张三
│ │ │ ├─基本工资->$1000
│ │ │ │
│ │ │ └─绩效奖金->$800
│ │ │
│ │ └─李四
│ │ ├─基本工资->$1000
│ │ │
│ │ └─绩效奖金->$800
│ │
│ └─焊接
│ ├─王二麻子
│ │ ├─基本工资->$1100
│ │ │
│ │ └─绩效奖金->$900
│ │
│ └─赵大
│ ├─基本工资->$1100
│ │
│ └─绩效奖金->$900
│
└─乙班
├─拼装
│ ├─诸葛
│ │ ├─基本工资->$1000
│ │ │
│ │ └─绩效奖金->$800
│ │
│ └─南宫
│ ├─基本工资->$1000
│ │
│ └─绩效奖金->$800
│
└─焊接
├─西门
│ ├─基本工资->$1100
│ │
│ └─绩效奖金->$900
│
└─轩辕
├─基本工资->$1100
│
└─绩效奖金->$900
那么,对于这样一个数据结构,我们调用张三的基本工资就会是这样子的:
张三的基本工资=dicSalary("甲班")("拼装")("张三")("基本工资")
当然,我们也可以使用自定义类型来实现这一目的,代码可能会是象下面这个样子:
复制内容到剪贴板
代码:
PublicTypeSalary
NameAsString
AmountAsSingle
EndType
PublicTypePerson
NameAsString
Salaries()AsSalary
EndType
PublicTypePosition
NameAsString
Persons()AsPerson
EndType
PublicTypeGroup
NameAsString
Positions()AsPosition
EndType
这里,我不想再去写赋值代码,因为那实在是一个非常繁琐的过程。
不过我们可以想象一下这个赋值的过程,我们需要重新定义每层的数组元素数量,可能还需要通过循环来定位是数组的第几个元素。
而反过来当我们需要调用某个值得时候,也同样的啰嗦。
这时我们可以非常明显的看到使用字典对象的方便了,因为字典对象让我们可以用关键字来进行索引,而不需要对整个元素集合进行顺序遍历来查找定位。
提到集合,实际上我们还可以使用VBA原生的一个对象,就是集合对象(Collection),来实现这一目的。
但这里存在一个问题,集合对象没有Exists方法,也就是说你无法知晓某个关键字是否存在,只能通过OnErrorResumeNext,引用此关键字,再去判断Err.Number>0来得到答案,同时还要再清除这个错误,会麻烦不少。
不过这里不得不提一下集合对象的一个优势,那就是在它的Add方法支持After/Before参数,使得在初始赋值时,非常适合同时进行排序工作,如果你需要对你的树结构进行排序的话,建议你考虑用Collection对象。
看到这,相信你已经完全了解了字典对象在构造树形结构方面的优势。
有必要在这里解释一下,为什么这种数据结构非常重要。
通过上面的树形图,想必很容易理解这种结构清晰的反映了数据间的归属关系或是上下级关系。
而在现实生活中,我们几乎可以用这种结构来描述各种事物,公司的人员结构、文档的归类整理、你家的门牌号,等等等等。
这也是为什么我们在EH的VBA版看到大量的字典对象应用的根本原因,因为它太适合用来处理最常见的各种数据了。
接下来,我会结合具体的案例,来聊聊字典是如何处理树形结构数据的。
<未完待续>
二之一、多层字典对象应用案例分析1
这里我们以一个跟据已有标签内容数据、创建格式化标签供打印用的实际案例,来聊聊使用多层字典对象构造树形数据结构如何解决问题的。
这个例子的帖子地址:
为避免由于楼主编辑帖子,我把他的原始附件和最后我帮他完成的附件都贴在这里。
有兴趣的同学也可以去这个贴子看看,我在11楼提到的楼主代码的问题其实就是一些经验之谈。
看看楼主原来的附件里的代码,你会发现他是在用SelectCase语句完成字典的翻译功能,这样的做法违背了一个编程的基本原则,混淆了代码和数据的区别。
正如我在该贴11楼提到的,他的SN(SerialNumber)和Mask(某种规格)、TrayType(托盘型号)之间存在着多对一的关系,即SN确定则后两者都是确定的。
这种情况下,这样关系太适合用字典来描述了,同时这种关系本身是数据的,那么理应让它成为数据,所以我才要求楼主准备这样一个对照表。
可能有朋友会发现我比较啰嗦,但实际情况说明作为一个在EH的VBA版帮人写代码的家伙,适当的和楼主交流是多么的必要。
这就和你作为一个销售人员,在商业谈判中穿插着聊一些有趣的话题同时进一步了解客户需求的道理是一样的,即增进了感情又能掌握更多的信息,避免走弯路,呵呵。
还是回到这个小节的主题上来,我们先来分析一下这个例子里的数据是什么样的一个结构。
1、标签的数据有了,而且是按通常的数据库结构建立的,即表头、记录
2、标签的结构有两种,是按托盘型号区分的
3、标签是按包打印的,即每包一个标签,而同一包的芯片对应的Mask和Chip这两个值都是一样的
4、每包内又有数个托盘,每个托盘有数个芯片即序列号
看到这里,相信你也同样的发现这就是一个典型的树形结构,它的数据归属关系应该是这样子的:
托盘型号->包号->托盘号->芯片。
为此,我写了这样的一个过程来构造这个标签数据结构,如下:
复制内容到剪贴板
代码:
PrivateFunctionParseData()
DimaData,lRow&,i&,dic,dTemp
lRow=Cells(65536,MPL_COL_SN).End(xlUp).Row
aData=Cells(MPL_START_ROW,1).Resize(lRow-MPL_START_ROW+1,7)
Setdic=CreateObject("Scripting.Dictionary")
Fori=1ToUBound(aData)
IfNotdic.exists(aData(i,MPL_COL_TYPE))Then_
Setdic(aData(i,MPL_COL_TYPE))=CreateObject("Scripting.Dictionary")
SetdTemp=dic(aData(i,MPL_COL_TYPE))
IfNotdTemp.exists(aData(i,MPL_COL_PACK))Then_
SetdTemp(aData(i,MPL_COL_PACK))=CreateObject("Scripting.Dictionary")
SetdTemp=dTemp(aData(i,MPL_COL_PACK))
dTemp("CHIP")=aData(i,MPL_COL_CHIP)
dTemp("MASK")=aData(i,MPL_COL_MASK)
IfNotdTemp.exists(aData(i,MPL_COL_TRAY))Then_
SetdTemp(aData(i,MPL_COL_TRAY))=CreateObject("Scripting.Dictionary")
SetdTemp=dTemp(aData(i,MPL_COL_TRAY))
dTemp(aData(i,MPL_COL_SN))=True
Next
SetParseData=dic
EndFunction
为了更加直观的表达这段代码,让我们来给它画个树形图
复制内容到剪贴板
代码:
dic(变量名)
│
└─托盘型号
│
└─包号
│
├─CHIP->CHIP值
│
├─MASK->MASK值
│
└─托盘号
│
└─序列号
话说我们在处理EXCEL数据时,绝大多数的情况都可以分为三个步骤,即读取整理数据、计算构造输出数据、输出结果,前者和后者会和EXCEL工作表交互,而中间的那个步骤则是在内存中完成的。
我们知道数据结构决定了算法,也就是说第一步的读取整理数据决定了中间的计算和后面的输出的代码难易度。
上面这段代码的作用就是把原始的数据记录构造成了上图示意的一个树形数据结构,这也是为什么我把这段程序命名为ParseData而不是ReadData的原因。
说起来这个问题,如果你碰到要编程序处理数据了,一时想不好该干些什么,我告诉你可以先写象这样的几行代码,肯定没错的:
复制内容到剪贴板
代码:
PublicSub我要炒股票挣钱()
Call读取分析股票数据'ParseData
C
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- VBA编程技巧 字典对象使用经验谈要点 VBA 编程 技巧 字典 对象 使用 经验谈 要点