在VBA中選擇(大)范圍內不同值的個數?

[英]Count number of different values in chosen (large) range in VBA?


How can I count the number of different values (numbers and strings mixed) in a chosen (large) range in VBA?

如何計算VBA中選擇的(大)范圍內不同值(數字和字符串混合)的數量?

I think about this in this way:
1. Read in data into one dimensional array.
2. Sort array (quick or merge sort) need to test which
3. Simply count number of different values if sorted array : if(a[i]<>a[i+1]) then counter=counter+1.

我這樣想:1。將數據讀入一維數組。2。排序數組(快速排序或合並排序)需要測試哪一個。如果排序后的數組是:if(a[i]<>a[i+1]),那么counter=counter+1。

Is it the most efficient way to solve this problem?

這是解決這個問題最有效的方法嗎?

Edit: I want to do it in Excel.

編輯:我想用Excel來做。

4 个解决方案

#1


7  

Here is a VBA Solution

這是VBA解決方案

You don't need an Array to get this done. You can also use a collection. Example

你不需要一個數組來完成這個。您還可以使用集合。例子

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

FOLLOWUP

跟蹤

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

#2


4  

Instead of steps 2 and 3, perhaps you could use a Scripting.Dictionary and add each value to the dictionary. Any duplicate entries would cause a runtime error which you could either trap or ignore (resume next). Finally, you could then just return the dictionary's count which would give you the count of unique entries.

也許可以使用腳本而不是步驟2和步驟3。並將每個值添加到字典中。任何重復的條目都會導致運行時錯誤,您可以捕獲或忽略它(接下來繼續)。最后,您可以返回字典的計數,它將給出唯一條目的計數。

Here's a scrap of code I hurriedly threw together:

這是我匆忙拼湊的一段代碼:

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

I know that resume next can be considered a 'code smell', but the alternative could be to use the exists function of the dictionary to test whether the specified key already exists and then add the value if did not. I just have a feeling that when I did a similar thing in the past that it was faster to just ignore any errors raised for duplicate keys rather than using exists YMMY. For completeness, here's the other method using exists:

我知道resume next可以被認為是一種“代碼味道”,但另一種選擇是使用字典的exist函數來測試指定的鍵是否已經存在,然后在不存在時添加值。我有一種感覺,當我以前做過類似的事情時,忽略重復鍵引起的錯誤比使用現有的YMMY更快。為了完整性,這里有另一種使用的方法:

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

Whilst the above code is simpler than your proposed method, it would be worth to test the performance of it against your solution.

雖然上面的代碼比您所建議的方法簡單,但是測試它對您的解決方案的性能是值得的。

#3


3  

Building on the idea presented by i_saw_drones, I strongly recommend the Scripting.Dictionary. However, this can be done without On Error Resume Next as shown below. Also, his example requires linking the Microsoft Scripting Runtime library. My example will demonstrate how to do this without needing to do any linking.

基於i_saw_drone提出的想法,我強烈推薦script.com . dictionary。但是,這可以在沒有錯誤恢復的情況下完成,如下所示。此外,他的示例還需要鏈接Microsoft腳本運行時庫。我的示例將演示如何在不需要進行任何鏈接的情況下執行此操作。

Also, since you're doing this in Excel, then you don't need to create the array in step 1 at all. The function below will accept a range of cells, which will be iterated through completely.

而且,由於您是在Excel中進行此操作,所以您根本不需要在步驟1中創建數組。下面的函數將接受一系列單元格,這些單元格將被完全迭代。

(i.e. UniqueCount = UniqueEntryCount(ActiveSheet.Cells) or UniqueCount = UniqueEntryCount(MySheet.Range("A1:D100"))

(即unique ecount = UniqueEntryCount(ActiveSheet.Cells)或uniquecentrycount (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

It might also be important to note that the above will count a null string "" as a distinct value. If you do not want this to be the case, simply change the code to this:

還需要注意的是,上面將null字符串“”作為一個不同的值。如果您不希望這種情況發生,只需將代碼更改為:

    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

#4


0  

Sorry this is written in C#. This is how I would do it.

對不起,這是用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

Have not tested it, please try.

沒有測試過,請嘗試。

You should wrap this inside a method so there is no messing around with the garbage. Otherwise you would lose the copy of the array only later. (return count)

你應該把它包裝在一個方法中,這樣就不會浪費你的垃圾了。否則,稍后您將丟失數組的副本。(返回計數)

EDIT: You need a list for this to work, use Array.ToList();

編輯:您需要一個列表來讓它工作,使用Array.ToList();


注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:https://www.itdaan.com/blog/2012/08/01/72f0d007e6873d5b3fe2a2ff7812ab83.html



 
粤ICP备14056181号  © 2014-2021 ITdaan.com