HOWTO · Python

Soundex en Python

Implementa en Python el algoritmo de codificación fonética Soundex para comparar nombres en inglés mediante un código de cuatro caracteres: una letra inicial seguida de tres dígitos.

En esta página

Soundex es un algoritmo de codificación fonética para indexar nombres en inglés. Convierte un nombre en un código de cuatro caracteres: la primera letra seguida de tres dígitos. Según el algoritmo, los nombres con el mismo código pueden sonar parecidos, lo que puede ayudar a encontrar variantes ortográficas en una búsqueda. Un código coincidente solo indica una posible coincidencia; no demuestra que dos nombres sean iguales ni que se pronuncien de forma idéntica.

Python no incluye una función soundex integrada. El código siguiente implementa las reglas habituales del Soundex estadounidense para que puedas ver y adaptar cada paso.

Qué hace Soundex

Soundex conserva la primera letra de un nombre y asigna dígitos a las consonantes posteriores según grupos de sonidos amplios. Elimina los códigos adyacentes repetidos y luego completa con ceros los resultados cortos o trunca los largos para dejar tres dígitos. La letra inicial y esos dígitos forman el código final.

Por ejemplo, la conocida pareja Robert y Rupert produce R163 con estas reglas. La codificación es deliberadamente compacta: sacrifica detalle para que algunos nombres escritos de forma distinta terminen con la misma clave. Es una ayuda para las búsquedas, no un sustituto de la comparación exacta ni de la revisión humana.

Implementar Soundex en Python

Esta implementación acepta una cadena que contenga al menos una letra ASCII. Ignora los espacios, los signos de puntuación y otros caracteres que no sean ASCII, y normaliza las mayúsculas y minúsculas. Si la entrada no es una cadena o no contiene ninguna letra ASCII, genera un ValueError con un mensaje claro. El código conserva la primera letra como prefijo y usa una tabla de correspondencias para las demás letras.

Ejecuta este ejemplo:

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

name = "Robert"
letters = [char.upper() for char in name if ("A" <= char <= "Z") or ("a" <= char <= "z")]
groups = {
    **dict.fromkeys("BFPV", "1"),
    **dict.fromkeys("CGJKQSXZ", "2"),
    **dict.fromkeys("DT", "3"),
    "L": "4",
    **dict.fromkeys("MN", "5"),
    "R": "6",
}
previous = groups.get(letters[0])
digits = []
for letter in letters[1:]:
    code = groups.get(letter)
    if code is None:
        if letter in "AEIOUY":
            previous = None
        continue
    if code != previous:
        digits.append(code)
    previous = code
print(letters[0] + "".join(digits)[:3].ljust(3, "0"))
R163

El programa conserva la primera letra ASCII, añade hasta tres dígitos asignados y completa con ceros los resultados más cortos. La función completa que aparece a continuación también valida la entrada antes de codificarla.

def soundex(name):
    """Return a four-character Soundex code for an ASCII-letter name."""
    if not isinstance(name, str):
        raise ValueError("name must be a string")

    letters = [
        char.upper()
        for char in name
        if ("A" <= char <= "Z") or ("a" <= char <= "z")
    ]
    if not letters:
        raise ValueError("name must contain an ASCII letter")

    groups = {
        **dict.fromkeys("BFPV", "1"),
        **dict.fromkeys("CGJKQSXZ", "2"),
        **dict.fromkeys("DT", "3"),
        "L": "4",
        **dict.fromkeys("MN", "5"),
        "R": "6",
    }

    first = letters[0]
    previous = groups.get(first)
    digits = []

    for letter in letters[1:]:
        code = groups.get(letter)
        if code is None:
            # Vowels and Y separate equal codes; H and W do not.
            if letter in "AEIOUY":
                previous = None
            continue
        if code != previous:
            digits.append(code)
        previous = code

    return first + "".join(digits)[:3].ljust(3, "0")


print(soundex("Robert"))
R163

Filtra los caracteres originales antes de convertirlos a mayúsculas. Así se evita que una expansión Unicode al pasar a mayúsculas, como la de ß, se convierta en letras ASCII; las letras ASCII minúsculas sí se normalizan. Una entrada que no sea una cadena, o una cadena sin letras ASCII, genera un ValueError.

