Page 1 sur 1

Coder un algorithme de compression Huffman en PHP

Posté : 05 nov. 2015, 15:58
par 7jaa
Bonjour,
avant toute chose je tiens à préciser que je ne m'attends pas à ce qu'on me crache des réponses toutes faites, je suis ici pour vraiment comprendre et surmonter mon problème.

Alors voilà, j'ai commencé le php il y a environ 2 mois, jusqu'à présent on avait des exercices assez simples à faire, pas de problème.
Mais maintenant, on doit rendre un algorithme de compression Huffman codé en php. Je sais ce qu'est Huffman, mais le problème c'est que je n'ai AUCUNE idée de comment commencer, je n'y arrive simplement pas.. Notre prof nous a mis 2 fichiers à disposition: un nommé "huffman-squelette.php" dans lequel il y a un include d'un 2ème fichier, nommé lui "entropie-include.php".

Je suis donc venu ici en espérant que qqn puisse me mettre sur la bonne voie, je voudrais vraiment commencer à coder mais après des heures passées devant ce squelette sans pouvoir avancer je perds un peu espoir...

Voici les 2 fichiers:
huffman-squelette.php
<!DOCTYPE html 
     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
      <title>Compresseur Huffman</title>
   </head>
   <body>
      <h1>Compresseur Huffman</h1>
<?php
    require("entropie-include.php");

# décommenter pour passer en mode test (cf exercice 2)
#    $test_mode = 1;

    $texte = "";
    if (isset($_POST["texte"])) {
       $texte = $_POST["texte"];
       list ($occurences, $total) = occurences($texte);

       if (isset($test_mode)) {
	  # pour tester avec l'exercice du cours!
	  $texte = "correct";
	  $total = 64;
	  $occurences = array("e" => 15,
                              "r" => 12,
                              "c" => 11,
                              "o" => 10,
                              "t" => 9,
                              "l" => 4,
                              "m" => 2,
                              "k" => 1);
          echo "<p>Mode de test!</p>";
       }

       ?>
       <p>La chaîne fait <?php echo $total; ?> symbole(s) dont
          <?php echo count($occurences) ?> unique(s).
          Les symboles se répartissent comme suit:
          <ul>
          <?php dump($occurences); ?>
           </ul></p>
       <?php
       if ($total) {
	  $entropie = calculer_entropie($occurences, $total);
	  ?>
	  <p>L'entropie calculée vaut: <?php echo $entropie; ?></p>
       <?php
       }

       $code = huffman($occurences);
       ?>
       <p>Voici la table de codage Huffman:
         <ul>
       <?php dump($code); ?>
       </ul></p>
       <p>Et voici votre texte compressé (sous forme de 0 et de 1 textuels):
       <?php echo compress($texte, $code); ?>
       </p>
       <?php
    }
?>
      <h2>Texte à compresser</h2>
      <form action="" method="post">
      <p><textarea name="texte" rows="10" cols="60"><?php echo htmlentities($texte, ENT_XHTML, "ISO-8859-1"); ?></textarea>
         <input type="submit" />
      </p>
      </form>
   </body>
</html>
<?php

/**
 * @param string à échapper
 * @return string échappée
 */
function html_protect($s) {
   return (ctype_graph($s)
           ? htmlentities($s, ENT_XHTML, "ISO-8859-1")
           : "ASC " . join(", ", array_map("ord", str_split($s))));
}

/**
 * @param array[string]string $a tableau associatif à afficher, suppose
 *                               que $value est déjà échappé
 */
function dump ($a) {
   foreach ($a as $key => $value) {
      echo "<li>" . html_protect($key)  . ": " . $value . "</li>";
   }
}

/**
 * @param array[string]integer tableau associatif symbole => occurence
 * @return array[string]string tableau associatif symbole => code
 */
function huffman($o) {
   global $test_mode;

   $code = array();

   # à compléter ...

   return $code;
}

/**
 * @param string $t texte dont les symboles sont à compresser
 * @param array[string]string tableau associatif symbole => code
 * @return string chaîne de bits en format texte
 */
function compress($t, $c) {
   $s = "";

   # à compléter

   return $s;
}

?>

entropie-include.php
<?php

/**
 * @param string $t source dont les symboles sont à compter
 * @return array(array[string]integer tableau associatif symbole => occurences,
 *               integer nombre d'occurences totales) tableau 
 *
 * inconvénients de cette implémentation:
 *    - calcul de strlen($t) *et* de $total
 *    - mise en mémoire du tampon entier sous forme de tableau,
 *      ce qui gaspille de la mémoire
 * -> une meilleure implémentation serait possible si le programme
 *    principal nous appelait ligne par ligne pour compléter
 *    un tableau d'occurence, par exemple! ou si l'on consommait
 *    dans la fonction occurences() une entrée fichier, par exemple.
 */
