Modification d'un algo de calculs de somme

Eléphant du PHP | 168 Messages

18 mai 2011, 20:28

Bonjour,

Il y a quelques années, un ami à moi m'avait conçu un algo me permettant sur la base de 3 données (somme à obtenir, liste de chiffres, et nombre de chiffres à prendre), un script me permettant de calculer toutes les additions possibles avec le nombre de chiffres ciblé et la somme donnée.

Cela donnait ceci.
<?php 
 
if($_POST['submit'])
{
	set_time_limit(0);
	extract($_POST);
	if(!empty($Somme) && !empty($nombre_el) && !empty($liste_nombres))
	{
		/** nombre a trouver **/ 
		$n = $Somme; 
		/** nombre d'element de la liste a utiliser **/ 
		$it = $nombre_el; 
		/** liste des éléments -- elle prend tout, même des negatifs, même 0, elle l'utilise **/ 
		//$nums = array(1,2,8,7,3,-1,24, 44, 12,0,4,2,7,5,3,13,14,15,16,3); 
		$nums = $liste_nombres;
		 
		$total = 0; 
		/** 
		 * on vire les doublons (evite d'avoir deux fois le même résultat avec parametre different utilisé) 
		 * on trie en ordre ascendant, c'est plus jolie à l'affichage 
		 */ 
		$nums = array_unique($nums); 
		rsort($nums); 
		 
		/**
		 * si on l'appel mal, pas la peine de continuer 
		 */ 
		if (count($nums) < $it || $it <= 0 || $n <= 0) { 
		  die('Not enough number or some other stupid errors'); 
		} 
		 
		/** 
		 * tableau qui va contenir les positions dans les differents elements 
		 * (position du nombre 1, du nombre 2, etc ...) 
		 */ 
		$my_pos = array(); 
		/** on met les positions par defaut: les premiers hciffres qui sortent **/ 
		for ($i = 0; $i < $it; $i++) { 
		  array_push($my_pos, $i); 
		} 
		 
		/** 
		 * boucle principale, s'execute tant que la première position (le premier element) 
		 * n'a pas parcourue toute la la liste 
		 */ 
		while ($my_pos[0] < count($nums)) { 
		  $bak_pos = $my_pos; /** copie de nos positions pour les restaurer **/ 
		 
		  /** 
		   * on fait tourner le dernier nombre et uniquement lui (si 3 element, resultat de la 
		   * forme x + y + z, on fait tourner z) de sa valeur atcuelle a la derniere de la 
		   * liste 
		   * 
		   * a chaque passage, on remplie notre liste de numeros actuelle, et on regarde si le 
		   * resultat convient 
		   */ 
		  while ($my_pos[count($my_pos) - 1] < count($nums)) { 
		    $t_nums = array(); 
		    /** pour chaque element qu'on a positionné, et qui n'est pas encore dans la liste **/ 
		    for ($i = 0; $i < count($my_pos) && (array_search($nums[$my_pos[$i]], $t_nums) === false); $i++) { 
		      array_push($t_nums, $nums[$my_pos[$i]]); 
		    } 
		    if (count($t_nums) == $it /** au cas où on a zappé un chiffre avec le array_search au dessus **/ 
		    && array_sum($t_nums) == $n) { 
			  echo implode(' + ', $t_nums).'<br />'; /** on a un resultat valide **/ 
			  $total++;
		    } 
		    $my_pos[count($my_pos) - 1]++; 
		  } 
		 
		  /** 
		   * on remonte en marche arrière jusqu'au dernier qui n'a pas atteint la fin, et on 
		   * l'increment. D'abord y, puis x donc. 
		   * 
		   * après ça on restaure ceux suivant a leur etat d'origine. 
		   * (si on trouve x en etat non final, on l'increment et on replace x et y a leur position 
		   * d'origine) 
		   */ 
		  for ($i = (count($my_pos) - 1); $i >= 0; $i--) { 
		    /** si on a déjà tout changé (premier element en position finale) on sort a terminé **/ 
		    if ($i == 0 && $my_pos[$i] == (count($nums) - 1)) { 
		      break 2; 
		    } 
		    if ($my_pos[$i] < (count($nums) - 1)) { /** on est pas en position finale **/ 
		      $my_pos[$i]++; 
		      for ($j = ($i + 1); $j < count($my_pos); $j++) { /** on restaure tout les suivants **/ 
		    $my_pos[$j] = $bak_pos[$j - 1] + 1; 
		      } 
		      break; 
		    } 
		  } 
		} 		 
		/** 
		 * Les choses qu'on peut améliorer: 
		 * 
		 * - Je calcul beaucoup trop de choses là. A chaque fois que je reset mes chiffres, je les remet 
		 * a leur positions d'origine, même si elle est dépassée. Par exemple (1,2,3,4,5), trouver 10 en 
		 * trois chiffres, je remet toujours le 2eme element sur le chiffre "2", même si le premier y est 
		 * (alors que je devrais le placer sur 3, puis sur 4 quand le premier est sur 3, etc ..) 
		 * - Je bouge tout les elements jusqu'à la fin. Dans le même exemple qu'au dessus, je bouge toujours 
		 * l'element 2 jusqu'à la fin jusque sur le 5, alors qu'il peut s'arreter au 4 (l'element deux n'aura 
		 * jamais 5, ce sera forcement l'element 3 ...) 
		 * - Vitesse: les array_search, array_sum et autre ça va bien 5 minutes poru la demo mais un buffer dynamique 
		 * c'est quand même vingt fois plus rapide. 
		 * - plein d'autres trucs mais je vais aller me pieuter 
		 */ 
	}
	else
	{
		echo 'Tous les champs ne sont pas remplis !!';
	}
}
?>
Mes connaissances en développement ne sont pas "mauvaises" mais je peine énormément en algorithmie...

