Как отсортировать массив строк UTF-8?

I currentyl не имеют понятия о том, как сортировать массив, который содержит кодированные строки UTF-8 в PHP. Массив поставляется с сервера LDAP, поэтому сортировка по базе данных (без проблем) не является решением. Следующее не работает на моей машине разработки Windows (хотя я думаю, что это должно быть как минимум возможным решением):

$array=array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich'); $oldLocal=setlocale(LC_COLLATE, "0"); var_dump(setlocale(LC_COLLATE, 'German_Germany.65001')); usort($array, 'strcoll'); var_dump(setlocale(LC_COLLATE, $oldLocal)); var_dump($array); 

Выход:

 string(20) "German_Germany.65001" string(1) "C" array(6) { [0]=> string(6) "Birnen" [1]=> string(9) "Ungetiere" [2]=> string(6) "Äpfel" [3]=> string(5) "Apfel" [4]=> string(9) "Ungetüme" [5]=> string(11) "Österreich" } 

Это полная чушь. Использование 1252 в качестве кодовой страницы для setlocale() дает другой результат, но все же явно неправильный:

 string(19) "German_Germany.1252" string(1) "C" array(6) { [0]=> string(11) "Österreich" [1]=> string(6) "Äpfel" [2]=> string(5) "Apfel" [3]=> string(6) "Birnen" [4]=> string(9) "Ungetüme" [5]=> string(9) "Ungetiere" } 

Есть ли способ отсортировать массив с локализацией строк UTF-8?

Просто отметили, что это похоже на проблему с PHP на Windows, поскольку тот же фрагмент с de_DE.utf8 используемый в качестве языка, работает на машине Linux. Тем не менее решение для этой проблемы, связанной с Windows, было бы неплохо …

 $a = array( 'Кръстев', 'Делян1', 'делян1', 'Делян2', 'делян3', 'кръстев' ); $col = new \Collator('bg_BG'); $col->asort( $a ); var_dump( $a ); 

Печать:

 array 2 => string 'делян1' (length=11) 1 => string 'Делян1' (length=11) 3 => string 'Делян2' (length=11) 4 => string 'делян3' (length=11) 5 => string 'кръстев' (length=14) 0 => string 'Кръстев' (length=14) 

Класс Collator определяется в расширенном расширении PECL . Он распространяется с источниками PHP 5.3, но может быть отключен для некоторых сборок. Например, в Debian он находится в пакете php5-intl.

Collator::compare полезен для usort .

Обновление по этой проблеме:

Несмотря на то, что обсуждение этой проблемы показало, что мы могли обнаружить ошибку PHP с помощью strcoll() и / или setlocale() , это явно не так. Проблема скорее ограничена реализацией Windows CRT для setlocale() (PHP setlocale() – это всего лишь тонкая оболочка вокруг вызова CRT). Ниже приведена ссылка на страницу MSDN «setlocale, _wsetlocale» :

Набор доступных языков, кодов страны / региона и кодовых страниц включает все те, которые поддерживаются API-интерфейсом Win32 NLS, за исключением кодовых страниц, для которых требуется более двух байтов на символ, таких как UTF-7 и UTF-8. Если вы предоставите кодовую страницу, такую ​​как UTF-7 или UTF-8, setlocale завершится с ошибкой, возвращая NULL. Набор языков и кодов стран / регионов, поддерживаемых setlocale, приведен в списке «Язык и страны / регионы».

Поэтому невозможно использовать языковые операции со строками в PHP на Windows, когда строки кодируются в нескольких байтах.

В конце концов эта проблема не может быть решена простым способом, не используя перекодированные строки (UTF-8 → Windows-1252 или ISO-8859-1), как было предложено ΤΖΩΤΖΙΟΥ из-за очевидной ошибки PHP, обнаруженной Huppie. Чтобы обобщить эту проблему, я создал следующий фрагмент кода, который наглядно демонстрирует, что проблема заключается в функции strcoll () при использовании кодовой страницы Windows-UTF-8 65001.

 function traceStrColl($a, $b) { $outValue=strcoll($a, $b); echo "$a $b $outValue\r\n"; return $outValue; } $locale=(defined('PHP_OS') && stristr(PHP_OS, 'win')) ? 'German_Germany.65001' : 'de_DE.utf8'; $string="ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜabcdefghijklmnopqrstuvwxyzäöüß"; $array=array(); for ($i=0; $i<mb_strlen($string, 'UTF-8'); $i++) { $array[]=mb_substr($string, $i, 1, 'UTF-8'); } $oldLocale=setlocale(LC_COLLATE, "0"); var_dump(setlocale(LC_COLLATE, $locale)); usort($array, 'traceStrColl'); setlocale(LC_COLLATE, $oldLocale); var_dump($array); 

