web-dev-qa-db-fra.com

Quel est le moyen le plus efficace / le plus rapide de parcourir les lignes dans VBA (Excel)?

Je sais que VBA dans Excel n'est pas la chose la plus rapide - mais j'ai besoin du moyen le plus efficace (c'est-à-dire le plus rapide) pour parcourir un large échantillon de lignes.

Actuellement, j'ai:

For Each c In Range("$A$2:$A$" & Cells(Rows.count, "A").End(xlUp).row
    ' do stuff
Next c

Le 'do stuff' comprend l'insertion d'une ligne ici et là (donc je dois garder la recherche dynamique de la plage.)

Des idées (en regardant 10 000 lignes +)?

EDIT J'utilise déjà

Application.ScreenUpdating = False
Application.Calculation = xlManual
23
Chris

Si vous parcourez simplement 10 000 lignes dans la colonne A, puis videz la ligne dans un tableau de variantes, puis parcourez-le.

Vous pouvez ensuite soit ajouter les éléments à un nouveau tableau (tout en ajoutant des lignes si nécessaire) et utiliser Transpose () pour placer le tableau sur votre plage en un seul mouvement, soit utiliser votre variable itérateur pour suivre la ligne sur laquelle vous vous trouvez et ajouter lignes de cette façon.

Dim i As Long
Dim varray As Variant

varray = Range("A2:A" & Cells(Rows.Count, "A").End(xlUp).Row).Value

For i = 1 To UBound(varray, 1)
    ' do stuff to varray(i, 1)
Next

Voici un exemple de la façon dont vous pouvez ajouter des lignes après avoir évalué chaque cellule. Cet exemple insère juste une ligne après chaque ligne qui a le mot "foo" dans la colonne A. Pas que le "+2" soit ajouté à la variable i pendant l'insertion puisque nous commençons sur A2. Ce serait +1 si nous commencions notre tableau avec A1.

Sub test()

Dim varray As Variant
Dim i As Long

varray = Range("A2:A10").Value

'must step back or it'll be infinite loop
For i = UBound(varray, 1) To LBound(varray, 1) Step -1
    'do your logic and evaluation here
    If varray(i, 1) = "foo" Then
       'not how to offset the i variable 
       Range("A" & i + 2).EntireRow.Insert
    End If
Next

End Sub
36
aevanko

MODIFIER Résumé et recommandations

Utilisant un for each cell in range la construction n'est pas en elle-même lente. Ce qui est lent est un accès répété à Excel dans la boucle (que ce soit la lecture ou l'écriture de valeurs de cellule, le format, etc., l'insertion/la suppression de lignes, etc.).

Ce qui est trop lent dépend entièrement de vos besoins. Un Sub qui prend quelques minutes à fonctionner peut être OK s'il n'est utilisé que rarement, mais un autre qui prend 10 secondes peut être trop lent s'il est exécuté fréquemment.

Donc, quelques conseils généraux:

  1. restez simple au début. Si le résultat est trop lent pour vos besoins, optimisez
  2. se concentrer sur l'optimisation du contenu de la boucle
  3. ne vous contentez pas de supposer qu'une boucle est nécessaire. Il existe parfois des alternatives
  4. si vous devez utiliser des valeurs de cellule (beaucoup) à l'intérieur de la boucle, chargez-les dans un tableau de variantes à l'extérieur de la boucle.
  5. un bon moyen d'éviter la complexité des inserts est de boucler la plage de bas en haut
    (for index = max to min step -1)
  6. si vous ne pouvez pas faire cela et que votre "insérer une ligne ici et là" n'est pas trop nombreux, pensez à recharger le tableau après chaque insertion
  7. Si vous devez accéder à des propriétés de cellule autres que value, vous êtes bloqué avec des références de cellule
  8. Pour supprimer un certain nombre de lignes, envisagez de créer une référence de plage à une plage multi-zones dans la boucle, puis supprimez cette plage en une seule fois après la boucle

par exemple (non testé!)

Dim rngToDelete as range
for each rw in rng.rows
    if need to delete rw then

        if rngToDelete is nothing then
            set rngToDelete = rw
        else
            set rngToDelete = Union(rngToDelete, rw)
        end if

    endif
next
rngToDelete.EntireRow.Delete

Message d'origine

La sagesse conventionnelle dit que la boucle à travers les cellules est mauvaise et la boucle à travers un tableau de variantes est bonne . Moi aussi, je défends cela depuis un certain temps. Votre question m'a fait réfléchir, j'ai donc fait quelques tests courts avec des résultats surprenants (pour moi en tout cas):

ensemble de données de test: une simple liste dans les cellules A1 .. A1000000 (c'est 1 000 000 de lignes)

Cas de test 1: boucle un tableau

Dim v As Variant
Dim n As Long

T1 = GetTickCount
Set r = Range("$A$1", Cells(Rows.Count, "A").End(xlUp)).Cells
v = r
For n = LBound(v, 1) To UBound(v, 1)
    'i = i + 1
    'i = r.Cells(n, 1).Value 'i + 1
Next
Debug.Print "Array Time = " & (GetTickCount - T1) / 1000#
Debug.Print "Array Count = " & Format(n, "#,###")

Résultat:

Array Time = 0.249 sec
Array Count = 1,000,001

Cas de test 2: boucle la gamme

T1 = GetTickCount
Set r = Range("$A$1", Cells(Rows.Count, "A").End(xlUp)).Cells
For Each c In r
Next c
Debug.Print "Range Time = " & (GetTickCount - T1) / 1000#
Debug.Print "Range Count = " & Format(r.Cells.Count, "#,###")

Résultat:

Range Time = 0.296 sec
Range Count = 1,000,000

Ainsi, boucler un tableau est plus rapide mais seulement de 19% - beaucoup moins que ce à quoi je m'attendais.

Test 3: boucle un tableau avec une référence de cellule

T1 = GetTickCount
Set r = Range("$A$1", Cells(Rows.Count, "A").End(xlUp)).Cells
v = r
For n = LBound(v, 1) To UBound(v, 1)
    i = r.Cells(n, 1).Value
Next
Debug.Print "Array Time = " & (GetTickCount - T1) / 1000# & " sec"
Debug.Print "Array Count = " & Format(i, "#,###")

Résultat:

Array Time = 5.897 sec
Array Count = 1,000,000

Cas de test 4: plage de boucle avec une référence de cellule

T1 = GetTickCount
Set r = Range("$A$1", Cells(Rows.Count, "A").End(xlUp)).Cells
For Each c In r
    i = c.Value
Next c
Debug.Print "Range Time = " & (GetTickCount - T1) / 1000# & " sec"
Debug.Print "Range Count = " & Format(r.Cells.Count, "#,###")

Résultat:

Range Time = 2.356 sec
Range Count = 1,000,000

Donc, événement avec une seule référence de cellule simple, la boucle est un ordre de grandeur plus lent, et de plus, la boucle de plage est deux fois plus rapide!

Donc, la conclusion est ce qui compte le plus, c'est ce que vous faites à l'intérieur de la boucle , et si la vitesse compte vraiment, testez toutes les options

FWIW, testé sur Excel 2010 32 bits, Win7 64 bits Tous les tests avec

  • ScreenUpdating off,
  • Calulation manuel,
  • Events désactivé.
19
chris neilsen

Pour Chacun est beaucoup plus rapide que pour I = 1 à X, pour une raison quelconque. Essayez simplement de parcourir le même dictionnaire,


une fois avec pour chaque Dkey dans dDict,


et une fois avec pour Dkey = lbound (dDict.keys) à ubound (dDict.keys)

=> Vous remarquerez une énorme différence, même si vous passez par la même construction.

1
Dumitru Daniel