Dia 6 — Funções de array: PHP e MySQL
array_map e array_filter
array_map e array_filter
São as duas funções que mais substituem laço em PHP. array_map transforma cada elemento e devolve um array do mesmo tamanho; array_filter seleciona os elementos que passam num teste e devolve um array menor. As duas recebem um *callback* — uma função pasada como argumento — que pode ser nomeada, uma closure ou uma arrow function.
Como se usa
$nomes = ['ana', 'bruno', 'carla']; $maiusculos = array_map('strtoupper', $nomes); $longos = array_filter($nomes, fn (string $n): bool => strlen($n) > 4);
O callback é chamado uma vez por elemento. Em array_map o retorno substitui o valor original; em array_filter o retorno é um booleano que decide se o elemento fica ou sai.
| Função | Assinatura | Devolve |
|---|---|---|
array_map | array_map(callable $callback, array $array, array ...$outros): array | transformação de cada elemento |
array_filter | array_filter(array $array, ?callable $callback = null, int $mode = ARRAY_FILTER_USE_BOTH): array | só os elementos aprovados |
array_map com dois arrays | array_map($fn, $a, $b) | o callback recebe os dois valores por vez |
array_filter com chaves | fn ($valor, $chave) => ... | o segundo argumento do callback é a chave |
array_filter por chave | fn ($chave) => ... com array_keys($linhas, $procura, true) antes | busca pela chave |
O que cada uma preserva
array_map com um único array mantém as chaves de origem: num array associativo, array_map('strtoupper', $aluno) devolve ['nome' => 'ANA', ...]. array_filter também mantém as chaves, e é por isso que o resultado de um filtro costuma vir com índice furado ([0, 2, 3]). Duas saídas resolvem isso:
$aprovados = array_values(array_filter($turma, fn (array $a): bool => $a['nota'] >= 7)); $maiores = array_filter($notas, fn (float $n): bool => $n >= 7, ARRAY_FILTER_USE_BOTH);
O terceiro argumento de array_filter é o modo: 0 (padrão) passa só o valor ao callback, ARRAY_FILTER_USE_KEY passa só a chave, e ARRAY_FILTER_USE_BOTH passa os dois. Sem callback, array_filter($array) remove tudo que for falsy — 0, "", null, [], false — o que quase nunca é o que se quer.
Callback é só uma função.
'strtoupper'(string),fn ($v) => $v * 2(arrow function) e uma função nomeada são a mesma coisa para estas duas funções. O que muda é quem escreve a lógica, não quem a executa.
Exemplo
<?php // ============================================================ // AULA 1 - array_map transforma, array_filter seleciona // ============================================================ declare(strict_types=1); $notas = [7.5, 4.0, 9.0, 6.5, 8.0]; // array_map: cada elemento vira outro. O callback pode ser uma funcao // interna, uma arrow function ou uma funcao nomeada. echo "Originais: " . implode(', ', $notas) . "\n"; echo "Dobro: " . implode(', ', array_map(fn (float $n): float => $n * 2, $notas)) . "\n"; echo "strtoupper: " . implode(', ', array_map('strtoupper', ['ana', 'bruno'])) . "\n"; echo "Com teto 10: " . implode(', ', array_map(fn (float $n): float => min($n + 1, 10.0), $notas)) . "\n\n"; // array_map com DOIS arrays: o callback recebe um valor de cada um por vez. $nomes = ['Ana', 'Bruno', 'Carla', 'Davi']; $idades = [31, 25, 28, 34]; $rotulos = array_map(fn (string $n, int $i): string => "$n tem $i anos", $nomes, $idades); echo "Rotulos de dois arrays:\n"; foreach ($rotulos as $rotulo) { echo " $rotulo\n"; } echo "\n"; // array_map em um unico array MANTEM as chaves do original. $usuario = ['nome' => 'ana souza', 'cidade' => 'recife', 'nivel' => 'admin']; $maiusculo = array_map('strtoupper', $usuario); echo "array_map preservou as chaves: " . json_encode($maiusculo, JSON_UNESCAPED_UNICODE) . "\n\n"; // array_filter: o callback devolve bool. O que for true fica. $aprovados = array_filter($notas, fn (float $n): bool => $n >= 7.0); echo "Aprovados (>= 7): " . implode(', ', $aprovados) . "\n"; echo "Quantos: " . count($aprovados) . " de " . count($notas) . "\n"; // CUIDADO: array_filter preserva as chaves, entao os indices ficam com buraco. echo "Indices do resultado: " . implode(', ', array_keys($aprovados)) . "\n"; $reindexado = array_values($aprovados); echo "Depois do array_values: " . implode(', ', $reindexado) . "\n\n"; // O segundo argumento do callback e a chave (ARRAY_FILTER_USE_BOTH). $precos = ['arroz' => 22.50, 'feijao' => 8.90, 'carne' => 45.00, 'leite' => 6.50]; $caros = array_filter($precos, fn (float $v, string $k): bool => $v > 20.0, ARRAY_FILTER_USE_BOTH); echo "Itens acima de 20 reais: " . json_encode($caros, JSON_UNESCAPED_UNICODE) . "\n\n"; // ARRAY_FILTER_USE_KEY passa so a chave ao callback. $somenteNumerico = array_filter( ['10', '20', 'a', 'b', '30'], fn (string $k): bool => is_numeric($k), ARRAY_FILTER_USE_KEY ); echo "Chaves numericas: " . implode(', ', $somenteNumerico) . "\n"; // Sem callback, array_filter remove tudo que e falsy: 0, '', null, [], false. $misturado = [0, 1, '', 'texto', null, [], false, 2.5]; echo "\nSem callback sobra: " . json_encode(array_values(array_filter($misturado))) . "\n"; // Os dois juntos: primeiro filtra, depois transforma. $turma = [ ['id' => 1, 'nome' => 'Ana', 'nota' => 8.5], ['id' => 2, 'nome' => 'Bruno', 'nota' => 6.0], ['id' => 3, 'nome' => 'Carla', 'nota' => 9.5], ['id' => 4, 'nome' => 'Davi', 'nota' => 7.0], ]; $aprovadosTurma = array_filter($turma, fn (array $a): bool => $a['nota'] >= 7.0); $nomesAprovados = array_map(fn (array $a): string => $a['nome'], $aprovadosTurma); echo "\nAprovados: " . implode(', ', array_values($nomesAprovados)) . "\n";
Saída real
Originais: 7.5, 4, 9, 6.5, 8
Dobro: 15, 8, 18, 13, 16
strtoupper: ANA, BRUNO
Com teto 10: 8.5, 5, 10, 7.5, 9
Rotulos de dois arrays:
Ana tem 31 anos
Bruno tem 25 anos
Carla tem 28 anos
Davi tem 34 anos
array_map preservou as chaves: {"nome":"ANA SOUZA","cidade":"RECIFE","nivel":"ADMIN"}
Aprovados (>= 7): 7.5, 9, 8
Quantos: 3 de 5
Indices do resultado: 0, 2, 4
Depois do array_values: 7.5, 9, 8
Itens acima de 20 reais: {"arroz":22.5,"carne":45}
Chaves numericas: 10, 20, a, b, 30
Sem callback sobra: [1,"texto",2.5]
Aprovados: Ana, Carla, Davi
array_reduce
array_reduce
array_reduce percorre a lista acumulando um valor até o fim, como um foreach que termina em um echo de uma conta. Ela serve para soma total, contagem, produto, concatenação e qualquer agregação que termine em um único valor. É a função que substitui o foreach de acumulador com $x = $x + $y.
Como se usa
$soma = array_reduce($notas, fn (float $acumulado, float $n): float => $acumulado + $n, 0.0);
O callback recebe dois argumentos: o acumulador (o que já foi somado) e o elemento atual. O terceiro argumento da função, o valor inicial, entra como o acumulador na primeira chamada.
| Função | Assinatura | Exemplo |
|---|---|---|
array_reduce | array_reduce(array $array, callable $callback, mixed $initial = null): mixed | soma, produto, contagem |
array_sum | array_sum(array $array): int ou float | soma numérica direta, sem callback |
array_product | array_product(array $array): int ou float | multiplica todos os elementos |
max | max(mixed $valor, mixed ...$valores): mixed | max($array) devolve o maior |
min | min(mixed $valor, mixed ...$valores): mixed | min($array) devolve o menor |
Quando o cálculo é só soma, produto ou contagem, use array_sum, array_product e count: são mais rápidos e não dão erro de tipo. array_reduce é para o resto — total de desconto, string concatenada, menor preço com uma condição.
O valor inicial importa
O valor inicial é obrigatório na prática: sem ele, o array_reduce começa com o primeiro elemento e o callback recebe $array[1] em diante, o que quebra código que espera começar em 0.0 ou em ''. Além disso, é ele que define o tipo de saída: somar int com um inicial int devolve int; somar float com inicial 0.0 devolve float.
| Situação | Valor inicial |
|---|---|
| Soma de decimais | 0.0 |
| Concatenar texto | '' |
| Contagem de um filtro | 0 |
| Multiplicar preços | 1 |
| Concatenar com separador | a string vazia, e o separador é tratado dentro do callback |
Se o array estiver vazio,
array_reducedevolve o valor inicial sem nunca chamar o callback. É por isso que$soma / count($notas)quebra com lista vazia: oarray_reducedevolve0.0e ocountdevolve0, e a divisão é por zero.
Exemplo
<?php // ============================================================ // AULA 2 - array_reduce: acumular um resultado item a item // ============================================================ declare(strict_types=1); $notas = [7.5, 4.0, 9.0, 6.5, 8.0]; // O callback recebe o ACUMULADO e o ELEMENTO ATUAL, nessa ordem. // O terceiro argumento de array_reduce e o valor inicial do acumulado. $soma = array_reduce($notas, fn (float $acumulado, float $n): float => $acumulado + $n, 0.0); echo "Soma com array_reduce: $soma\n"; echo "Soma com array_sum: " . array_sum($notas) . "\n\n"; // O valor inicial define o tipo da saida e evita o erro do primeiro item. $somaInt = array_reduce([1, 2, 3], fn (int $acc, int $n): int => $acc + $n, 0); echo "Soma de inteiros (inicial 0): $somaInt\n"; echo "Media: " . round($soma / count($notas), 2) . "\n\n"; // array_sum e array_product resolvem os casos simples sem callback. echo "array_sum: " . array_sum($notas) . "\n"; echo "array_product: " . array_product([1, 2, 3, 4]) . "\n"; echo "max: " . max($notas) . "\n"; echo "min: " . min($notas) . "\n\n"; // Concatenar e uma agregacao valida: o acumulado e uma string. $linguagens = ['PHP', 'SQL', 'HTML', 'CSS']; $frase = array_reduce($linguagens, fn (string $acc, string $item): string => $acc === '' ? $item : "$acc, $item", ''); echo "Frase montada: $frase\n\n"; // Contar com condicao: e o agregador mais usado em relatorio. $turma = [ ['id' => 1, 'nome' => 'Ana', 'nota' => 8.5, 'cidade' => 'Recife'], ['id' => 2, 'nome' => 'Bruno', 'nota' => 6.0, 'cidade' => 'Olinda'], ['id' => 3, 'nome' => 'Carla', 'nota' => 9.5, 'cidade' => 'Recife'], ['id' => 4, 'nome' => 'Davi', 'nota' => 7.0, 'cidade' => 'Jaboatao'], ['id' => 5, 'nome' => 'Elena', 'nota' => 5.5, 'cidade' => 'Recife'], ]; $aprovados = array_reduce($turma, fn (int $acc, array $a): int => $acc + ($a['nota'] >= 7.0 ? 1 : 0), 0); echo "Quantos aprovados (>= 7): $aprovados de " . count($turma) . "\n"; $maiorNota = array_reduce($turma, fn (float $recorde, array $a): float => max($recorde, $a['nota']), 0.0); echo "Maior nota da turma: $maiorNota\n"; $somaNotas = array_reduce($turma, fn (float $acc, array $a): float => $acc + $a['nota'], 0.0); echo "Media da turma: " . round($somaNotas / count($turma), 2) . "\n\n"; // Reduzir para um associative: o acumulado vira chave, o resto vira valor. $porCidade = array_reduce($turma, function (array $acc, array $a): array { $acc[$a['cidade']] = ($acc[$a['cidade']] ?? 0) + 1; return $acc; }, []); echo "Alunos por cidade: " . json_encode($porCidade, JSON_UNESCAPED_UNICODE) . "\n\n"; // Array vazio: o callback nunca e chamado e o valor inicial volta intacto. // Por isso media sobre lista vazia quebra na divisao por zero. $semAlunos = []; $somaVazia = array_reduce($semAlunos, fn (float $acc, float $n): float => $acc + $n, 0.0); echo "Soma de array vazio: $somaVazia\n"; echo "count de array vazio: " . count($semAlunos) . " (dividir por isso quebra)\n"; echo "Media protegida: " . round($somaVazia / max(count($notas), 1), 2) . "\n";
Saída real
Soma com array_reduce: 35
Soma com array_sum: 35
Soma de inteiros (inicial 0): 6
Media: 7
array_sum: 35
array_product: 24
max: 9
min: 4
Frase montada: PHP, SQL, HTML, CSS
Quantos aprovados (>= 7): 3 de 5
Maior nota da turma: 9.5
Media da turma: 7.3
Alunos por cidade: {"Recife":3,"Olinda":1,"Jaboatao":1}
Soma de array vazio: 0
count de array vazio: 0 (dividir por isso quebra)
Media protegida: 0
Ordenação: sort, usort e spaceship
Ordenação: sort, usort e spaceship
Existem duas famílias de ordenação. sort e afins reordenam os valores ou as chaves com a comparação padrão do PHP. usort, uasort e uksort reordenam com um callback que você escreve, e é por isso que eles resolvem o caso real: ordenar lista de registros por um campo, sem perder a associação entre os campos.
O operador spaceship
$a <=> $b // devolve -1, 0 ou 1
É o comparador de três vias, e o usort espera exatamente um retorno assim:
| Retorno | Significado |
|---|---|
-1 | $a vem antes de $b (é menor) |
0 | $a e $b são equivalentes |
1 | $a vem depois de $b (é maior) |
O callback recebe sempre dois elementos e devolve int. Para ordem crescente, o operador vai direto ($a['nota'] <=> $b['nota']); para decrescente, os operandos invertem ($b['nota'] <=> $a['nota']). Ele também funciona direto em if, onde devolve booleano por coerção: if ($a <=> $b) { /* a > b */ }.
Cada função reordena uma coisa diferente
| Função | Assinatura | O que é reordenado | Associações |
|---|---|---|---|
sort | sort(array &$array, int $flags = SORT_REGULAR): bool | os valores | perdidas, chaves renumeradas |
rsort | rsort(array &$array, int $flags = SORT_REGULAR): bool | os valores, decrescente | perdidas |
usort | usort(array &$array, callable $callback): bool | os valores, pelo callback | perdidas |
asort | asort(array &$array, int $flags = SORT_REGULAR): bool | os valores | preservadas |
uasort | uasort(array &$array, callable $callback): bool | os valores, pelo callback | preservadas |
uksort | uksort(array &$array, callable $callback): bool | as chaves, pelo callback | preservadas |
ksort | ksort(array &$array, int $flags = SORT_REGULAR): bool | as chaves | preservadas |
array_multisort | array_multisort(array &$array, mixed ...$rest): bool | vários arrays em paralelo | por posição |
A diferença entre usort e uasort é a mesma entre sort e asort, com callback no lugar da comparação padrão:
usort($turma, fn (array $a, array $b): int => $a['nota'] <=> $b['nota']); // reordena tudo uasort($turma, fn (array $a, array $b): int => $a['nota'] <=> $b['nota']); // reordena, mantém chave/valor juntos uksort($turma, fn (int $a, int $b): int => $b <=> $a); // ordena pelo id
Em $turma (lista de linhas), usort e uasort produzem a mesma ordem, porque a chave é o índice automático. A diferença aparece quando a chave é a identificação: uksort($porNome, ...) ordena mantendo cada nome preso à sua nota; usort($porNome, ...) ordenaria as notas e jogaria os nomes fora.
Escolhendo em três passos
- O array tem valores soltos (nomes, ids, números)?
sortoursort. - É um array associativo e a chave identifica o elemento?
asort,arsort,ksortoukrsort. - Precisa de uma regra que o PHP não tem (por um campo, por texto, por vários campos)?
usort,uasortouuksortcom callback.
Todas as oito recebem o array por referência e devolvem bool. Por isso não funciona sort(minhaFuncao()) nem sort($a + $b): o argumento precisa ser uma variável.
Todas usam ordenação instável (empates podem trocar de lugar entre versões do PHP). Quando o empate importa, acrescente um segundo critério no callback:
$a['nota'] <=> $b['nota'] ?: $a['nome'] <=> $b['nome']. O?:trata a comparação como0no empate, e o PHP 8.0 em diante também ofereceusortcom array de callback para desempate explícito.
Exemplo
<?php // ============================================================ // AULA 3 - Ordenacao: sort, usort e o operador spaceship // ============================================================ declare(strict_types=1); // O operador <=> (spaceship) devolve -1, 0 ou 1. E o que o usort espera: // menor que -> -1 | igual -> 0 | maior que -> 1 echo "spaceship: 3 <=> 7 = " . (3 <=> 7) . " (-1: o da esquerda vem antes)\n"; echo "spaceship: 7 <=> 7 = " . (7 <=> 7) . " ( 0: equivalentes)\n"; echo "spaceship: 9 <=> 7 = " . (9 <=> 7) . " ( 1: o da esquerda vem depois)\n"; echo "No if, o <=> vira booleano: " . var_export(9 <=> 7, true) . "\n\n"; // sort reordena os VALORES e renumera as chaves. $notas = [7.5, 4.0, 9.0, 6.5]; sort($notas); echo "sort (crescente): " . implode(', ', $notas) . "\n"; rsort($notas); echo "rsort (decrescente): " . implode(', ', $notas) . "\n\n"; // sort joga as chaves fora. Com chave que identifica o elemento, // quem serve e asort, que reordena mantendo o par junto. $porAluno = ['Ana' => 8.5, 'Bruno' => 6.0, 'Carla' => 9.5]; $copia = $porAluno; sort($copia); echo "sort em associativo perde a chave: " . json_encode($copia, JSON_UNESCAPED_UNICODE) . "\n"; asort($porAluno); echo "asort mantem o par: " . json_encode($porAluno, JSON_UNESCAPED_UNICODE) . "\n"; arsort($porAluno); echo "arsort mantem o par, invertido: " . json_encode($porAluno, JSON_UNESCAPED_UNICODE) . "\n\n"; // ksort ordena pelas CHAVES, nao pelos valores. ksort($porAluno); echo "ksort por chave: " . json_encode($porAluno, JSON_UNESCAPED_UNICODE) . "\n"; krsort($porAluno); echo "krsort por chave: " . json_encode($porAluno, JSON_UNESCAPED_UNICODE) . "\n\n"; // usort: o COMPARADOR e voce. Ordena o array inteiro, sem preservar chave. $turma = [ ['id' => 1, 'nome' => 'Ana', 'nota' => 8.5], ['id' => 2, 'nome' => 'Bruno', 'nota' => 6.0], ['id' => 3, 'nome' => 'Carla', 'nota' => 9.5], ['id' => 4, 'nome' => 'Davi', 'nota' => 7.0], ]; usort($turma, fn (array $a, array $b): int => $a['nota'] <=> $b['nota']); echo "usort por nota (crescente):\n"; foreach ($turma as $linha) { echo " {$linha['nome']}: {$linha['nota']}\n"; } // Invertendo os operandos do spaceship, a ordem fica decrescente. usort($turma, fn (array $a, array $b): int => $b['nota'] <=> $a['nota']); echo "\nusort por nota (decrescente):\n"; foreach ($turma as $linha) { echo " {$linha['nome']}: {$linha['nota']}\n"; } echo "Chaves depois do usort: " . implode(', ', array_keys($turma)) . " (renumeradas)\n"; // Com dois criterios: quando as notas empatam, o nome decide. usort($turma, fn (array $a, array $b): int => $b['nota'] <=> $a['nota'] ?: $a['nome'] <=> $b['nome']); echo "\nusort com desempate por nome:\n"; foreach ($turma as $linha) { echo " {$linha['nome']}: {$linha['nota']}\n"; } // uasort e usort, mas mantendo a chave ligada ao valor. $porNome = ['Ana' => 8.5, 'Bruno' => 6.0, 'Carla' => 9.5]; uasort($porNome, fn (float $a, float $b): int => $b <=> $a); echo "\nuasort (preserva a associacao): " . json_encode($porNome, JSON_UNESCAPED_UNICODE) . "\n"; usort($porNome, fn (float $a, float $b): int => $a <=> $b); echo "usort (perde a associacao): " . json_encode($porNome, JSON_UNESCAPED_UNICODE) . "\n"; // uksort ordena pelas CHAVAS: util para deixar o menu na ordem do banco. $menu = ['sobre' => 3, 'contato' => 1, 'home' => 2, 'blog' => 4]; uksort($menu, fn (string $a, string $b): int => $a <=> $b); echo "\nuksort por chave: " . json_encode($menu, JSON_UNESCAPED_UNICODE) . "\n\n"; // Todas devolvem bool, entao nunca use o resultado direto como valor. // E o array precisa ser uma variavel: sort(minhaFuncao()) nao compila. $falha = ['c', 'a', 'b']; $ok = sort($falha); echo "sort devolveu " . var_export($ok, true) . " e a variavel ficou: " . implode(', ', $falha) . "\n";
Saída real
spaceship: 3 <=> 7 = -1 (-1: o da esquerda vem antes)
spaceship: 7 <=> 7 = 0 ( 0: equivalentes)
spaceship: 9 <=> 7 = 1 ( 1: o da esquerda vem depois)
No if, o <=> vira booleano: 1
sort (crescente): 4, 6.5, 7.5, 9
rsort (decrescente): 9, 7.5, 6.5, 4
sort em associativo perde a chave: [6,8.5,9.5]
asort mantem o par: {"Bruno":6,"Ana":8.5,"Carla":9.5}
arsort mantem o par, invertido: {"Carla":9.5,"Ana":8.5,"Bruno":6}
ksort por chave: {"Ana":8.5,"Bruno":6,"Carla":9.5}
krsort por chave: {"Carla":9.5,"Bruno":6,"Ana":8.5}
usort por nota (crescente):
Bruno: 6
Davi: 7
Ana: 8.5
Carla: 9.5
usort por nota (decrescente):
Carla: 9.5
Ana: 8.5
Davi: 7
Bruno: 6
Chaves depois do usort: 0, 1, 2, 3 (renumeradas)
usort com desempate por nome:
Carla: 9.5
Ana: 8.5
Davi: 7
Bruno: 6
uasort (preserva a associacao): {"Carla":9.5,"Ana":8.5,"Bruno":6}
usort (perde a associacao): [6,8.5,9.5]
uksort por chave: {"blog":4,"contato":1,"home":2,"sobre":3}
sort devolveu true e a variavel ficou: a, b, c
Combinando funções de array
Combinando funções de array
Encadear funções de array é o que substitui o laço aninhado. Cada etapa pega o array anterior e entrega um novo, e o conjunto inteiro lê como uma frase: filtra, transforma, ordena, resume. Quando o foreach tem três níveis e um if dentro, o encadeamento quase sempre fica menor e mais claro.
Como se usa
$busca = array_filter($turma, fn (array $a): bool => $a['nota'] >= 7); usort($busca, fn (array $a, array $b): int => $b['nota'] <=> $a['nota']); $nomes = array_column($busca, 'nome');
Cada função devolve um array novo; a original nunca é alterada por array_map, array_filter, array_column e array_slice. Só as que recebem por referência (sort, usort, array_push, array_pop) mexem no array passado.
O pipeline mais usado
| Etapa | Função | O que faz |
|---|---|---|
| 1. selecionar | array_filter | mantém o que interessa |
| 2. ordenar | usort com <=> | põe em ordem |
| 3. extrair | array_column | vira lista simples |
| 4. deduzir | array_sum, count, max | reduz a um valor |
$pagas = array_filter($turma, fn (array $a): bool => $a['nota'] >= 7.0); $ordem = array_column($pagas, 'nome'); usort($ordem, 'strcmp'); // o callback tambem pode ser strcmp
O callback não precisa ser closure: usort($nomes, 'strcmp'), usort($nomes, 'strcasecmp') e usort($nomes, 'strnatcmp') ordenam texto, com a diferença de diferenciar maiúsculas, ignorar a diferença ou interpretar "item2" depois de "item10".
Agrupar e contar
O agrupamento não tem função pronta. A forma curta é um foreach com array de acumulação; a forma com array_reduce devolve o mesmo resultado e deixa a intenção explícita:
| Abordagem | Trecho |
|---|---|
foreach | foreach ($linhas as $l) { $por[$l['cidade']][] = $l['nome']; } |
array_reduce | array_reduce($linhas, fn ($acc, $l) => [...$acc, $l['cidade'] => [...($acc[$l['cidade']] ?? []), $l['nome']]], []) |
| contar por grupo | array_count_values(array_column($linhas, 'cidade')) |
| somar por grupo | array_reduce com $acc[$chave] = ($acc[$chave] ?? 0) + $valor |
array_count_values é o atalho para contar ocorrências de um campo — é o GROUP BY do PHP, válido porque a lista já está em memória.
Encadeamento longo vira ilegível. A partir de três ou quatro etapas, ou quando alguma delas tem mais de uma linha de callback, o
foreachpasso a passo com nomes descritivos ($aprovados,$ordenados,$nomes) é mais fácil de depurar do que uma expressão de dez linhas.
Exemplo
<?php // ============================================================ // AULA 4 - Combinando funcoes de array: pipeline, agrupamento e deduzir // ============================================================ declare(strict_types=1); $turma = [ ['id' => 1, 'nome' => 'Ana', 'nota' => 8.5, 'cidade' => 'Recife'], ['id' => 2, 'nome' => 'bruno', 'nota' => 6.0, 'cidade' => 'Olinda'], ['id' => 3, 'nome' => 'Carla', 'nota' => 9.5, 'cidade' => 'Recife'], ['id' => 4, 'nome' => 'Davi', 'nota' => 7.0, 'cidade' => 'Jaboatao'], ['id' => 5, 'nome' => 'elena', 'nota' => 5.5, 'cidade' => 'Recife'], ]; // --- Pipeline 1: filtrar, ordenar, extrair --- $aprovados = array_filter($turma, fn (array $a): bool => $a['nota'] >= 7.0); usort($aprovados, fn (array $a, array $b): int => $b['nota'] <=> $a['nota']); $nomesAprovados = array_column($aprovados, 'nome'); echo "Aprovados, do maior para o menor: " . implode(', ', $nomesAprovados) . "\n"; echo "Quantidade: " . count($aprovados) . " de " . count($turma) . "\n\n"; // --- Pipeline 2: a mesma coisa em uma expressao, com array_column e sum --- $soma = array_sum(array_column( array_filter($turma, fn (array $a): bool => $a['cidade'] === 'Recife'), 'nota' )); echo "Soma das notas dos alunos do Recife: $soma\n\n"; // --- O original nao e alterado por map, filter, column e slice --- echo "A turma original continua com " . count($turma) . " linhas e nota inicial " . $turma[0]['nota'] . "\n\n"; // --- Ordenar texto: o callback pode ser uma funcao interna --- $linguagens = ['SQL', 'php', 'HTML', 'css', 'Java']; usort($linguagens, 'strcasecmp'); echo "strcasecmp (ignora maiusculas): " . implode(', ', $linguagens) . "\n"; $itens = ['item10', 'item2', 'Item1']; usort($itens, 'strnatcmp'); echo "strnatcmp (ordem natural): " . implode(', ', $itens) . "\n\n"; // --- Agrupar com foreach: a forma mais legivel --- $porCidade = []; foreach ($turma as $aluno) { $porCidade[$aluno['cidade']][] = $aluno['nome']; } echo "Nomes por cidade:\n"; foreach ($porCidade as $cidade => $nomes) { echo " $cidade: " . implode(', ', $nomes) . "\n"; } // --- Agrupar com array_reduce: mesmo resultado, intencao explicita --- $porCidadeReduce = array_reduce($turma, function (array $acc, array $a): array { $acc[$a['cidade']] = ($acc[$a['cidade']] ?? 0) + 1; return $acc; }, []); echo "\nQuantos por cidade (array_reduce): " . json_encode($porCidadeReduce, JSON_UNESCAPED_UNICODE) . "\n"; // --- array_count_values e o GROUP BY quando a lista ja esta em memoria --- echo "array_count_values: " . json_encode(array_count_values(array_column($turma, 'cidade')), JSON_UNESCAPED_UNICODE) . "\n\n"; // --- Somar por grupo: a nota media de cada cidade --- $somaPorCidade = array_reduce($turma, function (array $acc, array $a): array { $acc[$a['cidade']] = ($acc[$a['cidade']] ?? 0) + $a['nota']; return $acc; }, []); $qtdPorCidade = array_count_values(array_column($turma, 'cidade')); echo "Media por cidade:\n"; foreach ($somaPorCidade as $cidade => $soma) { printf(" %-9s %5.2f\n", $cidade, $soma / $qtdPorCidade[$cidade]); } // --- Deduzir varios numeros de uma vez --- echo "\nResumo da turma:\n"; echo " maior nota: " . max(array_column($turma, 'nota')) . "\n"; echo " menor nota: " . min(array_column($turma, 'nota')) . "\n"; echo " media: " . round(array_sum(array_column($turma, 'nota')) / count($turma), 2) . "\n"; echo " aprovados: " . count(array_filter($turma, fn (array $a): bool => $a['nota'] >= 7.0)) . "\n";
Saída real
Aprovados, do maior para o menor: Carla, Ana, Davi
Quantidade: 3 de 5
Soma das notas dos alunos do Recife: 23.5
A turma original continua com 5 linhas e nota inicial 8.5
strcasecmp (ignora maiusculas): css, HTML, Java, php, SQL
strnatcmp (ordem natural): Item1, item2, item10
Nomes por cidade:
Recife: Ana, Carla, elena
Olinda: bruno
Jaboatao: Davi
Quantos por cidade (array_reduce): {"Recife":3,"Olinda":1,"Jaboatao":1}
array_count_values: {"Recife":3,"Olinda":1,"Jaboatao":1}
Media por cidade:
Recife 7.83
Olinda 6.00
Jaboatao 7.00
Resumo da turma:
maior nota: 9.5
menor nota: 5.5
media: 7.3
aprovados: 3