【问题标题】:VBA Collections Increase Speed: Matching Two Lists, Find What Doesn't MatchVBA 集合提高速度:匹配两个列表,找出不匹配的
【发布时间】:2016-06-08 19:29:54
【问题描述】:

我必须处理大量 Excel 工作表(第 7500 和 16000 行)。我需要查看列表一中的哪些项目不在列表二中...以及列表二中的哪些项目不在列表一中,然后将这些结果粘贴到第三张纸上。

我决定将这两个列表存储在两个集合中。到目前为止,效果很好。当我尝试遍历集合以查找不匹配的内容时,我的计算机因文件太大而冻结。

如何更改我的代码以使其更快?我觉得必须有更好的方法来做到这一点,而不是循环遍历列表一中的每个 i 和列表二中的每个 z。

谢谢!

    Sub FullListCompareFSvDF()
Worksheets("FundserveFL").Activate
'Open New Collection and define every variable
Dim FSTrades As New Collection
Dim c As Long
Dim i As Long
Dim z As Long
Dim searchFor As String

'enter the items into the list. There are blank rows and so the first IF Statement is to ignore these.
' The Else Statement shows an account number as the item and an account number & balance (FS.Offset(0,6).Value) as the key

Dim FS As Range
 For Each FS In Sheet1.Range("L:L")
    If FS = "" Then
    Else: FSTrades.Add CStr(FS.Value & " " & FS.Offset(0, 6).Value)
    End If
 Next

Worksheets("DatafileFL").Activate
Dim DFTrades As New Collection

'enter the items into the list. There are blank rows as well as random numbers  and so the first IF Statement is to ignore these (all account numbers are greater than 10000
'"Matching" is displayed for all errors - during an error read the account number from two columns over.
' The Else Statement shows an account number as the item and an account number & balance (FS.Offset(0,6).Value) as the key

Dim DF As Range
    For Each DF In Sheet2.Range("H:H")
    If DF = "" Or Not IsNumeric(DF.Offset(0, 2)) Or DF < 10000 Then
    ElseIf DF.Offset(0, -4) = "MATCHING" Then
    DFTrades.Add CStr(DF.Offset(0, 2).Value & " " & DF.Value)
    Else:
    DFTrades.Add CStr(DF.Value & " " & DF.Offset(0, -2).Value)
    End If
Next

'loop through the first collection. Find the first item and try to match it with the items in the second collection.
'Collection 1 Item 1... is it in Collection 2 Item 1? No - then is it in Collection 2 Item 2? When a match is found, move on to Collection 1 Item 2... If no match is found send the item to "ForInvestigation" worksheet

For i = 1 To FSTrades.Count
searchFor = FSTrades(i)
z = 0
    Do
        z = z + 1
        If z > DFTrades.Count Then
        c = c + 1
        Worksheets("ForInvestigation").Activate
        Cells(c, 1).Value = DFTrades(i)
        Exit Do
            Else:
                If DFTrades(z) = searchFor Then
                    Exit Do
                End If
        End If
    Loop
Next

'Clear Collections
Set FSTrades = Nothing
Set DFTrades = Nothing

End Sub

【问题讨论】:

  • 一方面,你为什么要遍历整个H:H 范围?这不会遍历H列的所有数百万行,其中大部分都是空白的吗?与L:L 相同。
  • 嗨,Marc - 是的,我正在循环整个范围。我不确定如何避免这种情况。你有什么想法?请记住,该列表不是一成不变的。它有断断续续的空白行,每次使用宏时列表的大小都不同。
  • 谢谢大卫 - 我正在阅读这篇文章。如果可能的话,我将在下面与 Ron 的答案一起实施。
  • @DavidZemens 好点。其他列中的数据可能低于 L 列中的最后一个条目。

标签: arrays excel performance vba list


【解决方案1】:

我有一个类似大小的东西列表,我经常需要创建一个唯一的值列表。不过,我不确定您为什么要同时使用两个集合。将数据从一张表加载到集合中要简单得多,然后遍历另一张表以查看它是否已存在于集合中。这是我的一些代码,可以帮助您编写自己的代码。

Dim colUniqueSNs As New Collection
On Error Resume Next
    For r = 2 To Sheets("Inventory").UsedRange.Rows.Count
        strSN = Sheets("Inventory").Cells(r, 6).Text
        strHost = Sheets("Inventory").Cells(r, 2).Text
        If Not InCollection(colUniqueSNs, strSN) Then colUniqueSNs.Add strHost, strSN
    Next
