Excel VBA里的“俄罗斯套娃”:当数组嵌套数组,你的CPU还好吗?
各位表哥表姐们,大家好。
咱们今天不聊那些“如何把A列复制到B列”的幼儿园操作,也不扯什么Range(“A1”).Value = “Hello”这种连实习生都能盲打的入门货色。既然你点开了这篇文章,我猜你大概已经受够了ReDim Preserve像蜗牛一样的扩容速度,或者正在琢磨怎么在内存里搞个“多维宇宙”。
今天,咱们来聊点刺激的:VBA里的数组嵌套数组(Array of Arrays)。
这就好比你在俄罗斯套娃,打开一个娃娃,里面还有一个;再打开,还有一个……直到你发现最里面那个娃娃居然是一张写着“#N/A”的纸条。
一、为什么我们要搞“套娃”?
在VBA的世界里,标准的二维数组(Dim arr(1 to 10, 1 to 5))就像是一个规规矩矩的矩阵宿舍,每个人(元素)都有固定的床位(行和列)。这很好,很稳定,适合处理那种方方正正的Excel表格。
但是!生活(和数据)往往是不规则的。
想象一下,你要处理这样的数据:
第一行:张三,买了苹果、香蕉、橘子(3个水果)
第二行:李四,只买了榴莲(1个水果)
第三行:王五,买了葡萄、提子、车厘子、草莓、蓝莓(5个水果)
如果你用标准的二维数组,你得按最多的那一行(王五的5个)来定义列数。结果就是,张三和李四的后面全是空位,浪费内存不说,还得写一堆If IsEmpty()来判断有没有数据。
这时候,“数组嵌套数组”就闪亮登场了。
它的逻辑是:创建一个主数组,主数组里的每一个元素,不再是一个简单的字符串或数字,而是另一个数组。
MainArray(1) = [“苹果”, “香蕉”, “橘子”]
MainArray(2) = [“榴莲”]
MainArray(3) = [“葡萄”, “提子”, “车厘子”, “草莓”, “蓝莓”]
看到了吗?这就是VBA里的“动态二维数组”,或者说,Jagged Array(锯齿状数组)。每一行的长度都不一样,像锯齿一样参差不齐,却完美契合了你的数据。
二、实操环节:手把手教你“造娃”
别被理论吓跑了,代码才是硬道理。咱们直接上干货。
1. 声明阶段:给变体(Variant)一点尊重
首先,记住一条铁律:只有 Variant 类型的变量才能容纳数组。 你不能声明 Dim arr() As Integer 然后往里面塞数组,那样VBA会直接报错,表情比吃了没熟的柿子还苦涩。
Sub CreateJaggedArray()
Dim mainArr() As Variant ‘ 主数组必须是 Variant
Dim i As Long
‘ 假设我们有3行数据
ReDim mainArr(1 To 3)
‘ 开始套娃!
‘ 第1个元素:塞入一个包含3个元素的数组
mainArr(1) = Array(“苹果”, “香蕉”, “橘子”)
‘ 第2个元素:塞入一个包含1个元素的数组
mainArr(2) = Array(“榴莲”)
‘ 第3个元素:塞入一个包含5个元素的数组
mainArr(3) = Array(“葡萄”, “提子”, “车厘子”, “草莓”, “蓝莓”)
Debug.Print “套娃制作完成!”
End Sub
看,就是这么简单。Array() 函数返回的就是一个基于0的 Variant 数组,我们把它直接赋值给 mainArr 的某个元素。此时,mainArr(1) 本身就是一个数组。
2. 读取阶段:剥开套娃的艺术
存进去容易,取出来就得小心了。你不能直接用 mainArr(1, 2) 这种写法(那是标准二维数组的语法)。你需要两次寻址。
Sub ReadJaggedArray()
Dim mainArr() As Variant
Dim subArr() As Variant ‘ 用来接收内部的小数组
Dim i As Long, j As Long
Dim output As String
‘ 重新初始化数据(为了演示完整性)
ReDim mainArr(1 To 3)
mainArr(1) = Array(“苹果”, “香蕉”, “橘子”)
mainArr(2) = Array(“榴莲”)
mainArr(3) = Array(“葡萄”, “提子”, “车厘子”, “草莓”, “蓝莓”)
‘ 遍历主数组
For i = LBound(mainArr) To UBound(mainArr)
‘ 【关键点】:先把内部的数组取出来,赋值给一个临时的Variant数组变量
‘ 这一步相当于把大娃娃打开,拿出里面的小娃娃
subArr = mainArr(i)
output = “第” & i & “位顾客买了:”
‘ 遍历内部的小数组
‘ 注意:Array()函数生成的数组下标默认是从0开始的!
For j = LBound(subArr) To UBound(subArr)
output = output & subArr(j) & “、”
Next j
‘ 去掉最后一个顿号,强迫症福音
If Len(output) > 0 Then output = Left(output, Len(output) – 1)
Debug.Print output
Next i
End Sub
运行结果(F8走起):
第1位顾客买了:苹果、香蕉、橘子
第2位顾客买了:榴莲
第3位顾客买了:葡萄、提子、车厘子、草莓、蓝莓
3. 进阶玩法:从Excel范围直接“套娃”
当然,没人会手动写 Array(“苹果”, “香蕉”),我们的数据都在Excel表格里。怎么把不规则的区域变成嵌套数组?
这里有个小技巧:利用 Application.Transpose 或者直接循环读取。但最优雅的方式,是结合 Range.Value 的特性。
注意:Range.Value 读出来的是二维数组。如果要转成“数组的数组”,我们需要稍微加工一下。
Sub ExcelToJaggedArray()
Dim ws As Worksheet
Dim rng As Range
Dim mainArr() As Variant
Dim i As Long, lastRow As Long
Dim cellVal As Variant
Set ws = ThisWorkbook.Sheets(“Sheet1”)
‘ 假设A列是名字,B列开始是不定长的水果列表(用逗号分隔,或者已经在不同单元格?)
‘ 场景假设:每行数据长度不一,我们模拟读取每一行的有效区域
lastRow = ws.Cells(ws.Rows.Count, “A”).End(xlUp).Row
ReDim mainArr(1 To lastRow)
For i = 1 To lastRow
‘ 假设我们要读取第i行,从B列开始,直到遇到空单元格
‘ 这里演示一种动态构建内部数组的方法
Dim tempCol As New Collection
Dim colIndex As Long
colIndex = 2 ‘ 从B列开始
Do While Not IsEmpty(ws.Cells(i, colIndex))
tempCol.Add ws.Cells(i, colIndex).Value
colIndex = colIndex + 1
Loop
‘ 将Collection转为Array (需要一个小技巧,因为Collection不能直接变Array)
If tempCol.Count > 0 Then
Dim tempArr() As Variant
ReDim tempArr(0 To tempCol.Count – 1)
Dim k As Long
For k = 1 To tempCol.Count
tempArr(k – 1) = tempCol(k)
Next k
mainArr(i) = tempArr ‘ 嵌套成功!
Else
mainArr(i) = Array() ‘ 空数组
End If
Next i
MsgBox “已成功将不规则区域转换为嵌套数组,共 ” & lastRow & ” 行数据!”
End Sub
三、避坑指南:那些让你半夜哭醒的坑
作为过来人,我有义务提醒你几个“血泪教训”:
下标起始值的混乱:
你自己 ReDim 的数组,默认下标取决于你的 Option Base 设置(通常是0或1)。
但是!VBA内置的 Array() 函数返回的数组,永远是从0开始的,不管你怎么设置 Option Base 1。
后果:当你遍历内部数组时,如果不小心写了 For j = 1 To UBound(subArr),你会完美错过第一个元素(下标0),并且可能报“下标越界”或者逻辑错误。切记:处理 Array() 生成的子数组,请用 LBound 和 UBound,别偷懒写死数字!
类型检查的必要性:
因为主数组是 Variant,它理论上可以塞进任何东西:数字、字符串、日期,甚至另一个对象。
如果在复杂的宏里,你不确定某个元素是不是数组,先用 IsArray(mainArr(i)) 判断一下。否则,当你试图对一个数字执行 UBound 时,VBA会毫不留情地甩给你一个“类型不匹配”的错误。
性能的双刃剑:
嵌套数组在处理不规则数据时效率极高,因为它避免了大量的空值判断和内存浪费。
但是,频繁的“拆包”和“打包”(即反复访问 mainArr(i)(j))在超大规模循环中,比直接访问标准二维数组稍微慢那么一丢丢(真的只是一丢丢,人类感知不到,除非你在处理千万级数据)。
最佳实践:像上面代码演示的那样,先把子数组赋值给一个临时变量 subArr = mainArr(i),然后再循环 subArr。这样能减少VBA解释器的解析开销。
四、结语:让数据飞一会儿
朋友们,掌握“数组嵌套数组”,你就掌握了VBA处理非结构化数据的钥匙。它让你的代码不再是被Excel表格形状束缚的囚徒,而是能在内存中自由构建数据模型的架构师。
下次当你看到那些长短不一的数据行,别再苦哈哈地写 If Cells(i, j) <> “” Then 了。掏出你的 Variant,吹起你的“套娃”号角,让数据在数组的嵌套中翩翩起舞吧!
最后送大家一句话:
代码如人生,不必处处整齐划一。偶尔的“锯齿状”,才是真实的模样。
觉得有用?点个小赞赞,分享,转发给那个还在用 ReDim Preserve 死磕的同事。毕竟,独乐乐不如众乐乐,大家一起头发少一点,世界更美好。
夜雨聆风