2012-07-12 363 views
2

目前我有一個宏,它貫穿列表並刪除重複值(在一列中),但它證明效率很低。對於每個檢查重複項的條目,它必須遍歷整個列;我的文件目前有50,000個條目,這不是一個小任務。Excel宏陣列

我認爲宏工作的一個更簡單的方法是讓宏檢查這個值是否在數組中。如果是,則刪除條目所在的行。如果不是,則將該值添加到數組中。

有人可以提供一些關於宏的基本輪廓的幫助嗎?謝謝

+0

將數組中的值複製到新工作表。然後在50k條目旁邊插入一個空白列並做一個查找。完成後,執行一次自動篩選,然後刪除重複條目。 – 2012-07-12 17:11:03

+0

還有一個選擇:循環訪問數組,並在50k上做一個自動過濾器,並簡單地將其逐個刪除。一個比上述更慢的過程... – 2012-07-12 17:13:53

+0

「刪除」是指刪除單元格內容,留下一個空白單元格,還是意味着刪除該值並將所有其他值移動到一個單元格上?這對答案的複雜性和速度都有很大的影響。 (擦除更簡單/更快)。 – RBarryYoung 2012-07-12 18:18:38

回答

3

下面的代碼將遍歷源數據並將其存儲在一個數組中,同時檢查重複項。收集完成後,它使用該數組作爲關鍵字來知道要刪除哪些列。

由於刪除的電位器屏幕更新次數很多,因此請務必關閉屏幕更新。 (含)

Sub Example() 
    Application.ScreenUpdating = false 
    Dim i As Long 
    Dim k As Long 
    Dim StorageArray() As String 
    Dim iLastRow As Long 
    iLastRow = ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlUp).Row 

    ReDim StorageArray(1 To iLastRow, 0 To 1) 

    'loop through column from row 1 to the last row 
    For i = 1 To iLastRow 
     'add each sheet value to the first column of the array 
     StorageArray(i, 0) = ActiveSheet.Range("A" & i).Value 
     '- keep the second column as 0 by default 
     StorageArray(i, 1) = 0 
     '- as each item is added, loop through previously added items to see if its a duplicate 
     For k = 1 To i-1 
      If StorageArray(k, 0) = StorageArray(i, 0) Then 
       'if it is a duplicate set the second column of the srray to 1 
       StorageArray(i, 1) = 1 
       Exit For 
      End If 
     Next k 
    Next i 

    'loop through sheet backwords and delete rows that were maked for deletion 
    For i = iLastRow To 1 Step -1 
     If StorageArray(i, 1) = 1 Then 
      ActiveSheet.Range("A" & i).EntireRow.Delete 
     End If 
    Next i 

    Application.ScreenUpdating = true 
End Sub 

按照要求,在這裏做一個類似的方式,使用集合而不是爲關鍵索引數組:(RBarryYoung)

Public Sub RemovecolumnDuplicates() 
    Dim prev as Boolean 
    prev = Application.ScreenUpdating 
    Application.ScreenUpdating = false 
    Dim i As Long, k As Long 

    Dim v as Variant, sv as String 
    Dim cl as Range, ws As Worksheet 
    Set ws = ActiveWorksheet 'NOTE: This really should be a parameter ... 

    Dim StorageArray As New Collection 
    Dim iLastRow As Long 
    iLastRow = ws.Cells(ActiveSheet.Rows.Count, "A").End(xlUp).Row 

    'loop through column from row 1 to the last row 
    i = 1 
    For k = 1 To iLastRow 
     'add each sheet value to the collection 
     Set cl = ws.Cells(i, 1) 
     v = cl.Value 
     sv = Cstr(v) 

     On Error Resume Next 
      StorageArray.Add v, sv 
     If Err.Number <> 0 Then 
      'must be a duplicate, remove it 
      cl.EntireRow.Delete 
      'Note: our index doesn't change here, since all of the rows moved 
     Else 
      'not a duplicate, so go to the next row 
      i = i + 1 
     End If 
    Next k 

    Application.ScreenUpdating = prev 
End Sub 

注意,此方法不不需要爲該列中的單元格的值假定任何數據類型或整數限制。

(過失:我不得不在記事本中手工輸入這一點,因爲我的Excel是忙碌的運行項目的測試,現在所以可能有一些拼寫/語法錯誤...。)

+0

