如何在Python中按字母顺序排序unicode字符串?

113

Python默认按字节值排序,这意味着é会排在z和其他同样有趣的字符之后。在Python中按字母顺序排序的最佳方法是什么?

是否有适用于此的库?我找不到任何东西。最好具备语言支持,以便它能够理解在瑞典语中,åäö应该在z之后排序,但ü应该按u排序等。因此,Unicode支持几乎是必需的。

如果没有库可用,那么最佳方法是什么?只需从字母到整数值的映射,并使用它将字符串映射到整数列表中吗?


12
请注意,这个问题的答案与语境密切相关:如您所述,瑞典语中字母 "Ä" 排在字母 "Z" 之后,但是在德语中,通常将字母 "Ä" 排序为 "AE"。 - balpha
@Georg:你在这个问题上开悬赏的原因是什么?当你需要使用用户语言环境进行Unicode排序时,locale.strcoll答案是正确的;当你需要更多时(使用多种区域设置进行排序),ICU答案是正确的。但大多数情况下,你都需要使用locale.strcoll - Glenn Maynard
@Glenn:我想知道 locale.strcoll 的工作效果如何,尤其是 ICU 在哪些方面比 Python 函数做得更好。基本上就是希望这个问题能够得到更多的关注。 - Georg Schölly
1
@Georg:最近我一直在玩Unicode排序算法,正如你从我的回答中看到的那样。例如,当你需要时,能够按--locale=de__phonebook进行排序是非常好的。Perl模块通过了UCA测试套件,而我提供的脚本使得从命令行轻松玩转整个UCA及其所有选项,包括语言环境变得更加容易。虽然它可能不能回答这个问题,但它仍然非常有趣。如果你在瑞士,我相信你会喜欢这种灵活性的。 :) - tchrist
11个回答

86

IBM的ICU库可以实现这个功能(以及更多)。它有Python绑定:PyICU

更新: ICU和locale.strcoll之间排序的核心差异在于,ICU使用完整的Unicode排序算法,而strcoll使用ISO 14651

这两种算法之间的区别在此简要概述。这些都是相当奇特的特殊情况,在实践中很少会有影响。

>>> import icu # pip install PyICU
>>> sorted(['a','b','c','ä'])
['a', 'b', 'c', 'ä']
>>> collator = icu.Collator.createInstance(icu.Locale('de_DE.UTF-8'))
>>> sorted(['a','b','c','ä'], key=collator.getSortKey)
['a', 'ä', 'b', 'c']

这对Python 2和Python 3适用吗?我使用了u0b34a0f6ae的答案中的locale.strxfrm,它似乎可以工作,并且更加优雅,不需要任何额外的软件。 - sup
1
对我来说无法在Python3中工作,sudo pip3 install PyICU安装失败,Python2也是如此。 - imrek
我必须安装libicu-devel.x86_64才能从Pip编译和安装pyICU。虽然最后一个“sorted”命令的输出是:['a','\ xc3 \ xa4','b','c'],但它可以正常工作。 - Mike Stoddart
你需要安装libicu-devel和build-essentials(适用于Debian系统),以便能够成功使用pip安装"pyicu"。这是因为"pyicu"在PyPI上只提供源代码分发(tarballs),所以在安装时需要将其编译成wheel文件。 - Pablo Alexis Domínguez Grau

59

我在回答中没有看到这个内容。我的应用程序使用Python标准库根据区域设置进行排序。这很容易。

# python2.5 code below
# corpus is our unicode() strings collection as a list
corpus = [u"Art", u"Älg", u"Ved", u"Wasa"]

import locale
# this reads the environment and inits the right locale
locale.setlocale(locale.LC_ALL, "")
# alternatively, (but it's bad to hardcode)
# locale.setlocale(locale.LC_ALL, "sv_SE.UTF-8")

corpus.sort(cmp=locale.strcoll)

# in python2.x, locale.strxfrm is broken and does not work for unicode strings
# in python3.x however:
# corpus.sort(key=locale.strxfrm)
Lennart和其他回答者,有没有人知道“locale”,或者它不能胜任这个任务?