function occurences($t) {  
   return array(array_count_values(split_str($t), strlen($t)));
}

   /* variante plus manuelle */
function occurences_more_manual($t) {
   $a = array();

   # définition d'une fonction anonyme qui a besoin d'utiliser
   # une référence au tableau local $a à la fonction occurences
   # pour pouvoir le modifier
   $callback = function ($val, $index) use (&$a) {
      if (!array_key_exists($val, $a)) {
         $a[$val] = 0;
      }
      $a[$val]++;
   };

   # appliquer la fonction anonyme à l'ensemble du tableau
   if (strlen($t)) {
      array_walk(str_split($t), $callback);
   }
   return array($a, strlen($t));
}

/**
 * @param array[string]integer $o tableau associatif symbole => occurences
 * @param integer $t nombre total d'occurences
 * @return float valeur de l'entropie sur $o
 *
 * alternative: array_map() et array_sum() pour éviter la boucle manuelle
 */
function calculer_entropie($o, $t) {
   $H = 0;
   foreach ($o as $symbol => $count) { /* ou sans le $symbol => */
      $Pi = $count/$t;
      $Hi = -log($Pi)/log(2); /* ou voir 2e argument de la fonction log() */
      $H += $Pi * $Hi;
   }

   return $H;
}

?>
Merci d'avance!

Re: Coder un algorithme de compression Huffman en PHP

Posté : 05 nov. 2015, 21:02
par @rthur
Bonjour,

A priori tu as "juste" à compléter les 2 fonctions suivantes :
/**
 * @param array[string]integer tableau associatif symbole => occurence
 * @return array[string]string tableau associatif symbole => code
 */
function huffman($o) {
   global $test_mode;

   $code = array();

   # à compléter ...

   return $code;
}

/**
 * @param string $t texte dont les symboles sont à compresser
 * @param array[string]string tableau associatif symbole => code
 * @return string chaîne de bits en format texte
 */
function compress($t, $c) {
   $s = "";

   # à compléter

   return $s;
}
Donc tout l'exercice c'est d'implémenter en PHP l'algo de Huffman et c'est difficile de t'aider quand on ne connait pas cet algo. :)

Déjà pour commencer, décommente le mode test pour pouvoir faire ton dev avec un jeu de données stable.
#    $test_mode = 1;

Et ensuite, regarde ce qu'il y a en entrée de ces fonctions, et ce qui est attendu en sortie, et essaye de voir en quoi ça s'applique pour cet algorithme...

Re: Coder un algorithme de compression Huffman en PHP

Posté : 05 nov. 2015, 23:33
par nestecha
Dis à ton prof de te refaire le squelette avec moins de commentaires, moins de HTML, + de PHP et surtout des variables avec des noms explicites. Les $t, $o, $s, $c... Ou alors rends lui un code avec des variables dynamiques.
La fonction occurences_more_manual est carrément infâme.

Re: Coder un algorithme de compression Huffman en PHP

Posté : 06 nov. 2015, 10:04
par @rthur
Dis à ton prof de te refaire le squelette avec moins de commentaires, moins de HTML, + de PHP et surtout des variables avec des noms explicites.
C'est rarement très apprécié par un prof quand on n'arrive pas à faire l'exercice de remettre en cause l’énoncé :D

Moi je trouve plutôt sympa déjà qu'il fournisse un squelette ^^ Il aurait juste pu leur dire de se débrouiller à implémenter l'algo en fournissant seulement les variables d'entrées

Re: Coder un algorithme de compression Huffman en PHP

Posté : 06 nov. 2015, 12:12
par 7jaa
Ok, merci pour vos réponses!
Donc en fait je peux ignorer le fichier entropie-include.php et m'en servir juste pour les fonctions si j'ai bien compris.
Quant au $test_mode = 1, pourriez-vous m'eclaircir sur ce qu'est son utilité exactement? Je n'ai jamais eu à utiliser une chose pareille, pour tester mon code jusqu'à présent je le lançais simplement en localhost sur un naviguateur!

Re: Coder un algorithme de compression Huffman en PHP

Posté : 06 nov. 2015, 13:15
par or 1
cela ne change pas le fait qu'il faille lancer dans son navigateur

