Archivo de la categoría: Programación

JSHELL

JShell es una herramienta que se instala con el Java Development Kit (JDK) y es muy útil para probar código Java, sin necesidad de hacer un programa completo y tener que compilarlo.

Para arrancar JShell tendrás que abrir un terminal y teclear:

jshell

Si todo va bien, deberías ver una pantalla similar a la de la siguiente figura:

Si quieres salir de la aplicación, tienes que teclear:

/exit

Nota: si al teclear jshell, no aparece el prompt de la aplicación, puede significar que no tienes instalado el JDK o que no tienes configurada correctamente la variable PATH del sistema.

Utilización como calculadora

Un posibilidad que ofrece JShell y que puede ser útil en determinadas circunstancias es utilizarlo como calculadora. Puedes realizar operaciones matemáticas y tendrás acceso a los métodos de la clase Math y al uso de variables.

Por ejemplo, trata de hacer en tu consola las operaciones que aparecen en la siguiente figura u otras similares:

Observa que, tras cada operación, aparece el signo del dólar ($) seguido de un número. Son variables que quedan guardadas en la memoria y pueden ser invocadas a posteriori. Observa la siguiente serie de operaciones, en la que se reutiliza uno de los resultados obtenidos:

Variables Java

También podemos definir y utilizar variables Java, pero en ese caso habrá que declarar y asignar las variables con su tipo de dato, como lo haríamos dentro de un programa:

Como detalle, puedes ver que estas instrucciones Java no las terminamos en punto y coma. En jshell no es necesario terminar las instrucciones sueltas en punto y coma (Cuando definamos clases, sí que hay que respetar la sintaxis Java).

En cualquier momento puedes consultar la lista de variables que hay en memoria tecleando la orden /vars:

Si quieres borrar totalmente el contenido de la memoria puedes teclear la orden /reset.

Probar código

Es cómodo probar código sin tener que teclear una clase completa, compilarla y ejecutarla. Además, para ver la salida por pantalla de las instrucciones, no necesitamos usar métodos System.out.println():

Probar funciones

Puedes teclear el código de una función y utilizar la función posteriormente.

Las funciones que hayas definido quedan guardadas en memoria. Puedes obtener el listado de las funciones que hayas definido en la sesión utilizando la orden /methods. Si tecleas /reset se borrará la memoria completa, incluyendo variables y funciones.

Clases y objetos

Puedes crear clases y objetos:

Podrías crear también, no solo clases, sino interfaces o enumeraciones. Para listar los tipos de datos que hayas definido en la sesión tienes que teclear la orden /types.

Una vez definido un tipo de datos, puedes crear funciones que lo utilicen, sin necesidad de incluir la función como método del tipo:

Guardar el trabajo

La orden /list sirve para ver las instrucciones que has tecleado. La orden /save, seguida de un nombre de fichero, sirve para guardar dichas órdenes en un fichero y poderlas reutilizar a posteriori. Podrías cargar el fichero en JShell al abrirlo o utilizando la orden /open.

Cargar ficheros

Podemos cargar ficheros existentes utilizando la orden /open. Imagina que tienes un fichero llamado Punto.java, en el directorio en el que has abierto JShell, con el siguiente contenido:

public class Punto {
   public int x, y;

   public Punto(int x, int y) {
      this.x = x;
      this.y = y;
   }   
   public double dist(Punto p) {
      return Math.sqrt(x*p.x + y*p.y);
   }
   public double distOrigen() {
      return Math.sqrt(x*x + y*y);
   }
}

Puedes abrir el fichero y utilizar sus definiciones:

Puedes cargar un fichero en memoria en el momento de abrir JShell tecleando su nombre a continuación de jshell:

jshell Punto.java

El fichero puede ser una clase .java o un fichero de órdenes guardadas previamente con /save. En ambos casos, las ordenes contenidas en el fichero se ejecutarán como si las tecleases dentro del terminal.

Edición en consola

Puedes usar las teclas de flecha arriba/abajo, para editar instrucciones anteriores y moverte dentro de ellas para cambiar su contenido. Esto permite modificar instrucciones anteriores que hayamos utilizado para definir tipos de datos o funciones.

Las instrucciones multilínea aparecerán de una sola vez y podrás moverte dentro de ellas para editarlas.

Cuando se necesita modificar un método ya definido, JShell ofrece el comando /edit seguido del nombre del método. Este comando abre una pequeña ventana de edición gráfica integrada en la que puede modificarse el código y confirmar los cambios con el botón Accept:

 /edit grad2rad

El editor integrado de JShell es una ventana gráfica propia de la herramienta, independiente del editor del sistema operativo. Funciona sin ninguna configuración adicional en Windows, macOS y en Linux con entorno gráfico de escritorio.

💡El editor de JShell se puede utilizar también para editar clases o interfaces. La condición para editar una función o una clase es que tienen que estar definidas previamente.

Un truco útil para crear una clase es definirla vacía en JShell y luego editarla en el editor para completar atributos y métodos de manera más cómoda. Algo parecido se puede hacer con las funciones y los interfaces.

Resumen de órdenes

La tabla siguiente muestra un resumen de las órdenes que hemos mostrado en el artículo.

InstrucciónExplicación
/exitCierra la consola JSHELL
CTRL + LLimpia la pantalla
/varsLista las variables en memoria
/methodsLista las funciones en memoria
/typesLista los tipos de datos definidos
/listLista las órdenes tecleadas con anterioridad
/resetLimpia la memoria de la sesión actual
/open nombre_ficheroAbre un fichero y carga sus definiciones
/save nombre_ficheroGuarda las ordenes en un fichero
/edit identificadorAbre un editor para editar el contenido de una función, un método o un interface

El separador de decimales en Java