顺便说一句,我不认为locale.strxfrm对于UTF-8编码的str有问题;我对我的应用程序进行了基准测试,并得出结论,在Unicode对象上使用cmp=strcoll比将所有内容解码为UTF-8并使用key=strxfrm更便宜。 - u0b34a0f6ae
6
顺便提一下,2) locale模块只能与生成的本地语言环境(对于Linux系统)配合使用,而不能使用任意的本地语言环境。通过命令"locale -a",可以查看可用的本地语言环境。 - u0b34a0f6ae
6
@Georg: 我相信,locale 只支持简单的子字符串->排序元素映射,它不能处理像扩展(将 "æ" 排序为 "ae")、法语重音排序(字母从左到右排序,但重音从右到左)和重新排列等一些其他功能。有关完整的 UCA 功能集,请参见此处:http://unicode.org/reports/tr10/,有关 locale 排序,请参见此处:http://www.chm.tu-dresden.de/edv/manuals/aix/files/aixfiles/LC_COLLATE.htm。 - Rafał Dowgird
3
很清楚地回答这个问题:是的,它能够胜任。显然,完整的Unicode排序算法在一些特殊情况下处理得更好,但除非你已经知道,否则你可能不会注意到。 - Lennart Regebro
3
这里最大的问题是:你必须为整个应用程序全局设置语言环境,而不能只针对手头的比较进行设置。 - Robert Siemer
显示剩余9条评论

10

你可能也对pyuca感兴趣:

http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/

虽然这肯定不是最精确的方法,但至少它是一个非常简单的方法,可以让它变得更为准确。在Web应用程序中,它也比区域设置好用,因为区域设置不是线程安全的,并且将语言设置进程范围内。相比于依赖外部C库的PyICU,它也更易于设置。

我将脚本上传到了github,因为原始版本正在撰写时下线,所以我不得不使用Web缓存获取它:

https://github.com/href/Python-Unicode-Collation-Algorithm

我成功地使用这个脚本在一个plone模块中对德语/法语/意大利语文本进行了合理的排序。


+1 for pyuca。它非常快(在3秒内对28000个单词进行排序),纯Python编写,无需依赖。 - michaelmeyer

10

至少这解决了通用问题。我猜针对语言的排序列表版本也可以创建。 - Lennart Regebro
这并不允许您指定区域设置,而参考配置文件会导致ValueError错误。 - thebjorn

8

总结与扩展回答:

在Python 2中,locale.strcolllocale.strxfrm实际上可以解决这个问题,并且做得非常好,假设您已安装所需的语言环境。我也在Windows下进行了测试,在那里语言环境的名称令人困惑,但另一方面它似乎默认安装了所有支持的语言环境。

ICU在实践中不一定做得更好,但它确实做了很多其他事情。最明显的是,它支持将文本拆分成单词并支持不同语言的拆分器。这对于没有词分隔符的语言非常有用。您需要有一个单词库作为拆分的基础,因为它不包括在内。

它还具有语言环境的长名称,因此您可以获得漂亮的显示名称,支持除公历以外的其他日历(尽管我不确定Python界面是否支持),以及大量的其他更或者说不太常见的区域设置功能。

所以总之:如果您想按字母顺序排序并依赖于语言环境,则可以使用locale模块,除非您有特殊要求,或者还需要更多的与语言环境相关的功能,例如单词拆分器。


7

我看到回答已经做得很好了,只想指出Human Sort中的一种编码效率问题。为了对一个Unicode字符串s应用逐字符选择性翻译,它使用以下代码:

spec_dict = {'Å':'A', 'Ä':'A'}

def spec_order(s):
    return ''.join([spec_dict.get(ch, ch) for ch in s])

Python有更好、更快、更简洁的方法来执行这个辅助任务(对于Unicode字符串而言——对于字节串,类似的方法具有不同且不太有用的规范!):

spec_dict = dict((ord(k), spec_dict[k]) for k in spec_dict)

def spec_order(s):
    return s.translate(spec_dict)

您传递给translate方法的字典具有Unicode序数(而不是字符串)作为键,这就是为什么我们需要从原始字符到字符的spec_dict进行重建的原因。 (您传递给翻译的字典中的值[与必须是序数的键相反]可以是Unicode序数,任意Unicode字符串或None以删除相应字符作为翻译的一部分,因此很容易指定“忽略某些字符进行排序”,“将ä映射到ae以进行排序”等)。

在Python 3中,您可以更简单地获得“重建”步骤,例如:

spec_dict = ''.maketrans(spec_dict)

请参阅Python 3中使用此maketrans静态方法的其他方法,请查看文档