On Error GoTo 0

Public Function InCollection(col As Collection, key As Variant) As Boolean
    Dim obj As Variant
    On Error GoTo err
    InCollection = True
    obj = col(key)
    Exit Function
err:
    InCollection = False
End Function

【讨论】:

  • 嗨蒂姆-谢谢你。我目前正在测试你的答案,罗恩的答案和德克的答案。在您的回答中,您如何将集合粘贴到工作表上?我问是因为使用 Ron 的回答我能够得到我需要的集合,但现在我不知道如何将它复制到工作表上。
  • 使用For 循环。例如。 For x=1 to colUniqueSNs.Count(下一行)Sheet1.Cells(x+1,1).Value = colUniqueSNs.Item(x)(下一行)Next
  • 啊好吧,这看起来很简单。我没有意识到我可以像这样将物品发送到单元格中。
  • Item(x) 有很多属性,但 value 是它的默认属性。 技术上我应该写Item(x).Value,但是嗯。罗恩的回答也很好。我们基本上在做同样的事情。请注意,如果您遇到内存问题或者它仍然很慢,您可能需要放弃设置该范围变量。变体是启动速度慢的数据类型和内存占用者,但当您不知道(或不想知道)您正在使用的数据类型时,它们非常有用。
【解决方案2】:
  • 不要Activate
  • 一步将所有相关单元格读入一个变量数组。例如:

Dim V As Variant
With Worksheets("FundserveFL")
    V = .Range("L1", .Cells(.Rows.Count, "L").End(xlUp)).Resize(columnsize:=6)
End With

  • 为您的集合创建一个可用于查看是否存在重复项的密钥。

On Error Resume Next
 For i = 1 To UBound(V, 1)
    If V(i, 1) <> "" Then
        FSTrades.Add Item:=CStr(V(i, 1) & " " & V(i, 6)), Key:=CStr(V(i, 1) & " " & V(i, 6))
    End If
 Next i
 On Error Resume Next

如果您类似地处理第二个工作表上的数据,创建一个数组,在创建一个键后将其添加到同一个集合中没有重复。使用该集合填充一个数组,并将其写入您的第三个工作表。

我猜想使用上述技术至少可以将你的速度提高十倍,甚至更多。

编辑:

如果你想做一些不同于唯一列表的事情,这只是理解逻辑的问题。例如,如果如您的评论中所述,您有两个数组 1,2,3,4 和 1,3,4,5,您可以执行以下操作。当然,要理解一个假设是两个数组中都没有重复:(如果有,也可以处理,只需要不同的逻辑)


Sub foo()
    Dim V1, V2
    Dim COL As Collection
    Dim I As Long

V1 = Array(1, 2, 3, 4)
V2 = Array(1, 3, 4, 5)

Set COL = New Collection
For I = 0 To UBound(V1)
    COL.Add V1(I), CStr(V1(I))
Next I

On Error Resume Next
For I = 0 To UBound(V2)
    COL.Add V2(I), CStr(V2(I))
    Select Case Err.Number
        Case 457  'This is a duplicate, so will remove
            Err.Clear
            COL.Remove CStr(V2(I))
        Case Is <> 0
            MsgBox "Error No. " & Err.Number & vbTab & Err.Description
    End Select
Next I

Stop

End Sub

当例程停止时,如果您检查COL,您会发现它只包含 2 和 5

【讨论】:

  • 嗨罗恩 - 谢谢你,这太棒了!一件事:我不是在寻找不包含重复项的集合。我正在寻找一个存在于 1 个列表中但不存在于第二个列表中的集合,反之亦然。看来您的答案是制作一个“主”列表。如果我有列表 1、2、3、4 和列表 1、3、4、5。我需要显示 2 和 5 的第三个列表。也许有一个“错误删除列表项”?
  • @Jonh 您只需操纵集合添加例程和错误,以便在存在重复项时删除该项目。查看我的帖子的编辑
  • @Jonh 如果我的回复对您有足够的帮助,请将其标记为答案。谢谢。
  • 这是一个很好的答案——它教会了我很多关于数组的力量的知识。谢谢。
  • 嗨罗恩,对不起,经过测试,我发现它不起作用。在上面的示例中考虑以下数组 - V1 = Array(1, 2, 3, 4, 5) V2 = Array(1, 3, 4, 5, 6)。它应该删除 5 但它没有。这样做的原因是,您的方法仅在列表 2 中的重复项处于相同的 I 间隔时才有效。列表中的重复项可能从匹配项向下迭代 100 次。有什么好的方法可以测试吗?
