计算VBA中所选(大)范围内不同值的数量?

时间:2012-08-01 14:43:38

标签: algorithm excel vba excel-vba mergesort

如何计算VBA中所选(大)范围内的不同值(数字和字符串混合)的数量?

我以这种方式思考:
 1.将数据读入一维数组  2.排序数组(快速或合并排序)需要测试哪个  3.如果排序数组为if(a[i]<>a[i+1]) then counter=counter+1,则只需计算不同值的数量。

这是解决此问题的最有效方法吗?

编辑:我想在Excel中完成。

4 个答案:

答案 0 :(得分:7)

这是一个VBA解决方案

您无需使用数组即可完成此操作。您还可以使用集合。实施例

Sub Samples()
    Dim scol As New Collection

    With Sheets("Sheet1")
        For i = 1 To 100 '<~~ Assuming the range is from A1 to A100
            On Error Resume Next
            scol.Add .Range("A" & i).Value, Chr(34) & _
            .Range("A" & i).Value & Chr(34)
            On Error GoTo 0
        Next i
    End With

    Debug.Print scol.Count

    'For Each itm In scol
    '   Debug.Print itm
    'Next
End Sub

<强>后续

Sub Samples()
    Dim scol As New Collection
    Dim MyAr As Variant

    With Sheets("Sheet1")
        '~~> Select your range in a column here
        MyAr = .Range("A1:A10").Value

        For i = 1 To UBound(MyAr)
            On Error Resume Next
            scol.Add MyAr(i, 1), Chr(34) & _
            MyAr(i, 1) & Chr(34)
            On Error GoTo 0
        Next i
    End With

    Debug.Print scol.Count

    'For Each itm In scol
    '   Debug.Print itm
    'Next
End Sub

答案 1 :(得分:4)

您可以使用Scripting.Dictionary并将每个值添加到字典中,而不是第2步和第3步。任何重复的条目都会导致运行时错误,您可以捕获或忽略(resume next)。最后,您可以返回字典count,它将为您提供唯一条目的计数。

这是我急忙扔在一起的一段代码:

Function UniqueEntryCount(SourceRange As Range) As Long

    Dim MyDataset As Variant
    Dim dic As Scripting.Dictionary
    Set dic = New Scripting.Dictionary

    MyDataset = SourceRange

    On Error Resume Next

    Dim i As Long

    For i = 1 To UBound(MyDataset, 1)

        dic.Add MyDataset(i, 1), ""

    Next i

    On Error GoTo 0

    UniqueEntryCount = dic.Count

    Set dic = Nothing

End Function

我知道resume next可以被认为是'代码味道',但替代方法可能是使用字典的exists函数来测试指定的密钥是否已经存在然后添加值如果没有。我只是觉得,当我过去做类似的事情时,更快地忽略为重复键而不是使用exists YMMY引发的任何错误。为了完整起见,这是使用exists的另一种方法:

Function UniqueEntryCount(SourceRange As Range) As Long

    Dim MyDataset As Variant
    Dim dic As Scripting.Dictionary
    Set dic = New Scripting.Dictionary

    MyDataset = SourceRange

    Dim i As Long

    For i = 1 To UBound(MyDataset, 1)

        if not dic.Exists(MyDataset(i,1)) then dic.Add MyDataset(i, 1), ""

    Next i

    UniqueEntryCount = dic.Count

    Set dic = Nothing

End Function

虽然上面的代码比您提出的方法简单,但是根据您的解决方案测试它的性能是值得的。

答案 2 :(得分:3)

基于i_saw_drones提出的想法,我强烈推荐Scripting.Dictionary。但是,这可以在没有On Error Resume Next的情况下完成,如下所示。此外,他的示例需要链接Microsoft Scripting Runtime库。我的示例将演示如何执行此操作而无需进行任何链接。

此外,由于您在Excel中执行此操作,因此您根本不需要在步骤1中创建数组。下面的函数将接受一系列单元格,这些单元格将完全迭代。

(即UniqueCount = UniqueEntryCount(ActiveSheet.Cells)UniqueCount = UniqueEntryCount(MySheet.Range("A1:D100")

Function UniqueEntryCount(SourceRange As Range) As Long
    Dim MyDataset As Variant
    Dim MyRow As Variant
    Dim MyCell As Variant
    Dim dic As Object
    Dim l1 As Long, l2 As Long

    Set dic = CreateObject("Scripting.Dictionary")
    MyDataset = SourceRange

    For l1 = 1 To UBound(MyDataset)
        ' There is no function to get the UBound of the 2nd dimension 
        ' of an array (that I'm aware of), so use this division to 
        ' get this value. This does not work for >=3 dimensions!
        For l2 = 1 To SourceRange.Count / UBound(MyDataset)
            If Not dic.Exists(MyDataset(l1, l2)) Then
                dic.Add MyDataset(l1, l2), MyDataset(l1, l2)
            End If
        Next l2
    Next l1

    UniqueEntryCount = dic.Count
    Set dic = Nothing
End Function

值得注意的是,上述内容会将空字符串""计为不同的值。如果您不希望出现这种情况,只需将代码更改为:

    For l1 = 1 To UBound(MyDataset)
        For l2 = 1 To SourceRange.Count / UBound(MyDataset)
            If Not dic.Exists(MyDataset(l1, l2)) And MyDataset(l1, l2) <> "" Then
                dic.Add MyDataset(l1, l2), MyDataset(l1, l2)
            End If
        Next l2
    Next l1

答案 3 :(得分:0)

抱歉,这是用C#编写的。我就是这样做的。

// first copy the array so you don't lose any data
List<value> copiedList = new List<value>(yourArray.ToList());

//for through your list so you test every value
for (int a = 0; a < copiedList.Count; a++)
{
  // copy instances to a new list so you can count the values and do something with them
  List<value> subList = new List<value>(copiedList.FindAll(v => v == copiedList[i]);

  // do not do anything if there is only 1 value found
  if(subList.Count > 1)
                        // You would want to leave 1 'duplicate' in
    for (int i = 0; i < subList.Count - 1; i++)
        // remove every instance from the array but one
        copiedList.Remove(subList[i]);
}
int count = copiedList.Count; //this is your actual count

没有测试过,请尝试。

你应该将它包装在一个方法中,这样垃圾就不会乱七八糟了。否则,您将在稍后丢失该数组的副本。 (返回计数)

编辑:您需要一个列表才能使用它,使用Array.ToList();