这个方法很好,但不允许你在az和b之间放置á。 - Barney Szabolcs

1

完整的UCA解决方案

最简单、最容易、最直接的方法是调用Perl库模块Unicode::Collate::Locale,它是标准Unicode::Collate模块的子类。你只需要将语言环境值设置为"xv"即可,适用于瑞典。

(对于瑞典文本,您可能不会特别欣赏此功能,但由于Perl使用抽象字符,因此您可以使用任何Unicode代码点,无论平台或构建方式如何!很少有语言提供这样的便利。我提到这一点是因为最近我一直在与Java为这个令人发狂的问题而斗争。)

问题在于我不知道如何从Python中访问Perl模块,除了使用shell调用或双向管道。为此,我已经提供了一个名为ucsort的完整工作脚本,您可以调用它以轻松实现您所要求的功能。

该脚本完全符合Unicode排序算法,支持所有定制选项!!如果您安装了可选模块或运行Perl 5.13或更高版本,则可以完全访问易于使用的CLDR语言环境。请参见下文。

演示

想象一组按此方式排序的输入:

b o i j n l m å y e v s k h d f g t ö r x p z a ä c u q

按代码点的默认排序结果如下:

a b c d e f g h i j k l m n o p q r s t u v x y z ä å ö

这在所有人看来都是不正确的。使用我的脚本,它使用Unicode排序算法,您会得到以下顺序:

% perl ucsort /tmp/swedish_alphabet | fmt
a å ä b c d e f g h i j k l m n o ö p q r s t u v x y z

这是默认的UCA排序。要获取瑞典语环境,请以以下方式调用ucsort

% perl ucsort --locale=sv /tmp/swedish_alphabet | fmt
a b c d e f g h i j k l m n o p q r s t u v x y z å ä ö

这里是一个更好的输入演示。首先,是输入集合:

% fmt /tmp/swedish_set
cTD cDD Cöd Cbd cAD cCD cYD Cud cZD Cod cBD Cnd cQD cFD Ced Cfd cOD
cLD cXD Cid Cpd cID Cgd cVD cMD cÅD cGD Cqd Cäd cJD Cdd Ckd cÖD cÄD
Ctd Czd Cxd cHD cND cKD Cvd Chd Cyd cUD Cld Cmd cED Crd Cad Cåd Ccd
cRD cSD Csd Cjd cPD

按代码点排序,如下所示:

Cad Cbd Ccd Cdd Ced Cfd Cgd Chd Cid Cjd Ckd Cld Cmd Cnd Cod Cpd Cqd
Crd Csd Ctd Cud Cvd Cxd Cyd Czd Cäd Cåd Cöd cAD cBD cCD cDD cED cFD
cGD cHD cID cJD cKD cLD cMD cND cOD cPD cQD cRD cSD cTD cUD cVD cXD
cYD cZD cÄD cÅD cÖD

但使用默认的UCA会按照以下方式进行排序:

% ucsort /tmp/swedish_set | fmt
cAD Cad cÅD Cåd cÄD Cäd cBD Cbd cCD Ccd cDD Cdd cED Ced cFD Cfd cGD
Cgd cHD Chd cID Cid cJD Cjd cKD Ckd cLD Cld cMD Cmd cND Cnd cOD Cod
cÖD Cöd cPD Cpd cQD Cqd cRD Crd cSD Csd cTD Ctd cUD Cud cVD Cvd cXD
Cxd cYD Cyd cZD Czd

但在瑞典语环境中,应该这样:

% ucsort --locale=sv /tmp/swedish_set | fmt
cAD Cad cBD Cbd cCD Ccd cDD Cdd cED Ced cFD Cfd cGD Cgd cHD Chd cID
Cid cJD Cjd cKD Ckd cLD Cld cMD Cmd cND Cnd cOD Cod cPD Cpd cQD Cqd
cRD Crd cSD Csd cTD Ctd cUD Cud cVD Cvd cXD Cxd cYD Cyd cZD Czd cÅD
Cåd cÄD Cäd cÖD Cöd

如果您希望大写字母排在小写字母之前,请执行以下操作:
% ucsort --upper-before-lower --locale=sv /tmp/swedish_set | fmt
Cad cAD Cbd cBD Ccd cCD Cdd cDD Ced cED Cfd cFD Cgd cGD Chd cHD Cid
cID Cjd cJD Ckd cKD Cld cLD Cmd cMD Cnd cND Cod cOD Cpd cPD Cqd cQD
Crd cRD Csd cSD Ctd cTD Cud cUD Cvd cVD Cxd cXD Cyd cYD Czd cZD Cåd
cÅD Cäd cÄD Cöd cÖD

