Comment trier les chaînes Unicode par ordre alphabétique en Python?

98

Python trie par valeur d'octet par défaut, ce qui signifie que é vient après z et d'autres choses tout aussi amusantes. Quelle est la meilleure façon de trier par ordre alphabétique en Python?

Y a-t-il une bibliothèque pour cela? Je n'ai rien trouvé. De préférence, le tri doit prendre en charge la langue, de sorte qu'il comprend que åäö doit être trié après z en suédois, mais que ü doit être trié par u, etc.

S'il n'y a pas de bibliothèque pour cela, quelle est la meilleure façon de le faire? Faites simplement un mappage d'une lettre à une valeur entière et mappez la chaîne à une liste d'entiers avec ça?

Lennart Regebro
la source
11
Notez que cela dépend encore plus des paramètres régionaux: en suédois (comme vous le dites) "Ä" vient après "Z", mais en allemand, "Ä" est généralement trié comme "AE".
balpha
@Georg: Y avait-il une raison pour laquelle vous avez ouvert une prime à ce sujet? La locale.strcollréponse est correcte lorsque vous avez besoin d'un tri Unicode en utilisant les paramètres régionaux de l'utilisateur, et l'ICU répond à vos souhaits lorsque vous en avez besoin de plus (classement utilisant plusieurs paramètres régionaux). La plupart du temps, vous voulez locale.strcoll.
Glenn Maynard
@Glenn: Je voulais savoir comment locale.strcollfonctionne bien et surtout ce que ICU fait mieux que la fonction Python. Fondamentalement, un peu plus d'attention pour la question.
Georg Schölly
1
@Georg: J'ai beaucoup joué avec l'algorithme de classement Unicode ces derniers temps, comme vous pouvez le voir dans ma réponse. C'est vraiment excellent de pouvoir, par exemple, trier --locale=de__phonebookquand vous en avez besoin. Le module Perl réussit la suite de tests UCA, et le script que j'ai fourni rend beaucoup plus facile de jouer avec l'ensemble de l'UCA plus toutes ses options, y compris les paramètres régionaux, juste à partir de la ligne de commande. Peut-être ne pas répondre à la question, mais cela devrait tout de même être très intéressant. Si vous êtes en Suisse, je suis sûr que vous pouvez utiliser la flexibilité. :)
tchrist

Réponses:

75

La bibliothèque ICU d'IBM fait cela (et bien plus encore). Il a des liaisons Python: PyICU .

Mise à jour : La principale différence dans le tri entre ICU locale.strcollest que ICU utilise l' algorithme de classement Unicode complet tout en strcollutilisant ISO 14651 .

Les différences entre ces deux algorithmes sont brièvement résumées ici: http://unicode.org/faq/collation.html#13 . Ce sont des cas particuliers plutôt exotiques, qui devraient rarement avoir une importance dans la pratique.

>>> 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
la source
Cela fonctionne-t-il de la même manière pour Python 2 et Python 3? J'ai utilisé locale.strxfrmde la réponse par u0b34a0f6ae et il semble fonctionner et est beaucoup plus élégant et ne nécessite aucun logiciel supplémentaire.
sup
Ne fonctionne pas avec Python3 pour moi, sudo pip3 install PyICUne parvient pas à s'installer, tout comme Python2.
imrek
J'ai dû installer libicu-devel.x86_64 pour que pyICU compile et installe à partir de Pip. Cela fonctionne, bien que la sortie de la dernière commande 'triée' soit: ['a', '\ xc3 \ xa4', 'b', 'c']
Mike Stoddart
53

Je ne vois pas cela dans les réponses. Mon application trie en fonction de la locale en utilisant la bibliothèque standard de python. C'est assez simple.

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

Question à Lennart et aux autres répondants: Est-ce que personne ne connaît la «locale» ou n'est-ce pas à la hauteur de cette tâche?