El Locale es la configuración regional que le dice a un programa informático cómo mostrar y formatear datos según un idioma, país y cultura específicos.

El Locale afecta a la forma de mostrar diversos elementos:

  • Números: separador decimal, miles
  • Fechas: DD/MM/YYYY vs MM/DD/YYYY
  • Moneda: €1.234,56 vs $1,234.56
  • Mayúsculas/minúsculas: Ñ → ñ (turco: i → İ)
  • Ordenación: ñ después de n en español

Por ejemplo, según el Locale seleccionado, estas serían distintas formas de mostrar el mismo número 1234.56:

🇺🇸 USA (en_US): 1,234.56
🇪🇸 España (es_ES): 1.234,56
🇩🇪 Alemania (de_DE): 1.234,56
🇫🇷 Francia (fr_FR): 1 234,56
🇯🇵 Japón (ja_JP): 1,234.56

El Locale consta de un idioma, un país y, opcionalmente, una variante. Por ejemplo:

Locale = Idioma + País [+ Variante]
ej: es_ES   → español (España)
    en_US   → inglés (Estados Unidos)
    pt_BR   → portugués (Brasil)

Al arrancar el sistema operativo (Windows, Linux, Mac,…) se carga un Locale por defecto que es el que usarán nuestros programas Java, salvo que indiquemos otra cosa.

Puedes comprobar el Locale que tienes cargado en el ordenador mediante la siguiente instrucción:

System.out.println(Locale.getDefault());

En mi caso, la salida ha sido:

es_ES

que corresponde al idioma español de España.

El Locale afecta a la forma de leer o escribir números double, ya sea en el terminal o en ficheros de texto.

Dentro de nuestros programas, podemos establecer el Locale de la siguiente forma:

Locale.setDefault(Locale.UK); // Locale del Reino Unido
Locale.setDefault(Locale.US); // Locale de Estados Unidos
Locale.setDefault(new Locale("es", "ES"));

Imprimir números double

Si se utilizan los métodos print() o println(), los números double se imprimen utilizando el punto como separador de decimales, independientemente del Locale activo en el ordenador:

System.out.println(1234.56); // Imprime 1234.56

El comportamiento es diferente cuando se usan métodos printf(). Con la configuración en español, cuando imprimimos un número double utilizando la instrucción printf(), el separador de decimales será la coma, no el punto:

System.out.printf("%.2f%n", 1234.56); // Imprime 1234,56

Si queremos que, además del separador de decimales, se imprima el separador de miles, hay que añadir al especificador de formato una coma antes del punto decimal:

System.out.printf("%,.2f%n", 1234.56); // Imprime 1.234,56

Si queremos forzar el uso del punto como separador de decimales podemos hacer:

Locale.setDefault(Locale.UK); // Locale del Reino Unido
System.out.printf("%.2f%n", 1234.56); // Imprime 1234.56
System.out.printf("%,.2f%n", 1234.56); // Imprime 1,234.56

Este comportamiento al escribir es el mismo cuando se escribe en el terminal o cuando se escribe en ficheros de texto.

¿Y por qué los métodos print() y println() utilizan siempre el punto como separador de decimales, independientemente del Locale que haya activo? La razón es que, internamente, estos métodos convierten los números double a cadena de texto utilizando el método toString() de la clase Double y dicho método no utiliza ningún formato regional, siempre utiliza el punto decimal en la conversión.

Hay que tener claras dos cosas:

  • Internamente, los números double no se ven afectados de ninguna manera por el Locale del ordenador. La representación interna del número utiliza otro tipo de evaluación, el Locale solo afecta a la representación textual del número al hacer entradas o salidas desde el terminal o desde ficheros de texto.
  • Al codificar, al escribir los programas, tenemos que utilizar siempre el punto como separador de decimales.

Leer números double

Con el ordenador usando el Locale español, si leemos un número double desde el terminal utilizando el método nextDouble() de la clase Scanner, deberá estar escrito con la coma como separador de decimales. Si el número está escrito con el punto, el programa lanzará una excepción y se interrumpirá abruptamente.

Prueba el siguiente programa:

package principal;

import java.util.Locale;
import java.util.Scanner;

public class Main {
   public static void main(String[] args) {
      System.out.println(Locale.getDefault());
	
      Scanner sc = new Scanner(System.in);
      System.out.print("Teclee un número con decimales: ");
      double x = sc.nextDouble();
      sc.close();
   }
}

He ejecutado el código en mi ordenador, configurado con Locale español y he tecleado el número utilizando el punto como separador de decimales. El resultado ha sido el siguiente:

Observa que el programa lanza una excepción del tipo InputMismatchException y se interrumpe.

He repetido la ejecución pero, en esta ocasión, he tecleado el número utilizando la coma como separador de decimales:

Observa que ahora el programa ha funcionado correctamente.

La clase Scanner ofrece el método setLocale(), que permite elegir un determinado Locale en las lecturas que haga. Por ejemplo, en el código siguiente, el primer double se lee utilizando el Locale del Reino Unido y, el segundo, utilizando el Locale español:

Scanner sc = new Scanner(System.in);

sc.useLocale(Locale.UK);
double x = sc.nextDouble(); // Espera leer 3.14

sc.useLocale(new Locale("es", "ES"));
double y = sc.nextDouble(); // Espera leer 3,14

Leer cadenas de texto y convertirlas en double

Otra técnica que puede ser útil al leer números con Scanner es leer una línea de texto con nextLine() y luego convertir la cadena leída a double utilizando el método estático parseDouble() de la clase Double.

El método Double.parseDouble(), al igual que sucede con el método toString(), siempre trabaja con el punto como separador de decimales. El siguiente ejemplo, leería un número double que debería estar escrito con punto decimal:

