如何在PHP中对一个UTF-8字符串数组进行排序?

26

需要帮助按utf-8排序单词。例如,我们有来自比利时的5个城市。

$array = array('Borgloon','Thuin','Lennik','Éghezée','Aubel');
sort($array); // Expected: Aubel, Borgloon, Éghezée, Lennik, Thuin
              // Actual: Aubel, Borgloon, Lennik, Thuin, Éghezée

城市 Éghezée 应该排在第三位。是否可以使用/设置某种 utf-8 或创建自己的字符顺序?


我只是想指出,供日后参考,natcasesort不能直接使用:http://codepad.org/QgdF5DUY - middus
看起来之前有类似的问题:https://dev59.com/83VD5IYBdhLWcg3wAWkO - user1012851
添加了一条注释,以减少你正在寻找的与你得到的内容混淆不清的情况。 - Billy ONeal
7个回答

49

intl自PHP 5.3起与PHP捆绑在一起,并且仅支持UTF-8

在这种情况下,您可以使用Collator

$array = array('Borgloon','Thuin','Lennik','Éghezée','Aubel');
$collator = new Collator('en_US');
$collator->sort($array);
print_r($array);

输出:

Array
(
    [0] => Aubel
    [1] => Borgloon
    [2] => Éghezée
    [3] => Lennik
    [4] => Thuin
)

我该如何将其按相反的顺序排序? - RandomNoobName
只需在结果上使用array_reverse函数即可。 - Renrhaf
按键排序:uksort($array, static fn($a, $b) => $collator->compare($a, $b)); - Radek Pech

12
我觉得你可以使用strcoll
setlocale(LC_COLLATE, 'nl_BE.utf8');
$array = array('Borgloon','Thuin','Lennik','Éghezée','Aubel');
usort($array, 'strcoll'); 
print_r($array);

结果:

Array
(
    [0] => Aubel
    [1] => Borgloon
    [2] => Éghezée
    [3] => Lennik
    [4] => Thuin
)

您的系统需要安装nl_BE.utf8区域设置。
fy@Heisenberg:~$ locale -a | grep nl_BE.utf8
nl_BE.utf8

如果你正在使用Debian,你可以使用dpkg --reconfigure locales来添加语言环境。

1
泰国人提供的 PHP 5.3 解决方案 似乎也很干净。 - Fy-
2
strcoll在Windows上与utf-8不兼容,这是由于CRT虚假实现所致。 - Enyby

8

这个脚本应该以一种自定义的方式解决问题。希望它能帮到你。请注意mb_strtolower函数。你需要使用它来使函数对大小写不敏感。我没有使用strtolower函数的原因是它在处理特殊字符时效果不佳。

<?php

function customSort($a, $b) {
    static $charOrder = array('a', 'b', 'c', 'd', 'e', 'é',
                              'f', 'g', 'h', 'i', 'j',
                              'k', 'l', 'm', 'n', 'o',
                              'p', 'q', 'r', 's', 't',
                              'u', 'v', 'w', 'x', 'y', 'z');

    $a = mb_strtolower($a);
    $b = mb_strtolower($b);

    for($i=0;$i<mb_strlen($a) && $i<mb_strlen($b);$i++) {
        $chA = mb_substr($a, $i, 1);
        $chB = mb_substr($b, $i, 1);
        $valA = array_search($chA, $charOrder);
        $valB = array_search($chB, $charOrder);
        if($valA == $valB) continue;
        if($valA > $valB) return 1;
        return -1;
    }

    if(mb_strlen($a) == mb_strlen($b)) return 0;
    if(mb_strlen($a) > mb_strlen($b))  return -1;
    return 1;

}
$array = array('Borgloon','Thuin','Lennik','Éghezée','Aubel');
usort($array, 'customSort');

编辑:抱歉,上一个代码中有很多错误。现在已经经过测试。

编辑 {2}:所有多字节函数都已更新。


很遗憾,这样做行不通,因为$a[$i]将从字符串中返回一个字节而不是一个字符。 - Leonid Shevtsov
之前,是的,你是对的。我几分钟前改变了算法。使用str_split会起作用。 - Jaison Erick
1
str_split 对于多字节字符串的处理并不好。 :) 可以参考 http://www.php.net/manual/en/function.mb-split.php#99851 - Leonid Shevtsov
1
不要频繁运行strlen函数,你只需要在前面运行一次它们,就可以获得两者的最小值。 - hakre

6

如果您想使用本地解决方案,我可以提出以下建议