自定义排序

你可以用ucsort来做许多其他的事情。例如,以下是如何按照英文标题进行排序:

% ucsort --preprocess='s/^(an?|the)\s+//i' /tmp/titles
Anathem
The Book of Skulls
A Civil Campaign
The Claw of the Conciliator
The Demolished Man
Dune
An Early Dawn
The Faded Sun: Kesrith
The Fall of Hyperion
A Feast for Crows
Flowers for Algernon
The Forbidden Tower
Foundation and Empire
Foundation’s Edge
The Goblin Reservation
The High Crusade
Jack of Shadows
The Man in the High Castle
The Ringworld Engineers
The Robots of Dawn
A Storm of Swords
Stranger in a Strange Land
There Will Be Time
The White Dragon

一般情况下,您需要Perl 5.10.1或更高版本才能运行脚本。为了支持本地化,您必须安装可选的CPAN模块Unicode::Collate::Locale。或者,您可以安装Perl的开发版本5.13+,该版本标准地包括该模块。

调用约定

这是一个快速原型,因此ucsort大多数情况下都没有文档记录。但以下是它在命令行上接受的开关/选项的概要:

    # standard options
    --help|?
    --man|m
    --debug|d

    # collator constructor options
    --backwards-levels=i
    --collation-level|level|l=i
    --katakana-before-hiragana
    --normalization|n=s
    --override-CJK=s
    --override-Hangul=s
    --preprocess|P=s
    --upper-before-lower|u
    --variable=s

    # program specific options
    --case-insensitive|insensitive|i
    --input-encoding|e=s
    --locale|L=s
    --paragraph|p
    --reverse-fields|last
    --reverse-output|r
    --right-to-left|reverse-input

好的,没问题:这确实是我用于调用Getopt::Long的参数列表,但你明白我的意思。:)

如果您能够找出如何直接从Python调用Perl库模块而不调用Perl脚本,那就请这样做。我自己不知道怎么做。我很想学习。

同时,我相信这个脚本将以其所有特定之处完成您需要完成的工作 - 甚至更多! 我现在使用它来进行所有文本排序。它终于做到了我长期以来所需的功能。

唯一的缺点是--locale参数会导致性能下降,尽管对于常规的非区域设置但仍然100% UCA兼容排序来说已经足够快了。由于它会将所有内容加载到内存中,因此您可能不想在千兆字节的文档上使用它。我每天使用它很多次,拥有理智的文本排序真是太棒了。


2
你为什么要调用Perl脚本去做一些Python库已经可以完成的事情呢? - Lennart Regebro
2
因为我不知道有一个Python库,这就是为什么! - tchrist
@Lennart:我真的更喜欢本地库,或者最多只链接到C API并动态加载(有时您需要这样做)。 我没有发现各种PyPerl和Inline :: Perl解决方案非常令人信服,稳健或灵活。 或其他什么。 由于某些原因,它们感觉不对。 我上次尝试这个是当我需要良好的字符集检测时(可惜我从未得到过)。 - tchrist
4
在Python中使用Perl就像上瘾一样。 - Utku Zihnioglu
1
哇。是的 - 看起来像是Perl,事实上我们可以看到现在有超过两种方法来做事情 :) 但是从Python调用C通常不意味着调用Perl所需的那些额外依赖和实际支持问题,因此很难看到这样做的必要性。 - nealmcb
显示剩余7条评论

1

最近我一直在使用zope.ucol(https://pypi.python.org/pypi/zope.ucol)来处理这个任务。例如,对德语字母ß进行排序:

>>> import zope.ucol
>>> collator = zope.ucol.Collator("de-de")
>>> mylist = [u"a", u'x', u'\u00DF']
>>> print mylist
[u'a', u'x', u'\xdf']
>>> print sorted(mylist, key=collator.key)
[u'a', u'\xdf', u'x']

zope.ucol 也包装了 ICU,因此可以作为 PyICU 的替代选择。


1

0

Jeff Atwood在自然排序方面写了一篇好文章,在其中他链接到了一个脚本,可以做几乎你所要求的一切

这不是一个简单的脚本,但它确实能够解决问题。


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