PHP中支持Unicode的自然排序算法?

Posted

技术标签:

【中文标题】PHP中支持Unicode的自然排序算法?【英文标题】:Natural sorting algorithm in PHP with support for Unicode? 【发布时间】:2010-10-24 09:07:04 【问题描述】:

是否可以使用自然顺序算法在 php 中使用 Unicode / UTF-8 字符对数组进行排序?例如(这个数组中的顺序是正确排序的):

$array = array
(
    0 => 'Agile',
    1 => 'Ágile',
    2 => 'Àgile',
    3 => 'Âgile',
    4 => 'Ägile',
    5 => 'Ãgile',
    6 => 'Test',
);

如果我尝试使用 asort($array) 我会得到以下结果:

Array
(
    [0] => Agile
    [6] => Test
    [2] => Àgile
    [1] => Ágile
    [3] => Âgile
    [5] => Ãgile
    [4] => Ägile
)

并使用 natsort($array):

Array
(
    [2] => Àgile
    [1] => Ágile
    [3] => Âgile
    [5] => Ãgile
    [4] => Ägile
    [0] => Agile
    [6] => Test
)

如何在 PHP 5 下实现返回正确结果顺序(0、1、2、3、4、5、6)的函数?我的系统上提供了所有多字节字符串函数(mbstring、iconv、...)。

编辑:我想 natsort() 值,而不是键 - 我明确定义键(并使用 asort() 而不是 sort())的唯一原因是为了简化找出 unicode 值排序出错的地方。

【问题讨论】:

【参考方案1】:
natsort($array);
$array = array_values($array);

【讨论】:

我的示例中的键不是问题,它们只是用来帮助对 unicode 值进行排序。【参考方案2】:

这个问题并不像第一眼看上去那么容易回答。这是 PHP 缺乏 unicode 支持的领域之一,这会让您全力以赴。

其他海报所建议的所有natsort() 中的第一个与您要排序的类型的排序数组无关。您正在寻找的是一种区域设置感知排序机制,因为使用扩展字符对字符串进行排序始终是所使用语言的问题。让我们以德语为例:A 和 Ä 有时可以被排序为好像它们是同一个字母(DIN 5007/1),有时 Ä 可以被排序,因为它实际上是“AE”(DIN 5007/2)。相比之下,在瑞典语中,Ä 出现在字母表的末尾。

如果您不使用 Windows,那么您很幸运,因为 PHP 提供了一些功能来实现这一点。结合使用 setlocale()usort()strcoll() 和适合您的语言的正确 UTF-8 语言环境,您会得到如下结果:

$array = array('Àgile', 'Ágile', 'Âgile', 'Ãgile', 'Ägile', 'Agile', 'Test');
$oldLocal = setlocale(LC_COLLATE, '<<your_RFC1766_language_code>>.utf8');
usort($array, 'strcoll');
setlocale(LC_COLLATE, $oldLocal);

请注意,必须使用 UTF-8 语言环境变体才能对 UTF-8 字符串进行排序。我将上面示例中的语言环境重置为其原始值,因为使用setlocale() 设置语言环境可能会在其他正在运行的 PHP 脚本中引入副作用 - 请参阅 PHP 手册了解更多详细信息。

当您使用 Windows 机器时,目前没有解决此问题的方法,而且我认为在 PHP 6 之前不会有任何解决方案。请参阅我自己的question,了解针对此特定问题的 SO。

【讨论】:

