HI,大家好,我是星光。在Excel基礎(chǔ)教程里,咱們講過函數(shù)+透視表是普通表格用戶處理數(shù)據(jù)的最佳組合,函數(shù)用于數(shù)據(jù)清洗整理,透視表用于數(shù)據(jù)匯總分析。如果你接觸過Power BI,會發(fā)現(xiàn)類似的情況,PowerQuery用于數(shù)據(jù)清洗整理,PowerPivot用于數(shù)據(jù)匯總分析。而VBA編程呢?也不例外,數(shù)組用于數(shù)據(jù)清洗整理,字典用于數(shù)據(jù)匯總分析……關(guān)于數(shù)組,可以看「零基礎(chǔ)學(xué)VBA編程系列教程」中的數(shù)組四篇,關(guān)于字典——打個響指,今天就來給大家講下什么是字典。 前方高能預(yù)警,本文共分8節(jié),建議先收后看。 目錄如下▼ 1 什么是字典 2 如何創(chuàng)建字典 3 如何將數(shù)據(jù)存入字典 4 如何將數(shù)據(jù)從字典取出 5 如何移除字典元素 6 如何遍歷字典元素 7 如何釋放字典 8 思考和其它 …… 1 丨 什么是字典 談到字典,有些朋友可能會想到新華字典、康熙字典、英漢字典、有道字典等等。我今天說的字典,和這些家伙——都沒關(guān)系。字典(Dictionary)是VBA編程中的一個對象,具有操作簡單、運行高效的特點,常用于數(shù)據(jù)的條件查詢、聚合匯總等。如果說數(shù)組是VBA處理數(shù)據(jù)的基礎(chǔ)結(jié)構(gòu),那么字典就可以被稱為核心。攤手,騙你娶你,不論男女。 我舉個例子。 如上圖所示的數(shù)據(jù)表,如果我們需要查詢看見星光的特長,數(shù)組的解法步驟如下▼ 首先將數(shù)據(jù)源存入數(shù)組,然后遍歷姓名,第1個人名不是,第2個人名不是,第3個人名不是,第4個還不是……一直到第7個人名才是,于是取特長,退出數(shù)組循環(huán)…… 數(shù)組循環(huán)是線性查找的方式,只能一個個元素找過去,如果查找值過多,效率就很不理想。 如果是用字典呢? 首先將數(shù)據(jù)源裝入字典,然后用一句代碼就可以查詢相關(guān)人名的特長了。 比如查詢看見星光的特長..▼ 字典('看見星光') 不用懷疑,不用循環(huán),就是這么簡單。 …… 還記得嗎?在數(shù)組四篇之什么是數(shù)組里,咱們講過數(shù)組處理數(shù)據(jù)的過程可以分為5步。創(chuàng)建數(shù)組->將數(shù)據(jù)存入數(shù)組->數(shù)組運算->將數(shù)組數(shù)據(jù)寫入Excel->釋放數(shù)組。 字典處理數(shù)據(jù)的過程與數(shù)組十分相似:創(chuàng)建字典->將數(shù)據(jù)裝入字典->將數(shù)據(jù)從字典取出->釋放字典。 ——那么如何創(chuàng)建字典? 2 丨 如何創(chuàng)建字典 如上文所講,字典(Dictionary)是VBA編程的一個對象,但它并非VBA自帶的妻妾,而是集成在動態(tài)鏈接庫文件Scrrun.dll中,需要綁定文件后才能夠使用。綁定文件有兩種方式,前期綁定和后期綁定。 先來說前期綁定。 操作步驟如下▼ 在VBE編輯器的[工具]選項卡下,單擊[引用],在打開的[引用]對話框中勾選'Microsoft Scripting Runtim'選項,單擊[確定]按鈕,關(guān)閉對話框即可。 通過'前期綁定'的方式引用Scrrun.dll文件后,即可在VBA代碼中利用Dim語句聲明變量為字典對象,然后使用字典處理數(shù)據(jù)。 示例代碼如下..▼
第2句代碼聲明一個字典對象,其名為d。語法格式如下▼ Dim 變量名 As New Dictionary …… 然后再說下后期綁定。 所謂后期綁定是指通過CreateObject函數(shù)創(chuàng)建對字典對象的引用,示例代碼如下▼
但是——前期綁定的代碼不適合發(fā)送給其它用戶使用,畢竟其它用戶未必會去手動綁定字典對象;因此后期綁定的方式兼容性更強些。 3 丨 如何將數(shù)據(jù)裝入字典 從表格角度,通俗而言,字典是有兩個一維數(shù)組或者說兩列數(shù)據(jù)構(gòu)成的特殊表。第1列是關(guān)鍵字,被稱為Key;第2列是每個關(guān)鍵字對應(yīng)的條目,被稱為Item。其中每個關(guān)鍵字在字典中都是唯一的,不會也不允許出現(xiàn)重復(fù)值。
那么如何將數(shù)據(jù)裝入字典呢? 直接賦值就可以了。語法格式如下▼ 字典(關(guān)鍵字)=條目 舉個例子。
第2行代碼使用前期綁定的方式聲明一個字典對象,其名為d。 第3行代碼將關(guān)鍵字'看見星光'裝入字典,對應(yīng)的條目是99。此時字典有一行數(shù)據(jù),如下圖所示。 第4行代碼將關(guān)鍵字'Excel星球'裝入字典,對應(yīng)的條目是98。此時字典就會有兩行數(shù)據(jù),如下圖所示。 但是我們前面講過,字典的關(guān)鍵字必須是唯一的,如果字典中已經(jīng)存在了某個關(guān)鍵字,我們又往里面添加了同樣的關(guān)鍵字,會怎么樣呢? 舉個例子。 Sub 字典添加重復(fù)數(shù)據(jù)() Dim d As New Dictionary '聲明一個字典對象 d('看見星光') = 99 d('看見星光') = 59 End Sub 如果我不想字典更新已經(jīng)存在的關(guān)鍵字記錄呢?憑什么把我從99分改為59分?對不對?我憑本事抄的答案你憑啥給我改成不及格?作為一個人,難道我連抄答案的自由都沒有了嗎?多么糟糕的組織會干出這樣無情的事?——摘自跨國出版物《星光日記》。 解決方案代碼如下▼
字典.Exists(關(guān)鍵字)
第4行代碼將數(shù)據(jù)源數(shù)據(jù)存入數(shù)組arr 第5至第7行代碼遍歷數(shù)組元素,將姓名作為key,特長作為item,分別存入字典。 …… 4 丨 如何將數(shù)據(jù)從字典取出 Sub 讀取數(shù)據(jù)() Dim d As New Dictionary '聲明一個字典對象 d('看見星光') = 99 d('Excel星球') = 98 MsgBox d('Excel星球') End Sub
![]() 如上圖所示的數(shù)據(jù)為例,需要根據(jù)A:B列的數(shù)據(jù)源,查詢D列人名對應(yīng)的特長,這就是所謂的條件查詢了。 Sub 讀取數(shù)據(jù)2() Dim d As New Dictionary Dim arr, brr, i As Long arr = Range('a1').CurrentRegion '數(shù)據(jù)源 For i = 2 To UBound(arr) '遍歷數(shù)組,數(shù)據(jù)裝入字典 d(arr(i, 1)) = arr(i, 2) 'key是人名,item是特長 Next brr = Range('d1:e' & Cells(Rows.Count, 'd').End(xlUp).Row) '查詢區(qū)域 For i = 2 To UBound(brr) '遍歷查詢值 If d.Exists(brr(i, 1)) Then '如果字典存在查詢值 brr(i, 2) = d(brr(i, 1)) '獲取人名對應(yīng)的條目 Else brr(i, 2) = '查無此人' End If Next Range('d1:e' & Cells(Rows.Count, 'd').End(xlUp).Row) = brr Set d = Nothing End Sub ![]() 字典除了支持通過指定關(guān)鍵字查詢對應(yīng)Item外,也支持一次性將所有的關(guān)鍵字或條目轉(zhuǎn)換為一維數(shù)組,這需要用到字典的Keys和Items屬性。 獲取字典所有的Key,語法格式如下▼
獲取字典所有的Item,語法格式如下▼ 字典.Items …… 我用麒麟雙臂給大家舉個典型的例子:數(shù)據(jù)去重。 如下圖所示的數(shù)據(jù)表,A列人名存在重復(fù)值,需要去重復(fù),獲取不重復(fù)的人員名單,結(jié)果如D列。
第4行至第9行代碼將數(shù)據(jù)源的姓名作為關(guān)鍵字存入字典。很多新手朋友可能困惑于下面這句代碼。 d(arr(i, 1)) = ''
需要重點說一下第11行代碼,它的作用是將字典所有關(guān)鍵字存入當(dāng)前工作表的D列。語句如下。 Range('d1').Resize(d.Count, 1) = Application.Transpose(d.Keys) Range('d1').Resize(d.Count, 1),以d1單元格為起點,向下擴展指定行數(shù),作為存放字典所有關(guān)鍵字的單元格區(qū)域。 d.Keys是以一維數(shù)組的形式返回字典所有的關(guān)鍵字。在數(shù)組四篇里咱們講過,一維數(shù)組就是一行數(shù)據(jù),需要通過Transpose函數(shù)進行一次轉(zhuǎn)置才能轉(zhuǎn)換為二維,然后才能直接寫入垂直單元格區(qū)域。 綜上所述——這四個字老霸氣了,但更霸氣的是隨后的四個字——您就懂了! …… 擴展一下,我再舉個與之相似的例子。 如下圖所示,需要在D:E列,獲取A:B列不重復(fù)的人名及其特長數(shù)據(jù)。 ![]()
和上一段代碼相比,所不同的有兩句。 第11行代碼放置數(shù)據(jù)的列數(shù)由1列改為了2列。 Range('d1').Resize(d.Count, 2) 代碼使用以下語句一次性獲取字典的Keys和Items,存入一個一維數(shù)組(在數(shù)組四篇里咱們講過,Array函數(shù)結(jié)果為一維數(shù)組),最后再通過一次轉(zhuǎn)置將一維數(shù)組修改為2維,直接寫入指定單元格區(qū)域。
…… 5 丨 如何移除字典中的元素 如果移除指定關(guān)鍵字,可以使用方法Remove,語法格式如下▼ 字典.Remove 關(guān)鍵字 示例代碼如下:
第3行和第4行代碼分別添加兩個關(guān)鍵字到字典中。此時字典有兩行數(shù)據(jù),如下圖所示▼ ![]() ![]() 除了根據(jù)指定關(guān)鍵字移除數(shù)據(jù)外,還可以使用方法RemoveAll將字典數(shù)據(jù)一次性清空。語法格式如下▼ 字典.RemoveAll
![]() 示例代碼如下▼
第6行代碼在實際處理每行數(shù)據(jù)之前,先清空字典中的所有元素。 第7至第11行代碼遍歷當(dāng)前行每列的元素,只將唯一值存入字典。 第12行代碼使用Join函數(shù),以逗號為分隔符,將當(dāng)前字典所有的關(guān)鍵字合并成為一個字符串,并存入結(jié)果數(shù)組。 arr(i, UBound(arr, 2)) = VBA.Join(d.Keys, ',') 然后再遍歷處理下一行數(shù)據(jù)…… …… 6 丨 如何遍歷字典中的元素 有時候,為了篩選出符合條件的數(shù)據(jù),我們需要像遍歷數(shù)組一樣,遍歷字典中的每個元素。這通常需要先獲取字典的Keys集合,再遍歷每個Key去篩選字典中符合條件的數(shù)據(jù)。 什么意思呢?舉個例子還是我。 如下圖所示的數(shù)據(jù)表,需要篩選出人名重復(fù)出現(xiàn)次數(shù)大于2次的人員名單,以及相關(guān)出現(xiàn)次數(shù),結(jié)果參考C:D列。
代碼解析: 第4行代碼將數(shù)據(jù)源數(shù)據(jù)存入數(shù)組arr。 第5行至第7行代碼將數(shù)組arr中的人名存入字典,重點是下面這句代碼。 d(arr(i, 1)) = d(arr(i, 1)) + 1 這句代碼類似于咱們在什么是變量里講過的計數(shù)器k=k+1。 作為賦值語句,它首先運算的是等號右側(cè)的表達式:d(arr(i,1))+1。有趣的是,在代碼運行這里的d(arr(i,1))的時候,我們還沒有將arr(i,1)的關(guān)鍵字存入字典,所以正常理解,這句代碼應(yīng)該返回程序錯誤,但事實并沒有。字典(關(guān)鍵字)語句的運算規(guī)則是,如果字典中存在指定關(guān)鍵字,則返回對應(yīng)的Item,否則會將該關(guān)鍵字存入字典,同時將其對應(yīng)的Item設(shè)置為Nothing。
等于▼ d(arr(i, 1)) = Nothing + 1 等于▼
也就是在字典中存入一個關(guān)鍵字arr(i,1),對應(yīng)的條目為1。 當(dāng)出現(xiàn)第2次出現(xiàn)同名的關(guān)鍵字時… d(arr(i, 1)) = d(arr(i, 1)) + 1 等于▼
等于▼ d(arr(i, 1)) = 2 也就是在字典將關(guān)鍵字arr(i,1)對應(yīng)的條目更新為2. ……以此實現(xiàn)了相同值出現(xiàn)次數(shù)在字典中不斷累加的效果。 你品品,是不是這個道理?品不出來?沒事,不怕你墮落,我送你一瓶82年的雪碧,你慢慢品。 ![]() …… 第8行代碼返回字典中所有的Key,結(jié)果是一個下標(biāo)為0的一維數(shù)組,命名為aKey。 第9行代碼聲明一個結(jié)果數(shù)組,行數(shù)為字典的個數(shù),列數(shù)是2列,一列放人名,一列放次數(shù)。 第10行至第16行代碼采用索引的方式遍歷數(shù)組aKey,查看每一個Key在字典中的Item是否大于2次,如果大于2次則將Key和Item分別存入結(jié)果數(shù)組。相似的套路咱們在數(shù)組4篇的數(shù)組運算里詳細講過了,這里就不再敲擊鍵盤,免得浪費它所剩無幾的生命力。 …… 7 丨 如何釋放字典內(nèi)存
在章節(jié)什么是變量里咱們講過了,它的作用是釋放對象變量所占用的內(nèi)存,提高代碼運行效率,雖然它未必是必須的,但聰明的您最好像優(yōu)秀的我一樣,養(yǎng)成使用它的好習(xí)慣。 有朋友可能會問,我用字典.RemoveAll語句清空字典,是不是可以代替Set 字典=Nothing?答案是否定的,你失戀了,傷感的把合租的房子清空,和你一怒之下把房子給燒沒了,是兩個概念好吧?前者房子雖然空了,但還在,還能住新歡和舊愛,后者是連房子都沒了,再愛也都煙消云散了……。 8 丨 思考題和其它 …… 1丨前期綁定和后期綁定的不同 在本章第2節(jié)如何創(chuàng)建字典,咱們講過,有些屬性前期綁定是支持的,但后期綁定并不能使用。這個有些屬性,其實指的就是Items和Keys。 在前期綁定的情況下,我們可以使用以下語句讀取Keys集合的指定索引元素。 讀取字典Keys的第2個元素,并賦值為變量strKey..▼ strKey = d.Keys(1) 但后期綁定并不支持運行該語句,必須先將Key或Item轉(zhuǎn)換為數(shù)組才能夠索引遍歷。 讀取字典Keys的第2個元素,并賦值為變量strKey..▼
…… 2丨如何讓字典不區(qū)分字母大小寫 一種是設(shè)置字典的CompareMode屬性為TextCompar,示例代碼如下▼ Sub 不區(qū)分字母大小寫() Dim d As New Dictionary d.CompareMode = TextCompare d('a') = 1 MsgBox d('A') End Sub 一種是將所有的字母統(tǒng)一轉(zhuǎn)換為大寫(UCase)或小寫(LCase),這種方式明顯修改了原值,因此通常不建議使用。示例代碼如下▼
…… 3丨字典對數(shù)據(jù)類型的態(tài)度是嚴(yán)格的 字典對數(shù)據(jù)類型的態(tài)度是嚴(yán)格的——這句話是什么意思呢?我們知道Excel是一款對數(shù)據(jù)類型要求很寬松的軟件,數(shù)值可以分為文本型數(shù)值和純數(shù)值兩種,在VBA的邏輯判斷中,文本型數(shù)值和純數(shù)值是相等的,比如以下代碼返回True。 Sub t() MsgBox '1' = 1 End Sub 但在字典中,純數(shù)值和文本型數(shù)值并不相等。 舉個例子,示例代碼如下▼
第3行代碼將純數(shù)值1作為Key存入字典。 第4行代碼判斷文本型數(shù)值1在字典中是否存在,結(jié)果返回False。 知道這個知識點有什么用? 當(dāng)你需要處理的Key有數(shù)值類型時,最好將源數(shù)據(jù)和查詢值都統(tǒng)一轉(zhuǎn)換為文本的形式,避免踩坑。如何統(tǒng)一轉(zhuǎn)換為文本的形式?可以通過聲明一個字符串類型的變量,強制進行轉(zhuǎn)換。 示例代碼如下▼ Sub 數(shù)據(jù)類型2() Dim d As New Dictionary Dim strKey As String '定義一個字符串類型的變量 strKey = '1' d(strKey) = '愛就一個字' strKey = '2' d(strKey) = '我要說兩次' strKey = 1 '強制轉(zhuǎn)換為字符串 MsgBox d.Exists(strKey) '結(jié)果返回True End Sub 4丨字典常用方法和屬性的另外表達方式 如果你看的VBA代碼多了,可能會看到有人使用以下方式往字典中添加關(guān)鍵字和對應(yīng)條目:
其中夏天是Key,吃冰棍是Item。 但這種方式在常規(guī)VBA代碼中我并不推薦使用,原因很簡單,它不夠靈活。如果字典中已存在相同關(guān)鍵字,該語句會返回錯誤值,而且它并不支持更新相關(guān)條目。當(dāng)然,最重要的是,它打字太多了,你數(shù)數(shù),相比下句代碼它多打了幾個字?別不把手指當(dāng)親骨肉啊同志們吶!! d('夏天')='吃冰棍' 與之相似的還有下面幾種語句,同樣因為不夠靈活,打字偏多等不推薦使用。
5丨如何學(xué)習(xí)更多VBA編程知識和技巧? |
|