2009-05-07 16 views
19

¿Es posible ordenar una matriz con caracteres Unicode/UTF-8 en PHP utilizando un algoritmo de orden natural? Por ejemplo (el orden en esta matriz se ordena correctamente):Algoritmo de clasificación natural en PHP con soporte para Unicode?

$array = array 
(
    0 => 'Agile', 
    1 => 'Ágile', 
    2 => 'Àgile', 
    3 => 'Âgile', 
    4 => 'Ägile', 
    5 => 'Ãgile', 
    6 => 'Test', 
); 

Si trato con asort ($ array) me sale el siguiente resultado:

Array 
(
    [0] => Agile 
    [6] => Test 
    [2] => Àgile 
    [1] => Ágile 
    [3] => Âgile 
    [5] => Ãgile 
    [4] => Ägile 
) 

Y el uso de natsort ($ array):

Array 
(
    [2] => Àgile 
    [1] => Ágile 
    [3] => Âgile 
    [5] => Ãgile 
    [4] => Ägile 
    [0] => Agile 
    [6] => Test 
) 

¿Cómo puedo implementar una función que devuelve el orden resultado correcto (0, 1, 2, 3, 4, 5, 6) bajo PHP 5? Todas las funciones de cadenas de bytes múltiples (mbstring, iconv, ...) están disponibles en mi sistema.

EDITAR: Quiero natsort() los valores, no las claves: la única razón por la que estoy definiendo explícitamente las claves (y usar asort() en lugar de ordenar()) es facilitar el trabajo de encontrar donde la clasificación de valores Unicode salió mal.

Respuesta

11

¡Clavado!

$array = array('Ägile', 'Ãgile', 'Test', 'カタカナ', 'かたかな', 'Ágile', 'Àgile', 'Âgile', 'Agile'); 

function Sortify($string) 
{ 
    return preg_replace('~&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|tilde|uml);~i', '$1' . chr(255) . '$2', htmlentities($string, ENT_QUOTES, 'UTF-8')); 
} 

array_multisort(array_map('Sortify', $array), $array); 

Salida:

Array 
(
    [0] => Agile 
    [1] => Ágile 
    [2] => Âgile 
    [3] => Àgile 
    [4] => Ãgile 
    [5] => Ägile 
    [6] => Test 
    [7] => かたかな 
    [8] => カタカナ 
) 

Aún mejor:

if (extension_loaded('intl') === true) 
{ 
    collator_asort(collator_create('root'), $array); 
} 

Gracias a @tchrist!

+3

Parece que lo que realmente necesita aquí es el Algoritmo de intercalación Unicode (UCA). Tengo una demostración de Perl de eso [en esta respuesta] (http://stackoverflow.com/questions/1097908/how-do-i-sort-unicode-strings-alphabetically-in-python/5024116#5024116), donde proporcionar una versión invocable de shell para quienes no tengan una biblioteca adecuada para llamar. Quizás eso también podría ayudar aquí. – tchrist

+0

@tchrist: UCA es lo que estoy buscando, voy a echar un vistazo más de cerca a su respuesta en un momento, ¡gracias por el aviso! ;) –

1
natsort($array); 
$array = array_values($array); 
+0

bueno uno. Tengo mi voto – Babiker

+0

Las claves en mi ejemplo no son el problema, solo están ahí para ayudar a ordenar los valores Unicode. –

24

La pregunta no es tan fácil de responder como parece en el primer vistazo. Esta es una de las áreas donde la falta de compatibilidad Unicode de PHP te golpea con toda su fuerza.

Frist de todos natsort() según lo sugerido por otros carteles no tiene nada que ver con ordenar matrices del tipo que desea ordenar. Lo que está buscando es un mecanismo de clasificación con reconocimiento de configuración regional, ya que la ordenación de cadenas con caracteres extendidos siempre es una cuestión del lenguaje utilizado. Tomemos, por ejemplo, el alemán: A y Ä a veces se pueden clasificar como si fueran la misma letra (DIN 5007/1), y a veces Ä se pueden clasificar como si de hecho fueran "AE" (DIN 5007/2). En sueco, en cambio, Ä viene al final del alfabeto.

Si no usa Windows, tiene suerte ya que PHP proporciona algunas funciones para esto. Usando una combinación de setlocale(), usort(), strcoll() y la correcta codificación UTF-8 para su idioma, se obtiene algo como esto:

$array = array('Àgile', 'Ágile', 'Âgile', 'Ãgile', 'Ägile', 'Agile', 'Test'); 
$oldLocal = setlocale(LC_COLLATE, '<<your_RFC1766_language_code>>.utf8'); 
usort($array, 'strcoll'); 
setlocale(LC_COLLATE, $oldLocal); 