伟大的洞察力,我正在 Windows 上开发,但这将在 *nix 机器上运行。如果我没记错的话,PHP 5.3 将通过某种类支持这种排序,但是我想避免依赖 set_locale() 主要有两个原因:1)它是不可预测的(取决于操作系统可用的语言环境) 2) 它不是线程安全的,可能会导致服务器出现意外行为。 使用 ord() 函数的多字节版本进行排序,得到的结果与简单的 sort() 完全相同。 =( 关于您的第一条评论:您是绝对正确的,我的答案中提出的解决方案不是一个,人们可能会期望它既不便携也不没有副作用。但是:它是目前唯一的一个 - 除了使用例如 ext/mbstring 在字符和字节级别上实现您自己的排序。 关于我的第二条评论,我使用 mbstring 扩展来编写与原始 PHP ord() 函数等效的多字节代码,但它给我的结果与 sort() 函数相同。 是的,对 mysql 服务器上的数据进行排序是一种可行的工作方法。 MySQL 不受这些限制的影响。您可以通过为数据选择正确的排序来控制排序顺序。【参考方案3】:

成功了!

$array = array('Ägile', 'Ãgile', 'Test', 'カタカナ', 'かたかな', 'Ágile', 'Àgile', 'Âgile', 'Agile');

function Sortify($string)

    return preg_replace('~&([a-z]1,2)(acute|cedil|circ|grave|lig|orn|ring|slash|tilde|uml);~i', '$1' . chr(255) . '$2', htmlentities($string, ENT_QUOTES, 'UTF-8'));


array_multisort(array_map('Sortify', $array), $array);

输出:

Array
(
    [0] => Agile
    [1] => Ágile
    [2] => Âgile
    [3] => Àgile
    [4] => Ãgile
    [5] => Ägile
    [6] => Test
    [7] => かたかな
    [8] => カタカナ
)

更好:

if (extension_loaded('intl') === true)

    collator_asort(collator_create('root'), $array);

感谢@tchrist!

【讨论】:

听起来您真正需要的是 Unicode 排序算法 (UCA)。我有一个 in this answer 的 Perl 演示,我在其中为可能没有合适的库可调用的人提供了它的 shell 可调用版本。也许这在这里也可能有所帮助。 @tchrist:UCA 是我正在寻找的,稍后我会仔细查看您的答案,感谢您的提醒! ;)【参考方案4】:

我在这个问题上苦苦挣扎。

排序:

Array
(
    [xa] => África
    [xo] => Australasia
    [cn] => China
    [gb] => Reino Unido
    [us] => Estados Unidos
    [ae] => Emiratos Árabes Unidos
    [jp] => Japón
    [lk] => Sri Lanka
    [xe] => Europa Del Este
    [xw] => Europa Del Oeste
    [fr] => Francia
    [de] => Alemania
    [be] => Bélgica
    [nl] => Holanda
    [es] => España
)

把非洲放在最后。我用这段肮脏的小代码解决了这个问题(适合我的目的和时间框架):

$sort = array();
foreach($retval AS $key => $value) 
    $v = str_replace('ä', 'a', $value);
    $v = str_replace('Ä', 'A', $v);
    $v = str_replace('Á', 'A', $v);
    $v = str_replace('é', 'e', $v);
    $v = str_replace('ö', 'o', $v);
    $v = str_replace('ó', 'o', $v);
    $v = str_replace('Ö', 'O', $v);
    $v = str_replace('ü', 'u', $v);
    $v = str_replace('Ü', 'U', $v);
    $v = str_replace('ß', 'S', $v);
    $v = str_replace('ñ', 'n', $v);
    $sort[] = "$v|$key|$value";

sort($sort);

$retval = array();
foreach($sort AS $value) 
    $arr = explode('|', $value);
    $retval[$arr[1]] = $arr[2]; 

【讨论】:

你是法国人吗?您可能想查看我对这个问题的回答,我的 preg_replace 方法的音译效果更好,array_multisort 函数还保留了值和非数字键的关联。【参考方案5】:

对于那些 setlocale 不起作用并且没有启用 intl 模块的情况,我还有另一种解决方法:

// The array to be sorted
$countries = array(
  'AT' => Österreich,
  'DE' => Deutschland,
  'CH' => Schweiz,
);

// Extend this array to your needs.
$utf_sort_map = array(
  "ä" => "a",
  "Ä" => "A",
  "Å" => "A",
  "ö" => "o",
  "Ö" => "O",
  "ü" => "u",
  "Ü" => "U",
);

uasort($my_array, function($a, $b) use ($utf_sort_map) 
  $initial_a = mb_substr($a, 0, 1);
  $initial_b = mb_substr($b, 0, 1);

  if (isset($utf_sort_map[$initial_a]) || isset($utf_sort_map[$initial_b])) 
    if (isset($utf_sort_map[$initial_a])) 
      $initial_a = $utf_sort_map[$initial_a];
    

    if (isset($utf_sort_map[$initial_b])) 
      $initial_b = $utf_sort_map[$initial_b];
    

    if ($initial_a == $initial_b) 
      return mb_substr($a, 1) < mb_substr($b, 1) ? -1 : 1;
    
    else 
      return $initial_a < $initial_b ? -1 : 1;
    
  

  return $a < $b ? -1 : 1;
);

【讨论】:

以上是关于PHP中支持Unicode的自然排序算法?的主要内容,如果未能解决你的问题,请参考以下文章

自然排序算法

php实现快速排序算法

吴裕雄--天生自然数据结构:十大经典排序算法——堆排序

吴裕雄--天生自然数据结构:十大经典排序算法——希尔排序

php算法----直接插入排序

吴裕雄--天生自然数据结构:十大经典排序算法——计数排序