如何对UTF-8字符串数组进行排序?

时间:2008-09-23 11:01:25

标签: php arrays sorting utf-8

我currentyl对如何在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字符串识别区域设置对数组进行排序?

刚才注意到这似乎是PHP上的问题,因为使用de_DE.utf8作为语言环境的相同代码段可以在Linux机器上运行。然而,针对这个特定于Windows的问题的解决方案会很好......

8 个答案:

答案 0 :(得分:28)

$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::compareusort非常有用。

答案 1 :(得分:7)

此问题的更新:

即使围绕此问题的讨论显示我们可能发现了strcoll()和/或setlocale()的PHP错误,但事实显然并非如此。问题是setlocale()的Windows CRT实现的限制(PHP setlocale()只是CRT调用的一个薄包装)。以下是MSDN page "setlocale, _wsetlocale"

的引用
  

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

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

答案 2 :(得分:5)

最终,由于Huppie发现了一个明显的PHP错误,如果不使用ΤΖΩΤΖΙΟΥ建议的重新编码字符串(UTF-8→Windows-1252或ISO-8859-1),这个问题无法以简单的方式解决。 为了总结这个问题,我创建了以下代码片段,它清楚地表明问题是使用65001 Windows-UTF-8代码页时的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.netBug #46165 strcoll() does not work with UTF-8 strings on Windows上提交了错误报告。如果您遇到同样的问题,可以在错误报告页面上向PHP团队提供反馈(另外两个,可能是相关的,错误被归类为 bogus - 我不认为这个bug是 bogus ; - )。

感谢你们所有人。

答案 3 :(得分:3)

这是一个非常复杂的issue,因为UTF-8编码的数据可以包含任何Unicode字符(即来自许多8位编码的字符,这些字符在不同的语言环境中进行不同的整理)。

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

检查我提供的链接。

编辑:既然你提到输入数据是清楚的(我假设它们都属于“windows-1252”代码页),那么你应该进行以下转换:UTF-8→Unicode→Windows-1252,其中Windows-1252编码数据会选择“CP1252”区域设置。

答案 4 :(得分: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

我确实发现一些bug reports被标记为bogus ... 你最好的选择是提交错误报告,我想虽然......

答案 5 :(得分:0)

found this following helper function将字符串的所有字母转换为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"
}

答案 6 :(得分:0)

我遇到了与德国“Umlaute”相同的问题。经过一番研究,这对我有用:

<trans-unit id="17">
    <source>description</source>
    <target>description</target>
</trans-unit>

结果:

  

阵列
      (
      [0] =&gt; Ägypten
      [1] =&gt;英格兰
      [2] =&gt;法国
      [3] =&gt; Österreich
      [4] =&gt;瑞士
      )

答案 7 :(得分:-1)

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

在UNIX系统上,您可以使用命令

获取当前安装的语言环境列表
locale -a