Подтвердить что ты не робот

Алгоритм естественной сортировки в 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 пошла не так.

4b9b3361

Ответ 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;
});