Tenga en cuenta que es obligatorio el uso de la variante de localización UTF-8 con el fin de ordenar Cadenas UTF-8. Restablecé la configuración regional en el ejemplo anterior a su valor original, ya que establecer una configuración regional usando setlocale() puede introducir efectos secundarios en otras secuencias de comandos PHP en ejecución. Consulte el manual de PHP para obtener más detalles.

Cuando utiliza una máquina con Windows, actualmente hay no solución a este problema y no habrá ninguna antes de que asuma PHP 6. Por favor, vea mi propio question en SO dirigiéndose a este problema específico.

+1

Gran idea, estoy desarrollando en Windows, pero esto se ejecutará en máquinas * nix. Si no me equivoco, PHP 5.3 admitirá este tipo de clasificación a través de algún tipo de clase. Sin embargo, me abstendré de confiar en set_locale() por dos motivos principalmente: 1) es impredecible (depende de las configuraciones regionales que tenga el sistema operativo) y 2) no es seguro para subprocesos y puede provocar un comportamiento inesperado en el servidor. –

+0

La ordenación utilizando una versión de varios bytes de la función ord() me da exactamente los mismos resultados que un simple sort(). = ( –

+0

Lo siento, pero no puedo seguir su segundo comentario ... –

0

Tuve problemas con Asort con este problema.

Clasificación:

Array 
(
    [xa] => África 
    [xo] => Australasia 
    [cn] => China 
    [gb] => Reino Unido 
    [us] => Estados Unidos 
    [ae] => Emiratos Árabes Unidos 
    [jp] => Japón 
    [lk] => Sri Lanka 
    [xe] => Europa Del Este 
    [xw] => Europa Del Oeste 
    [fr] => Francia 
    [de] => Alemania 
    [be] => Bélgica 
    [nl] => Holanda 
    [es] => España 
) 

que África al final. Lo resuelto con este pequeño sucio trozo de código (que es apto para mi propósito y mi calendario):

$sort = array(); 
foreach($retval AS $key => $value) { 
    $v = str_replace('ä', 'a', $value); 
    $v = str_replace('Ä', 'A', $v); 
    $v = str_replace('Á', 'A', $v); 
    $v = str_replace('é', 'e', $v); 
    $v = str_replace('ö', 'o', $v); 
    $v = str_replace('ó', 'o', $v); 
    $v = str_replace('Ö', 'O', $v); 
    $v = str_replace('ü', 'u', $v); 
    $v = str_replace('Ü', 'U', $v); 
    $v = str_replace('ß', 'S', $v); 
    $v = str_replace('ñ', 'n', $v); 
    $sort[] = "$v|$key|$value"; 
} 
sort($sort); 

$retval = array(); 
foreach($sort AS $value) { 
    $arr = explode('|', $value); 
    $retval[$arr[1]] = $arr[2]; 
} 
+0

¿Eres francés? Es posible que desee comprobar mi respuesta a esta pregunta, mi enfoque 'preg_replace' hace la transliteración un poco mejor y la función' array_multisort' también conserva la asociación de valores y claves no numéricas. –

0

He también otra solución para aquellos setlocale no funciona y no tiene el módulo intl habilitado:

// The array to be sorted 
$countries = array(
    'AT' => Österreich, 
    'DE' => Deutschland, 
    'CH' => Schweiz, 
); 

// Extend this array to your needs. 
$utf_sort_map = array(
    "ä" => "a", 
    "Ä" => "A", 
    "Å" => "A", 
    "ö" => "o", 
    "Ö" => "O", 
    "ü" => "u", 
    "Ü" => "U", 
); 

uasort($my_array, function($a, $b) use ($utf_sort_map) { 
    $initial_a = mb_substr($a, 0, 1); 
    $initial_b = mb_substr($b, 0, 1); 

    if (isset($utf_sort_map[$initial_a]) || isset($utf_sort_map[$initial_b])) { 
    if (isset($utf_sort_map[$initial_a])) { 
     $initial_a = $utf_sort_map[$initial_a]; 
    } 

    if (isset($utf_sort_map[$initial_b])) { 
     $initial_b = $utf_sort_map[$initial_b]; 
    } 

    if ($initial_a == $initial_b) { 
     return mb_substr($a, 1) < mb_substr($b, 1) ? -1 : 1; 
    } 
    else { 
     return $initial_a < $initial_b ? -1 : 1; 
    } 
    } 

    return $a < $b ? -1 : 1; 
});