Transform result combination function into a dynamic function

I have a function that generates all possible combinations of numbers from 1 to 10. The combinations are 4 numbers, that is, each combination has 4 different numbers. Ex:

[1,2,3,4], [1,2,3,5], [1,2,3,6] ... [7,8,9,10];

I can specify the range of possible numbers, which at the moment is from 1 to 10.

My problem is that I cannot specify the amount of numbers that will make the combination. Which is currently 4 to another number.

I've broken my head to try to leave the dynamic function and only pass the parameters(in this case it would be the range of numbers and the amount of numbers of a combination), like this:

GerarCombinacoes(10, 4); //onde 10 seria o range de numeros de 1 a 10 e 4 a quantidade de numeros da combinacao

My function is like this at the moment:

function GerarCombinacoes() {
    totalCombinacoes = 0;
    numeroMaximo = 10; //range de 1 a 10

    for (a = 1; a <= numeroMaximo ; a++) {
        for (b = 2; b <= numeroMaximo ; b++) {
            for (c = 3; c <= numeroMaximo ; c++) {
                for (d = 4; d <= numeroMaximo ; d++) {
                    if ((a != d && a != c && a != b)) {
                        if ((b != d && b != c && b != a) && b > a) {
                            if ((c != d && c != b && c != a) && c > b) {
                                if ((d != c && d != b && d != a) && d > c) {
                                    numeros = a + " - " + b + " - " + c + " - " + d;
                                    totalCombinacoes++;
                                    $("#Numeros").append("<p style='margin-left: 10px'>("+ totalCombinacoes+ ") " + numeros + "</p>");
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    alert(totalCombinacoes);
}  

If I want to increase the amount of numbers of the combination I need to put one more for and one more check to not repeat numbers, but so it gets complicated because there are some cases that I would need to do more than 20 for everything in mao.

Author: Joao Paulo, 2014-09-04

6 answers

This is the kind of situation that a recursive solution would be more elegant than an iterative one. I will suggest a very simple implementation, then refine it to become more flexible and efficient:

function combinacoes(a, b, m, acc, retorno) {
    // acc são os elementos que já fazem parte da combinação
    if ( acc == undefined ) acc = []; // no início, ela começa vazia
    // retorno é a lista de combinações encontradas
    if ( retorno === undefined ) retorno = []; // idem
    if ( m == 0 ) {        // se o número de elementos a serem inseridos chegou a zero
        retorno.push(acc); // coloque a combinação atual na lista
        return retorno;    // e retorne
    }
    if ( a > b )           // se a passou b, não existem mais combinações
        return retorno;    // retorne o que foi achado anteriormente

    // Primeiro fazemos todas as combinações que incluem a
    // i.e. combinamos a com as combinações de tamanho m-1
    combinacoes(a+1, b, m-1, acc.concat([a]), retorno);

    // Depois todas as que não incluem a
    // i.e. ignoramos a, e fazemos as combinações de a+1 pra frente
    return combinacoes(a+1, b, m, acc, retorno);
}

Example in jsFiddle . The fact that the solution is recursive has no impact on efficiency, since the number of combinations of N elements m to m is so large (have you heard the expression "combinatorial explosion"?) that long before you have problems of stack overflow you will have problems with lack of memory....

Now I'm going to refine it a bit so as not to generate all the combinations at once; instead, I'm going to create a "paging system" where the function only generates a small subset of the results at a time:

// Função auxiliar: quantas combinações existem de n elementos m a m?
// n! / (m! * (n-m)!)
function quantas(n, m) {
    m = ( m < n - m ? m : n - m ); // Para facilitar o cálculo... (pois é simétrico)
    var dividendo = 1;
    var divisor = 1;
    for ( var i = 2 ; i <= n ; i++ ) {
        if ( i <= m )
            divisor *= i;
        if ( n-m < i )
            dividendo *= i;
    }
    return dividendo / divisor;
}

function combinacoes(a, b, m, inicio, fim, acc, retorno) {
    if ( inicio === undefined ) inicio = 0;
    if ( fim === undefined ) fim = quantas(b-a+1, m);
    if ( fim <= 0 )
        return retorno;
    ...
    // Primeiro fazemos todas as combinações que incluem a
    if ( inicio < quantas(b-a, m-1) )
        combinacoes(a+1, b, m-1, inicio, fim, acc.concat([a]), retorno);
    // Depois todas as que não incluem a
    inicio -= quantas(b-a, m-1);
    fim -= quantas(b-a, m-1);
    return combinacoes(a+1, b, m, inicio, fim, acc, retorno);
}

Example in jsFiddle . Experiment with an "absurdly large" number, such as combinations from 1 to 1000, 100 to 100. (Note: there are 6.38 × 10^139 results - greater than fits in a double - by that the above method will "skip" some results... but most of them will come right)

 6
Author: mgibsonbr, 2014-09-05 17:43:58

Forget for a moment that we are talking about combinations of numbers.

Let's say we have N elements, and that I want to calculate how many possibilities of combinations containing M elements are possible.

This is simply binary calculation.

I can ask myself another way: in N bits, I want all numbers with M bits attached.

The function to generate all the binary maps of the combinations would be like this:

function GenerateCombinations(totalElements, elementCount) {

    console.debug('Total de elementos: ' + totalElements);
    console.debug('Elementos por combinação: ' + elementCount);

    var res = [];

    for (i = 0; i < Math.pow(2, totalElements); i++) { 

        var probe = i.toString(2).replace(new RegExp('0', 'g'), '');

        if(probe.length == elementCount)
            res.push(i);
    }    

    console.debug(res.length + ' combinações encontradas.');

    return res;
}

And finally the function that performs the combination of elements:

function CombineElements(collection, elementCount) {
    var combinations = GenerateCombinations(collection.length, elementCount);

    var res = [];

    for(i = 0; i < combinations.length; i++) {

        bitmapIndex = combinations[i].toString(2).split("").reverse().join("");
        console.debug(i + ':' + bitmapIndex);
        var arItem = '';

        for(j = 0; j < bitmapIndex.length + 1; j++)
        {
            if (bitmapIndex.substring(j,j+1) == '1')
                arItem += collection[j];
        }

        res.push(arItem);
    }
    return res;
}

Some examples:

Collections of 2 items among 4 elements:

CombineElements([1,2,3,4], 2)

Total de elementos: 4  
Elementos por combinação: 2  
6 combinações encontradas.  
["12", "13", "23", "14", "24", "34"] 

Collections of 3 items among 6 elements:

CombineElements([1,2,3,4,5,6], 3)

Total de elementos: 6
Elementos por combinação: 3
20 combinações encontradas.
["123", "124", "134", "234", "125", "135", "235", "145", "245", "345", "126", "136", "236", "146", "246", "346", "156", "256", "356", "456"]

Code not JSFiddle.

 5
Author: OnoSendai, 2014-09-05 00:17:57

Follows a combination function that allows you to use an array as data input:

function combinations( inpSet, qty ) {
  var inpLen = inpSet.length;
  var combi = [];
  var result = []; 
  var recurse = function( left, right ) {
    if ( right == 0 ) {
      result.push( combi.slice(0) );
    } else {
      for ( var i = left; i <= inpLen - right; i++ ) {
        combi.push( inpSet[i] );
        recurse( i + 1, right - 1 );
        combi.pop();
      }
    }
  }
  recurse( 0, qty );
  return result;
}

// Teste:
document.body.innerHTML += JSON.stringify(
  combinations( ['a','b','c','d'] , 3 )
);

This way, you can exchange the input for the values you want.

 4
Author: Bacco, 2014-12-07 22:46:04

I do not know JS, but I will explain a solution and show in C++, it should not be difficult to translate.

One caveat: this type of algorithm has a disgusting complexity and soon stops working. For something less worse, you can try some of the solutions here .

The algorithm works like this:

  1. first we need a structure to store the permutations. In my case it will be a set, A C++ structure that allows you to store a single copy of each element in it (as a set of mathematics).
  2. we will also need another set to save the combinacao atual we are generating.
  3. now for the algorithm itself:
    1. we will have a recursive function that works like this: for each element x of the list that goes from 1 up to its limit N, it tries to insert x into a set that is storing the combination up to here. If she can put x (which may not happen, since x can already be in the combination), it sees if the total size of the set is equal to the number of elements you want in the combination.
    2. if the set size is equal, store it in the combination vector. If not, call the same function with the set with the new element.
    3. when the recursive call returns, it removes from the set the element it placed (in the case x).

When this thing is over, you'll have all the combinations in the vector.

The code in C++:

#include <iostream>
#include <set>
using namespace std;

set< set<int> > combinacoes;

void gen(int limite, unsigned qtos, set<int>& ate_aqui) {
    for (int i = 1; i <= limite; ++i) {
    auto it = ate_aqui.insert(i);
    if (it.second == false) continue;
    if (ate_aqui.size() == qtos)
        combinacoes.insert(ate_aqui);
    else
        gen(limite, qtos, ate_aqui);
    ate_aqui.erase(ate_aqui.find(i));
    }
}

int main() {
    set<int> v;
    gen(4, 3, v);
    for (auto &c : combinacoes) {
        for (auto &x : c) cout << x << ' ';
        cout << endl;
    }
    return 0;
}

When I trim this to gen(4, 3, v), i.e. all combinations of 1 to 4 with 3 elements, the output is:

1 2 3 
1 2 4 
1 3 4 
2 3 4 

I don't know if it helps, but if not someone already posts something more specific to JS.

 3
Author: Lucas Virgili, 2017-05-23 12:37:23

To generate all possible combinations, including repeated numbers, use the excerpt below. The parameter gama denotes the number of options for each cell (that is, each cell will be filled with a value from 1 to gama). Already tamanho represents the length of each row of the resulting array, that is, how many numbers from 1 to gama should be added to the array.

function GerarCombinacoes(gama, tamanho){
    var arr = [];
    for(var i = 0; i < Math.pow(gama, tamanho); i++){
        arr[i] = [];
        for(var j = 0; j < tamanho; j++){
            arr[i].push(Math.floor(i / Math.pow(gama, tamanho - j - 1)) % gama + 1);
        }
        arr[i] = arr[i].sort();
    }
    arr = arr.sort();

Note that the sort() show the same rows as they arrange the array vertically and horizontally. Add a filter to the resulting array to exclude all rows that have repeated elements or that are equal to the top row (which, after sort(), ensures that there will be only single rows).

    arr = arr.filter(function(linha, ind, array){
        var igual_linha_acima = true;
        for(var i = 0; i < linha.length; i++){
            for(var j = 0; j < linha.length; j++){
                if(i != j && linha[i] == linha[j]) return false;
            }
            if(linha[i] != array[ind-1][i]) igual_linha_acima = false;
        }
        if(igual_linha_acima) return false;
        return true;
    });

Finally, just return the resulting combination array.

    return arr;
}

Any questions, just ask that I detail more :)

Edit: I edited the answer to ensure single rows, and I also prepared a jsfiddle .

 2
Author: Rui Pimentel, 2014-09-04 22:15:45

Opps, 3 years late! a functional version in Python but easily javascriptible:

def p(comp,max,pref=[],min=1):
   if comp==0 :
      print(pref)
   else:
      for x in range(min, max-comp+2):
         p(comp-1, max, pref+[x], x+1)

p(3,4)
 0
Author: JJoao, 2017-07-20 12:04:01