26

我目前不知道如何在 PHP 中对包含 UTF-8 编码字符串的数组进行排序。该数组来自 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 字符串区域设置对数组进行排序?

刚刚注意到这似乎是 Windows 上的 PHP 问题,因为与de_DE.utf8used as locale 相同的片段在 Linux 机器上工作。尽管如此,这个Windows特定问题的解决方案会很好......

4

8 回答 8

31
$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)

该类CollatorPECL intl extension中定义。它与 PHP 5.3 源一起分发,但在某些构建中可能会被禁用。例如,在 Debian 中,它位于包 php5-intl 中。

Collator::compare对 有用usort

于 2012-03-06T00:30:05.273 回答
8

关于这个问题的更新:

尽管围绕这个问题的讨论表明我们可以用strcoll()and/or发现一个 PHP 错误setlocale(),但显然不是这样。问题是 Windows CRT 实现的限制setlocale()(PHPsetlocale()只是 CRT 调用的一个薄包装器)。以下是对MSDN 页面 "setlocale, _wsetlocale"的引用:

可用语言、国家/地区代码和代码页集包括 Win32 NLS API 支持的所有语言,但每个字符需要两个以上字节的代码页除外,例如 UTF-7 和 UTF-8。如果您提供像 UTF-7 或 UTF-8 这样的代码页,setlocale 将失败,返回 NULL。setlocale 支持的语言和国家/地区代码集在语言和国家/地区字符串中列出。

因此,当字符串是多字节编码时,不可能在 Windows 上的 PHP 中使用区域设置感知字符串操作。

于 2008-12-08T09:54:40.097 回答
6

最终,由于 Huppie 发现的一个明显的 PHP 错误,如果不使用 ΤZΩΤZΙΟΥ 建议的重新编码的字符串(UTF-8 → Windows-1252 或 ISO-8859-1),则无法以简单的方式解决此问题。为了总结这个问题,我创建了以下代码片段,它清楚地证明了问题是使用 65001 Windows-UTF-8-codepage 时的 strcoll() 函数。

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() 在 Windows 上不适用于 UTF-8 字符串。如果您遇到同样的问题,您可以在错误报告页面上向 PHP 团队提供反馈(另外两个可能相关的错误已被归类为伪造的- 我不认为这个错误是伪造的;-)。

感谢大家。

于 2008-09-24T07:42:28.867 回答
4

这是一个非常复杂的问题,因为 UTF-8 编码的数据可以包含任何 Unicode 字符(即来自许多 8 位编码的字符,它们在不同的语言环境中的排列方式不同)。

也许如果您将 UTF-8 数据转换为 Unicode(不熟悉 PHP unicode 函数,抱歉),然后将它们规范化为NFD 或 NFKD,然后对代码点进行排序可能会给您一些有意义的排序规则(即“A”在“Ä”之前)。

检查我提供的链接。

编辑:既然您提到您的输入数据是明确的(我假设它们都属于“windows-1252”代码页),那么您应该进行以下转换:UTF-8 → Unicode → Windows-1252,其中 Windows-1252编码数据进行排序选择“CP1252”语言环境。

于 2008-09-23T11:12:10.290 回答
1

发现以下帮助函数将字符串的所有字母转换为 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"
}
于 2015-09-25T09:46:04.630 回答
0

将您的示例与代码页 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

我确实发现了一些被标记为虚假的错误报告......不过,我认为最好的办法是提交错误报告......

于 2008-09-23T11:21:18.427 回答
0

我遇到了与德语“元音变音”相同的问题。经过一些研究,这对我有用:

$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] => England
[2] => France
[3] => Österreich
[4] => Schweiz

于 2016-10-11T09:58:59.857 回答
-1

您的排序规则需要匹配字符集。由于您的数据是 UTF-8 编码的,因此您应该使用 UTF-8 排序规则。它可以在不同的平台上以不同的方式命名,但一个很好的猜测是de_DE.utf8.

在 UNIX 系统上,您可以使用以下命令获取当前安装的语言环境列表

locale -a
于 2008-09-23T14:40:05.080 回答