如何在 VBA 中一次为数组的某些元素分配相同的值?

How does one assign the same value to some elements of an array at once in VBA?

我很想知道如何在 VBA 中同时为数组的某些元素、行或列分配相同的值?例如,我有一个像这样的 3x3 数组 (MyArray)

1   2   3
4   5   6
7   8   9

我需要改变元素的值 MyArray(1,1), MyArray(2,2), MyArray(1,3)MyArray(3,2) 为 11 或第 2 行的值为 13。现在,如何通过一个声明?我想是这样的:

Union(MyArray(1,1), MyArray(2,2), MyArray(1,3), MyArray(3,2)) = 11

MyArray(2,1 : 2,3) = 13

可以,但出现错误。我处理这件事的方法是把数组的数据放在一个未使用的工作表上,更改那里的值,然后从工作表中读回值到数组中,如this method。使用上面的示例,然后我使用以下代码:

Range("A1:C3") = MyArray: Range("A1,B2,C1,B3") = 11: MyArray = Range("A1:C3")

Range("A1:C3") = MyArray: Range("A2:C2") = 13: MyArray = Range("A1:C3")

是否还有其他不使用工作表或循环的替代方法?

VBA 不提供任何方法在单个语句中分配多维数组,因此您需要声明数组然后分配每个值:

Dim MyArray(2,2)

MyArray(0,0) = 1
MyArray(0,1) = 2
MyArray(0,2) = 3
MyArray(1,0) = 4
MyArray(1,1) = 5
MyArray(1,2) = 6
MyArray(2,0) = 7
MyArray(2,1) = 8
MyArray(2,2) = 9

编辑 Slai 的回答的早期版本有一个部分解决方案,我将其充实为避免使用锯齿状数组的解决方案。

事实证明,您可以在一条语句中分配一个二维数组,但数组必须 是 Variant 且基于 1,并且它 适用于 Excel.

您可以分配单个元素,也可以使用 CopyMemory 技巧分配整个 "column"(但 第一列)。如果您需要分配整个 "rows" 或 "columns",那么使用锯齿状数组可能会更好。

Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
    lpvDest As Any, _
    lpvSource As Any, _
    ByVal cbCopy As Long)

Sub test()
    'assign a 2D array using the [] shorthand syntax for Eval
    myArray = [{1,2,3;4,5,6;7,8,9}]

    'Change an individual value
    myArray(1, 2) = 11

    'Change the entire first row (this ONLY works for the first row)
    'Using a 1-based array assigned with []/Eval
    colarray = [{14,15,16}]
    CopyMemory myArray(1, 1), colarray(1), (UBound(colarray, 1) - LBound(colarray, 1) + 1) * 16 ' changes a whole "column"

    'Change the entire first row (this ONLY works for the first row)
    'Using a 0-based array assigned with Array()
    colarray = Array(17, 18, 19)
    CopyMemory myArray(1, 1), colarray(LBound(colarray, 1)), (UBound(colarray, 1) - LBound(colarray, 1) + 1) * 16 ' changes a whole "column"

End Sub

我没有完全理解你的问题,所以我专注于问题的标题

How does one assign the same value to some elements of an array at once in VBA?

我不使用 VBA 所以我不确定,但在 Ruby 之前我经常使用 VbScript,这里是一个如何在 VbScript 中执行此操作的示例。我从来没有遇到过 VBA 中没有 运行 的 VbScript 代码片段(我遇到过相反的方法),但谁知道呢..

MyArray = array(_
array(1, 2, 3),_
array(4, 5, 6),_
array(7, 8, 9)_
)

Wscript.echo MyArray(0)(0) '1'
Wscript.echo MyArray(2)(2) '9'

编辑:正如德克所说:这个解决方案是一个包含其他数组的单维数组,但正如我的解决方案所示,它通常更通用,例如不需要初始化..

VBA 不支持这样的东西。只有范围才有可能。

理论上……

您可以使用 Evaluate 以非常有限的方式执行此类操作。显示一些仅更改一个 row/column 或二维数组的特定值的示例:

Dim MyArray() As Variant

Public Function MyArrayOut() As Variant
  MyArrayOut = MyArray
End Function

Sub testing()
  'we are using a 5x4 (4 columns / 5 rows) array
  ReDim MyArray(0 To 4, 0 To 3) As Variant 'also 1 to 5 and 1 to 4 are possible.... doesn't matter

  Dim i As Long, j As Long 'input some numbers
  For i = 0 To 4
    For j = 0 To 3
      MyArray(i, j) = i + 5 * j + 1
    Next
  Next

  [A1:D5].Value2 = MyArray
  Stop ' check the sheet -> should be 1 to 20

  'lets add 11 to the second row
  MyArray = Evaluate("=IF({0;1;0;0;0},MyArrayOut()+11,MyArrayOut())")

  [A1:D5].Value2 = MyArray
  Stop ' check again. row 2 should be 13, 18, 23, 28

  'set only 1,1 ; 2,2 ; 4,2 and 5,4 to 0. just keep in mind that using math will fail if ANY value is not numeric (or empty)
  MyArray = Evaluate("=MyArrayOut()*{0,1,1,1;1,0,1,1;1,1,1,1;1,0,1,1;1,1,1,0}")

  [A1:D5].Value2 = MyArray
  Stop 'A1, B2, B4, D5 all should be 0

  'set column 3 to "abc"

  MyArray = Evaluate("=IF({0,0,1,0},""abc"",MyArrayOut())")

  [A1:D5].Value2 = MyArray
  Stop ' check again. column C should be just abc

