¿Cómo ordenar una matriz de matrices asociativas por valor de una clave dada en 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),

);

Me gustaría ordenar $inventorylos elementos por precio para obtener:

$inventory = array(

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

);

¿Cómo puedo hacer esto?

Mate
fuente

Respuestas:

605

Tienes razón, la función que estás buscando es array_multisort().

Aquí hay un ejemplo tomado directamente del manual y adaptado a su caso:

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

A partir de PHP 5.5.0, puede usar array_column () en lugar de ese foreach

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

array_multisort($price, SORT_DESC, $inventory);
Josh Davis
fuente
55
Aunque esto es definitivamente más caro que las alternativas.
Matt
55
¿Más caro? Eso es extraño, en mi máquina (ejecutando PHP 5.3.1-dev), array_multisort () es un poco más rápido en matrices pequeñas y hasta 100 veces más rápido en matrices grandes (más de 100 elementos)
Josh Davis,
3
No debería requerir ningún cambio para trabajar con teclas numéricas. Si te encuentras con un error o un comportamiento extraño relacionado con las teclas numéricas, publícalo como una nueva pregunta.
Josh Davis el
44
array_multisort tiene un gran problema: no mantiene la clave original.
machineaddict
1
@machineaddict mantiene las claves asociativas.
Matej Svajger
318

PHP 7+

A partir de PHP 7, esto se puede hacer de manera concisa usando usortuna función anónima que usa el operador de nave espacial para comparar elementos.

Puedes hacer un tipo ascendente como este:

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

O un tipo descendente como este:

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

Para comprender cómo funciona esto, tenga en cuenta que usorttoma una función de comparación proporcionada por el usuario que debe comportarse de la siguiente manera (de los documentos):

La función de comparación debe devolver un número entero menor que, igual o mayor que cero si el primer argumento se considera respectivamente menor, igual o mayor que el segundo.

Y tenga en cuenta también que <=>, el operador de la nave espacial,

devuelve 0 si ambos operandos son iguales, 1 si la izquierda es mayor y -1 si la derecha es mayor

que es exactamente lo que usortnecesita De hecho, casi toda la justificación dada para agregar <=>al lenguaje en https://wiki.php.net/rfc/combined-comparison-operator es que

facilita la escritura de devoluciones de llamadas de pedidos para usar con usort()más facilidad


PHP 5.3+

PHP 5.3 introdujo funciones anónimas, pero aún no tiene el operador de nave espacial. Todavía podemos usar usortpara ordenar nuestra matriz, pero es un poco más detallado y más difícil de entender:

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

Tenga en cuenta que, aunque es bastante común que los comparadores que tratan con valores enteros solo devuelvan la diferencia de los valores, por ejemplo $item2['price'] - $item1['price'], no podemos hacerlo con seguridad en este caso. Esto se debe a que los precios son números de coma flotante en el ejemplo del autor de la pregunta, pero la función de comparación a la que pasamos usorttiene que devolver enteros para usortque funcionen correctamente:

La devolución de valores no enteros de la función de comparación, como float, dará como resultado una conversión interna a un entero del valor de retorno de la devolución de llamada. Por lo tanto, los valores como 0.99 y 0.1 se convertirán en un valor entero de 0, que comparará dichos valores como iguales.

¡Esta es una trampa importante a tener en cuenta al usar usortPHP 5.x! Mi versión original de esta respuesta cometió este error y, sin embargo, acumulé diez votos a favor en miles de visitas aparentemente sin que nadie se diera cuenta del error grave. La facilidad con la que los imbéciles como yo pueden arruinar las funciones de comparación es precisamente la razón por la que el operador de nave espacial más fácil de usar se agregó al lenguaje en PHP 7.

Mark Amery
fuente
8
Lo sentimos, pero este enfoque elimina las claves de cadena de las matrices asociativas. En su lugar, se debe usar la función "uasort".
Matteo-SoftNet
8
@DotMat Interesante: no lo sabía uasort. Sin embargo, después de mirar los documentos, esta respuesta sigue siendo correcta en este caso . En el ejemplo del OP, la matriz que se ordenará tiene índices numéricos secuenciales en lugar de índices de cadena, por lo que usortes más apropiado. El uso uasorten una matriz indexada secuencialmente dará como resultado una matriz ordenada que no está ordenada por sus índices numéricos, de modo que el primer elemento visto en un foreachbucle no $your_array[0]lo es, lo que es poco probable que sea un comportamiento deseable.
Mark Amery
100

