PHP Recursão
Uma função recursiva é uma função que chama a si mesma. Parece um truque de mágica (ou um jeito de travar o computador), mas é a forma mais natural de resolver problemas que têm "coisas dentro de coisas": pastas dentro de pastas, menus com submenus, comentários com respostas. Nesta aula você vai entender como isso funciona por dentro, quando usar e o que acontece quando dá errado.
- O que é recursão e as duas partes obrigatórias: o caso base e o passo recursivo.
- A contagem regressiva e o fatorial, passo a passo.
- O que é a pilha de chamadas e por que o código depois da chamada roda "na volta".
- Como percorrer uma árvore: um menu com submenus e as pastas da Ana (arrays dentro de arrays).
- O que acontece quando a recursão não para (
Allowed memory size … exhausted) e os outros erros comuns. - Quando é melhor usar um laço no lugar.
Antes, você precisa saber criar uma função com parâmetros e return (aula Funções) e percorrer um array com foreach. Os exemplos rodam na máquina da Ana, dentro da pasta ~/site.
A ideia antes do código: a fila do cinema
A Ana está numa fila enorme de cinema e quer saber em que posição está. Ela não consegue ver o começo da fila. Então ela pergunta para a pessoa da frente: "Em que posição você está?". Essa pessoa também não sabe, e faz a mesma pergunta para quem está na frente dela. E assim por diante…
…até chegar na primeira pessoa da fila. Ela não precisa perguntar a ninguém: responde "estou na posição 1". A resposta volta: quem estava atrás dela responde "então eu sou a 2", o próximo diz "3"… até a resposta chegar na Ana.
Repare nas duas partes dessa história. Toda recursão tem exatamente essas duas:
| Parte | Na fila do cinema | No código |
|---|---|---|
| Caso base (onde para) | A primeira pessoa sabe a resposta sem perguntar. | Um if que devolve a resposta direto, sem chamar a função de novo. |
| Passo recursivo (a pergunta para a frente) | "Pergunto para quem está na minha frente e somo 1." | A função chama a si mesma com um problema menor (mais perto do caso base). |
Se faltar o caso base, a pergunta nunca para. Se o problema não diminuir, nunca se chega ao caso base. Os dois erros terminam do mesmo jeito, como você vai ver no fim da aula.
Primeira recursão: contagem regressiva
Vamos começar pelo exemplo mais simples possível: contar de 3 até 1 e dizer "Fogo!".
<?php
function contagem(int $n): void
{
if ($n === 0) {
echo "Fogo!\n";
return;
}
echo $n, "\n";
contagem($n - 1);
}
contagem(3);
| Linha | O que faz |
|---|---|
function contagem(int $n): void | Cria a função. Ela recebe um número inteiro e não devolve nada (void, "vazio"; veja Tipos e Retorno). |
if ($n === 0) { … return; } | Caso base: quando chegar em zero, escreve "Fogo!" e sai da função. Aqui a recursão para. |
echo $n, "\n"; | Escreve o número atual. |
contagem($n - 1); | Passo recursivo: a função chama a si mesma com um número menor. Cada chamada chega mais perto do zero. |
contagem(3); | A primeira chamada, fora da função, que começa tudo. |
$ php contagem.php 3 2 1 Fogo!
O que aconteceu, chamada por chamada:
contagem(3): 3 não é 0, escreve3e chamacontagem(2).contagem(2): escreve2e chamacontagem(1).contagem(1): escreve1e chamacontagem(0).contagem(0): é o caso base. EscreveFogo!e volta. Como não há mais nada depois das chamadas, todas terminam.
$n
Quando contagem(3) chama contagem(2), o $n valendo 3 não é apagado. Cada chamada da função ganha uma cópia nova e separada das suas variáveis (você vai ver isso em detalhe em Escopo). Em certo momento existem quatro $n ao mesmo tempo: 3, 2, 1 e 0, cada um na sua chamada.
A ida e a volta: a pilha de chamadas
Esse "cada chamada espera a próxima terminar" tem nome: pilha de chamadas (call stack). Pense numa pilha de pratos: cada chamada nova é um prato colocado em cima; quando uma chamada termina, o prato de cima sai, e quem estava embaixo continua de onde parou.
Fica fácil de ver se colocarmos um echo depois da chamada recursiva:
<?php
function idaEVolta(int $n): void
{
if ($n === 0) {
echo "chegou ao fundo\n";
return;
}
echo "descendo: $n\n";
idaEVolta($n - 1);
echo "subindo: $n\n";
}
idaEVolta(3);
$ php ida-e-volta.php descendo: 3 descendo: 2 descendo: 1 chegou ao fundo subindo: 1 « a chamada com $n = 1 continua depois que a de baixo terminou subindo: 2 subindo: 3 « a primeira chamada é a última a terminar
A linha echo "subindo: $n\n" de cada chamada só roda quando a chamada de baixo termina. Por isso os números voltam ao contrário: 1, 2, 3. E repare que cada chamada se lembra do próprio $n, como dissemos.
Experimente: rode, depois troque idaEVolta(3) por idaEVolta(5). Em seguida, apague a linha do echo "descendo…" e veja que a contagem passa a sair só "na volta", de 1 até 5.
<?php
function idaEVolta(int $n): void
{
if ($n === 0) {
echo "chegou ao fundo\n";
return;
}
echo "descendo: $n\n";
idaEVolta($n - 1);
echo "subindo: $n\n";
}
idaEVolta(3);
Uma recursão que devolve valor: o fatorial
O fatorial de um número (escrito com !) é a multiplicação dele por todos os inteiros menores até 1. Ele aparece em matemática para contar combinações: de quantas formas a Ana pode arrumar 5 livros na estante? 5! = 5 × 4 × 3 × 2 × 1 = 120.
Repare num detalhe: 5 × 4 × 3 × 2 × 1 é o mesmo que 5 × (4 × 3 × 2 × 1), ou seja, 5 × 4!. O fatorial de 5 é 5 vezes o fatorial de 4. Isso é um problema definido por uma versão menor dele mesmo, o lugar perfeito para recursão:
| Regra | Parte |
|---|---|
1! = 1 (e, por definição, 0! = 1) | Caso base |
n! = n × (n − 1)! | Passo recursivo |
<?php
function fatorial(int $n): int
{
if ($n <= 1) {
return 1;
}
return $n * fatorial($n - 1);
}
echo fatorial(5), "\n";
for ($i = 0; $i <= 6; $i++) {
echo "$i! = ", fatorial($i), "\n";
}
echo fatorial(20), "\n";
- function fatorial(int $n): int
- Recebe um inteiro e devolve um inteiro.
- if ($n <= 1) { return 1; }
- Caso base. Usamos
<= 1(e não=== 1) para quefatorial(0)também pare aqui. - return $n * fatorial($n - 1);
- Passo recursivo: para calcular a multiplicação, o PHP precisa primeiro do resultado de
fatorial($n - 1). Ele chama a função, espera a resposta, multiplica por$ne devolve.
$ php fatorial.php 120 0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 2432902008176640000 « 20!, já um número enorme
Seguindo o fatorial(4) de perto
Na ida, cada chamada fica esperando. Na volta, cada uma recebe a resposta de baixo, faz a sua multiplicação e passa o resultado para cima:
| Chamada | Na ida (esperando) | Na volta (devolve) |
|---|---|---|
fatorial(4) | 4 * fatorial(3) | 4 * 6 = 24 |
fatorial(3) | 3 * fatorial(2) | 3 * 2 = 6 |
fatorial(2) | 2 * fatorial(1) | 2 * 1 = 2 |
fatorial(1) | caso base, não chama ninguém | 1 |
Leia a coluna "na volta" de baixo para cima: 1, 2, 6, 24. É a mesma fila do cinema, com multiplicação no lugar da soma.
Missão: o caso base devolve o valor errado, e tudo dá zero. Rode, veja o problema e corrija o return de dentro do if para que 4! = 24 e 5! = 120 apareçam.
<?php
function fatorial(int $n): int
{
if ($n === 0) {
return 0;
}
return $n * fatorial($n - 1);
}
echo "4! = ", fatorial(4), "\n";
echo "5! = ", fatorial(5), "\n";
<?php
function fatorial(int $n): int
{
if ($n === 0) {
return 1;
}
return $n * fatorial($n - 1);
}
echo "4! = ", fatorial(4), "\n";
echo "5! = ", fatorial(5), "\n";
Por que dava zero? O caso base devolvia 0, e qualquer número multiplicado por zero é zero. O valor do caso base "contamina" todas as voltas: escolher o valor certo nele é tão importante quanto ter um.
Onde a recursão brilha: árvores
Contagem e fatorial são bons para aprender, mas, na prática, um for resolve os dois (você vai ver isso daqui a pouco). A recursão fica insubstituível quando os dados têm uma forma de árvore: um item que tem itens dentro, que têm itens dentro… sem você saber quantos níveis existem.
Um menu com submenus
A Ana quer um menu para o site de receitas. Algumas seções têm subseções, que podem ter subseções também. Em PHP, isso é um array associativo em que cada valor é outro array (os filhos), vazio quando não há filhos:
<?php
$menu = [
"Início" => [],
"Receitas" => [
"Doces" => [
"Bolos" => [],
"Tortas" => [],
],
"Salgados" => [],
],
"Contato" => [],
];
function mostrarMenu(array $itens, int $nivel = 0): void
{
foreach ($itens as $nome => $filhos) {
echo str_repeat(" ", $nivel), "- ", $nome, "\n";
mostrarMenu($filhos, $nivel + 1);
}
}
mostrarMenu($menu);
- array $itens, int $nivel = 0
- A função recebe um pedaço do menu e o nível de profundidade, que começa em 0 se ninguém disser nada (valor padrão; veja Parâmetros).
- foreach ($itens as $nome => $filhos)
- Para cada item deste nível:
$nomeé o texto ("Receitas") e$filhosé o array de dentro. - str_repeat(" ", $nivel)
- Repete 4 espaços
$nivelvezes: é o recuo que mostra a profundidade. - mostrarMenu($filhos, $nivel + 1)
- Passo recursivo: "mostre os filhos deste item, um nível mais para dentro". O problema diminui porque
$filhosé um pedaço menor da árvore.
$ php menu.php
- Início
- Receitas
- Doces
- Bolos
- Tortas
- Salgados
- Contato
Onde está o caso base? Ele está escondido: quando um item não tem filhos, $filhos é [] e o foreach de um array vazio não faz nenhuma volta. A função termina sem chamar mais ninguém. Todo array vazio é uma "folha" da árvore.
O mais bonito: se a Ana acrescentar "Brigadeiros" => [] dentro de "Bolos", a função continua funcionando sem mudar uma vírgula. Ela não sabe quantos níveis existem, nem precisa.
O mesmo menu numa página web
No site, o menu vira listas HTML (<ul>) dentro de listas. A função agora devolve o HTML como texto em vez de escrevê-lo. Repare no htmlspecialchars: o nome de cada item vai para o HTML protegido, como deve ser com todo texto que vira página (veja Escapar a Saída).
Missão: rode e veja o menu na aba Resultado. Depois acrescente o item "Pudins" dentro de "Doces", ao lado de Bolos e Tortas. A função não precisa mudar.
<?php
$menu = [
"Início" => [],
"Receitas" => [
"Doces" => [
"Bolos" => [],
"Tortas" => [],
],
"Salgados" => [],
],
"Contato" => [],
];
function menuHtml(array $itens): string
{
if ($itens === []) {
return "";
}
$html = "<ul>\n";
foreach ($itens as $nome => $filhos) {
$html .= "<li>" . htmlspecialchars($nome) . menuHtml($filhos) . "</li>\n";
}
return $html . "</ul>\n";
}
?>
<!doctype html>
<html lang="pt-BR">
<head><meta charset="utf-8"><title>Receitas da Ana</title></head>
<body>
<h1>Receitas da Ana</h1>
<?= menuHtml($menu) ?>
</body>
</html>
<?php
$menu = [
"Início" => [],
"Receitas" => [
"Doces" => [
"Bolos" => [],
"Tortas" => [],
"Pudins" => [],
],
"Salgados" => [],
],
"Contato" => [],
];
function menuHtml(array $itens): string
{
if ($itens === []) {
return "";
}
$html = "<ul>\n";
foreach ($itens as $nome => $filhos) {
$html .= "<li>" . htmlspecialchars($nome) . menuHtml($filhos) . "</li>\n";
}
return $html . "</ul>\n";
}
?>
<!doctype html>
<html lang="pt-BR">
<head><meta charset="utf-8"><title>Receitas da Ana</title></head>
<body>
<h1>Receitas da Ana</h1>
<?= menuHtml($menu) ?>
</body>
</html>
Aqui o caso base é explícito: if ($itens === []) return "";. Sem ele, todo item sem filhos ganharia um <ul></ul> vazio no HTML. O código-fonte gerado pelo exemplo (antes da missão) é este; note como cada <ul> de dentro fica no meio de um <li>:
$ php menu-web.php <!doctype html> <html lang="pt-BR"> <head><meta charset="utf-8"><title>Receitas da Ana</title></head> <body> <h1>Receitas da Ana</h1> <ul> <li>Início</li> <li>Receitas<ul> <li>Doces<ul> <li>Bolos</li> <li>Tortas</li> </ul> </li> <li>Salgados</li> </ul> </li> <li>Contato</li> </ul> </body> </html>
As pastas da Ana: somar o que está dentro
Agora uma árvore em que a função precisa juntar respostas. A Ana representou as pastas do computador assim: um arquivo é nome => tamanho em bytes; uma pasta é nome => [o que tem dentro]. Quanto espaço tudo ocupa?
<?php
// nome do arquivo => tamanho em bytes; nome da pasta => [o que tem dentro]
$casa = [
"frutas.txt" => 47,
"Documentos" => [
"notas.txt" => 69,
"alunos.csv" => 180,
"projetos" => [
"site" => [
"index.php" => 340,
"ola.php" => 26,
],
],
],
"Música" => [],
];
function tamanhoTotal(array $pasta): int
{
$total = 0;
foreach ($pasta as $nome => $conteudo) {
if (is_array($conteudo)) {
$total += tamanhoTotal($conteudo); // é uma pasta: pergunte a ela
} else {
$total += $conteudo; // é um arquivo: some o tamanho
}
}
return $total;
}
echo "Tudo junto: ", tamanhoTotal($casa), " bytes\n";
echo "Só Documentos: ", tamanhoTotal($casa["Documentos"]), " bytes\n";
| Trecho | O que faz |
|---|---|
$total = 0; | Cada chamada tem o seu próprio total, que começa em zero. |
is_array($conteudo) | "Isto é um array?" Se for, é uma pasta; se não, é o tamanho de um arquivo. |
$total += tamanhoTotal($conteudo); | Passo recursivo: pergunta à subpasta quanto ela ocupa e soma a resposta no total desta pasta. |
$total += $conteudo; | Caso base: um arquivo sabe o próprio tamanho, sem perguntar a ninguém. |
return $total; | Devolve a soma para quem perguntou (a pasta de cima, ou o echo no fim). |
$ php pastas.php Tudo junto: 662 bytes Só Documentos: 615 bytes
Confira de cabeça: site = 340 + 26 = 366; projetos = 366; Documentos = 69 + 180 + 366 = 615; tudo = 47 + 615 + 0 (a pasta Música está vazia) = 662. Cada pasta responde só por si e pergunta às de dentro, exatamente como as pessoas da fila.
array_walk_recursive() visita todas as "folhas" de um array aninhado, e count($array, COUNT_RECURSIVE) conta os elementos de todos os níveis (pastas inclusive). Elas resolvem casos simples; quando você precisa saber o nível, o caminho ou montar HTML, escrever a sua função recursiva é o caminho. Veja Funções de Array.
Recursão × laço
Tudo o que se faz com recursão dá para fazer com um laço, e vice-versa. O fatorial com um for fica assim:
<?php
function fatorial(int $n): int
{
$resultado = 1;
for ($i = 2; $i <= $n; $i++) {
$resultado *= $i;
}
return $resultado;
}
echo fatorial(5), "\n";
$ php fatorial-laco.php 120
Mesmo resultado. Então, qual usar? Há uma diferença prática importante: cada chamada que fica esperando na pilha ocupa memória. Um laço de um milhão de voltas usa a mesma memória do começo ao fim; uma recursão de um milhão de níveis guarda um milhão de chamadas ao mesmo tempo. Veja a soma de 1 até n feita com recursão:
<?php
function somarAte(int $n): int
{
if ($n === 0) {
return 0;
}
return $n + somarAte($n - 1);
}
echo somarAte(100), "\n";
echo somarAte(100000), "\n";
echo somarAte(1000000), "\n";
$ php somar.php | head -4 5050 5000050000 Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /home/ana/site/somar.php on line 7
Cem mil níveis funcionaram; um milhão, não: a memória que o PHP pode usar (128 MB, o padrão do memory_limit) acabou antes. O | head -4 está ali porque, depois dessa linha, o PHP 8.5 mostra o caminho de todas as chamadas que estavam na pilha, mais de 700 mil linhas (mais sobre isso em Quando dá errado). Com um laço, o mesmo cálculo para um milhão não tem problema nenhum.
| Situação | Use | Por quê |
|---|---|---|
| Repetir algo N vezes, contar, somar uma lista | Laço (for, foreach, while) | Mais simples de ler e não gasta memória a cada volta. |
| Dados em árvore: menus, pastas, categorias, comentários com respostas, JSON aninhado | Recursão | Você não sabe quantos níveis existem; um laço precisaria de uma "pilha" feita à mão. |
| Problema definido por uma versão menor dele mesmo (fatorial, potência) | Os dois servem | A recursão fica parecida com a definição; o laço aguenta números maiores. |
| Profundidade muito grande (dezenas de milhares de níveis ou mais) | Laço | A recursão pode esgotar a memória. |
Um menu tem 3 ou 4 níveis; uma pasta de computador, raramente mais de 20. Nesses casos a memória nunca é problema, e a recursão é a escolha certa. O limite só preocupa quando a profundidade depende de um número grande, como no somarAte(1000000).
Quando dá errado
Allowed memory size of 134217728 bytes exhausted
<?php
function contagem(int $n): void
{
echo $n, "\n";
contagem($n - 1);
}
contagem(3);
Não há nenhum if que pare a função. Ela conta 3, 2, 1, 0, −1, −2… e não para nunca. Como a saída é gigantesca, a Ana mandou tudo para um arquivo e olhou só os pedaços:
$ php infinita.php > saida.txt
$ echo $?
255 « 255 = o PHP terminou com erro fatal
$ grep -A4 "Fatal" saida.txt
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /home/ana/site/infinita.php on line 5
Stack trace:
#0 /home/ana/site/infinita.php(5): contagem(-915005)
#1 /home/ana/site/infinita.php(5): contagem(-915004)
#2 /home/ana/site/infinita.php(5): contagem(-915003)
$ tail -3 saida.txt
#915007 /home/ana/site/infinita.php(5): contagem(2)
#915008 /home/ana/site/infinita.php(8): contagem(3)
#915009 {main}
$ wc -l saida.txt
1830022 saida.txt « quase 2 milhões de linhas!
O que significa: "o tamanho de memória permitido, de 134217728 bytes (128 MB), se esgotou (ao tentar reservar mais 262144 bytes)". A função chamou a si mesma cerca de 915 mil vezes; cada chamada que fica esperando ocupa um pouco de memória, até o limite. O Stack trace ("rastro da pilha") lista as chamadas que estavam na pilha, da mais recente (#0, com $n = −915005) até a primeira (contagem(3), na linha 8). O PHP 8.5 passou a mostrar esse rastro também nos erros fatais como este PHP 8.5+; nas versões antigas aparecia só a linha do Fatal error.
Como consertar: toda função recursiva precisa de um caso base, um if que devolva a resposta sem chamar a função de novo. Quando vir Allowed memory size … exhausted e um stack trace em que a mesma função se repete, procure o caso base que falta ou que nunca é alcançado.
No editor do guia: o PHP do navegador tem outros limites de memória e de pilha, então o número de chamadas e a mensagem podem ser diferentes (e a página pode demorar). Prefira testar recursão infinita só na sua máquina, com Ctrl+C à mão.
<?php
function fatorial(int $n): int
{
if ($n === 0) {
return 1;
}
return $n * fatorial($n - 1);
}
echo fatorial(-1), "\n";
$ php negativo.php | head -3 Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /home/ana/site/negativo.php on line 7 Stack trace:
O que aconteceu: o caso base espera $n === 0, mas começando em −1 o $n só diminui: −2, −3, −4… nunca passa pelo zero. É o mesmo erro de não ter caso base.
Como consertar: use uma condição que cubra todos os casos "pequenos demais": if ($n <= 1). Melhor ainda, recuse entradas que não fazem sentido (fatorial de número negativo não existe); você vai aprender a fazer isso com exceções em Exceções.
return na chamada recursiva: none returned
<?php
function fatorial(int $n): int
{
if ($n <= 1) {
return 1;
}
$n * fatorial($n - 1);
}
echo fatorial(5), "\n";
$ php sem-return.php
Fatal error: Uncaught TypeError: fatorial(): Return value must be of type int, none returned in /home/ana/site/sem-return.php:8
Stack trace:
#0 /home/ana/site/sem-return.php(7): fatorial(2)
#1 /home/ana/site/sem-return.php(7): fatorial(3)
#2 /home/ana/site/sem-return.php(7): fatorial(4)
#3 /home/ana/site/sem-return.php(10): fatorial(5)
#4 {main}
thrown in /home/ana/site/sem-return.php on line 8
O que significa: "o valor devolvido deve ser do tipo int, nenhum foi devolvido". A linha 7 calcula $n * fatorial($n - 1), mas joga o resultado fora; a função chega ao } da linha 8 sem return. Quem quebrou foi fatorial(2), a primeira a voltar depois do caso base: o stack trace mostra que ela tinha sido chamada por fatorial(3), que foi chamada por fatorial(4), que foi chamada na linha 10 com 5.
Como consertar: return $n * fatorial($n - 1);. O tipo de retorno : int foi o que denunciou o erro; sem ele, a função devolveria null em silêncio e o echo mostraria uma linha vazia. Mais um motivo para declarar tipos (aula Tipos e Retorno).
<?php
$casa = [
"frutas.txt" => 47,
"Documentos" => [
"notas.txt" => 69,
"alunos.csv" => 180,
],
];
function tamanhoTotal(array $pasta): int
{
$total = 0;
foreach ($pasta as $nome => $conteudo) {
if (is_array($conteudo)) {
tamanhoTotal($conteudo);
} else {
$total += $conteudo;
}
}
return $total;
}
echo tamanhoTotal($casa), " bytes\n";
$ php esqueceu-somar.php 47 bytes « deveria ser 296: os arquivos de Documentos sumiram
O que aconteceu: a função perguntou à pasta Documentos quanto ela ocupa (a chamada calculou 249 direitinho), mas não guardou a resposta. Cada chamada tem o seu próprio $total; o da subpasta não é o mesmo da pasta de cima. Nenhum aviso aparece, porque o código é válido: ele só não faz o que você queria.
Como consertar: $total += tamanhoTotal($conteudo);. Regra prática: numa função recursiva que devolve valor, a chamada recursiva quase sempre aparece num return, numa conta ou numa atribuição, nunca sozinha na linha.
float returned
$ php fatorial.php « o mesmo fatorial.php, com echo fatorial(21) no fim
…
2432902008176640000
Fatal error: Uncaught TypeError: fatorial(): Return value must be of type int, float returned in /home/ana/site/fatorial.php:7
Stack trace:
#0 /home/ana/site/fatorial.php(15): fatorial(21)
#1 {main}
thrown in /home/ana/site/fatorial.php on line 7
O que aconteceu: não é um erro da recursão. 21! passa do maior inteiro que o PHP guarda (PHP_INT_MAX, 9223372036854775807); a conta vira um float e o tipo de retorno int recusa (veja int e float). O erro só aparece na última volta, em fatorial(21), que é quem faz 21 * 20!.
Como consertar: para números tão grandes, devolva float (perdendo precisão) ou use a extensão bcmath, que faz contas com quantos dígitos você quiser.
Referência
| Termo / recurso | O que é |
|---|---|
| Função recursiva | Uma função que chama a si mesma, direta ou indiretamente (a() chama b(), que chama a()). |
| Caso base | A situação em que a função responde sem se chamar. Obrigatório. |
| Passo recursivo | A chamada a si mesma com um problema menor, mais perto do caso base. |
| Pilha de chamadas (call stack) | As chamadas que começaram e ainda esperam terminar. A última a entrar é a primeira a sair. |
Stack trace | A lista dessas chamadas que o PHP mostra num erro, da mais recente (#0) até o {main} (o programa principal). |
is_array($x) | "É um array?" Separa pasta de arquivo, galho de folha. |
memory_limit | Memória máxima de um script (padrão 128 MB). Esgotá-la gera Allowed memory size of … bytes exhausted. |
array_walk_recursive() | Função pronta que visita todas as folhas de um array aninhado. |
count($a, COUNT_RECURSIVE) | Conta os elementos de todos os níveis. |
Exercícios
1. O caso base
Complete a condição para a contagem parar quando chegar em zero:
2. O passo recursivo
Complete a linha do fatorial:
3. Folha ou galho?
Complete o teste que separa uma pasta (um array) de um arquivo:
4. O que idaEVolta(2) mostra?
5. Uma função recursiva terminou com Fatal error: Allowed memory size of 134217728 bytes exhausted e o stack trace repete a mesma função milhares de vezes. O que verificar primeiro?
6. Em qual destes casos a recursão é a escolha mais natural?
Missão final
Missão: complete a função contarArquivos para contar quantos arquivos existem em todas as pastas (as pastas não contam). Se o conteúdo for um array, some o que a subpasta responder; se não for, conte mais um. A resposta certa é A Ana tem 6 arquivos.
<?php
$casa = [
"frutas.txt" => 47,
"Documentos" => [
"notas.txt" => 69,
"alunos.csv" => 180,
"projetos" => [
"site" => [
"index.php" => 340,
"ola.php" => 26,
],
],
],
"Música" => [
"forro.mp3" => 4100000,
],
];
function contarArquivos(array $pasta): int
{
$quantos = 0;
foreach ($pasta as $conteudo) {
// complete aqui
}
return $quantos;
}
echo "A Ana tem ", contarArquivos($casa), " arquivos.\n";
<?php
$casa = [
"frutas.txt" => 47,
"Documentos" => [
"notas.txt" => 69,
"alunos.csv" => 180,
"projetos" => [
"site" => [
"index.php" => 340,
"ola.php" => 26,
],
],
],
"Música" => [
"forro.mp3" => 4100000,
],
];
function contarArquivos(array $pasta): int
{
$quantos = 0;
foreach ($pasta as $conteudo) {
if (is_array($conteudo)) {
$quantos += contarArquivos($conteudo);
} else {
$quantos++;
}
}
return $quantos;
}
echo "A Ana tem ", contarArquivos($casa), " arquivos.\n";
Resumo
- Uma função recursiva chama a si mesma. Ela precisa de um caso base (responde sem se chamar) e de um passo recursivo (se chama com um problema menor).
- Cada chamada tem as suas próprias variáveis. As chamadas ficam empilhadas esperando; o código depois da chamada recursiva roda "na volta", em ordem contrária.
- Numa recursão que devolve valor, use a resposta da chamada:
return $n * fatorial($n - 1);,$total += tamanhoTotal($sub);. - A recursão brilha em árvores: menus, pastas, categorias, comentários com respostas. Para contar e repetir, prefira um laço.
- Sem caso base (ou com um caso base inalcançável), o PHP esgota a memória:
Fatal error: Allowed memory size of … bytes exhausted, seguido de um stack trace com a mesma função repetida.