2009-05-07 9 views
19

Ist es möglich, ein Array mit Unicode/UTF-8-Zeichen in PHP mit einem natürlichen Ordnungsalgorithmus zu sortieren? Zum Beispiel (die Reihenfolge in diesem Array richtig geordnet):Natürlicher Sortieralgorithmus in PHP mit Unterstützung für Unicode?

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

Wenn ich versuche, mit asort ($ array) bekomme ich folgendes Ergebnis:

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

Und mit natsort ($ array):

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

Wie kann ich eine Funktion implementieren, die das korrekte Ergebnis liefert, um (0, 1, 2, 3, 4, 5, 6) unter PHP 5? Alle Multi-Byte-String-Funktionen (mbstring, iconv, ...) sind auf meinem System verfügbar.

EDIT: Ich möchte Natsort() die Werte, nicht die Schlüssel - der einzige Grund, warum ich explizit die Schlüssel definieren (und mit asort() statt sort()) ist die Suche erleichtern heraus, wo die Sortierung von Unicode-Werten ging schief.

Antwort

11

Nailed it!

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

Ausgang:

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

Noch besser:

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

Dank @tchrist!

+3

Klingt nach dem, was Sie wirklich brauchen, ist der Unicode Collation Algorithm (UCA). Ich habe eine Perl-Demonstration davon [in dieser Antwort] (http://stackoverflow.com/questions/1097908/how-do-i-sort-unicode-strings-alphabetically-in-python/5024116#5024116), wo ich Stellen Sie eine shell-callable-Version für diejenigen zur Verfügung, die möglicherweise keine richtige Bibliothek zum Anrufen haben. Vielleicht hilft das auch hier. – tchrist

+0

@tchrist: UCA ist, was ich suche, ich werde Ihre Antwort ein wenig näher betrachten, danke für die Köpfe hoch! ;) –

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

gut eins. Habe meine Stimme bekommen. – Babiker

+0

Die Schlüssel in meinem Beispiel sind nicht das Problem, sie dienen nur dazu, die Unicode-Werte zu sortieren. –

24

Die Frage ist nicht so einfach zu beantworten, wie es auf den ersten Blick scheint. Dies ist einer der Bereiche, in denen PHP mangels Unicode-Unterstützung Sie mit voller Stärke trifft.

Die erste von allen natsort(), wie von anderen Postern vorgeschlagen, hat nichts mit Sortieren von Arrays des Typs, den Sie sortieren möchten, zu tun. Was Sie suchen, ist ein länderspezifischer Sortiermechanismus, da das Sortieren von Strings mit erweiterten Zeichen immer eine Frage der verwendeten Sprache ist. Nehmen wir zum Beispiel deutsch: A und Ä können manchmal so sortiert werden, als wären sie der gleiche Buchstabe (DIN 5007/1), und manchmal kann Ä sortiert werden, wie es tatsächlich war "AE" (DIN 5007/2). Im Schwedischen kommt Ä dagegen am Ende des Alphabets.

Wenn Sie nicht mit Windows arbeiten, haben Sie Glück, denn PHP bietet genau diese Funktionen. Mit einer Kombination aus setlocale(), usort(), strcoll() und der korrekten UTF-8-Locale für Ihre Sprache, erhalten Sie so etwas wie diese:

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

Bitte beachten Sie, dass es zwingend notwendig ist, die UTF-8 locale Variante zu verwenden, um zu sortieren UTF-8-Zeichenfolgen. Ich setze das Gebietsschema im obigen Beispiel auf seinen ursprünglichen Wert zurück, da das Setzen eines Gebietsschemas mit setlocale() Nebeneffekte in anderen laufenden PHP-Skripten verursachen kann - bitte lesen Sie das PHP-Handbuch für weitere Details.

Wenn Sie eine Windows-Maschine verwenden, gibt es zur Zeit keine Lösung für dieses Problem, und es wird nicht vor PHP 6 Ich nehme an. Bitte beachten Sie meine eigene question auf SO für dieses spezifische Problem.

+1

Toller Einblick, ich entwickle unter Windows, aber das wird auf * nix Maschinen laufen. Wenn ich nicht irre, wird PHP 5.3 diese Art der Sortierung durch irgendeine Art von Klasse unterstützen, aber ich möchte mich aus set_locale() aus zwei Gründen zurückhalten: 1) es ist unvorhersehbar (hängt von den Locales ab, die das OS zur Verfügung hat) und 2) es ist nicht Thread-sicher und kann zu unerwartetem Verhalten auf dem Server führen. –

+0

Sortierung mit einer Multi-Byte-Version der ord() -Funktion gibt mir die gleichen Ergebnisse wie eine einfache sort(). = ( –

+0

Entschuldigung, aber ich kann Ihrem zweiten Kommentar nicht folgen ... –

0

Ich habe mit diesem Problem mit asort gekämpft.

Sortierung:

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 
) 

África am Ende setzen. Ich löste es mit diesem schmutzigen kleinen Stück Code (was für meine Zwecke und meine zeitlichen Rahmen fit ist):

$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

Bist du Französisch? Vielleicht möchten Sie meine Antwort auf diese Frage überprüfen, mein 'preg_replace'-Ansatz macht die Transliteration ein wenig besser und die' array_multisort'-Funktion behält auch die Assoziation von Werten und nicht-numerischen Schlüsseln bei. –

0

Ich habe auch eine andere Abhilfe für diejenigen setlocale nicht funktioniert und nicht über das intl Modul aktiviert:

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