Как отсортировать массив строк UTF-8? - PullRequest
24 голосов
/ 23 сентября 2008

Я не знаю, как отсортировать массив, содержащий строки в кодировке 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 проблемы было бы неплохо ...

Ответы [ 8 ]

28 голосов
/ 06 марта 2012
$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 extension . Он распространяется с исходными текстами PHP 5.3, но может быть отключен для некоторых сборок. Например. в Debian он находится в пакете php5-intl.

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

7 голосов
/ 08 декабря 2008

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

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

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

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

5 голосов
/ 24 сентября 2008

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

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 на странице отчета об ошибках (две другие, вероятно, связанные, ошибки были классифицированы как поддельные - я не думаю, что эта ошибка это фальшивка ; -).

Спасибо всем вам.

3 голосов
/ 23 сентября 2008

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

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

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

РЕДАКТИРОВАТЬ: так как вы упоминаете, что ваши входные данные чисты (я предполагаю, что они все попадают в кодовую страницу "windows-1252"), то вы должны сделать следующее преобразование: UTF-8 → Unicode → Windows-1252, на котором Данные в кодировке Windows-1252 выполняют сортировку, выбирая локаль «CP1252».

0 голосов
/ 11 октября 2016

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

$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] => Швейц
)

0 голосов
/ 25 сентября 2015

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"
}
0 голосов
/ 23 сентября 2008

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

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

locale -a
0 голосов
/ 23 сентября 2008

Использование вашего примера с кодовой страницей 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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...