Como classificar uma matriz de matrizes associativas pelo valor de uma determinada chave no PHP?


446

Dada esta matriz:

$inventory = array(

   array("type"=>"fruit", "price"=>3.50),
   array("type"=>"milk", "price"=>2.90),
   array("type"=>"pork", "price"=>5.43),

);

Gostaria de classificar $inventoryos elementos por preço para obter:

$inventory = array(

   array("type"=>"pork", "price"=>5.43),
   array("type"=>"fruit", "price"=>3.50),
   array("type"=>"milk", "price"=>2.90),

);

Como posso fazer isso?


Respostas:


605

Você está certo, a função que você está procurando é array_multisort().

Aqui está um exemplo retirado diretamente do manual e adaptado ao seu caso:

$price = array();
foreach ($inventory as $key => $row)
{
    $price[$key] = $row['price'];
}
array_multisort($price, SORT_DESC, $inventory);

A partir do PHP 5.5.0, você pode usar array_column () em vez daquele foreach

$price = array_column($inventory, 'price');

array_multisort($price, SORT_DESC, $inventory);

5
Embora isso seja definitivamente mais caro que as alternativas.
Matt

5
Mais caro? Isso é estranho, na minha máquina (rodando o PHP 5.3.1-dev) array_multisort () é uma pequena porcentagem mais rápido em pequenas matrizes e até 100 vezes mais rápido em grandes matrizes (100 + elementos)
Josh Davis

3
Não deve exigir nenhuma alteração para trabalhar com teclas numéricas. Se você estiver enfrentando um bug ou comportamento estranho relacionado a teclas numéricas, publique-o como uma nova pergunta.
Josh Davis

4
array_multisort tem um grande problema: ele não mantém a chave original.
precisa saber é o seguinte

1
@machineaddict mantém as chaves associativas.
Matej Svajger 23/04

317

PHP 7+

A partir do PHP 7, isso pode ser feito de forma concisa, usando usortuma função anônima que usa o operador de nave espacial para comparar elementos.

Você pode fazer uma classificação ascendente como esta:

usort($inventory, function ($item1, $item2) {
    return $item1['price'] <=> $item2['price'];
});

Ou um tipo descendente como este:

usort($inventory, function ($item1, $item2) {
    return $item2['price'] <=> $item1['price'];
});

Para entender como isso funciona, observe que usortutiliza uma função de comparação fornecida pelo usuário que deve se comportar da seguinte maneira (nos documentos):

A função de comparação deve retornar um número inteiro menor que, igual a ou maior que zero se o primeiro argumento for considerado respectivamente menor que, igual a ou maior que o segundo.

E observe também que <=>, o operador da nave espacial,

retorna 0 se ambos os operandos forem iguais, 1 se a esquerda for maior e -1 se a direita for maior

o que é exatamente o que usortprecisa. De fato, quase toda a justificativa dada para adicionar <=>ao idioma em https://wiki.php.net/rfc/combined-comparison-operator é que

faz escrever callbacks ordenação para uso com usort()mais fácil


PHP 5.3 ou superior

O PHP 5.3 introduziu funções anônimas, mas ainda não possui o operador de nave espacial. Ainda podemos usar usortpara classificar nossa matriz, mas é um pouco mais detalhado e difícil de entender:

usort($inventory, function ($item1, $item2) {
    if ($item1['price'] == $item2['price']) return 0;
    return $item1['price'] < $item2['price'] ? -1 : 1;
});

Observe que, embora seja bastante comum que comparadores que lidam com valores inteiros retornem apenas a diferença dos valores, por exemplo $item2['price'] - $item1['price'], não podemos fazer isso com segurança nesse caso. Isso ocorre porque os preços são números de ponto flutuante no exemplo do autor da pergunta, mas a função de comparação para a qual passamos usortprecisa retornar números inteiros para usortque funcionem corretamente:

Retornar valores não inteiros da função de comparação, como float, resultará em uma conversão interna para inteiro do valor de retorno do retorno de chamada. Portanto, valores como 0,99 e 0,1 serão convertidos em um valor inteiro 0, que comparará esses valores como iguais.

