Algorithme pour renvoyer toutes les combinaisons de k éléments de n


571

Je veux écrire une fonction qui prend un tableau de lettres comme argument et un certain nombre de ces lettres à sélectionner.

Supposons que vous fournissiez un tableau de 8 lettres et que vous souhaitiez en sélectionner 3. Ensuite, vous devriez obtenir:

8! / ((8 - 3)! * 3!) = 56

Tableaux (ou mots) en retour composés de 3 lettres chacun.


2
Une préférence pour le langage de programmation?
Jonathan Tran

7
Comment voulez-vous gérer les doublons?
wcm

Aucune préférence de langage, je vais le coder en rubis mais une idée générale des algorithmes à utiliser serait bien. Deux lettres de même valeur peuvent exister mais pas deux fois la même lettre exacte.
Fredrik


En php, ce qui suit devrait faire l'affaire: stackoverflow.com/questions/4279722/…
Kemal Dağ

Réponses:


413

Art of Computer Programming Volume 4: Fascicle 3 en a une tonne qui pourrait mieux s'adapter à votre situation particulière que celle que je décris.

Codes gris

Un problème que vous rencontrerez est bien sûr la mémoire et assez rapidement, vous aurez des problèmes avec 20 éléments dans votre ensemble - 20 C 3 = 1140. Et si vous voulez parcourir l'ensembles, il est préférable d'utiliser un gris modifié algorithme de code afin que vous ne les gardiez pas tous en mémoire. Ceux-ci génèrent la combinaison suivante de la précédente et évitent les répétitions. Il y en a beaucoup pour différentes utilisations. Voulons-nous maximiser les différences entre les combinaisons successives? minimiser? etc.

Certains des articles originaux décrivant les codes gris:

  1. Quelques chemins de Hamilton et un algorithme de changement minimal
  2. Algorithme de génération de combinaison d'échange adjacent

Voici quelques autres articles couvrant le sujet:

  1. Une implémentation efficace de l'algorithme Eades, Hickey, Read Adjacent Interchange Combination Generation (PDF, avec code en Pascal)
  2. Générateurs combinés
  3. Enquête sur les codes gris combinatoires (PostScript)
  4. Un algorithme pour les codes gris

Twiddle de Chase (algorithme)