function compare($a, $b)
{
        $alphabet = 'aąbcćdeęfghijklłmnnoóqprstuvwxyzźż'; // i used polish letters
        $a = mb_strtolower($a);
        $b = mb_strtolower($b);

        for ($i = 0; $i < mb_strlen($a); $i++) {
            if (mb_substr($a, $i, 1) == mb_substr($b, $i, 1)) {
                continue;
            }
            if ($i > mb_strlen($b)) {
                return 1;
            }
            if (mb_strpos($alphabet, mb_substr($a, $i, 1)) > mb_strpos($alphabet, mb_substr($b, $i, 1))) {
                return 1;
            } else {
                return -1;
            }
        }
}

usort($needed_array, 'compare');

我不确定这是最好的解决方案,但对我有效 =)


不错,太棒了。 - Eir
与php 7和新操作符“spaceship”相关的小更新。您可以在最后一个条件中使用<=>返回1或-1。 - Amir Djaminov
你缺少 ś 字符,应该这样写:$alphabet = 'aąbcćdeęfghijklłmnnoóqprsśtuvwxyzźż';如果你想保留数组键值,只需使用 uksort 函数。 - Bartłomiej Jakub Kwiatek

2

关于 strcoll,我想这是个好主意,但似乎并不起作用:

<?php

// Some 
$strings = array('Alpha', 'Älpha', 'Bravo');
// make it German: A, Ä, B
setlocale(LC_COLLATE, 'de_DE.UTF8', 'de.UTF8', 'de_DE.UTF-8', 'de.UTF-8');
usort($strings, 'strcoll');
var_dump($strings);
// as you can see, Ä is last, so this didn't work

不久前,我写了一个UTF-8到ASCII的工具,可以将“älph#bla”转换为“aelph-bla”。您可以使用它来“规范化”输入以进行排序。这基本上是一种替换,类似于@Nick所说的。

您应该使用单独的数组进行排序,因为在usort()回调中调用urlify()将浪费大量资源。请尝试。

<?php
// data to sort
$array = array('Borgloon','Thuin','Lennik','Éghezée','Aubel');
// container for modified strings
$_array = array();
foreach ($array as $k => $v) {
    // "normalize" utf8 to ascii
    $_array[$k] = urlify($v);
}
// sort the ASCII stuff (while preserving indexes)
asort($_array);
foreach ($_array as $key => &$v) {
    // copy the original value of the ASCIIfied element
    $v = $array[$k];
}
var_dump($_array);

如果您有PHP5.3或者intl PECL编译,可以尝试@Thai的解决方案,看起来很不错!

2

这里有很好的答案,但对于大多数情况来说,这是一个非常简单的解决方案。

function globalsort($array, $in = 'UTF-8', $out = 'ASCII//TRANSLIT//IGNORE')
{
    return usort($array, function ($a, $b) use ($in, $out) {
        $a = @iconv($in, $out, $a);
        $b = @iconv($in, $out, $b);
        return strnatcasecmp($a, $b);
    });
}

然后像这样使用:

globalsort($array);

这对我来说不起作用,如字母Ö,应该在O之后排序。 - ESP32

1
我会尝试循环遍历数组,在排序之前将其转换为英文字符。例如:
<?php
  $array = array('Borgloon','Thuin','Lennik','Éghezée','Aubel');

  setlocale(LC_CTYPE, 'nl_BE.utf8');

  $newarray = array();
  foreach($array as $k => $v) {
    $newarray[$k] = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $v);
  }

  sort($newarray);
  print_r($newarray);
?>

在处理速度和资源使用方面可能不是最佳选择。但肯定可以使代码更易于理解。

编辑:

现在考虑起来,您可能最好使用某种查找表,类似于这样:

<?php
  $accentedCharacters = array ( 'à', 'á', 'â', 'ã', 'ä', 'å', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', 'Š', 'Ž', 'š', 'ž', 'Ÿ', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý' ); 

  $replacementCharacters = array ( 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'y', 'y', 'S', 'Z', 's', 'z', 'Y', 'A', 'A', 'A', 'A', 'A', 'A', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y' );

  $array = array('Borgloon','Thuin','Lennik','Éghezée','Aubel');

  $newarray = array();
  foreach($array as $k => $v) {
    $newarray[$k] = str_replace($accentedCharacters,$replacementCharacters,$v);
  }

  sort($newarray);
  print_r($newarray);
?>

你为什么建议使用 nl_BE?(比利时荷兰语) - middus
说实话,考虑到数据集,这是我首先想到的可行地区。现在想想,如果数据集将使用其他异常字符,他最好使用转换查找表。 - Nick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接