[RESOLU] Récursivité toujours et encore !

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : [RESOLU] Récursivité toujours et encore !

Re: [RESOLU] Récursivité toujours et encore !

par moogli » 11 nov. 2012, 22:43

j'ajouterais que de toute façon php4 est mort depuis longtemps donc php5 est rien d'autre (5.4 svp) :mrgreen:

Re: [RESOLU] Récursivité toujours et encore !

par AB » 11 nov. 2012, 22:35

Oui moogli a raison de faire ces distinctions.

PHP5 ne se limite pas à la POO. Par exemple des fonctions basiques bien pratiques comme file_put_contents sont sorties avec php5.
Ne te prive donc pas d'utiliser php5 et comme déjà dit tu peux tout aussi bien faire du code procédural avec php5.

On associe assez souvent POO avec php5 parce qu'il est vrai que la POO de php5 a beaucoup évoluée par rapport à la POO de php4. On a "rattrapé" pas mal de fonctionnalités existantes dans d'autres langages et qui n'étaient pas disponibles avec la POO de php4. Pour dire au passage que si tu t'intéresse à la POO, regarde plutôt des tutos assez récents faits avec php5, autant partir directement sur de bonnes bases.

Ensuite les lib comme la SPL c'est encore une autre histoire. C'est des classes très pratiques mais pour pouvoir les utiliser il est vrai qu'il faut avoir un minimum de base en POO et du temps disponible. Au final on écrit moins de code mais on passe beaucoup plus de temps le nez dans la doc, ce qui n'est pas forcément rentable. Après c'est pas forcément du temps perdu car on pourra appliquer sur le même principe des méthodes analogues pour par exemple faire de la récursivité sur des tableaux plutôt que sur des répertoires. La rentabilité d'apprendre le fonctionnement de ces classes est donc fonction de la quantité de code que tu produiras susceptible de les réutiliser.

Enfin bon on peut dire que l'utilisation de la SPL fait partie de "php avancé" et que c'est loin d'être la meilleure porte d'entrée pour un débutant, même voulant s'initier à la POO :)

Re: Récursivité toujours et encore !

par moogli » 11 nov. 2012, 17:42

POO ne veux pas dire PHP5.

Il s'agit d'une façon de concevoir une application : un modèle objet.
Il ne faut pas "faire de la poo" parce que l'en parle on que ça peu faire bien.

Il soft procédural peu très suffire, voir même mieux remplir son role que la même chose en poo.

La poo est intéressante pour du code partagé, utilisé en équipe et réutilisable.
En général elle apporte aussi de la complexité au code, et si mal utilisée c'est un bordel sans nom ;)

ensuite il y a la façon dont PHP (5 ou pas) l'implémente.


Une récursivité c'est le même principe en poo qu'en procédurale (une fonction / méthode qui fait appel à elle même).


bon courage

@+

Re: Récursivité toujours et encore !

par bostak » 11 nov. 2012, 17:35

Bonjour,

Je reviens vers vous et tiens à tous vous remercier pour vos explications et également d'avoir pris le temps de m'expliquer pour la récursivité. Et c'est vrai que faire de la récursivité avec php5 au début c'est pas évident même en lisant un peu la doc je dois dire que sa file un sacré mal de crâne. Je pense que je vais continuer pour le moment sans utiliser php5 car comme je débuté avec la POO mieux vaut que je comprenne bien tout en POO pour ensuite m'attaquer à PHP5.

Encore merci à vous tous, et je garde dans un coin ce post car il est très utile.

Cordialement,

Re: Récursivité toujours et encore !

par AB » 05 nov. 2012, 23:26

Pour comprendre comment cela fonctionne il faut te farcir la doc, c'est à dire les liens que je t'ai donnés dans mon précédent post). J'ai pas dis que c'était simple, surtout au début #-o
Oui enfin je parlais du principe.
Quand tu vois le nom d'une classe utilisée, une recherche dans la doc php te permettra de connaître son fonctionnement. Il est important de bien comprendre les "héritages" qui donnent accès aux méthodes parentes.
C'est en fonction de son héritage et de ses méthodes spécifiques (ajoutées) que l'on peut savoir si telle ou telle classe peut répondre à son besoin. Ensuite, la plupart du temps des exemples d'utilisation sont donnés dans la méthode _construct.

