2009-05-07 8 views
19

È possibile ordinare un array con caratteri Unicode/UTF-8 in PHP utilizzando un algoritmo di ordine naturale? Per esempio (l'ordine in questo array è correttamente ordinata):Algoritmo di ordinamento naturale in PHP con supporto per Unicode?

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

Se provo con asort ($ array) ottengo il seguente risultato:

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

E usando natsort ($ array):

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

Come posso implementare una funzione che restituisce l'ordine risultato corretto (0, 1, 2, 3, 4, 5, 6) sotto PHP 5? Tutte le funzioni stringa multi byte (mbstring, iconv, ...) sono disponibili sul mio sistema.

MODIFICA: Desidero specificare i valori (non i tasti), ma solo i nomi che definisco esplicitamente le chiavi (e utilizzare asort() anziché sort()) per facilitare il lavoro di ricerca dove l'ordinamento di valori unicode è andato storto.

risposta

11

Inchiodato!

$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); 

uscita:

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

Ancora meglio:

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

Grazie a @tchrist!

+3

Sembra proprio che quello di cui hai veramente bisogno sia l'Unicode Collation Algorithm (UCA). Ho una dimostrazione di Perl di questo [in questa risposta] (http://stackoverflow.com/questions/1097908/how-do-i-sort-unicode-strings-alphabetically-in-python/5024116#5024116), dove I fornire una versione calleble di essa per coloro che potrebbero non avere una libreria appropriata da chiamare. Forse potrebbe essere d'aiuto anche qui. – tchrist

+0

@tchrist: UCA è quello che sto cercando, darò un'occhiata più da vicino alla tua risposta tra un po ', grazie per l'attenzione! ;) –

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

buono. Ho avuto il mio voto – Babiker

+0

Le chiavi nel mio esempio non sono il problema, sono solo lì per aiutare l'ordinamento dei valori Unicode. –

24

La domanda non è facile rispondere come sembra al primo sguardo. Questa è una delle aree in cui la mancanza di supporto Unicode di PHP ti colpisce con tutta la forza.

Frist di tutti gli natsort() come suggerito da altri poster non ha nulla a che fare con gli array di ordinamento del tipo che si desidera ordinare. Quello che stai cercando è un meccanismo di ordinamento basato sulle impostazioni locali poiché le stringhe di ordinamento con caratteri estesi sono sempre una questione della lingua utilizzata. Prendiamo ad esempio il tedesco: A e A possono a volte essere ordinati come se fossero la stessa lettera (DIN 5007/1), e talvolta Ä può essere ordinato in quanto era in effetti "AE" (DIN 5007/2). In svedese, al contrario, arriva alla fine dell'alfabeto.

Se non si utilizza Windows, si è fortunati perché PHP fornisce alcune funzioni esattamente a questo. Usando una combinazione di setlocale(), usort(), strcoll() e la corretta localizzazione UTF-8 per la propria lingua, si ottiene qualcosa di simile:

$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); 

Si prega di notare che è obbligatorio l'uso della variante locale UTF-8 al fine di risolvere Stringhe UTF-8. Ho ripristinato le impostazioni internazionali nell'esempio sopra al suo valore originale, in quanto l'impostazione di una locale utilizzando setlocale() può introdurre effetti collaterali in altri script PHP in esecuzione. Per ulteriori dettagli, consultare il manuale di PHP.

Quando si utilizza un computer Windows, al momento è disponibile la soluzione no e non ci saranno precedenti PHP 6. Si prega di vedere il mio question su SO per questo specifico problema.

+1

Grande intuizione, sto sviluppando su Windows ma questo verrà eseguito su macchine * nix. Se non sbaglio, PHP 5.3 supporterà questo tipo di ordinamento attraverso qualche tipo di classe, ma voglio astenermi dal fare affidamento su set_locale() principalmente per due motivi: 1) è imprevedibile (dipende dalle impostazioni locali che il sistema operativo ha a disposizione) e 2) non è thread-safe e può causare un comportamento imprevisto sul server. –

+0

L'ordinamento utilizzando una versione multi byte della funzione ord() mi dà esattamente gli stessi risultati di un semplice ordinamento(). = ( –

+0

Scusate ma non posso seguire il vostro secondo commento ... –

0

Ho lottato con asort con questo problema.

Ordinamento:

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 
) 

messo Africa alla fine. Ho risolto con questo piccolo sporco pezzo di codice (che è adatto per il mio scopo e la mia tempistica):

$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

Sei francese? Potresti voler controllare la mia risposta a questa domanda, il mio approccio 'preg_replace' rende la traslitterazione un po' migliore e la funzione 'array_multisort' conserva anche l'associazione di valori e chiavi non numeriche. –

0

Ho anche un'altra soluzione per coloro setlocale non funziona e non hanno il modulo intl abilitato:

// 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; 
});