Entender las reglas de Soundex

Los grupos de consonantes son BFPV → 1, CGJKQSXZ → 2, DT → 3, L → 4, MN → 5 y R → 6. Las vocales y la Y no añaden dígitos. Separan los códigos de consonantes iguales, por lo que los sonidos repetidos a ambos lados de una vocal o de una Y pueden contribuir por separado. La H y la W se ignoran sin separar los códigos.

La primera letra se conserva aunque también pertenezca a un grupo de consonantes. Para no contarla dos veces, la implementación inicializa el código anterior a partir de esa letra. Por ejemplo, la P inicial y la F siguiente de Pfister se asignan ambas al 1, así que ese grupo adyacente solo produce un 1. Tras procesar el nombre, se conservan únicamente los tres primeros dígitos y se completan con ceros las posiciones que falten.

Solo intervienen las letras ASCII. Por eso, en esta implementación, los espacios y los signos de puntuación no interrumpen una secuencia de códigos adyacentes. Es una decisión explícita de normalización: si tu aplicación necesita otra política para apóstrofos, letras acentuadas u otros sistemas de escritura, defínela y pruébala antes de usar el código en búsquedas.

Probar la implementación

Ejecuta la función completa con una pareja cuyos nombres compartan código y con otros nombres que pongan a prueba la regla de la H y la W:

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

def soundex(name):
    """Return a four-character Soundex code for an ASCII-letter name."""
    if not isinstance(name, str):
        raise ValueError("name must be a string")
    letters = [
        char.upper()
        for char in name
        if ("A" <= char <= "Z") or ("a" <= char <= "z")
    ]
    if not letters:
        raise ValueError("name must contain an ASCII letter")
    groups = {
        **dict.fromkeys("BFPV", "1"),
        **dict.fromkeys("CGJKQSXZ", "2"),
        **dict.fromkeys("DT", "3"),
        "L": "4",
        **dict.fromkeys("MN", "5"),
        "R": "6",
    }
    first = letters[0]
    previous = groups.get(first)
    digits = []
    for letter in letters[1:]:
        code = groups.get(letter)
        if code is None:
            if letter in "AEIOUY":
                previous = None
            continue
        if code != previous:
            digits.append(code)
        previous = code
    return first + "".join(digits)[:3].ljust(3, "0")


for name in ("Robert", "Rupert", "Ashcraft", "Pfister"):
    print(f"{name}: {soundex(name)}")
Robert: R163
Rupert: R163
Ashcraft: A261
Pfister: P236

Robert y Rupert comparten un código. Ashcraft muestra que la H no separa códigos de consonantes repetidos, mientras que Pfister muestra que la consonante inicial participa en la supresión de duplicados frente a la F.

En un proyecto, convierte nombres representativos en aserciones con los códigos esperados de la variante exacta de Soundex que quieras admitir. Incluye nombres que empiecen con cada grupo de consonantes, nombres con vocales entre grupos repetidos, signos de puntuación y entradas sin letras ASCII. Las comprobaciones de entrada de la función generan un ValueError cuando la entrada no es válida; captura ese error donde tu aplicación pueda mostrar un mensaje útil o descartar el registro. Mantén estas comprobaciones junto a tus propias reglas de normalización, ya que cambiar qué caracteres participan puede cambiar el código.

Limitaciones de Soundex

Soundex se diseñó en torno a las convenciones de pronunciación y escritura del inglés. Sus grupos amplios pueden no reconocer nombres que las personas consideran parecidos y pueden asignar el mismo código a nombres que no son parecidos en su contexto. La pronunciación varía entre idiomas, regiones y personas, mientras que esta implementación solo procesa la escritura.

El código de cuatro caracteres también produce colisiones: muchos nombres distintos tienen que compartir un conjunto limitado de códigos posibles. Úsalo para acotar una búsqueda y luego compara registros más completos o pide a una persona que confirme la coincidencia. Para datos multilingües, considera un método diseñado para los idiomas y nombres de tu conjunto de datos, y documenta la normalización exacta y la variante de Soundex para que los resultados se puedan reproducir.