web-dev-qa-db-fra.com

En termes simples, qu'est-ce qu'une fonction récursive utilisant PHP

Quelqu'un peut-il m'expliquer une fonction récursive dans PHP (sans utiliser Fibonacci) en langage profane et à l'aide d'exemples? Je cherchais un exemple, mais le Fibonacci m'a totalement perdu!

Merci d’avance ;-) En outre, à quelle fréquence les utilisez-vous dans le développement Web?

70
Imran

Termes de Laymens:

Une fonction récursive est une fonction qui appelle lui-même 

Un peu plus en profondeur:

Si la fonction continue à s’appeler elle-même, comment sait-elle quand s’arrêter? Vous définissez une condition, appelée cas de base. Les cas de base indiquent à notre appel récursif quand nous devons nous arrêter, sinon il se mettra en boucle indéfiniment.

Ce qui a été un bon exemple d’apprentissage pour moi, puisque j’ai une solide formation en mathématiques, était factorial . D'après les commentaires ci-dessous, il semble que la fonction factorielle soit un peu trop lourde, je la laisse ici au cas où vous le voudriez. 

function fact($n) {
  if ($n === 0) { // our base case
     return 1;
  }
  else {
     return $n * fact($n-1); // <--calling itself.
  }
}

En ce qui concerne l'utilisation de fonctions récursives dans le développement Web, je n'ai pas personnellement recours à des appels récursifs. Non pas que je considère comme une mauvaise pratique de compter sur la récursivité, mais cela ne devrait pas être votre première option. Ils peuvent être mortels s'ils ne sont pas utilisés correctement.

Bien que je ne puisse pas rivaliser avec l'exemple de répertoire, j'espère que cela aidera un peu.

(20/04/10) Mise à jour:

Il serait également utile de vérifier cette question, où la réponse acceptée montre en termes simples comment une fonction récursive fonctionne. Même si la question du PO traitait de Java, le concept est le même,

87
Anthony Forloney

Un exemple serait d’imprimer chaque fichier dans n’importe quel sous-répertoire d’un répertoire donné (si vous n’avez pas de lien symbolique dans ces répertoires qui pourrait casser la fonction d’une manière ou d’une autre). Un pseudo-code d'impression de tous les fichiers ressemble à ceci:

function printAllFiles($dir) {
    foreach (getAllDirectories($dir) as $f) {
        printAllFiles($f); // here is the recursive call
    }
    foreach (getAllFiles($dir) as $f) {
        echo $f;
    }
}

L'idée est d'imprimer d'abord tous les sous-répertoires, puis les fichiers du répertoire actuel. Cette idée est appliquée à tous les sous-répertoires et c’est la raison pour laquelle cette fonction est appelée de manière récursive pour tous les sous-répertoires.

Si vous voulez essayer cet exemple, vous devez rechercher les répertoires spéciaux . et .., sinon vous resteriez bloqué à appeler printAllFiles(".") tout le temps. De plus, vous devez vérifier quoi imprimer et quel est votre répertoire de travail actuel (voir opendir(), getcwd(), ...).

30
Progman

La récursivité est quelque chose qui se répète. Comme une fonction qui s’appelle de soi-même. Permettez-moi de démontrer dans un pseudo exemple:

Imaginez que vous sortiez avec vos amis en train de boire de la bière, mais que votre femme va vous donner l'enfer si vous ne rentrez pas avant minuit. Pour cela, créons la fonction orderAndDrinkBeer ($ time) où $ time correspond à minuit moins le temps qu'il vous faut pour terminer votre boisson actuelle et rentrer à la maison.

Alors, en arrivant au bar, vous commandez votre première bière et commencez à boire:

$timeToGoHome = '23';  // Let's give ourselves an hour for last call and getting home

