Ordenação de arrays é um tema muito simples, mas que também gera muitas dúvidas na comunidade de PHP. Neste artigo, veremos desde uma ordenação simples de arrays usando a função sort até as formas mais sofisticadas, envolvendo matrizes ou regras de comparação específicas.
Ordenação simples de Arrays (sort e rsort)
Quando temos um array simples de valores numéricos (int ou float) ou de strings, onde o índice do array não é importante, utilizamos as funções sort ou rsort para ordená-lo. A função sort ordena os elementos do array em ordem crescente, enquanto a função rsort ordena os elementos do array em ordem decrescente (você pode lembrar de ordem "reversa"). Ambas as funções refazem os índices do array, convertendo-os para números e começando no índice zero. O array é passado para a função por referência e seus valores são alterados dentro da função.
Como sort e rsort são funções nativas do PHP, realizam a ordenação de forma bastante otimizada. Dificilmente você conseguirá implementar um algoritmo de ordenação na linguagem PHP que seja mais rápida e eficiente que o sort e rsort, salvo em casos especiais em que os elementos do array possuem alguma lógica previamente conhecida.
Veja um exemplo simples de ordenação de um array de números, em ordem crescente, usando a função sort:
$itens = array(4, 8, 1, 4, 2); sort($itens); var_dump($itens); /* Imprime: array(5) { [0]=> int(1) [1]=> int(2) [2]=> int(4) [3]=> int(4) [4]=> int(8) } */
As duas funções (sort e rsort) podem receber o segundo parâmetro, que representa o tipo de algoritmo que será usado para comparações de elementos, que pode ser uma das constantes:
- SORT_REGULAR: comparação de acordo com o tipo (int ou string). Este é o tipo de comparação padrão.
- SORT_NUMERIC: comparação de números (converte para número para comparar).
- SORT_STRING: comparação de strings (converte para string para comparar).
- SORT_LOCALE_STRING: comparação de strings considerando a localidade definida via setlocale.
- SORT_NATURAL: comparação usando ordem natural (veremos detalhes mais a frente).
- SORT_FLAG_CASE: pode ser passada em conjunto com os tipos de ordenação de strings, via máscara de bits, para indicar que a comparação será case insensitive.
Ordenação de Arrays mantendo o índice (asort e arsort)
Existem situações em que o índice do array é importante, pois está associado ao seu respectivo valor. Por exemplo, um array de nomes de usuários que está indexado pelo ID do usuário no banco de dados. Neste caso, se quisermos ordenar o array de nomes em ordem crescente e preservando o índice, precisaríamos utilizar a função asort (você pode se lembrar de ordenação de arrays "associativos"). Já a função arsort faz a ordenação em ordem decrescente e também mantém os índices do array intactos.
As funções asort e arsort também recebem o array por referência e o modificam internamente.
Veja um exemplo simples de ordenação de um array associativo usando a função asort:
$itens = array(42 => 'Shadow of the Colosus', 67 => 'God of War', 82 => 'Mortal Kombat'); asort($itens); var_dump($itens); /* Imprime: array(3) { [67]=> string(10) "God of War" [82]=> string(13) "Mortal Kombat" [42]=> string(21) "Shadow of the Colosus" } */
Observe que o índice de cada elemento continua o mesmo. A diferença é que se percorrermos o array usando foreach (ou similar), cada posição será retornada com base na nova ordem.
Os índices do array poderiam ser do tipo string, que as funções também preservariam os índices do array.
As duas funções (asort e arsort) também podem receber o tipo de algoritmo usado para comparações, como visto na função sort.
Ordenação baseada nos índices do Array (ksort e krsort)
Existem casos em que o array possui um índice importante e queremos ordenar os elementos com base no índice ao invés de utilizarmos o valor. Para estes casos, utilizamos ksort (lembre-se de ordenação baseada na key) para ordenar os índices em ordem crescente ou krsort para ordenar dos índices em ordem decrescente.
Veja Um exemplo de ordenação com base no índice do array:
$itens = array(18 => 'GTA', 1 => 'Fifa Soccer', 3 => 'Burnout Paradise'); ksort($itens); var_dump($itens); /* Imprime: array(3) { [1]=> string(11) "Fifa Soccer" [3]=> string(16) "Burnout Paradise" [18]=> string(3) "GTA" } */
As duas funções (ksort e krsort) também podem receber o tipo de algoritmo usado para comparações, como visto na função sort.
Ordenação de Arrays com ordem natural (natsort e natcasesort)
A função natsort é uma forma simplificada de se chamar a função sort passando o tipo de algoritmo de comparação de valores SORT_NATURAL.
As funções de ordenação mostradas anteriormente utilizam, por padrão, o tipo de comparação SORT_REGULAR, que realizam as comparações entre elementos levando-se em consideração os tipos envolvidos e os seus bytes. Por exemplo, em ASCII, a letra "a" tem valor binário 01100001 (decimal 97) enquanto a letra "b" tem valor binário 1100010 (decimal 98). Então, quando pedimos para ordená-los, os elementos que começam com "a" ficam antes dos que começam com "b". Se algum elemento tem menos bytes que outro e possui os bytes idênticos aos do início de outro elemento, então ele é colocado antes. Por exemplo a string "bom" fica antes da string "bom dia".
O problema da ordenação SORT_REGULAR aparece quando comparamos elementos envolvendo letras e números. Por exemplo, se ordenarmos a sequência de strings "img10", "img2" e "img1" com a função sort, teremos como resultado "img1", "img10" e "img2". Note que não é uma ordem ideal para humanos, pois foi levado em conta byte por byte.
Para ordenar elementos usando a ordem "natural", ou seja, a ordem que um humano realizaria, utilizamos a função natsort. A função natcasesort tem comportamento similar, a diferença é que ela é case insensitive, ou seja, não leva em consideração se as letras estão na forma maiúscula ou minúscula (é uma forma simplificada de passar o valor SORT_NATURAL | SORT_FLAG_CASE para a função sort). Já natsort considera as letras maiúsculas anteriores às letras minúsculas.
Veja um exemplo de ordenação natural:
$itens = array('img10', 'img1', 'img2'); natsort($itens); var_dump($itens); /* Imprime: array(3) { [1]=> string(4) "img1" [2]=> string(4) "img2" [0]=> string(5) "img10" } */
Note que estas funções são usadas apenas para arrays de strings (mesmo que sejam strings contendo apenas caracteres numéricos). Note também que o índice do array é refeito. Para preservá-lo, seria necessário usar asort passando o tipo de comparação SORT_NATURAL.
Para ordenar de forma natural, mas em ordem decrescente, é preciso usar a função rsort ou arsort passando-se o tipo de comparação SORT_NATURAL.
Ordenação de Arrays com critérios especiais
Até aqui, as funções mostradas funcionam bem para arrays de strings e, em alguns casos, arrays com valores numéricos. Mas e quando temos um array de objetos? Ou um array de registros (criados na forma de array associativo)? Ou se queremos ordenar com base em um critério próprio de comparação? Para estes casos, utilizamos as funções:
- usort: semelhante a sort, mas permite especificar uma função de comparação de elementos (o índice do array é sobrescrito).
- uasort: semelhante a asort, mas permite especificar uma função de comparação de elementos (o índice do array é preservado).
- uksort: semelhante a ksort, mas permite especificar uma função de comparação dos índices.
A especificação de como será feita a comparação é através de callback ou funções anônimas. Se você não sabe o que são callbacks, leia o artigo Callback em PHP. E se você não sabe o que são funções anônimas, leia a documentação sobre funções anônimas do PHP.
Veja um exemplo de como ordenar um array de objetos levando em consideração o atributo "nome" e, caso este atributo seja igual, leve em consideração o atributo "id":
/** * Funcao que compara dois objetos levando em consideracao os atributos nome e id * @param stdClass $obj1 * @param stdClass $obj2 * @return int Retorna 0 se os objetos tem os atributos iguais. * Retorna um valor negativo se $obj1 e' anterior a $obj2. * Retorna um valor positivo se $obj1 e' posterior a $obj2. */ function comparar_objetos($obj1, $obj2) { $anterior = strcmp($obj1->nome, $obj2->nome); if ($anterior != 0) { return $anterior; } $anterior = $obj1->id - $obj2->id; return $anterior; } $obj1 = new stdClass(); $obj1->id = 14; $obj1->nome = 'Rubens'; $obj2 = new stdClass(); $obj2->id = 1; $obj2->nome = 'Teste'; $obj3 = new stdClass(); $obj3->id = 13; $obj3->nome = 'Rubens'; $itens = array($obj1, $obj2, $obj3); usort($itens, 'comparar_objetos'); var_dump($itens); /* Imprime: array(3) { [0]=> object(stdClass)#2 (2) { ["id"]=> int(13) ["nome"]=> string(6) "Rubens" } [1]=> object(stdClass)#1 (2) { ["id"]=> int(14) ["nome"]=> string(6) "Rubens" } [2]=> object(stdClass)#3 (2) { ["id"]=> int(1) ["nome"]=> string(5) "Teste" } } */
A operação básica de qualquer ordenação é a comparação de dois elementos. Por isso, a função que você passa para usort (e similares) é sempre uma função que recebe dois parâmetros (os dois elementos a serem comparados) e devolve um valor inteiro. Este retorno da função deve ser:
- O valor 0 (zero), caso os elementos sejam considerados iguais em relação ao critério de comparação.
- Um valor negativo, caso o primeiro parâmetro seja anterior ao segundo parâmetro.
- Um valor positivo, caso o primeiro parâmetro seja posterior ao segundo parâmetro.
Existem algumas funções do PHP que fazem esta tarefa e que podem ser úteis conhecer:
- strcmp: compara duas strings de forma case sensitive.
- strcasecmp: compara duas strings de forma case insensitive.
- strncmp: compara os N primeiros caracteres de duas strings de forma case sensitive (útil para comparar apenas parte de textos muito grandes).
- strncasecmp: compara os N primeiros caracteres de duas strings de forma case insensitive.
- strnatcmp: compara duas strings com ordem natural de forma case sensitive.
- strnatcasecmp: compara duas strings com ordem natural de forma case insensitive.
- strcoll: compara duas strings com base na localidade definida por setlocale.
Para comparar dois números, basta fazer a subtração do primeiro pelo segundo. Afinal, se o primeiro for menos, vai devolver um número negativo; se for igual, vai devolver zero; e se for maior, vai devolver um número positivo.
Ordenação de multiplos Arrays com mesmo índice
Existem casos em que temo dois ou mais arrays indexados da mesma forma, mas queremos ordenar todos eles com base nos critérios de ordenação utilizados para ordenar o primeiro array. Neste caso, utilizamos a função array_multisort. Se você não entendeu a explicação, vamos ver um exemplo para facilitar:
// Arrays com os nomes e anos de fundacao de times paulistas $nomes = array('SCCP' => 'Corinthians', 'SPFC' => 'São Paulo', 'SEP' => 'Palmeiras'); $fundacao = array('SCCP' => 1910, 'SPFC' => 1935, 'SEP' => 1914); // Ordenando os dois arrays ao mesmo tempo, considerando o ano de fundacao em ordem crescente array_multisort($fundacao, SORT_ASC, SORT_NUMERIC, $nomes); var_dump($fundacao, $nomes); /* Imprime: array(3) { ["SCCP"]=> int(1910) ["SEP"]=> int(1914) ["SPFC"]=> int(1935) } array(3) { ["SCCP"]=> string(11) "Corinthians" ["SEP"]=> string(9) "Palmeiras" ["SPFC"]=> string(10) "São Paulo" } */
Quando chamamos array_multisort, o primeiro parâmetro é o array que será usado para comparar os elementos. Em seguida, passamos o tipo de ordenação e o tipo de comparação. Depois, passamos outros arrays. As mesmas trocas de posições ocorridas no primeiro array serão refletidas nos demais arrays.
Os tipos de ordenação são:
- SORT_ASC: ordem crescente.
- SORT_DESC: ordem descendente.
Os tipos de comparação são as mesmas aceitas pela função sort.
Ordenação randômica de Arrays (shuffle)
Apenas para caráter de curiosidade, também existe uma função para embaralhar os elementos de um array, que é a função shuffle. Cada vez que você chamar esta função com um mesmo array, ela pode devolver os elementos em uma ordem diferente e aleatória. O índice do array é desfeito, assim como ocorre em sort.
1 comentário
Parabéns pelo grande e útil conhecimento e pela humildade de compartilha-lo conosco.
Postar um comentário
Nota: fique a vontade para expressar o que achou deste artigo ou do blog.
Dica: para acompanhar as respostas, acesse com uma conta do Google e marque a opção "Notifique-me".
Atenção: o blogger não permite inclusão de tags nos comentários, por isso, use algum site externo para postar seu código com dúvidas e deixe o link aqui. Exemplo: pastebin.com