double z = Double.parseDouble(sc.nextLine());

Imponer un Locale para todo el programa

En programas profesionales, si no tomamos precauciones, puede resultar que la ejecución sea diferente según el Locale del ordenador en el que se esté ejecutando el programa. Por tanto, es necesario tomar medidas de forma que la ejecución siempre sea la misma, independientemente del Locale que esté configurado en el ordenador en el que se ejecute el programa.

Podemos imponer al principio del programa que se utilice un Locale determinado. Esta solución afectará tanto a las entradas que hagamos a través de objetos Scanner como a las salidas que hagamos con métodos printf(). Por ejemplo, podríamos imponer el Locale del Reino Unido o el de Estados Unidos y forzar a que las entradas y salidas de números decimales haya que hacerlas usando el punto como separador de decimales. La instrucción sería una de las dos siguientes:

Locale.setDefault(Locale.UK);
Locale.setDefault(Locale.US);

Otra opción sería imponer el Locale español de España. Para seleccionar el Locale de España no disponemos de una constante predefinida como en los casos anteriores y la instrucción que habría que poner al principio del programa sería:

Locale.setDefault(new Locale("es", "ES"));

También es posible imponer el Locale pasando un parámetro a la Máquina Virtual de Java al ejecutar el programa. Si ejecutamos desde el terminal y queremos imponer el Locale de Estados Unidos, habría que hacer:

java -Duser.language=en -Duser.country=US principal.Main

En Eclipse, podemos configurar la ejecución del programa y pasarle los argumentos a la JVM, como se muestra en la figura:

Cualquiera que sea la opción que uses, en programas profesionales en los que se realicen cálculos científicos con números decimales, es necesario tomar las precauciones necesarias para que el programa se ejecute de manera correcta en cualquier entorno de ejecución.

Ver la carpeta bin y los archivos .class en Eclipse

Eclipse ofrece lo que llama perspectivas, que son disposiciones concretas de las ventanas y paneles del editor. Las perspectivas se seleccionan en el menú “Window”.

Cuando utilizamos Eclipse Standard Edition para editar nuestros proyectos Java, lo más frecuente es tener activada la perspectiva Java, que es la perspectiva por defecto de la edición estándar de Eclipse.

En esta perspectiva, el panel de la izquierda está ocupado por la vista llamada Explorador de paquetes. El explorador de paquetes no muestra ni la carpeta .bin, en la que solemos guardar los ficheros compilados, ni los ficheros .class, aunque los tuviéramos en otra carpeta.

La siguiente figura muestra la vista del Explorador de paquetes de Eclipse, con un proyecto abierto, donde se puede apreciar que la carpeta bin no se muestra:

Para ejecutar un programa Java, el procedimiento habitual es pichar con el botón derecho del ratón sobre el fichero .java que contiene el método main() y elegir la opción “Run as -> Java application”.

Esto puede llevar a los programadores nóveles a pensar que están ejecutando el fichero .java y eso es un error conceptual importante. Los ficheros .java no son ejecutables, primero hay que compilarlos a bytecode (.class) utilizando el compilador de Java y luego, lo que se ejecuta, es el método main() del fichero .class que lo contenga.

Vamos a explicar cómo activar la visualización de la carpeta .bin y de los ficheros .class. Para ello, hay que mostrar la vista Explorador de proyectos, que la podemos activar desde la opción de menú “Window -> Show View -> Project Explorer“. El resultado será el de la siguiente figura:

Por defecto, esta vista tampoco muestra la carpeta bin ni los ficheros .class. Tendremos que configurar la vista de manera adecuada. Hay que abrir el menú de la vista, que son los tres puntos que se muestran en la barra de herramientas de la vista y elegir la opción denominada “Filters and customization”, como se muestra en la siguiente figura:

Al acceder a esta opción del menú de la vista, se nos ofrece una lista de tipos de elementos para los que podemos activar o desactivar el filtro: si el filtro está activado, ese tipo de elementos no se mostrará en la vista.

Tenemos que asegurarnos de no tener activados los filtros correspondientes a*.class resources y Java output folders, como se muestra en la siguiente figura:

Con esto, ya podremos ver en el explorador de proyectos la carpeta bin y los ficheros .class. Es posible que tengas que refrescar la vista para que se hagan efectivas las opciones de visualización seleccionadas, utilizando la opción de menú “File -> Refresh” (F5).

La siguiente figura muestra el resultado con el proyecto usado en los ejemplos, donde se puede ver la carpeta bin y los ficheros .class dentro de ella.

Ahora puedes hacer la siguiente prueba: borra el fichero .class correspondiente a la clase Java que contiene el método main(). A continuación, vuelve a probar a ejecutar el fichero .java correspondiente. Podrás comprobar que no se puede ejecutar el programa.

Para regenerar el fichero compilado .class, haz cualquier modificación en el fichero fuente .java y vuelve a guardarlo en el disco. Verás que, cuando grabas el fichero .java modificado, Eclipse recompila el proyecto y vuelve a generar los .class. Cuando estamos trabajando con Eclipse, cada vez que modificamos un fichero .java, Eclipse recompila el proyecto.

De modo que, a partir de ahora, cada vez que ejecutes un programa Java, ten bien presente que los ficheros .java no son ejecutables y que es necesario compilar y ejecutar.

Codificación de ficheros fuente en Java

Al compilar ficheros .java, el compilador javac asume que los ficheros tienen una codificación por defecto. En los sistemas Linux o Mac, javac asume que los ficheros están codificados en UTF-8. En Windows, las versiones posteriores a Java 17 también asumen que los ficheros fuente están codificados en UTF-8, pero hasta la versión Java 17 inclusive, javac supone que los ficheros utilizan la codificación de Windows. En España, la codificación que utiliza Windows suele ser Windows-1252.

