EDITION:
This solution will only be feasible for a limited number of items, not by
space, but by the processing time due to the large number of
possible combinations.
To know how many combinations can be found between all
items to be compared, this is the formula:
So, if the array $dados
for example only 30 items items, the number of possible combinations would be 2^30 - 1
= 1.073.741.823; if 100 items =
1,26765E+30!!!
One can also calculate how many combinations are possible per level, that is, combination with 2 numbers together, 3 numbers, etc. In this case the formula is:
... where n
= total number of items and k
= current level.
Ex: to know how many combinations are possible in a sequence of 5 numbers within level 2: 5! / (2! * (5 - 2)!)
= 10. That is, if we make a comparison for the sequence 1, 2, 3, 4, 5
, in level 2 of the comparison (1 2, 1 3, 1 4, 1 5, 2 3, 2 4, 2 5, 3 4, 3 5, 4 5)
= 10 items.
This second answer solves the RAM limitation question: Mysql!
If in the first answer I took 8 hours, I will not say how much I spent on this, otherwise they will think I’m crazy!... rsrsrs
I recreated the entire algorithm so that large arrays are used in database tables.
This way, the number of combinations is virtually unlimited, only depending on the disk space.
It is worth noting that although this second approach solves the problem of RAM bursting, the counterpart is that it will become absurdly slower. I mean, if you’re going to do the process with for example 100 values, put the computer to work and go to sleep... :)
Check out an example working in Mysql on this link: http://sandbox.sortemaniasudoeste.com.br/wp-content/php/comb.php
In short, to function, it is enough:
- Create a Mysql database (empty)
- Modify database data in source code
- Modify the value to be tested and the number of elements (this part you will replace with your original table)
Below the code:
<?php
$total = 3.6; // preencher com valor total a ser testado
$num_dados = 12; // preencher com o total de números que serão gerados para teste
$time_start = microtime(true); // cronometra o tempo total de processamento
// ****************** CONEXÃO COM BANCO DE DADOS **************************
$servername = "localhost"; // nome do servidor
$username = "root"; // nome do usuario
$password = ""; // senha
$dbname = "comb"; // nome do banco de dados
$db = mysqli_connect($servername, $username, $password, $dbname);
if (!$db) {
die("Falha na conexão: " . mysqli_connect_error());
}
cria_tabela($db, 'dados', 1);
cria_tabela($db, 'base', 2, 'int(10) UNSIGNED NOT NULL');
cria_tabela($db, 'resultados', 1, 'varchar(10000)');
gera_dados($db, $num_dados); // gera x registros de dados float
//******************** ALGORITMO ***********************
$cont_combina = 0;
echo "Para um total de $total:\n\n";
// carrega dados no array $dados --------------------
$result = mysqli_query($db, 'SELECT * FROM dados');
while ($row = mysqli_fetch_assoc($result))
$dados[] = $row['valor'];
// testa se existe o valor (sem soma) dentro de $dados
if (in_array($total, $dados))
echo "1 nível\nCombinação " . ++$cont_combina . ': ' . number_format($total, 2) . ' = ' . number_format($total, 2) . "\n\n";
// cria os primeiros valores de base-----------------
$key1 = 0; $key2 = 0;
for ($i = 0; $i < sizeof($dados) - 1; $i++) { // base são sempre a soma dos valores anteriores exceto 1 à direita que será comparado
$sql = "INSERT INTO base (key1, key2, valor) VALUES ($key1, 0, $i)";
mysqli_query($db, $sql) or die(mysqli_error($db) . "\nFalha ao inserir registro na tabela 'base'");
$key1++;
}
// processa as combinações gerais --------------------
for ($nivel = 1;$nivel < sizeof($dados) - 1; $nivel++) {
echo "-------------------------\n" . ($nivel+1) . " níveis (";
printf("%d", $nivel * 100 / (sizeof($dados) - 1));
echo "%)\n";
// soma os números de base-----------------------------
cria_tabela($db, 'soma_base', 1);
$sql = "INSERT INTO soma_base (key1, valor)
SELECT base.key1, SUM(dados.valor) FROM base
INNER JOIN dados ON base.valor = dados.key1
GROUP BY base.key1";
mysqli_query($db, $sql) or die(mysqli_error($db) . "\nFalha ao gerar registros na tabela 'soma_base'");
$result = mysqli_query($db, "SELECT * FROM soma_base ORDER BY key1 DESC LIMIT 1");
if (!$soma_base = mysqli_fetch_assoc($result)) die ("erro leitura tabela 'soma_base'");
$ult_combinacao = $soma_base['key1'];
// análise combinatória -----------------------------
cria_tabela($db, 'base_ant', 2, 'int(10) UNSIGNED NOT NULL');
$key1_base_ant = 0;
for ($combinacao = 0; $combinacao < $ult_combinacao; $combinacao++) {
// acha valor do último elemento da base atual para calcular o próximo
$result2 = mysqli_query($db, 'SELECT count(*) AS tot_elem_key1 FROM base WHERE key1 = ' . $combinacao); // total elementos key1
$base2 = mysqli_fetch_assoc($result2);
$tot_elem_key1 = $base2['tot_elem_key1'] - 1;
$result2 = mysqli_query($db, 'SELECT * FROM base WHERE key1 = ' . $combinacao . ' AND key2 < 9999999999 ORDER BY key1, key2 DESC'); //acha último elemento da base
if (!$base2 = mysqli_fetch_assoc($result2)) die ("não achou último elemento");
// $p = ponteiro para valor o valor a ser comparado. Ex: soma dos valores base + valor indicado pelo ponteiro
for ($p = $base2['valor'] + 1; $p < sizeof($dados); $p++) { // pega o valor do último elemento da base atual + 1 (próximo elemento)
$result3 = mysqli_query($db, 'SELECT * FROM soma_base WHERE key1 = ' . $combinacao); // acha soma deste nível
if (!$soma_base = mysqli_fetch_assoc($result3)) die ("não achou soma base");
// if ($soma_base['valor'] >= $total) break;
if (number_format($soma_base['valor'] + $dados[$p], 4) == number_format($total, 4)) { // *** encontrou combinação da soma ***
// mostra combinação encontrada -------------------
echo 'Combinação ' . ++$cont_combina . ': ';
$d = 0;
$result5 = mysqli_query($db, 'SELECT * FROM base WHERE key1 = ' . $combinacao);
$numeros = []; // armazena resultados num array para gravar em tabela
while ($base5 = mysqli_fetch_assoc($result5) and $d <= $tot_elem_key1) {
echo number_format($dados[$base5['valor']], 2) . ' + ';
$numeros[] = floatval($dados[$base5['valor']]);
$d++;
}
echo number_format($dados[$p], 2) . ' = ' . number_format($total, 2) . "\n";
$numeros[] = floatval($dados[$p]);
$sql = "INSERT INTO resultados (key1, valor) VALUES ($cont_combina, '" . serialize($numeros) . "')"; // grava resultado encontrado
mysqli_query($db, $sql) or die(mysqli_error($db) . "\nFalha ao inserir registro na tabela 'resultados'");
}
// transfere base para base_ant -------------------
$result4 = mysqli_query($db, 'SELECT * FROM base WHERE key1 = ' . $combinacao);
while ($base4 = mysqli_fetch_assoc($result4)) {
$key2 = $base4['key2'];
$valor = $base4['valor'];
$sql = "INSERT INTO base_ant (key1, key2, valor) VALUES ($key1_base_ant, $key2, $valor)";
mysqli_query($db, $sql) or die(mysqli_error($db) . "\nFalha ao inserir registro na tabela 'base_ant'");
$key2++;
}
$sql = "INSERT INTO base_ant (key1, key2, valor) VALUES ($key1_base_ant, $key2, $p)";
$key1_base_ant++;
mysqli_query($db, $sql) or die(mysqli_error($db) . "\nFalha ao inserir registro 2 na tabela 'base_ant'");
}
}
mysqli_query($db, "DROP TABLE base") or die(mysqli_error($db));
mysqli_query($db, "RENAME TABLE base_ant TO base") or die(mysqli_error($db));
}
mysqli_close($db);
echo "\n(100%)\n";
$time_end = microtime(true);
$execution_time = ($time_end - $time_start)/60;
echo "Tempo total de execução: " . number_format($execution_time, 2) . " Minutos\n";
return;
//---------------------------------------------------------------------------------------------
function cria_tabela($db, $tabela, $campos_chave, $tipo_valor = "float UNSIGNED NOT NULL") {
$sql = "DROP TABLE IF EXISTS $tabela";
mysqli_query($db, $sql) or die(mysqli_error($db));
$sql = "CREATE TABLE $tabela (";
if ($campos_chave > 0) $sql .= "key1 int(10) UNSIGNED NOT NULL";
if ($campos_chave > 1) $sql .= ", key2 int(10) UNSIGNED NOT NULL";
if ($campos_chave > 0) $sql .= ", ";
$sql .= "valor $tipo_valor)";
mysqli_query($db, $sql) or die(mysqli_error($db) . "\nNão foi possível criar tabela '$tabela'");
if ($campos_chave > 0) { // cria índice
$sql = "ALTER TABLE $tabela ADD PRIMARY KEY (key1";
if ($campos_chave > 1) $sql .= ",key2";
$sql .=")";
mysqli_query($db, $sql) or die(mysqli_error($db) . "\nNão foi possível criar índice para tabela '$tabela'");
}
}
function gera_dados($db, $num) {
for ($i = 1; $i<=$num; $i++) {
$sql = "INSERT INTO dados (key1, valor) VALUES (" . ($i - 1) . ", " . ($i / 10) . ")";
mysqli_query($db, $sql) or die(mysqli_error($db) . "\nFalha ao inserir registro na tabela 'dados'");
}
}
?>
Just to confirm, although your example shows only 2 values per sum, the combination may involve ANY amount of elements, right? Ex: if the value X is 2.10, the elements could be for example 0+10 + 0.20 + 0.30 + 0.50 + 1.00 = 2.10 or 1.00 + 1.10 = 2.10 or 0.10 + 2.00 = 2.10, etc.?
– Rogério Dec
@Rogériodec exactly! I will have a lines with thousands of values! I added 1 more example.
– rbz
I’m trying to understand, it would be to add all the smaller values into one, or add one to one of these smaller ones with the
X
?– Guilherme Nascimento
Would be all possible sum combinations, which would give the value of X.
– rbz
For thousands of values I do not think it is possible to get all combinations, because if there are a thousand values the possible combinations are
1000!
. But for something not too big you can do thus– Isac
@Isac did not understand your code, it does not have the reference value for the sums. And I also did not understand "because if there are a thousand values the possible combinations are 1000"... Possible combinations would start with values smaller than X, since the maximum value is X. With these values, you would have to test all possible combinations that give exactly the value of X.
– rbz
Theoretically I would take 1 by 1, add it up in an ordered sequence, until it reaches the value of X or if it exceeds, the combination is no longer valid. Then it starts again, 1 by 1 and skips the value of the sequence, and continues to add the next ones, until see if it gives the value of X. So sequentially until testing all the possibilities of sum that will give X. Yes, it would be millions of calculations.
– rbz
"if there are a thousand values the possible combinations are 1000!" (a thousand factorial) Do it in the calculator to see the number we are talking about. These are all the combinations that need to be analyzed to see if they give the sum you want. Of course many of them can be cut without being fully analyzed, but it’s still a lot. The reference value for the sum is passed in the function
combinacoesParaObjetivo
as the second parameter (in the example1.1
)– Isac
Wouldn’t they be more? Why do we count at the beginning of the array: I start by adding up the value of the
array[0], array[1], array[2],...
... if it doesn’t equal X, then it would start againarray[0], array[2], array[3],...
... really would be MANY "tests"...– rbz
The idea is this: I have for example 10,000 notes, and at their value, I need to find all that have the value of
100,51
, or all combinations of values less, that the sum is equal to100,51
.– rbz
I don’t think you’re visualizing how much it is
1000!
Has here on this page just for the fun :D. So yes have a "few" combinations to test– Isac
Now I get it! hahaha... this "!" is purposeful! I didn’t even know what that meant! rs Dude, so there is no such possibility ?
– rbz
then just so you can understand better, you want to return a vector only with the combinations added to the past value? In a simpler example,
A = 100; B = [...valores]; X = [A + B[0], A + B[1], A + B[2]];
, of course the X array would be generated by the algorithm, but idea is to understand if I understood your need– Guilherme Nascimento
That’s it. It’s just that printing or returning in array doesn’t matter, because I’ll have other manipulations later. But yes, it would be all possible combinations that the sum gives X. Like in the examples I gave, the return will have to contain what are the combinations of the values of the array, which summed give X.
– rbz
For everyone who wants to discuss the question, I’ve created a chat
– rbz