Phillip J Chase, ` Algorithm 382: Combinations of M out of N Objects '(1970)

L'algorithme en C ...

Index des combinaisons dans l'ordre lexicographique (algorithme de boucles 515)

Vous pouvez également référencer une combinaison par son index (dans l'ordre lexicographique). En réalisant que l'indice devrait être une certaine quantité de changement de droite à gauche en fonction de l'indice, nous pouvons construire quelque chose qui devrait récupérer une combinaison.

Donc, nous avons un ensemble {1,2,3,4,5,6} ... et nous voulons trois éléments. Disons que {1,2,3} nous pouvons dire que la différence entre les éléments est un et dans l'ordre et minime. {1, 2, 4} a un changement et est lexicographiquement numéro 2. Ainsi, le nombre de "changements" en dernier lieu explique un changement dans l'ordre lexicographique. La deuxième place, avec un changement {1,3,4} a un changement mais représente plus de changement car il est à la deuxième place (proportionnel au nombre d'éléments dans l'ensemble d'origine).

La méthode que j'ai décrite est une déconstruction, comme il semble, de l'ensemble à l'index, nous devons faire l'inverse - ce qui est beaucoup plus délicat. C'est ainsi que Buckles résout le problème. J'ai écrit du C pour les calculer , avec des changements mineurs - j'ai utilisé l'index des ensembles plutôt qu'une plage de nombres pour représenter l'ensemble, donc nous travaillons toujours de 0 à n. Remarque:

  1. Les combinaisons n'étant pas ordonnées, {1,3,2} = {1,2,3} - nous les ordonnons lexicographiques.
  2. Cette méthode a un 0 implicite pour démarrer l'ensemble pour la première différence.

Index des combinaisons dans l'ordre lexicographique (McCaffrey)

Il y a une autre façon : son concept est plus facile à saisir et à programmer mais il est sans les optimisations de Buckles. Heureusement, il ne produit pas non plus de combinaisons en double:

L'ensemble x_k ... x_1 en Nqui maximise i = C (x_1, k) + C (x_2, k-1) + ... + C (x_k, 1), où C (n, r) = {n choisissez r}.

Pour un exemple: 27 = C(6,4) + C(5,3) + C(2,2) + C(1,1). Donc, la 27e combinaison lexicographique de quatre choses est: {1,2,5,6}, ce sont les index de tout ensemble que vous voulez regarder. Exemple ci-dessous (OCaml), nécessite une choosefonction, laissé au lecteur:

(* this will find the [x] combination of a [set] list when taking [k] elements *)
let combination_maccaffery set k x =
    (* maximize function -- maximize a that is aCb              *)
    (* return largest c where c < i and choose(c,i) <= z        *)
    let rec maximize a b x =
        if (choose a b ) <= x then a else maximize (a-1) b x
    in
    let rec iterate n x i = match i with
        | 0 -> []
        | i ->
            let max = maximize n i x in
            max :: iterate n (x - (choose max i)) (i-1)
    in
    if x < 0 then failwith "errors" else
    let idxs =  iterate (List.length set) x k in
    List.map (List.nth set) (List.sort (-) idxs)

Un itérateur de combinaisons petites et simples

Les deux algorithmes suivants sont fournis à des fins didactiques. Ils implémentent un itérateur et (plus généralement) des combinaisons globales de dossiers. Ils sont aussi rapides que possible, ayant la complexité O ( n C k ). La consommation de mémoire est liée par k.

Nous commencerons par l'itérateur, qui appellera une fonction fournie par l'utilisateur pour chaque combinaison

let iter_combs n k f =
  let rec iter v s j =
    if j = k then f v
    else for i = s to n - 1 do iter (i::v) (i+1) (j+1) done in
  iter [] 0 0

Une version plus générale appellera la fonction fournie par l'utilisateur avec la variable d'état, à partir de l'état initial. Étant donné que nous devons passer l'état entre différents états, nous n'utiliserons pas la boucle for, mais à la place, utilisez la récursivité,

let fold_combs n k f x =
  let rec loop i s c x =
    if i < n then
      loop (i+1) s c @@
      let c = i::c and s = s + 1 and i = i + 1 in
      if s < k then loop i s c x else f c x
    else x in
  loop 0 0 [] x

1
Cela produira-t-il des combinaisons en double dans le cas où l'ensemble contient des éléments égaux?
Thomas Ahle

2
Oui, ce sera Thomas. Il est indépendant des données du tableau. Vous pouvez toujours filtrer les doublons en premier, si c'est l'effet souhaité, ou choisir un autre algorithme.
nlucaroni

19
Réponse géniale. Pouvez-vous fournir un résumé du temps d'exécution et de l'analyse de la mémoire pour chacun des algorithmes?
uncaught_exceptions

2
Assez bonne réponse. 20C3 est 1140, le point d'exclamation prête à confusion ici car il ressemble à une factorielle, et les factorielles entrent dans la formule pour trouver des combinaisons. Je vais donc éditer le point d'exclamation.
CashCow

3
Il est nul que de nombreuses citations soient derrière un mur payant. Existe-t-il une possibilité d'inclure des liens non-paywall ou d'inclure des extraits de devis à partir de sources?
Terrance

195

En C #:

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
{
  return k == 0 ? new[] { new T[0] } :
    elements.SelectMany((e, i) =>
      elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] {e}).Concat(c)));
}

Usage:

var result = Combinations(new[] { 1, 2, 3, 4, 5 }, 3);

Résultat:

123
124
125
134
135
145
234
235
245
345

2
Cette solution fonctionne bien pour les "petits" ensembles, mais pour les ensembles plus grands, elle utilise un peu de mémoire.
Artur Carvalho

1
pas directement lié mais, le code est très intéressant / lisible et je me demande quelle version de c # a ces constructions / méthodes? (Je n'ai utilisé que c # v1.0 et pas tant que ça).
LBarret

Certainement élégant, mais le IEnumerable sera énumérait un grand nombre de fois. Si cela est soutenu par une opération importante ...
Drew Noakes

2
puisqu'il s'agit d'une méthode d'extension, votre ligne d'utilisation peut lire:var result = new[] { 1, 2, 3, 4, 5 }.Combinations(3);
Dave Cousineau

1
Pouvez-vous fournir une version non linq exacte de cette requête en utilisant des boucles récursives
irfandar

81

Solution java courte:

import java.util.Arrays;

public class Combination {
    public static void main(String[] args){
        String[] arr = {"A","B","C","D","E","F"};
        combinations2(arr, 3, 0, new String[3]);
    }

    static void combinations2(String[] arr, int len, int startPosition, String[] result){
        if (len == 0){
            System.out.println(Arrays.toString(result));
            return;
        }       
        for (int i = startPosition; i <= arr.length-len; i++){
            result[result.length - len] = arr[i];
            combinations2(arr, len-1, i+1, result);
        }
    }       
}

Le résultat sera

[A, B, C]
[A, B, D]
[A, B, E]
[A, B, F]
[A, C, D]
[A, C, E]
[A, C, F]
[A, D, E]
[A, D, F]
[A, E, F]
[B, C, D]
[B, C, E]
[B, C, F]
[B, D, E]
[B, D, F]
[B, E, F]
[C, D, E]
[C, D, F]
[C, E, F]
[D, E, F]

cela semble être O (n ^ 3) non? Je me demande s'il existe un algorithme plus rapide pour ce faire.
LZH

Je travaille avec 20, choisissez 10 et cela semble être assez rapide pour moi (moins de 1 seconde)
demongolem

4
@NanoHead vous vous trompez. C'est une combinaison sans répétition. Et votre cas est à répétition.
Jack The Ripper

Ce morceau de code devrait être plus facile à trouver sur le web ... c'est exactement ce que je cherchais!
Manuel S.

Je viens de tester cela et 7 autres implémentations java - celle-ci était de loin la plus rapide. Le 2e plus rapide a été plus lent d'un ordre de grandeur.
stuart

77

Puis-je présenter ma solution Python récursive à ce problème?

def choose_iter(elements, length):
    for i in xrange(len(elements)):
        if length == 1:
            yield (elements[i],)
        else:
            for next in choose_iter(elements[i+1:len(elements)], length-1):
                yield (elements[i],) + next
def choose(l, k):
    return list(choose_iter(l, k))

Exemple d'utilisation:

>>> len(list(choose_iter("abcdefgh",3)))
56

Je l'aime pour sa simplicité.


16
len(tuple(itertools.combinations('abcdefgh',3)))réalisera la même chose en Python avec moins de code.
hgus1294

59
@ hgus1294 C'est vrai, mais ce serait de la triche. Op a demandé un algorithme, pas une méthode "magique" liée à un langage de programmation particulier.
MestreLion

1
À proprement parler, la première plage de boucles ne devrait-elle pas être for i in xrange(len(elements) - length + 1):? Peu importe en python car sortir de l'index de tranche est géré avec élégance mais c'est l'algorithme correct.
Stephan Dollberg

62

Disons que votre tableau de lettres ressemble à ceci: "ABCDEFGH". Vous disposez de trois indices (i, j, k) indiquant quelles lettres vous allez utiliser pour le mot courant, vous commencez par:

ABCDEFGH
^ ^ ^
ijk

D'abord, vous faites varier k, donc la prochaine étape ressemble à ceci:

ABCDEFGH
^ ^ ^
ijk

Si vous avez atteint la fin, continuez et faites varier j puis k à nouveau.

ABCDEFGH
^ ^ ^
ijk

ABCDEFGH
^ ^ ^
ijk

Une fois que vous avez atteint G, vous commencez également à varier i.

ABCDEFGH
  ^ ^ ^
  ijk

ABCDEFGH
  ^ ^ ^
  ijk
...

Écrit dans le code cela ressemble à ça

void print_combinations(const char *string)
{
    int i, j, k;
    int len = strlen(string);

    for (i = 0; i < len - 2; i++)
    {
        for (j = i + 1; j < len - 1; j++)
        {
            for (k = j + 1; k < len; k++)
                printf("%c%c%c\n", string[i], string[j], string[k]);
        }
    }
}

115
Le problème avec cette approche est qu'elle connecte en dur le paramètre 3 au code. (Et si 4 caractères étaient souhaités?) Si j'ai bien compris la question, le tableau de caractères ET le nombre de caractères à sélectionner seraient fournis. Bien sûr, une solution à ce problème consiste à remplacer les boucles imbriquées explicitement par la récursivité.
joel.neely

10
@ Dr.PersonPersonII Et pourquoi les triangles sont-ils pertinents pour l'OP?
MestreLion

7
Vous pouvez toujours transformer cette solution en solution récursive avec un paramètre arbitraire.
Rok Kralj

5
@RokKralj, comment "transformer cette solution en solution récursive avec un paramètre arbitraire"? Cela me semble impossible.
Aaron McDaid

3
Une belle explication intuitive de la façon de le faire
Yonatan Simson

53

L'algorithme récursif suivant sélectionne toutes les combinaisons d'éléments k dans un ensemble ordonné:

  • choisissez le premier élément ide votre combinaison
  • combiner iavec chacune des combinaisons d' k-1éléments choisis récursivement dans l'ensemble d'éléments plus grands que i.

Répétez ce qui précède pour chacun idans l'ensemble.

Il est essentiel que vous choisissiez le reste des éléments comme étant plus grand que i, pour éviter la répétition. De cette façon, [3,5] sera sélectionné une seule fois, comme [3] combiné avec [5], au lieu de deux (la condition élimine [5] + [3]). Sans cette condition, vous obtenez des variations au lieu de combinaisons.


12
Très bonne description en anglais de l'algorithme utilisé par de nombreuses réponses
MestreLion

deuxième ce qui précède; en particulier, cela m'a aidé à comprendre la solution posée par user935714. les deux sont excellents.
jacoblambert

25

En C ++, la routine suivante produira toutes les combinaisons de distance de longueur (première, k) entre la plage [première, dernière):

#include <algorithm>

template <typename Iterator>
bool next_combination(const Iterator first, Iterator k, const Iterator last)
{
   /* Credits: Mark Nelson http://marknelson.us */
   if ((first == last) || (first == k) || (last == k))
      return false;
   Iterator i1 = first;
   Iterator i2 = last;
   ++i1;
   if (last == i1)
      return false;
   i1 = last;
   --i1;
   i1 = k;
   --i2;
   while (first != i1)
   {
      if (*--i1 < *i2)
      {
         Iterator j = k;
         while (!(*i1 < *j)) ++j;
         std::iter_swap(i1,j);
         ++i1;
         ++j;
         i2 = k;
         std::rotate(i1,j,last);
         while (last != j)
         {
            ++j;
            ++i2;
         }
         std::rotate(k,i2,last);
         return true;
      }
   }
   std::rotate(first,k,last);
   return false;
}

Il peut être utilisé comme ceci:

#include <string>
#include <iostream>

int main()
{
    std::string s = "12345";
    std::size_t comb_size = 3;
    do
    {
        std::cout << std::string(s.begin(), s.begin() + comb_size) << std::endl;
    } while (next_combination(s.begin(), s.begin() + comb_size, s.end()));

    return 0;
}

Cela imprimera ce qui suit:

123
124
125
134
135
145
234
235
245
345

1
Qu'est-ce qui commence, qu'est-ce qui se termine dans ce cas? Comment peut-il réellement retourner quelque chose si toutes les variables passées à cette fonction sont passées par valeur?
Sergej Andrejev

6
@Sergej Andrejev: remplacer beinget beginavec s.begin(), et endavec s.end(). Le code suit de près l' next_permutationalgorithme de STL , décrit ici plus en détail.
Anthony Labarre

5
Qu'est-ce qui se passe? i1 = dernier; --i1; i1 = k;
Manoj R

24

J'ai trouvé ce fil utile et j'ai pensé ajouter une solution Javascript que vous pouvez insérer dans Firebug. Selon votre moteur JS, cela peut prendre un peu de temps si la chaîne de départ est grande.

function string_recurse(active, rest) {
    if (rest.length == 0) {
        console.log(active);
    } else {
        string_recurse(active + rest.charAt(0), rest.substring(1, rest.length));
        string_recurse(active, rest.substring(1, rest.length));
    }
}
string_recurse("", "abc");

La sortie doit être la suivante:

abc
ab
ac
a
bc
b
c

4
@NanoHead Ce n'est pas faux. La sortie affiche déjà "ac" - et "ca" est la même combinaison que "ac". Vous parlez de permutations (en mathématiques) où "ac" ne serait pas la même chose que "ca".
Jakob Jenkov

1
Ce n'est pas n choisissez k.
shinzou

20
static IEnumerable<string> Combinations(List<string> characters, int length)
{
    for (int i = 0; i < characters.Count; i++)
    {
        // only want 1 character, just return this one
        if (length == 1)
            yield return characters[i];

        // want more than one character, return this one plus all combinations one shorter
        // only use characters after the current one for the rest of the combinations
        else
            foreach (string next in Combinations(characters.GetRange(i + 1, characters.Count - (i + 1)), length - 1))
                yield return characters[i] + next;
    }
}

Belle solution. Je l'ai référencé en répondant à cette question récente: stackoverflow.com/questions/4472036/…
salarioghe

Le seul problème avec cette fonction est la récursivité. Bien que cela soit généralement bien pour les logiciels fonctionnant sur PC, si vous travaillez avec une plate-forme plus limitée en ressources (intégrée par exemple), vous n'avez pas de chance
Padu Merloti

Il allouera également un grand nombre de listes et fera beaucoup de travail pour dupliquer les éléments du tableau dans chaque nouveau. Il me semble que ces listes ne seront pas collectables tant que l'énumération ne sera pas terminée.
Niall Connaughton

C'est lisse. Avez-vous trouvé un algorithme ou est-ce à partir de zéro?
paparazzo

20

Petit exemple en Python:

def comb(sofar, rest, n):
    if n == 0:
        print sofar
    else:
        for i in range(len(rest)):
            comb(sofar + rest[i], rest[i+1:], n-1)

>>> comb("", "abcde", 3)
abc
abd
abe
acd
ace
ade
bcd
bce
bde
cde

Pour explication, la méthode récursive est décrite avec l'exemple suivant:

Exemple: ABCDE
Toutes les combinaisons de 3 seraient:

  • A avec toutes les combinaisons de 2 du reste (BCDE)
  • B avec toutes les combinaisons de 2 du reste (CDE)
  • C avec toutes les combinaisons de 2 du reste (DE)

17

Algorithme récursif simple dans Haskell

import Data.List

combinations 0 lst = [[]]
combinations n lst = do
    (x:xs) <- tails lst
    rest   <- combinations (n-1) xs
    return $ x : rest

Nous définissons d'abord le cas particulier, c'est-à-dire la sélection de zéro élément. Il produit un seul résultat, qui est une liste vide (c'est-à-dire une liste qui contient une liste vide).

Pour n> 0, xpasse par chaque élément de la liste et xsest chaque élément après x.

restsélectionne des n - 1éléments à xspartir d'un appel récursif à combinations. Le résultat final de la fonction est une liste où chaque élément est x : rest(c'est-à-dire une liste qui a xcomme tête et restcomme queue) pour chaque valeur différente de xet rest.

> combinations 3 "abcde"
["abc","abd","abe","acd","ace","ade","bcd","bce","bde","cde"]

Et bien sûr, comme Haskell est paresseux, la liste est progressivement générée selon les besoins, de sorte que vous pouvez évaluer partiellement des combinaisons exponentiellement grandes.

> let c = combinations 8 "abcdefghijklmnopqrstuvwxyz"
> take 10 c
["abcdefgh","abcdefgi","abcdefgj","abcdefgk","abcdefgl","abcdefgm","abcdefgn",
 "abcdefgo","abcdefgp","abcdefgq"]

13

Et voici grand-papa COBOL, la langue très décriée.

Supposons un tableau de 34 éléments de 8 octets chacun (sélection purement arbitraire.) L'idée est d'énumérer toutes les combinaisons possibles de 4 éléments et de les charger dans un tableau.

Nous utilisons 4 indices, un pour chaque position dans le groupe de 4

Le tableau est traité comme ceci:

    idx1 = 1
    idx2 = 2
    idx3 = 3
    idx4 = 4

Nous varions idx4 de 4 à la fin. Pour chaque idx4, nous obtenons une combinaison unique de groupes de quatre. Lorsque idx4 arrive à la fin du tableau, nous incrémentons idx3 de 1 et définissons idx4 sur idx3 + 1. Ensuite, nous exécutons à nouveau idx4 jusqu'à la fin. Nous procédons de cette manière, en augmentant idx3, idx2 et idx1 respectivement jusqu'à ce que la position de idx1 soit inférieure à 4 à partir de la fin du tableau. Cela termine l'algorithme.

1          --- pos.1
2          --- pos 2
3          --- pos 3
4          --- pos 4
5
6
7
etc.

Premières itérations:

1234
1235
1236
1237
1245
1246
1247
1256
1257
1267
etc.

Un exemple COBOL:

01  DATA_ARAY.
    05  FILLER     PIC X(8)    VALUE  "VALUE_01".
    05  FILLER     PIC X(8)    VALUE  "VALUE_02".
  etc.
01  ARAY_DATA    OCCURS 34.
    05  ARAY_ITEM       PIC X(8).

01  OUTPUT_ARAY   OCCURS  50000   PIC X(32).

01   MAX_NUM   PIC 99 COMP VALUE 34.

01  INDEXXES  COMP.
    05  IDX1            PIC 99.
    05  IDX2            PIC 99.
    05  IDX3            PIC 99.
    05  IDX4            PIC 99.
    05  OUT_IDX   PIC 9(9).

01  WHERE_TO_STOP_SEARCH          PIC 99  COMP.

* Stop the search when IDX1 is on the third last array element:

COMPUTE WHERE_TO_STOP_SEARCH = MAX_VALUE - 3     

MOVE 1 TO IDX1

PERFORM UNTIL IDX1 > WHERE_TO_STOP_SEARCH
   COMPUTE IDX2 = IDX1 + 1
   PERFORM UNTIL IDX2 > MAX_NUM
      COMPUTE IDX3 = IDX2 + 1
      PERFORM UNTIL IDX3 > MAX_NUM
         COMPUTE IDX4 = IDX3 + 1
         PERFORM UNTIL IDX4 > MAX_NUM
            ADD 1 TO OUT_IDX
            STRING  ARAY_ITEM(IDX1)
                    ARAY_ITEM(IDX2)
                    ARAY_ITEM(IDX3)
                    ARAY_ITEM(IDX4)
                    INTO OUTPUT_ARAY(OUT_IDX)
            ADD 1 TO IDX4
         END-PERFORM
         ADD 1 TO IDX3
      END-PERFORM
      ADD 1 TO IDX2
   END_PERFORM
   ADD 1 TO IDX1
END-PERFORM.

mais pourquoi {} {} {} {}
shinzou

9

Voici une implémentation élégante et générique dans Scala, comme décrit dans 99 Problèmes Scala .

object P26 {
  def flatMapSublists[A,B](ls: List[A])(f: (List[A]) => List[B]): List[B] = 
    ls match {
      case Nil => Nil
      case sublist@(_ :: tail) => f(sublist) ::: flatMapSublists(tail)(f)
    }

  def combinations[A](n: Int, ls: List[A]): List[List[A]] =
    if (n == 0) List(Nil)
    else flatMapSublists(ls) { sl =>
      combinations(n - 1, sl.tail) map {sl.head :: _}
    }
}

9

Si vous pouvez utiliser la syntaxe SQL - par exemple, si vous utilisez LINQ pour accéder aux champs d'une structure ou d'un tableau, ou accédez directement à une base de données qui a une table appelée "Alphabet" avec un seul champ de caractères "Letter", vous pouvez adapter ce qui suit code:

SELECT A.Letter, B.Letter, C.Letter
FROM Alphabet AS A, Alphabet AS B, Alphabet AS C
WHERE A.Letter<>B.Letter AND A.Letter<>C.Letter AND B.Letter<>C.Letter
AND A.Letter<B.Letter AND B.Letter<C.Letter

Cela renverra toutes les combinaisons de 3 lettres, quel que soit le nombre de lettres que vous avez dans le tableau "Alphabet" (cela peut être 3, 8, 10, 27, etc.).

Si ce que vous voulez, ce sont toutes les permutations, plutôt que les combinaisons (c'est-à-dire que vous voulez que "ACB" et "ABC" comptent comme différents, plutôt que d'apparaître une seule fois) supprimez simplement la dernière ligne (la ET) et c'est fait.

Post-édition: Après avoir relu la question, je me rends compte que ce qui est nécessaire est l' algorithme général , pas seulement spécifique pour le cas de la sélection de 3 éléments. La réponse d'Adam Hughes est la réponse complète, malheureusement je ne peux pas (encore) voter. Cette réponse est simple mais ne fonctionne que lorsque vous voulez exactement 3 éléments.


7

Une autre version C # avec génération paresseuse des indices de combinaison. Cette version gère un seul tableau d'index pour définir un mappage entre la liste de toutes les valeurs et les valeurs de la combinaison actuelle, c'est-à-dire utilise constamment O (k) d' espace supplémentaire pendant toute la durée d'exécution. Le code génère des combinaisons individuelles, y compris la première, dans O (k) .

public static IEnumerable<T[]> Combinations<T>(this T[] values, int k)
{
    if (k < 0 || values.Length < k)
        yield break; // invalid parameters, no combinations possible

    // generate the initial combination indices
    var combIndices = new int[k];
    for (var i = 0; i < k; i++)
    {
        combIndices[i] = i;
    }

    while (true)
    {
        // return next combination
        var combination = new T[k];
        for (var i = 0; i < k; i++)
        {
            combination[i] = values[combIndices[i]];
        }
        yield return combination;

        // find first index to update
        var indexToUpdate = k - 1;
        while (indexToUpdate >= 0 && combIndices[indexToUpdate] >= values.Length - k + indexToUpdate)
        {
            indexToUpdate--;
        }

        if (indexToUpdate < 0)
            yield break; // done

        // update combination indices
        for (var combIndex = combIndices[indexToUpdate] + 1; indexToUpdate < k; indexToUpdate++, combIndex++)
        {
            combIndices[indexToUpdate] = combIndex;
        }
    }
}

Code de test:

foreach (var combination in new[] {'a', 'b', 'c', 'd', 'e'}.Combinations(3))
{
    System.Console.WriteLine(String.Join(" ", combination));
}

Production:

a b c
a b d
a b e
a c d
a c e
a d e
b c d
b c e
b d e
c d e

Cela préserve la commande. Je m'attends à ce que l'ensemble de résultats contienne également c b ace qu'il ne contient pas.
Dmitri Nesteruk,

La tâche consiste à générer toutes les combinaisons qui satisfont n sur k. Les coefficients binomiaux répondent à la question du nombre de façons de choisir un sous-ensemble non ordonné de k éléments parmi un ensemble fixe de n éléments. Par conséquent, l'algorithme proposé fait ce qu'il devrait.
Christoph

6

https://gist.github.com/3118596

Il existe une implémentation pour JavaScript. Il a des fonctions pour obtenir des k-combinaisons et toutes les combinaisons d'un tableau d'objets. Exemples:

k_combinations([1,2,3], 2)
-> [[1,2], [1,3], [2,3]]

combinations([1,2,3])
-> [[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]

6

Voici une version évaluée paresseuse de cet algorithme codé en C #:

    static bool nextCombination(int[] num, int n, int k)
    {
        bool finished, changed;

        changed = finished = false;

        if (k > 0)
        {
            for (int i = k - 1; !finished && !changed; i--)
            {
                if (num[i] < (n - 1) - (k - 1) + i)
                {
                    num[i]++;
                    if (i < k - 1)
                    {
                        for (int j = i + 1; j < k; j++)
                        {
                            num[j] = num[j - 1] + 1;
                        }
                    }
                    changed = true;
                }
                finished = (i == 0);
            }
        }

        return changed;
    }

    static IEnumerable Combinations<T>(IEnumerable<T> elements, int k)
    {
        T[] elem = elements.ToArray();
        int size = elem.Length;

        if (k <= size)
        {
            int[] numbers = new int[k];
            for (int i = 0; i < k; i++)
            {
                numbers[i] = i;
            }

            do
            {
                yield return numbers.Select(n => elem[n]);
            }
            while (nextCombination(numbers, size, k));
        }
    }

Et partie test:

    static void Main(string[] args)
    {
        int k = 3;
        var t = new[] { "dog", "cat", "mouse", "zebra"};

        foreach (IEnumerable<string> i in Combinations(t, k))
        {
            Console.WriteLine(string.Join(",", i));
        }
    }

J'espère que cela vous aidera!


6

J'avais un algorithme de permutation que j'ai utilisé pour le projet euler, en python:

def missing(miss,src):
    "Returns the list of items in src not present in miss"
    return [i for i in src if i not in miss]


def permutation_gen(n,l):
    "Generates all the permutations of n items of the l list"
    for i in l:
        if n<=1: yield [i]
        r = [i]
        for j in permutation_gen(n-1,missing([i],l)):  yield r+j

Si

n<len(l) 

vous devriez avoir toutes les combinaisons dont vous avez besoin sans répétition, en avez-vous besoin?

C'est un générateur, vous l'utilisez donc dans quelque chose comme ceci:

for comb in permutation_gen(3,list("ABCDEFGH")):
    print comb 

5
Array.prototype.combs = function(num) {

    var str = this,
        length = str.length,
        of = Math.pow(2, length) - 1,
        out, combinations = [];

    while(of) {

        out = [];

        for(var i = 0, y; i < length; i++) {

            y = (1 << i);

            if(y & of && (y !== of))
                out.push(str[i]);

        }

        if (out.length >= num) {
           combinations.push(out);
        }

        of--;
    }

    return combinations;
}

5

Version Clojure:

(defn comb [k l]
  (if (= 1 k) (map vector l)
      (apply concat
             (map-indexed
              #(map (fn [x] (conj x %2))
                    (comb (dec k) (drop (inc %1) l)))
              l))))

5

Disons que votre tableau de lettres ressemble à ceci: "ABCDEFGH". Vous disposez de trois indices (i, j, k) indiquant quelles lettres vous allez utiliser pour le mot courant, vous commencez par:

ABCDEFGH
^ ^ ^
ijk

D'abord, vous faites varier k, donc la prochaine étape ressemble à ceci:

ABCDEFGH
^ ^ ^
ijk

Si vous avez atteint la fin, continuez et faites varier j puis k à nouveau.

ABCDEFGH
^ ^ ^
ijk

ABCDEFGH
^ ^ ^
ijk

Une fois que vous avez atteint G, vous commencez également à varier i.

ABCDEFGH
  ^ ^ ^
  ijk

ABCDEFGH
  ^ ^ ^
  ijk
...
function initializePointers($cnt) {
    $pointers = [];

    for($i=0; $i<$cnt; $i++) {
        $pointers[] = $i;
    }

    return $pointers;     
}

function incrementPointers(&$pointers, &$arrLength) {
    for($i=0; $i<count($pointers); $i++) {
        $currentPointerIndex = count($pointers) - $i - 1;
        $currentPointer = $pointers[$currentPointerIndex];

        if($currentPointer < $arrLength - $i - 1) {
           ++$pointers[$currentPointerIndex];

           for($j=1; ($currentPointerIndex+$j)<count($pointers); $j++) {
                $pointers[$currentPointerIndex+$j] = $pointers[$currentPointerIndex]+$j;
           }

           return true;
        }
    }

    return false;
}

function getDataByPointers(&$arr, &$pointers) {
    $data = [];

    for($i=0; $i<count($pointers); $i++) {
        $data[] = $arr[$pointers[$i]];
    }

    return $data;
}

function getCombinations($arr, $cnt)
{
    $len = count($arr);
    $result = [];
    $pointers = initializePointers($cnt);

    do {
        $result[] = getDataByPointers($arr, $pointers);
    } while(incrementPointers($pointers, count($arr)));

    return $result;
}

$result = getCombinations([0, 1, 2, 3, 4, 5], 3);
print_r($result);

Basé sur https://stackoverflow.com/a/127898/2628125 , mais plus abstrait, pour n'importe quelle taille de pointeurs.


Quelle est cette horrible langue? Frapper?
shinzou

1
php, mais la langue n'a pas d'importance ici, l'algorithme le fait
Oleksandr Knyga

Je suis tellement content que je refuse d'apprendre cette langue. Un langage où son interprète / compilateur a besoin d'aide pour reconnaître les variables ne devrait pas exister en 2018.
shinzou

4

Tout est dit et fait ici vient le code O'caml pour cela. L'algorithme est évident d'après le code.

let combi n lst =
    let rec comb l c =
        if( List.length c = n) then [c] else
        match l with
        [] -> []
        | (h::t) -> (combi t (h::c))@(combi t c)
    in
        combi lst []
;;

4

Voici une méthode qui vous donne toutes les combinaisons de taille spécifiées à partir d'une chaîne de longueur aléatoire. Similaire à la solution de quinmars, mais fonctionne pour des entrées et k variés.

Le code peut être modifié pour envelopper, c'est-à-dire 'dab' de l'entrée 'abcd' wk = 3.

public void run(String data, int howMany){
    choose(data, howMany, new StringBuffer(), 0);
}


//n choose k
private void choose(String data, int k, StringBuffer result, int startIndex){
    if (result.length()==k){
        System.out.println(result.toString());
        return;
    }

    for (int i=startIndex; i<data.length(); i++){
        result.append(data.charAt(i));
        choose(data,k,result, i+1);
        result.setLength(result.length()-1);
    }
}

Sortie pour "abcde":

abc abd abe acd ace ade bcd bce bde cde


4

code python court, donnant des positions d'index

def yield_combos(n,k):
    # n is set size, k is combo size

    i = 0
    a = [0]*k

    while i > -1:
        for j in range(i+1, k):
            a[j] = a[j-1]+1
        i=j
        yield a
        while a[i] == i + n - k:
            i -= 1
        a[i] += 1

C'est très élégant / efficace et fonctionne bien. Je viens de le traduire en C ++.
Chaton accroupi


3

Voici ma proposition en C ++

J'ai essayé d'imposer aussi peu de restrictions que possible sur le type d'itérateur, de sorte que cette solution suppose simplement un itérateur de transfert, et il peut s'agir d'un const_iterator. Cela devrait fonctionner avec n'importe quel conteneur standard. Dans les cas où les arguments n'ont pas de sens, il lance std :: invalid_argumnent

#include <vector>
#include <stdexcept>

template <typename Fci> // Fci - forward const iterator
std::vector<std::vector<Fci> >
enumerate_combinations(Fci begin, Fci end, unsigned int combination_size)
{
    if(begin == end && combination_size > 0u)
        throw std::invalid_argument("empty set and positive combination size!");
    std::vector<std::vector<Fci> > result; // empty set of combinations
    if(combination_size == 0u) return result; // there is exactly one combination of
                                              // size 0 - emty set
    std::vector<Fci> current_combination;
    current_combination.reserve(combination_size + 1u); // I reserve one aditional slot
                                                        // in my vector to store
                                                        // the end sentinel there.
                                                        // The code is cleaner thanks to that
    for(unsigned int i = 0u; i < combination_size && begin != end; ++i, ++begin)
    {
        current_combination.push_back(begin); // Construction of the first combination
    }
    // Since I assume the itarators support only incrementing, I have to iterate over
    // the set to get its size, which is expensive. Here I had to itrate anyway to  
    // produce the first cobination, so I use the loop to also check the size.
    if(current_combination.size() < combination_size)
        throw std::invalid_argument("combination size > set size!");
    result.push_back(current_combination); // Store the first combination in the results set
    current_combination.push_back(end); // Here I add mentioned earlier sentinel to
                                        // simplyfy rest of the code. If I did it 
                                        // earlier, previous statement would get ugly.
    while(true)
    {
        unsigned int i = combination_size;
        Fci tmp;                            // Thanks to the sentinel I can find first
        do                                  // iterator to change, simply by scaning
        {                                   // from right to left and looking for the
            tmp = current_combination[--i]; // first "bubble". The fact, that it's 
            ++tmp;                          // a forward iterator makes it ugly but I
        }                                   // can't help it.
        while(i > 0u && tmp == current_combination[i + 1u]);

        // Here is probably my most obfuscated expression.
        // Loop above looks for a "bubble". If there is no "bubble", that means, that
        // current_combination is the last combination, Expression in the if statement
        // below evaluates to true and the function exits returning result.
        // If the "bubble" is found however, the ststement below has a sideeffect of 
        // incrementing the first iterator to the left of the "bubble".
        if(++current_combination[i] == current_combination[i + 1u])
            return result;
        // Rest of the code sets posiotons of the rest of the iterstors
        // (if there are any), that are to the right of the incremented one,
        // to form next combination

        while(++i < combination_size)
        {
            current_combination[i] = current_combination[i - 1u];
            ++current_combination[i];
        }
        // Below is the ugly side of using the sentinel. Well it had to haave some 
        // disadvantage. Try without it.
        result.push_back(std::vector<Fci>(current_combination.begin(),
                                          current_combination.end() - 1));
    }
}

3

Voici un code que j'ai récemment écrit en Java, qui calcule et renvoie toute la combinaison des éléments "num" à partir des éléments "outOf".

// author: Sourabh Bhat (heySourabh@gmail.com)

public class Testing
{
    public static void main(String[] args)
    {

// Test case num = 5, outOf = 8.

        int num = 5;
        int outOf = 8;
        int[][] combinations = getCombinations(num, outOf);
        for (int i = 0; i < combinations.length; i++)
        {
            for (int j = 0; j < combinations[i].length; j++)
            {
                System.out.print(combinations[i][j] + " ");
            }
            System.out.println();
        }
    }

    private static int[][] getCombinations(int num, int outOf)
    {
        int possibilities = get_nCr(outOf, num);
        int[][] combinations = new int[possibilities][num];
        int arrayPointer = 0;

        int[] counter = new int[num];

        for (int i = 0; i < num; i++)
        {
            counter[i] = i;
        }
        breakLoop: while (true)
        {
            // Initializing part
            for (int i = 1; i < num; i++)
            {
                if (counter[i] >= outOf - (num - 1 - i))
                    counter[i] = counter[i - 1] + 1;
            }

            // Testing part
            for (int i = 0; i < num; i++)
            {
                if (counter[i] < outOf)
                {
                    continue;
                } else
                {
                    break breakLoop;
                }
            }

            // Innermost part
            combinations[arrayPointer] = counter.clone();
            arrayPointer++;

            // Incrementing part
            counter[num - 1]++;
            for (int i = num - 1; i >= 1; i--)
            {
                if (counter[i] >= outOf - (num - 1 - i))
                    counter[i - 1]++;
            }
        }

        return combinations;
    }

    private static int get_nCr(int n, int r)
    {
        if(r > n)
        {
            throw new ArithmeticException("r is greater then n");
        }
        long numerator = 1;
        long denominator = 1;
        for (int i = n; i >= r + 1; i--)
        {
            numerator *= i;
        }
        for (int i = 2; i <= n - r; i++)
        {
            denominator *= i;
        }

        return (int) (numerator / denominator);
    }
}

3

Une solution Javascript concise:

Array.prototype.combine=function combine(k){    
    var toCombine=this;
    var last;
    function combi(n,comb){             
        var combs=[];
        for ( var x=0,y=comb.length;x<y;x++){
            for ( var l=0,m=toCombine.length;l<m;l++){      
                combs.push(comb[x]+toCombine[l]);           
            }
        }
        if (n<k-1){
            n++;
            combi(n,combs);
        } else{last=combs;}
    }
    combi(1,toCombine);
    return last;
}
// Example:
// var toCombine=['a','b','c'];
// var results=toCombine.combine(4);

3

Algorithme:

  • Comptez de 1 à 2 ^ n.
  • Convertissez chaque chiffre en sa représentation binaire.
  • Traduisez chaque bit «activé» en éléments de votre ensemble, en fonction de la position.

En C #:

void Main()
{
    var set = new [] {"A", "B", "C", "D" }; //, "E", "F", "G", "H", "I", "J" };

    var kElement = 2;

    for(var i = 1; i < Math.Pow(2, set.Length); i++) {
        var result = Convert.ToString(i, 2).PadLeft(set.Length, '0');
        var cnt = Regex.Matches(Regex.Escape(result),  "1").Count; 
        if (cnt == kElement) {
            for(int j = 0; j < set.Length; j++)
                if ( Char.GetNumericValue(result[j]) == 1)
                    Console.Write(set[j]);
            Console.WriteLine();
        }
    }
}

Pourquoi ça marche?

Il existe une bijection entre les sous-ensembles d'un ensemble de n éléments et les séquences de n bits.

Cela signifie que nous pouvons déterminer le nombre de sous-ensembles en comptant les séquences.

Par exemple, les quatre éléments définis ci-dessous peuvent être représentés par {0,1} X {0, 1} X {0, 1} X {0, 1} (ou 2 ^ 4) séquences différentes.

Donc - tout ce que nous avons à faire est de compter de 1 à 2 ^ n pour trouver toutes les combinaisons. (Nous ignorons l'ensemble vide.) Ensuite, traduisez les chiffres en leur représentation binaire. Ensuite, remplacez les éléments de votre ensemble par des bits «on».

Si vous ne souhaitez que des résultats sur k éléments, imprimez uniquement lorsque k bits sont «activés».

(Si vous voulez tous les sous-ensembles au lieu des sous-ensembles de longueur k, supprimez la partie cnt / kElement.)

(Pour la preuve, voir le didacticiel gratuit MIT Mathematics for Computer Science, Lehman et al, section 11.2.2. Https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-042j-mathematics- for-computer-science-fall-2010 / lectures / )


2

J'ai écrit une classe pour gérer les fonctions courantes pour travailler avec le coefficient binomial, qui est le type de problème auquel votre problème appartient. Il effectue les tâches suivantes:

  1. Sort tous les K-index dans un format sympa pour tout N choisissez K dans un fichier. Les index K peuvent être remplacés par des chaînes ou des lettres plus descriptives. Cette méthode rend la résolution de ce type de problème assez triviale.

  2. Convertit les K-index en l'index approprié d'une entrée dans la table des coefficients binomiaux triés. Cette technique est beaucoup plus rapide que les anciennes techniques publiées qui reposent sur l'itération. Il le fait en utilisant une propriété mathématique inhérente au Triangle de Pascal. Mon article en parle. Je crois que je suis le premier à découvrir et publier cette technique, mais je peux me tromper.

  3. Convertit l'index d'une table de coefficients binomiaux triés en index K correspondants.

  4. Utilise la méthode Mark Dominus pour calculer le coefficient binomial, qui est beaucoup moins susceptible de déborder et fonctionne avec de plus grands nombres.

  5. La classe est écrite en .NET C # et fournit un moyen de gérer les objets liés au problème (le cas échéant) en utilisant une liste générique. Le constructeur de cette classe prend une valeur booléenne appelée InitTable qui, lorsqu'elle est vraie, créera une liste générique pour contenir les objets à gérer. Si cette valeur est fausse, elle ne créera pas la table. Il n'est pas nécessaire de créer le tableau pour exécuter les 4 méthodes ci-dessus. Des méthodes d'accesseur sont fournies pour accéder à la table.

  6. Il existe une classe de test associée qui montre comment utiliser la classe et ses méthodes. Il a été largement testé avec 2 cas et il n'y a aucun bogue connu.

Pour en savoir plus sur cette classe et télécharger le code, voir Tablizing The Binomial Coeffieicent .

Il ne devrait pas être difficile de convertir cette classe en C ++.


Ce n'est vraiment pas correct de l'appeler la "méthode Mark Dominus", car comme je l'ai mentionné, elle a au moins 850 ans et n'est pas si difficile à penser. Pourquoi ne pas l'appeler la méthode Lilavati ?
MJD
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.