Le script me convenait parfaitement mais j'ai un paramètre de plus à rajouter et je ne suis plus en contact avec la personne depuis quelques temps maintenant...

Quelqu'un pourrait il se pencher dessus svp (pas tous les jours que l'on a un peu d'algo à résoudre, ca peut être un ptit défi personnel :lala:)


Actuellement, il m'autorise seulement une fois le même nombre, je ne peux donc pas faire pour obtenir 15 avec 3 chiffres (5 + 5 + 5), j'aimerai avoir cette possibilité, j'ai testé de modifier mais mis à part une boucle me gelant le pc j'ai rien obtenu de concret...


MERCI d'avance.

Dux
Eléphant du PHP | 127 Messages

23 mai 2011, 12:33

J'ai essayé de te faire le plus simple possible dans le cas où tu veuilles faire des modifs.
Pour la petite astuce, j'ai utilisé la fonction eval() afin de construire des boucles de calcul de la taille du nombre d'éléments à utiliser. Et c'est dans ce cas une des méthodes les plus simples.
Les lignes de codes supplémentaires retirent les doublons dans un ordre différent et tri par ordre croissant les éléments utilisés.
<?php 
if(count($_POST) > 0) {
    extract($_POST);
    if($somme != '' && $nombre_el != '' && $liste_nombres != '') {
    
        // Mise en tableau des éléments
        $nums = explode(',', $liste_nombres);
        
        // Construction des boucles de calcul et vérif des sommes
        for ($i = 0; $i < $nombre_el; $i++) $calcul .= "foreach(\$nums as \$num[$i]) ";
        $calcul .= " if (array_sum(\$num) == \$somme) { sort(\$num); \$result_tmp[] = implode(' + ', \$num); } ";
        eval($calcul);

        // Retire les doublons
        $fin = array_unique($result_tmp); 
        // Affiche les résultats
        foreach($fin as $aff) echo $aff. " = $somme<br />";
            
    } else {
            echo 'Tous les champs ne sont pas remplis !!';
    }
}
?>
 
 <form method="post" action="test.php">
 somme <input name="somme" value="<?=$somme?>" /><br />
 nombre_el à utiliser <input name="nombre_el" value="<?=$nombre_el?>" /><br />
 liste_nombres <input name="liste_nombres" value="<?=$liste_nombres?>" /><br />
 <input type="submit" />
 </form>

Eléphant du PHP | 168 Messages

15 juin 2011, 18:42

Salut,

Moi qui venait relancer car je ne veux recevez pas de notifications de réponse par mail, j'ai été très agréablement surpris de voir une réponse à mon problème !

Après un premier test du script, tu as tout compris ! mes félicitations !!