function orderAndDrinkBeer($timeToGoHome) {  // Let's create the function that's going to call itself.
    $beer = New Beer();  // Let's grab ourselves a new beer
    $currentTime = date('G'); // Current hour in 24-hour format

    while ($beer->status != 'empty') {  // Time to commence the drinking loop
        $beer->drink();  // Take a sip or two of the beer(or chug if that's your preference)
    }

    // Now we're out of the drinking loop and ready for a new beer

    if ($currentTime < $timeToGoHome) { // BUT only if we got the time
        orderAndDrinkBeer($timeToGoHome);  // So we make the function call itself again!
    } else {  // Aw, snap!  It is time :S
        break; // Let's go home :(
    }
}

Maintenant, espérons simplement que vous n'êtes pas capable de boire suffisamment de bière pour devenir tellement intoxiqué que votre femme va vous faire dormir sur le canapé, même si vous êtes chez vous à temps --.-

Mais oui, c'est à peu près ce que la récursivité va.

22
Freyr

C'est une fonction qui s'appelle elle-même. Son utile pour parcourir certaines structures de données qui se répètent, telles que les arbres. Un DOM HTML est un exemple classique.

Un exemple d'arborescence en javascript et une fonction récursive pour "parcourir" l'arborescence.

    1
   / \
  2   3
     / \
    4   5

-

var tree = {
  id: 1,
  left: {
    id: 2,
    left: null,
    right: null
  },
  right: {
    id: 3,
    left: {
      id: 4,
      left: null,
      right: null
    },
    right: {
      id: 5,
      left: null,
      right: null
    }
  }
};

Pour parcourir l'arborescence, nous appelons la même fonction à plusieurs reprises, en transmettant les nœuds enfants du nœud actuel à la même fonction. Nous appelons ensuite à nouveau la fonction, d'abord sur le nœud gauche, puis sur le droit.

Dans cet exemple, nous allons obtenir la profondeur maximale de l'arbre

var depth = 0;

function walkTree(node, i) {

  //Increment our depth counter and check
  i++;
  if (i > depth) depth = i;

  //call this function again for each of the branch nodes (recursion!)
  if (node.left != null) walkTree(node.left, i);
  if (node.right != null) walkTree(node.right, i);

  //Decrement our depth counter before going back up the call stack
  i--;
}

Enfin on appelle la fonction

alert('Tree depth:' + walkTree(tree, 0));

Un excellent moyen de comprendre la récursivité consiste à parcourir le code lors de l'exécution. 

9
James Westgate

En termes simples: une fonction récursive est une fonction qui s’appelle elle-même.

7
Samuel

C’est très simple, quand une fonction s’appelle elle-même à accomplir une tâche pour un nombre indéfini et fini. Un exemple de mon propre code, une fonction pour remplir une arborescence de catégories multiniveaux

