python unicode indic

python - Jugar con los personajes devanagari



unicode indic (5)

Tengo algo así como

a = "बिक्रम मेरो नाम हो"

Quiero lograr algo así como

a[0] = बि a[1] = क्र a[3] = म

pero como म toma 4 bytes mientras que बि toma 8 bytes, no puedo llegar a esa línea recta. Entonces, ¿qué se puede hacer para lograr eso? En Python.


El algoritmo para dividir texto en clústeres de grafemas se da en Unicode, Anexo 29 , sección 3.1. No voy a implementar el algoritmo completo aquí, pero le mostraré cómo manejar el caso de Devanagari, y luego podrá leer el Anexo y ver qué más necesita implementar.

El módulo unicodedata contiene la información que necesita para detectar los clústeres de grafemas.

>>> import unicodedata >>> a = "बिक्रम मेरो नाम हो" >>> [unicodedata.name(c) for c in a] [''DEVANAGARI LETTER BA'', ''DEVANAGARI VOWEL SIGN I'', ''DEVANAGARI LETTER KA'', ''DEVANAGARI SIGN VIRAMA'', ''DEVANAGARI LETTER RA'', ''DEVANAGARI LETTER MA'', ''SPACE'', ''DEVANAGARI LETTER MA'', ''DEVANAGARI VOWEL SIGN E'', ''DEVANAGARI LETTER RA'', ''DEVANAGARI VOWEL SIGN O'', ''SPACE'', ''DEVANAGARI LETTER NA'', ''DEVANAGARI VOWEL SIGN AA'', ''DEVANAGARI LETTER MA'', ''SPACE'', ''DEVANAGARI LETTER HA'', ''DEVANAGARI VOWEL SIGN O'']

En Devanagari, cada grupo de grafemas consta de una letra inicial, pares opcionales de virama (asesino de vocales) y letra, y un signo de vocal opcional. En la notación de expresión regular que sería LETTER (VIRAMA LETTER)* VOWEL? . Puede ver cuál es cuál buscando la categoría Unicode para cada punto de código:

>>> [unicodedata.category(c) for c in a] [''Lo'', ''Mc'', ''Lo'', ''Mn'', ''Lo'', ''Lo'', ''Zs'', ''Lo'', ''Mn'', ''Lo'', ''Mc'', ''Zs'', ''Lo'', ''Mc'', ''Lo'', ''Zs'', ''Lo'', ''Mc'']

Las letras son categoría Lo (letra, otro), los signos vocálicos son categoría Mc (marca, combinación de espacios), virama es categoría Mn (marca, no espaciado) y los espacios son categoría Zs (separador, espacio).

Así que aquí hay un enfoque aproximado para dividir los conglomerados de grafemas:

def splitclusters(s): """Generate the grapheme clusters for the string s. (Not the full Unicode text segmentation algorithm, but probably good enough for Devanagari.) """ virama = u''/N{DEVANAGARI SIGN VIRAMA}'' cluster = u'''' last = None for c in s: cat = unicodedata.category(c)[0] if cat == ''M'' or cat == ''L'' and last == virama: cluster += c else: if cluster: yield cluster cluster = c last = c if cluster: yield cluster >>> list(splitclusters(a)) [''बि'', ''क्र'', ''म'', '' '', ''मे'', ''रो'', '' '', ''ना'', ''म'', '' '', ''हो'']


Entonces, quieres lograr algo como esto

a[0] = बि a[1] = क्र a[3] = म

Mi consejo es abandonar la idea de que la indexación de cadenas corresponde a los caracteres que ves en la pantalla. Devanagari, así como varios otros guiones, no juegan bien con los programadores que crecieron con caracteres latinos. Sugiero leer el capítulo 9 de la norma Unicode ( disponible aquí ).

Parece que lo que estás tratando de hacer es dividir una cadena en grupos de grafemas. La indexación de cadenas por sí sola no te permitirá hacer esto. Hangul es otro script que funciona mal con indexación de cadenas, aunque con la combinación de caracteres, incluso algo tan familiar como el español causará problemas.

Necesitará una biblioteca externa como ICU para lograr esto (a menos que tenga mucho tiempo libre). ICU tiene enlaces de Python.

>>> a = u"बिक्रम मेरो नाम हो" >>> import icu # Note: This next line took a lot of guesswork. The C, C++, and Java # interfaces have better documentation. >>> b = icu.BreakIterator.createCharacterInstance(icu.Locale()) >>> b.setText(a) >>> i = 0 >>> for j in b: ... s = a[i:j] ... print ''|'', s, len(s) ... i = j ... | बि 2 | क् 2 | र 1 | म 1 | 1 | मे 2 | रो 2 | 1 | ना 2 | म 1 | 1 | हो 2

Observe cómo algunos de estos "caracteres" (clústeres de grafema) tienen longitud 2, y algunos tienen longitud 1. Esta es la razón por la cual la indexación de cadenas es problemática: si quiero obtener el grafema cluster 69450 de un archivo de texto, entonces tengo que escanear linealmente a través de todo el archivo y el recuento. Entonces tus opciones son:

  • Construye un índice (tipo de loco ...)
  • Solo date cuenta de que no puedes romper cada límite de personaje. El objeto del iterador de ruptura puede avanzar y retroceder, por lo que si necesita extraer los primeros 140 caracteres de una cadena, observe el índice 140 e itere hacia atrás hasta el salto del clúster de grafema anterior, de esa manera no termina con texto divertido. (Mejor aún, puede usar un iterador de salto de palabra para la configuración regional apropiada.) El beneficio de usar este nivel de abstracción (iteradores de caracteres y similares) es que ya no importa qué codificación utiliza: puede usar UTF-8, UTF-16, UTF-32 y todo funciona. Bueno, la mayoría funciona.

Hay una biblioteca de Python pura llamada uniseg que proporciona una serie de utilidades que incluyen un iterador de clúster de grafema que proporciona el comportamiento que describió:

>>> a = u"बिक्रम मेरो नाम हो" >>> from uniseg.graphemecluster import grapheme_clusters >>> for i in grapheme_clusters(a): print(i) ... बि क् र म मे रो ना म हो

Afirma implementar el algoritmo de segmentación de texto Unicode completo descrito en http://www.unicode.org/reports/tr29/tr29-21.html .


Los guiones índicos y no latinos como Hangul generalmente no siguen la idea de unir los índices de cuerdas con los puntos de código. En general, es un dolor trabajar con scripts de Indic. La mayoría de los personajes son dos bytes con algunos raros que se extienden en tres. Con Dravidian, no hay un orden definido. Ver la especificación Unicode para más detalles.

Dicho esto, mira here algunas ideas sobre unicode y python con C ++.

Finalmente, como dijo Dietrich , es posible que también desee consultar ICU . Tiene enlaces disponibles para C / C ++ y java a través de icu4c e icu4j, respectivamente. Hay una cierta curva de aprendizaje involucrada, así que sugiero que dediques algo de tiempo para ello. :)


Puede lograr esto con una expresión regular simple para cualquier motor que admita /X

Demo

Desafortunadamente, la versión de Python no es compatible con la coincidencia de / X grafema.

Afortunadamente, el reemplazo propuesto, regex , admite /X :

>>> a = "बिक्रम मेरो नाम हो" >>> regex.findall(r''/X'', a) [''बि'', ''क्'', ''र'', ''म'', '' '', ''मे'', ''रो'', '' '', ''ना'', ''म'', '' '', ''हो'']