End Sub

使用有效的 IF 语句可以做很多事情,但您需要对其进行硬编码,或者使用相当复杂的算法 "kills" 与 [=63] 的想法一样好=].

此外,这将占用更多资源,并且总是比直接执行更慢。您还需要一个额外的函数来将数组放入求值中。

还有一种情况在我看来是有用的:通过INDEX提取一个row/column。请记住,输出仍然是一个二维数组,可以通过 Application.Transpose-Trick 取消它。它可用的原因只是一个事实,即不需要知道数组的大小。这样就可以直接通过extrVar = Evaluate("=INDEX(MyArrayOut(),2,)")提取。而 2 可以通过不同的变量设置。这样您就不需要 ReDim 新变量(在本例中为 extrVar)。它只需要变体。也就是说,使用 Application.Index 你也可以在不使用 Evaluate 的情况下实现这一点。 (它也使用更少的资源并且更快)

我希望您对可以使用求值完成工作的方式有所了解。

不过,VBA 本身并没有提供任何方法来通过语法本身一步操作数组的多个内容。 (至少不是你可以用于这种情况的方式)

编辑
我差点忘了:要将一个变量中的所有值都设置为相同的值,像 MyArray = Evaluate("=IF(MyArrayOut()="""",5,5)") 这样的东西会把所有东西都设置为 5 或 MyArray = Evaluate("=IF(MyArrayOut()="""",""abc"",""abc"")") 只包含 "abc"。要清空它,您可以使用 MyArray = Evaluate("=IF(MyArrayOut()="""","""","""")"),但是没有 Preserve 的简单 ReDim 也可以在一行中完成。

编辑 2

对于您的 Range("A1:C3") = MyArray: Range("A1,B2,C1,B3") = 11: MyArray = Range("A1:C3") 示例,您可以将其用作自己函数的基础,例如:

Public Sub multiSetSame(ByRef arr As Variant, ByVal val As Variant, ParamArray str() As Variant)
  Dim runner As Variant
  For Each runner In str
    arr(Split(runner, ",")(0), Split(runner, ",")(1)) = val
  Next
End Sub

Sub test()
  Dim MyArray(1 To 3, 1 To 4) As Long

  multiSetSame MyArray, 11, "1,1", "2,2", "1,3", "3,2"

  [A1:D3].Value2 = MyArray
End Sub

但是对于这个例子,虽然我不知道它是如何评估的要更改哪些值,最好在评估时直接更改它,或者如果它们总是相同的,只需编写一个 sub 来完成所有值的工作。任何 "workaround" 只会减慢整个过程。

如果您还有任何问题,请尽管提问 ;)

为了避免每次需要在定义的模式中分配值时都编写 for 循环,据我所知,您必须像这样实现一个 Sub:

SetArray(MyArray, patternStr, value)

patternStr 是将被解析并告诉 Sub 将值放在哪里的字符串。一些可能性是:

"r1" - 值转到第 1 行中的所有列

"c2" - 值转到第 2 列中的所有行

"all" - 值转到所有元素

"3,2 1,1" - 值转到 (3,2) 和 (1,1)...如果它是 3x3 矩阵,也许您会想要省略模式中的逗号

您将必须使用一些 VBA 代码来实现它,至少一次,然后可以毫不费力地调用它很多次,并且调用代码非常容易理解。

您可能希望以字符串与空格连接的方式实现解析器,例如 "r1 c2",执行所有单独的分发。

那么,你的例子就变成了:

SetArray(MyArray, "1,1 2,2 1,3 3,2", 11)

编辑1

也可以将数组包装在自定义 class 实例中,以便调用变得更短:

Set aw = New MyArrayWrapper
MyArrayWrapper.array = MyArray

aw.SetArray("1,1 2,2 1,3 3,2", 11)
aw.SetArray("r1 c2", 12)

如果您不清楚,我会进行编辑。希望对您有所帮助!

我从来没有见过一种语言可以用矩形数组做到这一点,但它可以用数组数组更容易一点:

MyArray = Array( [{1, 2, 3}], [{4, 5, 6}], [{7, 8, 9}] )
MyArray(0)(1) = 11 
MyArray(1) = [Column(A1:A3)*0+13]   ' changes a whole "row" to 13
MyArray(2) = MyArray(1)             ' copies one row to another

交错数组的缺点(或优点)是每个数组可以有不同的大小(元素数量)甚至是 Nothing.

使用for循环将相同的值赋给多个变量:

myArray[valueOne, valueTwe, valueThree];

for(var i = 0; i < myArray.length; i++) {           
    myArray[i] = true;          
}