if (isset($test_mode)) {
# pour tester avec l'exercice du cours!

cela va donc utiliser ces données fixes au lieu des données du formulaire.

à noter qu'il a des implémentations de l'algo en php sur le net.

Re: Coder un algorithme de compression Huffman en PHP

Posté : 06 nov. 2015, 22:31
par 7jaa
Ok je comprends, merci!
Après avoir passé encore plusieurs heures dessus aujourd'hui j'ai finalement pu commencer quelque chose.

J'ai regardé sur le net pour voir des implémentations de cet algo en php, mais j'ai vraiment du mal à comprendre le code des autres. Je préfère continuer à ma manière, même si je n'arrive pas à un résultat parfait

Je vous en redonnerai des nouvelles.

Re: Coder un algorithme de compression Huffman en PHP

Posté : 07 nov. 2015, 01:24
par 7jaa
Bonsoir,
j'ai pas mal avancé mais ça fait bien 4h que je suis bloqué sur un problème qui m'empêche d'avancer.

J'ai créé un fichier test.php à part juste pour coder la manipulation de mes array pour l'instant, voici mon code:

important il faut faire varier la condition d'arrêt de la boucle for ($i < 2 ici) entre 1 et environ 4 ou 5 pour voir l'évolution de l'array

Code : Tout sélectionner

<?php $a = array("e" => 15, "r" => 12, "c" => 11, "o" => 10, "t" => 9, "l" => 4, "m" => 2, "k" => 1); for($i = 0; $i<2; $i++){ //ordonner l'array de l'occurence la plus basse à l'occurence la plus haute asort($a); //addition des deux occurences les plus basses $somme = current($a) + next($a); //compteur qui nous sera utile plus tard, qui contient l'indexe de l'array sur lequel est le pointeur $compteur = 1; //les clés sont mises ensemble dans la chaine $symb $symb = array_search(current($a), $a).array_search(prev($a), $a); //pointeur remis à sa place next($a); //calcul des occurences les plus basses, //jusqu'à ce que la somme de celles-ci dépasse l'occurence suivante //les clés correspondantes sont mises ensemble dans la chaine $symb while($somme < next($a)){ $somme += current($a); $symb .= array_search(current($a), $a); $compteur++; } //incrémenter une fois le compteur car la condition du while "next($a)" a été lue une dernière fois sans pour autant $compteur++; //ne pas en tenir compte //$symb = strrev($symb); //supprimer de l'array les éléments déjà présents dans la chaine $symb $a = array_slice($a, $compteur); //remettre dans l'array la chaine $symb, avec sa nouvelle occurence $a[$symb] = $somme; } print_r($a); ?>
Voici mon problème:
j'arrive donc avec un associative array $a. Tout d'abord je l'ordonne selon les valeurs (occurences), de la plus petite à la plus grande.

Ensuite, la démarche est la suivante: additionner les 2 plus petites valeurs et comparer la somme de l'addition avec la valeur suivante. Deux cas se présentent:

1) la somme est plus grande que la valeur suivante, dans ce cas on passe directement à la fin du code, c'est à dire qu'on met les 2 clés des 2 valeurs que l'on a sommées collées ensembles dans une string, qu'on va placer ensuite dans notre array (les 2 anciennes clés sont supprimées).

2) la somme est plus petite que la valeur suivante, dans ce cas on additionne la somme avec cette même valeur suivante, et on procède jusqu'à ce que la somme totale soit plus grande que la valeur suivante, dans quel cas on arrive au point 1) ci-dessus.

Je vais essayer de vous montrer par écrit ce que le résultat devrait être (avec le même array que j'utilise dans mon code):

à la première itération de la boucle for principale, le 'm' et le 'k' sont mis ensemble, mais comme la somme de leurs valeurs est < que la valeur suivante (c'est à dire 4, qui est la valeur de la clé 'l'), alors on continue de sommer. Ca se répète jusqu'au 't' => 9

Sous forme de calcul, ça donne ça:
'k' et 'm': 1+2=3, avec 3<4 donc on ajoute 'l'
'l' et 'mk': 4+3=7, avec 7<9 donc on ajoute 't'
't' et 'lmk': 9+7=16, avec 16>10 donc on s'arrête là.

Et à ce moment, la chaîne qu'on devrait avoir est tlmk (qui deviendra une nouvelle clé), avec comme valeur 16. Donc 'tlmk' => 16

Mon problème est là, je n'obtiens pas la chaîne 'tlmk' mais la chaine 'mklt'. Pas dans le bon ordre. J'ai essayé plein de truc différents mais pas moyen de l'avoir dans le bon ordre. Ou alors je réussissais à l'avoir dans le bon ordre, mais l'ordre de ce qui suit était perturbé à son tour par la suite. Bref un casse tête et après plusieurs heures à chercher un moyen j'ai décidé de demander de l'aide ici. Au pire des cas je continue mon code avec cette erreur et c'est pas si grave!

Pour ceux qui souhaiteraient voir le résultat après plusieurs itérations, voici ce que ça devrait donner:

array au début
k=>1
m=>2
l=>4
t=>9
o=>10
c=>11
r=>12
e=>15

array après 1 itération
o=>10
c=>11
r=>12
e=>15
tlmk=>16

array après 2 itérations
r=>12
e=>15
tlmk=>16
co=>21

array après 3 itérations
tlmk=>16
co=>21
er=>27

array après 4 itérations
er=>27
cotlmk=>37

array après 5 itérations
cotlmker=>64

Bon je me rends compte que c'est un peu beaucoup de choses que je balance ici, je n'en voudrai à personne si je n'ai pas de réponse :)