Sí, sigo usando PHP. Sí, todavía me encanta. Sí, soy viejo 😁. No, no es un lenguaje muerto. Puede que no sea la mejor elección para lo que estoy tratando de hacer aquí, pero a veces simplemente no tienes otra alternativa o tal vez solo quieres demostrar que los lenguajes son solo herramientas, y si sabes cómo usarlos, puedes lograr cualquier cosa (con compromisos que pueden ser aceptables en ciertas situaciones).
La revisión ortográfica y la autocorrección se han convertido en una parte indispensable de muchos productos y servicios de software, ayudando a los usuarios con su escritura, dando sugerencias mientras escriben, mostrando errores o mejores formas de expresar algo. Últimamente, y especialmente desde el avance de la IA, un número cada vez mayor de aplicaciones también han comenzado a ayudar a la gente a usar la puntuación correcta. Es posible que lo haya notado en herramientas de oficina y productividad, aplicaciones de mensajes de texto o plataformas sociales.
Quise profundizar un poco en el asunto, pero no encontré nada para PHP por ahí; así que decidí al menos poner las cosas en marcha y echar un vistazo a algunas formas en las que podríamos verificar y restaurar la puntuación en un entorno de desarrollo basado en PHP, da igual si se utiliza la CLI o simplemente se quiere implementar esto en el lado del servidor de una aplicación web - el escenario principal que tuve en mente durante mi investigación.
A diferencia de la corrección ortográfica - donde se puede usar tan solo una función básica que simplemente compara un texto con una lista de términos correctos y se asegura de que todo esté escrito en conformidad - la puntuación no es tan simple, porque sus reglas son un poco más complejas y también pueden variar según el contexto de la escritura y el escritor.
Generalmente, utilizamos la puntuación para dos propósitos principales: la lógica (para construir textos gramaticalmente correctos que puedan ser seguidos y entendidos) y la retórica (para ayudar al lector a moverse a través de un texto a un cierto ritmo, para transmitir un sentimiento o una idea concreta, y para ayudar con la entonación). Mientras que la primera parte puede ser manejada con bastante elegancia por los algoritmos hasta cierto punto, la segunda es en gran medida influenciada por la persona que escribe, el estilo y la naturaleza de su trabajo; identificar estas características con precisión y sin notas adicionales por parte del usuario no es una tarea fácil para una computadora digital.
Permíteme comenzar dando un ejemplo de unas cuantas oraciones, primero sin puntuación, luego con una puntuación básica para una mejor lógica y, por último, el texto escrito por alguien que usa la puntuación ampliamente para mejorar el aspecto retórico del texto.
- Sin puntuación
- Fui a la tienda hoy Fue un viaje largo y aburrido Y compré algunas cosas huevos pan y queso Maravilloso
También vi a mi amigo Juan allí Estaba buscando un regalo de cumpleaños para su hermana - Puntuación básica
- Fui a la tienda hoy. Fue un viaje largo y aburrido. Y compré algunas cosas: huevos, pan y queso. ¡Maravilloso!
También vi a mi amigo Juan allí. Estaba buscando un regalo de cumpleaños para su hermana. - Puntuación enriquecida
- Fui a la tienda hoy - fue un viaje largo y aburrido - y compré algunas cosas: huevos, pan y queso. ¡Maravilloso!
También vi allí a mi amigo Juan; estaba buscando un regalo de cumpleaños para su hermana.
Como se puede ver, además de los que no dejan lugar a dudas (por lo general, las comas y los puntos son los más fáciles de colocar), muchos de ellos dependen de los factores presentados anteriormente (como el guión o el punto y coma). Por lo tanto, podemos tratar de llegar al mejor compromiso entre lógica y retórica (una opción adecuada en contextos informales), pero también podemos dar al usuario opciones para guiar el proceso de restauración.
Dejemos estos ejemplos aquí para posterior referencia y veamos cuáles son los posibles cursos que uno podría tomar al embarcarse en tal empresa. Inicialmente, quería añadir código en el artículo, pero realicé que lo volvería demasiado grande, desordenado y no añadiría mucho. En cualquier caso, también he decidido iniciar un proyecto que implemente las soluciones aquí propuestas y adjuntaré un enlace al repositorio al final de esta página, para aquellos interesados en algo más que explicaciones.
Solución 1: solamente PHP
Probablemente la forma más laboriosa de hacer algo como esto es usando PHP puro. Quiero decir, no es difícil, pero es un poco como construir una de esas viejas computadoras mecánicas. Un montón de pequeños engranajes para ajustar y retocar hasta que funcione. La ventaja es que no se necesitará más que este código para ejecutarlo en cualquier lugar (CLI, sitio web, aplicación web). En este primer reto, el paso más importante es establecer reglas claras para cada uno de los signos de puntuación: cuándo y cómo deben aplicarse.
No es que sea un genio de la gramática (aunque era bastante bueno en el instituto), pero si tuviéramos que comenzar con el punto, una regla obvia es que la mayoría de las oraciones terminan con uno. Ahora bien - e dejando de lado las excepciones por el momento - sabemos que una oración debe tener al menos un sujeto y un predicado para tener sentido; lo que significa que necesitamos identificar las partes del texto dado. En realidad, esta será una primera acción esencial a tomar a la hora de colocar correctamente cualquier signo de puntuación en una frase.
Etiquetado de palabras
Por lo tanto, el primer componente de nuestro script de restauración debería ser lo que se llama un etiquetador de PDLO (partes de la oración): una función que identifica cada palabra en una oración y devuelve su papel más probable. Una forma sencilla de lograrlo será creando un diccionario. Podría existir como una tabla dentro de una base de datos, como un archivo de datos usando formatos como XML o JSON, o simplemente podríamos usar un arreglo. Algo como esto:
$diccionario = [
"hoy" => "adverbio", "fui" => "verbo", "a" => "preposición", "la" => "artículo",
"tienda" => "sustantivo", "fue" => "verbo", "un" => "artículo", "viaje" => "sustantivo",
"largo" => "adjetivo", "y" => "conjunción", "aburrido" => "adjetivo", "compré" => "verbo",
"algunas" => "determinador", "cosas" => "sustantivo", "huevos" => "sustantivo", "pan" => "sustantivo",
"queso" => "sustantivo", "maravilloso" => "adjetivo"
];
Sin embargo, esto no funcionará cuando se trate de cubrir un idioma completo. El arreglo sería demasiado grande y poco práctico. Una base de datos haría un mejor trabajo, especialmente si la caché también está habilitada. Otra alternativa sería una API que pueda proporcionar tal información y mucho más de un diccionario como el de la RAE. Incluso se podrían mezclar ambas fuentes (usar API cuando la palabra no se encuentre en la base de datos). Hasta ahora no he encontrado ninguna API que sirva, aunque se podrían hacer solicitudes HTTP directamente al diccionario de la RAE y luego buscar los elementos que contienen el tipo de palabra en la respuesta. Tal como lo hacen aquí o aquí
Como último recurso, también podríamos tratar de adivinar qué parte de la oración podría ser una palabra basándonos en patrones generales que a veces comparten palabras de la misma clase. Por ejemplo, las palabras que terminan en "ndo" suelen ser verbos (gerundios).
Extracción de sentencias
A menos que abordemos un escenario muy específico, y si queremos crear una solución que pueda restaurar cualquier texto dado, los casos más desfavorables deben ser tomados en cuenta. Lo peor: no hay puntuación en absoluto en el texto. Extraer frases de textos así es todo un reto. Volvamos a nuestros ejemplos y veamos una de las razones:
Fui a la tienda hoy Fue un viaje largo y aburrido Y compré algunas cosas huevos pan y queso Maravilloso¿Dónde termina una frase y dónde empieza la otra? Si nos fijamos bien, vemos que hay varios resultados posibles:
Fui a la tienda hoy. Fue un viaje largo y aburrido. Y compré algunas cosas ...
Fui a la tienda. Hoy fue un viaje largo y aburrido. Y compré algunas cosas ...
Fui a la tienda. Hoy fue un viaje largo y aburrido y compré algunas cosas ...
Para resolver el misterio, se podría solicitar la opinión del usuario en este punto. Explícitamente, mediante cuadros de aviso, ventanas emergentes o elementos menos intrusivos, como sugerencias o señales; o simplemente proporcionando configuraciones que se pueden ajustar según las preferencias de cada uno. Por ejemplo, podría haber una opción que no permita oraciones que comiencen con una conjunción (como "y") o con un adverbio (como "hoy").
En última instancia, debería haber un modo de interpretación predeterminado para aquellos que solo desean una restauración rápida y no están demasiado preocupados por todo esto; luego, un modo avanzado para que los usuarios puedan modificar diferentes ajustes hasta que obtengan exactamente lo que esperan.
Después de numerosas pruebas y errores, logré crear el siguiente diagrama de flujo inicial para el algoritmo que extraerá oraciones de un texto:
- Obtén la función de la palabra según el diccionario y su relación con las palabras circundantes
- Inserta una nueva oración en el arreglo si aún no se ha creado ninguna
- Si existe la posibilidad de que la palabra pertenezca a otra oración que no sea la actual, inserta la nueva oración en el arreglo
- Asigna valores de peso a la palabra para cada oración existente a la que pueda pertenecer
- Aumenta o disminuye estos valores en función de las palabras circundantes o de factores como la configuración del usuario
- Por último, coloca las palabras en las frases para las que tienen mayor peso y devuelve el resultado
Algunos de estos pasos son bastante fáciles de implementar, otros necesitarán más paciencia (tendremos que probar muchos escenarios de casos - las pruebas unitarias pueden ser muy útiles en esta etapa). Pero creo que esto es lo suficientemente sólido como para servirnos tanto para extraer oraciones como incluso para encontrar el lugar de cualquier otros signos de puntuación. Es un buen punto de partida.
Modificadores
Mencioné que la función de una palabra y su peso pueden ser modificados por la presencia de otras palabras. La palabra "tienda" es un sustantivo por sí misma, pero cuando tenemos la preposición "a" y el artículo "la" delante, esta palabra pasa a formar parte de un complemento circunstancial de lugar, lo que aumenta la probabilidad de que todas pertenezcan a la misma oración.
Para tener en cuenta todas estas combinaciones complejas, podemos definir modificadores para que el algoritmo haga suposiciones más precisas sobre las propiedades de una palabra. Por ejemplo, "a" viene con un modificador que aumentará la propiedad peso del siguiente sustantivo, lo que significa que hay una mayor probabilidad de que las dos palabras pertenezcan juntas a la misma oración. El modificador también puede hacer que el sustantivo sea el objeto de la misma preposición, lo que puede ser útil para averiguar otros signos de puntuación.
Habrán algunos modificadores generales para las ocurrencias más comunes (como un artículo definido, que casi siempre va seguido de un sustantivo) y luego habrán modificadores específicos que apuntan a palabras o grupos de palabras particulares. El comportamiento de los modificadores generales podría incluso estar codificado de forma rígida (a menos que queramos soporte para varios idiomas) o definid como una lista separada de estructuras que son analizadas e interpretadas por el algoritmo a nivel global. Los modificadores específicos se pueden definir dentro del diccionario, con las palabras con las que están estrechamente relacionados.
Una última cosa en la que hay que pensar es en la abstracción de tales modificadores. Lo más probable es que se almacenen en forma de una estructura (tal vez en formato JSON) que contenga varios pares de elementos clave-valor que especifiquen las palabras o clases que se ven afectadas por el modificador, la relación y el rango / distancia entre palabras, el valor del peso y, si el modificador cambia el rol de una palabra, la nueva clase. Esta estructura se cristalizará a medida que comencemos a construir nuestro diccionario y a probar el código. También permitirá la colaboración de un lingüista, que podrá trabajar en la mejora de la parte gramatical del proyecto sin tener conocimientos técnicos avanzados y sin modificar el código.
Solo por dar un ejemplo, la estructura de un modificador podría tomar tal forma cuando se establece como un arreglo PHP:
[ "a" => [ [ "+[0..2]sustantivo" ] => [ "peso" => .8, "funcion" => "adverbio" ] ] ]
Primero, la palabra que viene con el modificador, el tipo de palabras que modifica (cualquier sustantivo que siga a una distancia de máximo 2 palabras, luego el peso añadido a las palabras afectadas y finalmente la nueva función asignada a estas).
Signos de puntuación
Las oraciones pueden terminar en otros signos de puntuación, no solo en un punto. Simplemente podemos agregar la puntuación final de una oración al arreglo conteniendo sus palabras que mencioné anteriormente. Después, se construirá la frase simplemente uniendo todos los elementos, incluidos los signos de puntuación.
$oraciones = [
[ "También", "vi", "a", "mi", "amigo", "Juan", "allí", "." ],
[ "Estaba", "buscando", "un", "regalo", "de", "cumpleaños", "para", "su", "hermana", "." ]
];
Ahora que hemos planteado un posible enfoque para la extracción de oraciones y su puntuación final, pasaremos a los otros signos de puntuación. Sus posiciones dentro de una oración se puede calcular de manera similar: usando tanto modificadores generales como específicos. Por ejemplo, una enumeración de sustantivos añade una coma entre cada uno de ellos. Este es un modificador general. La palabra "cosas", seguida de una enumeración de sustantivos será seguida de dos puntos, antes de la enumeración. Este es un modificador específico que viene exactamente con esta palabra. De nuevo, a los usuarios se les pueden proporcionar diferentes configuraciones para controlar casos especiales y ajustar el comportamiento del algoritmo de acuerdo a sus necesidades.
Una última cosa a tener en cuenta es si el texto a analizar ya tiene algún signo de puntuación o no. En este caso, podríamos despojarlo de todos ellos y confiar plenamente en nuestro algoritmo, o aprovechar la puntuación existente para acelerar el proceso. Por ejemplo, los períodos existentes ayudarían a extraer las oraciones más rápido y pasar a las siguientes etapas. El usuario debería, de nuevo, tener la opción de elegir cómo la puntuación existente debe ser tratada.
... continuará