Как отсортировать массив строк 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, было бы неплохо...
Ответы
Ответ 1
В конце концов эта проблема не может быть решена простым способом, не используя перекодированные строки (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 на странице отчета об ошибках (две другие, вероятно, связанные, ошибки были классифицированы как фиктивные - я не думаю, что эта ошибка является фиктивной;-).
Спасибо всем вам.
Ответ 2
$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 intl. Он распространяется с источниками PHP 5.3, но может быть отключен для некоторых сборок. Например. в Debian находится в пакете php5-intl.
Collator::compare
полезен для usort
.
Ответ 3
Обновление по этой проблеме:
Несмотря на то, что обсуждение этой проблемы показало, что мы могли обнаружить ошибку PHP с strcoll()
и/или setlocale()
, это явно не так. Проблема скорее является ограничением реализации Windows CRT setlocale()
(PHPs setlocale()
- это всего лишь тонкая оболочка вокруг вызова CRT), Ниже приведена ссылка на страницу MSDN" setlocale, _wsetlocale:
Набор доступных языков, коды страны/региона и кодовые страницы включает все те, которые поддерживаются Win32 NLS API кроме кодовых страниц, которые требуется более двух байт за символов, таких как UTF-7 и UTF-8. Если вы предоставляете кодовую страницу, такую как UTF-7 или UTF-8, setlocale не удастся, вернувшись NULL. Набор языков и коды стран/регионов, поддерживаемые setlocale указан на языке и Строки страны/региона.
Поэтому невозможно использовать языковые операции с строками в PHP в Windows, когда строки кодируются в несколько байт.
Ответ 4
Это очень сложная проблема, поскольку кодированные данные UTF-8 могут содержать любой символ Юникода (т.е. символы из многих 8-битных кодировок которые различаются по разному в разных локалях).
Возможно, если вы преобразовали ваши данные UTF-8 в Unicode (не знакомы с функциями юникода PHP, извините), а затем нормализировали их в NFD или NFKD, а затем сортировка по кодовым точкам может привести к некоторой сортировке, которая имела бы смысл для вас (т.е. "A" перед "Ä" ).
Проверьте ссылки, которые я предоставил.
EDIT: поскольку вы упоминаете, что ваши входные данные ясны (я предполагаю, что все они попадают в кодовую страницу "windows-1252" ), тогда вы должны сделать следующее преобразование: UTF-8 → Юникод → Windows-1252, на котором Кодированные данные Windows-1252 выполняют сортировку, выбрав локаль "CP1252".
Ответ 5
Использование вашего примера с кодовой страницей 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
Я нашел некоторые отчеты об ошибках, которые были отмечены как фиктивный...
Лучшее, что у вас есть, это подача отчета об ошибке, я полагаю, хотя...
Ответ 6
I нашел эту следующую вспомогательную функцию, чтобы преобразовать все буквы строки в буквы 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"
}
Ответ 7
Я столкнулся с той же проблемой с немецким "Умлаут". После некоторых исследований это сработало для меня:
$laender =array("Österreich", "Schweiz", "England", "France", "Ägypten");
$laender = array_map("utf8_decode", $laender);
setlocale(LC_ALL,"[email protected]", "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] = > Швейц
)
Ответ 8
Ваша сортировка должна соответствовать набору символов. Поскольку ваши данные кодируются в кодировке UTF-8, вы должны использовать сортировку UTF-8. Его можно было бы назвать по-разному на разных платформах, но хорошее предположение было бы de_DE.utf8
.
В системах UNIX вы можете получить список локально установленных локалей с помощью команды
locale -a