Excel VBA,使用数组加速代码

时间:2012-12-30 18:58:28

标签: arrays excel vba sorting

事先感谢任何有关此问题的帮助,我有一个很大的电子表格,我需要解析其他电子表格,我有一些工作,虽然很慢。我已经读过使用数组是一种更好的方法,但我似乎无法让它工作,我想我可以把主电子表格拉成一个数组,但我不能像我想的那样操作它。具体来说,我无法从主数组中获取某些行,并将它们插入到另一个数组中,以便最后复制到目标工作表中。以下是原始的工作函数:

Private Function CopyValues(rngSource As Range, rngTarget As Range)

    rngTarget.Resize(rngSource.Rows.Count, rngSource.Columns.Count).Value = rngSource.Value

End Function

Private Function RESORT(FROMSHEET As Variant, Column As Variant, TOSHEET As Variant, EXTRA1 As Variant, EXTRA2 As Variant, EXTRA3 As Variant)
Set i = Sheets(FROMSHEET)
Set e = Sheets(TOSHEET)


Dim d
Dim j
Dim q
d = 1
j = 2


e.Select
Cells.Select
Selection.Clear
i.Select
Rows(1).Copy
e.Select
Rows(1).PasteSpecial

Do Until IsEmpty(i.Range("G" & j))
    If i.Range(Column & j) = "Total" Then
        i.Select
        Rows(j).Copy
        e.Select
        Rows(2).PasteSpecial
        ' CopyValues i.Rows(j), e.Rows(2)
        Exit Do
    End If
    j = j + 1
Loop

d = 2
j = 2

Do Until IsEmpty(i.Range("G" & j))

    If i.Range(Column & j) = TOSHEET Or i.Range(Column & j) = EXTRA1 Or i.Range(Column & j) = EXTRA2 Or i.Range(Column & j) = EXTRA3 Then
        d = d + 1
        CopyValues i.Range(i.Cells(j, 1), i.Cells(j, 11)), e.Range(e.Cells(d, 1), e.Cells(d, 11)) 'e.Range("A" & d)

    ElseIf i.Range("A" & j) = e.Range("A" & d) And i.Range("I" & j) = "Total" Then
        d = d + 1
        e.Select
        Rows(2).Copy
        Rows(d).PasteSpecial
        ' CopyValues e.Rows(2), e.Rows(d)
    End If
    j = j + 1
Loop
e.Select
Rows(2).Delete
Range("A1").Select

End Function

所以,我有两个问题。首先,我是否正确,移动到阵列将加快这一点?其次,我该怎么做数组的东西?谢谢!这就是我正在攻击的东西,在那里做了很多不同的尝试,我发现它很难看:

Private Function RESORT2(FROMSHEET As Variant, Column As Variant, TOSHEET As Variant, EXTRA1 As Variant, EXTRA2 As Variant, EXTRA3 As Variant)
' Set i = Sheets(FROMSHEET)
' Set e = Sheets(TOSHEET)
Dim d
Dim j As Long
Dim i As Long
Dim k As Long

Dim myarray As Variant
Dim arrTO As Variant


d = 1

j = 1

      'myarray = Worksheets(FROMSHEET).Range("a1").Resize(10, 20)
      myarray = Worksheets(FROMSHEET).Range("a1:z220").Value 'Resize(10, 20)
      For i = 1 To UBound(myarray)
        If myarray(i, 9) = TOSHEET Then
        'arrTO = myarray
          '  Worksheets(TOSHEET).Range("A" & j).Resize(1, 20) = Application.WorksheetFunction.Transpose(myarray(i))
            Worksheets(TOSHEET).Range("A" & j).Value = Application.WorksheetFunction.Transpose(myarray)
         '   arrTO = j 'Application.WorksheetFunction.Index(myarray, 0, 1)

            j = j + 1

        End If

      Next
      Worksheets(TOSHEET).Range("a1").Resize(10, 20) = arrTO


End Function

===================================

首先编辑

好的,我尝试清理并执行以下操作:

Private Function RESORT(FROMSHEET As Variant, Column As Variant, TOSHEET As Variant, EXTRA1 As Variant, EXTRA2 As Variant, EXTRA3 As Variant)
Set FRO = Sheets(FROMSHEET)
Set TOO = Sheets(TOSHEET)

Dim TOO_IND
Dim FRO_IND
Dim TotalRow