Par exemple pour la classe "RecursiveDirectoryIterator", il faut commencer par bien étudier son synopsis. Ensuite tu peux regarder des exemples de fonctionnement dans la méthode _construct.
Tu peux tester aussi la différence entre
$file = new DirectoryIterator($repertoire);

echo iterator_count($file);

	foreach ($file as $key=>$f) 
	{
		echo $f.'<br>';
		echo $key.'<br>';
	}	
et
$file = new recursiveDirectoryIterator($repertoire);

echo iterator_count($file);

	foreach ($file as $key=>$f) 
	{
		echo $f.'<br>';
		echo $key.'<br>';
	}	
Dans le deuxième cas tu peux voir que les '.', '..' n'apparaissent pas. Cela vient du fait que "recursiveDirectoryIterator" hérite de "FilesystemIterator" qui elle même est configurée avec la constante "FilesystemIterator::SKIP_DOTS". En utilisant "recursiveDirectoryIterator" on a donc plus à se soucier de filtrer ces entrées.

Note au passage que "recursiveDirectoryIterator" par elle même ne parcours qu'une dimension et l'on a donc que les deux entrées '.' et '..' en moins par rapport à "DirectoryIterator".
Mais on a un objet récursif et donc on pourra utiliser "RecursiveIteratorIterator" pour le parcourir et explorer toutes ses dimensions (la même classe appliquée sur "DirectoryIterator" renverrait une erreur).

Bon je te l'accorde la SPL (dont ces classes font partie) n'est pas très sexy (litote) et pas mal de fonctions sont non documentées. Si tu veux te plonger dedans plus en avant tu peux regarder ce lien.
C'est vrai qu'il faut être très très motivé pour se lancer là dedans, surtout à ses débuts. Si tu débute en POO tu ferais peut-être mieux d'attendre d'en connaître plus sinon ça peut te faire une "indigestion" voire même une allergie à la SPL :)

Je t'en ai parlé un peu parce que tu trouvais cela magique. Et tu vois, y'a pas de secret, c'est comme ailleurs, pour qu'un tour de magie puisse s'exécuter facilement, il faut beaucoup de travail avant :wink:

Re: Récursivité toujours et encore !

par sirakawa » 05 nov. 2012, 21:35

Un détail en passant:
la factorielle de 0 est 1, comme celle de 1, évidemment.
Un autre détail:
un exemple célèbre d'emploi de la récursivité est le problème des tours de Hanoï.
Un troisième détail:
Un problème qui a une solution récursive a toujours une solution non récursive.

Re: Récursivité toujours et encore !

par AB » 05 nov. 2012, 02:07

Pour comprendre comment cela fonctionne il faut te farcir la doc, c'est à dire les liens que je t'ai donnés dans mon précédent post). J'ai pas dis que c'était simple, surtout au début #-o

Sinon je t'avais parlé de RecursiveFilterIterator (toujours dans ce même post). Tu peux t'en servir pour compter tes fichiers et en même temps tes dossiers, par exemple
$dossier='Mon_repertoire';

$files = new RecursiveIteratorIterator(new Tri_perso(new RecursiveDirectoryIterator($dossier)));

$nb_dos = 0;
class Tri_perso extends RecursiveFilterIterator {
 
    public function accept() {
		global $nb_dos;
		
		if($this->isDir()) $nb_dos++;
		
		return $this;
    }
}

$nb_fichiers =  iterator_count($files);

echo 'nb_fichiers = '.$nb_fichiers.'<br>';
echo 'nb_dossiers = '.$nb_dos.'<br>';
Remarque que ce n'est pas l'équivalent du code que t'a montré Sadeq puisque c'est un résultat global (non détaillé pour chaque dossier), et c'est aussi une façon détournée d'utiliser "RecursiveFilterIterator" qui évite juste de lister le tableau avec un foreach.

Pour avoir l'équivalent du code de Sadeq avec les classes php5, on ferait plus classiquement :
$repertoire = 'Mon_repertoire';
$tab = array();
if(is_dir($repertoire))
{
	$file = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($repertoire, RecursiveDirectoryIterator::KEY_AS_FILENAME));
	// Tableau des résultats
	foreach ($file as $key=>$f) 
	{
		$dos = $repertoire.DIRECTORY_SEPARATOR.$file->getSubPath();
		$tab[$dos][] = $key;
	}
}

//Exploitation des données
$total_fichiers = 0;
foreach ($tab as $dos => $fichiers)
{
	$nb_fich = count($fichiers);
	$total_fichiers += $nb_fich;
	echo $dos.' = '.$nb_fich.' fichiers <br>';
}

