来自一维数组的唯一值,无需迭代

Jvd*_*vdV 4 arrays excel vba unique filter

冒着成为话题的风险,我决定分享一些问答式的代码。如果一般意见认为这将是题外话,我很乐意在需要时删除。


背景

我们可以从任何一维数组或Range变成一维数组的对象中检索所有唯一值,而不必遍历其元素吗?就我而言,普遍的共识是必须迭代不同的元素,最好的方法是使用字典或集合来存储唯一值。是我发现非常有效的方法为了这个目的。


那么如何从一维数组中检索唯一元素,例如:

Dim arr As Variant: arr = Array("A", "A", "C", "D", "A", "E", "G")
Run Code Online (Sandbox Code Playgroud)

结果数组在哪里:

{"A", "C", "D", "E", "G"}
Run Code Online (Sandbox Code Playgroud)

Jvd*_*vdV 5

真正需要的所有代码只是几行:

Sub test()

Dim arr As Variant: arr = Array("A", "A", "C", "D", "A", "E", "G")
With Application
    uniques = .Index(arr, 1, Filter(.IfError(.Match(.Transpose(.Evaluate("ROW(1:" & UBound(.Match(arr, arr, 0)) & ")")), .Match(arr, arr, 0), 0), "|"), "|", False))
End With

End Sub
Run Code Online (Sandbox Code Playgroud)

以上将返回一个一维数组,返回原始数组中的所有唯一元素:

在此处输入图片说明


说明:

检索所有这些值的行看起来很紧张,所以让我们把它分成几部分:


在此处输入图片说明

Application.Match能够在其参数内处理数组。所以基本上我们正在看:.Match({"A","A","C","D","A","E","G"},{"A","A","C","D","A","E","G"},0)。返回的数组将是: {1,1,3,4,1,6,7},这实际上是找到每个值的第一个位置。这一结果将是我们进一步发展的基础。


在此处输入图片说明

我们可以看到第三个.Match在我们的代码,我们需要大致看出以下几点:.Match({1,2,3,4,5,6,7},{1,1,3,4,1,6,7},0)。第一个参数是上面高亮代码检索到的内容。

从哪里.Evaluate("ROW(1:" & UBound(.Match(arr, arr, 0)) & ")")返回一个值数组1-7Application.Transpose将返回它,使其成为一维数组。


在此处输入图片说明

最后一步将返回一个包含错误的数组,但是代码不会中断,因为我们使用的是Application代替WorksheetFunction. 结果数组看起来像{1,Error 2042,3,4,Error 2042,6,7}. 现在的重点是摆脱这些Error价值观。

这样做的方法是通过Application.IfError,它将评估数组并将所有错误值更改为给定的字符串值。在我们的例子中,我使用了管道符号。由用户决定一个足够独特的符号,它不会出现在原始数组的任何元素中。所以经过评价。我们目前的阵列将是这样的:{1,|,3,4,|,6,7}


在此处输入图片说明

现在我们检索了一个包含管道符号的数组,我们希望它们出来!一个快速的方法是使用Filter函数。Filter返回一个包含或不包含符合我们条件的元素的数组(取决于第三个参数中的TRUEFALSE)。

所以基本上我们要返回一个数组,像这样:Filter(<array>, "|", False)。得到的一维数组现在看起来像:{1,3,4,6,7}


在此处输入图片说明

我们在这一点上有点。我们只需要从原始数组中切出正确的值。为此,我们可以使用Application.Index. 我们只想知道.Index我们对哪些行感兴趣。为此我们可以加载我们之前找到的一维数组。所以代码看起来像:.Index(arr1, <array>, 1)这将产生一个一维数组:{"A","C","D","E","G"}


结论:

你有它。一行(不仅仅是一个操作)从另一个一维数组中检索唯一值的一维数组,无需迭代。此代码已准备好用于任何用 声明的一维数组arr

有用吗?我不是 100% 确定,但我终于达到了我在项目中尝试的目标。生成的数组可以立即用于您需要在其中使用唯一值的任何任务。

比较:字典与应用程序。方法:

对 中的随机项目进行比较Range(A1:A50000),性能确实受到了打击。因此,迭代字典与Application.Methods1000 个项目步骤中的非迭代方法之间的时间比较。低于 1000 项和每 10000 项标记的结果(以秒为单位):

| Items     | Dictionary    | Methods       |
|-------    |------------   |-------------  |
| 1000      | 0,02          | 0,03          |
| 10000     | 0             | 0,88          |
| 20000     | 0,02          | 3,31          |
| 30000     | 0,02          | 7,3           |
| 40000     | 0,02          | 12,84         |
| 50000     | 0,03          | 20,2          |
Run Code Online (Sandbox Code Playgroud)

使用的Dictionary方法:

Sub test()

Dim arr As Variant: arr = Array("A", "A", "C", "D", "A", "E", "G")
With Application
    uniques = .Index(arr, 1, Filter(.IfError(.Match(.Transpose(.Evaluate("ROW(1:" & UBound(.Match(arr, arr, 0)) & ")")), .Match(arr, arr, 0), 0), "|"), "|", False))
End With

End Sub
Run Code Online (Sandbox Code Playgroud)

结论:最多 1000 个项目,与更常见的Dictionary做法相比,此方法的处理时间大致相同。在任何更大的事情上,迭代(通过内存)总是会击败方法方法!

我敢肯定,使用@ScottCraner所示的新动态数组函数,处理时间会受到更多限制。

  • 恕我直言,使用字典的解决方案更加清晰和简单。即使您提供的链接中的代码也可以缩短并且会变得清晰。所以我不会在这篇文章中寻求这个解决方案。 (3认同)
  • 很好的工作,并且有非常清晰的解释。你正在整理一个很好的图书馆——也许你可以把它们放在某个地方。 (2认同)

Sco*_*ner 5

使用新的动态数组函数,可以将其简化为:

Sub test()

Dim arr As Variant: arr = Array("A", "A", "C", "D", "A", "E", "G")
With Application
    Dim uniques as variant
    uniques = .Transpose(.Unique(.Transpose(arr)))
End With

End Sub
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

新的Uniques Formula需要一个垂直数组,并且可以是2d。它的行为就像Range.RemoveDuplicate无法选择列一样。