Esto es así, independientemente de la página de códigos que tengamos activa en el terminal.

Podemos indicar al compilador que utilice una codificación específica para los ficheros fuente .java, utilizando el parámetro -encoding:

javac -encoding UTF-8 *.java

Vamos a ver un ejemplo completo utilizando una variante de programa Hola Mundo, que utiliza caracteres especiales del español y servirá para entender el problema.

Vamos a compilar y ejecutar la siguiente clase Java:

public class Hola {
   public static void main(String[] args) {
      System.out.println("¡Hola, ¿qué tal?, ¡Vaya año llevamos!");
   }
}

Vamos a utilizar, en primer lugar, la siguiente configuración: Windows 10, Windows Terminal con la página de códigos activa en UTF-8, compilación y ejecución con Java 17 y ficheros .java codificados en UTF-8. La salida obtenida es la de la siguiente figura:

Se puede ver que no se reconocen los caracteres especiales del español. Lo que está pasando es que javac 17 asume que los ficheros fuente están codificados en Windows-1252. Y este comportamiento es independiente de la codificación que tengamos activa en el terminal.

Si compilamos con javac 17, utilizando el parámetro -encoding UTF-8, el fichero fuente se interpreta de manera adecuada:

Java proporciona un comando para poder ver el valor de las diferentes variables que usa:

java -XshowSettings:properties -version

La salida del comando anterior, en la configuración indicada, fue la siguiente:

Se puede observar que Java asume que los ficheros están codificados en Windows-1252.

Ahora vamos a utilizar la versión 21 de Java. En el terminal de Windows, vamos a dejar activa la página de código 850 y compilar y ejecutar el mismo programa. La salida es la siguiente:

Observa que ahora la interpretación de los caracteres especiales del español es correcta. Una vez más, independientemente de la codificación activa del terminal, que en este caso no era UTF-8.

Si consultamos las opciones que está utilizando Java 21:

Vemos que ahora Java 21 asume que los ficheros fuente están codificados en UTF-8.

Podemos hacer una última prueba: compilar con Java 21, pero diciéndole al compilador que el fichero Hola.java está codificado en Windows-1252:

Observa que la salida es la misma que obteníamos con Java 17, cuando interpretaba mal la codificación del fichero fuente.

Por tanto, al compilar ficheros fuente codificados en UTF-8, si utilizamos la versión Java 17 o una inferior, tendremos que utilizar el parámetro -encoding UTF-8 para obtener resultados correctos. Si usamos una versión de Java superior a la 17, ya se presupone que los ficheros fuente están codificados en UTF-8 y no será necesario utilizar el parámetro -encoding.

¿Y si estamos trabajando en Eclipse? Bueno, Eclipse proporciona una opción para indicar la codificación de los ficheros fuente y luego se encarga de pasarla al compilador o al entorno de ejecución. La opción la podemos configurar en: “Propiedades del proyecto -> Resource -> Text file encoding“, como se ve en la siguiente figura:

Programación en C

https://www.garceta.es/catalogo/libro.php?ISBN=978-84-1903-425-0&idd=12

Aprender a programar constituye hoy en día una de las competencias fundamentales en la formación de cualquier ingeniero. Incluso en un mundo en el que las herramientas de Inteligencia Artificial están cada vez más presentes y facilitan muchas tareas, comprender cómo funciona un programa, cómo se organiza un algoritmo y cómo se traduce una idea en código sigue siendo imprescindible. El ingeniero no puede limitarse a utilizar soluciones ya hechas: debe ser capaz de analizar un problema, diseñar una estrategia de resolución y llevarla a la práctica con precisión y rigor.

Este libro está destinado a los estudiantes universitarios que se enfrentan por primera vez al aprendizaje de la programación. No se requieren conocimientos previos: el texto parte desde los conceptos más básicos y avanza de forma gradual, apoyándose en una gran cantidad de ejemplos explicados con detalle. Sin embargo, el camino recorrido conduce hasta un nivel medio de programación, que permitirá al lector enfrentarse a problemas de cierta complejidad y sentar las bases para aprendizajes más avanzados.

La extensión y profundidad del libro hacen que difícilmente pueda agotarse en un único semestre. Más bien, debe entenderse como un manual de referencia que acompaña al estudiante durante toda su formación inicial: una obra a la que volver en distintos momentos para repasar conceptos, reforzar técnicas o explorar nuevas posibilidades del lenguaje C.

A lo largo de sus capítulos se incluyen cientos de ejemplos repartidos estratégicamente para ilustrar cada idea, junto con más de doscientos ejercicios propuestos que permiten practicar y afianzar los contenidos. Cada ejercicio va acompañado de su solución, de manera que el alumno pueda comprobar su progreso y, al mismo tiempo, aprender distintas formas de abordar un mismo problema.

Pero aprender a programar no consiste solo en lograr que un programa funcione. La programación es también un medio de comunicación entre personas: el código debe ser comprensible, legible y mantener una estructura clara. Por ello, este libro insiste de manera constante en las buenas prácticas de programación, en el estilo y en la importancia de escribir programas que puedan ser entendidos y mantenidos por otros.

La elección del lenguaje C como punto de partida responde a varias razones. Se trata de un lenguaje con una larga tradición en el mundo de la ingeniería y la informática, en el que se apoyan muchos otros lenguajes modernos. Aprender C significa adquirir un conocimiento profundo de los fundamentos de la programación, de la relación entre el código y la máquina, y de los principios que siguen vigentes en entornos actuales. C, con su sencillez y potencia, ofrece al principiante una oportunidad única de comprender de verdad cómo se construye un programa desde sus cimientos.

