¿Cómo ordeno las cadenas Unicode alfabéticamente en Python?

97

Python ordena por valor de byte por defecto, lo que significa que é viene después de zy otras cosas igualmente divertidas. ¿Cuál es la mejor manera de ordenar alfabéticamente en Python?

¿Hay una biblioteca para esto? No pude encontrar nada. Preferiblemente, la ordenación debe tener soporte de idioma para que entienda que åäö debe ser ordenada después de z en sueco, pero que ü debe ser ordenada por u, etc. Por lo tanto, la compatibilidad con Unicode es prácticamente un requisito.

Si no hay una biblioteca para ello, ¿cuál es la mejor manera de hacerlo? ¿Simplemente haga una asignación de letra a un valor entero y asigne la cadena a una lista de enteros con eso?

Lennart Regebro
fuente
11
Tenga en cuenta que esto depende aún más de la configuración regional: en sueco (como usted dice) "Ä" viene después de "Z", pero en alemán, "Ä" generalmente se clasifica como "AE".
Balpha
@Georg: ¿Hubo alguna razón por la que abriste una recompensa por esto? La locale.strcollrespuesta es correcta cuando necesita ordenar Unicode usando la configuración regional del usuario, y la UCI responde lo que desea cuando necesita más que eso (clasificación usando más de una configuración regional). La mayoría de las veces, quieres locale.strcoll.
Glenn Maynard
@Glenn: Quería saber qué tan bien locale.strcollfunciona y especialmente qué hace ICU mejor que la función Python. Básicamente un poco más de atención a la pregunta.
Georg Schölly
1
@Georg: He estado jugando mucho con el algoritmo de clasificación Unicode últimamente, como puede ver en mi respuesta. Es realmente excelente poder, por ejemplo, clasificar --locale=de__phonebookcuando lo necesite. El módulo Perl pasa el conjunto de pruebas UCA, y el script que proporcioné hace que sea mucho más fácil jugar con todo el UCA más todas sus opciones, incluidas las configuraciones regionales, solo desde la línea de comandos. Puede que no responda a la pregunta, pero aún así debería ser muy interesante. Si estás en Suiza, estoy seguro de que podrías aprovechar la flexibilidad. :)
tchrist

Respuestas:

75

La biblioteca ICU de IBM hace eso (y mucho más). Tiene enlaces de Python: PyICU .

Actualización : la diferencia principal en la clasificación entre ICU y locale.strcolles que ICU usa el algoritmo de clasificación Unicode completo mientras que strcollusa ISO 14651 .

Las diferencias entre esos dos algoritmos se resumen brevemente aquí: http://unicode.org/faq/collation.html#13 . Estos son casos especiales bastante exóticos, que rara vez deberían importar en la práctica.

>>> 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
fuente
¿Funciona igual para Python 2 y Python 3? Utilicé locale.strxfrmla respuesta de u0b34a0f6ae y parece funcionar y es mucho más elegante y no requiere ningún software adicional.
Sup
No funciona con Python3 para mí, sudo pip3 install PyICUno se instala y también lo hace para Python2.
imrek
Tuve que instalar libicu-devel.x86_64 para que pyICU se compilara e instalara desde Pip. Funciona, aunque la salida del último comando 'ordenado' es: ['a', '\ xc3 \ xa4', 'b', 'c']
Mike Stoddart
53

No veo esto en las respuestas. Mi aplicación se ordena de acuerdo con la configuración regional usando la biblioteca estándar de Python. Es bastante sencillo.

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

Pregunta para Lennart y otras personas que responden: ¿Nadie sabe 'locale' o no está a la altura de esta tarea?