u0b34a0f6ae
la source
Au fait 1) Je ne pense pas que locale.strxfrm soit cassé pour `str 'encodé en UTF-8; J'ai évalué par application et conclu que l'utilisation de cmp = strcoll sur des objets Unicode était moins chère que de tout décoder en UTF-8 et d'utiliser key = strxfrm
u0b34a0f6ae
6
Au fait 2) Le module locale ne fonctionnera qu'avec vos locales générées (pour une machine Linux), pas avec n'importe quelle locale arbitraire. "locale -a" vous dira lequel
u0b34a0f6ae
6
@Georg: Je crois que les paramètres régionaux ne prennent en charge qu'un simple mappage sous-chaîne-> collating_element. Il ne gère pas des choses comme les expansions (æ trié comme "ae"), le tri d'accent français (lettres triées de gauche à droite, mais les accents de droite à gauche), le réarrangement et probablement quelques autres. Détails ici (ensemble complet de fonctionnalités UCA): unicode.org/reports/tr10 et ici (classement des paramètres régionaux): chm.tu-dresden.de/edv/manuals/aix/files/aixfiles/LC_COLLATE.htm
Rafał Dowgird
3
Pour répondre clairement à la question: Oui , il est à la tâche. Il y a apparemment des cas particuliers que l'algorithme de classement Unicode complet gère mieux, mais à moins que vous ne sachiez déjà que vous ne le remarquerez probablement pas.
Lennart Regebro
1
Le plus gros problème ici est: vous devez définir globalement les paramètres régionaux pour l'ensemble de l'application. - Vous ne pouvez pas simplement l'avoir pour la comparaison à portée de main.
Robert Siemer
9

Essayez l' algorithme de classement Python Unicode de James Tauber . Il ne fait peut-être pas exactement ce que vous voulez, mais semble valoir le coup d'œil. Pour plus d'informations sur les problèmes, consultez cet article de Christopher Lenz.

Vinay Sajip
la source
Cela résout au moins le problème générique. Je suppose que des versions sensibles à la langue de la liste de classement pourraient également être créées.
Lennart Regebro
Cela ne vous permet pas de spécifier les paramètres régionaux et le fichier de configuration de référence provoque une ValueError.
thebjorn
8

Vous pourriez également être intéressé par pyuca :

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

Bien que ce ne soit certainement pas le moyen le plus précis, c'est un moyen très simple de faire au moins un peu raison. Il bat également les paramètres régionaux dans une application Web, car les paramètres régionaux ne sont pas sûrs pour les threads et définissent les paramètres de langue à l'échelle du processus. Il est également plus facile à configurer que PyICU qui s'appuie sur une bibliothèque C externe.

J'ai téléchargé le script sur github car l'original était en panne au moment de la rédaction de cet article et j'ai dû recourir à des caches Web pour l'obtenir:

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

J'ai utilisé avec succès ce script pour trier correctement le texte allemand / français / italien dans un module plone.

href_
la source
+1 pour pyuca. C'est assez rapide (3 secondes pour trier 28000 mots), c'est du pur python et ne nécessite aucune dépendance.
michaelmeyer
7

Une réponse synthétique et détaillée:

locale.strcollsous Python 2, et locale.strxfrmrésoudra en fait le problème, et fait du bon travail, en supposant que vous avez installé la locale en question. Je l'ai également testé sous Windows, où les noms de paramètres régionaux sont différents, mais d'un autre côté, il semble que tous les paramètres régionaux pris en charge soient installés par défaut.

ICUne le fait pas nécessairement mieux dans la pratique, mais bien plus . Plus particulièrement, il prend en charge les séparateurs qui peuvent diviser des textes dans différentes langues en mots. Ceci est très utile pour les langues qui n'ont pas de séparateurs de mots. Vous aurez besoin d'un corpus de mots à utiliser comme base pour le fractionnement, car ce n'est pas inclus, cependant.

Il a également des noms longs pour les paramètres régionaux afin que vous puissiez obtenir de jolis noms d'affichage pour les paramètres régionaux, la prise en charge d'autres calendriers que le grégorien (bien que je ne sois pas sûr que l'interface Python le prenne en charge) et des tonnes et des tonnes d'autres supports de paramètres régionaux plus ou moins obscurs .

Donc dans l'ensemble: si vous souhaitez trier par ordre alphabétique et en fonction des paramètres régionaux, vous pouvez utiliser le localemodule, sauf si vous avez des exigences particulières, ou si vous avez également besoin de fonctionnalités plus dépendantes des paramètres régionaux, comme le séparateur de mots.

Lennart Regebro
la source
6

Je vois que les réponses ont déjà fait un excellent travail, je voulais juste souligner une inefficacité de codage dans Human Sort . Pour appliquer une traduction sélective char-par-char à une chaîne unicode s, il utilise le code:

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

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

Python a un moyen bien meilleur, plus rapide et plus concis pour effectuer cette tâche auxiliaire (sur les chaînes Unicode - la méthode analogue pour les chaînes d'octets a une spécification différente et un peu moins utile! -):

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

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

Le dict que vous passez à la translateméthode a des ordinaux Unicode (et non des chaînes) comme clés, c'est pourquoi nous avons besoin de cette étape de reconstruction à partir du char-à-char d'origine spec_dict. (Les valeurs du dict que vous passez à traduire [par opposition aux clés, qui doivent être des ordinaux] peuvent être des ordinaux Unicode, des chaînes Unicode arbitraires ou None pour supprimer le caractère correspondant dans le cadre de la traduction, il est donc facile de spécifier "ignorer un certains caractères à des fins de tri "," mapper ä à ae à des fins de tri ", etc.).

Dans Python 3, vous pouvez obtenir l'étape de "reconstruction" plus simplement, par exemple:

spec_dict = ''.maketrans(spec_dict)

Consultez la documentation pour découvrir d'autres façons d'utiliser cette maketransméthode statique dans Python 3.

Alex Martelli
la source
Cette méthode est sympa mais ne permet pas de placer á entre az et b
Barney
1

Dernièrement, j'utilise zope.ucol ( https://pypi.python.org/pypi/zope.ucol ) pour cette tâche. Par exemple, en triant l'allemand ß:

>>> 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 enveloppe également ICU, ce serait donc une alternative à PyICU.

Brian Sutherland
la source
1

Une solution UCA complète

La manière la plus simple, la plus simple et la plus directe de le faire est de faire un appel au module de bibliothèque Perl, Unicode :: Collate :: Locale , qui est une sous-classe du module Unicode :: Collate standard . Tout ce que vous avez à faire est de transmettre au constructeur une valeur locale de "xv"pour la Suède.

(Vous n'apprécierez peut-être pas nécessairement cela pour le texte suédois, mais comme Perl utilise des caractères abstraits, vous pouvez utiliser n'importe quel point de code Unicode que vous voulez - peu importe la plate-forme ou la construction! Peu de langues offrent une telle commodité. Je le mentionne parce que j'ai J'ai beaucoup perdu la bataille avec Java sur ce problème exaspérant ces derniers temps.)

Le problème est que je ne sais pas comment accéder à un module Perl à partir de Python - à part, c'est-à-dire en utilisant une légende de shell ou un tube à deux côtés. À cette fin, je vous ai donc fourni un script de travail complet appelé ucsort que vous pouvez appeler pour faire exactement ce que vous avez demandé avec une facilité parfaite.

Ce script est 100% compatible avec l' algorithme de classement Unicode complet , avec toutes les options de personnalisation prises en charge !! Et si un module optionnel est installé ou que vous exécutez Perl 5.13 ou supérieur, vous avez un accès complet aux locales CLDR faciles à utiliser. Voir ci-dessous.

Manifestation

Imaginez un ensemble d'entrée ordonné de cette façon:

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

Un tri par défaut par point de code donne:

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

ce qui est incorrect dans le livre de tout le monde. En utilisant mon script, qui utilise l'algorithme de classement Unicode, vous obtenez cet ordre:

% 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

C'est le tri UCA par défaut. Pour obtenir les paramètres régionaux suédois, appelez ucsort de cette façon:

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

Voici une meilleure démo d'entrée. Tout d'abord, l'ensemble d'entrée:

% 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

Par point de code, cela trie de cette façon:

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

Mais l'utilisation de l'UCA par défaut permet de le trier de cette façon:

% 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

Mais dans la langue suédoise, de cette façon:

% 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

Si vous préférez trier les majuscules avant les minuscules, procédez comme suit:

% 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

Sortes personnalisées

Vous pouvez faire beaucoup d'autres choses avec ucsort . Par exemple, voici comment trier les titres en anglais:

% 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

Vous aurez besoin de Perl 5.10.1 ou supérieur pour exécuter le script en général. Pour la prise en charge des paramètres régionaux, vous devez soit installer le module CPAN en option Unicode::Collate::Locale. Vous pouvez également installer une version de développement de Perl, 5.13+, qui inclut ce module en standard.

Conventions d'appel

Il s'agit d'un prototype rapide, donc ucsort n'est généralement pas (der) documenté. Mais voici son SYNOPSIS des commutateurs / options qu'il accepte sur la ligne de commande:

    # 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

Ouais, ok: c'est vraiment la liste d'arguments que j'utilise pour l'appel Getopt::Long, mais vous voyez l'idée. :)

Si vous pouvez trouver comment appeler directement des modules de bibliothèque Perl depuis Python sans appeler un script Perl, faites-le par tous les moyens. Je ne sais pas comment faire moi-même. J'adorerais apprendre comment.

En attendant, je pense que ce script fera tout ce dont vous avez besoin dans ses particularités - et plus encore! J'utilise maintenant ceci pour tout le tri de texte. Il fait enfin ce dont j'avais besoin depuis très, très longtemps.

Le seul inconvénient est que cet --localeargument entraîne une baisse des performances dans les tubes, bien que cela soit suffisamment rapide pour un tri régulier, non local mais toujours 100% conforme à UCA . Puisqu'il charge tout en mémoire, vous ne voudrez probablement pas l'utiliser sur des documents gigaoctets. Je l'utilise plusieurs fois par jour, et c'est vraiment génial d'avoir enfin un tri de texte sain.

tchrist
la source
2
Pourquoi diable appelleriez-vous un script Perl pour faire quelque chose pour lequel il existe des bibliothèques Python?
Lennart Regebro
2
Parce que je ne savais pas qu'il y avait une bibliothèque Python, c'est pourquoi!
tchrist le
@Lennart: Je préfère vraiment les bibliothèques natives, ou tout au plus celles liées à une API C et chargées dynamiquement (dont vous avez parfois besoin). Je n'ai pas trouvé les différentes solutions PyPerl et Inline :: Perl très convaincantes, robustes ou flexibles. Ou quelque chose. Ils ne se sentent tout simplement pas bien pour certaines raisons. J'ai essayé cela pour la dernière fois lorsque j'avais besoin d'une bonne détection de jeu de caractères (ce que je n'ai jamais eu, hélas).
tchrist
4
Utiliser Perl dans Python n'est qu'une dépendance.
Utku Zihnioglu
1
Sensationnel. Oui, cela ressemble à Perl pour moi, en fait, nous voyons qu'il y a maintenant plus de deux façons de faire les choses :) Mais appeler C depuis Python n'implique généralement pas les sortes de dépendances ajoutées et de problèmes de support pratique qu'appeler Perl, donc c'est terriblement difficile de voir un appel pour le faire de cette façon.
nealmcb
0

Il est loin d' être une solution complète pour votre cas d'utilisation, mais vous pouvez jeter un oeil à l' unaccent.py script à partir effbot.org. Ce qu'il fait essentiellement, c'est supprimer tous les accents d'un texte. Vous pouvez utiliser ce texte «épuré» pour trier par ordre alphabétique. (Pour une meilleure description, consultez cette page.)

Mark van Lent
la source