Mientras que otros han sugerido correctamente el uso de array_multisort(), por alguna razón, ninguna respuesta parece reconocer la existencia de array_column(), lo que puede simplificar enormemente la solución. Entonces mi sugerencia sería:

array_multisort(array_column($inventory, 'price'), SORT_DESC, $inventory);
Mariano Iglesias
fuente
1
Por alguna razón, no pude hacerlo funcionar con cadenas con letras minúsculas / mayúsculas. Incluso usando el SORT_FLAG_CASE. Lo siguiente funcionó para la comparación de cadenas para mí: array_multisort (array_map (strtolower, array_column ($ ipr_projects, 'Name')), SORT_ASC, $ ipr_projects);
Pabamato
8
Esta es la respuesta más elegante. Debe ser calificado mucho más alto!
Armin Hierstetter
3
la más corta y fácil, acepté una en mi opinión
StudioX
1
Buenas cosas aquí. ¡Funcionó perfectamente para mí!
Funk Doc
Trabajó como un encanto, ty
Leif_Lundberg
42

Dado que los elementos de su matriz son matrices con teclas de cadena, su mejor opción es definir una función de comparación personalizada. Es bastante rápido y fácil de hacer. Prueba esto:

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

Produce lo siguiente:

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

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

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

)
zombat
fuente
44
En combinación con algunos de los otros comentarios aquí (uasort y funciones anónimas en línea), obtienes esta frase:uasort( $inventory, function ($a, $b) { if ( $a==$b ) return 0; else return ($a > $b) ? -1 : 1; });
Alan Porter
@AlanPorter usortparece más apropiado que uasortpara ordenar una matriz con teclas numéricas secuenciales. Terminar con una matriz donde el primer elemento está en el índice 1y el segundo elemento está en el índice 0es un comportamiento extraño y una trampa segura para las personas que no están familiarizadas con los detalles de las matrices de PHP; usortle brinda el resultado que intuitivamente esperaría.
Mark Amery
25

Terminé con esto:

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

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

Simplemente llame a la función, pasando la matriz y el nombre del campo de la matriz de segundo nivel. Me gusta:

sort_array_of_array($inventory, 'price');
Danielzt
fuente
1
¡Decir ah! Acabo de hacer casi exactamente lo mismo e iba a publicar pero vi el tuyo ... votado.
Rob Evans
1
Votación negativa porque esta es exactamente la misma solución que Josh Davis publicó años antes.
Mark Amery
No estoy de acuerdo ... No dije que era una solución diferente, solo dije que terminé con esta solución y le di una función de trabajo completa.
Danielzt
1
@ MarkAmery Prefiero respuestas contenidas en funciones. Alienta a copiar los pegadores a usar funciones y, con suerte, escribir menos código de espagueti.
Goose
19

Se puede usar usortcon funciones anónimas, p. Ej.

usort($inventory, function ($a, $b) { return strnatcmp($a['price'], $b['price']); });
kenorb
fuente
Versiones PHP 5> = 5.5.0, PHP 7 para aquellos de ustedes como yo que realmente quería que esto funcionara para ellos ..
Matt P
1
Notable que strnatcmp, destinado a comparar cadenas, parece funcionar bien aquí. Aparentemente, el "orden natural" que implementa incluye la clasificación de cadenas numéricas numéricamente en lugar de léxico.
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");
}

salidas:

first: pork

pork: 5.43
fruit: 3.5
milk: 2.9
danamlund
fuente
6

Desde Ordenar una matriz de matrices asociativas por valor de clave dada en php :