echo 'Total '.count($tab).' dossiers et '.$total_fichiers.' fichiers';

//Visualisation du résultat du tableau
echo '<pre>';
print_r($tab);
echo '</pre>';
Le code pour créer le tableau est plus beaucoup plus court (mais pas forcément plus court à trouver :) ). Cela dit ça devrait-être aussi plus rapide.

Re: Récursivité toujours et encore !

par sadeq » 05 nov. 2012, 01:56

Bonjour, je t'expliquerais tout d'abord la récursivité et en suite la technique pour utiliser la récursivité pour compter dans des dossiers et sous dossiers.

La récursivité est une technique qui permet d'appliquer le même algorithme à des objets ayant la même nature et dont le nombre est inconnu par le programme. De ce fait, le programme doit répéter cet algorithme autant de fois qu'il rencontre d'objets traitables.
Le risque de la récursivité est l'infinité de répétition ce qui peut saturer la pile d'exécution et planter la machine. C'est pour cela que le programme récursif doit contenir une condition de sortie qui permet de l'arrêter.
La récursivité ressemble à une boucle While sauf que c'est une procédure qui appelle elle-même.

Exemple : Calculer le factoriel d'un nombre.
Factoriel de 4 = 4x3x2x1 = 24
Factoriel de 10 = 10x9x8x7x6x5x4x3x2x1 = 3 628 800
Donc, le factoriel est la multiplication de N et N-1 jusqu'à 1. La procédure qui se répète ici est : N x N-1 et la condition d'arrêt est quand N=1 pour éviter zéro.
Cette procédure est donc récursive et peut s'écrire :
Programme : Factoriel.php
<?php
//Calcul du Factoriel
function factoriel($N) {
   //calculer factoriel si N>1
   if ($N>1) {
		//dans ce cas: appeler récursivement la fonction factoriel pour calculer celui de $N-1
        return ($N * factoriel($N-1));
    }
   //tester la condition d'arrêt
   if ($N==1) { 
       return $N;
    }   
    //convention: factoriel de zéro=1
    if ($N==0) { return 1; }
}

// Programme principal
echo  'Factoriel de 4 = ' . factoriel(4);
echo  '<br>';
echo  'Factoriel de 10 = ' . factoriel(10);
?>
Déroulement du programme:
Etat1 : à l'instruction echo 'Factoriel de 4 = ' . factoriel(4); la fonction factoriel(4) est appelée:
Etat2 : $N=4, l'instruction return ($N * factoriel($N-1)); est exécutée car $N >1 donc la fonction factoriel($N-1) est appelée pour $N-1=3.
Etat3 : $N=3, l'instruction return ($N * factoriel($N-1)); est exécutée car $N >1 donc la fonction factoriel($N-1) est appelée pour $N-1=2.
Etat4 : $N=2, l'instruction return ($N * factoriel($N-1)); est exécutée car $N >1 donc la fonction factoriel($N-1) est appelée pour $N-1=1.
Etat5 : $N=1, l'instruction return $N; est exécutée car $N=1 donc la fonction actuelle se termine et retourne la valeur 1 à celle de l'Etat4.
Etat4 : $N=2, l'instruction return ($N * factoriel($N-1)); est exécutée et la fonction de l'Etat4 se termine et retourne la valeur (2*1)=2 à celle de l'Etat3.
Etat3 :$N=3, l'instruction return ($N * factoriel($N-1)); est exécutée et la fonction de l'Etat3 se termine et retourne la valeur (3*2)=6 à celle de l'Etat2.
Etat2 :$N=4, l'instruction return ($N * factoriel($N-1)); est exécutée et la fonction de l'Etat2 se termine et retourne la valeur (4*6)=24 à celle de l'Etat1.
Etat1 : à l'instruction echo 'Factoriel de 4 = ' . factoriel(4); la fonction factoriel(4) reçoit finalement la valeur 24 qui s'affiche donc.

Bien que le sujet n'est pas de développer le factoriel, voici le cas amélioré qui prend en compte le factoriel des suites positives, négatives et le zéro:
<?php
//Calcul du Factoriel
function factoriel($N) {
   //calculer factoriel si N>1
   if (abs($N)>1) {
        //dans ce cas: appeler récursivement la fonction factoriel pour calculer celui de $N précédent
		$N_precedent = $N<0 ? $N+1 : $N-1; //prise en compte des suites positives et négatives
        return ($N * factoriel($N_precedent));
    }
   //tester la condition d'arrêt
   if (abs($N)==1) {
       return $N;
    }  
    //convention: factoriel de zéro=1
    if (abs($N)==0) { return 1; }
}

