Excel 列出需要较长时间处理的唯一值公式
我有一个来自上一个问题的公式,它很好用。它列出了从B2开始的动态列A到列B的唯一值。通常A列有几千个值,因此处理需要很长时间。增加计算线程并没有节省很多时间。我正在寻找一个更好的方法或公式,可以节省我很多时间Excel 列出需要较长时间处理的唯一值公式,excel,excel-formula,Excel,Excel Formula,我有一个来自上一个问题的公式,它很好用。它列出了从B2开始的动态列A到列B的唯一值。通常A列有几千个值,因此处理需要很长时间。增加计算线程并没有节省很多时间。我正在寻找一个更好的方法或公式,可以节省我很多时间 =IFERROR(索引(A:A;聚合(15;6;行($A$2:INDEX(A:A;MATCH($zzz;A:A)))/(COUNTIF($B$1:B1;$A$2:INDEX(A:A;MATCH($zzz;A:A)))=0;1));“”) 如您问题的评论中所述,使用新的“预官方”唯一函数或透
=IFERROR(索引(A:A;聚合(15;6;行($A$2:INDEX(A:A;MATCH($zzz;A:A)))/(COUNTIF($B$1:B1;$A$2:INDEX(A:A;MATCH($zzz;A:A)))=0;1));“”)
如您问题的评论中所述,使用新的“预官方”唯一函数或透视表可能是获取唯一值的最简单、最快速的方法。但是,如果要使用不需要按下按钮或使用更新版本的Excel的VBA函数,您可能需要尝试下面介绍的VBA函数“GetUniques” 这是如何使用该函数的示例: 要使用该功能,必须做3件事:
ArrayList
”类(这比使用Collection
类更容易),是因为它附带了排序方法(否则,我们必须实现快速排序过程)。为了避免后期绑定,我在VBA编辑器中添加了对此库的引用(位于我的计算机上的“C:\Windows\Microsoft.NET\Framework\v4.0.30319”)。有关如何使用此类的详细信息,请转到下面的链接:
VBA函数实际上在调用它的公式单元格之外写入值。由于Excel不能很好地实现这一点,因此需要一种变通方法。我尝试使用“应用程序.评估”
”方法作为一种变通方法,这在很多地方都有建议,但由于某种原因,它对我不起作用。因此,我被迫使用SheetCalculate事件(正如在其他地方建议的那样)。简言之,函数本身不在调用方单元格外写入值,而是在“准队列”中为其保留一个“请求”,然后在Excel处理SheetCalculate事件时对其进行处理;此事件将在VBA函数完成执行后触发。此函数将第一个值写入公式单元格本身,其余值直接写入公式单元格下方
“GetUniques”函数有两个参数:
Option Explicit
'This is the "commands queue" that is filled up in this module and is "executed" during the SheetCalculate event
Public ExtraCalcCommands As New Collection
Function GetUniques(ByVal dataRange As Range, Optional ByVal dataType As String = "") As Variant
'Attempt to remove unused cells from the data range to make it smaller
Dim dataRng As Range
Set dataRng = Application.Intersect(dataRange, dataRange.Worksheet.UsedRange)
'If the range is completely empty, simply exit
If dataRng Is Nothing Then
GetUniques = ""
Exit Function
End If
'Read in all the data values from the range
Dim values As Variant: values = dataRng.value
'If the values do not form an array, it is a single value, so just return it
If Not IsArray(values) Then
GetUniques = values
Exit Function
End If
'Get the 2-dimensional array's bounds
Dim arrLb As Long: arrLb = LBound(values, 1)
Dim arrUb As Long: arrUb = UBound(values, 1)
Dim index2 As Long: index2 = LBound(values, 2) 'In the 2nd dimension, we only
' care about the first column
'Remember the original number of values
Dim arrCount As Long: arrCount = arrUb - arrLb + 1
'Since [values] is an array, we know that arrCount >= 2
Dim i As Long
'Using ArrayList based on ideas from https://excelmacromastery.com/vba-arraylist
'Copy the values to an ArrayList object, discarding blank values and values
' that cannot be converted to the desired data type (if one was specified)
Dim valuesList As New ArrayList
Dim arrValue As Variant
For i = arrLb To arrUb
arrValue = values(i, index2)
If (arrValue & "") = "" Then
'Skip blank values
ElseIf Not CouldConvert(arrValue, dataType) Then
'This conversion may be necessary to ensure that the values can be compared against each other during the sort
Else
valuesList.Add arrValue
End If
Next
Dim valuesCount As Long: valuesCount = valuesList.Count
'Sort the list to easily remove adjacent duplicates
If Not CouldSort(valuesList) Then
GetUniques = "#ERROR: Could not sort - consider using the data type argument"
Exit Function
End If
'Remove duplicates (which are now adjacent due to the sort)
Dim previous As Variant
If valuesCount > 0 Then previous = valuesList.Item(0)
Dim current As Variant
i = 1
Do While i < valuesCount
current = valuesList.Item(i)
If ValuesMatch(current, previous, dataType) Then 'Remove duplicates
valuesList.RemoveAt i
valuesCount = valuesCount - 1
Else
previous = current
i = i + 1
End If
Loop
'Replace the removed items with empty strings at the end of the list
' This is to get back to the original number of values
For i = 1 To arrCount - valuesCount
valuesList.Add ""
Next
'Return the first value as the function result
GetUniques = valuesList.Item(0) 'We know valuesList.Count=arrCount>=2
'Write the rest of the values below
valuesList.RemoveAt 0
WriteArrayTo valuesList, Application.Caller.Offset(1, 0)
End Function
Private Function CouldSort(ByRef valuesList As ArrayList)
On Error Resume Next
valuesList.Sort
CouldSort = Err.Number = 0
End Function
Private Function CouldConvert(ByRef value As Variant, ByVal dataType As String)
CouldConvert = True
If dataType = "" Then Exit Function
On Error Resume Next
Select Case dataType
Case "L": value = CLng(value)
Case "F": value = CDbl(value)
Case "D": value = CDate(value)
Case "S", "S2": value = value & ""
End Select
CouldConvert = Err.Number = 0
End Function
Private Function ValuesMatch(ByVal v1 As Variant, ByVal v2 As Variant, ByVal dataType As String) As Boolean
On Error Resume Next
Select Case dataType
Case "S": ValuesMatch = StrComp(v1, v2, vbTextCompare) = 0
Case "S2": ValuesMatch = StrComp(v1, v2, vbBinaryCompare) = 0
Case Else: ValuesMatch = v1 = v2
End Select
If Err.Number <> 0 Then ValuesMatch = False
End Function
Private Sub WriteArrayTo(ByVal list As ArrayList, ByRef destination As Range)
'This procedure does not do the actual writing but saves the "command" to do the writing in a "queue";
' this "commands queue" will be executed in the SheetCalculate event;
'We cannot write to cells outside the UDF's formula whilst the function is being calculated
' because of Excel restrictions; that is why we must postpone the writing for later
Dim coll As New Collection
coll.Add "DoWriteList" 'Name of the procedure to execute
coll.Add destination '1st argument used by the procedure
coll.Add list '2nd argument used by the procedure
ExtraCalcCommands.Add coll
End Sub
我希望以上内容能有所帮助,如果是的话,我希望这是对原始IFERROR公式的一种速度改进。我还希望SheetCalculate事件处理程序不会在包含许多公式和计算的密集工作簿中引起问题。如果您可以通过Office Insiders程序访问它,您可以使用。否则VBA可能会更快。还是仅仅删除重复?一个数据透视表?电力查询?还是VBA…高级过滤器?@JvdV我创建了一个数据透视表,计算时间现在可能快了10倍。唯一的缺点是,它需要单击刷新按钮来更新透视表。我可以接受。非常感谢。
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
Dim i&
Do While ExtraCalcCommands.Count > 0
Dim cmdColl As Collection: Set cmdColl = ExtraCalcCommands.Item(1)
Select Case cmdColl.Item(1)
Case "DoWriteList": DoWriteList cmdColl.Item(2), cmdColl.Item(3)
'Other procedure names could go here in future
End Select
'Remove the processed "command" from the queue
ExtraCalcCommands.Remove 1
Loop
End Sub
Private Sub DoWriteList(ByRef destination As Range, ByVal list As ArrayList)
destination.Resize(list.Count, 1).value = WorksheetFunction.Transpose(list.ToArray)
End Sub