uasort ( http://php.net/uasort ) le permite ordenar una matriz por su propia función definida. En tu caso, eso es simple:

$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");
Kamal
fuente
1
Esta respuesta apareció en la cola de revisión de baja calidad, presumiblemente porque no proporciona ninguna explicación del código. Si este código responde a la pregunta, considere agregar un texto que explique el código en su respuesta. De esta manera, es mucho más probable que obtenga más votos positivos y ayude al interlocutor a aprender algo nuevo.
lmo
1
hmpf. Esta es la mejor respuesta aunque.
commonpike
1
-1; La cmpfunción aquí es incorrecta. Se supone que devuelve "un número entero menor, igual o mayor que cero si se considera que el primer argumento es respectivamente menor, igual o mayor que el segundo", pero en su lugar devuelve trueo false. Parece, notablemente, que no obstante funciona, quizás porque la implementación actual de usorty amigos trata los casos de "menor que" e "igual a" de manera idéntica, pero no cuente con que continúe trabajando en futuras versiones de PHP. Si intentan que los géneros sean estables (es decir, que no se muevan innecesariamente por elementos iguales), esto se romperá.
Mark Amery
Además, usortsería más apropiado que uasortaquí, ya que uasortconserva la asociación entre claves y valores que es confusa e inesperada cuando se trata de una matriz numérica secuencial. Por ejemplo, los índices de $arrayarriba después de llamar uasortson 2, 0 y 1, en ese orden. A menos que por alguna razón desee eso, probablemente se sentirá más cómodo usando usort, lo que reindexa la matriz y la reordena.
Mark Amery
5

Se probó en 100 000 registros: Tiempo en segundos (calculado por microtiempo de función). Solo para valores únicos en la clasificación de posiciones clave.

Solución de la función de @Josh Davis: Tiempo invertido: 1.5768740177155

Solución minera : Tiempo invertido: 0.094044923782349

Solución:

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*
}
Nefelim
fuente
77
Sin embargo, el requisito de claves de clasificación únicas es una especie de factor decisivo. Si tiene valores de clasificación únicos que pueden ser claves, surge la pregunta: ¿por qué no simplemente construir la matriz con esas claves para empezar? En el escenario del OP, es difícil imaginar que dos artículos con el mismo precio serían imposibles . Eso en mente, usar esta solución causaría que los elementos de la matriz desaparezcan misteriosa y silenciosamente del conjunto de resultados ordenados.
Chris Baker
@ Chris Baker, tienes razón. Esto funciona solo para valores únicos. Pero esta solución funciona muy rápido, por lo que la velocidad fue la razón de su fabricación y uso. Por el momento puede no ser real, es necesario probarlo con PHP 7.1.x.
Nefelim
4

Yo uso uasortasi

<?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);
mirado
fuente
3

Esta función es reutilizable:

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

Funciona bien en los valores de cadena de forma predeterminada, pero tendrá que sustituir la devolución de llamada para una función de comparación de números si todos sus valores son números.

mpen
fuente
Llamas a esto usortarrpero luego llamas en uasortlugar de usort; quizás un poco confuso. El último es, en el caso de una matriz secuencial con índices numéricos, como el que se muestra en la pregunta, probablemente lo que realmente desea.
Mark Amery
2

Puede intentar definir su propia función de comparación y luego usar usort .

Alex Sexton
fuente
Si. Lo haré si no puedo encontrar una solución. Estoy bastante seguro de que hay algunos parámetros extraños que puede agregar a uno de los tipos para lograr esto. ¡Gracias por tus pensamientos!
Matt
2

Aquí hay un método que encontré hace mucho tiempo y lo limpié un poco. Esto funciona muy bien y se puede cambiar rápidamente para aceptar objetos también.

/**
 * 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 cambiar el método para ordenar objetos simplemente cambie la siguiente línea:

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

Para ejecutar el método simplemente haz

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

lzoesch
fuente
Este enfoque funciona, pero es un poco menos conciso que la respuesta de Josh Davis (con array_multisort) o la mía (con usort) y, a cambio, parece no ofrecer ventajas sobre ellos.
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
    )

)
Kamal
fuente
1

prueba esto:

$prices = array_column($inventory, 'price');
array_multisort($prices, SORT_DESC, $inventory);
print_r($inventory);
jamshid
fuente
Hola y bienvenido a stackoverflow, y gracias por responder. Si bien este código puede responder la pregunta, ¿puede considerar agregar alguna explicación sobre cuál fue el problema que resolvió y cómo lo resolvió? Esto ayudará a los futuros lectores a comprender mejor su respuesta y aprender de ella.
Plutian
0

Función dinámica completa Salté aquí para la ordenación asociativa de matrices y encontré esta increíble función en http://php.net/manual/en/function.sort.php . Esta función es muy dinámica y se ordena en orden ascendente y descendente con la tecla especificada.

Función simple para ordenar una matriz por una clave específica. Mantiene asociación de í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
Ahmad Sayeed
fuente
-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;


} 
Chirag Pipariya
fuente
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código. ¡Intente también no saturar su código con comentarios explicativos, ya que esto reduce la legibilidad tanto del código como de las explicaciones!
Goodbye StackExchange
-5

prueba esto:

asort($array_to_sort, SORT_NUMERIC);

para referencia vea esto: http://php.net/manual/en/function.asort.php

ver varios indicadores de clasificación aquí: http://www.php.net/manual/en/function.sort.php

culebra
fuente
esto no funcionará para matrices multidimensionales, pero solo me ayudó con otro problema, gracias :)
schellmax
44
Esto no se puede usar para ordenar una lista de diccionarios por una clave de diccionario en particular y, por lo tanto, no responde a la pregunta planteada.
Mark Amery