u0b34a0f6ae
fuente
Por cierto 1) No creo que locale.strxfrm esté roto para `str 'codificado en UTF-8; Hice una evaluación comparativa por aplicación y concluí que usar cmp = strcoll en objetos Unicode es más barato que decodificar todo en UTF-8 y usar key = strxfrm
u0b34a0f6ae
6
Por cierto 2) El módulo de configuración regional solo funcionará con las configuraciones regionales generadas (para una caja de Linux), no con ninguna configuración regional arbitraria. "locale -a" le dirá cuál
u0b34a0f6ae
6
@Georg: Creo que la configuración regional solo admite una asignación simple de subcadena-> collating_element. No maneja cosas como expansiones (æ ordenadas como "ae"), clasificación de acento francés (letras ordenadas de izquierda a derecha, pero acentos de derecha a izquierda), reordenación y probablemente algunas más. Detalles aquí (conjunto completo de características de UCA): unicode.org/reports/tr10 y aquí (colación de locale): chm.tu-dresden.de/edv/manuals/aix/files/aixfiles/LC_COLLATE.htm
Rafał Dowgird
2
Para responder claramente a la pregunta: Sí, está a la altura de la tarea. Aparentemente, hay algunos casos especiales que el algoritmo de clasificación Unicode completo maneja mejor, pero a menos que ya lo sepa, es probable que no lo note.
Lennart Regebro
1
El mayor problema aquí es: debe establecer la configuración regional globalmente para toda la aplicación. - No puede tenerlo solo para la comparación a mano.
Robert Siemer
9

Pruebe el algoritmo de intercalación Unicode de Python de James Tauber . Puede que no funcione exactamente como usted desea, pero parece que vale la pena echarle un vistazo. Para obtener más información sobre los problemas, consulte esta publicación de Christopher Lenz.

Vinay Sajip
fuente
Eso al menos soluciona el problema genérico. Supongo que también se podrían crear versiones sensibles al idioma de la lista de clasificación.
Lennart Regebro
Esto no le permite especificar la configuración regional y el archivo de configuración de referencia genera un ValueError.
thebjorn
8

También te puede interesar pyuca :

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

Aunque ciertamente no es la forma más exacta, es una forma muy sencilla de al menos hacerlo bien. También supera a la configuración regional en una aplicación web, ya que la configuración regional no es segura para subprocesos y establece la configuración de idioma en todo el proceso. También es más fácil de configurar que PyICU, que se basa en una biblioteca C externa.

Subí el script a github ya que el original estaba caído en el momento de escribir este artículo y tuve que recurrir a cachés web para obtenerlo:

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

Utilicé con éxito este script para ordenar con cordura texto alemán / francés / italiano en un módulo plone.

href_
fuente
+1 para pyuca. Es bastante rápido (3 segundos para ordenar 28000 palabras), es Python puro y no requiere dependencia.
Michaelmeyer
7

Un resumen y una respuesta ampliada:

locale.strcollbajo Python 2, y locale.strxfrmde hecho resolverá el problema, y ​​hace un buen trabajo, asumiendo que tiene instalada la configuración regional en cuestión. También lo probé en Windows, donde los nombres de las configuraciones regionales son confusamente diferentes, pero por otro lado parece tener todas las configuraciones regionales compatibles instaladas de forma predeterminada.

ICUno necesariamente hace esto mejor en la práctica, sin embargo, hace mucho más . En particular, tiene soporte para divisores que pueden dividir textos en diferentes idiomas en palabras. Esto es muy útil para idiomas que no tienen separadores de palabras. Necesitará tener un corpus de palabras para usar como base para la división, porque eso no está incluido.

También tiene nombres largos para las configuraciones regionales, por lo que puede obtener bonitos nombres para mostrar para la configuración regional, compatibilidad con otros calendarios además del gregoriano (aunque no estoy seguro de que la interfaz de Python lo admita) y toneladas y toneladas de otras configuraciones regionales más o menos oscuras. .

Entonces, en general: si desea ordenar alfabéticamente y según la configuración regional, puede usar el localemódulo, a menos que tenga requisitos especiales, o también necesite más funciones dependientes de la configuración regional, como el divisor de palabras.

Lennart Regebro
fuente
6

Veo que las respuestas ya han hecho un excelente trabajo, solo quería señalar una ineficiencia de codificación en Human Sort . Para aplicar una traducción selectiva char-by-char a una cadena Unicode s, usa el código:

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

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

Python tiene una forma mucho mejor, más rápida y más concisa de realizar esta tarea auxiliar (en cadenas Unicode, el método análogo para cadenas de bytes tiene una especificación diferente y algo menos útil):

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

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

El dictado que le pasa al translatemétodo tiene ordinales Unicode (no cadenas) como claves, por lo que necesitamos ese paso de reconstrucción del char a char original spec_dict. (Los valores en el dict que pasa para traducir [a diferencia de las claves, que deben ser ordinales] pueden ser ordinales Unicode, cadenas Unicode arbitrarias o Ninguno para eliminar el carácter correspondiente como parte de la traducción, por lo que es fácil especificar "ignorar un cierto carácter para fines de clasificación "," mapear ä a ae para fines de clasificación ", y similares).

En Python 3, puede obtener el paso de "reconstrucción" de manera más simple, por ejemplo:

spec_dict = ''.maketrans(spec_dict)

Consulte los documentos para conocer otras formas en que puede usar este maketransmétodo estático en Python 3.

Alex Martelli
fuente
Este método es bueno pero no le permite colocar á entre az y b
Barney
1

Últimamente he estado usando zope.ucol ( https://pypi.python.org/pypi/zope.ucol ) para esta tarea. Por ejemplo, ordenando el alemán ß:

>>> 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 también envuelve ICU, por lo que sería una alternativa a PyICU.

Brian Sutherland
fuente
1

Una solución completa de UCA

La forma más sencilla, fácil y directa de hacerlo es hacer una llamada al módulo de la biblioteca Perl, Unicode :: Collate :: Locale , que es una subclase del módulo Unicode :: Collate estándar . Todo lo que necesita hacer es pasar al constructor un valor de configuración regional "xv"para Suecia.

(Es posible que no necesariamente aprecie esto para el texto sueco, pero debido a que Perl usa caracteres abstractos, puede usar cualquier punto de código Unicode que desee, ¡sin importar la plataforma o compilación! Pocos idiomas ofrecen tal conveniencia. Lo menciono porque he luchado contra un perdiendo mucho la batalla con Java por este enloquecedor problema últimamente).

El problema es que no sé cómo acceder a un módulo de Perl desde Python, es decir, aparte de usar una llamada de shell o una tubería de dos lados. Con ese fin, le he proporcionado un script de trabajo completo llamado ucsort al que puede llamar para hacer exactamente lo que ha pedido con perfecta facilidad.

Este script es 100% compatible con el algoritmo de clasificación Unicode completo , ¡con todas las opciones de adaptación compatibles! Y si tiene un módulo opcional instalado o ejecuta Perl 5.13 o mejor, entonces tiene acceso completo a configuraciones regionales CLDR fáciles de usar. Vea abajo.

Demostración

Imagine un conjunto de entrada ordenado de esta manera:

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

Una clasificación predeterminada por punto de código produce:

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

lo cual es incorrecto según el libro de todos. Usando mi script, que usa el algoritmo de intercalación Unicode, obtienes este orden:

% 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

Ese es el tipo UCA predeterminado. Para obtener la configuración regional sueca, llame a ucsort de esta manera:

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

Aquí hay una demostración de entrada mejor. Primero, el conjunto de entrada:

% 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

Por punto de código, eso se ordena de esta manera:

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

Pero el uso del UCA predeterminado hace que se ordene de esta manera:

% 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

Pero en la configuración regional sueca, de esta manera:

% 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 prefiere ordenar en mayúsculas antes que en minúsculas, haga esto:

% 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

Clasificaciones personalizadas

Puede hacer muchas otras cosas con ucsort . Por ejemplo, aquí se explica cómo ordenar los títulos en inglés:

% 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

Necesitará Perl 5.10.1 o mejor para ejecutar el script en general. Para obtener soporte de configuración regional, debe instalar el módulo CPAN opcional Unicode::Collate::Locale. Alternativamente, puede instalar versiones de desarrollo de Perl, 5.13+, que incluyen ese módulo de manera estándar.

Convenciones de llamadas

Este es un prototipo rápido, por lo que ucsort no está documentado en su mayoría. Pero esta es su SINOPSIS de qué conmutadores / opciones acepta en la línea de comando:

    # 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

Sí, está bien: esa es realmente la lista de argumentos que uso para la llamada Getopt::Long, pero entiendes la idea. :)

Si puede descubrir cómo llamar a los módulos de la biblioteca de Perl desde Python directamente sin llamar a un script de Perl, hágalo. Yo solo no sé cómo. Me encantaría aprender a hacerlo.

Mientras tanto, creo que este script hará lo que necesita hacer en todo su particular, ¡ y más! Ahora lo uso para toda la clasificación de texto. Que finalmente hace lo que he necesitado desde hace mucho, mucho tiempo.

El único inconveniente es que el --localeargumento hace que el rendimiento baje por los tubos, aunque es lo suficientemente rápido para una clasificación normal, no local, pero aún 100% compatible con UCA . Dado que carga todo en la memoria, probablemente no desee utilizar esto en documentos de gigabytes. Lo uso muchas veces al día, y seguro que es genial tener por fin una ordenación sensata de texto.

tchrist
fuente
2
¿Por qué demonios llamarías a un script de Perl para hacer algo para lo que hay bibliotecas de Python?
Lennart Regebro
2
Porque yo no sabía que era una biblioteca de Python, por eso!
tchrist
@Lennart: Realmente prefiero las bibliotecas nativas, o como máximo las vinculadas a una API de C y cargadas dinámicamente (que a veces necesitas). No he encontrado las distintas soluciones PyPerl e Inline :: Perl muy convincentes, robustas o flexibles. O algo. Simplemente no se sienten bien por algunas razones. Intenté esto por última vez cuando necesitaba una buena detección de juegos de caracteres (que nunca obtuve, por desgracia).
tchrist
4
Usar Perl dentro de Python es solo una adicción.
Utku Zihnioglu
1
Guau. Sí, me parece Perl, de hecho vemos que ahora hay más de dos formas de hacer las cosas :) Pero llamar a C desde Python generalmente no implica el tipo de dependencias agregadas y problemas prácticos de soporte que llamaría a Perl, por lo que es terriblemente difícil ver mucho llamado para hacerlo de esta manera.
nealmcb
0

Está lejos de ser una solución completa para su caso de uso, pero se puede echar un vistazo a la unaccent.py script desde effbot.org. Lo que básicamente hace es eliminar todos los acentos de un texto. Puede usar ese texto 'desinfectado' para ordenar alfabéticamente. (Para obtener una mejor descripción, consulte esta página).

Mark van Lent
fuente