2012-08-01 73 views
5

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

我這樣想:
1.將數據讀入一維數組。
2.排序陣列(快速或排序合併)需要測試,如果排序陣列
3.只需計數不同值的數目:if(a[i]<>a[i+1]) then counter=counter+1

它是解決這一問題的最有效方法是什麼?

編輯:我想這樣做在Excel中。

+1

您可以加載範圍轉換爲二維數組,然後循環儘管它並使用腳本字典來檢查的獨特性。你完成後,字典有你的計數。 – 2012-08-01 15:08:01

+0

@TimWilliams你打敗了我,完全是我的想法:) – 2012-08-01 15:14:55

+0

三個答案 - 不錯,我會檢查他們,並在週五選擇一個。謝謝 – Qbik 2012-08-01 15:46:49

回答

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 
+0

+1很高興地補充說你*不需要任何特殊的庫來使用'Collection'對象,這可以讓事情變得更容易。 :-) – Gaffi 2012-08-01 17:29:53

+3

+1好答案!迭代對象(比如Range對象)和數組仍然很慢,因此複製到一個變體數組然後添加到集合中* *更快*(對不起,我是一個Excel性能極客!) – 2012-08-01 22:03:29

+0

@i_saw_drones好點的你想優化它? – Qbik 2012-08-02 07:40:47

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();

+0

如果(subArray.count> 1)檢查不是必需的,則for循環會解決它。 – AmazingDreams 2012-08-01 15:00:39

+2

在C#幫助中如何回答VBA問題? ;) – 2012-08-01 15:17:59

+0

代碼可以'翻譯'正確 – AmazingDreams 2012-08-01 15:21:53

4

代替步驟2和3,也許你可以使用一個Scripting.Dictionary每個值添加到字典中。任何重複的條目都會導致運行時錯誤,您可能會陷入或忽略(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 

雖然上面的代碼比你提出的方法更簡單,這將是值得來測試它的性能對您的解決方案。

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 
+1

從性能角度來看,我不會推薦遍歷每個單元格(即對象)並對變體執行隱式類型強制,因爲循環遍歷對象的計算量很大。這就是爲什麼將它強制轉換爲數組並循環遍歷數組的更高性能。微軟也建議這樣做:http://msdn.microsoft.com/en-us/library/office/ff726673.aspx - 標題爲「在單一操作中讀寫大塊數據」的部分 – 2012-08-01 16:36:23

+0

@i_saw_drones我同意。 :-)我只是認爲我會把它作爲一個選項扔出去。我也想剽竊你儘可能少的東西。 ;-) – Gaffi 2012-08-01 17:02:32

+0

@i_saw_drones是的,您可以執行二維數組強制,這可以在我的函數版本中完成(更新我的答案),而不必將一維數組/範圍傳遞給函數。 – Gaffi 2012-08-01 17:41:56