Esta é uma armadilha importante a ser lembrada ao usar usortno PHP 5.x! Minha versão original desta resposta cometeu esse erro e, no entanto, acumulei dez upvotes em milhares de visualizações, aparentemente sem que ninguém percebesse o bug sério. A facilidade com que idiotas como eu podem estragar as funções do comparador é precisamente o motivo pelo qual o operador de nave espacial mais fácil de usar foi adicionado à linguagem no PHP 7.


8
Desculpe, mas essa abordagem exclui as chaves de seqüência de caracteres de matrizes associativas. A função "uasort" deve ser usada.
Matteo-SoftNet 26/03

8
@DotMat Interessante - eu não sabia uasort. Depois de analisar os documentos, no entanto, esta resposta ainda está correta neste caso . No exemplo do OP, a matriz a ser classificada possui índices numéricos seqüenciais em vez de índices de string, portanto, usorté mais apropriado. O uso uasortem uma matriz indexada seqüencialmente resultará em uma matriz classificada que não é ordenada por seus índices numéricos, de modo que o primeiro elemento visto em um foreachloop não seja $your_array[0], o que é improvável que seja um comportamento desejável.
Mark Amery

100

Enquanto outros sugeriram corretamente o uso de array_multisort(), por algum motivo, nenhuma resposta parece reconhecer a existência de array_column(), o que pode simplificar bastante a solução. Então, minha sugestão seria:

array_multisort(array_column($inventory, 'price'), SORT_DESC, $inventory);

1
Por alguma razão, não consegui fazê-lo funcionar com strings com letras maiúsculas / minúsculas. Mesmo usando o SORT_FLAG_CASE. O seguinte funcionou para comparação de cadeias de caracteres para mim: array_multisort (array_map (strtolower, array_column ($ ipr_projects, 'Name')), SORT_ASC, $ ipr_projects);
Pabamato

8
Esta é a resposta mais elegante. Deve ser avaliado muito mais alto!
Armin Hierstetter 01/08/19

3
o menor e o mais fácil, aceitei um na minha opinião
StudioX 15/12/18

1
Coisas boas aqui. Funcionou perfeitamente para mim!
Funk Doc

Funcionou como um encanto, ty
Leif_Lundberg

42

Como seus elementos de matriz são matrizes com chaves de seqüência de caracteres, sua melhor aposta é definir uma função de comparação personalizada. É bem rápido e fácil de fazer. Tente o seguinte:

function invenDescSort($item1,$item2)
{
    if ($item1['price'] == $item2['price']) return 0;
    return ($item1['price'] < $item2['price']) ? 1 : -1;
}
usort($inventory,'invenDescSort');
print_r($inventory);

Produz o seguinte:

Array
(
    [0] => Array
        (
            [type] => pork
            [price] => 5.43
        )

    [1] => Array
        (
            [type] => fruit
            [price] => 3.5
        )

    [2] => Array
        (
            [type] => milk
            [price] => 2.9
        )

)

4
Combinando com alguns dos outros comentários aqui (uasort e funções em linha anônimos), você receber esse one-liner:uasort( $inventory, function ($a, $b) { if ( $a==$b ) return 0; else return ($a > $b) ? -1 : 1; });
Alan Porter

O @AlanPorter usortparece mais apropriado do que uasortpara classificar uma matriz com chaves numéricas seqüenciais. Terminar com uma matriz em que o primeiro elemento está no índice 1e o segundo elemento está no índice 0é um comportamento estranho e uma armadilha certa para pessoas que não estão familiarizadas com os detalhes das matrizes do PHP; usortfornece a saída que você esperaria intuitivamente.
Marque Amery

25

Eu terminei com isso:

function sort_array_of_array(&$array, $subfield)
{
    $sortarray = array();
    foreach ($array as $key => $row)
    {
        $sortarray[$key] = $row[$subfield];
    }

    array_multisort($sortarray, SORT_ASC, $array);
}

Basta chamar a função, passando a matriz e o nome do campo da matriz de segundo nível. Gostar:

sort_array_of_array($inventory, 'price');

1
Ha! Eu apenas faço exatamente a mesma coisa e ia postar, mas vi a sua ... votada.
Rob Evans

1
Voto negativo, porque esta é exatamente a mesma solução que Josh Davis postou anos antes.
Mark Amery

Discordo ... Eu não disse que é uma solução diferente, apenas disse que terminei com essa solução e ofereço uma função de trabalho completa.
Danielzt

