Tengo una aplicación que implementa la búsqueda incremental. Tengo un catálogo de cadenas unicode para que coincidan y las combine con una cadena de "clave" dada; una cadena de catálogo es un "hit" si contiene todos los caracteres de la clave, en orden, y se clasifica mejor si los caracteres clave se agrupan en la cadena de catálogo.

De todos modos, esto funciona bien y coincide exactamente con Unicode, de modo que "öst" coincidirá con el bloque " Öst " o "r öst " o "r ö d st es ".

De todos modos, ahora quiero implementar el plegado, ya que hay algunos casos en los que no es útil distinguir entre un carácter de catálogo como "á" o "é" y el carácter clave "a" o "e".

Por ejemplo: "Ole" debe coincidir con "Olé"

¿Cómo implemento mejor este emparejador plegable Unicode en Python? La eficiencia es importante ya que tengo que hacer coincidir miles de cadenas de catálogo con la clave corta dada.

No tiene que convertirlo en ascii; de hecho, la cadena de salida del algoritmo podría ser unicode. Dejar un personaje es mejor que desnudarlo.


No sé qué respuesta aceptar, ya que uso un poco de ambas. Tomar la descomposición de NKFD y eliminar las marcas combinadas es casi la forma de terminar, solo agrego algunas transliteraciones personalizadas a eso. Aquí está el módulo como se ve ahora: (Advertencia, contiene caracteres unicode en línea, ya que es mucho mejor editar de esa manera).

# -*- encoding: UTF-8 -*-

import unicodedata
from unicodedata import normalize, category

def _folditems():
    _folding_table = {
        # general non-decomposing characters
        # FIXME: This is not complete
        u"ł" : u"l",
        u"œ" : u"oe",
        u"ð" : u"d",
        u"þ" : u"th",
        u"ß" : u"ss",
        # germano-scandinavic canonical transliterations
        u"ü" : u"ue",
        u"å" : u"aa",
        u"ä" : u"ae",
        u"æ" : u"ae",
        u"ö" : u"oe",
        u"ø" : u"oe",
    }

    for c, rep in _folding_table.iteritems():
        yield (ord(c.upper()), rep.title())
        yield (ord(c), rep)

folding_table = dict(_folditems())

def tofolded(ustr):
    u"""Fold @ustr

    Return a unicode str where composed characters are replaced by
    their base, and extended latin characters are replaced by
    similar basic latin characters.

    >>> tofolded(u"Wyłącz")
    u'Wylacz'
    >>> tofolded(u"naïveté")
    u'naivete'

    Characters from other scripts are not transliterated.

    >>> tofolded(u"Ἑλλάς") == u'Ελλας'
    True

    (These doctests pass, but should they fail, they fail hard)
    """
    srcstr = normalize("NFKD", ustr.translate(folding_table))
    return u"".join(c for c in srcstr if category(c) != 'Mn')

if __name__ == '__main__':
    import doctest
    doctest.testmod()

(Y, para la coincidencia real si eso le interesa a alguien: construyo cadenas plegadas para todo mi catálogo de antemano, y pongo las versiones plegadas en la propiedad de alias de objeto de catálogo ya disponible).

10
u0b34a0f6ae 11 sep. 2009 a las 15:15

5 respuestas

La mejor respuesta

Puede usar esta strip_accents función para eliminar los acentos:

def strip_accents(s):
   return ''.join((c for c in unicodedata.normalize('NFD', unicode(s)) if unicodedata.category(c) != 'Mn'))

>>> strip_accents(u'Östblocket')
'Ostblocket'
6
Community 23 may. 2017 a las 10:32

Eche un vistazo a esto: ftp://alan.smcvt.edu/hefferon/unicode2ascii.py

Probablemente no esté completo, pero podría comenzar.

1
stefanw 11 sep. 2009 a las 11:28

Una solución de propósito general (especialmente para la normalización de búsqueda y generación de babosas) es el módulo unidecode:

http://pypi.python.org/pypi/Unidecode

Es un puerto del módulo Text :: Unidecode para Perl. No está completo, pero traduce todos los caracteres derivados del latín que pude encontrar, translitera el cirílico, el chino, etc. al latín e incluso maneja los caracteres de ancho completo correctamente.

Probablemente sea una buena idea eliminar todos los caracteres que no desea tener en la salida final o reemplazarlos con un relleno (por ejemplo, "äßœ$" se descodificará a "assoe$", por lo que es posible que desee eliminar los no alfanuméricos). En el caso de los caracteres, se transliterará pero no debería (digamos, § => SS y => EU) necesita limpiar la entrada:

input_str = u'äßœ$'
input_str = u''.join([ch if ch.isalnum() else u'-' for ch in input_str])
input_str = str(unidecode(input_str)).lower()

Esto reemplazaría todos los caracteres no alfanuméricos con un reemplazo ficticio y luego transliteraría la cadena y la convertiría en minúsculas.

1
Alan Plum 24 feb. 2010 a las 17:18

Para mi aplicación, ya abordé esto en un comentario diferente: quiero tener un resultado unicode y dejar caracteres no controlados sin tocar.

En ese caso, la forma correcta de hacer esto es crear un objeto clasificador UCA con su fuerza establecida para comparar solo con la fuerza primaria, lo que ignora por completo los signos diacríticos.

Muestro cómo hacer esto usando Perl en esta respuesta. El primer objeto clasificador tiene la fuerza que necesita, mientras que el segundo considera los acentos para romper el empate.

Notará que no se han dañado las cadenas en la realización de estas comparaciones: los datos originales están intactos.

4
Community 23 may. 2017 a las 12:26

¿Qué hay de este?

normalize('NFKD', unicode_string).encode('ASCII', 'ignore').lower()

Tomado de aquí (español) http://python.org.ar/pyar/Recetario/NormalizarCaracteresUnicode

1
Esteban Feldman 3 may. 2010 a las 16:20