Par contre j'avoue avoir décroché à partir du foreach :/ , j'ai essayé d'imaginer le déroulement de manière abstraite mais mis à part me faire un noeud, j'ai pas réussi ;-)

Ne voulant pas abuser de ta gentillesse mais essayant de comprendre d'avantage afin de savoir le reproduire, pense qu'il est également possible de l'optimiser ? en testant sur 40 chiffres différents par groupe de 5 et un résultat de 150 par ex, je dépasse le 1/4 d'heure sur mon C2Duo 4Go Ram.


Merci bien !

Eléphant du PHP | 209 Messages

16 juin 2011, 06:39

Allez, j'essaye, j'espère avoir bien compris le problème...

$somme_a_trouver = 45;
$nb_element_max = 5;
$all_numero = array(1,2,8,7,3,-1,24, 44, 12,0,4,2,7,5,3,13,14,15,16,3);

$all_combinaison  = array();

foreach($all_numero as $j => $numero){    
    foreach($all_combinaison as $i=> $list_element){
        $all_combinaison[$i][] = $numero; 
        if (count($all_combinaison[$i]) == $nb_element_max){
            $sum = array_sum($all_combinaison[$i]);
            if ($sum == $somme_a_trouver){
                $solution[] = $all_combinaison[$i];
            }
            unset($all_combinaison[$i]);
        } else {
            $all_combinaison[] = $list_element;
        }
    }
    $all_combinaison[] = array($numero);
}

print_r($solution); 
--
Eric

Eléphant du PHP | 168 Messages

16 juin 2011, 10:22

Merci epommate2 pour ta participation !

Tu as presque bon, sauf que tu as le même résultat que mon script original, c'est à dire que tu ne donnes pas la possibilité d'utiliser plusieurs fois le même nombre, et cette particularité m'est très importante :)

Merci encore

Eléphant du PHP | 209 Messages

16 juin 2011, 14:06

Merci epommate2 pour ta participation !

Tu as presque bon, sauf que tu as le même résultat que mon script original, c'est à dire que tu ne donnes pas la possibilité d'utiliser plusieurs fois le même nombre, et cette particularité m'est très importante :)

Merci encore

ah, ok... tu veux que l'on puisse utiliser plusieurs fois le 1er 1 par exemple ? Effectivement, je n'avais pas compris la modif à faire :-)
--
Eric

Eléphant du PHP | 168 Messages

16 juin 2011, 14:10

Et la "grosse" difficulté est bien là :/

Eléphant du PHP | 209 Messages

16 juin 2011, 14:36

Finalement, ca ne change pas grand chose :
<?php 

$debut=microtime(true);

$somme_a_trouver = 45;
$nb_element_max = 5;
$all_numero = array(1,2,8,7,3,-1,24, 44, 12,0,4,2,7,5,3,13,14,15,16,3);

array_unique($all_numero);

foreach($all_numero as $num){
	for($i=0; $i<$nb_element_max; $i++){
		$all_numero_final[] = $num;
	}
}

$all_combinaison  = array();

foreach($all_numero_final as $j => $numero){	
		foreach($all_combinaison as $i=> $list_element){			
			$all_combinaison[$i][] = $numero; 
			sort($all_combinaison[$i]);
			if (count($all_combinaison[$i]) == $nb_element_max){
				$sum = array_sum($all_combinaison[$i]);
				if ($sum == $somme_a_trouver){
					$solution[] = $all_combinaison[$i];
				}
				unset($all_combinaison[$i]);
			} else {
				if ( ! in_array($list_element,$all_combinaison)){
					$all_combinaison[] = $list_element;
				}
			}
		}
		
		$all_combinaison[] = array($numero);
	
	
}


print_r($solution);

echo microtime(true) - $debut;
--
Eric

Eléphant du PHP | 168 Messages

16 juin 2011, 15:42

En mettant les mêmes paramètres, le script n'est toujours pas fini après 1H de lancement :oops:

chiffre: 150
nbre par : 5
1 a 40

Eléphant du PHP | 209 Messages

16 juin 2011, 16:17

ok, je n'avais pas vu non plus les données du problème :-)
--
Eric

Eléphant du PHP | 209 Messages

16 juin 2011, 18:48

Re-tentative :

