代码之家  ›  专栏  ›  技术社区  ›  Stefan Gehrig

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

  •  23
  • Stefan Gehrig  · 技术社区  · 17 年前

    我不知道如何在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"
    }
    

    de_DE.utf8 在Linux机器上用作区域设置。尽管如此,解决这个特定于Windows的问题还是不错的。。。

    8 回复  |  直到 17 年前
        1
  •  31
  •   Delian Krustev    14 年前
    $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 . 它是用PHP5.3源代码发布的,但在某些版本中可能会被禁用。例如,在Debian中,它位于包php5 intl中。

    Collator::compare 是有用的 usort .

        2
  •  8
  •   Stefan Gehrig    17 年前

    有关这一问题的最新情况:

    strcoll() setlocale() ,情况显然并非如此。这个问题相当于Windows CRT实现的一个限制 setlocale() (PHPs 只是围绕CRT调用的薄薄包装)。以下是一篇引文 MSDN page "setlocale, _wsetlocale"

    可用语言集, 国家/地区代码和代码页 Win32 NLS API 除了代码页 字符,例如UTF-7和UTF-8。如果 您可以提供类似UTF-7或 UTF-8,setlocale将失败,返回 无效的 语言和语言的集合 setlocale在语言和语言中列出 国家/地区字符串。

        3
  •  6
  •   Stefan Gehrig    17 年前

    最终,由于Huppie发现了一个明显的PHP错误,如果不使用··建议的重新编码字符串(UTF-8 Windows-1252或ISO-8859-1),这个问题就无法以简单的方式解决。

    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"
      [...]
    

    我提交了一份关于 bugs.php.net : Bug #46165 strcoll() does not work with UTF-8 strings on Windows -我不认为这个错误是正确的 假的 ;-).

        4
  •  4
  •   tzot    17 年前

    这是一个非常复杂的问题 issue ,因为UTF-8编码的数据可以包含任何Unicode字符(即来自许多8位编码的字符,这些编码在不同的地区具有不同的排序)。

    如果您将UTF-8数据转换为Unicode(对不起,不熟悉PHP Unicode函数),然后将其规范化为 NFD or NFKD 然后,对代码点进行排序可能会提供一些对您有意义的排序规则(即“%”之前的“A”)。

    检查我提供的链接。

    编辑:由于您提到您的输入数据是清晰的(我假设它们都属于“windows-1252”代码页),那么您应该执行以下转换:UTF-8 Unicode windows-1252,windows-1252编码的数据在其上选择“CP1252”区域设置进行排序。

        5
  •  1
  •   Community Mohan Dere    9 年前

    found this following helper function

    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
  •   Huppie    17 年前

    在我的windows开发机器上,使用代码页1252的示例非常有效。

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

    这是在PHP5.2.6中实现的。顺便提一下


    错误的
    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 ... 我想你最好的办法就是提交一份bug报告。。。

        7
  •  0
  •   Misa Lazovic Francisco Balam    9 年前

    我在德语“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);
    

    结果是:


    (

    [1] =>英格兰
    [2] =>法国

    [4] =>施韦兹

        8
  •  -1
  •   troelskn    17 年前

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

    locale -a
    
    推荐文章