Алгоритм естественной сортировки в PHP с поддержкой Unicode?
Можно ли отсортировать массив с символами Unicode/UTF-8 в PHP с использованием алгоритма естественного порядка? Например (порядок в этом массиве правильно упорядочен):
$array = array
(
0 => 'Agile',
1 => 'Ágile',
2 => 'Àgile',
3 => 'Âgile',
4 => 'Ägile',
5 => 'Ãgile',
6 => 'Test',
);
Если я попытаюсь с asort ($ array), я получаю следующий результат:
Array
(
[0] => Agile
[6] => Test
[2] => Àgile
[1] => Ágile
[3] => Âgile
[5] => Ãgile
[4] => Ägile
)
И используя natsort ($ array):
Array
(
[2] => Àgile
[1] => Ágile
[3] => Âgile
[5] => Ãgile
[4] => Ägile
[0] => Agile
[6] => Test
)
Как я могу реализовать функцию, которая возвращает правильный результат (0, 1, 2, 3, 4, 5, 6) в PHP 5? Все многобайтовые строковые функции (mbstring, iconv,...) доступны в моей системе.
EDIT: я хочу natsort() значения, а не ключи - единственная причина, по которой я явно определяю ключи (и использование asort() вместо sort()) - облегчить работу выясняя, где сортировка значений в unicode пошла не так.
Ответы
Ответ 1
Пригвожденный!
$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);
Вывод:
Array
(
[0] => Agile
[1] => Ágile
[2] => Âgile
[3] => Àgile
[4] => Ãgile
[5] => Ägile
[6] => Test
[7] => かたかな
[8] => カタカナ
)
Еще лучше:
if (extension_loaded('intl') === true)
{
collator_asort(collator_create('root'), $array);
}
Благодаря @tchrist!
Ответ 2
Вопрос не так прост ответить, как кажется на первый взгляд. Это одна из областей, где PHP отсутствие поддержки юникода вызывает вас в полной мере.
Фрист всех natsort()
, как это было предложено другими плакатами, не имеет ничего общего с сортировкой массивов типа, который вы хотите отсортировать. То, что вы ищете, - это механизм сортировки, известный в локали, поскольку сортировка строк с расширенными символами всегда связана с используемым языком. Например, возьмите немецкий язык: A и Ä иногда можно сортировать так, как если бы они были одной и той же буквой (DIN 5007/1), а иногда Ä можно сортировать так, как это было на самом деле "AE" (DIN 5007/2). На шведском языке, напротив, Ä приходит в конце алфавита.
Если вы не используете Windows, вам повезло, поскольку PHP предоставляет некоторые функции именно этому. Используя комбинацию setlocale()
, usort()
, strcoll()
и правильный языковой стандарт UTF-8 для вашего языка, вы получите что-то вроде этого:
$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);
Обратите внимание, что для сортировки строк UTF-8 обязательно использовать вариант локали UTF-8. я reset языковой стандарт в приведенном выше примере с его исходным значением как установка языкового стандарта с использованием setlocale()
может привести к побочным эффектам в других режимах PHP script - см. Руководство по PHP для более подробной информации.
Когда вы используете компьютер Windows, в настоящее время существует решение нет, и до PHP 6 я не буду этого делать. Посмотрите мой собственный question на SO, предназначенный для этой конкретной проблемы.
Ответ 3
natsort($array);
$array = array_values($array);
Ответ 4
Я боролся с асорт с этой проблемой.
Сортировка:
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 в конец. Я решил это с помощью этого грязного маленького фрагмента кода (который подходит для моей цели и моих временных рамок):
$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];
}
Ответ 5
У меня есть еще одно обходное решение для тех setlocale
не работает и не имеет модуля intl
:
// 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;
});