1
@ MarkAmery Prefiro respostas contidas em funções. Ele encoraja os usuários de cópias a usar funções e, esperamos, escrever menos código de espaguete.
Ganso

19

Você pode usar usortcom função anônima, por exemplo

usort($inventory, function ($a, $b) { return strnatcmp($a['price'], $b['price']); });

Versões PHP 5> = 5.5.0, PHP 7 para aqueles que como eu que realmente queria que este trabalho para eles ..
Matt P

1
É notável que strnatcmp, destinado a comparar seqüências de caracteres, parece funcionar bem aqui. Aparentemente, a "ordem natural" implementada inclui a classificação de seqüências numéricas numericamente em vez de lexicamente.
Mark Amery

10
$inventory = 
    array(array("type"=>"fruit", "price"=>3.50),
          array("type"=>"milk", "price"=>2.90),
          array("type"=>"pork", "price"=>5.43),
          );

function pricesort($a, $b) {
  $a = $a['price'];
  $b = $b['price'];
  if ($a == $b)
    return 0;
  return ($a > $b) ? -1 : 1;
}

usort($inventory, "pricesort");
// uksort($inventory, "pricesort");

print("first: ".$inventory[0]['type']."\n\n");
// for usort(): prints milk (item with lowest price)
// for uksort(): prints fruit (item with key 0 in the original $inventory)

// foreach prints the same for usort and uksort.
foreach($inventory as $i){
  print($i['type'].": ".$i['price']."\n");
}

saídas:

first: pork

pork: 5.43
fruit: 3.5
milk: 2.9

6

De Classificar uma matriz de matrizes associativas pelo valor da chave fornecida no php :

