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

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

Здесь он работал отлично (см. Мой пост ниже), вы уверены, что это не имеет ничего общего с конфигурацией машины?

Huppie 23.09.2008 15:26

Обратите внимание, что порядок сортировки зависит от языка. В немецком языке A и Ä иногда можно отсортировать, как если бы они были одной и той же буквой, а иногда Ä можно отсортировать, поскольку на самом деле это было «AE». Шведский, однако, Ä стоит в конце алфавита. Карл

Carl Seleborg 24.09.2008 12:16

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

Stefan Gehrig 24.09.2008 12:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
26
3
31 978
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

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

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

Спасибо за информацию - посмотрю по ссылкам. Но я сомневаюсь, что усилия стоят результата, потому что я просто хочу отсортировать список названий стран и штатов. Возможно, есть более простое решение.

Stefan Gehrig 23.09.2008 15:35

Вроде разумное решение ... Попробую отсортировать преобразованный массив. Вы правы, что Windows-1252 должна охватывать все используемые символы.

Stefan Gehrig 23.09.2008 16:20

Что значит преобразовать UTF-8 в Unicode. UTF-8 - это кодировка символов переменной длины для Unicode.

grom 23.09.2008 16:46

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

tzot 23.09.2008 23:41

В PHP нет такого понятия :(

George Lund 24.06.2013 18:15

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


The above example is неправильный, it uses ASCII encoding instead of UTF-8. I did trace the strcoll() calls and look what I found:
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

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

Вы уверены, что ваш PHP-файл, используемый для тестирования, имеет кодировку UTF-8? Если я использую кодировку ISO-8859-1 для самого файла, я получу тот же результат, который вы опубликовали выше.

Stefan Gehrig 23.09.2008 15:29

Я дважды проверил его вторым файлом (убедился, что он закодирован в UTF-8), но теперь он (действительно), кажется, воспроизводит вашу проблему, извините за дерьмо в этом случае.

Huppie 23.09.2008 15:32

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

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

locale -a

Я использую Windows-машину для разработки ... Подходящей кодовой страницей UTF-8 в Windows является 65001 - вот почему моя локаль должна быть German_Germany.65001.

Stefan Gehrig 23.09.2008 20:21
Ответ принят как подходящий

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

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

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

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

The set of available languages, country/region codes, and code pages includes all those supported by the Win32 NLS API except code pages that require more than two bytes per character, such as UTF-7 and UTF-8. If you provide a code page like UTF-7 or UTF-8, setlocale will fail, returning NULL. The set of language and country/region codes supported by setlocale is listed in Language and Country/Region Strings.

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

Это является ошибка в PHP, полагающаяся на способность ОС правильно сопоставлять строки, зная, что некоторые ОС этого не делают. Если тонкой оболочки недостаточно, PHP следует использовать что-то еще.

matteo 05.08.2012 16:46

$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.

Расширение ext/intl действительно спасло меня - к сожалению, его не так просто установить в некоторых системах (например, Mac OS X со встроенным PHP).

Stefan Gehrig 06.03.2012 12:17

Я часто разрабатываю на Mac и никогда не использую в комплекте, например. Стек ЛАМПА. Вместо этого установите что-то вроде homebrew или macports, и вы избавите себя от некоторых проблем (и, возможно, добавите еще немного, но это будет меньшее количество проблем)

chrishiestand 03.10.2013 03:14

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

Результат:

Array
(
[0] => Ägypten
[1] => England
[2] => France
[3] => Österreich
[4] => Schweiz
)

Проблема здесь в том, что вы потеряете символы, которые не могут быть представлены в ISO-8859-1.

Stefan Gehrig 11.10.2016 13:55

Другие вопросы по теме