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.

Você vai aprender
  • Ordenar listas com sort (crescente) e rsort (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 usort e 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 $notas do 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:

LetraSignificaExemplo
aassociative: mantém a associação chave => valor e ordena pelo valorasort, arsort
kkey: ordena pela chave (e também mantém a associação)ksort, krsort
rreverse: ordem decrescentersort, arsort, krsort
uuser: você escreve a regra de comparaçãousort, uasort, uksort
Regra prática

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: $a vem antes;
  • zero: tanto faz (são "iguais" para a ordem);
  • positivo: $b vem 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 $alunos no lugar, usando a sua regra. Como o sort, 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çãoCompara
(nenhuma) = SORT_REGULARNúmeros como números, textos como texto; textos numéricos como números
SORT_NUMERICTudo como número
SORT_STRINGTudo como texto, caractere por caractere
SORT_NATURALTexto, mas com os números do meio comparados pelo valor (foto2 antes de foto10)
| SORT_FLAG_CASESomado 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).

Os nomes dos produtos sumiram depois de ordenar

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çãoOrdena porOrdemMantém as chaves?
sort($a)valorcrescentenão (renumera)
rsort($a)valordecrescentenão
asort($a)valorcrescentesim
arsort($a)valordecrescentesim
ksort($a)chavecrescentesim
krsort($a)chavedecrescentesim
usort($a, fn)sua regra (valores)a da regranão
uasort($a, fn)sua regra (valores)a da regrasim
uksort($a, fn)sua regra (chaves)a da regrasim

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:

($pontos);

2. Preços do mais barato ao mais caro, sem perder os nomes

($precos);

3. A nave espacial

Complete para ordenar os produtos pelo preço, do mais caro para o mais barato:

usort($produtos, fn($a, $b) => );

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/rsort ordenam listas; asort/arsort ordenam pelo valor mantendo as chaves; ksort/krsort ordenam 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 $a e $b para 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_CASE resolve); SORT_NATURAL põe foto2 antes de foto10.
  • Acentos vão para o fim; use Collator (extensão intl) ou ordene por uma versão sem acentos.