En suma, este libro no pretende únicamente enseñar a programar en C, sino formar en el arte de pensar de manera algorítmica, rigurosa y creativa. Su meta es que los estudiantes, además de aprobar un curso, desarrollen una herramienta intelectual que les será útil durante toda su vida profesional como ingenieros.

Espero que este libro te acompañe en la aventura de descubrir la programación y que en cada página encuentres, no solo respuestas, sino también nuevas preguntas que despierten tu curiosidad como futuro ingeniero.

Santiago Higuera de Frutos

Doctor Ingeniero de Caminos

Profesor en Teleco Campus Sur

GeoRust

Introducción

Desde el año 2015, de acuerdo con la encuesta anual que realiza Stack Overflow entre más de 80.000 programadores de todo el mundo, Rust ha resultado ser el lenguaje que más gustaba (The most loved language).

Rust se creó con el objetivo de obtener un lenguaje de prestaciones similares o superiores a las de C o C++, pero poniendo énfasis en la seguridad del código, aspecto en el que estos lenguajes han dado lugar a numerosos problemas.

Pero Rust no solo ofrece código seguro. Rust ofrece una documentación de alta calidad, permite la programación concurrente de manera eficiente y segura, permite programar en WebAssembly, ofrece un alto rendimiento en el procesamiento de grandes cantidades de datos y, además, dispone de un compilador muy efectivo y un entorno de desarrollo completo, con servicios para documentación de programas, servicios para test unitarios o de integración, servicios para control de versiones y mucho más.

Rust es un lenguaje de código abierto. Inicialmente se desarrolló al amparo de la empresa Mozilla. Desde 2021, el desarrollo está coordinado por la Fundación Rust, que es apoyada y financiada por las grandes empresas del software.

Rust no es un lenguaje orientado a objetos propiamente dicho, aunque se pueden emular muchas de las técnicas que se utilizan en ese paradigma de programación. La mayores influencias en el lenguaje Rust provienen de SML, OCaml, C++, Cyclone, Haskell y Erlang. La programación funcional sí casa mejor con la filosofía del lenguaje Rust, que sin ser un lenguaje funcional estricto, permite realizar una programación funcional eficiente. Puede ser una buena herramienta para pasarse a la programación funcional.

Desde su primera versión estable de enero de 2014, Rust se utiliza en los desarrollos de grandes empresas, como Amazon, Discord, Dropbox, Facebook (Meta), Google (Alphabet) y Microsoft. Actualmente, Rust se utiliza para desarrollar el nucleo de sistemas operativos como Linux, Windows y Android.

A pesar de que se trata de un lenguaje relativamente nuevo, dispone ya de una amplia librería de utilidades que facilitan la programación dentro de cualquier ámbito.

Por supuesto, todas estas ventajas que ofrece Rust son a costa de una curva de aprendizaje un poco más pendiente al principio. Programar en Rust requiere más esfuerzo que programar en otros lenguajes, como por ejemplo, Python. Pero los resultados que se obtienen compensan el esfuerzo dedicado.


GeoRust

Existe un ecosistema muy amplio de herramientas geoespaciales para trabajar con Rust. En este artículo se va a hacer una introducción al uso de la librería geo_types, aunque hay otras que servirán para ampliar conocimientos con posterioridad:

  • geo: utiliza los tipos de geo_types y añade un gran número de funciones para cálculos geoespaciales.
  • geojson: proporciona métodos para leer y escribir objetos Geo-JSON según la especificación IETF RFC 7946.
  • proj: proporciona servicios de cambio de coordenadas utilizando bindings a proj.
  • gdal: proporciona bindings para usar la librería GDAL, que permite procesar formatos geoespaciales raster y vectoriales.
  • Librerías para formatos específicos: geotiff, kml, netCDF, osm, shapefile, tilejson y algunas más.

Además se dispone de diversas librerías con utilidades para geocodificación y otras tareas de análisis geoespacial.

La totalidad de librerías se puede consultar en el siguiente enlace:

https://georust.org/


Crate geo_types

Esta librería proporciona estructuras para los tipos de geometría habituales. Se añade al proyecto con:

cargo add geo_types

Si se necesitan algoritmos geoespaciales, hay que usar la crate geo, que reexporta los tipos de datos de geo_type. Se realiza a continuación una introducción al uso de algunas de las primitivas fundamentales.

Coord

La estructura en la que se basan los demás tipos es geo::geometry::Coord, que es una pareja de coordenadas de algún tipo numérico, por defecto, f64.

pub struct Coord<T = f64> where
    T: CoordNum,{
    pub x: T,
    pub y: T,
}

El trait CoordNum proporciona operaciones sumar, restar, multiplicar, dividir y algunas otras, sobre objetos del tipo Coord. Este trait lo implementan los números float y los enteros. Em los algoritmos que solo tienen sentido con floats, como el cálculo de un área, hay que usar tipos que implementen CoordFloat.

También hay una macro, coord!{}, que permite crear instancias de Coord. Se puede acceder a los campos x o y de manera individual y también hay un método x_y()que devuelve una tupla con las coordenadas (x, y):

use geo_types::coord;

let c = coord! {
    x: 40.02f64,
    y: 116.34,
};
assert_eq!(c.x, 40.02);
assert_eq!(c.y, 116.34);
let (x, y) = c.x_y();
assert_eq!(y, 116.34);
assert_eq!(x, 40.02f64);

Coord no es una primitiva geoespacial, pero forma parte de todas las primitivas.


Point

La estructura Point se utiliza para representar un punto en dos dimensiones.

pub struct Point<T = f64>(pub Coord<T>)
where
    T: CoordNum;

Se pueden crear instancias de Point utilizando el método Point::new(), la macro point!, o, utilizando from(), a partir de una tupla, de una instancia de Coord o de un array:

