</> Cómo funciona
Dada una forma verbal, caza el infinitivo o infinitivos REGULARES de los que puede ser forma —aunque el verbo no exista en el diccionario— con su análisis (modo, tiempo, persona). Método algorítmico: genera candidatos invirtiendo el paradigma y deshaciendo las mutaciones ortográficas, y verifica cada uno conjugándolo (el conjugador es el juez). Una forma puede corresponder a varios infinitivos: se devuelven todos y se marca 'ambiguo'. El análisis viene estructurado en dos caminos por vocal temática: A (-ar) y E/I (-er/-ir). A diferencia de /v1/analizar_verbo (que busca entre los verbos reales), este caza también lo inventado: la parte generativa de la lengua.
cazador/inverso.py → función cazar(), con cazador/motor.py (el conjugador) como oráculo.flowchart TD _es_vocal["_es_vocal()"] _ajustar_raiz["_ajustar_raiz()"] _ajustar_terminacion["_ajustar_terminacion()"] _unir["_unir()"] todas_las_formas["todas_las_formas()"] _terminaciones_superficie["_terminaciones_superficie()"] _deshacer_mutacion["_deshacer_mutacion()"] generar_candidatos["generar_candidatos()"] cazar["cazar()"] fila["fila()"] fila_inf["fila_inf()"] _ajustar_terminacion --> _es_vocal _unir --> _ajustar_raiz _unir --> _ajustar_terminacion cazar --> generar_candidatos cazar --> todas_las_formas fila --> _unir generar_candidatos --> _deshacer_mutacion generar_candidatos --> _terminaciones_superficie todas_las_formas --> _unir todas_las_formas --> fila todas_las_formas --> fila_inf
# -*- coding: utf-8 -*-
"""cazar — dada una FORMA verbal, ¿de qué infinitivo(s) regular(es) es forma?
Es el reverso del conjugador, y con una gracia: caza también verbos INVENTADOS
(sirve cualquier forma bien construida, exista el verbo o no). No hay base de
datos: es álgebra pura, así que aquí está TODO el algoritmo a la vista — no hay
tesoro que esconder, solo lógica.
Idea central: GENERAR y VERIFICAR.
1) Generar candidatos: se le quita a la palabra cada terminación posible del
paradigma y se deshace la mutación ortográfica hacia atrás. Sale un conjunto
de infinitivos "sospechosos" (puede sobrar, nunca falta el verdadero).
2) Verificar: cada sospechoso se CONJUGA entero y se comprueba si la palabra
aparece en su paradigma. El conjugador es el juez. Si aparece, es válido; y
de regalo sabemos en qué casilla (modo, tiempo, persona).
Propiedad "ningún fallo": como la generación solo tiene que SOBRAR y la
verificación es exacta, el infinitivo verdadero está SIEMPRE en el resultado, y
no se cuela ni un falso positivo. No es una apuesta: es la preimagen de una
función determinista.
"""
import re
VOCALES = set("aeiouáéíóú")
FUERTES = set("aeoáéó") # vocales que forman hiato con la i (leído, caído)
def _es_vocal(c):
return c in VOCALES
# ---------------------------------------------------------------------------
# EL ORÁCULO: conjugador regular con las reglas ORTOGRÁFICAS del español.
# Estas reglas conservan la FONÉTICA de la raíz al cambiar la terminación.
# ---------------------------------------------------------------------------
def _ajustar_raiz(raiz, terminacion, clase):
"""Muta la última consonante de la raíz para que el SONIDO no cambie.
En -ar solo muta ante terminación que empieza por e/i; en -er/-ir solo ante
terminación que empieza por o/a. (Por eso 'sacar' -> 'saqué' pero 'plingar'
-> 'plingo', no 'plinjo': un -ar con terminación en o/a no muta.)
"""
f = terminacion[0]
frontal = f in ("e", "é", "i", "í")
posterior = f in ("a", "á", "o", "ó")
if clase == "ar":
if frontal:
if raiz.endswith("gu"): return raiz[:-2] + "gü" # averiguar -> averigüé
if raiz.endswith("c"): return raiz[:-1] + "qu" # sacar -> saqué
if raiz.endswith("g"): return raiz[:-1] + "gu" # pagar -> pagué
if raiz.endswith("z"): return raiz[:-1] + "c" # cazar -> cacé
else: # er / ir
if posterior:
if raiz.endswith("gu"): return raiz[:-2] + "g" # distinguir -> distingo
if raiz.endswith("qu"): return raiz[:-2] + "c" # delinquir -> delinco
if raiz.endswith("c"): return raiz[:-1] + "z" # vencer -> venzo
if raiz.endswith("g"): return raiz[:-1] + "j" # coger -> cojo
return raiz
def _ajustar_terminacion(raiz, terminacion, clase):
"""La 'i' átona entre vocales se vuelve 'y' (leyó, leyendo); tras ll/ñ se
absorbe (bulló, tañendo)."""
if clase == "ar":
return terminacion
if not re.match(r"^i[aeoáéó]", terminacion):
return terminacion
if raiz.endswith("ll") or raiz.endswith("ñ"):
return terminacion[1:] # bull + ió -> bulló
if raiz.endswith("gu") or raiz.endswith("qu"):
return terminacion # la u de gu/qu es muda: distinguieron, no distinguyeron
if raiz and _es_vocal(raiz[-1]):
return "y" + terminacion[1:] # le + ió -> leyó
return terminacion
def _unir(raiz, terminacion, clase):
"""Pega raíz + terminación aplicando las dos reglas ortográficas."""
r = _ajustar_raiz(raiz, terminacion, clase)
t = _ajustar_terminacion(r, terminacion, clase)
return r + t
PERSONAS = ["yo", "tú", "él/ella", "nosotros", "vosotros", "ellos/ellas"]
# Las terminaciones del paradigma regular, por conjugación.
TERMINACIONES = {
"ar": {"presente": ["o", "as", "a", "amos", "áis", "an"],
"imperfecto": ["aba", "abas", "aba", "ábamos", "abais", "aban"],
"preterito": ["é", "aste", "ó", "amos", "asteis", "aron"],
"presubj": ["e", "es", "e", "emos", "éis", "en"],
"imsubjRa": ["ara", "aras", "ara", "áramos", "arais", "aran"],
"imsubjSe": ["ase", "ases", "ase", "ásemos", "aseis", "asen"]},
"er": {"presente": ["o", "es", "e", "emos", "éis", "en"],
"imperfecto": ["ía", "ías", "ía", "íamos", "íais", "ían"],
"preterito": ["í", "iste", "ió", "imos", "isteis", "ieron"],
"presubj": ["a", "as", "a", "amos", "áis", "an"],
"imsubjRa": ["iera", "ieras", "iera", "iéramos", "ierais", "ieran"],
"imsubjSe": ["iese", "ieses", "iese", "iésemos", "ieseis", "iesen"]},
"ir": {"presente": ["o", "es", "e", "imos", "ís", "en"],
"imperfecto": ["ía", "ías", "ía", "íamos", "íais", "ían"],
"preterito": ["í", "iste", "ió", "imos", "isteis", "ieron"],
"presubj": ["a", "as", "a", "amos", "áis", "an"],
"imsubjRa": ["iera", "ieras", "iera", "iéramos", "ierais", "ieran"],
"imsubjSe": ["iese", "ieses", "iese", "iésemos", "ieseis", "iesen"]},
}
# Futuro y condicional se pegan al INFINITIVO entero (no a la raíz).
FUTURO = ["é", "ás", "á", "emos", "éis", "án"]
CONDIC = ["ía", "ías", "ía", "íamos", "íais", "ían"]
def todas_las_formas(infinitivo):
"""Conjuga el infinitivo entero y devuelve la lista plana de sus formas, cada
una etiquetada con (modo, tiempo, persona). Es el paradigma completo que usa
el cazador como 'verdad' contra la que verificar."""
inf = infinitivo.strip().lower()
clase = inf[-2:]
raiz = inf[:-2]
E = TERMINACIONES[clase]
formas = []
def fila(clave, modo, tiempo):
for i, t in enumerate(E[clave]):
formas.append((_unir(raiz, t, clase), modo, tiempo, PERSONAS[i]))
def fila_inf(terms, tiempo):
for i, t in enumerate(terms):
formas.append((inf + t, "indicativo", tiempo, PERSONAS[i]))
formas.append((_unir(raiz, "ando" if clase == "ar" else "iendo", clase), "no personal", "gerundio", "—"))
fila("presente", "indicativo", "presente")
fila("imperfecto", "indicativo", "pretérito imperfecto")
fila("preterito", "indicativo", "pretérito perfecto simple")
fila_inf(FUTURO, "futuro")
fila_inf(CONDIC, "condicional")
fila("presubj", "subjuntivo", "presente")
fila("imsubjRa", "subjuntivo", "pretérito imperfecto")
fila("imsubjSe", "subjuntivo", "pretérito imperfecto (-se)")
return formas
# ---------------------------------------------------------------------------
# EL CAZADOR: de la forma al infinitivo, por generar-y-verificar.
# ---------------------------------------------------------------------------
CLASES = ["ar", "er", "ir"]
def _terminaciones_superficie(clase):
"""Todas las terminaciones que pueden aparecer en SUPERFICIE (incluidas las
variantes con 'y' y las de la i absorbida). Generoso a propósito: puede
sobrar; lo que sobra lo poda la verificación."""
E = TERMINACIONES[clase]
lst = [t for clave in E for t in E[clave]]
lst.append("ando" if clase == "ar" else "iendo")
if clase != "ar":
for t in list(lst):
if re.match(r"^i[aeoáéó]", t):
lst.append("y" + t[1:]) # variante leyó / leyendo
lst.append(t[1:]) # variante bulló / tañó
return list(dict.fromkeys(lst))
def _deshacer_mutacion(raiz, terminacion, clase):
"""Inverso de _ajustar_raiz: dada la raíz de superficie, ¿de qué raíz(es)
subyacentes pudo salir? (qu <- c, gü <- gu, gu <- g, c <- z; y en -er/-ir:
z <- c, j <- g, g <- gu, c <- qu)."""
f = terminacion[0]
out = []
frontal = f in ("e", "é", "i", "í")
posterior = f in ("a", "á", "o", "ó")
if clase == "ar" and frontal:
if raiz.endswith("qu"): out.append(raiz[:-2] + "c")
if raiz.endswith("gü"): out.append(raiz[:-2] + "gu")
elif raiz.endswith("gu"): out.append(raiz[:-2] + "g")
if raiz.endswith("c"): out.append(raiz[:-1] + "z")
if clase in ("er", "ir") and posterior:
if raiz.endswith("z"): out.append(raiz[:-1] + "c")
if raiz.endswith("j"): out.append(raiz[:-1] + "g")
if raiz.endswith("g"): out.append(raiz[:-1] + "gu")
if raiz.endswith("c"): out.append(raiz[:-1] + "qu")
return out
def generar_candidatos(palabra):
"""Superconjunto de infinitivos que PODRÍAN producir la palabra. Para cada
clase y cada terminación de superficie que encaje, se propone la raíz por
identidad y las raíces de la de-mutación."""
cands = set()
for clase in CLASES:
for t in _terminaciones_superficie(clase):
if len(palabra) > len(t) and palabra.endswith(t):
raiz = palabra[: -len(t)]
cands.add(raiz + clase)
for r in _deshacer_mutacion(raiz, t, clase):
cands.add(r + clase)
return cands
def cazar(palabra):
"""Caza el/los infinitivo(s) del que la palabra es forma, con su análisis.
Recorre los DOS CAMINOS de vocal temática (A = -ar, E/I = -er/-ir): dentro de
E/I, -er y -ir solo divergen en cuatro puntos; en el resto son el mismo verbo,
así que se verifican las dos raíces. Nada se poda: se devuelven TODOS los
infinitivos que de verdad la producen (una forma puede tener varios).
"""
w = palabra.strip().lower()
resultado = []
for inf in generar_candidatos(w):
if len(inf) < 3:
continue
# VERIFICACIÓN: conjugar el candidato y ver si la palabra está.
casillas = [
{"modo": m, "tiempo": ti, "persona": p}
for (forma, m, ti, p) in todas_las_formas(inf) if forma == w
]
if casillas:
resultado.append({"infinitivo": inf, "conjugacion": inf[-2:], "casillas": casillas})
resultado.sort(key=lambda a: a["infinitivo"])
infinitivos = [a["infinitivo"] for a in resultado]
return {"forma": w, "es_forma": bool(infinitivos),
"ambiguo": len(infinitivos) > 1, "infinitivos": infinitivos, "analisis": resultado}
Llámala con el código a la vista.
curl "https://api.wordkers.com/v1/cazar?forma=casa" \
-H "X-API-Key: TU_CLAVE"