PHP Ordenar Arrays
Colocar coisas em ordem é uma das tarefas mais comuns de um programa: nomes em ordem alfabética, produtos do mais barato ao mais caro, alunos da maior nota para a menor. O PHP tem uma família de funções para isso, todas terminadas em sort ("ordenar"). As letras antes do sort dizem o que é ordenado e se as chaves são mantidas. Esta aula mostra cada uma, com saídas reais, e as armadilhas: maiúsculas, números guardados como texto e acentos.
- Ordenar listas com
sort(crescente) ersort(decrescente). - Por que as funções de ordenar mudam o array e devolvem só
true. - Ordenar arrays associativos sem perder as chaves:
asort,arsort,ksort,krsort. - Ordenar do seu jeito com
usorte o operador "nave espacial"<=>. - Ordenar a tabela de alunos por nota, por nome, e pelos dois.
- O que acontece com maiúsculas, com
"10"e"9", e com nomes acentuados como"Ágata".
sort e rsort: ordenar uma lista
<?php
$notas = [8.5, 6.0, 9.5, 7.0];
sort($notas);
echo implode(" ", $notas), "\n";
rsort($notas);
echo implode(" ", $notas), "\n";
$nomes = ["Diego", "Ana", "Carla", "Bruno"];
sort($nomes);
echo implode(", ", $nomes), "\n";
rsort($nomes);
echo implode(", ", $nomes), "\n";
- sort($notas);
- Ordena
$notasdo menor para o maior (ordem crescente). Números por valor, textos em ordem alfabética. - rsort($notas);
- O
ré de reverse ("ao contrário"): do maior para o menor (ordem decrescente).
$ php o-sort.php 6 7 8.5 9.5 9.5 8.5 7 6 Ana, Bruno, Carla, Diego Diego, Carla, Bruno, Ana
Repare que a linha é só sort($notas);, sem =. A função mexe no próprio array: depois dela, $notas está ordenado.
As funções de ordenar devolvem true, não o array
Quem vem de outras linguagens costuma escrever $ordenados = sort($nomes);. Veja o que acontece:
<?php
$nomes = ["Diego", "Ana", "Carla"];
$ordenados = sort($nomes);
var_dump($ordenados);
print_r($nomes);
foreach ($ordenados as $n) {
echo $n, "\n";
}
$ php o-retorno.php
bool(true) « sort devolve só true ("deu certo")
Array
(
[0] => Ana « quem ficou ordenado foi o próprio $nomes
[1] => Carla
[2] => Diego
)
Warning: foreach() argument must be of type array|object, true given in /home/ana/site/o-retorno.php on line 6
Todas as funções desta aula funcionam assim: ordenam no lugar e devolvem true. O aviso da última linha diz que o foreach precisa de um array, mas recebeu true. Esse comportamento vem da forma como o array é passado à função ("por referência", explicada em Parâmetros).
E se eu quiser manter a ordem original?
Faça uma cópia antes e ordene a cópia. No PHP, $b = $a; copia o array inteiro: mexer em um não mexe no outro.
<?php
$chegada = ["Diego", "Ana", "Carla"];
$alfabetica = $chegada; // cópia
sort($alfabetica);
echo "Ordem de chegada: ", implode(", ", $chegada), "\n";
echo "Ordem alfabética: ", implode(", ", $alfabetica), "\n";
$ php o-copia.php Ordem de chegada: Diego, Ana, Carla Ordem alfabética: Ana, Carla, Diego
Arrays associativos: asort, arsort, ksort, krsort
O sort tem um efeito colateral sério: ele joga fora as chaves e renumera tudo a partir de 0. Numa lista, tudo bem. Numa tabela de preços, é um desastre:
<?php $precos = ["café" => 18.9, "pão" => 0.75, "leite" => 5.49, "arroz" => 6.2]; $copia = $precos; sort($copia); print_r($copia); asort($precos); print_r($precos); arsort($precos); print_r($precos); ksort($precos); print_r($precos); krsort($precos); print_r($precos);
$ php o-assoc.php
Array
(
[0] => 0.75 « sort: os preços em ordem, mas de quê? os nomes sumiram
[1] => 5.49
[2] => 6.2
[3] => 18.9
)
Array
(
[pão] => 0.75 « asort: ordena pelo VALOR e mantém cada chave com o seu valor
[leite] => 5.49
[arroz] => 6.2
[café] => 18.9
)
Array
(
[café] => 18.9 « arsort: pelo valor, do maior para o menor
[arroz] => 6.2
[leite] => 5.49
[pão] => 0.75
)
Array
(
[arroz] => 6.2 « ksort: pela CHAVE, em ordem alfabética
[café] => 18.9
[leite] => 5.49
[pão] => 0.75
)
Array
(
[pão] => 0.75 « krsort: pela chave, de Z para A
[leite] => 5.49
[café] => 18.9
[arroz] => 6.2
)
Para lembrar, leia as letras do nome:
| Letra | Significa | Exemplo |
|---|---|---|
a | associative: mantém a associação chave => valor e ordena pelo valor | asort, arsort |
k | key: ordena pela chave (e também mantém a associação) | ksort, krsort |
r | reverse: ordem decrescente | rsort, arsort, krsort |
u | user: você escreve a regra de comparação | usort, uasort, uksort |
Lista (chaves 0, 1, 2…)? sort/rsort. Array com chaves que importam? asort/arsort (pelo valor) ou ksort/krsort (pela chave). Nunca use sort num array associativo.
Troque asort por arsort, ksort, krsort e sort e compare os resultados. Qual delas perde os nomes dos produtos?
<?php $estoque = ["café" => 12, "pão" => 4, "leite" => 15, "açúcar" => 7]; asort($estoque); print_r($estoque); ksort($estoque); print_r($estoque);
usort: a sua própria regra
E a tabela de alunos da aula Arrays Multidimensionais? Cada aluno é um array com nome, idade e nota. Ordenar "os alunos" não tem resposta óbvia: por nome? por nota? O usort deixa você dizer. Ele recebe o array e uma função de comparação.
A função de comparação e o operador <=>
Durante a ordenação, o PHP pega dois itens, $a e $b, e pergunta à sua função: "qual vem primeiro?". A função responde com um número:
- negativo:
$avem antes; - zero: tanto faz (são "iguais" para a ordem);
- positivo:
$bvem antes.
O operador <=>, apelidado de nave espacial (spaceship) pelo formato, dá exatamente essa resposta:
<?php
var_dump(1 <=> 2);
var_dump(2 <=> 2);
var_dump(3 <=> 2);
var_dump("Ana" <=> "Bruno");
$ php o-nave.php int(-1) « o da esquerda é menor int(0) « iguais int(1) « o da esquerda é maior int(-1) « "Ana" vem antes de "Bruno"
Com isso, a regra "do menor para o maior" é $a <=> $b, e "do maior para o menor" é trocar a ordem: $b <=> $a. O operador também aparece em Comparação.
Ordenar os alunos por nota e por nome
<?php
$alunos = [
["nome" => "Diego", "idade" => 41, "nota" => 6.0],
["nome" => "Ana", "idade" => 29, "nota" => 9.5],
["nome" => "Elisa", "idade" => 19, "nota" => 8.5],
["nome" => "Bruno", "idade" => 34, "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "nota" => 8.5],
];
usort($alunos, fn($a, $b) => $b["nota"] <=> $a["nota"]);
echo "Por nota (maior primeiro):\n";
foreach ($alunos as $al) {
echo " {$al['nome']}: {$al['nota']}\n";
}
usort($alunos, fn($a, $b) => $a["nome"] <=> $b["nome"]);
echo "Por nome:\n";
foreach ($alunos as $al) {
echo " {$al['nome']}: {$al['nota']}\n";
}
usort($alunos, fn($a, $b) => [$b["nota"], $a["nome"]] <=> [$a["nota"], $b["nome"]]);
echo "Por nota e, no empate, por nome:\n";
foreach ($alunos as $al) {
echo " {$al['nome']}: {$al['nota']}\n";
}
- usort($alunos, …)
- Ordena
$alunosno lugar, usando a sua regra. Como osort, renumera as chaves (0, 1, 2…). - fn($a, $b) => …
- Uma arrow function: uma função curta, escrita ali mesmo, que recebe dois alunos e devolve o resultado da expressão depois do
=>. Ela é explicada em Funções Anônimas e Arrow Functions. - $b["nota"] <=> $a["nota"]
- Compara as notas com
$bà esquerda: maior nota primeiro. - [$b["nota"], $a["nome"]] <=> [$a["nota"], $b["nome"]]
- Compara dois arrays, item por item: primeiro as notas (decrescente,
$bà esquerda); se empatarem, os nomes (crescente,$aà esquerda).
$ php o-usort.php Por nota (maior primeiro): Ana: 9.5 Elisa: 8.5 « empate com a Carla: ficou a ordem em que estavam Carla: 8.5 Bruno: 7 Diego: 6 Por nome: Ana: 9.5 Bruno: 7 Carla: 8.5 Diego: 6 Elisa: 8.5 Por nota e, no empate, por nome: Ana: 9.5 Carla: 8.5 « agora o empate é decidido pelo nome Elisa: 8.5 Bruno: 7 Diego: 6
Na primeira ordenação, Elisa e Carla empataram com 8.5, e a Elisa ficou na frente porque já estava na frente no array original. Desde o PHP 8.0, a ordenação é estável: itens "iguais" mantêm a ordem que tinham. Para não depender disso, use o critério de desempate, como no terceiro exemplo.
Existem também uasort (como o usort, mas mantém as chaves) e uksort (sua regra, aplicada às chaves).
Armadilhas: maiúsculas, números como texto e acentos
Maiúsculas vêm antes
Para o computador, cada letra é um número (o código dela na tabela de caracteres), e todas as maiúsculas vêm antes de todas as minúsculas. Por isso:
<?php
$frutas = ["banana", "Abacate", "cereja", "Damasco", "amora"];
sort($frutas);
echo implode(", ", $frutas), "\n";
sort($frutas, SORT_STRING | SORT_FLAG_CASE);
echo implode(", ", $frutas), "\n";
$ php o-maiusc.php Abacate, Damasco, amora, banana, cereja « primeiro as maiúsculas, depois as minúsculas Abacate, amora, banana, cereja, Damasco « ignorando maiúsculas
O segundo argumento do sort é uma "opção" (flag). SORT_STRING | SORT_FLAG_CASE quer dizer "compare como texto, sem diferenciar maiúsculas". As mesmas opções valem para rsort, asort, ksort etc.
Números guardados como texto
Dados de formulários e arquivos chegam como texto ("10", não 10). O sort percebe que são números e compara pelo valor, o que normalmente é o que você quer. Já nomes de arquivo com números no meio ficam em ordem "de computador":
<?php
$a = ["10", "9", "2", "1"];
sort($a);
echo implode(" ", $a), "\n";
sort($a, SORT_STRING);
echo implode(" ", $a), "\n";
$fotos = ["foto10.jpg", "foto2.jpg", "foto1.jpg", "Foto3.jpg"];
sort($fotos);
echo implode(" ", $fotos), "\n";
sort($fotos, SORT_NATURAL | SORT_FLAG_CASE);
echo implode(" ", $fotos), "\n";
$ php o-numstr.php
1 2 9 10 « textos numéricos: comparados como números
1 10 2 9 « SORT_STRING: letra por letra ("10" começa com "1")
Foto3.jpg foto1.jpg foto10.jpg foto2.jpg « texto: F maiúsculo primeiro, e foto10 antes de foto2
foto1.jpg foto2.jpg Foto3.jpg foto10.jpg « ordem "natural", como uma pessoa ordenaria
| Opção | Compara |
|---|---|
(nenhuma) = SORT_REGULAR | Números como números, textos como texto; textos numéricos como números |
SORT_NUMERIC | Tudo como número |
SORT_STRING | Tudo como texto, caractere por caractere |
SORT_NATURAL | Texto, mas com os números do meio comparados pelo valor (foto2 antes de foto10) |
| SORT_FLAG_CASE | Somado a SORT_STRING ou SORT_NATURAL: ignora maiúsculas |
Acentos: a Ágata vai para o fim
Este é o problema que mais aparece em sistemas em português:
<?php
$nomes = ["Bruno", "Ágata", "Carla", "Ana", "Élcio", "Eduardo"];
sort($nomes);
echo implode(", ", $nomes), "\n";
$ php o-acento.php Ana, Bruno, Carla, Eduardo, Ágata, Élcio
A Ágata deveria vir logo depois (ou antes) da Ana, e o Élcio junto do Eduardo. Mas as funções de ordenar comparam bytes: o Á, em UTF-8, é formado por bytes que vêm depois de todas as letras sem acento, então todo nome com letra acentuada no começo vai para o fim. Nenhuma opção do sort resolve isso.
A solução completa é a classe Collator, da extensão intl (de "internacionalização"), que conhece as regras de ordenação de cada idioma:
<?php
$nomes = ["Bruno", "Ágata", "Carla", "Ana", "Élcio", "Eduardo"];
$collator = new Collator("pt_BR");
$collator->sort($nomes);
echo implode(", ", $nomes), "\n";
Mas a intl nem sempre está instalada. No laboratório deste guia, ela não está (e o editor do guia também não a tem):
$ php -m | grep intl $ echo $? 1 « o grep não achou nada: sem a extensão intl
Sem ela, o código acima para logo na linha 3:
$ php collator.php
Fatal error: Uncaught Error: Class "Collator" not found in /home/ana/site/collator.php:3
Stack trace:
#0 {main}
thrown in /home/ana/site/collator.php on line 3
Num servidor Debian ou Ubuntu, ela se instala com o pacote php-intl (veja Instalar no Linux); com ela instalada, o Collator põe a Ágata no começo e o Élcio depois do Eduardo, como num dicionário.
A solução sem extensão: ordenar por uma "versão sem acentos" de cada nome. A função strtr troca letras usando uma tabela (um array associativo), e mb_strtolower passa tudo para minúsculas respeitando os acentos (aula Acentos e mbstring):
<?php
$nomes = ["Bruno", "Ágata", "Carla", "Ana", "Élcio", "Eduardo"];
$mapa = ["á" => "a", "à" => "a", "â" => "a", "ã" => "a", "é" => "e", "ê" => "e",
"í" => "i", "ó" => "o", "ô" => "o", "õ" => "o", "ú" => "u", "ç" => "c"];
$chave = fn($nome) => strtr(mb_strtolower($nome), $mapa);
usort($nomes, fn($a, $b) => $chave($a) <=> $chave($b));
echo implode(", ", $nomes), "\n";
echo $chave("Ágata"), "\n";
$ php o-acento2.php Ágata, Ana, Bruno, Carla, Eduardo, Élcio agata « a "chave de ordenação" da Ágata
Os nomes continuam com acento na saída: só a comparação usa a versão simplificada. É uma solução boa para nomes em português; para outros idiomas, ou regras mais finas, prefira o Collator.
Quando dá errado
foreach() argument must be of type array|object, true given
$ php o-retorno.php
bool(true)
Array
(
[0] => Ana
[1] => Carla
[2] => Diego
)
Warning: foreach() argument must be of type array|object, true given in /home/ana/site/o-retorno.php on line 6
O que aconteceu: o código guardou o retorno de sort ($ordenados = sort($nomes);) e depois tentou percorrê-lo. O retorno é true, não o array.
Como resolver: chame sort($nomes); sozinho e use $nomes depois. Se precisar manter o original, copie antes: $ordenados = $nomes; sort($ordenados);.
Deprecated: usort(): Returning bool from comparison function is deprecated
<?php
$notas = [8.5, 6.0, 9.5];
usort($notas, fn($a, $b) => $a > $b);
echo implode(" ", $notas), "\n";
$ php o-bool.php Deprecated: usort(): Returning bool from comparison function is deprecated, return an integer less than, equal to, or greater than zero in /home/ana/site/o-bool.php on line 3 6 8.5 9.5
O que aconteceu: a função de comparação devolveu true/false (resultado de $a > $b), mas o usort espera um número negativo, zero ou positivo. Ainda funciona (o PHP dá um jeito), mas está "desaprovado" e pode virar erro.
Como resolver: use a nave espacial: fn($a, $b) => $a <=> $b.
sort(): Argument #1 ($array) must be of type array, string given
<?php $nomes = "Diego, Ana, Carla"; sort($nomes);
$ php o-string.php
Fatal error: Uncaught TypeError: sort(): Argument #1 ($array) must be of type array, string given in /home/ana/site/o-string.php:3
Stack trace:
#0 /home/ana/site/o-string.php(3): sort('Diego, Ana, Car...')
#1 {main}
thrown in /home/ana/site/o-string.php on line 3
O que aconteceu: $nomes é um texto com vírgulas, não um array. O Stack trace mostra até o valor que foi passado (cortado em ...).
Como resolver: transforme o texto em array antes: $nomes = explode(", ", $nomes); (aula Funções de String).
O que aconteceu: foi usado sort ou rsort num array associativo; as chaves foram trocadas por 0, 1, 2… (veja a primeira parte de o-assoc.php). O mesmo vale para usort.
Como resolver: asort/arsort para ordenar pelo valor, ksort/krsort pela chave, uasort com uma regra sua.
Referência rápida
| Função | Ordena por | Ordem | Mantém as chaves? |
|---|---|---|---|
sort($a) | valor | crescente | não (renumera) |
rsort($a) | valor | decrescente | não |
asort($a) | valor | crescente | sim |
arsort($a) | valor | decrescente | sim |
ksort($a) | chave | crescente | sim |
krsort($a) | chave | decrescente | sim |
usort($a, fn) | sua regra (valores) | a da regra | não |
uasort($a, fn) | sua regra (valores) | a da regra | sim |
uksort($a, fn) | sua regra (chaves) | a da regra | sim |
Todas mudam o array e devolvem true. Opções: SORT_STRING, SORT_NUMERIC, SORT_NATURAL, | SORT_FLAG_CASE. Regra do usort: $a <=> $b crescente, $b <=> $a decrescente.
Missão final
Missão: a Ana quer a lista de alunos do mais novo para o mais velho. Use usort com o operador <=> comparando a "idade", para a saída ser Elisa, Carla, Ana, Bruno, Diego.
<?php
$alunos = [
["nome" => "Diego", "idade" => 41, "nota" => 6.0],
["nome" => "Ana", "idade" => 29, "nota" => 9.5],
["nome" => "Elisa", "idade" => 19, "nota" => 8.5],
["nome" => "Bruno", "idade" => 34, "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "nota" => 8.5],
];
// ordene aqui
echo implode(", ", array_column($alunos, "nome")), "\n";
<?php
$alunos = [
["nome" => "Diego", "idade" => 41, "nota" => 6.0],
["nome" => "Ana", "idade" => 29, "nota" => 9.5],
["nome" => "Elisa", "idade" => 19, "nota" => 8.5],
["nome" => "Bruno", "idade" => 34, "nota" => 7.0],
["nome" => "Carla", "idade" => 22, "nota" => 8.5],
];
usort($alunos, fn($a, $b) => $a["idade"] <=> $b["idade"]);
echo implode(", ", array_column($alunos, "nome")), "\n";
$ php idades.php Elisa, Carla, Ana, Bruno, Diego
Desafio extra: mude para o mais velho primeiro, trocando só a ordem de $a e $b.
Exercícios
1. Do maior para o menor
Complete com a função que ordena uma lista em ordem decrescente:
2. Preços do mais barato ao mais caro, sem perder os nomes
3. A nave espacial
Complete para ordenar os produtos pelo preço, do mais caro para o mais barato:
4. Depois de $r = sort($lista);, o que há em $r?
5. Quanto vale 5 <=> 3?
6. Por que sort(["Bruno", "Ágata", "Ana"]) coloca "Ágata" por último?
Resumo
sort/rsortordenam listas;asort/arsortordenam pelo valor mantendo as chaves;ksort/krsortordenam pela chave.- Todas ordenam no lugar e devolvem
true. Para manter o original, copie antes. usort($a, fn($a, $b) => $a[…] <=> $b[…])ordena com a sua regra; troque$ae$bpara inverter. Para desempatar, compare arrays:[x1, y1] <=> [x2, y2].- Desde o PHP 8.0 a ordenação é estável: empates mantêm a ordem anterior.
- Maiúsculas vêm antes (
SORT_FLAG_CASEresolve);SORT_NATURALpõefoto2antes defoto10. - Acentos vão para o fim; use
Collator(extensão intl) ou ordene por uma versão sem acentos.