// Programme principal
echo  'Factoriel de 0 = ' . factoriel(0);
echo  '<br>';
echo  'Factoriel de 4 = ' . factoriel(4);
echo  '<br>';
echo  'Factoriel de -4 = ' . factoriel(-4);
echo  '<br>';
echo  'Factoriel de 9 = ' . factoriel(9);
echo  '<br>';
echo  'Factoriel de -9 = ' . factoriel(-9);
?>

Conclusion:
Un procédure récursive est une procédure qui appelle elle-même sous forme d’empilation de procédures sans prendre le risque de boucler à l'infini, elle contient donc une condition d'arrêt qui permet à la dernière instance de la pile de se terminer pour retourner à l'instance qui la précède jusqu'à arrivée à la surface d'exécution du programme principal.

Voila. Mintenant qu'on a compris ce qu'est la récursivité, il va falloir l'utiliser pour le cas d'école de lecture d'un dossier qui peut contenir une infinité de sous-dossiers qui eux-mêmes peuvent en contenir d'autres.

L'algorithme principal est celui qui décrit pourquoi lire un dossier, la réponse est pour traiter ses fichiers et parcourir ses sous-dossiers etc...
La récursivité ici est le fait de devoir parcourir une infinité de dossiers pour faire la même chose, c'est à dire : traiter les fichiers et parcourir les sous-dossiers. Mais que ça s'arrête, il faut une condition d'arrêt. La plus logique est quand le traitement ne trouve plus de sous-dossier à parcourir.

Voici donc le programme: lire_dossier.php
<pre>
<?php
//fonction qui parcourt un dossier et découvrir son contenu
function explorer($dossier) {
//variable globale vue par toutes les fonctions
global $result_list;
	//ouvrir le dossier
	if ($dh = @opendir($dossier)) {
		//parcourir les objets fichiers ou sous-dossiers du dossier
		while (($objet = @readdir($dh)) !== false) {
			//éviter les marques du parent
			if ($objet !== '.' && $objet !== '..') {
				//nom complet de l'objet
				$objet = $dossier .'/'. $objet;
				//verfier s'il s'agit d'un fichier
				if (is_file($objet)) {
						//enregistrer le fichier comme objet trouvé dans ce dossier
						$result_list['dossiers'][$dossier][] = $objet;
				} 
				//verfier s'il s'agit d'un sous-dossier
				elseif (is_dir($objet)) {
					//parcourir ce sous-dossier pour découvrir son contenu
					explorer($objet);
				}
			}
		}
	}
	//compte le nombre de total des fichiers
	$result_list['nbre_fichiers'] += count($result_list['dossiers'][$dossier]);
	//fin de la fonction
	return $result_list;
}

//test de la fonction
$result_list = explorer('c:/wamp/www/test');
print_r($result_list);
echo "<br>";
//stats
echo "Le nombre total des fichiers est : ". $result_list['nbre_fichiers'];
echo "<br>";
echo "Le nombre total de dossiers est : ". count($result_list['dossiers']);
echo "<br>";
foreach($result_list['dossiers'] as $dossier=>$list_fichiers){
	echo "Le nombre de fichiers du dossier '". $dossier ."' est : " . count($list_fichiers);
	echo "<br>";
}
?>
</pre>
J'en ai profité pour lister et compter les fichiers :-)
Voila.

Récursivité toujours et encore !

par bostak » 04 nov. 2012, 20:44

Bonsoir,

Je reviens vers vous, pour trouver une application sur ceci :

Code : Tout sélectionner

$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->files."/".$filename)); echo iterator_count($files);
Que l'on m'a suggérer d'utiliser dans un post précédent pour compter tous les fichier d'un répertoire. Sa fonctionne magnifiquement bient, mais je ne comprend pas du tout comme cela fonctionne.
J'ai essayé de faire de même pour compter le nombre de dossier et sous dossier dans le répertoire mais en vain.
J'ai regardé la doc de php.net mais sa ne m'a pas avancé à grand chose. Si une âme charitable peut m'expliquer je suis preneur.

Cordialement,