Excel-VBA - 在数据字段数组中插入新的第一列,无需循环或 API 调用
Posted
技术标签:
【中文标题】Excel-VBA - 在数据字段数组中插入新的第一列,无需循环或 API 调用【英文标题】:Excel-VBA - Insert new first column in datafield array without loops or API calls 【发布时间】:2019-01-12 06:57:12 【问题描述】:简介
去年@PrzemyslawRemin 提出了问题how to add a counter column to an existing matrix in VBA没有额外的循环和没有修改工作表。
本例中的原始矩阵是一个(基于 1 的 2-dim)数据字段数组,由(源单元格仅包含其地址字符串;插入的行将用数字填充)产生
Dim matrix As Variant
matrix = Range("A1:C5").value
输入矩阵:------------ ▼ 期望结果:
+----+----+----+ +----+----+----+----+
| A1 | B1 | C1 | | 1 | A1 | B1 | C1 |
+----+----+----+ +----+----+----+----+
| A2 | B2 | C2 | | 2 | A2 | B2 | C2 |
+----+----+----+ +----+----+----+----+
| A3 | B3 | C3 | | 3 | A3 | B3 | C3 |
+----+----+----+ +----+----+----+----+
| A4 | B4 | C4 | | 4 | A4 | B4 | C4 |
+----+----+----+ +----+----+----+----+
| A5 | B5 | C5 | | 5 | A5 | B5 | C5 |
+----+----+----+ +----+----+----+----+
当然,建议自己的想法是使用重新调整的newMatrix
,就像 Dy.Lee 建议的那样,但这将包括两个 循环 来移动行和列:
Sub test()
Dim matrix As Variant, newMatrix()
Dim i As Long, n As Long, c As Long, j As Long
matrix = Range("A1:C5").Value
n = UBound(matrix, 1)
c = UBound(matrix, 2)
ReDim newMatrix(1 To n, 1 To c + 1)
For i = 1 To n
newMatrix(i, 1) = i
For j = 2 To c + 1
newMatrix(i, j) = matrix(i, j - 1)
Next j
Next i
Range("a1").Resize(n, c + 1) = newMatrix
End Sub
另一个避免不必要循环的解决方法是将数组写回从 B 列开始的临时工作表,并从那里再次收集数据,包括 A:D 列,但这意味着修改工作表.
Florent B. 单独 solved 通过极快的 API 调用 使用 MemCopy
解决问题,此后似乎没有其他方法。 - 因此,出于主要原因,如果这应该是 ultima ratio 或者是否可以找到另一种方法,这会引起一些兴趣。
► 修改后的问题(无重复!)
是否有可能在现有数据字段数组中插入新的第一个“列”
无需循环“行”和“列”来移动现有值, 无需修改工作表和 没有使用 VBA 的 API 调用?与 Prezmyslaw 的 OP 不同,我没有使用庞大的数据集,因此可能会限制为大约 64k 行(参见最大转置限制)。
【问题讨论】:
【参考方案1】:通过Application.Index
函数找到解决方案
我通过尝试 Application.Index
函数的一些不寻常变体找到了一个解决方案,我尝试将其恢复为一个全面的通用概述以展示丰富的应用范围。因此,欢迎任何有用的补充(c.f. @chrisneilsen 的评论)。
Application.Index
函数的一些特性
通常,索引函数会通过其行和列位置提供明确定义的项目,但也有一些不那么广为人知的特性:
与Worksheet.Index
函数类似,如果行号或列号参数设置为零 (0),您可以获得整个列或行项目。 - 另一种通过传递双零参数创建 2-dim 数组的常见方法可以在 How to initialize a 2-dim array in Excel VBA
可以使用数组参数 - 此函数不仅允许通过给定数字指示已知索引,还允许数组参数提取“行”或“列”,因此可以通过Array(1,2,3)
指示一组想要的列,例如A:C
作为列数组参数。
过滤效果 - 此外,我了解到可以仅将选择减少到某些列(行),例如通过Array(1,3) and even to change the internal order, e.g.
Array(3,2,1)`。
重组 - 然而,最令人惊讶的事实是可以重复列选择,例如通过Array(1,1,2,3)
甚至Array(0,1,2,3)
,其中0
项与第1 列相同。这可用于达到与列插入相同的效果。
提到的Index
函数的这个最后的重组能力是我方法的关键部分:
代码示例
Sub AddFirstIndexColumn()
Dim v, i&, ws As Worksheet
Set ws = ThisWorkbook.Worksheets("SourceSheet") ' << change to source sheet name
' [1] get data
v = ws.[A1:C5].Value2
' [2] define column array inserting first column (0 or 1) and preserving old values (1,2,3)
v = Application.Index(v, _
Application.Evaluate("row(1:" & UBound(v) & ")"), _
Array(0, 1, 2, 3)) ' columns array where 0 reinserts the first column
' [3] add an current number in the first column
For i = LBound(v) To UBound(v): v(i, 1) = i: Next i
End Sub
如何测试结果
只需在上面的代码中插入以下内容:
' [4a] test result by debugging in immediate window
For i = LBound(v) To UBound(v)
Debug.Print "#" & i & ": " & Join(Application.Index(v, i, 0), ", ")
Next i
' [4b] test result by writing back to target sheet
Dim ws2 As Worksheet
Set ws2 = ThisWorkbook.Worksheets("TargetSheet") ' << change to target sheet name
ws2.Range("A1").Resize(UBound(v), UBound(v, 2)).Offset(0, 0) = v
警告
找到的解决方案似乎限制在 65536 行(可能类似于数组转置限制),因此您无法将其用于更大的数据。
最近的一些Application.Index
示例
【讨论】:
仅供参考,虽然使用INDEX
的方法很有趣,但对于 this 应用程序,它比循环方法或 API 方法慢得多。循环运行的时间大约是一半,而 API 又是一半(在 10,000 行范围内进行了测试)。 FWIW,我知道您将 Q 定义为“没有额外的循环......”,但恕我直言,这是一个没有价值的任意约束。如果代码需要在 Mac 上运行,则排除 API 调用将是有效的。
感谢您的宝贵反馈。我完全同意您的观点,即当时间 获取更大的数据集至关重要时,循环 是无与伦比的。 - 我的意图在这里是出于好奇和为了艺术而丰富传统解决方案的集合。在通过实验深入了解INDEX
的特性 时,我发现了重组可能性的真正宝藏,我试图将其列为有用的通用 概述。因此,欢迎像您这样的任何有用的补充。 @chrisneilsen
关于API方法:我故意排除了我在原帖中特别提到的API调用,因为在我看来,它已经得到了一定的限制。 - 但是,我正在考虑提出一个新的、扩展的问题,关注 API 问题 :-) @chrisneilsen以上是关于Excel-VBA - 在数据字段数组中插入新的第一列,无需循环或 API 调用的主要内容,如果未能解决你的问题,请参考以下文章