function trouve($all_numero,$somme,$nb_max){
	if ($nb_max ==4){
		static $test;
		$test++;
		echo "$test/".count($all_numero)."\n";
	}
	if ($nb_max == 1){
		if (in_array($somme,$all_numero)){
			return array(array($somme));
			return true;
		} else {
			return array();
		}
	}
	$r = false;
	$solution = array();
	foreach($all_numero as $numero){
		$trouve = trouve($all_numero,$somme-$numero,$nb_max-1) ;
		foreach ($trouve as $i => $s){
			$trouve[$i][] = $numero;
			$solution[] = $trouve[$i];
		} 
		
	}
	return $solution;	
}


$result = trouve($all_numero,150,5,1);
echo count($result)." résultat non trié";
foreach($result as $i => $r){
	sort($result[$i]);
}
array_unique($result);

print_r($result);
Mais j'ai l'impression qu'il y a BEAUCOUP de résultat ?
--
Eric

Eléphant du PHP | 168 Messages

16 juin 2011, 19:52

Il y a en effet plus de 2000 resultats de mémoire.

J'ai testé ta dernière version mais j'ai 2 questions dont un problème.

Pourquoi il y a un 4ème paramètre sur ta fonction alors que la fonction en compte que 3 ?

Sinon je me prends un

Code : Tout sélectionner

Fatal error: Allowed memory size of 524288000 bytes exhausted (tried to allocate 24 bytes) in C:\... on line 25
500MO alloué est déjà pas mal, il essaie d'allouer .....24 octects ? truc qui cloche lol ?

Merci de tes réponses et ta volonté de m'aider ;-)

Eléphant du PHP | 209 Messages

17 juin 2011, 08:09

Pourquoi il y a un 4ème paramètre sur ta fonction alors que la fonction en compte que 3 ?
Une erreur de ma part :-)
500MO alloué est déjà pas mal, il essaie d'allouer .....24 octects ? truc qui cloche lol ?
Non, il a déjà alloué 500Mo et du coup, il essaye d'allouer un tout petit tableau de 24 octets supplémentaire et il n'y arrive pas ....

J'ai ca dans mon php.ini :
memory_limit = -1

Mais, je me suis apercu d'une petite erreur : array_unique ne fonctionne pas sur des tableaux grr...
$somme_a_trouver = 150;
$nb_element_max = 5;
$all_numero = array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40);

function super_unique($array) {
  $result = array_map("unserialize", array_unique(array_map("serialize", $array)));
  return $result;
}

function trouve($all_numero,$somme,$nb_max){
	if ($nb_max == 4){
		static $test;
		$test++;
		echo "$test/".count($all_numero)."\n";
	}
	if ($nb_max == 1){
		if (in_array($somme,$all_numero)){
			return array(array($somme));
			return true;
		} else {
			return array();
		}
	}
	$r = false;
	$solution = array();
	foreach($all_numero as $numero){
		$trouve = trouve($all_numero,$somme-$numero,$nb_max-1) ;
		foreach ($trouve as $i => $s){
			$trouve[$i][] = $numero;
			sort($trouve[$i]);
			$solution[] = $trouve[$i];
		} 
	}
	$solution = super_unique($solution);
	return $solution;	
}


$result = trouve($all_numero,$somme_a_trouver,$nb_element_max);

print_r($result);
Par contre, j'ai une question... A quoi ca sert ?
--
Eric

Eléphant du PHP | 168 Messages

17 juin 2011, 09:10

Je teste dans la journée la version corrigé :-)

Pour ce qui est a quoi cela sert : je ne suis pas celui qui va utiliser directement le script.
De ce que j'ai compris, cela va servir a compléter un modèle mathématique en croisant plusieurs données. Le but final ? Aucune idée :-)

Eléphant du PHP | 168 Messages

17 juin 2011, 22:38

Ca marche comme un charme :-) <2Min pour obtenir le résultat, chapeau ;-)

Afin de conclure et te remercier une nouvelle fois, pourrez tu comme l'avait fait mon ami sur mon script original, me commenter le code afin d'y percer tous ces secrets ?

Merci bien.

ps : D'ailleurs je ne vois plus d'array_sum, comment fait tu les additions ? (car je faisais une alternative avec array_produit pour les multiplications :roll: )