fonction category_tree ($ parent = 0, $ sep = '') 
 {
 $ q = "select id, name from categorye où parent_id =". $ parent; 
 $ rs = mysql_query ($ q); 
 while ($ rd = mysql_fetch_object ($ rs)) 
 {
 echo ('id.' "> '. $ sep. $ rd-> nom.' '); 
 category_tree ($ rd-> id, $ sep .'--'); 
} 
 }
5
Imran Naqvi

La récursivité est une manière élégante de dire "refait ce travail jusqu'à ce qu'il soit terminé".

Deux choses importantes à avoir: 

  1. Un cas de base - Vous avez un objectif à atteindre.
  2. Un test - Comment savoir si vous en êtes à l'endroit où vous allez.

Imaginez une tâche simple: trier une pile de livres par ordre alphabétique. Un processus simple consisterait à prendre les deux premiers livres, à les trier. Maintenant, voici la partie récursive: Y a-t-il plus de livres? Si c'est le cas, recommencez. Le "refais-le" est la récursivité. Le "y a-t-il d'autres livres" est le test. Et "non, pas plus de livres" est le cas de base.

4
Shane Castle

La meilleure explication que j'ai trouvée lorsque j'apprenais que moi-même est ici: http://www.elated.com/articles/php-recursive-functions/

C'est parce qu'une chose:

La fonction lors de la création de l'appelé est créée en mémoire (une nouvelle instance est créée)

Ainsi, la fonction récursive IS NE S'APPELANT PAS, mais son autre instance d'appel - de sorte que sa fonction en mémoire ne fait pas de magie. Ses quelques instances en mémoire qui renvoient certaines valeurs - et ce comportement est identique lorsque, par exemple, la fonction a appelle la fonction b. Vous avez deux instances aussi bien que si la fonction récursive appelée nouvelle instance de elle-même.

Essayez de dessiner de la mémoire avec des occurrences sur papier - cela aura du sens.

2
Ales

Fondamentalement cela. Il continue à s’appeler jusqu’à ce qu’il ait terminé

void print_folder(string root)
{
    Console.WriteLine(root);
    foreach(var folder in Directory.GetDirectories(root))
    {
        print_folder(folder);
    }
}

Fonctionne également avec des boucles!

void pretend_loop(int c)
{
    if(c==0) return;
    print "hi";
    pretend_loop(c-);
}

Vous pouvez également essayer de googler. Notez le "Voulez-vous dire" (cliquez dessus ...). http://www.google.com/search?q=recursion&spell=1

1
user34537

La récursivité est une alternative aux boucles, il est assez rare qu'elles apportent plus de clarté ou d'élégance à votre code. La réponse de Progman est un bon exemple: s'il n'utilisait pas la récursivité, il serait obligé de garder trace du répertoire dans lequel il se trouve (c'est ce qu'on appelle l'état) et l'adresse de retour d'une méthode sont stockés)

Les exemples factoriels et Fibonacci standard ne sont pas utiles pour comprendre le concept car ils sont faciles à remplacer par une boucle.

1
stacker

Voici un exemple pratique (il y en a déjà plusieurs bons). Je voulais juste en ajouter un qui soit utile à presque tous les développeurs.

À un moment donné, les développeurs devront analyser un objet comme avec une réponse d'une API ou d'un type d'objet ou de tableau.

Cette fonction est initialement appelée pour analyser un objet pouvant contenir uniquement des paramètres, mais que se passe-t-il si l'objet contient également d'autres objets ou tableaux? Ceci devra être traité et, dans l’ensemble, la fonction de base le fait déjà. La fonction s’appelle de nouveau (après avoir confirmé que la clé ou la valeur est un objet ou un tableau) et analyse ce nouvel objet ou tableau. En fin de compte, ce qui est renvoyé est une chaîne qui crée chaque paramètre sur une ligne par souci de lisibilité, mais vous pouvez également enregistrer les valeurs dans un fichier journal ou les insérer dans une base de données ou autre.

J'ai ajouté le paramètre $prefix pour utiliser l'élément parent afin de décrire plus précisément la variable de fin afin que nous puissions voir à quoi la valeur se rapporte. Cela n'inclut pas des choses comme les valeurs NULL, mais cela peut être modifié à partir de cet exemple.

Si vous avez l'objet:

$apiReturn = new stdClass();
$apiReturn->shippingInfo = new stdClass();
$apiReturn->shippingInfo->fName = "Bill";
$apiReturn->shippingInfo->lName = "Test";
$apiReturn->shippingInfo->address1 = "22 S. Deleware St.";
$apiReturn->shippingInfo->city = "Chandler";
$apiReturn->shippingInfo->state = "AZ";
$apiReturn->shippingInfo->Zip = 85225;
$apiReturn->phone = "602-312-4455";
$apiReturn->transactionDetails = array(
    "totalAmt" => "100.00",
     "currency" => "USD",
     "tax" => "5.00",
     "shipping" => "5.00"
);
$apiReturn->item = new stdClass();
$apiReturn->item->name = "T-shirt";
$apiReturn->item->color = "blue";
$apiReturn->item->itemQty = 1;

et utilise:

var_dump($apiReturn);

il reviendra:

object (stdClass) # 1 (4) {["shippingInfo"] => objet (stdClass) # 2 (6) {["fName"] => string (4) "Bill" ["lName"] => string ( 4) "Test" ["address1"] => string (18) "22 S. Deleware St." ["ville"] => chaîne (8) "Chandler" ["état"] => chaîne (2) "AZ" ["Zip"] => int (85225)} ["téléphone"] => chaîne (12 ) "602-312-4455" ["transactionDetails"] => tableau (4) {["totalAmt"] => chaîne (6) "100.00" ["devise"] => chaîne (3) "USD" [" tax "] => string (4)" 5.00 "[" shipping "] => string (4)" 5.00 "} [" item "] => objet (stdClass) # 3 (3) {[" nom "] = > string (7) "T-shirt" ["color"] => string (4) "blue" ["itemQty"] => int (1)}}

et voici le code pour l'analyser dans une chaîne avec un saut de ligne pour chaque paramètre:

function parseObj($obj, $prefix = ''){
    $stringRtrn = '';
    foreach($obj as $key=>$value){
        if($prefix){
            switch ($key) {
                case is_array($key):
                    foreach($key as $k=>$v){
                        $stringRtrn .= parseObj($key, $obj);
                    }
                    break;
                case is_object($key):
                    $stringRtrn .= parseObj($key, $obj);
                    break;
                default:
                    switch ($value) {
                        case is_array($value):
                            $stringRtrn .= parseObj($value, $key);
                            break;
                        case is_object($value):
                            $stringRtrn .= parseObj($value, $key);
                            break;
                        default:
                            $stringRtrn .= $prefix ."_". $key ." = ". $value ."<br>";
                            break;
                    }
                    break;
            }
        } else { // end if($prefix)
            switch($key){
                case is_array($key):
                    $stringRtrn .= parseObj($key, $obj);
                    break;
                case is_object($key):

                    $stringRtrn .= parseObj($key, $obj);
                    break;
                default:
                    switch ($value) {
                        case is_array($value):
                            $stringRtrn .= parseObj($value, $key);
                            break;
                        case is_object($value):
                            $stringRtrn .= parseObj($value, $key);
                            break;                      
                        default:
                            $stringRtrn .= $key ." = ". $value ."<br>";
                            break;
                    } // end inner switch 
            } // end outer switch
        } // end else
    } // end foreach($obj as $key=>$value)
    return $stringRtrn;
} // END parseObj()

Cela retournera l'objet comme suit:

shippingInfo_fName = Bill
shippingInfo_lName = Test
shippingInfo_address1 = 22 S. Deleware St.
shippingInfo_city = Chandler
shippingInfo_state = AZ
shippingInfo_Zip = 85225
phone = 602-312-4455
transactionDetails_totalAmt = 100.00
transactionDetails_currency = USD
transactionDetails_tax = 5.00
transactionDetails_shipping = 5.00
item_name = T-shirt
item_color = blue
item_itemQty = 1

J'ai fait les déclarations imbriquées pour éviter toute confusion avec if . . . ifelse . . . else, mais c'était presque aussi long. Si cela vous aide, demandez simplement le si conditionnel et je peux le coller pour ceux qui en ont besoin.

1
Joel

Si vous ajoutez une certaine valeur (par exemple, "1") à l'exemple d'Anthony Forloney, tout sera clair:

function fact(1) {
  if (1 === 0) { // our base case
  return 1;
  }
  else {
  return 1 * fact(1-1); // <--calling itself.
  }
}

original:

function fact($n) {
  if ($n === 0) { // our base case
    return 1;
  }
  else {
  return $n * fact($n-1); // <--calling itself.
  }
}
0
Kevin

Je ne trouve pas les exemples utiles non plus. Vous ne pouvez pas résoudre deux problèmes à la fois quand il y a des mathématiques en jeu, votre esprit bascule entre deux problèmes. Assez parlé

Exemple:

J'ai une fonction où j'explose des chaînes dans un tableau basé sur le délimiteur :.

public function explodeString($string)
{
  return explode(":", $string);
}

J'ai une autre fonction où je prends des chaînes comme entrée

public function doRec()
{
    $strings = [
        'no:go',
        'hello:world',
        'nested' => [
            'iam:good',
            'bad:array',
            'bad:how',
            'bad:about',
        ]
    ];

    $response = [];

    foreach ($strings as $string) {
        array_Push($response,$this->explodeString($string));
    }

    return $response;
}

Le problème est que mon entrée a un tableau imbriqué et que ma fonction explodeString reçoit un type string. Je peux réécrire du code dans la fonction explodeString pour m'y adapter, mais j'ai toujours besoin de la même fonction pour effectuer la même opération sur ma chaîne. C’est là que je peux appeler la méthode recursively dedans. Donc, voici la dernière fonction explodeString avec récursion.

public function explodeString($string)
{
    if (is_array($string)) {
       $combine = [];
       foreach ($string as $str) {
           array_Push($combine, $this->explodeString($str));
        }
       return $combine;
    }

    return explode(":", $string);
}
0
Faiz Khan

Récursion utilisée pour la constante de Kaprekar 

function KaprekarsConstant($num, $count = 1) {
    $input = str_split($num);
    sort($input);

    $ascendingInput  = implode($input);
    $descendingInput = implode(array_reverse($input));

    $result = $ascendingInput > $descendingInput 
        ? $ascendingInput - $descendingInput 
        : $descendingInput - $ascendingInput;

    if ($result != 6174) {
        return KaprekarsConstant(sprintf('%04d', $result), $count + 1);
    }

    return $count;

}

La fonction continue à s’appeler elle-même avec le résultat du calcul jusqu’à ce qu’elle atteigne la constante de Kaprekars, à laquelle elle renvoie le nombre de fois où les calculs ont été effectués.

/ edit Pour ceux qui ne connaissent pas Kaprekars Constant, il faut une entrée de 4 chiffres avec au moins deux chiffres distincts.

0
Bart

Parcourir un arbre de répertoires est un bon exemple. Vous pouvez faire quelque chose de similaire pour traiter un tableau. Voici une fonction récursive très simple qui traite simplement une chaîne, un simple tableau de chaînes ou un tableau imbriqué de chaînes de n'importe quelle profondeur, en remplaçant les occurrences de 'hello' par 'au revoir' dans la chaîne ou les valeurs du tableau sous-tableau: 

function replaceHello($a) {
    if (! is_array($a)) {
        $a = str_replace('hello', 'goodbye', $a);
    } else {
        foreach($a as $key => $value) {
            $a[$key] = replaceHello($value);
        }
    }
    return $a
}

Il sait quand arrêter car, à un moment donné, la "chose" qu'il traite n'est pas un tableau. Par exemple, si vous appelez replaceHello ("hello"), le message "au revoir" sera renvoyé. Si vous lui envoyez un tableau de chaînes, même s’il s’appellera une fois pour chaque membre du tableau, il retournera le tableau traité.

0
Bob Ray

Voici un exemple très simple de factorielle avec Récursion:

Les factorielles sont un concept mathématique très simple. Ils sont écrits comme 5! et cela signifie 5 * 4 * 3 * 2 * 1. Donc 6! c'est 720 et 4! est 24.

function factorial($number) { 

    if ($number < 2) { 
        return 1; 
    } else { 
        return ($number * factorial($number-1)); 
    } 
}

espérons que cela vous sera utile. :)

0
user4614642

Cela fonctionne un exemple simple récursif (Y)

<?php 


function factorial($y,$x) { 

    if ($y < $x) { 
        echo $y; 
    } else { 
        echo $x; 
        factorial($y,$x+1);
    } 
}

$y=10;

$x=0;
factorial($y,$x);

 ?>
0
Ignacio Olivieri