O uasort ( http://php.net/uasort ) permite que você classifique uma matriz por sua própria função definida. No seu caso, isso é simples:

$array = array(
  array('price'=>'1000.50','product'=>'test1'),
  array('price'=>'8800.50','product'=>'test2'),
  array('price'=>'200.0','product'=>'test3')
);

function cmp($a, $b) {
  return $a['price'] > $b['price'];
}

uasort($array, "cmp");

1
Essa resposta apareceu na fila de revisão de baixa qualidade, presumivelmente porque você não fornece nenhuma explicação do código. Se esse código responder à pergunta, considere adicionar um texto explicando o código em sua resposta. Dessa forma, é muito mais provável que você receba mais votos - e ajude o interlocutor a aprender algo novo.
lmo

1
hmpf. Esta é a melhor resposta.
commonpike

1
-1; a cmpfunção aqui está errada. Ele deve retornar "um número inteiro menor que, igual a ou maior que zero se o primeiro argumento for considerado respectivamente menor que, igual a ou maior que o segundo", mas retorna trueou false. Parece, extraordinariamente, funcionar - talvez porque a implementação atual de usorte amigos trate os casos "menor que" e "igual a" de forma idêntica - mas não conte com isso continuando trabalhando em futuras versões do PHP. Se eles tentarem fazer com que as classificações sejam estáveis ​​(ou seja, não se movam desnecessariamente em elementos iguais), isso será interrompido.
Mark Amery

Além disso, usortseria mais apropriado do que uasortaqui, pois uasortpreserva a associação entre chaves e valores, o que é confuso e inesperado ao ser utilizado com uma matriz numérica sequencial. Por exemplo, os índices $arrayacima após a chamada uasortsão 2, 0 e 1, nessa ordem. A menos que você, por algum motivo, queira isso, provavelmente ficará mais confortável com o uso usort, o que reindexa a matriz e também a reordena.
Mark Amery

5

Foi testado em 100.000 registros: Tempo em segundos (calculado pela função microtime). Somente para valores exclusivos na classificação de posições-chave.

Solução de função de @Josh Davis: tempo gasto: 1.5768740177155

Solução da mina: Tempo gasto: 0.094044923782349

Solução:

function SortByKeyValue($data, $sortKey, $sort_flags=SORT_ASC)
{
    if (empty($data) or empty($sortKey)) return $data;

    $ordered = array();
    foreach ($data as $key => $value)
        $ordered[$value[$sortKey]] = $value;

    ksort($ordered, $sort_flags);

    return array_values($ordered); *// array_values() added for identical result with multisort*
}

7
O requisito para chaves de classificação exclusivas é uma espécie de quebra de negócio, no entanto. Se você possui valores de classificação exclusivos que podem ser chaves, isso levanta a questão: por que não simplesmente construir a matriz com essas chaves para começar? No cenário do OP, é difícil imaginar que dois itens com o mesmo preço seriam impossíveis . Isso significa que o uso dessa solução faria com que os itens da matriz desaparecessem misteriosa e silenciosamente do conjunto de resultados classificados.
Chris Baker

@ Chris Baker, você está certo. Isso funciona apenas para valores exclusivos. Mas essa solução funciona muito rápido, então a velocidade foi o motivo de fazer e usá-lo. No momento, pode ser que não seja real, é necessário testá-lo com o PHP 7.1.x.
Nefelim

4

Eu uso uasortassim

<?php
$users = [
    [
        'username' => 'joe',
        'age' => 11
    ],
    [
        'username' => 'rakoto',
        'age' => 21
    ],
    [
        'username' => 'rabe',
        'age' => 17
    ],
    [
        'username' => 'fy',
        'age' => 19
    ],    
];


uasort($users, function ($item, $compare) {
    return $item['username'] >= $compare['username']; 
});

var_dump($users);

3

Esta função é reutilizável:

function usortarr(&$array, $key, $callback = 'strnatcasecmp') {
    uasort($array, function($a, $b) use($key, $callback) {
        return call_user_func($callback, $a[$key], $b[$key]);
    });
}

Por padrão, ele funciona bem em valores de seqüência de caracteres, mas você precisará subverter o retorno de chamada para uma função de comparação de números se todos os seus valores forem números.


Você chama isso, usortarrmas depois chama em uasortvez de usort; talvez um pouco confuso. O último é - no caso de uma matriz seqüencial com índices numéricos, como o exibido na pergunta - provavelmente o que você realmente deseja.
Mark Amery

2

Você pode tentar definir sua própria função de comparação e usar usort .


Sim. Farei isso se não encontrar uma solução. Tenho certeza de que existem alguns parâmetros estranhos que você pode adicionar a um dos tipos para fazer isso. Obrigado por seus pensamentos!
Matt

2

Aqui está um método que eu encontrei há muito tempo e limpei um pouco. Isso funciona muito bem e pode ser alterado rapidamente para aceitar objetos também.

/**
 * A method for sorting arrays by a certain key:value.
 * SortByKey is the key you wish to sort by
 * Direction can be ASC or DESC.
 *
 * @param $array
 * @param $sortByKey
 * @param $sortDirection
 * @return array
 */
private function sortArray($array, $sortByKey, $sortDirection) {

    $sortArray = array();
    $tempArray = array();

    foreach ( $array as $key => $value ) {
        $tempArray[] = strtolower( $value[ $sortByKey ] );
    }

    if($sortDirection=='ASC'){ asort($tempArray ); }
        else{ arsort($tempArray ); }

    foreach ( $tempArray as $key => $temp ){
        $sortArray[] = $array[ $key ];
    }

    return $sortArray;

}

Para alterar o método para classificar objetos, basta alterar a seguinte linha:

$tempArray[] = strtolower( $value[ $sortByKey ] ); para $tempArray[] = strtolower( $value->$sortByKey );

Para executar o método, basta fazer

sortArray($inventory,'price','ASC');


Essa abordagem funciona, mas é bem menos concisa do que a resposta de Josh Davis (com array_multisort) ou a minha (com usort) e parece não oferecer vantagens sobre elas em troca.
Mark Amery

1
//Just in one line custom function
function cmp($a, $b)
{
return (float) $a['price'] < (float)$b['price'];
}
@uasort($inventory, "cmp");
print_r($inventory);

//result

Array
(
[2] => Array
    (
        [type] => pork
        [price] => 5.43
    )

[0] => Array
    (
        [type] => fruit
        [price] => 3.5
    )

[1] => Array
    (
        [type] => milk
        [price] => 2.9
    )

)

1

tente isto:

$prices = array_column($inventory, 'price');
array_multisort($prices, SORT_DESC, $inventory);
print_r($inventory);

Olá e bem-vindo ao stackoverflow, e obrigado por responder. Embora esse código possa responder à pergunta, considere adicionar algumas explicações sobre qual foi o problema que você resolveu e como o resolveu? Isso ajudará os futuros leitores a entender melhor sua resposta e aprender com ela.
Plutian

0

Função dinâmica completa Eu pulei aqui para a classificação associativa de matrizes e encontrei essa incrível função em http://php.net/manual/en/function.sort.php . Essa função é muito dinâmica, classificada em ordem crescente e decrescente com a chave especificada.

Função simples para classificar uma matriz por uma chave específica. Mantém a associação do índice

<?php

function array_sort($array, $on, $order=SORT_ASC)
{
    $new_array = array();
    $sortable_array = array();

    if (count($array) > 0) {
        foreach ($array as $k => $v) {
            if (is_array($v)) {
                foreach ($v as $k2 => $v2) {
                    if ($k2 == $on) {
                        $sortable_array[$k] = $v2;
                    }
                }
            } else {
                $sortable_array[$k] = $v;
            }
        }

        switch ($order) {
            case SORT_ASC:
                asort($sortable_array);
            break;
            case SORT_DESC:
                arsort($sortable_array);
            break;
        }

        foreach ($sortable_array as $k => $v) {
            $new_array[$k] = $array[$k];
        }
    }

    return $new_array;
}

$people = array(
    12345 => array(
        'id' => 12345,
        'first_name' => 'Joe',
        'surname' => 'Bloggs',
        'age' => 23,
        'sex' => 'm'
    ),
    12346 => array(
        'id' => 12346,
        'first_name' => 'Adam',
        'surname' => 'Smith',
        'age' => 18,
        'sex' => 'm'
    ),
    12347 => array(
        'id' => 12347,
        'first_name' => 'Amy',
        'surname' => 'Jones',
        'age' => 21,
        'sex' => 'f'
    )
);

print_r(array_sort($people, 'age', SORT_DESC)); // Sort by oldest first
print_r(array_sort($people, 'surname', SORT_ASC)); // Sort by surname

-1
$arr1 = array(

    array('id'=>1,'name'=>'aA','cat'=>'cc'),
    array('id'=>2,'name'=>'aa','cat'=>'dd'),
    array('id'=>3,'name'=>'bb','cat'=>'cc'),
    array('id'=>4,'name'=>'bb','cat'=>'dd')
);

$result1 = array_msort($arr1, array('name'=>SORT_DESC);

$result2 = array_msort($arr1, array('cat'=>SORT_ASC);

$result3 = array_msort($arr1, array('name'=>SORT_DESC, 'cat'=>SORT_ASC));


function array_msort($array, $cols)
{
    $colarr = array();
    foreach ($cols as $col => $order) {
    $colarr[$col] = array();
    foreach ($array as $k => $row) { $colarr[$col]['_'.$k] = strtolower($row[$col]); }
}

$eval = 'array_multisort(';

foreach ($cols as $col => $order) {
    $eval .= '$colarr[\''.$col.'\'],'.$order.',';
}

$eval = substr($eval,0,-1).');';
eval($eval);
$ret = array();
foreach ($colarr as $col => $arr) {
    foreach ($arr as $k => $v) {
        $k = substr($k,1);
        if (!isset($ret[$k])) $ret[$k] = $array[$k];
        $ret[$k][$col] = $array[$k][$col];
    }
}
return $ret;


} 

Embora esse trecho de código possa resolver a questão, incluir uma explicação realmente ajuda a melhorar a qualidade da sua postagem. Lembre-se de que você está respondendo à pergunta dos leitores no futuro e essas pessoas podem não saber os motivos da sua sugestão de código. Tente também não sobrecarregar seu código com comentários explicativos, pois isso reduz a legibilidade do código e das explicações!
Adeus StackExchange

-5

tente isto:

asort($array_to_sort, SORT_NUMERIC);

para referência, veja o seguinte: http://php.net/manual/en/function.asort.php

veja vários sinalizadores de classificação aqui: http://www.php.net/manual/en/function.sort.php


isto não vai funcionar para arrays multidimensionais, mas apenas me ajudou a sair para um outro problema, graças :)
schellmax

4
Isso não pode ser usado para classificar uma lista de dicionários por uma chave de dicionário específica e, portanto, não responde à pergunta feita.
Mark Amery
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.