Результат:

 string(20) "German_Germany.65001" a B 2147483647 [...] array(59) { [0]=> string(1) "c" [1]=> string(1) "B" [2]=> string(1) "s" [3]=> string(1) "C" [4]=> string(1) "k" [5]=> string(1) "D" [6]=> string(2) "ä" [7]=> string(1) "E" [8]=> string(1) "g" [...] 

Тот же фрагмент работает на машине Linux без каких-либо проблем, производя следующий вывод:

 string(10) "de_DE.utf8" a B -1 [...] array(59) { [0]=> string(1) "a" [1]=> string(1) "A" [2]=> string(2) "ä" [3]=> string(2) "Ä" [4]=> string(1) "b" [5]=> string(1) "B" [6]=> string(1) "c" [7]=> string(1) "C" [...] 

Фрагмент также работает при использовании закодированных строк Windows-1252 (ISO-8859-1) (конечно, необходимо изменить кодировки mb_ * и локаль).

Я отправил отчет об ошибке на bugs.php.net : Ошибка # 46165 strcoll () не работает с строками UTF-8 в Windows . Если у вас возникли те же проблемы, вы можете дать свои отзывы команде PHP на странице отчета об ошибках (две другие, вероятно, связанные, ошибки были классифицированы как фиктивные – я не думаю, что эта ошибка является фиктивной ;-).

Всем спасибо.

Это очень сложная проблема , поскольку кодированные данные UTF-8 могут содержать любой символ Юникода (т. Е. Символы из многих 8-битных кодировок, которые по-разному сопоставляются в разных локалях).

Возможно, если вы преобразуете данные UTF-8 в Unicode (не знакомы с функциями юникода PHP, извините), а затем нормализуете их в NFD или NFKD, а затем сортировка по кодовым точкам может привести к некоторой сортировке, которая имела бы смысл для вас (например, «A», перед").

Проверьте ссылки, которые я предоставил.

EDIT: поскольку вы упоминаете, что ваши входные данные ясны (я предполагаю, что все они попадают в кодовую страницу «windows-1252»), тогда вы должны сделать следующее преобразование: UTF-8 → Юникод → Windows-1252, на котором Windows-1252 закодированные данные выполняют сортировку, выбирающую локаль «CP1252».

Использование вашего примера с кодовой страницей 1252 отлично работало на моей машине разработки Windows.

 $array=array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich'); $oldLocal=setlocale(LC_COLLATE, "0"); var_dump(setlocale(LC_COLLATE, 'German_Germany.1252')); usort($array, 'strcoll'); var_dump(setlocale(LC_COLLATE, $oldLocal)); var_dump($array); 

… чик …

Это было с PHP 5.2.6. Кстати.


Вышеприведенный пример неверен , он использует кодировку ASCII вместо UTF-8. Я проследил вызовы strcoll () и посмотрел, что я нашел:

 function traceStrColl($a, $b) { $outValue = strcoll($a, $b); echo "$a $b $outValue\r\n"; return $outValue; } $array=array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich'); setlocale(LC_COLLATE, 'German_Germany.65001'); usort($array, 'traceStrColl'); print_r($array); 

дает:

  Ungetüme Äpfel 2147483647
 Ungetüme Birnen 2147483647
 Ungetüme Apfel 2147483647
 Ungetüme Ungetiere 2147483647
 Österreich Ungetüme 2147483647
 Äpfel Ungetiere 2147483647
 Äpfel Birnen 2147483647
 Apfel Äpfel 2147483647
 Ungetiere Birnen 2147483647 

Я нашел некоторые отчеты об ошибках, которые были отмечены как поддельные … Лучшая ставка у вас есть, подача отчета об ошибке, я полагаю, хотя …

Я нашел эту вспомогательную функцию для преобразования всех букв строки в буквы ASCII, которые очень полезны здесь.

 function _all_letters_to_ASCII($string) { return strtr(utf8_decode($string), utf8_decode('ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ'), 'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy'); } 

После этого простой array_multisort() дает вам то, что вы хотите.

 $array = array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich'); $reference_array = $array; foreach ($reference_array as $key => &$value) { $value = _all_letters_to_ASCII($value); } var_dump($reference_array); array_multisort($reference_array, $array); var_dump($array); 

Конечно, вы можете сделать вспомогательную функцию более совершенной. Но пока это выглядит неплохо.

 array(6) { [0]=> string(6) "Birnen" [1]=> string(5) "Apfel" [2]=> string(8) "Ungetume" [3]=> string(5) "Apfel" [4]=> string(9) "Ungetiere" [5]=> string(10) "Osterreich" } array(6) { [0]=> string(5) "Apfel" [1]=> string(6) "Äpfel" [2]=> string(6) "Birnen" [3]=> string(11) "Österreich" [4]=> string(9) "Ungetiere" [5]=> string(9) "Ungetüme" } 

Я столкнулся с той же проблемой с немецким «Умлаут». После некоторых исследований это сработало для меня:

 $laender =array("Österreich", "Schweiz", "England", "France", "Ägypten"); $laender = array_map("utf8_decode", $laender); setlocale(LC_ALL,"de_DE@euro", "de_DE", "deu_deu"); sort($laender, SORT_LOCALE_STRING); $laender = array_map("utf8_encode", $laender); print_r($laender); 

Результат:

массив
(
[0] => Ägypten
[1] => Англия
[2] => Франция
[3] => Österreich
[4] => Schweiz
)

Ваша сортировка должна соответствовать набору символов. Поскольку ваши данные кодируются в кодировке UTF-8, вы должны использовать сортировку UTF-8. Его можно было бы назвать по-разному на разных платформах, но хорошим предположением было бы de_DE.utf8 .

В системах UNIX вы можете получить список локально установленных локалей с помощью команды

 locale -a