Wie sortiere ich Unicode-Zeichenfolgen in Python alphabetisch?

97

Python sortiert standardmäßig nach Byte-Wert, was bedeutet, dass é nach z und anderen ebenso lustigen Dingen kommt. Was ist der beste Weg, um in Python alphabetisch zu sortieren?

Gibt es dafür eine Bibliothek? Ich konnte nichts finden. Das Sortieren sollte vorzugsweise eine Sprachunterstützung haben, damit verstanden wird, dass åäö auf Schwedisch nach z sortiert werden sollte, ü jedoch nach u usw. sortiert werden sollte. Die Unicode-Unterstützung ist daher so ziemlich eine Voraussetzung.

Wenn es keine Bibliothek dafür gibt, wie geht das am besten? Machen Sie einfach eine Zuordnung von einem Buchstaben zu einem ganzzahligen Wert und ordnen Sie die Zeichenfolge damit einer ganzzahligen Liste zu?

Lennart Regebro
quelle
11
Beachten Sie, dass dies noch stärker vom Gebietsschema abhängt: Auf Schwedisch (wie Sie angeben) folgt "Ä" nach "Z", auf Deutsch wird "Ä" normalerweise als "AE" sortiert.
Balpha
@Georg: Gab es einen Grund, warum Sie ein Kopfgeld dafür eröffnet haben? Die locale.strcollAntwort ist richtig, wenn Sie eine Unicode-Sortierung nach dem Gebietsschema des Benutzers benötigen, und die Intensivstation antwortet, was Sie möchten, wenn Sie mehr als das benötigen (Sortierung mit mehr als einem Gebietsschema). Meistens willst du locale.strcoll.
Glenn Maynard
@Glenn: Ich wollte wissen, wie gut locale.strcollfunktioniert und was ICU besser macht als die Python-Funktion. Grundsätzlich etwas mehr Aufmerksamkeit für die Frage.
Georg Schölly
1
@Georg: Ich habe in letzter Zeit viel mit dem Unicode-Kollatierungsalgorithmus herumgespielt, wie Sie meiner Antwort entnehmen können. Es ist wirklich hervorragend, zum Beispiel sortieren zu können, --locale=de__phonebookwenn Sie es brauchen. Das Perl-Modul besteht die UCA-Testsuite, und das von mir bereitgestellte Skript erleichtert das Spielen mit der gesamten UCA und allen Optionen, einschließlich Gebietsschemas, nur über die Befehlszeile. Könnte die Frage nicht beantworten , sollte aber trotzdem sehr interessant sein. Wenn Sie in der Schweiz sind, können Sie sicher die Flexibilität nutzen. :)
tchrist

Antworten:

75

Die IBM ICU- Bibliothek macht das (und vieles mehr). Es hat Python-Bindungen: PyICU .

Update : Der Hauptunterschied bei der Sortierung zwischen der Intensivstation und der Intensivstation locale.strcollbesteht darin, dass die Intensivstation den vollständigen Unicode- Kollatierungsalgorithmusstrcoll verwendet, während ISO 14651 verwendet wird .

Die Unterschiede zwischen diesen beiden Algorithmen werden hier kurz zusammengefasst: http://unicode.org/faq/collation.html#13 . Dies sind eher exotische Sonderfälle, die in der Praxis selten eine Rolle spielen sollten.

>>> 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']
Rafał Dowgird
quelle
Funktioniert dies auch für Python 2 und Python 3? Ich habe locale.strxfrmaus der Antwort von u0b34a0f6ae verwendet und es scheint zu funktionieren und ist viel eleganter und erfordert keine zusätzliche Software.
sup
Funktioniert bei mir nicht mit Python3, lässt sich sudo pip3 install PyICUnicht installieren und Python2 auch nicht.
Imrek
Ich musste libicu-devel.x86_64 installieren, damit pyICU von Pip kompiliert und installiert werden konnte. Es funktioniert, obwohl die Ausgabe des letzten 'sortierten' Befehls lautet: ['a', '\ xc3 \ xa4', 'b', 'c']
Mike Stoddart
53

Ich sehe das nicht in den Antworten. Meine Anwendung sortiert nach dem Gebietsschema unter Verwendung der Python-Standardbibliothek. Es ist ziemlich einfach.

# 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)

Frage an Lennart und andere Antwortende: Kennt niemand das Gebietsschema oder liegt es nicht an dieser Aufgabe?