use geo_types::{coord, point, Point};

let p0: Point = Point::new(3.14, 6.28);
let p1 = point! { x: 1.5, y: 0.5 };
let p2: Point = (0., 1.).into();
let p3: Point = [1.0, 2.0].into();
let c = coord! { x: 10., y: 20. };
let p4: Point = c.into();

El tipo Point es una estructura no etiquetada cuyo único campo es una instancia de Coord. Se puede acceder a x e y utilizando los métodos x(), y() o el método x_y(), que devuelve una tupla:

use geo_types::{Point};

let p: Point = Point::new(3.14, 6.28);
assert_eq!(p.x(), 3.14);
assert_eq!(p.y(), 6.28);
assert_eq!(p.x_y(), (3.14, 6.28));

Line

La estructura Line representa un segmento con dos Coord:

pub struct Line<T = f64>where
    T: CoordNum,{
    pub start: Coord<T>,
    pub end: Coord<T>,
}

Se pueden crear instancias de Line utilizando el método new() y a partir de un array de tuplas:

use geo_types::{Coord, Line};

let line_1: Line = Line::new(
    Coord{x:3.14, y:6.28}, 
    Coord{x:10., y: 6.}
);
let line_2: Line = [(3.14, 6.28), (10., 6.)].into();

assert_eq!(line_2.start_point().x(), 3.14);

Dispone de unos métodos start_point() y end_point() que devuelven los puntos inicial y final. También hay un método points() que devuelve una tupla con los dos puntos. Hay métodos dx() y dy() que devuelven el incremento en cada dirección. El método delta() devuelve una tupla con ambos incrementos. El método slope() devuelve la pendiente del segmento.


LineString

Es una colección ordenada de dos o mas Coord que representa la trayectoria entre dos puntos.

pub struct LineString<T: CoordNum = f64>(pub Vec<Coord<T>>);

Una LineString puede ser cerrada si no tiene puntos o si el primer y último puntos son el mismo.

Se puede crear una LineString llamando directamente a la función new(), utilizando la macro line_string!, convirtiendo un vector de tuplas de Coord o un vector de arrays de Coord:

use geotypes::{coord, LineString, linestring};

let line_string = LineString::new(vec![
   coord! { x: 0., y: 0. },
   coord! { x: 10., y: 0. },
]);
let line_string = line_string![
   (x: 0., y: 0.),
   (x: 10., y: 0.),
];
let line_string: LineString = vec![(0., 0.), (10., 0.)].into();
let line_string: LineString = vec![[0., 0.], [10., 0.]].into();

También se pueden obtener un LineString a partir de iteradores de Coord:

let mut coords_iter = vec![
    coord! { x: 0., y: 0. }, 
    coord! { x: 10., y: 0. }].into_iter();
let line_string: LineString<f32> = coords_iter.collect();

LineString ofrece cinco iteradores: coords, coords_mut, points, lines y triangles.

use geo_types::{coord, LineString};

let line_string = LineString::new(vec![
    coord! { x: 0., y: 0. },
    coord! { x: 10., y: 0. }
]);
linestring.coords().for_each(|coord| println!("{:?}", coord));

for point in line_string.points() {
   println!("Point x = {}, y = {}", point.x(), point.y());
}

Si en un bucle se utiliza directamente el iterador procedente del trait IntoIterator, hay que cuidar la propiedad del LineString:

for coord in &line_string {
   println!("Coordinate x = {}, y = {}", coord.x, coord.y);
}
for coord in line_string {
   println!("Coordinate x = {}, y = {}", coord.x, coord.y);
}

A partir de un LineString se puede obtener directamente un vector de Coord o un vector de Point:

use geo_types::{coord, LineString, Point};

let line_string = LineString::new(vec![ coord! { x: 0., y: 0. }, coord! { x: 10., y: 0. }, ]); let coordinate_vec = line_string.clone().into_inner(); point_vec = line_string.clone().into_points();


Polygon

Es un área bidimensional. La frontera exterior es una LineString. Puede tener huecos, que estarán definidos por otras LineStrings.

Se puede consultar en:

Polygon in geo_types::geometry – Rust

Geometry

Es un enum representando todos los tipos de geometrías:

pub enum Geometry<T: CoordNum = f64> {
    Point(Point<T>),
    Line(Line<T>),
    LineString(LineString<T>),
    Polygon(Polygon<T>),
    MultiPoint(MultiPoint<T>),
    MultiLineString(MultiLineString<T>),
    MultiPolygon(MultiPolygon<T>),
    GeometryCollection(GeometryCollection<T>),
    Rect(Rect<T>),
    Triangle(Triangle<T>),
}

Todas las primitivas geométricas se pueden convertir en un Geometry utilizando Into::into(). De manera similar, se puede utilizar TryFrom::try_from()para obtener la primitiva a partir de un Geometry:

use std::convert::TryFrom;
use geo_types::{Point, point, Geometry};

let p = point!(x: 1.0, y: 1.0);
let pe: Geometry = p.into();
let pn = Point::try_from(pe).unwrap();

JSON

Como ejemplo, se va a mostrar cómo pasar una Line a JSON:

fn test_json() -> serdejson::error::Result<()>  {
   use geotypes::{Coord, Line};
   use serdejson;

   let line: Line = [(3.14, 6.28), (10., 6.)].into();
   let cad = serde_json::to_string(&line)?;
   println!("{}", cad);
   let value: serde_json::Value = serde_json::from_str(cad.as_str())?;
   assert!(value.is_object());
   Ok(())
}

(Santiago Higuera de Frutos – Enero 2024)

Los lenguajes de programación más solicitados en 2021