【解决方案3】:

你从范围开始,你以它们结束。完全跳过集合怎么样?

请试试这个:

Sub FullListCompareFSvDF()

  Dim Ran1Val As Variant, Ran1ValOffset As Variant, Ran2Val As Variant
  Ran1Val = Intersect(Sheet1.Columns(12), Sheet1.UsedRange).Value
  Ran2Val = Intersect(Sheet1.Columns(18), Sheet1.UsedRange).Value

  Dim i As Long, j As Long
  For i = 1 To UBound(ranval1)
    If Len(Ran1Val(i, 1)) Then Ran1Val(i, 1) = Ran1Val(i, 1) & " " & Ran2Val(i, 1)
  Next

  Ran2Val = Intersect(Sheet2.Range("D:J"), Sheet2.UsedRange).Value
  Dim OutputVal() As Variant
  ReDim OutputVal(1 To UBound(Ran1Val) + UBound(Ran2Val), 1 To 1)

  For i = 1 To UBound(Ran2Val)
    If Ran2Val(i, 5) <> "" And IsNumeric(Ran2Val(i, 7)) And Ran2Val(i, 5) > 10000 Then
      If Ran2Val(i, 1) = "MATCHING" Then
        Ran2Val(i, 1) = CStr(Ran2Val(i, 7) & " " & Ran2Val(i, 5))
      Else
        Ran2Val(i, 1) = CStr(Ran2Val(i, 5) & " " & Ran2Val(i, 3))
      End If

      If IsNumeric(Application.Match(Ran2Val(i, 1), Ran1Val, 0)) Then
        j = j + 1
        OutputVal(j, 1) = Ran2Val(i, 1)
      End If

    Else
      Ran2Val(i, 1) = ""
    End If
  Next

  ReDim Preserve Ran2Val(1 To UBound(Ran2Val), 1 To 1)

  Dim runNer As Variant
  For Each runNer In Ran1Val
    If Len(runNer) Then
      If IsNumeric(Application.Match(runNer, Ran2Val, 0)) Then
        j = j + 1
        OutputVal(j, 1) = runNer
      End If
    End If
  Next

  If j > 0 Then
    Worksheets("ForInvestigation").Range("A1:A" & j).Value = OutputVal
  End If

End Sub

我只是在数组中获取Range.Value。删除所有未使用的值并将一个维度设置为 (1 To 1) 允许我们使用Application.Match,这是 excel 中最快的函数之一。

在构建第二个数组时,我们已经可以检查第一个数组并将唯一性直接推送到输出数组。
调整第二个数组的大小(保留)允许我们将其与Match 一起使用。

最后将第一个数组的条目与第二个数组的条目进行对比,并将它们也推送到我们的输出数组中。

现在我们可以直接将值复制到您的目的地(一步)

注意:
- 您可以先删除“输出范围”(稍后较小的列表不会覆盖 oler 值。)
- 我无法进行真正的检查(您可能需要通过我错过的评论报告错误)
- 此代码不检查一个列表中的双精度(在列表 1 中有 2 次但不在列表 2 中的 1 项,将在最后打印 2 次/如果您需要此检查,则只需写评论)

【讨论】:

  • 嗨 Dirk - 这是运行代码的一种非常酷的方式。我以前从未使用过 Application.Match 函数。话虽如此,老实说,它仍然需要很长时间才能运行。它还吐出一个没有任何重复项的列表,但我正在寻找存在于 1 个列表中但不存在于第二个列表中的项目,反之亦然。看来您的答案是制作一个“主”列表。如果我有列表 1、2、3、4 和列表 1、3、4、5。我需要显示 2 和 5 的第三个列表。我会继续修改它。我也在看 Ron 的,它似乎工作得非常快。
  • 我很困惑......它应该这样做。 OutputVal 应该只保存仅在其中一个列表中的这些项目......此外,它也是一个尝试跳过集合的尝试。需要一些样本数据来优化它,但是从 Ron 那里得到一个很好的解决方案(在我看来)并没有真正的需要......而且我现在很懒惰:P
