Cuando estáis desarrollando en PHP, como nosotros con WordPress o WooCommerce, en más de una ocasión por ejemplo cuando haces una llamada a un filtro en WordPress y devuelves un array, te gustaría hacerlo ordenado de otra forma. Cosas que se me ocurren: elemento de «Mi cuenta» en WooCommerce, métodos de envío en WooCommerce o tantos otros. El caso es que muchos de estos array están referenciados a una cadena, lo que también llamamos matriz asociativa, array asociativo o tabla hash; y no es tan inmediato reordenarlos.
Pues bueno como es un problema con el que me enfrento a menudo, voy a describir unas pocas soluciones.
Os doy primero el array con los datos y el array que marcará el orden.
$datos_usuario = array( 'first_name' => 'Javier', 'user_email' => 'carazo@codection.com', 'last_login' => '2020-01-25', 'last_name' => 'Carazo' ); $orden = array('first_name', 'last_name', 'user_email', 'last_login');
El resultado que deseamos es el siguiente por lo tanto:
$datos_usuario = array( 'first_name' => 'Javier', 'last_name' => 'Carazo', 'user_email' => 'carazo@codection.com', 'last_login' => '2020-01-25' );
Veamos tres formas diferentes de hacerlo.
¿De qué hablamos aquí?
Usando array_merge()
$array_ordenado = array_merge( array_flip( $orden ), $datos_usuario );
- array_flip(): convierte valores en claves y viceversa, como en este caso solo tiene valores, los convierte en claves
- array_merge(): como los dos arrays tienen las mismas claves, los valores del segundo array son escritos en el orden del primero
Usando array_replace()
$array_ordenado = array_replace( array_flip( $orden ), $datos_usuario );
- array_flip(): hace lo mismo que antes, convertir un array de valores en un array de claves
- array_replace(): reemplaza los valores del primer arary con los valores del segundo teniendo en cuenta el orden del primero, al ir sustituyendo sus términos, vacíos, por los del segundo, rellenos
Usando uksort()
uksort( $datos_usuario, function( $key1, $key2 ) use ( $orden ) { return ( ( array_search( $key1, $orden ) > array_search( $key2, $orden ) ) ? 1 : -1 ); });
- uksort() permite ordenar un array por claves usando una función de ordenación, que se llama en cada comparación de dos elementos
- La función que se ha definido, hace búsquedas sobre el array de orden de forma que el array $datos_usuario se ordena en base al orden del array de orden
Por gustarme más me gusta más uno de los dos primeros métodos (por su limpieza de código), aunque si en los comentarios podéis aportar vuestros pensamientos de cuál de ellas es más rápida, será interesante saberlo para cuando el array a ordenar sea de cierto tamaño. Y si conocéis otro método más, encantado de que lo aportéis.
Actualizado
Gracias a la aportación de Manuel Canga, en este tweet:
https://twitter.com/trasweb/status/1221766615458631681
Incluimos otro método más basado en el uso de foreach:
foreach( $order as $field ){ $datos_usuario_ordenado[ $field ] = $datos_usuario[ $field ]; }
¿Cuál método es más rápido?
Seguimos actualizando este post y es que Manuel Canga ha tenido el detalle de calcular tiempos, para ver cuál es más rápido y aquí os dejo el resultado. Tenéis la ejecución online aquí en PHP Sandbox.
Array merge: 1580140177.172296
Array replace: 1580140177.453599
Array uksort: 1580140178.319940
Foreach: 1580140178.606628
<?php const MAX_LOOPS = 1000000; // 1 million $datos_usuario = array( 'first_name' => 'Javier', 'user_email' => 'carazo@codection.com', 'last_login' => '2020-01-25', 'last_name' => 'Carazo' ); $orden = array('first_name', 'last_name', 'user_email', 'last_login'); $time_start = microtime(true); for($i = 0; $i<MAX_LOOPS; $i++ ) { $array_ordenado = array_merge( array_flip( $orden ), $datos_usuario ); } $time_end = microtime(true); printf("\nArray merge: %f", $time_end); //------- $time_start = microtime(true); for($i = 0; $i<MAX_LOOPS; $i++ ) { $array_ordenado = array_replace( array_flip( $orden ), $datos_usuario ); } $time_end = microtime(true); printf("\nArray replace: %f", $time_end); //------- $time_start = microtime(true); for($i = 0; $i<MAX_LOOPS; $i++ ) { uksort( $datos_usuario, function( $key1, $key2 ) use ( $orden ) { return ( ( array_search( $key1, $orden ) > array_search( $key2, $orden ) ) ? 1 : -1 ); }); } $time_end = microtime(true); printf("\nArray uksort: %f", $time_end); //------- $time_start = microtime(true); for($i = 0; $i<MAX_LOOPS; $i++ ) { $array_ordenado = []; foreach($orden as $field ) { $array_ordenado[$field] = $datos_usuario[$field]; } } $time_end = microtime(true); printf("\nForeach: %f", $time_end);
Se han hecho un total de 1 millón de iteraciones sobre cada operación para así ver el rendimiento de cada instrucción, sobre esta estructura de datos. Otra prueba diferente sería ordenar una estructura de datos grande (que no tienen por qué ser igual los resultados) y de estos resultados podemos deducir que en estructuras pequeñas como esta:
- Los tiempos son prácticamente iguales
- En todo caso, los primeros métodos son algo más ligeros que los últimos
Y por lo tanto, os invito a hacerlo como más os guste. A mí personalmente me gustan más los primeros también por su sencillez a la hora de escribirlos, así que me quedo con ellos por dos motivos, sencillo y rápido.
Si tenéis más aportaciones, ya sabéis a través de tweets o comentarios podemos agregarlas.