Los lenguajes de programación más solicitados en 2021

La programación de ordenadores sigue siendo un campo con interesantes salidas laborales. Esto es especialmente cierto en el campo de la Ingeniería Civil, por tratarse de un sector consumidor de muchos recursos informáticos. Actualmente existen más de 700 lenguajes de programación activos. Este artículo hablará sobre los lenguajes más demandados en el año 2021, según distintas fuentes.

Con la excepción de los lenguajes dedicados a la programación de bases de datos, la mayoría de los índices miden la importancia de los lenguajes en su uso general, no dentro de un campo específico. Es por ello, que los lenguajes cuya utilización se da principalmente dentro de un campo concreto, salen penalizados, y su posición en los índices generales no muestra realmente su posición si se considerara solo su prevalencia en dicho campo.

Es el caso de MATLAB-Octave, cuyo uso está restringido al ámbito científico. Uno de los índices más prestigiosos es el índice TIOBE, basado en las búsquedas en internet referentes a los diferentes lenguajes. El índice TIOBE del mes de octubre de 2021 indica que MATLAB ocupa la posición 13 entre los lenguajes más utilizados. Esto supone un incremento de uso respecto de octubre de 2020, cuando ocupaba la posición 15.

En todos los índices, las primeras posiciones están copadas por Python, Java y las diferentes variantes del C: C, C++ y C#. Son sin duda los lenguajes más utilizados para programación general. Java, que venía siendo el más utilizado durante muchos años, ha cedido la primera posición en favor de Python y C. Cualquiera de ellos es una buena opción para profundizar en la programación. Si nos limitamos al ámbito científico, tal vez Python ofrece, a día de hoy, una combinación de recursos y facilidad de uso que lo convierten en la opción más atractiva.

Es significativa la evolución del lenguaje Fortran. Hace muchos años era el lenguaje científico por excelencia. De hecho, era el lenguaje que se estudiaba en la Escuela de Caminos allá por los años 80 y 90 del siglo pasado. Más tarde, Fortran perdió importancia y pasó a ser un lenguaje muy minoritario. Las mejoras aportadas en las útimas versiones, junto con su rapidez de ejecución y su enfoque científico han hecho que vuelva a ser un lenguaje muy utilizado. En el citado índice TIOBE de octubre de 2021, ocupa la posición 18 entre los lenguajes más utilizados, ascendiendo a esta posición desde la posición 37 que ocupaba el año pasado.

En el último artículo que dediqué a los lenguajes más utilizados, hablaba de Rust y Go. Los sigo considerando dos lenguajes muy interesantes y con mucho futuro. En el índice TIOBE de octubre 2021, Go ocupa la posición 12 y Rust la 26. Son lenguajes fundamentalmente dedicados a la programación de sistemas. Como opción laboral, son de los que mejores sueldos ofrecen a los programadores. Les seguiremos la pista para ver su evolución. Seguro que seguirán dando que hablar en el futuro.

Es muy interesante la encuesta anual de Stack Overflow, que ofrece respuestas a numerosas preguntas acerca del perfil de los lenguajes de programación y de los propios programadores.

A continuación se ofrece una lista de enlaces a algunos índices y a artículos comentando los distintos índices:

Stack Overflow Developer Survey 2021
TIOBE Index
The most in-demand programming languages of 2021 by TNW
Most in Demand Programming Languages 2021 by Merehead
GitHub Top Programming Languages
Top Programming Languages 2021 by IEEE Spectrum

Ficheros: lectura de lineas con datos de distinto tipo

Un problema habitual al leer datos de un fichero de texto es que cada linea de datos contenga datos de distinto tipo: cadenas de caracteres y datos numéricos. La lectura y extracción de dichos datos a variables es un poco más complicada que los casos en los que toda la información es del mismo tipo. Se va a resolver un ejemplo que permita aprender las técnicas que son necesarias en estos casos.

Para la resolución se va a utilizar el fichero datos.txt que, tras una línea de cabecera, contiene en cada línea los datos de incidencia COVID de un municipio de la provincia de Madrid a fecha 24 de noviembre de 2020. El fichero es el siguiente:

El alumno deberá crear este fichero con el editor, copiando los datos que contiene, y grabarlo con el nombre ‘datos.txt’, en el mismo directorio de trabajo donde se va a crear el programa .m de lectura del mismo.

La lectura del fichero debe comenzar con la apertura del mismo en modo lectura, ‘read’. El listado siguiente realiza la apertura del fichero y la comprobación de que el fichero se ha abierto correctamente. Al final del programa se pone la orden fclose(fid), para cerrar el fichero. El alumno deberá copiar estas instrucciones en el editor y guardarlo en el disco, en el mismo directorio en el que ha guardado el fichero ‘datos.txt’. El fichero del programa lo podemos llamar ‘readcovid.m‘, por ejemplo:

Conviene probar esta parte del programa. El programa se ejecuta desde la ventana de comandos, tecleando el nombre del programa ‘readcovid’. Antes de ejecutar el programa haga un ‘clear’, para borrar las variables del espacio de trabajo. Si al ejecutarlo no aparece nada en pantalla, es que ha funcionado bien. En el espacio de trabajo veremos la variable ‘fid’ con el identificador de fichero que le haya sido asignado, y la variable ‘ans’ con el valor cero, correspondiente a la ejecución correcta de la instrucción ‘fclose()’. Si en pantalla se muestra el mensaje ‘Error al abrir fichero’, es que el programa no encuentra el fichero de datos, quizás por poner mal el nombre en la instrucción ‘fopen’, o quizás porque el fichero de datos no está en el mismo directorio que el fichero del programa.