u0b34a0f6ae
quelle
Übrigens 1) Ich glaube nicht, dass locale.strxfrm für UTF-8-codiertes `str 'kaputt ist; Ich habe ein Benchmarking nach Anwendung durchgeführt und bin zu dem Schluss gekommen, dass die Verwendung von cmp = strcoll für Unicode-Objekte billiger ist als die Dekodierung aller Dateien in UTF-8 und die Verwendung von key = strxfrm
u0b34a0f6ae
6
Übrigens 2) Das Gebietsschemamodul funktioniert nur mit Ihren generierten Gebietsschemas (für eine Linux-Box), nicht mit einem beliebigen Gebietsschema. "locale -a" wird Ihnen sagen, welche
u0b34a0f6ae
6
@Georg: Ich glaube, dass das Gebietsschema nur eine einfache Zuordnung von Teilzeichenfolgen-> Kollatierungselementen unterstützt. Es behandelt keine Dinge wie Erweiterungen (æ sortiert als "ae"), französische Akzentsortierung (Buchstaben von links nach rechts sortiert, aber Akzente von rechts nach links), Neuanordnung und wahrscheinlich einige mehr. Details hier (vollständiger UCA-Funktionsumfang): unicode.org/reports/tr10 und hier (Gebietsschemasammlung): chm.tu-dresden.de/edv/manuals/aix/files/aixfiles/LC_COLLATE.htm
Rafał Dowgird
2
Um die Frage klar zu beantworten: Ja, es liegt an der Aufgabe. Es gibt anscheinend einige Sonderfälle, die der vollständige Unicode-Kollatierungsalgorithmus besser handhabt, aber wenn Sie nicht bereits wissen, dass die Chancen stehen, werden Sie es nicht bemerken.
Lennart Regebro
1
Das größte Problem hierbei ist: Sie müssen das Gebietsschema global für die gesamte Anwendung festlegen. - Sie können es nicht nur für den Vergleich zur Hand haben.
Robert Siemer
9

Probieren Sie den Python Unicode-Kollatierungsalgorithmus von James Tauber aus . Es kann nicht genau das tun, was Sie wollen, aber es scheint einen Blick wert zu sein. Weitere Informationen zu den Themen finden Sie in diesem Beitrag von Christopher Lenz.

Vinay Sajip
quelle
Das behebt zumindest das generische Problem. Ich denke, es könnten auch sprachempfindliche Versionen der Kollatierungsliste erstellt werden.
Lennart Regebro
Auf diese Weise können Sie kein Gebietsschema angeben, und die Referenzkonfigurationsdatei verursacht einen ValueError.
Thebjorn
8

Sie könnten auch an Pyuca interessiert sein :

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

Obwohl es sicherlich nicht der genaueste Weg ist, ist es ein sehr einfacher Weg, es zumindest etwas richtig zu machen. Es schlägt auch das Gebietsschema in einer Webanwendung, da das Gebietsschema nicht threadsicher ist und die Spracheinstellungen prozessweit festlegt. Es ist auch einfacher einzurichten als PyICU, das auf einer externen C-Bibliothek basiert.

Ich habe das Skript auf github hochgeladen, da das Original zum Zeitpunkt des Schreibens nicht verfügbar war, und ich musste auf Web-Caches zurückgreifen, um es zu erhalten:

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

Ich habe dieses Skript erfolgreich verwendet, um deutschen / französischen / italienischen Text in einem Plone-Modul zu sortieren.

href_
quelle
+1 für Pyuca. Es ist ziemlich schnell (3 Sekunden, um 28000 Wörter zu sortieren), ist reines Python und erfordert keine Abhängigkeit.
Michaelmeyer
7

Eine Zusammenfassung und erweiterte Antwort:

locale.strcollunter Python 2, und locale.strxfrmwird in der Tat das Problem lösen, und macht einen guten Job, vorausgesetzt, Sie haben das betreffende Gebietsschema installiert. Ich habe es auch unter Windows getestet, wo die Namen der Gebietsschemas verwirrend unterschiedlich sind, aber andererseits scheinen standardmäßig alle unterstützten Gebietsschemas installiert zu sein.

ICUmacht dies in der Praxis nicht unbedingt besser, macht aber viel mehr . Insbesondere werden Splitter unterstützt, mit denen Texte in verschiedenen Sprachen in Wörter aufgeteilt werden können. Dies ist sehr nützlich für Sprachen ohne Worttrennzeichen. Sie benötigen ein Korpus von Wörtern, um es als Grundlage für die Aufteilung zu verwenden, da dies jedoch nicht enthalten ist.

Es hat auch lange Namen für die Gebietsschemas, so dass Sie hübsche Anzeigenamen für das Gebietsschema erhalten, Unterstützung für andere Kalender als Gregorian (obwohl ich nicht sicher bin, ob die Python-Oberfläche dies unterstützt) und Tonnen von anderen mehr oder weniger obskuren Gebietsschemas .

Alles in allem: Wenn Sie alphabetisch und vom Gebietsschema abhängig sortieren möchten, können Sie das localeModul verwenden, es sei denn, Sie haben spezielle Anforderungen oder benötigen mehr vom Gebietsschema abhängige Funktionen wie den Wortsplitter.

Lennart Regebro
quelle
6

Ich sehe, dass die Antworten bereits hervorragende Arbeit geleistet haben, wollte nur auf eine Ineffizienz der Codierung in Human Sort hinweisen . Um eine selektive char-by-char-Übersetzung auf eine Unicode-Zeichenfolge anzuwenden, wird der folgende Code verwendet:

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

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

Python bietet eine viel bessere, schnellere und präzisere Möglichkeit, diese Hilfsaufgabe auszuführen (bei Unicode-Zeichenfolgen - die analoge Methode für Byte-Zeichenfolgen hat eine andere und etwas weniger hilfreiche Spezifikation! -):

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

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

Das Diktat, das Sie an die translateMethode übergeben, enthält Unicode-Ordnungszahlen (keine Zeichenfolgen) als Schlüssel. Deshalb benötigen wir diesen Wiederherstellungsschritt vom ursprünglichen Zeichen zu Zeichen spec_dict. (Werte in dem Diktat, das Sie zur Übersetzung übergeben [im Gegensatz zu Schlüsseln, bei denen es sich um Ordnungszahlen handeln muss], können Unicode-Ordnungszahlen, beliebige Unicode-Zeichenfolgen oder Keine sein, um das entsprechende Zeichen als Teil der Übersetzung zu entfernen. Daher ist es einfach, "Ignorieren a" anzugeben bestimmtes Zeichen für Sortierzwecke "," Zu ä für Sortierzwecke zuordnen "und dergleichen).

In Python 3 können Sie den Schritt "Wiederherstellen" einfacher ausführen, z.

spec_dict = ''.maketrans(spec_dict)

Sehen Sie die Dokumentation für andere Möglichkeiten , wie Sie diese verwenden können maketransstatische Methode in Python 3.

Alex Martelli
quelle
Diese Methode ist nett, erlaubt es Ihnen jedoch nicht, á zwischen az und b zu platzieren
Barney
1

In letzter Zeit habe ich zope.ucol ( https://pypi.python.org/pypi/zope.ucol ) für diese Aufgabe verwendet. Zum Beispiel sortieren Sie das deutsche ß:

>>> 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 verpackt auch die Intensivstation und wäre somit eine Alternative zur PyICU.

Brian Sutherland
quelle
1

Eine vollständige UCA-Lösung

Die einfachste, einfachste und einfachste Möglichkeit, dies zu tun, besteht darin, ein Callout für das Perl-Bibliotheksmodul Unicode :: Collate :: Locale zu erstellen , das eine Unterklasse des Standardmoduls Unicode :: Collate ist . Sie müssen dem Konstruktor lediglich einen Gebietsschemawert "xv"für Schweden übergeben.

(Sie werden dies vielleicht nicht unbedingt für schwedischen Text zu schätzen wissen, aber da Perl abstrakte Zeichen verwendet, können Sie jeden beliebigen Unicode-Codepunkt verwenden - unabhängig von der Plattform oder dem Build! Nur wenige Sprachen bieten einen solchen Komfort. Ich erwähne es, weil ich gegen a gekämpft habe Ich habe in letzter Zeit viel mit Java wegen dieses verrückten Problems verloren.)

Das Problem ist, dass ich nicht weiß, wie ich von Python aus auf ein Perl-Modul zugreifen soll - abgesehen von der Verwendung eines Shell-Callouts oder einer zweiseitigen Pipe. Zu diesem Zweck habe ich Ihnen daher ein vollständiges Arbeitsskript namens ucsort zur Verfügung gestellt , das Sie aufrufen können, um genau das zu tun, wonach Sie gefragt haben.

Dieses Skript ist zu 100% mit dem vollständigen Unicode-Kollatierungsalgorithmus kompatibel , wobei alle Anpassungsoptionen unterstützt werden !! Wenn Sie ein optionales Modul installiert haben oder Perl 5.13 oder besser ausführen, haben Sie vollen Zugriff auf benutzerfreundliche CLDR-Gebietsschemas. Siehe unten.

Demonstration

Stellen Sie sich einen Eingabesatz vor, der folgendermaßen angeordnet ist:

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

Eine Standard-Sortierung nach Codepunkt ergibt:

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 ä å ö

Das ist falsch in jedermanns Buch. Mit meinem Skript, das den Unicode-Kollatierungsalgorithmus verwendet, erhalten Sie folgende Reihenfolge:

% 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

Dies ist die Standard-UCA-Sortierung. Um das schwedische Gebietsschema zu erhalten, rufen Sie ucsort folgendermaßen auf :

% 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 å ä ö

Hier ist eine bessere Eingabedemo. Zunächst der Eingabesatz:

% 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

Nach Codepunkt sortiert das folgendermaßen:

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

Bei Verwendung der Standard-UCA wird dies jedoch folgendermaßen sortiert:

% 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

Aber im schwedischen Gebietsschema so:

% 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

Wenn Sie es vorziehen, Großbuchstaben vor Kleinbuchstaben zu sortieren, gehen Sie folgendermaßen vor:

% 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

Kundenspezifische Sorten

Mit ucsort können Sie viele andere Dinge tun . So sortieren Sie beispielsweise Titel auf Englisch:

% 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
Foundations 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

Sie benötigen Perl 5.10.1 oder besser, um das Skript im Allgemeinen auszuführen. Für die Unterstützung des Gebietsschemas müssen Sie entweder das optionale CPAN-Modul installieren Unicode::Collate::Locale. Alternativ können Sie eine Entwicklungsversion von Perl 5.13+ installieren, die dieses Modul standardmäßig enthält.

Konventionen aufrufen

Dies ist ein schneller Prototyp, daher ist ucsort größtenteils nicht (der) dokumentiert. Dies ist jedoch die ÜBERSICHT darüber, welche Schalter / Optionen in der Befehlszeile akzeptiert werden:

    # 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

Ja, ok: Das ist wirklich die Argumentliste, die ich für den Anruf verwende Getopt::Long, aber Sie haben die Idee. :) :)

Wenn Sie herausfinden können, wie Sie Perl-Bibliotheksmodule direkt aus Python aufrufen können, ohne ein Perl-Skript aufzurufen, tun Sie dies auf jeden Fall. Ich weiß nur nicht wie ich. Ich würde gerne lernen wie.

In der Zwischenzeit glaube ich, dass dieses Skript genau das tun wird, was Sie brauchen - und noch mehr! Ich benutze dies jetzt für die gesamte Textsortierung. Es macht endlich das, was ich für eine lange, lange Zeit gebraucht habe.

Der einzige Nachteil ist, dass das --localeArgument dazu führt, dass die Leistung in die Hose geht, obwohl es schnell genug für eine reguläre, nicht lokale, aber dennoch 100% UCA-konforme Sortierung ist. Da alles in den Speicher geladen wird, möchten Sie dies wahrscheinlich nicht für Gigabyte-Dokumente verwenden. Ich benutze es oft am Tag und es ist sicher großartig, endlich eine vernünftige Textsortierung zu haben.

tchrist
quelle
2
Warum um alles in der Welt würden Sie ein Perl-Skript aufrufen, um etwas zu tun, für das es Python-Bibliotheken gibt?
Lennart Regebro
2
Da ich nicht wusste, dass es eine Python-Bibliothek gibt, ist das der Grund!
Tchrist
@ Lennart: Ich bevorzuge wirklich native Bibliotheken oder höchstens solche, die mit einer C-API verknüpft und dynamisch geladen sind (was manchmal erforderlich ist). Ich fand die verschiedenen PyPerl- und Inline :: Perl-Lösungen nicht sehr überzeugend, robust oder flexibel. Oder so. Sie fühlen sich aus bestimmten Gründen einfach nicht richtig. Ich habe es zuletzt versucht, als ich eine gute Zeichensatzerkennung brauchte (die ich leider nie bekommen habe).
Tchrist
4
Die Verwendung von Perl in Python ist nur Sucht.
Utku Zihnioglu
1
Beeindruckend. Ja - sieht für mich wie Perl aus, tatsächlich sehen wir, dass es jetzt mehr als zwei Möglichkeiten gibt, Dinge zu tun :) Aber das Aufrufen von C aus Python impliziert im Allgemeinen nicht die Art von zusätzlichen Abhängigkeiten und praktischen Supportproblemen, die das Aufrufen von Perl verursachen würde Es ist furchtbar schwer zu sehen, wie man es so macht.
Nealmcb
0

Es ist weit von einer vollständigen Lösung für Ihren Anwendungsfall, aber man kann einen Blick auf das nehmen unaccent.py Skript von effbot.org. Grundsätzlich werden alle Akzente aus einem Text entfernt. Sie können diesen "bereinigten" Text verwenden, um alphabetisch zu sortieren. (Eine bessere Beschreibung finden Sie auf dieser Seite.)

Mark van Lent
quelle