TotalRow = 2
TOO_IND = 2
FRO_IND = 2

TOO.Cells.Clear
TOO.Rows(1).Value = FRO.Rows(1).Value

Do Until IsEmpty(FRO.Range("G" & TotalRow))
    If FRO.Range(Column & TotalRow) = "Total" Then
        FRO.Select
        Rows(TotalRow).Copy
        TOO.Select
        Rows(2).PasteSpecial
        ' CopyValues FRO.Rows(j), TOO.Rows(2)
        Exit Do
    End If
    TotalRow = TotalRow + 1
Loop

Do Until IsEmpty(FRO.Range("G" & FRO_IND))

    If FRO.Range(Column & FRO_IND) = TOSHEET Or FRO.Range(Column & FRO_IND) = EXTRA1 Or FRO.Range(Column & FRO_IND) = EXTRA2 Or FRO.Range(Column & FRO_IND) = EXTRA3 Then
        TOO_IND = TOO_IND + 1
        TOO.Rows(TOO_IND).Value = FRO.Rows(FRO_IND).Value
    ElseIf FRO.Range("A" & FRO_IND) = TOO.Range("A" & TOO_IND) And FRO.Range("I" & FRO_IND) = "Total" Then
        TOO_IND = TOO_IND + 1
        TOO.Select
        Rows(2).Copy
        Rows(TOO_IND).PasteSpecial
     '   TOO.Rows(TOO_IND).PasteSpecial = FRO.Rows(2).PasteSpecial  ' this isn't working, I need format and formula, if I just do .formula it doesn't work
    End If
    FRO_IND = FRO_IND + 1
Loop

TOO.Rows(2).Delete
'Range("A1").Select

End Function

因此,虽然它看起来更干净且更具可读性,但它实际上更慢(在我最小的样本集上为3.2秒与2.86秒)。

我认为阵列将成为解决方案。我在同一个样本集上多次运行此例程,但是使用不同的限定符,如果在main中我将样本集转储到数组中,然后将此数组传递给此排序例程,我认为它会更快。但是我仍然不确定如何在数组上进行操作,特别是将一行从数组复制到数组。

谢谢大家,我会坚持下去!

=============================================== ===============

第二次编辑 好的,我现在离我更近了!曾经花了大约133秒,现在只需要10.51秒!

这是最新的,如果有方法可以调整一下,请告诉我,我还在努力减少一些时间。我还没有编写任何东西来抓取数组一次,然后将数组传递给RESORT函数,我正在研究下一步,看看是否有助于加快速度。

有没有办法将公式和值复制到同一个数组中?我不喜欢我这样做的方式,但确实有效。

Private Function RESORT(FROMSHEET As Variant, Column As Variant, TOSHEET As Variant, EXTRA1 As Variant, EXTRA2 As Variant, EXTRA3 As Variant)
Set FRO = Sheets(FROMSHEET)
Set TOO = Sheets(TOSHEET)

Dim TotalRow

TotalRow = 2
TOO_IND = 2
FRO_IND = 2

Dim Col As Long
Dim FROM_Row As Long
Dim TO_Row As Long

Const NumCol = 25

Dim myarray As Variant
Dim myarrayform As Variant
Dim arrTO(1 To 1000, 1 To 2000)
Dim arrTotal(1 To 1, 1 To NumCol)

TO_Row = 2
myarray = Worksheets(FROMSHEET).Range("a1:z1000").Value
myarrayform = Worksheets(FROMSHEET).Range("a1:z1000").FormulaR1C1

TOO.Cells.Clear

For Col = 1 To NumCol
    arrTO(1, Col) = myarray(1, Col)
Next

For FROM_Row = 1 To UBound(myarray)
    If myarray(FROM_Row, Column) = "Total" Then
        For Col = 1 To NumCol
            arrTotal(1, Col) = myarrayform(FROM_Row, Col)
        Next
        Exit For
    End If
Next

For FROM_Row = 1 To UBound(myarray)
    If myarray(FROM_Row, Column) = TOSHEET Or myarray(FROM_Row, Column) = EXTRA1 Or myarray(FROM_Row, Column) = EXTRA2 Or myarray(FROM_Row, Column) = EXTRA3 Then
        For Col = 1 To NumCol
            arrTO(TO_Row, Col) = myarray(FROM_Row, Col)
        Next
        TO_Row = TO_Row + 1
    ElseIf myarray(FROM_Row, 1) = arrTO(TO_Row - 1, 1) And myarray(FROM_Row, Column) = "Total" Then
        For Col = 1 To NumCol
            arrTO(TO_Row, Col) = arrTotal(1, Col)
        Next
        TO_Row = TO_Row + 1
    End If
