Python sorts by byte value by default, which means é comes after z and other equally funny things. What is the best way to sort alphabetically in Python?
Is there a library for this? I couldn't find anything. Preferrably sorting should have language support so it understands that åäö should be sorted after z in Swedish, but that ü should be sorted by u, etc. Unicode support is thereby pretty much a requirement.
If there is no library for it, what is the best way to do this? Just make a mapping from letter to a integer value and map the string to a integer list with that?
IBM's ICU library does that (and a lot more). It has Python bindings: PyICU.
Update: The core difference in sorting between ICU and
locale.strcoll
is that ICU uses the full Unicode Collation Algorithm whilestrcoll
uses ISO 14651.The differences between those two algorithms are briefly summarized here: http://unicode.org/faq/collation.html#13. These are rather exotic special cases, which should rarely matter in practice.
Jeff Atwood wrote a good post on Natural Sort Order, in it he linked to a script which does pretty much what you ask.
It's not a trivial script, by any means, but it does the trick.
To implement it you will need to read about "Unicode collation algorithm" see http://en.wikipedia.org/wiki/Unicode_collation_algorithm
http://www.unicode.org/unicode/reports/tr10/
a sample implementation is here
http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/
A summary and extended answer:
locale.strcoll
under Python 2, andlocale.strxfrm
will in fact solve the problem, and does a good job, assuming that you have the locale in question installed. I tested it under Windows too, where the locale names confusingly are different, but on the other hand it seems to have all locales that are supported installed by default.ICU
doesn't necessarily do this better in practice, it however does way more. Most notably it has support for splitters that can split texts in different languages into words. This is very useful for languages that doesn't have word separators. You'll need to have a corpus of words to use as a base for the splitting, because that's not included, though.It also has long names for the locales so you can get pretty display names for the locale, support for other calendars than Gregorian (although I'm not sure the Python interface supports that) and tons and tons of other more or less obscure locale supports.
So all in all: If you want to sort alphabetically and locale-dependent, you can use the
locale
module, unless you have special requirements, or also need more locale dependent functionality, like words splitter.It is far from a complete solution for your use case, but you could take a look at the unaccent.py script from effbot.org. What it basically does is remove all accents from a text. You can use that 'sanitized' text to sort alphabetically. (For a better description see this page.)
A Complete UCA Solution
The simplest, easiest, and most straightforward way to do this it to make a callout to the Perl library module, Unicode::Collate::Locale, which is a subclass of the standard Unicode::Collate module. All you need do is pass the constructor a locale value of
"xv"
for Sweden.(You may not neccesarily appreciate this for Swedish text, but because Perl uses abstract characters, you can use any Unicode code point you please — no matter the platform or build! Few languages offer such convenience. I mention it because I’ve fighting a losing battle with Java a lot over this maddening problem lately.)
The problem is that I do not know how to access a Perl module from Python — apart, that is, from using a shell callout or two-sided pipe. To that end, I have therefore provided you with a complete working script called ucsort that you can call to do exactly what you have asked for with perfect ease.
This script is 100% compliant with the full Unicode Collation Algorithm, with all tailoring options supported!! And if you have an optional module installed or run Perl 5.13 or better, then you have full access to easy-to-use CLDR locales. See below.
Demonstration
Imagine an input set ordered this way:
A default sort by code point yields:
which is incorrect by everybody’s book. Using my script, which uses the Unicode Collation Algorithm, you get this order:
That is the default UCA sort. To get the Swedish locale, call ucsort this way:
Here is a better input demo. First, the input set:
By code point, that sorts this way:
But using the default UCA makes it sort this way:
But in the Swedish locale, this way:
If you prefer uppercase to sort before lowercase, do this:
Customized Sorts
You can do many other things with ucsort. For example, here is how to sort titles in English:
You will need Perl 5.10.1 or better to run the script in general. For locale support, you must either install the optional CPAN module
Unicode::Collate::Locale
. Alternately, you can install a development versions of Perl, 5.13+, which include that module standardly.Calling Conventions
This is a rapid prototype, so ucsort is mostly un(der)documented. But this is its SYNOPSIS of what switches/options it accepts on the command line:
Yeah, ok: that’s really the argument list I use for the call to
Getopt::Long
, but you get the idea. :)If you can figure out how to call Perl library modules from Python directly without calling a Perl script, by all means do so. I just don’t know how myself. I’d love to learn how.
In the meantime, I believe this script will do what you need done in all its particular — and more! I now use this for all of text sorting. It finally does what I’ve needed for a long, long time.
The only downside is that
--locale
argument causes performance to go down the tubes, although it’s plenty fast enough for regular, non-locale but still 100% UCA compliant sorting. Since it loads everything in memory, you probably don’t want to use this on gigabyte documents. I use it many times a day, and it sure it great having sane text sorting at last.