Una vez que se comprueba que funciona correctamente, vamos a proceder a completar las instrucciones de lectura antes de la orden ‘fclose(fid)’. El problema que vamos a resolver es leer los datos del fichero, determinar el municipio con mayor incidencia de COVID, y mostrar en pantalla sus datos: Municipio, Habitantes, Cod e Incidencia.

Vamos a empezar leyendo solo una línea, para entender el funcionamiento. La siguiente versión del programa lee los datos del primer municipio y los guarda en variables.

Lo primero que hace el programa es saltarse la línea de las cabeceras, mediante ‘fgetl()’. A continuación, lee los datos de una línea, de uno en uno, y los guarda en variables. Por último, hace un ‘fgets()’, para completar la lectura de la línea, leyendo el caracter fin de línea. De esta manera, el ‘cabezal de lectura’ quedará posicionado al principio de la siguiente línea de datos, preparado para seguir leyendo. El alumno debe observar que, para leer cada dato, la instrucción ‘fscanf’ utiliza el formato adecuado e indica que solo lee 1 dato. Tras ejecutar el programa y si todo va bien, la ventana del espacio de trabajo deberá mostrar las variables con los valores correctos (conviene hacer un ‘clear’, antes de cada ejecución del programa, para vaciar la memoria y el espacio de trabajo. Si se hace así, el espacio de trabajo mostrará, además de las variables del municipio, la variable ‘ans’ con el valor 0, correspondiente al resultado correcto de la orden ‘fclose’).

Una vez comprobado que podemos leer una línea de manera correcta, podemos proceder a leer todo el fichero, mediante un bucle ‘while’ con la condición ‘~feof(fid)’, o sea, ‘mientras no estemos en el FILE END OF FILE’. Para localizar el municipio con mayor incidencia de COVID en los últimos catorce días, aplicaremos el algoritmo del máximo. Inicializamos a cero unas variables donde guardar los datos máximos: munimax, habmax, codmax y incidmax. Cada vez que encontremos un municipio con incidencia máxima, guardaremos sus datos en las variables. Al final del bucle, tendremos guardados en las variables los datos del municipio con mayor incidencia, y los mostraremos en pantalla:

Al ejecutar el programa, y si todo va bien, deberíamos obtener una salida como la siguiente:

Otra técnica para leer este tipo de ficheros consiste en utilizar la instrucción ‘sscanf()’, que permite decodificar una cadena de texto. La instrucción ‘sscanf (String Scan Formatted) funciona igual que la instrucción ‘fscanf’ (File Scan Formatted), con la salvedad de que ‘sscanf’ lee datos desde una cadena de texto, mientras que ‘fscanf’ lee datos desde un fichero.

La técnica es similar, pero se leen líneas completas del fichero a una cadena de texto con ‘fgetl()’, y luego se decodifica la cadena con ‘sscanf()’. Para explicar el funcionamiento de la instrucción ‘sscanf’, vamos a jugar un poco en la ventana de comandos, antes de hacer el programa que lee el fichero COVID.

Ejecute en la ventana de comandos las siguientes instrucciones:

Creamos primero una cadena de texto en la variable cad, y luego utilizamos la instrucción ‘sscanf’ para extraer el primer valor, como cadena de texto, a la variable ‘muni’. Observese que el primer parámetro que pasamos a la instrucción ‘sscanf’ es la cadena que queremos decodificar, luego el formato de la decodificación y, por último, el número de elementos que queremos extraer con ese formato.

Para extraer el segundo valor de la cadena, hay que saltarse el primer valor y leer el segundo. Esto se consigue poniendo un formato ‘%*s’. El asterisco que ponemos entre el símbolo % y la letra s hace que dicho formato lo lea, pero no lo guarde. El valor que guarda en la variable es el del segundo formato. Seguimos diciendo a ‘sscanf’ que lea un solo valor, el valor que se salta no cuenta:

Para leer el tercer valor, hay que saltarse los dos primeros. Aquí hay que tener cuidado: aunque el segundo valor sea un entero, para saltarlo hay que utilizar ‘%*s’, como si fuera una cadena de texto. Si lo intentáramos saltar con un formato ‘%*d’, el tercer valor no lo leería como cadena, sino como un array de doubles. Por tanto, para saltarse valores, siempre hay que hacerlo con ‘%*s’, como si fueran cadenas, aunque sean números lo que queremos saltar:

La lectura del cuarto dato no presenta mayores dificultades:

Una vez entendido cómo funciona la instrucción ‘sscanf’, reproducimos a continuación el listado del mismo programa que hicimos antes, para extraer los datos del municipio con mayor incidencia:

La salida de este programa debería ser idéntica a la de la otra versión.

Vamos a ver, por último, cómo resolver si no se quieren guardar todos los datos de cada línea, sino solo alguno de ellos, descartando los demás. Por ejemplo, vamos a leer el número de habitantes de cada municipio a un vector, pero descartando todo el resto de información. La técnica consiste en leer en cada línea hasta el dato que buscamos, y descartar el resto de la línea haciendo ‘fgets()’:

La salida de este programa debería ser la siguiente:

Analogamente a lo realizado en el caso resuelto con ‘sscanf’, aquí también podríamos utilizar la técnica de saltarnos datos, con lo que el programa quedaría:

Al ejecutar el programa, el resultado sería idéntico al anterior.

Para este caso de querer leer solo los datos numéricos de una de las columnas del fichero a un vector, podemos combinar la técnica de saltarse datos con la de repetir el formato hasta que se acabe el fichero, y leer el vector con el número de habitantes de los municipios en una única instrucción y sin utilizar bucles:

La salida de resultados sería la misma que en los dos casos anteriores.

Por último, utilizando esta técnica, podríamos leer una matriz que tenga en la primera columna el número de habitantes, y en la segunda columna la incidencia COVID: