Amores perros, textofilia y fundamentos de programación (en proceso)

Índice

Introducción

Qué es y qué no es este manual

1. El clásico «¡Hola, mundo!»

2. La dinámica input-output

3. ¡Dime cuántas cuartillas, palabras y caracteres son!

4. Una idea para hacer diccionarios o glosarios

Ha llegado la hora de partir


Introducción

¡Bienvenido! En este manual aprenderemos a programar en Ruby para procesar texto. En otros términos, ¡haremos que nuestros documentos se adapten a nuestras necesidades!

Con mucha probabilidad en varias ocasiones te has encontrado con tareas repetitivas y un montón de líos al momento de procesar texto. ¿Eres tú? ¿Es el universo? ¿Es porque incluso ni tú sabes qué es lo que quieres? Quizá solo sea que, en lugar de adaptarte a formatos o programas, sea necesario que estos se ajusten a tus necesidades y te inventes metodologías.

En este manual se verá una introducción de cómo, a través de Ruby, es posible eliminar algunas de las pesadillas al momento de procesar texto.

Los siguientes ejercicios fueron elaborados para el cuarto ciclo (2019-2) del Taller de Edición Digital, impartido en el Rancho Electrónico. «Amores perros» fue como Hacklib y este can decidimos titular esta serie de talleres debido a que aquí mostraré una de mis actividades favoritas: hacer un desastre con los formatos, los estándares y los programas.

Así que este manual consiste en una serie de ejercicios pensados para empezar a procesar texto lo más pronto posible:

  1. El clásico «¡Hola, mundo!»

  2. La dinámica input-output

  3. ¡Dime cuántas cuartillas, palabras y caracteres son!

  4. Una idea para hacer diccionarios o glosarios

Este perro con el entusiasmo de que uses este manual.
Este perro con el entusiasmo de que uses este manual.

Qué es y qué no es este manual

En este manual no encontrarás:

En este manual sí encontrarás:

1. El clásico «¡Hola, mundo!»

Como primer ejercicio, ¿qué te parece si abres tu editor de texto preferido? El editor puede ser uno que ya tengas instalado en tu sistema como Gedit, Bloc de Notas o TextEdit. Si no cuentas con ninguno o quisieras probar con uno, puedes empezar con Geany, un editor de texto multiplataforma.

Dentro de un nuevo documento escribe:

                puts "¡Hola, mundo!"
            

Ahora guardamos el documento como hola-mundo.rb en un directorio cuya ubicación conozcas. A continuación, desde la terminal:

  1. Ve al directorio donde tienes guardado el archivo.

  2. Escribe ruby hola-mundo.rb.

Editor Gedit y terminal Bash abiertos para imprimir «¡Hola, mundo!».
Editor Gedit y terminal Bash abiertos para imprimir «¡Hola, mundo!».

Si todo salió bien, en la terminal se tiene que haber impreso la oración «¡Hola, mundo!». Si no fue así:

  1. Verifica que estés en el directorio donde está el archivo. Desde la terminal ejecuta pwd para imprimir la ruta donde te encuentras y ls para ver los ficheros que contiene. Si no está ahí hola-mundo.rb quiere decir que lo has guardado en otra ubicación.

  2. Comprueba que tienes instalado Ruby. Desde la terminal ejecuta ruby -v. Si no se imprime el nombre de versión de Ruby que tienes en tu sistema, quiere decir que no lo tienes instalado. Para su instalación, revisa esta sección de Aprende a programar con Ruby de RubySur.

Impresión de pwd, ls y ruby -v en la terminal.
Impresión de pwd, ls y ruby -v en la terminal.

El ejercicio «¡Hola, mundo!» es un tanto aburrido. Pero es así como muchos manuales de programación comienzan… así que ahí está: tu primer script de Ruby.

Aunque sin mucho chiste, la línea de código puts "¡Hola, mundo!" tiene dos elementos relevantes para comentar:

  1. Ruby cuenta con una serie de métodos ya definidos que podemos usar para realizar alguna tarea específica. El método puts es uno de ellos, el cual nos permite imprimir en la terminal algún tipo de dato.

  2. El resto de la línea es el tipo de dato que imprimimos en la terminal. Con las comillas rectas simples ' o dobles " es como indicamos una cadena de caracteres. Estas cadenas (String, por su nombre en inglés) son un tipo de dato que utilizamos para referirnos a lo que de manera habitual catalogamos como «texto».

¿Son las cadenas de caracteres lo mismo que el texto? Mmm, no lo creo. Me parece un reduccionismo considerar que el texto es —de manera exclusiva o principalmente— un conjunto de caracteres. No obstante, la mayoría de los lenguajes de programación así lo entienden, lo procesan y lo modifican. ¿Cómo sería posible un tipo de dato textual que tome en cuenta otros elementos además de sus caracteres? ¿Para qué nos serviría?

A lo largo de este manual empezaremos a ver otros tipos de datos. Lo relevante aquí es que ¡ya sabes cómo colocar texto en Ruby e imprimirlo en la terminal! Dos habilidades básicas para el procesamiento de texto.

2. La dinámica input-output

La dinámica habitual para procesar texto comienza con algunas cadenas de caracteres u otro tipo de dato o con uno o varios documentos —todo esto se conoce como input— que a través de un lenguaje de programación se lee su contenido, se analiza su información y se extraen o se modifican los elementos indicados. Lo que realiza el lenguaje de programación, que en este manual es Ruby, es lo que llamamos procesamiento.

Una vez cumplidos esos pasos se arroja un resultado —lo que se denomina output— en forma de cadenas de caracteres, conjuntos (Array), objetos (Object), números enteros (Integer), números decimales (Float) u otro tipo de dato. Estos pueden imprimirse en nuestra terminal, almacenarse en la memoria o guardarse en uno o más documentos. Como esta dinámica es muy habitual, tiende a abreviarse como IO porque vamos de un input, pasando por la magia de la programación (?) para concluir con un output.

Además, esta dinámica puede repetirse una y otra vez hasta lograr el resultado deseado. Este tipo de encadenamiento implica que los outputs pueden ser inputs para posteriores rutinas de procesamiento.

Diagrama de la dinámica input-output.
Diagrama de la dinámica input-output.

¿Recuerdas nuestro primer ejercicio? El input fue una cadena de caracteres cuya contenido es "¡Hola, mundo!". El procesamiento fue un simple traslado de la cadena en el archivo hola-mundo.rb a la terminal. El output fue la cadena de caracteres impresa en la terminal. En otros ejercicios veremos ejemplos más complejos de IO, lo importante aquí es que incluso ese simple ejercicio ya implica la dinámica input-output.

¿Te parece complicado? En la edición tenemos una analogía que nos permite ilustrarlo de otra manera. Al momento de editar texto nosotros requerimos de alguna clase de documento por el cual comenzar a trabajar: esto sería nuestro input. Con esta información empezamos la tarea de edición, diseño, cotejo e impresión. Todos estos pasos son un tipo de procesamiento para producir una publicación, que en este caso se trataría de nuestro output. Esta salida se distribuye o se comercializa para que el lector lo tenga a su alcance, de la misma manera que el output se imprime en la terminal o se almacena de alguna manera para que esté al alcance del usuario. Por último, cualquier output puede servir para ediciones posteriores u «obras derivadas», por lo que se convierte en input para otros procesos de reproducción, como los de la edición.

Diagrama de procesos editoriales.
Diagrama de procesos editoriales.

La analogía puede sonar forzada; sin embargo, la edición consiste en una serie de procesos. Es decir, la edición es un método al mismo tiempo que es una profesión y un arte. Si se pone énfasis en esto, empezaremos a ver muchas semejanzas entre un método para hacer libros y un método para procesar texto usando lenguajes de programación.

Esta dinámica es fundamental tenerla en cuenta porque es así como se trabaja el procesamiento de texto. Con un pleno conocimiento del input y una idea clara del output se obtiene un gran control y menos problemas al momento de pensar en la magia necesaria para ir de uno al otro a través del procesamiento de texto con lenguajes de programación.

3. ¡Dime cuántas cuartillas, palabras y caracteres son!

En este ejercicio vamos a desarrollar un script para indicar el total de cuartillas según un número determinado de palabras o de caracteres.

Muestra del ejercicio de conteo de cuartillas.
Muestra del ejercicio de conteo de cuartillas.

Para cumplir este objetivo vamos a crear un documento que nos servirá de experimento. Para ello:

  1. Abre un nuevo documento en tu editor de textos favorito.

  2. Copia de tres a cinco párrafos de texto —puedes usar algún artículo aleatorio de la Wikipedia—.

  3. Pega el texto en el archivo.

  4. Guarda el documento como prueba.txt en una ubicación conocida.

Muestra del documento prueba.txt.
Muestra del documento prueba.txt.

¡Con eso ya tenemos listo nuestro input! El archivo de entrada es un documento de texto plano. En otros ejercicios veremos cómo usar otro tipo de formatos como ODT o DOCX. Lo importante a resaltar aquí es que Ruby puede leer de manera nativa cualquier archivo en formato de texto plano, incluyendo lenguajes de marcado. Ejemplos de este tipo de formatos tenemos TXT, MD, HTML, XML, JSON, etcétera.

¿Qué pasa con los formatos que Ruby no puede leer de manera nativa? Con la inclusión de gemas podemos solucionarlo, pero eso es otra historia…

Ya contamos con nuestro input y sabemos que se trata de un documento TXT con una serie de párrafos. Pero ¿cuál será nuestro output? Como lo sospechas, el output consistirá en una impresión en la terminal que nos indique el número de cuartillas.

Con estos dos elementos ya definidos ahora es momento de empezar con la magia que nos llevará del input al output a través de una serie de procesos. ¿Sabes cuáles pasos pueden ser?… Con Ruby tenemos que:

  1. Obtener el contenido del archivo, lo cual en Ruby se conoce como leer.

  2. Dividir el documento por palabras y por caracteres.

  3. Definir cuántas palabras y cuántos caracteres son necesarios para determinar una cuartilla.

  4. Calcular el resultado, el cual se basa en una regla de tres que nos dará un número decimal.

  5. Imprimir el resultado, el cual se convertirá en una cadena de caracteres para poderlo mostrar.

Diagrama del ejercicio de conteo de cuartillas.
Diagrama del ejercicio de conteo de cuartillas.

¡Empecemos a hacer el código de estos pasos! Para eso:

  1. Abre un nuevo documento en tu editor de textos favorito.

  2. Guarda el documento como ted_sesion_1.rb en la misma ubicación donde está el archivo prueba.txt.

  3. Mantén tu editor de textos abierto.

  4. Abre la terminal.

  5. Ve a la ubicación de los ficheros desde la terminal.

  6. Comprueba que ambos ficheros estén en la misma ubicación con el comando ls —te tiene que imprimir el nombre de ambos archivos—.

Estado actual del ejercicio; por un lado la terminal y por el otro el editor de textos.
Estado actual del ejercicio; por un lado la terminal y por el otro el editor de textos.

Ahora comencemos a escribir —evita en lo posible el copiar y pegar, para que así practiques la redacción de código— lo siguiente en nuestro archivo ted_sesion_1.rb:

                # File.read lee el archivo de texto y me genera un objeto de textoprueba = File.read('prueba.txt')puts prueba
            

Para hacer un prueba rápida vamos a guardar el documento y en nuestra terminal ejecutaremos el script al escribir y presionar Enter después de lo siguiente:

                ruby ted_sesion_1.rb
            
Resultado de la primera prueba de ejecución de ted_sesion_1.rb.
Resultado de la primera prueba de ejecución de ted_sesion_1.rb.

La prueba tuvo que haber impreso todo el contenido de prueba.txt. Si no es tu caso, revisa la sintaxis, tu ubicación en la terminal y la ubicación de los dos archivos con los que estamos trabajando…

La impresión no es el output que estamos esperando. Sin embargo, es un buen indicativo de que al fin estamos tomando el contenido del documento TXT para imprimirlo como cadena de caracteres en la terminal. Así que analicemos un poco lo que hemos escrito.

Línea 1. Aquí tenemos una explicación que comienza con una almohadilla (#). Las almohadillas nos ayudan para indicar comentarios dentro del código. Estos serán ignorados por la computadora pero sirven de apoyo para quien escribe o estudia el código. Por el momento puede no tener mucho valor, pero su importancia estriba en poder explicar lo que está haciendo el código. Quizá ahorita lo tengas fresco, pero hay que pensar en tu yo del futuro o, mejor aún, en posibles colaboradores.

Cabe resaltar que, como los comentarios no afectan la ejecución del código, eres libre de colocar u obviar cualquier clase de comentario en tus ejercicios. La recomendación es que los escribas con tus propias palabras.

Aunque se trate de código para tu uso personal o en el que eres el único desarrollador, la redacción de código tiene una característica relevante respecto a otros tipos de redacción: su escritura deja abierta la puerta para que otro la continúe. La insistencia en usar comentarios, que encontrarás en cualquier manual, supone ya que el código es una «obra abierta». Es decir, hacer código es una tarea que nunca acaba y que siempre puede modificarse —incluso de manera fundamental— para cumplir la misma función pero con menos recursos o adaptado a nuevos contextos. Este tipo de redacción, esta visión sobre el texto y esta invitación a su constante cambio no son comunes en el tipo de trabajo al que el editor está habituado. Tal vez la escritura de código pueda incentivar al editor a desacralizar las categorías de «autor» y de «obra» que en algunas ocasiones interfieren con el cumplimiento de al menos una de las funciones de las publicaciones: la accesibilidad a determinado público lector.

Linea 2. Aquí están sucediendo dos cosas. Del lado derecho del símbolo = tenemos el método propio de Ruby para leer el texto: File.read. Este método requiere de un parámetro que se coloca entre paréntesis. Este parámetro es el documento de texto del cual Ruby extraerá su contenido a través de un proceso de lectura, que en este ejercicio es prueba.txt.

Del lado izquierdo del símbolo = estamos pasando la lectura del documento a una variable. ¿Qué es una variable? Es algo así como un nombre que sirve para denotar algún tipo de dato.

Por ejemplo, en la edición escuchamos muchas veces hablar sobre si ya están listos los forros, la legal o las preliminares. Aunque en cada libro cada uno de estos elementos varia en su contenido, se tiene la expectativa de que se tratan de unos tipos de datos constantes en una publicación impresa. Es decir, usamos estos términos como variables constantes dentro de nuestro entorno aunque su contenido cambie. Lo mismo sucede en las variables que usamos en un archivo de código: las utilizamos para simplificar nuestro trabajo; son constantes pero de contenido variable, lo que facilita aglutinar en un término un conjunto de elementos que lo componen.

En este caso la variable prueba contiene el texto extraído de prueba.txt a partir del uso del método File.read. Con esto, cada vez que llamemos a prueba, estaremos llamando al resultado del método File.read('prueba.txt').

Pero ¡cuidado! Esto puede hacerte pensar que el símbolo = significa algo así como «prueba es igual a File.read('prueba.txt')». ¡No es así! El símbolo = en programación por lo usual no se usa como un operador de igualdad entre elementos, sino uno de asignación. Por ello, la lectura debería ser: «prueba designa a File.read('prueba.txt')». En muchos lenguajes de programación la igualdad se expresa como ==, mientras que la identidad —igualdad estricta— como ===; en estos dos casos se usan como operadores lógicos, los cuales veremos luego en otro ejercicio.

Línea 3. Aquí solo hay un espacio en blanco. Es una línea un tanto aburrido a no ser que nos ayuda a observar una cuestión importante de legibilidad. Si bien la computadora hace caso omiso a la gran mayoría de los espacios que pongamos en nuestros archivos de código, el espaciamiento nos permite mejorar la presentación del contenido. Esto puede parecer poco importante; sin embargo, la ausencia de espacio puede dificultar la lectura y la inteligibilidad del código. Con decirte que el espaciado es tan relevante que en algunos casos ya no solo se emplea por funcionalidad, sino también por estética, cuyo principal punto de relieve es la poesía de código.

Línea 4. Aquí nos encontramos con algo familiar: el método puts. En esta línea le estamos indicando a Ruby que imprima en la terminal la variable prueba. Como vimos en la línea 1, esta variable designa a File.read('prueba.txt'), el método de lectura de documentos de Ruby. Por lo tanto, debido a esta línea nosotros podemos ver todo el contenido de prueba.txt en la terminal cuando ejecutamos ruby ted_sesion_1.rb. ¿Maravilloso, cierto?

En la edición no es común hablar sobre la pertinencia de formatos abiertos. De hecho, la mayoría de los editores no saben qué es esto Un formato abierto es aquel documento que no necesita de ningún programa en específico para ser abierto. El formato TXT, como muchos otros, puede ser abierto en cualquier editor, porque es un formato abierto. El formato INDD solo puede ser abierto con Adobe InDesign porque se trata de un formato cerrado, también conocido como propietario. Entre muchos inconvenientes, los formatos cerrados impiden —sea por vía técnica o legal— su procesamiento de manera independiente al uso de su programa. ¿Ves aquí el inconveniente? No es posible usar la información de la manera que más nos convenga y, por ese motivo, nuestro flujo de trabajo queda sujeto a las posibilidades y permisiones del programa que usamos. Algo que no sucede si utilizas formatos abiertos…

Sigamos con nuestro ejercicio. Para continuar vamos a eliminar el contenido de la línea 4, ya que la usamos para probar que todo estuviera en orden. En su lugar colocaremos lo siguiente:

                # El objeto de texto es dividido y me genera un conjuntoprueba = prueba.split(/\s+/)
            

El archivo ted_sesion_1.rb tiene que lucir más o menos así:

                # File.read lee el archivo de texto y me genera un objeto de textoprueba = File.read('prueba.txt')# El objeto de texto es dividido y me genera un conjuntoprueba = prueba.split(/\s+/)
            

Vamos a hacer una nueva prueba, por lo que en la línea 6 pondremos un puts prueba. Ahora pasamos a ejecutar de nuevo ruby ted_sesion_1.rb, cuyo resultado tiene que ser ¡la impresión de cada palabra de prueba.txt!

Resultado de la segunda prueba de ejecución de ted_sesion_1.rb.
Resultado de la segunda prueba de ejecución de ted_sesion_1.rb.

Analicemos lo que hemos escrito ahora.

Línea 4. Aquí —una vez eliminado el método puts— tenemos un nuevo comentario sobre lo que hace la siguiente línea de código, así que pasemos a revisarla.

Línea 5. Aquí tenemos de nuevo una asignación. La variable prueba designa a la misma variable prueba… pero con una diferencia. En Ruby contamos con un método propio que nos permite separar una cadena de caracteres según alguna regla de operación. Este método es split y su regla se escribe a modo de parámetro dentro de un paréntesis. Hasta aquí sabemos que «prueba designa a una separación de prueba».

Esto puede ser un tanto confuso, así que vayámonos con calma. Todas las variables sí o sí son siempre algún tipo de dato. ¿Cuál tipo de dato es prueba si este designa a File.read('prueba.txt')? Lo que tenemos en nuestro documento prueba.txt es texto y el método File.read lo único que hace es leerlo. Por lo tanto, el tipo de dato de la variable prueba.txt es una cadena de caracteres.

Hay que tener cuidado. Se trata de un String, sin importar los saltos de línea que tengamos en nuestro documento o que bajo nuestra mirada el contenido de prueba.txt sea más que una cadena de caracteres. La computadora trata al contenido de este documento como una sola cadena debido a que para esta el texto se define como una cadena de caracteres, incluyendo espacios y saltos de líneas.

Por ejemplo, para nosotros lo siguiente puede interpretarse como dos cadenas de caracteres:

                Esto es una cadena.Esto es otra cadena.
            

Sin embargo, para la máquina es una sola cadena de caracteres porque los espacios (representados a continuación como ·) y los saltos de línea (), aunque se traten de caracteres no imprimibles, son, valga la redundancia, caracteres:

                Esto·es·una·cadena.¶Esto·es·otra·cadena.
            

Por este motivo es posible usar el método split. Nuestra variable prueba incluye todo el contenido de nuestro documento prueba.txt como una sola cadena de caracteres. Lo que lleva a cabo el método split es separar esta única cadena de caracteres según una regla de operación que se escribe a modo de parámetro dentro de los paréntesis: split(regla).

¿Cuál es esta regla? Aquí es donde empieza una gran discusión, ¿qué es una palabra? Si solo tomamos en cuenta la sintaxis del documento —es decir, el texto como cadena de caracteres—, ¿cómo podemos definir lo que es una palabra?

Nuestras definiciones habituales del término «palabra» involucran más que su sintaxis. En el caso en español no es tan complicado porque tendemos a dividir cada palabra por algún tipo de espacio. Sin embargo, hay casos donde tenemos dos palabras unidas por un guion, por ejemplo «teórico-práctico». Si nosotros estableciéramos como regla de operación que una nueva palabra viene después de cualquier espacio, las palabras «teórico-práctico» se tratarían como una sola palabra.

Para solventar este inconveniente podemos añadir una regla de operación adicional: una nueva palabra también viene después de un guion. No obstante, con esta regla de operación tendríamos que la palabra «anti-Mussolini» se trataría como ¡dos palabras!

Entonces, para evitar este problema, podemos redefinir esta regla de operación: una nueva palabra también viene ocasionalmente después de un guion. Sin embargo, ¿cómo podremos determinar en cuáles ocasiones sí y en cuáles no? Como las cadenas de caracteres es un tratamiento sintáctico del texto, la manera más sencilla de hacerlo sería utilizando un diccionario para contrastar cada uno de los casos…

En la gran mayoría de las situaciones esto es engorroso e impráctico. Para un conteo de cuartillas según su número de palabras, ¿qué tan relevante es la exactitud? No es que en español, por ejemplo, existan muchos casos de palabras unidas por guiones en lugar de estar separadas por espacios.

Entonces, para que el remedio no salga más caro que la enfermedad, se partirá del supuesto de que cada palabra se delimita por espacios. Se trata de una solución aproximada, es verdad, pero es la más común al menos para nuestra lengua. No es que seamos flojos y que esto sea un caso aislado, sino que es la manera en como muchas veces se opera: la practicidad ante todo, si y solo si la exactitud no es necesaria —como puede pasar si se busca realizar un análisis lingüístico—.

¿Ves aquí el gran inconveniente? Existe un desfase entre lo que nosotros entendemos por «texto» y por «palabra» y lo que la máquina es capaz de procesar. Aunque se piense que es una limitante de la computadora, pienso que nos permite observar varios de los matices que están en esos dos términos. Por un lado hace patente que, en un puro nivel sintáctico, la máquina aún no tiene posibilidades de suplantar el trabajo de cuidado editorial. Por el otro, demuestra que la manera en como nosotros entendemos el texto no es igual a como la computadora lo procesa. Existen muchas coincidencias, pero en estos desfases quizá se puede empezar a vislumbrar que, pese a ser el mismo objeto, su definición y su uso difiere según quién o qué lo trate. ¿Qué podríamos extraer de esto? ¿Hacia dónde podríamos ir si el texto se tratara como un tipo de dato más complejo y no solo como una cadena de caracteres? ¿Cómo repensaríamos el texto si nos percatamos que no es un monolito, sino que sus características y su constitución dependen de sus usos?

Ahora bien, estamos siendo prácticos pero aún así tenemos un problema por resolver. En nuestra regla de operación cada palabra está delimitada por un espacio. Pero ¿qué es un espacio? Por fortuna, en un nivel sintáctico sí es posible distinguir entre distintos tipos de espacios. Los más habituales son:

Pese a que sean distintos tipos de espacios, en RegEx existe un caracter especial para denotarlos a todos: el «espacio en blanco» (whitespace). Este caracter se usa para indicar cualquier tipo de espacio normal, de salto de línea o de tabulador. La pertinencia de uso es que con un solo caracter especial tendremos la posibilidad de llamarlos a todos. En RegEx este caracter se escribe como \s.

Quizá hayas notado que esta solución es solo parcial. ¿Qué pasa con otro tipo de espacios, como el espacio de no separación? Por desgracia, otros tipos de espacios presentan algunos de estos inconvenientes:

Debido a esto, de nueva cuenta nos percatamos que nuestra regla de operación para separar palabras es más inexacta de lo esperado. Pero, vamos, ¿qué tan común es encontrar otro tipo de espacios en un texto? En este ejercicio estamos haciendo a un lado el aspecto tradicionalmente controlador de la profesión editorial. Y no solo nosotros, sino prácticamente cualquier programa que utilizas para contar palabras en tu día a día…

El método split permite incluir literales o expresiones regulares como sus parámetros. Un literal es, valga la redundancia, una regla de operación a tratar de manera literal. Si nosotros indicáramos split('a'), la cadena de caracteres sería dividida cada caracter a. Como puedes observar, un literal se delimita como cualquier tipo de cadena de caracteres: con comillas rectas simples (') o dobles (").

Para indicar que estamos tratando con expresiones regulares, el parámetro se delimita con diagonales (/). Entonces, para indicar una división con el caracter especial de espacio en blanco de RegEx, escribimos split(/\s/).

Pero, ¿qué hay del símbolo + en lo escrito en esta línea? Aquí hemos colocado un split(/\s+/) y no un split(/\s/), ¿por qué?

Aunque en un mundo ideal todas las personas escriben solo un espacio de separación para delimitar palabras, en varios casos sucede lo contrario. Nosotros podemos pensar en que siempre tendremos casos como este:

                Cada·palabra·dividida·por·un·espacio,·¿será?
            

Sin embargo, tendemos a toparnos con casos como este:

                Cada·palabra··dividida··por·un···espacio,·····¿será?
            

Para evitarnos cualquier inconveniente o cualquier funcionamiento inesperado en nuestro código, es buena práctica contemplar cualquier tipo de descuido en el formato que pueda tener nuestro input. En varias ocasiones no podrás considerar todos los posibles horrores en el formato, pero si ya tienes en mente alguna posibilidad desastrosa, te recomiendo que no la dejes de lado al momento de escribir código.

Cuando se trabaja con el texto como cadenas de caracteres se empieza a ser más visible un gran problema que tenemos en la edición: la mayoría de los editores no saben cómo formatear de manera adecuada sus documentos. Parecerá una exageración, pero un formato inadecuado tiene el potencial de impedir un procesamiento automatizado y multiformato del texto. Un ejemplo claro lo tenemos al momento de exportar o convertir libros electrónicos: la calidad técnica y editorial de este tipo de publicaciones es inversamente proporcional a la cantidad de descuidos en el formato. No es un problema menor, la mayoría del tiempo es la falta de cuidado en el formato por parte del editor lo que estropea su trabajo. ¿Quién tiene o no el control en la edición? Por lo general se regirá por la calidad en el formateo del texto.

En este caso, el descuido puede ser una adición innecesaria de espacios en blanco. Para evitarlo, es necesario especificar en RegEx que el espacio en blanco puede aparecer una o más veces consecutivas para que así uno o diez espacios continuos, por ejemplo, se consideren de la misma manera. El símbolo para expresar «una o más veces» en RegEx —de manera formal es uno de los distintos tipos de cuantificadores que existen en este lenguaje— es +.

Con esto, el código prueba = prueba.split(/\s+/) presente en la línea 5 puede leerse como «prueba designa a una separación de prueba a partir de uno o más “espacios en blanco” de RegEx».

Tenemos otras dos consideraciones para terminar de comprender lo que se está haciendo en esta línea. La primera es que, como lo has notado con el método File.read, la sintaxis de los métodos de Ruby se concatenan a la clase con la que se desea trabajar por medio de un punto (.). En File.read tenemos la clase File y el método read, así como en prueba.split(/\s+/) está la clase String y el método split. ¿Por qué prueba se considera una clase String? Porque desde la línea 2 hemos hecho de la variable prueba un tipo de dato determinado: una cadena de caracteres. De manera formal los tipos de datos se conocen como clases en Ruby.

La segunda consideración es que ciertos métodos de Ruby pueden convertir el tipo de clase. Por ejemplo, si el método split trabaja sobre una cadena de caracteres para dividirla, ¿qué tipo de dato daría como resultado? No puede ser una cadena de caracteres ya que la división genera varias cadenas de caracteres. Para aglutinar una cantidad de datos, sin importar su tipo, nosotros tenemos la clase Array. Los conjuntos son un tipo de dato que nos permite tener una serie de elementos. Más adelante empezaremos a ver más qué son los conjuntos, por el momento es importante tener en cuenta que en la línea 5 nuestra variable prueba ha pasado de ser una cadena de caracteres a convertirse en un conjunto de varias cadenas de caracteres, donde cada uno de estos elementos es una palabra según la regla de operación que definimos con el método split.

¡Uf! Vaya explicaciones, es un tanto complejo todo lo que sucede en un par de líneas de código, ¿cierto? Pero no es nada que sea inalcanzable.

Ya contamos con un conjunto de palabras, ahora empezaremos el proceso de división por caracteres. Para ello, primero elimina el puts prueba de la línea 6 porque ya no lo necesitamos. A continuación, de la línea 6 a la 8 vamos a escribir:

                # El objeto de texto es dividido por caracterescaracteres = File.read('prueba.txt').split('')
            

El archivo ted_sesion_1.rb tiene que lucir más o menos así:

                # File.read lee el archivo de texto y me genera un objeto de textoprueba = File.read('prueba.txt')# El objeto de texto es dividido y me genera un conjuntoprueba = prueba.split(/\s+/)# El objeto de texto es dividido por caracterescaracteres = File.read('prueba.txt').split('')
            

Para hacer una prueba, vamos a colocar un puts caracteres en la línea 9. En nuestro editor guardamos el archivo y en la terminal lo ejecutamos.

Resultado de la tercera prueba de ejecución de ted_sesion_1.rb.
Resultado de la tercera prueba de ejecución de ted_sesion_1.rb.

Analicemos ahora las tres líneas añadidas.

Línea 6. Se trata de una simple línea en blanco para cuidar la legibilidad del código.

Línea 7. Se trata de un comentario para cuidar la inteligibilidad de lo que hemos hecho.

Línea 8. Aquí tenemos una serie de elementos ya familiares, como son:

En Ruby y muchos otros lenguajes de programación siempre es posible escribir las mismas funciones de diferente manera. Como observas en esta línea, en lugar de separar las operaciones de lectura y de división —como el caso de prueba en las líneas 2 y 5— hemos optado por una sintaxis más sintética.

¿Recuerdas cuando mencionamos que es posible concatenar métodos? Pues en esta línea decidimos unir los métodos read y split por medio de un punto .. Detengámonos un poco a analizar lo que sucede a través de estas concatenaciones.

La clase File funciona para los tipos de datos que son documentos. Mediante esta clase es posible llamar a casi cualquier tipo de archivo que tengamos en nuestro sistema. Para ello tenemos que decidir cuál método elegir para trabajar con el documento y, por supuesto, hay que indicar su ubicación y nombre. Por eso empleamos el método read y el parámetro 'prueba.txt'. Con ello Ruby lee el archivo prueba.txt para obtener su contenido como una cadena de caracteres. A continuación esta cadena la dividimos con el método split. Al hacerlo nuestra cadena de caracteres pasa a ser un conjunto de cadenas. Todo este proceso queda asignado a la variable caracteres. Por este motivo cuando imprimimos la variable ¡nos muestra cada caracter en una nueva línea en la terminal.

Ahora bien, de seguro tienes la siguiente pregunta: ¿cuál es la regla de operación que usamos para dividir la cadena de caracteres por cada caracter? En un primer momento observamos que el método split que usamos aquí está indicando una división literal y no mediante expresiones regulares. No se están empleando las diagonales /, como se usa para llamar a una fórmula de RegEx, por lo que no estamos utilizando ningún tipo de caracter especial para nuestra regla de operación.

Con el uso de comillas simples ' estamos indicando que la regla de operación es mediante un literal. Pero como puedes percatarte, literalmente no estamos delimitando nada porque '' es igual a un parámetro vacío…

Esto puede ser confuso; sin embargo, queda más claro si nos preguntamos: ¿cómo se delimita un caracter? ¿Qué hay entre cada caracter que lo distingue de otro caracter? En realidad nada. No es que, por ejemplo, usemos guiones para dividir caracteres como v-a-r-i-o-s- -c-a-r-a-c-t-e-r-e-s. Entonces, cuando usamos el método split con un parámetro vacío '' lo que le estamos indicando a Ruby es que divida la cadena de caracteres cada caracter consecutivo… Así de sencillo.

Con los dos conjuntos, uno de palabras con la variable prueba y otro de caracteres con la variable caracteres, ya estamos listos para definir el tamaño de una cuartilla según el número de palabras o de caracteres.

No existe consenso sobre la cantidad exacta de palabras o de caracteres para definir una cuartilla. Según el idioma, la editorial o el profesionista encontraremos distintos criterios. No obstante, sí existe un rango por el cual se define la cuartilla a partir de alguno de estos dos elementos.

Para el caso de las palabras el rango va de 220 a 250 palabras por cuartilla. En este ejercicio optaremos por un punto medio, por lo que una cuartilla será definida cada 235 palabras.

Para el caso de los caracteres el rango va de 1600 a 1800 caracteres con espacios. También elegiremos un punto medio por lo que una cuartilla será definida cada 1700 caracteres.

Ahora es momento de formalizar estas definiciones para incluirlas en nuestro script. Lo primero que haremos es eliminar el puts caracteres de la línea 9 porque ya no lo necesitamos. A continuación en esa línea y las siguientes escribimos:

                # Definición de tamaños de cuartillacuartilla_palabra    = 235.0cuartilla_caracteres = 1700.0
            

El archivo ted_sesion_1.rb tiene que lucir más o menos así:

                # File.read lee el archivo de texto y me genera un objeto de textoprueba = File.read('prueba.txt')# El objeto de texto es dividido y me genera un conjuntoprueba = prueba.split(/\s+/)# El objeto de texto es dividido por caracterescaracteres = File.read('prueba.txt').split('')# Definición de tamaños de cuartillacuartilla_palabra    = 235.0cuartilla_caracteres = 1700.0
            

En la línea 13 agregaremos un puts cuartilla_palabra, cuartilla_caracteres para la siguiente prueba. En el editor guardamos el archivo y en la terminal lo ejecutamos.

Resultado de la cuarta prueba de ejecución de ted_sesion_1.rb.
Resultado de la cuarta prueba de ejecución de ted_sesion_1.rb.

La terminal nos ha impreso el valor de las variables cuartilla_palabra y cuartilla_caracteres mediante el método puts. La búsqueda de síntesis también es perceptible en este método. Si deseamos imprimir varios elementos de la manera menos repetitiva posible, basta con utilizar comas para indicar una nueva impresión. Así, la línea:

                puts cuartilla_palabra, cuartilla_caracteres
            

Es lo mismo a:

                puts cuartilla_palabraputs cuartilla_caracteres
            

Ahora bien, empecemos a analizar lo que hemos escrito.

Línea 9. Se trata de un espacio en blanco para legibilidad.

Línea 10. Se trata de un comentario para inteligibilidad.

Línea 11. Aquí tenemos la definición de una nueva variable cuyo nombre es cuartilla_palabra. El valor asignado a esta variable es 235.0, un número decimal. Como puedes observar, con esta variable hemos ya definido el número de palabras por cuartilla.

Aquí podría surgir una duda: ¿por qué un número decimal y no un entero? Más adelante, cuando realicemos el cálculo de cuartillas, veremos que rara vez tenemos cuartillas enteras. En muchos casos el tamaño de un documento según el número de cuartillas nos dará como resultado un número decimal. Para evitar cualquier problema de cómputo, desde un principio asignamos a nuestras definiciones de cuartilla un número decimal. Esto nos ayuda a anticipar un potencial dolor de cabeza. Recuerda, entre más casos conflictivos se puedan evitar, mejor.

Línea 12. Aquí hemos realizado un ejercicio análogo a la línea anterior. Sin embargo, en lugar de definir una cuartilla cada cierto número de palabras, la hemos definido cada número de caracteres. Por ello el nombre de esta variable es cuartilla_caracteres.

Como has observado a lo largo de este ejercicio, tenemos mucha libertad al momento de nombrar variables. Solo se tiene que tomar en cuenta que las variables solo pueden definirse en una palabra, por lo que los delimitadores no pueden ser espacios. De ser necesarios solo sustituimos espacios por guiones. Otra limitante al momento de nombrar variables es que estas no pueden utilizar nombres reservados. Por ejemplo, no puedo llamar a una variable File porque esta es una palabra reservada de Ruby para la clase File. No obstante, sí puedo nombrar una variable file porque esta no es una palabra utilizada por Ruby. Además de estas limitantes, una recomendación para nuestra lengua es evitar el uso de tildes o de la letra «ñ». Su empleo podría ocasionar funcionamientos inesperados o, sencillamente, dificultaría la escritura de código para colaboradores que no tienen fácil acceso a estos caracteres en sus teclados.

¡Ya estamos en la recta final! Lo único que nos resta es realizar el cálculo mediante una regla de tres.

¿No recuerdas que es una regla de tres? Podemos refrescar este tipo de cálculo con la siguiente fórmula:

Formalización de la regla de tres.
Formalización de la regla de tres.

Para obtener B es necesario multiplicar A por Y y posteriormente dividir ese resultado entre X. Para ser más claros, en este ejercicio la regla de tres sería:

Regla de tres en este ejercicio.
Regla de tres en este ejercicio.

La cantidad de cuartillas se obtiene al multiplicar una cuartilla por la cantidad de palabras totales del documento y, después, dividir este resultado por la cantidad de palabras que en nuestra definición representa una cuartilla.

Entonces, a partir de las variables y los valores que tenemos en nuestro script, esta regla de tres se representa como:

Regla de tres en este ejercicio con sus nombres de variables.
Regla de tres en este ejercicio con sus nombres de variables.

La novedad que tenemos en esta fórmula es un nuevo método: length. Quizá te habías preguntado los motivos de querer tener conjuntos de palabras o de caracteres si lo que estamos buscando es la cantidad de uno y del otro…

Los conjuntos de elementos también tienen sus propios métodos. Así como con la clase String tiene el método split, en la clase Array tenemos un método que nos indica la cantidad de elementos que hay en un conjunto. Este método es length. Entonces, con prueba.length nosotros pedimos que Ruby nos indique la cantidad de elementos que tenemos en nuestro conjunto de palabras. Para nuestros intereses en este ejercicio, ¡esta extensión equivale al número total de palabras!

Cabe resaltar que este método pasa del tipo de dato Array a la clase Integer. Es decir, con el método length lo que obtendremos ya no es un conjunto, sino un número entero.

Por último, es posible simplificar nuestra regla de tres porque la multiplicación de cualquier número por uno da como resultado el mismo número. Entonces, nuestra regla de tres para obtener el número total de cuartillas según la cantidad de palabras de nuestro documento es:

Regla de tres simplificada.
Regla de tres simplificada.

Ahora pasemos a escribir esta regla de tres. Primero borraremos el puts de la línea 13 porque ya no lo necesitamos. A continuación a partir de esa línea escribimos:

                # Imprime la extensión del conjuntoputs "Cuartilla (palabra): " +     (prueba.length / cuartilla_palabra).to_s
            

El archivo ted_sesion_1.rb tiene que lucir más o menos así:

                # File.read lee el archivo de texto y me genera un objeto de textoprueba = File.read('prueba.txt')# El objeto de texto es dividido y me genera un conjuntoprueba = prueba.split(/\s+/)# El objeto de texto es dividido por caracterescaracteres = File.read('prueba.txt').split('')# Definición de tamaños de cuartillacuartilla_palabra    = 235.0cuartilla_caracteres = 1700.0# Imprime la extensión del conjuntoputs "Cuartilla (palabra): " +     (prueba.length / cuartilla_palabra).to_s
            

Para una nueva prueba, en el editor guardamos nuestro documento y en la terminal lo ejecutamos.

Resultado de la quinta prueba de ejecución de ted_sesion_1.rb.
Resultado de la quinta prueba de ejecución de ted_sesion_1.rb.

Analicemos ahora lo que hemos escrito.

Línea 13. Se trata de un espacio en blaco para legibilidad.

Línea 14. Se trata de un comentario para inteligibilidad.

Línea 15 y 16. Aquí estamos llevando a cabo dos pasos: el cálculo y la impresión de su resultado. Como lo he estado mencionando, los lenguajes de programación permiten ser sintéticos. En este caso, en lugar de hacer el cálculo para luego imprimirlo, estamos realizando ambos pasos al mismo tiempo.

En la línea 16 estamos realizando la regla de tres a partir de la división de la cantidad de palabras de nuestro documento (prueba.length) entre la cantidad de palabras en como definimos una cuartilla (cuartilla_palabra). Aquí tenemos que prestar atención a los paréntesis y a un nuevo método.

En Ruby y en varios lenguajes de programación los paréntesis sirven para delimitar entidades. En este ejercicio estamos usando los paréntesis de la línea 16 para poder delimitar el cálculo de la regla de tres. Lo hacemos de esta manera para que el resultado de este cálculo pueda ser sujeto al método to_s.

Este nuevo método nos permite convertir un número en una cadena de caracteres. A diferencia de otros lenguajes de programación, Ruby no convierte de manera automática un tipo de dato a cadena de caracteres cuando este se concatena con otra cadena. Si nosotros no usamos el método to_s, la terminal nos imprimirá un error que nos indica esta falta de conversión.

El cálculo ya convertido en cadena de caracteres nos permite concatenarlo con la cadena de caracteres presente en la línea 15. Es posible unir distintas cadenas de caracteres si utilizamos el operador +. Por este motivo la cadena presente en la línea 15 queda unida al resultado del cálculo de la línea 16.

Esta concatenación genera una sola cadena de caracteres que imprimimos en la terminal al usar puts. Como puedes observar, es posible usar varias líneas para indicar este tipo de operaciones. La computadora ignoraría los saltos de línea. Para nosotros estos son muy útiles para no tener líneas de código largas que restan legibilidad.

Con esto ¡al fin nuestra terminal nos muestra la cantidad de cuartillas del documento según el número de palabras! Impresionante, ¿cierto?

Como ya puedes imaginarlo, solo nos resta calcular e imprimir la cantidad de cuartillas según la cantidad de caracteres. Para ello es suficiente con agregar unas líneas semejantes a las anteriores:

                puts "Cuartilla (caracteres): " +     (caracteres.length / cuartilla_caracteres).to_s
            

El archivo ted_sesion_1.rb tiene que lucir más o menos así:

                # File.read lee el archivo de texto y me genera un objeto de textoprueba = File.read('prueba.txt')# El objeto de texto es dividido y me genera un conjuntoprueba = prueba.split(/\s+/)# El objeto de texto es dividido por caracterescaracteres = File.read('prueba.txt').split('')# Definición de tamaños de cuartillacuartilla_palabra    = 235.0cuartilla_caracteres = 1700.0# Imprime la extensión del conjuntoputs "Cuartilla (palabra): " +     (prueba.length / cuartilla_palabra).to_sputs "Cuartilla (caracteres): " +     (caracteres.length / cuartilla_caracteres).to_s
            
Resultado final de ted_sesion_1.rb.
Resultado final de ted_sesion_1.rb.

Estas nuevas líneas son un calco de las anteriores pero con los cambios de variables necesarios para obtener la cantidad de cuartillas según la cantidad de caracteres: caracteres en lugar de prueba y cuartilla_caracteres en lugar de cuartilla_palabra.

Con esto acabamos este ejercicio, el cual fue el primero en realizarse durante el cuarto ciclo del Taller de Edición Digital. En este enlace puedes descargar los archivos para tu cotejo. ¡Pasemos ahora a la siguiente lección!

La inclinación por tener un producto editorial final hace que varios editores obvien un elemento de mucha importancia: el pleno dominio y conocimiento del tipo de objeto con el que trabajan. La tradición editorial ha sido fundamentalmente gráfica. Lo que tenemos con las nuevas tecnologías de la información y la comunicación es una constitución del texto como un tipo de dato informático. El desconocimiento de esta característica del texto provoca una pérdida de control en la calidad editorial. Pero no solo eso. En la falta de conocimiento de las posibilidades y los límites del principal tipo de dato que usa en su trabajo, el editor rara vez podrá encontrar las soluciones o los cambios metodológicos que su profesión demanda hoy en día. En ese contexto el quehacer editorial queda a la disposición de quienes sí conocen el tratamiento de este tipo dato: los desarrolladores de software.

4. Una idea para hacer diccionarios o glosarios

En redacción…

Ha llegado la hora de partir

¿Qué puedo decirte? Esto es solo la punta el iceberg para una idea que se ha estado trabajando a lo largo de los ejercicios. Tal como lo hemos entendido en la edición digital, el texto se ha visto como tipos de datos.

Sin embargo, el paradigma del texto como dato, sean tratado con edición directa —el famoso WYSIWYG— o con edición estructural —lo que se conoce como WYSIWYM—, es insuficiente para las necesidades actuales de publicación automatizada, multiformato y con altos controles técnico y de calidad editorial.

Quizá —y solo quizá— es necesario el advenimiento de otro paradigma: el texto como lenguaje de programación. Es decir, ya no solo se escribirían oraciones para dar lugar al contenido. Tampoco sería que, de manera exclusiva, este contenido se elabore a partir de las reglas de formación que han emergido de la escritura a mano alzada a la mecanografía. Más bien serían unas clases de redacción y de edición que de alguna forma funcionan según sus distintos tipos de salida y modos de lectura.

¿Cómo podemos empezar a trabajar este paradigma? Esa es la pregunta del millón. De su respuesta dependerá una renovación o la muerte de lo que hemos entendido como edición de textos…