這非常接近正確/最佳答案。您不應該假設範圍也是活動工作表,您應該保存然後恢復當前的屏幕更新設置,最重要的是,您應該使用哈希或索引(集合)來檢查是否存在,而不是 - 掃描整個StorageArray。如果你願意,我可以爲你做出這些改變。 – RBarryYoung 2012-07-12 18:15:40

+0

另外,我剛剛注意到,您正在刪除整行,但OP指定只修改一列。 – RBarryYoung 2012-07-12 18:22:40

+0

感謝您的幫助;我認爲這是最接近於工作的方式,但不是通過表格中的每個值來運行,我怎樣才能讓它只運行一列? – user1521458 2012-07-12 18:30:31

0

我會建議填充你的列,然後使用公式來找到重複項並刪除它們。我沒有爲你的實際代碼(你沒有給我們任何代碼)

dim a as range 
dim b as range 
set a = Range ("A1") 

Do while Not isEmpty(A) 
Set b = a.offset(1,0) 

If b = a then 
b= "" 
else a.offset (1,0) 

Loop 

我相信你可以把過濾器中的代碼,或只是你的rember運行宏之前fillter。

+1

這似乎只是檢查列表中的先前值的範圍值,而列表中的任何兩個條目之間可能會出現重複。另外聲明「else a.offset(1,0)」對我來說看起來像一個bug。 – RBarryYoung 2012-07-12 18:28:04

+0

我試圖保持簡單,並假設重複將按順序進行過濾。我現在很確定我的IF聲明不會正確起作用。 – Nick 2012-07-12 20:18:24

1

這是一個後續對我的評論。 循環50k記錄 + 循環陣列將是一個這樣一個簡單的操作過度殺死。

就像我在我的評論中提到的,將數組中的值複製到新工作表中。然後在50k條目旁邊插入空白列並執行VlookupCountIf。完成後,執行一次自動篩選,然後刪除重複條目。我們來舉個例子,看看它是如何工作的。

假設我們有一個包含1000個項目的數組?在1頁中我們有50k數據。下面的代碼將與1000 items in Array50k Data見快照

enter image description here

此代碼的模塊在粘貼進行測試(的代碼了不到5秒以完成

enter image description here

Sub Sample() 
    Dim ws As Worksheet, wstemp As Worksheet 
    Dim LRow As Long 
    Dim Ar(1 To 1000) As Long 
    Dim startTime As String, EndTime As String 

    startTime = Format(Now, "hh:mm:ss") 

    Set ws = Sheets("Sheet1") 
    Set wstemp = Sheets.Add 

    '~~> Creating a dummy array 
    For i = 1 To 1000 
     Ar(i) = i 
    Next i 

    '~~> Copy it to the new sheet 
    wstemp.Range("A1:A1000").Value = Application.Transpose(Ar) 

    With ws 
     LRow = .Range("A" & .Rows.Count).End(xlUp).Row 

     .Columns(2).Insert Shift:=xlToRight 
     .Range("B1").Value = "For Deletion" 
     .Range("B2:B" & LRow).FormulaR1C1 = "=COUNTIF(" & wstemp.Name & "!C[-1],RC[-1])" 
     .Columns(2).Value = .Columns(2).Value 

     '~~> Remove any filters 
     .AutoFilterMode = False 

     '~~> Filter, offset(to exclude headers) and delete visible rows 
     With .Range("B1:B" & LRow) 
      .AutoFilter Field:=1, Criteria1:="<>0" 
      .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete 
     End With 

     '~~> Remove any filters 
     .AutoFilterMode = False 

     .Columns(2).Delete 
    End With 

    EndTime = Format(Now, "hh:mm:ss") 

    MsgBox "The process started at " & startTime & " and finished at" & EndTime 
End Sub 
+0

這似乎既緩慢又過於複雜。 – RBarryYoung 2012-07-12 18:16:37

+0

5秒慢?你覺得哪個部分很複雜? :) – 2012-07-12 18:18:43

+0

是的抱歉,我現在正在嘗試它。謝謝你的幫助! – user1521458 2012-07-12 18:36:55

1

對於Excel 2007及更高版本:將數組複製到工作表並使用removeduplicates方法:

set ws = worksheets.add 
ws.[A1].resize(ubound(yourarray,1),ubound(yourarray,2)).value = yourarray 
ws.usedrange.removeduplicates columns:=1, header:=no 

這裏假定數組的下限爲1,表示要刪除的列是列1,並且列表中沒有標題。然後可以找到新範圍的邊界並將其讀回到陣列中(首先擦除當前陣列)。