【解决方案4】:

感谢您的所有帮助!这是我的答案。它主要来自 Ron 的回答 - 我当然对其进行了一些调整。

Sub MatchFSTradesDFTrades2()

Dim V1 As Variant
Dim V2 As Variant
Dim COL As New Collection
Dim I As Long

Worksheets("DatafileFL").Activate

With Worksheets("FundserveFL")
    V1 = .Range("L1", .Cells(.Rows.Count, "L").End(xlUp)).Resize(columnsize:=7)
End With

With Worksheets("DatafileFL")
    V2 = .Range("F1", .Cells(.Rows.Count, "D").End(xlUp)).Resize(columnsize:=12)
End With

For I = 1 To UBound(V1)
    If V1(I, 1) = " " Or Not IsNumeric(V1(I, 1)) Or V1(I, 1) < 10000 Or V1(I, 1) = "***" Or Not IsNumeric(V1(I, 3)) Or (V1(I, 5)) = "Buy-EC" Or (V1(I, 5)) = "Sell-EC" Then
    Else:
        COL.Add (V1(I, 1)) & " " & (V1(I, 7)), CStr(V1(I, 1)) & " " & (V1(I, 7))
    End If
Next I
For I = 1 To COL.Count
    Sheet3.Cells(I + 1, 1).Value = COL.Item(I)
Next
On Error Resume Next
For I = 1 To UBound(V2)
    If V2(I, 1) = "MATCHING" Then
        If IsNumeric(V2(I, 5)) Then
            COL.Add (V2(I, 7)) & " " & V2(I, 5), CStr(V2(I, 7)) & " " & V2(I, 5)
                Select Case Err.Number
                    Case 457  'This is a duplicate, so will remove

                        Err.Clear
                        COL.Remove CStr(V2(I, 7)) & " " & V2(I, 5)
                End Select
        Else: V2(I, 12) = Right(V2(I, 5), Len(V2(I, 5)) - 1)
              V2(I, 12) = Format(V2(I, 12), "General Number")
            COL.Add (V2(I, 7)) & " " & V2(I, 12), CStr(V2(I, 7)) & " " & V2(I, 12)
                Select Case Err.Number
                    Case 457  'This is a duplicate, so will remove
                        Err.Clear
                        COL.Remove CStr(V2(I, 7)) & " " & V2(I, 12)
                End Select
        End If
    ElseIf V2(I, 5) = " " Or Not IsNumeric(V2(I, 5)) Or V2(I, 5) < 10000 Or V2(I, 5) = "***" Or V2(I, 1) = "BULK" Then
    Else:
        If IsNumeric(V2(I, 3)) Then
            COL.Add (V2(I, 5)) & " " & V2(I, 3), CStr(V2(I, 5)) & " " & V2(I, 3)
                Select Case Err.Number
                    Case 457  'This is a duplicate, so will remove
                        Err.Clear
                        COL.Remove CStr(V2(I, 5)) & " " & V2(I, 3)
                End Select
        Else: V2(I, 12) = Right(V2(I, 3), Len(V2(I, 3)) - 1)
              V2(I, 12) = Format(V2(I, 12), "General Number")
            COL.Add (V2(I, 5)) & " " & V2(I, 12), CStr(V2(I, 5)) & " " & V2(I, 12)
                Select Case Err.Number
                    Case 457  'This is a duplicate, so will remove
                        Err.Clear
                        COL.Remove CStr(V2(I, 5)) & " " & V2(I, 12)
                End Select
        End If
    End If
Next

Worksheets("ForInvestigation").Activate
Cells.Clear

For I = 1 To COL.Count
    Sheet3.Cells(I + 1, 1).Value = COL.Item(I)
Next

Range("A:A").Select
Selection.TextToColumns DataType:=xlDelimited, Space:=True, Other:=True
Range("A1") = "Trade ID Number"
Range("A1").Font.Bold = True
Range("B1") = "Net Balanace On Trade"
Range("B1").Font.Bold = True
End Sub

【讨论】:

    猜你喜欢
    • 2018-09-29
    • 2015-08-06
    • 1970-01-01
    • 2018-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-30
    • 2020-03-29
    相关资源
    最近更新 更多