web-dev-qa-db-fra.com

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.

11
Kelvin

É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.

enter image description here

É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:

  1. Un nom convivial pour votre connexion

  2. choisissez le pilote Microsoft Excel approprié

... puis Connect

enter image description here

Étape 3

Select Workbook... puis recherchez votre fichier.

enter image description here

É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 ...

enter image description here

É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.

enter image description here

Étape 6

Vous revenez à la feuille de calcul. Presque terminé, je le promets! Cocher New worksheet et OK.

enter image description here

Étape 7

Les résultats croisés sont renvoyés.

enter image description here

22
andy holaday

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É

enter image description here

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.

10
Siddharth Rout

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
4
spioter

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:

enter image description here

Sur Sheet2:

enter image description here

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

3
andy holaday

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
0
NDavid RU