Next
Worksheets(TOSHEET).Range("a1").Resize(1000, 2000) = arrTO

End Function

感谢您的帮助! 新年快乐!

3 个答案:

答案 0 :(得分:4)

在VBA中迭代数组不一定比迭代第一个方法使用的集合对象更快。这些集合可能是作为链表实现的,所以为了从头开始并循环遍历它们,它们将与数组一样快。

高级答案是您的排序算法通常比您的特定代码详细信息更重要。也就是说,只要您的详细信息不会以某种方式增加运行该算法的复杂性。

根据我的经验,加速VBA的最佳方法是避开所有对UI产生影响的功能。如果您的代码在所选单元格周围移动,或者切换活动查看的工作表等,那么这是最大的时间链接。我认为这些函数SelectCopy()PasteSpecial()可能会犯这样的错误。最好存储工作表和范围对象,并根据需要直接写入其单元格。你在第二种方法中这样做,我认为它比改变你的数据类型更重要。

答案 1 :(得分:1)

我同意@Seth Battin,但还有一些补充。

虽然数组可以更快,但如果你需要搜索它们,它们就不能很好地扩展。您编写的代码将遍历数据集n次(其中n是您拥有的TOSHEET的数量)。此外,您的代码正在为每一行向工作表写入一次数据(这非常耗时)。将所有数据放入单个2D数组并写入一次更快(但代码更多)。

更好的计划流程可能是

阅读每一行数据

将其分配给数据结构(我会使用包含2D数组的脚本字典)

在读取所有数据之后,迭代输出每个2D数组的脚本字典

这将最大限度地减少对电子表格的读取和写入,这是此类vba程序的性能瓶颈所在。

答案 2 :(得分:0)

是。通过使用数组而不是单元格集合,您肯定会加快代码速度。这是因为访问对象的属性需要时间。

老实说,你的代码可能不会因使用数组而受益,因为通过消除不必要的循环可以更合理地修改代码。

我以更加以Excel为中心的方式重新编写了RESORT函数的开头,避免了一些像选择这样的陷阱。我还建议尝试使用有意义的变量名,尤其是对象。

OPTION EXPLICIT
Private Function RESORT(FROMSHEET As Variant, Column As Variant, TOSHEET As Variant, EXTRA1 As Variant, EXTRA2 As Variant, EXTRA3 As Variant)
'Actually indicate variable types.
dim i as worksheet, dim e as worksheet
dim searchRange as Range

Set i = Sheets(FROMSHEET)
Set e = Sheets(TOSHEET)


Dim d as long
Dim j as long
dim lastRow as long 'Using a meaningful variable name
d = 1
j = 2

'I'm assuming you were using PasteSpecial because you only want values.
'I removed your unnecessary selects
e.Cells.Clear
'Move values directly instead of copy paste
i.Rows(1).value = e.Rows(1).value

'Check the first range
If Not IsEmpty(.Range("G" & j)) Then
    'Determine the last row to check.
    'This would break if j is equivalent to the last possible row... 
    'but only an example
    If IsEmpty(.Range("G" & j+1) then
        lastRow = j
    else 
        lastrow = i.Range("G" & j).End(xlDown).Row
    end if
    'Get the search Range
    'We might have used arrays here but it's less complicated to 
    ' use built in functions.
    Set searchRange = i.Range(i.Range(Column & j), _
                      i.Range(Column, lastrow).Find("Total"))
    If Not (searchRange Is Nothing) Then
        'Copy the values of the found row.
        e.Rows(2).value = searchRange.EntireRow.value
    End If
End If

在这样做之后,我意识到可能更合理地使用数组的部分是在我停止的地方之后。如果你想在这里使用数组,你需要做的是有效地将所有相关区域复制到一个数组,然后引用数组,就像引用单元格一样。

例如:

myArray = i.Range("A1:B10")
MsgBox myArray(10, 2) 'Displays value of B10 (10th row, 2nd column)
MsgBox i.Cells(10, 2) 'Displays value of B10 (10th row, 2nd column)