Excel vba pour créer toutes les combinaisons possibles d'une plage
J'ai un problème que je n'ai trouvé nulle part sur le Web (il est peut-être là, mais je ne le trouve pas, hé).
J'ai une feuille de calcul avec 13 colonnes de données. Chacune des colonnes contient des variations d'un paramètre qui doit entrer dans un scénario de test global.
Tous diffèrent, comme
E:
101%
105%
110%
120%
J:
Supérieur S
Upside L
Inconvénient B
Premium V
J'ai vu plusieurs solutions au problème de combinaison qui utilise des boucles imbriquées. Je voudrais éviter 13 boucles imbriquées (mais c'est mon meilleur pari pour le moment). Je ne sais pas comment générer chaque combinaison unique dans chaque colonne.
Je ne sais pas si cela a suffisamment de sens pour vous. J'espérais que quelqu'un pourrait au moins me diriger dans la bonne direction avec un algorithme récursif. J'aimerais le rendre suffisamment dynamique pour prendre un nombre variable de colonnes et de lignes.
Merci pour toute aide que vous pouvez me donner.
Étant donné que j'ai proposé une approche ODBC, j'ai pensé que je devrais en parler, car il n'est pas immédiatement évident de savoir comment le faire. Et, honnêtement, je devais réapprendre le processus et le documenter par moi-même. .
C'est un moyen de générer un produit cartésien de deux ou plusieurs tableaux de données unidimensionnels à l'aide d'Excel et de Microsoft Query.
Ces instructions ont été écrites avec XL2007 mais devraient fonctionner avec des modifications mineures (le cas échéant) dans n'importe quelle version.
Étape 1
Organisez les tableaux en colonnes.
Important: Chaque colonne doit avoir deux noms "d'en-tête" comme indiqué en gras ci-dessous. Le nom le plus haut sera interprété plus tard comme un "nom de table". Le deuxième nom sera interprété comme un "nom de colonne". Cela deviendra apparent quelques étapes plus tard.
Sélectionnez tour à tour chaque plage de données, y compris les deux "en-têtes", et appuyez sur Ctrl+Shift+F3
. Cochez uniquement Top row
dans la boîte de dialogue "Créer des noms" et cliquez sur OK
.
Une fois que toutes les plages nommées sont établies, enregistrez le fichier.
Étape 2
Données | Obtenir des données externes | À partir d'autres sources | Depuis Microsoft Query
Choisissez <New Data Source>
. Dans le Choose New Data Source
boîte de dialogue:
Un nom convivial pour votre connexion
choisissez le pilote Microsoft Excel approprié
... puis Connect
Étape 3
Select Workbook...
puis recherchez votre fichier.
Étape 4
Ajoutez les "colonnes" de vos "tables". Vous pouvez maintenant voir pourquoi la disposition "à deux en-têtes" à l'étape 1 est importante - elle incite le pilote à comprendre correctement les données.
Cliquez ensuite sur Cancel
(vraiment!). Vous pouvez être invité à ce stade à "continuer la modification dans Microsoft Query?" (réponse Yes
), ou une réclamation qui rejoint ne peut pas être représentée dans l'éditeur graphique. Ignorez cela et continuez ...
Étape 5
Microsoft Query s'ouvre et, par défaut, les tables que vous avez ajoutées seront jointes. Cela va générer un produit cartésien, c'est ce que nous voulons.
Fermez maintenant complètement MSQuery.
Étape 6
Vous revenez à la feuille de calcul. Presque terminé, je le promets! Cocher New worksheet
et OK
.
Étape 7
Les résultats croisés sont renvoyés.
Je ne sais pas pourquoi vous êtes opposé à la boucle. Voir cet exemple. Cela a pris moins d'une seconde.
Option Explicit
Sub Sample()
Dim i As Long, j As Long, k As Long, l As Long
Dim CountComb As Long, lastrow As Long
Range("G2").Value = Now
Application.ScreenUpdating = False
CountComb = 0: lastrow = 6
For i = 1 To 4: For j = 1 To 4
For k = 1 To 8: For l = 1 To 12
Range("G" & lastrow).Value = Range("A" & i).Value & "/" & _
Range("B" & j).Value & "/" & _
Range("C" & k).Value & "/" & _
Range("D" & l).Value
lastrow = lastrow + 1
CountComb = CountComb + 1
Next: Next
Next: Next
Range("G1").Value = CountComb
Range("G3").Value = Now
Application.ScreenUpdating = True
End Sub
INSTANTANÉ
NOTE : Ce qui précède était un petit exemple. J'ai fait un test sur 4 colonnes avec 200 lignes chacune. La combinaison totale possible dans un tel scénario est 1600000000
et cela a pris 16 secondes.
Dans ce cas, il dépasse la limite de lignes Excel. Une autre option à laquelle je peux penser est d'écrire la sortie dans un fichier texte dans un tel scénario. Si vos données sont petites, vous pouvez vous en sortir sans utiliser de tableaux et écrire directement dans les cellules. :) Mais en cas de données volumineuses, je recommanderais d'utiliser des tableaux.
J'en ai eu besoin plusieurs fois moi-même et je l'ai finalement construit.
Je crois que le code évolue pour n'importe quel nombre total de colonnes et n'importe quel nombre de valeurs distinctes dans les colonnes (par exemple, chaque colonne peut contenir n'importe quel nombre de valeurs)
Il suppose que toutes les valeurs de chaque colonne sont uniques (si ce n'est pas vrai, vous obtiendrez des lignes en double)
Il suppose que vous souhaitez effectuer une jointure croisée en fonction des cellules que vous avez actuellement sélectionnées (assurez-vous de toutes les sélectionner)
Il suppose que vous souhaitez que la sortie démarre une colonne après la sélection actuelle.
Fonctionnement (bref): d'abord pour chaque colonne et pour chaque ligne: il calcule le nombre total de lignes nécessaires pour prendre en charge tous les combos dans N colonnes (éléments de la colonne 1 * éléments de la colonne 2 ... * éléments de la colonne N)
deuxième pour chaque colonne: Sur la base du total des combos et du total des combos des colonnes précédentes, il calcule deux boucles.
ValueCycles (combien de fois vous devez parcourir toutes les valeurs de la colonne actuelle) ValueRepeats (combien de fois pour répéter chaque valeur dans la colonne consécutivement)
Sub sub_CrossJoin()
Dim rg_Selection As Range
Dim rg_Col As Range
Dim rg_Row As Range
Dim rg_Cell As Range
Dim rg_DestinationCol As Range
Dim rg_DestinationCell As Range
Dim int_PriorCombos As Long
Dim int_TotalCombos As Long
Dim int_ValueRowCount As Long
Dim int_ValueRepeats As Long
Dim int_ValueRepeater As Long
Dim int_ValueCycles As Long
Dim int_ValueCycler As Long
int_TotalCombos = 1
int_PriorCombos = 1
int_ValueRowCount = 0
int_ValueCycler = 0
int_ValueRepeater = 0
Set rg_Selection = Selection
Set rg_DestinationCol = rg_Selection.Cells(1, 1)
Set rg_DestinationCol = rg_DestinationCol.Offset(0, rg_Selection.Columns.Count)
'get total combos
For Each rg_Col In rg_Selection.Columns
int_ValueRowCount = 0
For Each rg_Row In rg_Col.Cells
If rg_Row.Value = "" Then
Exit For
End If
int_ValueRowCount = int_ValueRowCount + 1
Next rg_Row
int_TotalCombos = int_TotalCombos * int_ValueRowCount
Next rg_Col
int_ValueRowCount = 0
'for each column, calculate the repeats needed for each row value and then populate the destination
For Each rg_Col In rg_Selection.Columns
int_ValueRowCount = 0
For Each rg_Row In rg_Col.Cells
If rg_Row.Value = "" Then
Exit For
End If
int_ValueRowCount = int_ValueRowCount + 1
Next rg_Row
int_PriorCombos = int_PriorCombos * int_ValueRowCount
int_ValueRepeats = int_TotalCombos / int_PriorCombos
int_ValueCycles = (int_TotalCombos / int_ValueRepeats) / int_ValueRowCount
int_ValueCycler = 0
int_ValueRepeater = 0
Set rg_DestinationCell = rg_DestinationCol
For int_ValueCycler = 1 To int_ValueCycles
For Each rg_Row In rg_Col.Cells
If rg_Row.Value = "" Then
Exit For
End If
For int_ValueRepeater = 1 To int_ValueRepeats
rg_DestinationCell.Value = rg_Row.Value
Set rg_DestinationCell = rg_DestinationCell.Offset(1, 0)
Next int_ValueRepeater
Next rg_Row
Next int_ValueCycler
Set rg_DestinationCol = rg_DestinationCol.Offset(0, 1)
Next rg_Col
End Sub
Solution basée sur mon deuxième commentaire. Cet exemple suppose que vous disposez de trois colonnes de données, mais peut être adapté pour en gérer davantage.
Je commence avec vos exemples de données. J'ai ajouté des chiffres sur la rangée supérieure pour plus de commodité. J'ai également ajouté le nombre total de combinaisons (produit des comptes). C'est Sheet1
:
Sur Sheet2
:
Formules:
A2:C2
(les cellules orange) sont codées en dur =0
A3=IF(SUM(B3:C3)=0,MOD(A2+1,Sheet1!$E$1),A2)
B3=IF(C3=0,MOD(B2+1,Sheet1!$G$1),B2)
C3=MOD(C2+1,Sheet1!$J$1)
D2=INDEX(Sheet1!$E$2:$E$5,Sheet2!A2+1)
E2=INDEX(Sheet1!$G$2:$G$6,Sheet2!B2+1)
F2=INDEX(Sheet1!$J$2:$J$5,Sheet2!C2+1)
Remplissez de la ligne 3 vers le bas autant de lignes que Total
s'affiche sur Sheet1
appeler la méthode et mettre dans le niveau actuel, qui sera décrémenté dans la méthode (désolé pour eng)
échantillon:
sub MyAdd(i as integer)
if i > 1 then
MyAdd = i + MyAdd(i-1)
else
MyAdd = 1
end if
end sub