UCA + 自然排序

6

我最近了解到PHP已经通过intl扩展支持Unicode排序算法

$array = array
(
    'al', 'be',
    'Alpha', 'Beta',
    'Álpha', 'Àlpha', 'Älpha',
    'かたかな',
    'img10.png', 'img12.png',
    'img1.png', 'img2.png',
);

if (extension_loaded('intl') === true)
{
    collator_asort(collator_create('root'), $array);
}

Array
(
    [0] => al
    [2] => Alpha
    [4] => Álpha
    [5] => Àlpha
    [6] => Älpha
    [1] => be
    [3] => Beta
    [11] => img1.png
    [9] => img10.png
    [8] => img12.png
    [10] => img2.png
    [7] => かたかな
)

正如您所看到的,这似乎完美地工作,即使是混合大小写字符串!到目前为止,我遇到的唯一缺点是没有支持自然排序,我想知道如何最好地解决这个问题,以便我可以融合两个世界的优点。

我尝试指定Collator :: SORT_NUMERIC排序标志,但结果混乱不堪:

collator_asort(collator_create('root'), $array, Collator::SORT_NUMERIC);

Array
(
    [8] => img12.png
    [7] => かたかな
    [9] => img10.png
    [10] => img2.png
    [11] => img1.png
    [6] => Älpha
    [5] => Àlpha
    [1] => be
    [2] => Alpha
    [3] => Beta
    [4] => Álpha
    [0] => al
)

然而,如果我只使用img*.png的值运行相同的测试,我将获得理想的输出:

Array
(
    [3] => img1.png
    [2] => img2.png
    [1] => img10.png
    [0] => img12.png
)

有没有办法在增加自然排序功能的同时保留Unicode排序?

@dan04:能详细说明一下吗? - Alix Axel
使用字典序,固定宽度的十六进制数字将正确排序。例如:80、8A、90。而使用“自然排序”,则会得到8A、80、90的排序结果。 - dan04
@dan04:是啊,但是...这和主题无关吧?如果我必须选择,我宁愿在任何其他进制中使用自然十进制排序,而不是自然排序。尽管如此,“完美运作”的评论是指UCA而不是自然排序,因为显然intl中没有实现自然排序。 - Alix Axel
你必须做出选择。我的观点是,如果你实现“自然排序”,它将破坏一些在词典顺序下运行良好的排序方式。不仅仅是十六进制,还包括任何混合字母/数字标识符,如车牌号码或产品/客户ID。 - dan04
3个回答

5

在进一步查阅文档后,我找到了解决方案:

if (extension_loaded('intl') === true)
{
    if (is_object($collator = collator_create('root')) === true)
    {
        $collator->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON);
        $collator->asort($array);
    }
}

输出:

Array
(
    [0] => al
    [3] => Alpha
    [5] => Álpha
    [6] => Àlpha
    [7] => Älpha
    [1] => be
    [4] => Beta
    [10] => img1.png
    [11] => img2.png
    [8] => img10.png
    [9] => img12.png
    [2] => かたかな
)

1
这很简单。您只需对列表进行预处理以零填充数字即可。例如,使用支持UCA的我的ucsort脚本,对文件名列表进行操作:
% cat /tmp/numfiles
img4.png
img1.png
img2.png
img12.png
img21.png
img10.png
img20.png
img3.png
img22.png

通过使用Unicode::Collate模块--preprocess钩子将数字序列转换为零填充序列,将产生所需的输出:

% ucsort --preprocess='s/(\d+)/sprintf "%020d", $1/ge' /tmp/numfiles
img1.png
img2.png
img3.png
img4.png
img10.png
img12.png
img20.png
img21.png
img22.png

从您引用的PHP文档来看,似乎该PHP库不支持Perl Unicode::Collate模块所支持的完整UCA定制功能。实际上,它更像是Perl的Unicode::Collate::Locale模块,只是PHP库代码似乎不支持Perl代码所支持的继承排序选项。

我想,如果其他方法都失败了,您可以调用Perl代码来完成需要做的事情。


谢谢。我可以想到一种用PHP实现的方法,需要使用2个数组、uasort()collator_compare()和带有eval修饰符的preg_replace()函数,但这种方法看起来效率很低... =( - Alix Axel

0

基于@tchrist的答案,我得出了这个:

function sortIntl($array, $natural = true)
{
    $data = $array;

    if ($natural === true)
    {
        $data = preg_replace_callback('~([0-9]+)~', 'natsortIntl', $data);
    }

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

    return array_intersect_key($array, $data);
}

function natsortIntl($number)
{
    return sprintf('%020d', $number);
}

输出:

Array
(
    [0] => 1
    [1] => 100
    [2] => al
    [3] => be
    [4] => Alpha
    [5] => Beta
    [6] => Álpha
    [7] => Àlpha
    [8] => Älpha
    [9] => かたかな
    [10] => img1.png
    [11] => img2.png
    [12] => img10.png
    [13] => img20.png
)

虽然仍然希望有更好的解决方案。


PS:array_intersect_key产生了意外的结果,如索引9所示,因为原始数组的所有键也都丢失了。 - Alix Axel

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