¿Cómo clasifico las cadenas de unicode alfabéticamente en Python?
sorting internationalization (11)
Python ordena por valor de byte de manera predeterminada, 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, por lo que entiende que åäö debe clasificarse después de z en sueco, pero que ü debe ser ordenado por u, etc. El soporte de Unicode es, por lo tanto, casi un requisito.
Si no hay una biblioteca para él, ¿cuál es la mejor manera de hacer esto? Simplemente haga un mapeo de una letra a un valor entero y asocie la cadena a una lista de enteros con eso.
Una solución completa de UCA
La forma más sencilla, sencilla y directa de hacerlo es hacer una llamada al módulo de biblioteca de Perl, Unicode::Collate::Locale , que es una subclase del módulo Unicode::Collate estándar. Todo lo que necesita hacer es pasarle al constructor un valor regional de "xv"
para Suecia.
(Es posible que no necesariamente aprecie esto para el texto sueco, pero dado que Perl usa caracteres abstractos, puede usar cualquier punto de código Unicode que desee, sin importar la plataforma o la compilación. Pocos idiomas ofrecen tal conveniencia. Lo menciono porque he luchado contra un perdiendo batalla con Java mucho sobre este enloquecedor problema últimamente).
El problema es que no sé cómo acceder a un módulo de Perl desde Python, aparte de usar un shell call o un canal de dos lados. Con ese fin, por lo tanto , le he proporcionado un script de trabajo completo llamado ucsort al que puede llamar para hacer exactamente lo que ha pedido con gran facilidad.
¡Esta secuencia de comandos es 100% compatible con el algoritmo de intercalación Unicode completo , con todas las opciones de adaptación compatibles! Y si tiene instalado un módulo opcional o ejecuta Perl 5.13 o superior, entonces tendrá acceso completo a las configuraciones regionales de 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 rendimiento de punto de código:
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 ä å ö
que es incorrecto por 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 de 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 mejor demostración de entrada. 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 usar el UCA predeterminado lo hace ordenar 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 localidad 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 mayúsculas para ordenar antes de 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
Clases personalizadas
Puedes hacer muchas otras cosas con ucsort . Por ejemplo, aquí está cómo ordenar 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
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
Necesitará Perl 5.10.1 o superior para ejecutar el script en general. Para soporte de localización, debe instalar el módulo de 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 está mayoritariamente (der) documentado. 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 utilizo para la llamada a Getopt::Long
, pero entiendes la idea. :)
Si puede averiguar cómo llamar directamente a los módulos de la biblioteca de Perl desde Python sin llamar a un script de Perl, hágalo de todos modos. Simplemente no sé cómo yo mismo. Me encantaría aprender cómo.
Mientras tanto, creo que este script hará lo que necesites en todos sus aspectos, ¡ y más! Ahora uso esto para todas las clasificaciones de texto. Finalmente hace lo que he necesitado durante mucho, mucho tiempo.
El único inconveniente es que el argumento de --locale
hace que el rendimiento baje, aunque es lo suficientemente rápido para la ordenación normal, no local pero aún 100% compatible con UCA . Como carga todo en la memoria, probablemente no desee utilizar esto en documentos de gigabyte. Lo uso muchas veces al día, y seguro que es genial tener una ordenada ordenación del texto por fin.
Últimamente he estado usando zope.ucol ( https://pypi.python.org/pypi/zope.ucol ) para esta tarea. Por ejemplo, clasificando 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.
Está lejos de ser una solución completa para su caso de uso, pero podría echarle un vistazo al script unaccent.py de 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 una mejor descripción, vea this página).
Jeff Atwood escribió una buena publicación sobre Natural Sort Order , en ella enlazó con un script que hace más o menos lo que usted pregunta .
No es un guión trivial, de ninguna manera, pero cumple su cometido.
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.strcoll
es que ICU usa el Algoritmo de strcoll
Unicode completo, mientras que strcoll
usa 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 raramente 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'']
No veo esto en las respuestas. Mi aplicación ordena de acuerdo con la configuración regional utilizando la biblioteca estándar de Python. Es bastante fácil.
# 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 a Lennart y otros que responden: ¿Alguien sabe ''locale'' o no está a la altura de esta tarea?
Para implementarlo, deberá leer sobre el "algoritmo de intercalación Unicode", consulte http://en.wikipedia.org/wiki/Unicode_collation_algorithm
http://www.unicode.org/unicode/reports/tr10/
una implementación de muestra está aquí
http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/
Prueba el algoritmo de intercalación Unicode Python de James Tauber. Puede que no haga exactamente lo que quiere, pero parece que vale la pena echarle un vistazo. Para obtener un poco más de información sobre los problemas, consulte esta publicación de Christopher Lenz.
Puede que también te interese pyuca :
http://jtauber.com/blog/2006/01/27/python_unicode_collation_algorithm/
Aunque ciertamente no es la forma más exacta, es una manera muy simple 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 en cuanto a los hilos y establece las configuraciones de idioma en todo el proceso. También es más fácil de configurar que la PyICU que se basa en una biblioteca de C externa.
Cargué el script en github ya que el original estaba inactivo en el momento de escribir este documento y tuve que recurrir a cachés web para obtenerlo:
https://github.com/href/Python-Unicode-Collation-Algorithm
Utilicé con éxito esta secuencia de comandos para ordenar sanamente el texto alemán / francés / italiano en un módulo plone.
Un resumen y una respuesta extendida:
locale.strcoll
en Python 2, y locale.strxfrm
de hecho resolverá el problema, y hace un buen trabajo, suponiendo que tiene instalada la localidad en cuestión. También lo probé en Windows, donde los nombres de la configuración regional son confusos y diferentes, pero por otro lado parece que tiene todas las configuraciones regionales admitidas instaladas por defecto.
ICU
no necesariamente lo hace mejor en la práctica, sin embargo lo 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. Tendrá que tener un corpus de palabras para usar como base para la división, porque eso no está incluido, sin embargo.
También tiene nombres largos para las configuraciones regionales para que puedas obtener nombres geniales para la configuración regional, soporte para otros calendarios además de Gregorian (aunque no estoy seguro de que la interfaz Python lo admita) y toneladas y toneladas de otros soportes locales más o menos oscuros .
Entonces todo: si desea ordenar alfabéticamente y dependiente de la configuración regional, puede usar el módulo de locale
, a menos que tenga requisitos especiales o también necesite más funciones dependientes de la configuración regional, como el separador de palabras.
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, 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 para 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 dict que pasas al método de translate
tiene ordinales Unicode (no cadenas) como claves, por lo que necesitamos ese paso de reconstrucción del spec_dict
char-to-char spec_dict
. (Los valores en el diccionario que pasa para traducir [en oposición a 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 "," mapa ä a ae para fines de clasificación ", y similares).
En Python 3, puede obtener el paso de "reconstrucción" más simple, por ejemplo:
spec_dict = ''''.maketrans(spec_dict)
Consulte los documentos de otras formas en que puede usar este método estático maketrans
en Python 3.