Arreglos de estructuras en C

mayo 8, 2020

En C, una estructura es una forma de agrupar variables de diferentes tipos bajo una misma etiqueta.

En el siguiente ejemplo declaramos una estructura llamada persona que contiene dos miembros. El primer miembro es un arreglo de 21 elementos de tipo char llamado nombre; el segundo miembro es una variable de tipo entero llamada edad.


struct persona {
 char nombre[21];
 int edad;
};

Ya que creamos la estructura llamada persona (en esta caso, la palabra persona es el identificador de la estructura o etiqueta), podemos declarar variables que sean de este tipo de estructura.


struct persona alumno, profesor;

El código anterior declara dos variables del tipo de estructura persona (la variable alumno y la variable profesor). Cada una de esas variables tiene un miembro nombre y un miembro edad.

Podemos crear estructuras sin ponerles etiqueta, pero en ese caso sólo podemos declarar variables de ese tipo de estructura al momento de definir la estructura.


struct {
 char nombre[21];
 int edad;
} alumno, profesor;

También podemos crear tipos de datos basados en estructuras utilizando la palabra reservada typedef. A continuación vamos a crear un tipo de dato llamado Persona.


typedef struct {
 char nombre[21];
 int edad;
} Persona;

En el código anterior podemos ver que en este caso no es necesario asignar una etiqueta a la estructura ya que para declarar variables que sean del tipo de nuestra estructura utilizaremos el tipo de dato Persona que estamos definiendo mediante typedef.

Una vez definido nuestro tipo de dato llamado Persona basado en la estructura que contiene los miembros nombre y edad, podemos crear variables de ese tipo de la siguiente forma


Persona alumno, profesor;

Podemos inicializar nuestras variables del tipo Persona asignando valores a cada uno de sus miembros al  momento de declararlas.


Persona alumno = {"Juan", 18}, profesor = {"Jesús", 30};

Para tener acceso a los miembros de una estrcutura podemos utilizar el operador punto. Por ejemplo, si queremos mostrar el nombre y edad de la variable alumno, es suficiente la siguiente línea de código


printf("Alumno: %s, Edad: %d\n", alumno.nombre, alumno.edad);

A continuación muestro un pequeño programa con todo lo que hemos visto hasta ahora


#include <stdio.h>

// Crea un tipo de dato llamado Persona
typedef struct {
 char nombre[21];
 int edad;
} Persona;

int main()
{
 Persona alumno = {"Juan", 18}, profesor = {"Jesús", 30};

 printf("Alumno: %s, Edad: %d\n", alumno.nombre, alumno.edad);
 printf("Profesor: %s, Edad: %d\n", profesor.nombre, profesor.edad);

 return 0;
}

Supongamos que en un preescolar, los grupos son de máximo 20 alumnos y hay dos grupos para cada grado (grupo A y grupo B). Podemos declarar arreglos de 20 elementos del tipo Persona para cada grupo.


Persona primeroGrupoA[20], primeroGrupoB[20];
Persona segundoGrupoA[20], segundoGrupoB[20];
Persona terceroGrupoA[20], terceroGrupoB[20];

Con eso ya podemos guardar el nombre y edad de cada alumno en cada uno de los grupos de los 3 grados.

El siguiente programa es un ejemplo en donde se captura el nombre y edad de 10 empleados y se pasa el arreglo a una función que regresa el promedio de edad. En un post anterior ya escribí sobre pasar arreglos a una función en C

En el programa uso fgets en lugar de scanf, para quienes no sepan por qué es mejor, les recomiendo el curso que puse sobre arreglos en C, es gratuito.


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Crea un tipo de dato llamado Persona
typedef struct {
 char nombre[21];
 int edad;
} Persona;

float PromedioEdad(Persona arreglo[], int tamanio)
{
 int contador;
 float suma=0, promedio=0;
 
 for (contador=0; contador<tamanio; contador++)
     {
      suma += arreglo[contador].edad;
     }
 promedio = suma/tamanio;
 
 return promedio;
}

int main()
{
 // Declara un arreglo de 10 elementos del tipo Persona
 Persona empleados[10];
 int i;
 char edad[3];
 char *p;
 
 printf("Capture el nombre y edad de %d empleados\n\n", 10);
 for (i=0; i<10; i++)
     {
      printf("Empleado %d\n", i+1);
      printf("Nombre: ");
      if (fgets(empleados[i].nombre,sizeof(empleados[i].nombre),stdin))
         {
          // Checa si existe el caracter \n
          if (p=strchr(empleados[i].nombre, '\n'))
             {
              *p = 0;
             }
          else
              {
               // Limpia el buffer
               scanf("%*[^\n]");
               scanf("%*c");
              }
         }
      printf("Edad: ");
      if (fgets(edad,sizeof(edad),stdin))
         {
          // Checa si existe el caracter \n
          if (p=strchr(edad, '\n'))
             {
              *p = 0;
             }
          else
              {
               // Limpia el buffer
               scanf("%*[^\n]");
               scanf("%*c");
              }
          
          empleados[i].edad = atoi(edad);
         }
     }
   
 printf("\nLista de empleados\n");
 printf("==================\n\n");
 for (i=0; i<10; i++)
     {
      printf("Empleado %d\n", i+1);
      printf("Nombre: %s, Edad: %d\n", empleados[i].nombre, empleados[i].edad);
      printf("-----------------------\n");
     }
 
 printf("El promedio de edad de los empleados es %.2f\n", PromedioEdad(empleados, 10));
 
 return 0;
}

 

Insertando caracteres en una matriz de forma ordenada

febrero 3, 2020

En una red social, una persona pidió ayuda para resolver un ejercicio que dijo era el ejercicio 34 (página 275) de un libro llamado “Lenguaje C – Teoría y Ejercicios Por Evelio Granizo Montalvo”.

El enunciado trata de realizar un programa que lea elementos de tipo caracter de una matriz (m, n). El programa debe ordenar la matriz de tal manera que todos los caracteres diferentes de blanco sean almacenados al comienzo de la matriz e imprima las matrices leída y ordenada.

Por ejemplo para la matriz con m = 3 y n = 4 se tendrá:

f
t a
u s r
Matriz leida
a f r s
t u
Matriz ordenada

Aquí está el código de una posible solución

/* Solución al ejercicio 34 página 275 del libro:
 * Lenguaje C - Teoría y Ejercicios Por Evelio Granizo Montalvo
 * 
 * 
 * Copyright 2020 SALOMON RINCON TORRES <rtmex@yahoo.com>
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *      
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *      
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA. */

#include <stdio.h>
#include <string.h>

#define FILAS 3
#define COLUMNAS 4

void ImprimeMatriz(unsigned char m[][COLUMNAS], int filas)
{
 int i=0,j=0;
 
 for (i=0; i<filas; ++i)
     {
      printf("|\t");
      for (j=0; j<COLUMNAS; j++)
           printf("%c\t|\t", m[i][j]);
      printf("\n");
     }
}

/* Recibe una matriz y la posición del caracter que se quiere comparar con los anteriores
   para ponerlo en la posición correspondiente para que queden en orden ascendente */
void PosicionaCaracter(unsigned char m[][COLUMNAS], int x, int y)
{
 int i=0,j=0;
 unsigned char caracter;
 
 for (i=y; i>=0; i--)
     {
      /* Si la fila actual es la fila que se recibió como parámetro,
       * empezamos por la columna que se recibió como parámetro.
       * En caso contrario (la fila es mayor a la que se recibió como
       * parámetro), iniciamos en la última columna de la fila*/
      j = (i==y ? x : COLUMNAS-1);
      for (; j>=0; j--)
          {
           /* Si la columna actual no es la primer columna de la fila, compara el
            * caracter actual con el caracter de la fila actual y la columna anterior */
           if (j>0)
              {
               if (m[i][j] < m[i][j-1])
                  {
                   caracter = m[i][j-1];
                   m[i][j-1] = m[i][j];
                   m[i][j] = caracter;
                  }
              }
           else
               /* Si la columna actual es la primer columna y la fila actual no 
                * es la primera fila (es decir, hay filas anteriores), compara
                * el caracter actual con el caracter de la última columna de la
                * fila anterior.
                * Si no se cumnple ese caso, significa que no hay caracteres anteriores
                * con los cuales comparar el caracter actual */
               if (j == 0 && i>0)
                  {
                   if (m[i][j] < m[i-1][COLUMNAS-1])
                      {
                       // Cambia con el último caracter de la fila anterior
                       caracter = m[i-1][COLUMNAS-1];
                       m[i-1][COLUMNAS-1] = m[i][j];
                       m[i][j] = caracter;
                      }
                  }
          }
     }
}

int main()
{
 int i, j;
 int posx=0, posy=0;
 unsigned char letra[2];
 unsigned char matrizA[FILAS][COLUMNAS], resultado[FILAS][COLUMNAS];
 
 /* Inicializa matrizA y resultado con espacios en cada celda */ 
 for (i=0; i<FILAS; i++)
     {
      for (j=0; j<COLUMNAS; j++)
          {
           matrizA[i][j] = ' ';
           resultado[i][j] = ' ';
          }
     }
 
 printf("ORDENA MATRIZ DE CARACTERES\n\n");
 printf("Este programa pide al usuario caracteres y los almacena en una matriz A\n");
 printf("El programa ordena la matriz de tal manera que todos los caracteres diferentes de blanco\n son almacenados al comienzo de la matriz\n");
 printf("Al final se imprimen la matriz leida (matriz A) y la matriz ordenada (matriz resultado)\n\n");
 
 /* Pide al usuario que introduzca los caracteres para matrizA */
 printf("Introduzca los caracteres para la matriz A\n");
 for (i=0; i<FILAS; i++)
     {
      for (j=0; j<COLUMNAS; j++)
          {
           printf("A[%d][%d] = ", i, j);
           
           if (fgets(letra,sizeof(letra),stdin))
              {
               char *p;
               // Checa si existe el caracter \n
               if (p=strchr(letra, '\n'))
                  { 
                   *p = 0;
                  }
               else
                   {
                    // Limpia el buffer
                    scanf("%*[^\n]");
                    scanf("%*c");
                   }
               
               matrizA[i][j] = letra[0];
              }
           
           // Sólo agrega el caracter a la matriz resultado si no es espacio en blanco o nulo
           if (matrizA[i][j] != ' ' && matrizA[i][j] != '\0') 
              {
               resultado[posy][posx] = matrizA[i][j];
               
               printf("Resultado\n");
               ImprimeMatriz(resultado,FILAS);
               printf("\n");
               PosicionaCaracter(resultado, posx, posy);
               printf("Resultado\n");
               ImprimeMatriz(resultado,FILAS);
               printf("\n");
               
               posx++;
               if (posx == COLUMNAS)
                  {
                   posy++;
                   posx = 0;
                  }
              }
          }
     }
     
 printf("\n");
 
 /* Imprime como quedó al final la matriz con los caracteres como se fueron
    insertando y como quedó la matriz con los caracteres ordenados */
 /* Imprime matrizA */ 
 printf("Resultado final\n***********************\n");
 printf("Matriz A (matriz original)\n");
 ImprimeMatriz(matrizA,FILAS);
 printf("\n");
 
 /* Imprime resultado */ 
 printf("Resultado (matriz ordenada)\n");
 ImprimeMatriz(resultado,FILAS);
 printf("\n");
 
 return 0;
}

 

Generador de sopa de letras hecho en Ruby

agosto 16, 2019

A mi hija (que al momento de escribir esto tiene 9 años) le gusta resolver sopa de letras, es ese juego que trata de encontrar en una cuadrícula llena de letras una lista de palabras.

Se me ocurrió hacer un programa que generara sopas de letras y así tener una fuente inagotable de éstas y con listas de palabras personalizadas.

En esta primera versión, hay que poner directamente en el código la lista de palabras (es un arreglo) y el programa sólo pone las palabras de forma horizontal o vertical. En versiones posteriores haré los cambios necesarios para que la lista de palabras la capture el usuario al inicio, también haré que el programa pueda acomodar palabras en diagonal y que al final pregunte al usuario si desea generar un archivo pdf de la sopa de letras generada.

Aquí un ejemplo del resultado de una ejecución

Y aquí el código fuente

Archivo sopa_letras.rb

# Regresa un arreglo cuyos elementos son las letras de la A a la Z (en mayúsculas)
def abecedario
    return Array('A'..'Z')
end

# Regresa una cadena de longitud caracteres tomados al azar del arreglo obtenido de llamar a la función abecedario
def cadena_aleatoria(longitud)
    return Array.new(longitud) { abecedario.sample }.join
end

# Reemplaza los ceros en el tablero con letras al azar
def reemplaza_ceros
    for i in 0...@tablero[0].length
        for j in 0...@tablero[0].length
            if @tablero[i][j] == 0
               @tablero[i][j] = abecedario.sample
            end
        end
    end
end

def imprime_tablero
    @tablero.each_with_index do |renglon, indice_r|
       renglon.each_with_index do |columna, indice_c|
          print "#{columna} "
       end
       puts
    end
    puts
end

# Busca en el tablero un renglón en donde existan longitud número de ceros consecutivos
# y regresa la posición del renglón y columna en caso de encontrarlo; de lo contrario
# regresa -1 para el renglón y -1 para la columna
def cabe_horizontal(longitud)
    fila = -1
    columna = -1
    
    # Obtiene al azar un número entre 1 y 2
    # Si el número es uno, intenta poner la palabra de forma horizontal empezando por el primer renglón
    # e irá bajando hasta el último
    # Si el número es dos, intenta poner la palabra de forma horizontal empezando por el último renglón
    # e irá subiendo hasta el primero
    inicio = rand(1..2)
    
    if inicio == 1
       @tablero.each_with_index do |renglon, indice_r|
          # Si en el renglón actual no hay ceros, procesa el siguiente renglón
          next if !renglon.include?(0)
          
          # Guarda la posición de la columna del primer cero que se encuentra en el renglón actual
          primer_cero = renglon.index(0)
          
          columna = primer_cero
          contador = 0
          for i in primer_cero...renglon.length
              if renglon[i] == 0
                 contador += 1
                 columna = i if contador == 1
              else
                  # Si el número de ceros consecutivos en el renglón actual es suficiente
                  # se sale del ciclo, de lo contrario inicializa contador y la columna
                  # en donde se empezaría a contar el número de ceros sería la actual (es decir, i)
                  break if contador >= longitud
                  contador = 0
              end
          end
          # Si el número de ceros consecutivos en el renglón actual es mayor o igual a la longitud
          # recibida como parámetro, no busca en los renglones restantes
          if contador >= longitud
             fila = indice_r
             break
          end
       end
    else
        (@tablero[0].size - 1).downto(0) do |indice_r|
           # Si en el renglón actual no hay ceros, procesa el siguiente renglón
           next if !@tablero[indice_r].include?(0)
           
           # Guarda la posición de la columna del primer cero que se encuentra en el renglón actual
           primer_cero = @tablero[indice_r].index(0)
           
           columna = primer_cero
           contador = 0
           for i in primer_cero...@tablero[indice_r].length
               if @tablero[indice_r][i] == 0
                  contador += 1
               else
                   # Si el número de ceros consecutivos en el renglón actual es suficiente
                   # se sale del ciclo, de lo contrario inicializa contador y la columna
                   # en donde se empezaría a contar el número de ceros sería la actual (es decir, i)
                   break if contador >= longitud
                   contador = 0
                   columna = i
               end
           end
           # Si el número de ceros consecutivos en el renglón actual es mayor o igual a la longitud
           # recibida como parámetro, no busca en los renglones restantes
           if contador >= longitud
              fila = indice_r
              break
           end
        end
    end
    
    return fila, columna
end

def cabe_vertical(longitud)
    fila = -1
    columna = -1
    @tablero.each_with_index do |renglon, indice_r|
       # Si en el renglón actual no hay ceros, procesa el siguiente renglón
       next if !renglon.include?(0)
       
       # Crea un arreglo con los números de columna que contienen ceros en el renglón actual
       columnas_cero = renglon.each_index.select { |indice| renglon[indice] == 0}
       columna = columnas_cero[0]
       
       # Para cada una de las columnas de columnas_cero, va contando los ceros consecutivos hacia abajo
       columnas_cero.each do |j|
          contador = 0
          columna = j
          # Sin cambiar de columna, empieza en el renglón actual y va contando los ceros en los renglones posteriores
          for i in indice_r...renglon.length
              if @tablero[i][j] == 0
                 contador += 1
              else
                  break
              end
          end
          # Si el número de ceros consecutivos en la columna actual es mayor o igual a la longitud
          # recibida como parámetro, no busca en las columnas restantes
          if contador >= longitud
             fila = indice_r
             break
          end
       end
       
       # Si el valor de la variable fila es mayor a cero, significa que iniciando en el renglón actual
       # se encontró una columna con suficientes ceros hacia abajo
       break if fila >= 0
    end
    
    return fila, columna
end

palabras = ["perro", "gato", "conejo", "elefante", "chimpance", "tigre", "pantera", "lobo", "panda", "perico"]

# Encuentra cuál es la cadena de mayor longitud en el arreglo palabras
longest_string = palabras.max_by(&:length)
mayor_longitud = longest_string.length

# Crea una copia del arreglo palabras
palabras_respaldo = palabras[0..palabras.length]

# Crea un arreglo llamado letras_azar que contendrá cadenas formadas por letras tomadas al azar
letras_azar = []
for i in 1..(mayor_longitud/2).to_int
    letras_azar.push(cadena_aleatoria(rand(1..3)))
end

# Agrega el contenido del arreglo letras_azar al arreglo palabras
palabras.concat(letras_azar)
# Revuelve los elementos del arreglo aleatoriamente
palabras.shuffle!

# Crea una matriz de longitud+3 x longitud+3 y la llena de ceros
@tablero = Array.new(mayor_longitud+3){Array.new(mayor_longitud+3,0)}

# Va sacando al azar elementos del arreglo palabras
while !palabras.empty? do
      selecciona = rand(palabras.length)
      # Copia la palabra que se encuentra en la posición selecciona del arreglo palabras
      palabra_seleccionada = palabras[selecciona].dup
      # Elimina del arreglo palabaras la palabra que se encuentra en la posición selecciona
      palabras.delete_at(selecciona)
      
      # Decide al azar si pondrá la palabra invertida o no
      invertir = [true, false].sample
      palabra_seleccionada.reverse! if invertir
      
      # Obtiene al azar un número entre 1 y 2
      # Si el número es uno, intenta poner la palabra de forma horizontal y si no es posible
      # después lo intenta de forma vertical
      # Si el número es dos, intenta poner la palabra de forma vertical y si no es posible
      # después lo intenta de forma horizontal
      opcion = rand(1..2)
      
      case opcion
           when 1
                # Verifica si la palabra seleccionada se puede poner de forma horizontal
                # en algún renglón del tablero
                row, column = cabe_horizontal(palabra_seleccionada.length)
                
                if row>=0 and column>=0
                   # Pone la palabra de forma horizontal en la posición indicada por los valores de las variables row y column
                   palabra_seleccionada.each_char do |letra|
                      @tablero[row][column] = letra.upcase
                      column +=1
                   end
                else
                    # Verifica si la palabra seleccionada se puede poner de forma vertical
                    # en algún renglón del tablero
                    row, column = cabe_vertical(palabra_seleccionada.length)
                    
                    if row>=0 and column>=0
                       # Pone la palabra de forma vertical en la posición indicada por los valores de las variables row y column
                       palabra_seleccionada.each_char do |letra|
                          @tablero[row][column] = letra.upcase
                          row +=1
                       end
                    end
                end
         when 2
              # Verifica si la palabra seleccionada se puede poner de forma vertical
              # en algún renglón del tablero
              row, column = cabe_vertical(palabra_seleccionada.length)
              
              if row>=0 and column>=0
                 # Pone la palabra de forma vertical en la posición indicada por los valores de las variables row y column
                 palabra_seleccionada.each_char do |letra|
                    @tablero[row][column] = letra.upcase
                    row +=1
                 end
              else
                  # Verifica si la palabra seleccionada se puede poner de forma horizontal
                  # en algún renglón del tablero
                  row, column = cabe_horizontal(palabra_seleccionada.length)
                  
                  if row>=0 and column>=0
                     # Pone la palabra de forma horizontal en la posición indicada por los valores de las variables row y column
                     palabra_seleccionada.each_char do |letra|
                        @tablero[row][column] = letra.upcase
                        column +=1
                     end
                  end 
              end
      end
end

# Reemplaza los ceros en el tablero con letras al azar
reemplaza_ceros

# Muestra la lista de palabras a encontrar en la sopa de letras
puts "SOPA DE LETRAS"
puts "="*50
puts "PALABRAS A ENCONTRAR:"
puts
palabras_respaldo.sort.each do |palabra|
   puts palabra.upcase
end
puts
puts "*"*50
imprime_tablero

Instalar el verdadero MySQL en Debian 9

mayo 30, 2019

Por si alguien no lo sabe, en los repositorios de Debian 9 ya no viene incluido MySQL, en su lugar viene MariaDB. El paquete llamado mysql-server, depende del paquete default-mysql-server y este depende del paquete mariadb-server-10.1.

Pueden encontrar más información al respecto en los siguientes enlaces:

Moving from MySQL to MariaDB in Debian 9
Debian 9 released with MariaDB as the only MySQL variant

Para instalar el verdadero MySQL debemos incluir los repositorios APT de MySQL para Debian en el archivo /etc/apt/sources.list agregandole las siguientes líneas

deb http://repo.mysql.com/apt/debian/ stretch mysql-5.7
deb-src http://repo.mysql.com/apt/debian/ stretch mysql-5.7

Antes de ejecutar apt update, debemos descargar el archivo RPM-GPG-KEY-mysql de https://repo.mysql.com

wget https://repo.mysql.com/RPM-GPG-KEY-mysql

Agregamos el paquete a apt keyring

sudo apt-key add RPM-GPG-KEY-mysql

Ahora si actualizamos la lista de paquetes
sudo apt update

Instalamos MySQL
sudo apt install mysql-server

Nos pide que asignemos un password para el usuario root de MySQL

Y eso es todo, tenemos instalado MySQL y no MariaDB

Instalar nginx + tomcat en Debian 9

febrero 1, 2019

Es muy común que aplicaciones web desarrolladas con Java Server Pages (JSP) se ejecuten con Apache + Tomcat. A mi me gusta más usar Nginx como servidor wen en lugar de Apache; así que en este post voy a poner los pasos a seguir para ejecutar en Debian 9 aplicaciones web desarrolladas con JSP usando Nginx en lugar de Apache.

Instalar Java

sudo apt install openjdk-8-jdk

Aparece lo siguiente:

Leyendo lista de paquetes… Hecho
Creando árbol de dependencias
Leyendo la información de estado… Hecho
Se instalarán los siguientes paquetes adicionales:
libice-dev libpthread-stubs0-dev libsm-dev libx11-dev libx11-doc libxau-dev
libxcb1-dev libxdmcp-dev libxt-dev openjdk-8-jdk-headless x11proto-core-dev
x11proto-input-dev x11proto-kb-dev xorg-sgml-doctools xtrans-dev
Paquetes sugeridos:
libice-doc libsm-doc libxcb-doc libxt-doc openjdk-8-demo openjdk-8-source
visualvm
Se instalarán los siguientes paquetes NUEVOS:
libice-dev libpthread-stubs0-dev libsm-dev libx11-dev libx11-doc libxau-dev
libxcb1-dev libxdmcp-dev libxt-dev openjdk-8-jdk openjdk-8-jdk-headless
x11proto-core-dev x11proto-input-dev x11proto-kb-dev xorg-sgml-doctools
xtrans-dev
0 actualizados, 16 nuevos se instalarán, 0 para eliminar y 0 no actualizados.
Se necesita descargar 13.7 MB de archivos.
Se utilizarán 60.0 MB de espacio de disco adicional después de esta operación.
¿Desea continuar? [S/n]

Decimos que si

Instalar Tomcat

sudo apt install tomcat8

Aparece lo siguiente:

Leyendo lista de paquetes… Hecho
Creando árbol de dependencias
Leyendo la información de estado… Hecho
Se instalarán los siguientes paquetes adicionales:
authbind libapr1 libcommons-dbcp-java libcommons-pool-java libecj-java
libtcnative-1 libtomcat8-java tomcat8-common
Paquetes sugeridos:
libcommons-dbcp-java-doc libgeronimo-jta-1.1-spec-java ecj libecj-java-gcj
tomcat8-admin tomcat8-docs tomcat8-examples tomcat8-user
Se instalarán los siguientes paquetes NUEVOS:
authbind libapr1 libcommons-dbcp-java libcommons-pool-java libecj-java
libtcnative-1 libtomcat8-java tomcat8 tomcat8-common
0 actualizados, 9 nuevos se instalarán, 0 para eliminar y 0 no actualizados.
Se necesita descargar 7 092 kB de archivos.
Se utilizarán 9 161 kB de espacio de disco adicional después de esta operación.
¿Desea continuar? [S/n]

Decimos que si

Instalar nginx

sudo apt install nginx

Aparece lo siguiente:

Leyendo lista de paquetes… Hecho
Creando árbol de dependencias
Leyendo la información de estado… Hecho
Se instalarán los siguientes paquetes adicionales:
libnginx-mod-http-auth-pam libnginx-mod-http-dav-ext libnginx-mod-http-echo
libnginx-mod-http-geoip libnginx-mod-http-image-filter
libnginx-mod-http-subs-filter libnginx-mod-http-upstream-fair
libnginx-mod-http-xslt-filter libnginx-mod-mail libnginx-mod-stream
nginx-common nginx-full
Paquetes sugeridos:
fcgiwrap nginx-doc ssl-cert
Se instalarán los siguientes paquetes NUEVOS:
libnginx-mod-http-auth-pam libnginx-mod-http-dav-ext libnginx-mod-http-echo
libnginx-mod-http-geoip libnginx-mod-http-image-filter
libnginx-mod-http-subs-filter libnginx-mod-http-upstream-fair
libnginx-mod-http-xslt-filter libnginx-mod-mail libnginx-mod-stream nginx
nginx-common nginx-full
0 actualizados, 13 nuevos se instalarán, 0 para eliminar y 0 no actualizados.
Se necesita descargar 1 588 kB de archivos.
Se utilizarán 2 865 kB de espacio de disco adicional después de esta operación.
¿Desea continuar? [S/n] s

Decimos que si

En un navegador web nos vamos a localhost y nos debe aparecer la siguiente pantalla, que significa que nginx quedó correctamente instalado

Crear la estructura de directorios

Creamos un directorio llamado ejemplo dentro de /var/lib/tomcat8/webapps

sudo mkdir /var/lib/tomcat8/webapps/ejemplo

Posteriormente creamos los siguientes directorios

ejemplo (este es el directorio que acabamos de crear)
|
|--- src
|--- WEB-INF
     |
     |--- classes
     |--- lib

sudo mkdir /var/lib/tomcat8/webapps/ejemplo/src

sudo mkdir /var/lib/tomcat8/webapps/ejemplo/WEB-INF

sudo mkdir /var/lib/tomcat8/webapps/ejemplo/WEB-INF/classes

sudo mkdir /var/lib/tomcat8/webapps/ejemplo/WEB-INF/lib

Dentro del direcorio src irán los archivos *.java

En el directorio WEB-INF debe existir un archivo llamado web.xml con el siguiente contenido


<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
</web-app>

En WEB-INF/classes van los archivos *.class y los archivos *.jar van en WEB-INF/lib
Por último, en nuestro directorio ejemplo van los *.jsp, *.js, *.html y demás contenido estático.

Crear nuestro archivo .jsp

En el directorio /var/lib/tomcat8/webapps/ejemplo vamos a crear un archivo llamado ciclo.jsp

sudo nano /var/lib/tomcat8/webapps/ejemplo/ciclo.jsp

El contenido es el siguiente:

<body>

Ejemplo JSP con nginx<br>

<%
for(int i = 0; i < 10; i++)
   {
    out.println("El valor de la variable i es: " + i + "<br>");
   }
%>
Fin del ciclo <br>
</body>

Para salir del editor nano, pulsamos las teclas Ctrl y X simultáneamente, nos pregunta si queremos guardar los cambios, a lo que respondemos que si.

En el directorio /etc/nginx/sites-available vamos a crear un archivo llamado ejemplo.conf

sudo nano /etc/nginx/sites-available/ejemplo.conf

El contenido del archivo es el siguiente:

server { 
  listen 80; 
  server_name localhost;
  
  root /var/lib/tomcat8/webapps/ejemplo; 
  index index.jsp index.html index.htm;
  
  charset utf-8; 
  client_max_body_size 4G; 
  
  location / { 
         try_files $uri $uri/ =404;
  }
  
  location ~ \.jsp$ { 
         proxy_pass http://127.0.0.1:8080;
         proxy_set_header X-Real-IP $remote_addr; 
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
         proxy_set_header X-Forwarded-Proto $scheme;
         proxy_set_header Host $host; 
         proxy_redirect off; 
         proxy_http_version 1.1;
  }
  
  #error_page 404 /404.html; 
  
  # redirect server error pages to the static page /50x.html 
  
  error_page 500 502 503 504 /50x.html; 
  location = /50x.html { 
         root html; 
  } 
}

Hacemos un enlace simbólico en el directorio /etc/nginx/sites-enabled que apunte al archivo de configuración que acabamos de crear en /etc/nginx/sites-available

sudo ln -s /etc/nginx/sites-available/ejemplo.conf /etc/nginx/sites-enabled/ejemplo.conf

Modificamos el archivo /etc/nginx/nginx.conf cambiando la línea
include /etc/nginx/sites-enabled/*;
por
include /etc/nginx/sites-enabled/*.conf;

Reiniciamos nginx

sudo service nginx restart

Configurar Tomcat para que trabaje en conjunto con nginx

Hacemos una copia de respaldo del archivo /etc/tomcat8/server.xml

cd /etc/tomcat8
sudo cp server.xml server_ORIG.xml

Editamos el archivo server.xml de tal forma que abajo de la línea <Engine name="Catalina" defaultHost="localhost">
quede lo siguiente:
<Valve className="org.apache.catalina.valves.RemoteIpValve"
       internalProxies="127\.0\.[0-1]\.1"
       remoteIpHeader="x-forwarded-for"
       requestAttributesEnabled="true"
       protocolHeader="x-forwarded-proto"
       protocolHeaderHttpsValue="https"/>

Después de guardar el archivo, reiniciamos Tomcat

sudo service tomcat8 restart

Probando la aplicación

En nuestro navegador web vamos a localhost/ejemplo/ciclo.jsp y el resultado debe ser el siguiente

Post 3: ¿Por qué no hago “coding challenges”?

enero 21, 2019

Es una práctica bastante extendida que las empresas subcontraten a otra empresa para hacer la búsqueda de talento cuando requieren contratar personal. La empresa que tiene el puesto vacante pasa a la agencia que hará la búsqueda de talento la descripción del puesto, los requisitos y el salario (o un rango salarial) y prestaciones que ofrecen.

Hace ya algún tiempo que empresas que buscan cubrir vacantes en el área de tecnologías de la información, específicamente para desarrollo de software, acostumbran incluir en los requisitos que se les pida a los candidatos hacer los llamados “coding challenges”, que básicamente consisten en hacer pequeños programas como una función, etc. en algún lenguaje de programación determinado.

Esto es aceptable cuando la empresa busca contratar un recién egresado o alguien que haga sus prácticas profesionales, porque desafortunadamente en México, el nivel de conocimientos de los egresados de alguna carrera relacionada con tecnologías de la información es bastante malo en la mayoría de los casos.

Lo que muchas de estas empresas parece que no han captado es que no puedes aplicar el mismo proceso de selección para alguien con experiencia. Cualquier profesional de TI enfocado en el desarrollo de software que se mantenga activo, normalmente tiene código disponible públicamente en internet al cual se puede tener acceso de forma gratuita, ya sea en una cuenta de github, en un blog, en youtube, etc. En el muy poco probable caso de que nunca hubiera hecho público algo de código programado por él, seguramente ha escrito muchos programas y podrá enviarle a la empresa el código fuente de alguno de ellos.

En lo personal yo he hecho público mucho código desarrollado por mi en este blog, y en otros lugares. Incluso el código fuente de mi proyecto final de maestría esta disponible en sourceforge.net. Cuando una empresa que me contacta para una vacante relacionada con desarrollo de software me pide que haga un “coding challenge” después de haber visto mi CV, sé que esa empresa no pone interés en los CV que recibe; ya que de ser así hubiera visto que en mi CV hay un enlace a mi blog, en el cual hay suficiente código.

No hago “coding challenges” porque si una empresa que busca ofrecerme un empleo que implique desarrollar software requiere pruebas de que sé programar, las pruebas están a la vista y ahí han estado durante años, si no quiere tomarse la molestia de revisarlas, entonces no me interesa, por lo que no tiene caso que los haga perder su tiempo ni que yo invierta el mio. Si me tratan como recién egresado, lo más probable es que me ofrezcan un salario de recién egresado, mismo que no voy a aceptar, así de simple.

El que alguien me pida que haga un examen de programación pudiendo ver todo el código que he puesto libremente y de manera gratuita a disposición del público en general, es tanto como decirme mentiroso, es una falta de respeto; en el mejor de los casos el mensaje que envía es de desconfianza y no estoy interesado en establecer una relación laboral (o de algún otro tipo) cuando hay desconfianza de la otra parte.

Post 2: ¿Cómo selecciono las herramientas que utilizo para desarrollar software?

enero 10, 2019

Debido a que existen muchos lenguajes de programación y herramientas para desarrollar software, es imposible saber todos los lenguajes y conocer todas las herramientas disponibles.

He visto que muchos desarrollan en lo que la mayoría usa o lo que está de moda.
En este post voy a describir cómo es que yo elegí algunos de los lenguajes de programación y herramientas que uso para desarrollar software.

Empece a crear programas para computadoras hace más de 30 años, el primer lenguaje de programación que aprendí fue Basic, porque era el único con el que podía programar la primer computadora a la que tuve acceso (mi hermano y yo vendimos nuestro Atari 2600 para comprar una computadora Commodore 16).

Después de varios años desarrollando software, ya sea en COBOL para una VAX 4200, o aplicaciones Desktop en Clipper, C++ y algunos otros lenguajes (antes no existía la web), llega el momento de tener que aprender a desarrollar aplicaciones web (no me refiero a una página HTML estática, sino a sistemas multiusuario con acceso a base de datos, etc.).

Aprendí Java e hice pruebas con servlets y Tomcat, no estuvo mal pero tenía que probar otros lenguajes y herramientas, así que probé con PHP; tampoco estuvo mal pero empezó a sonar por todos lados Ruby on Rails así que decidí probarlo. Leí la documentación del sitio web oficial, seguí unos tutoriales y un curso gratuito en internet y después me dispuse a hacer una aplicación (no es lo mismo un ejemplo de un tutorial o un curso que el desarrollo de un sistema real).

Una vez que ya había hecho el diagrama entidad-relación de la base de datos para la aplicación que iba a crear, empiezo a intentar crear los modelos en Rails y empiezo a descubrir algunos de los varios problemas que tiene ActiveRecord (el ORM que usa Rails), como el hecho de que la llave primaria de cada tabla tenga que estar formada por un sólo campo, que dicho campo deba llamarse id, ser de tipo entero y autoincrementable. Hay formas de “forzar” a Rails para que el campo que será la llave primaria no tenga que llamarse id (una forma de hacerlo se describe en este post http://ruby-journal.com/how-to-override-default-primary-key-id-in-rails/), pero hay otros problemas más, algunos de ellos los menciono en este post ¿Cómo diferenciar un buen desarrollador de software en Ruby del resto? en mi blog.

No tardé mucho tiempo en darme cuenta que debido a la filosofía de Rails de “Convention over Configuration”, estaba invirtiendo mucho tiempo tratando de implementar un modelo de base de datos bien diseñado en Rails. Así que en mi opinión Rails es un mal framework si quieres desarrollar aplicaciones bien hechas (con un buen diseño de base de datos para empezar). Busqué otros frameworks para desarrollo web con Ruby y vi algunos pero también eran “opinionated” como Rails, así que eso no me servía, hasta que descubrí Sinatra. En Sinatra no estás obligado a usar ActiveRecord, puedes usar otro ORM, así que intenté desarrollar mi aplicación utilizando Datamapper y pude implementar la base de datos tal y como la había diseñado, sin tener que estarme peleando con la herramienta.

La conclusión fue: Ruby si, Rails no.

No me gustan las herramientas que me quieren obligar a hacer las cosas de cierta manera, y menos si esa manera es la manera incorrecta de hacer las cosas. El hecho de que Rails te “obligue” a que la llave primaria de cada tabla esté formada por un solo campo llamado id de tipo entero autoincrementable porque “es más fácil” así, no significa que sea la forma correcta. Mi prioridad es hacer las cosas bien, no hacerlas de la forma que me resulte más fácil sin importar si están mal hechas. Pero resulta que puedas hacer las cosas bien y “fácil” si usas las herramientas correctas.

No está mal si un día usas Rails probablemente para hacer una aplicación que se va a usar una sóla vez y se necesita terminarla rápido, por ejemplo si necesitas crear un sistema para el registro de los asistentes a un congreso y ya tienes muy poco tiempo, ahí no importa mucho que la base de datos esté mal diseñada, es decir, como Rails la necesita para que funcione sin interponerse en tu camino.

Desarrollé unas cuantas aplicaciones con Sinatra y Datamapper (aplicaciones reales que actualmente están instaladas y funcionando), pero como siempre busco la mejor forma de hacer las cosas, encontré otro ORM llamado Sequel, que es más versátil que Datamapper, así que desarrollé una aplicación con Sinatra y Sequel para probarlo y quedé muy satisfecho. Desde entonces las aplicaciones web que he desarrollado (que también están instaladas y funcionando en producción) han sido utilizando Ruby con Sinatra y Sequel (además de otras herramientas desde luego).

Continuamente estoy aprendiendo y probando diferentes lenguajes y herramientas para desarrollo de software, desde luego pruebo lo que todos usan y pruebo alternativas y muy frecuentemente encuentro cosas mejores, y eso es lo que uso, no me importa que no sea lo que la mayoría usa. Yo uso lo que he probado que funciona mejor y con lo que me siento más cómodo trabajando. No decido usar un lenguaje de programación o herramienta sólo porque “hay muchas empresas que lo usan”, o “es lo que las empresas están solicitando si buscas trabajo”.

Como dice un refrán asiático: “Un tigre no pierde el sueño por la opinión de las ovejas”.

Otra combinación de lenguaje de programación y framework para desarrollo web que utilizaría es Python con Flask, (ya probé Django y aunque no es tan “opinionated” como Rails, no me ofrece la libertad que yo requiero para sentirme cómodo trabajando). No he tenido tiempo de hacer muchas pruebas con Flask pero lo poco que he probado me ha parecido bien.

Para concluir este post, mi recomendación es que si te interesa utilizar un lenguaje de programación o herramienta porque está de moda, te informes leyendo la documentación oficial, si te sigue llamando la atención lo aprendas y lo pruebes pero busques alternativas, probablemente encuentres algo mejor o que su performance y otras cualidades sean similares pero con lo cual te sientas más cómodo trabajando. Si lo encuentras, que no te importe que los demás te digan “pero la mayoría usa X en lugar de lo que tú estás usando” o algún otro argumento tonto; y si no lo encuentras, al menos estarás usando lo que usas no porque esté de moda sino porque realmente es lo que mejor funciona para ti.

Post 1: Las prácticas profesionales

enero 7, 2019

Recuerdo que en la universidad en donde estudié la licenciatura, a partir de quinto semestre podíamos realizar algo que llamaban “prácticas profesionales”, consistía en cubrir cierto número de horas trabajando en algún lugar y dicho lugar expedía un documento dirigido a la universidad en donde confirmaba que habías trabajado ahí dicho número de horas.

Estando en quinto semestre un compañero de carrera nos comento a otro estudiante y a mí que se había enterado que en una dependencia de gobierno existía la posibilidad de realizar las prácticas profesionales, fuimos los 3 y nos entrevistó una persona de dicha dependencia de gobierno.
Como yo quería que mis prácticas profesionales realmente me fueran útiles hice tres preguntas fundamentalmente.

  1.  ¿Qué trabajo es el que realizaría?
  2.  ¿Cuál es el horario?
  3.  ¿Hay alguna remuneración ?

No recuerdo las palabras exactas de las respuestas pero básicamente fue lo siguiente:

Respuestas a las preguntas 1 y 2.

Pues algunas veces tendrán que graficar datos en una hoja de cálculo, pero el trabajo realmente es estar aquí, si el “jefe” sigue en la oficina, no importa que ustedes no tengan trabajo asignado, tienen que estar aquí, porque en cualquier momento puede necesitar algo. Así que no hay hora de salida, la hora de salida es cuando hayan terminado lo que tenían asignado y el jefe ya se haya retirado.

Respuesta a la pregunta 3

No, no hay remuneración alguna

Desde ese momento yo di las gracias pero como mis compañeros (extrañamente para mi) si estuvieron interesados, esperé a que terminaran de ponerse de acuerdo con la persona que nos atendió para empezar a realizar sus prácticas profesionales en ese lugar.

Lo que pregunté era importante para mi porque muchos estudiantes que realizan sus prácticas profesionales, no hacen algo relacionado con su área de estudio, los ponen a fotocopiar documentos o hacer diligencias para los otros empleados del departamento (ir por las memelas, tacos, tamales, refrescos , etc.). El horario es importante porque debía tener libertad para seleccionar mi carga académica en la universidad y además, no estaba interesado en hacer mis prácticas profesionales en un lugar en donde te ven como esclavo y no como practicante.

Pregunté si había alguna remuneración porque desde luego no esperaba que fuera un sueldo, pero por lo menos que me dieran algo que me ayudara a pagar la gasolina o el pasaje para llegar ahí, no estaba interesado en hacer mis prácticas en un lugar en donde esperaran que yo tuviera que incurrir en gastos por ir a hacer trabajo para ellos.

Cuando los 3 nos retiramos del lugar, mis compañeros me decían que no podían creer que no hubiera aceptado y que nunca iba a encontrar un lugar en donde pagaran a las
personas que hacían sus prácticas profesionales. Yo les comenté que había tiempo todavía para buscar un lugar, creo que era posible hacer las prácticas profesionales hasta séptimo semestre, así que yo no tenía prisa. Un par de meses después un primo mío vio un anuncio en el periódico en donde solicitaban practicantes para el departamento de sistemas. Acudí a entrevista y las respuestas básicamente fueron las siguientes:

Respuesta a la pregunta 1

Vas a desarrollar reportes en COBOL con SQL

Respuesta a la pregunta 2

Tu pones tu horario pero debes cubrir un total de 20 horas a la semana (equivalente a 4 horas diarias de Lunes a Viernes)

Respuesta a la pregunta 3

Si, es una pequeña ayuda mensual

Como podrán imaginarse, hice mis prácticas profesionales en esa empresa.

Presentación de la categoría “I don’t lead, nor follow… I make my own path”

enero 7, 2019

Las personas que tenemos una mente curiosa generalmente somos como dice la canción Thunder de Imagine Dragons “Not a Yes Sir, not a follower”.
En lo personal esto me ha traído ciertas complicaciones desde que estudiaba la licenciatura en la universidad (posiblemente desde antes).

En esta categoría voy a escribir sobre algunas cosas que he vivido y que de alguna forma reflejan mi forma de pensar y de ser, sobre todo en el ámbito profesional. Probablemente le sea útil a los recién egresados de alguna licenciatura relacionada con tecnologías de la información, ya sea para aprender de mis errores y no cometerlos o para sentirse identificados si encuentran que han vivido algo similar.

Utilizando ZeroMQ en Ruby

noviembre 29, 2018

ZMQ (o ZeroMQ) es una biblioteca de mensajería desarrollada en C++. Es software libre que se distribuye bajo una licencia LGPL.

Existen bindings (APIs) para utilizar ZMQ en muchos lenguajes de programación, en éste artículo voy a mostrar un ejemplo utilizando Ruby.

Simula el desarrollo de un sistema para un restaurant; la idea es que en el área de cocina exista una computadora en donde estén llegando automáticamente las comandas que los meseros capturan en otra PC que se encuentra en el área de mesas.

La computadora que vamos a usar para simular la PC que estará en el área de cocina tiene instalado Debian 9 y la PC que utilizaremos para simular la computadora en donde los meseros capturan las comandas tiene instalado PointLinux (es una distribución basada en Debian).

En ambas PC debemos instalar Ruby, la bibilioteca zmq, el paquete ruby2.3-dev y la gema ffi-rzmq.

sudo apt install ruby
sudo apt install libzmq3-dev
sudo apt install ruby2.3-dev
sudo gem install ffi-rzmq

Aquí un video con la explicación y después los programas utilizados para cada una de las PC.

Programa servidorComandas.rb

#!/usr/bin/ruby
#encoding: utf-8

require 'ffi-rzmq'
require 'json'

# Crea un socket tipo PULL, para recibir mensajes
context = ZMQ::Context.new(1)
pull = context.socket(ZMQ::PULL)
# Escucha (recibe mensajes de cualquier IP) en el puerto 5567
pull.bind("tcp://*:5567")

puts "SERVIDOR DE COMANDAS EN FUNCIONAMIENTO"
while true
      mensaje = ''
      pull.recv_string(mensaje)
      
      comanda = JSON.parse(mensaje)
      puts "COMANDA #{comanda['folio']} recibida a las: "+Time.now.strftime("%H:%M:%S")
      puts "Mesa: #{comanda['mesa']}, Mesero: #{comanda['mesero']}"
      puts "="*50
      puts " "
      
      comanda["orden"].each do |partida|
         puts "#{partida['cantidad']} #{partida['descripcion']}"
      end
      puts "*"*50
      puts " "
end

Programa creaComandas.rb

#!/usr/bin/ruby
#encoding: utf-8

require 'ffi-rzmq'
require 'json'

# Esta función regresa verdadero si el dato que recibe es un número
# y regresa falso en caso contrario
def numero_valido?(dato)
    true if Float(dato) rescue false
end

# Crea un socket tipo PUSH, para enviar mensajes
context = ZMQ::Context.new(1)
socket = context.socket(ZMQ::PUSH)
# Se conecta al servidor en el puerto 5567
socket.connect("tcp://192.168.1.1:5567")

folio = 0

loop do
     folio += 1
     puts "COMANDA #{folio}"
     print "Mesa: "
     # Pide al usuario que teclee la mesa
     mesa = gets.chomp
     
     # Si el usuario teclea enter, sale del ciclo loop do (termina el programa)
     if mesa.empty?
        break
     else
         print "Mesero: "
         # Pide al usuario que teclee el mesero
         mesero = gets.chomp
         puts "\nOrden"
         puts "="*50
         orden = []
         
         # Inicia un ciclo loop do para ir capturando la orden (cantidad y descripción)
         loop do
            cantidad = nil
            # Mientras el usuario no teclee un número mayor o igual a cero,
            # el programa le pide que teclee la cantidad
            while true
                  print "Cantidad: "
                  cantidad = gets.chomp
                  # Si el usuario teclea enter, sale del ciclo while
                  break if cantidad.empty?
                  
                  # Si el usuario tecleó un número, sólo sale del ciclo while si el número
                  # es mayor o igual a cero
                  # Si el usuario no tecleó un número, el ciclo while continúa ejecutándose
                  if numero_valido?(cantidad)
                     break if cantidad.to_i>=0
                  end
            end
            # Si el usuario tecleó cero para el campo cantidad, significa que terminó
            # de capturar la orden; por lo tanto se sale del ciclo loop do
            
            break if cantidad.to_i == 0
            # Como el usuario no tecleó cero para el campo cantidad, le pide que teclee la descripción
            print "Descripción: "
            descripcion = gets.chomp
            # Agrega al arreglo orden la cantidad y descripción tecleadas por el usuario
            orden.push({:cantidad => cantidad, :descripcion => descripcion})
         end
         
         comanda = {:folio => folio, :mesa => mesa, :mesero => mesero,
                    :orden => orden}.to_json
         
         # Manda los datos de la comanda al servidor
         socket.send_string(comanda)
     end
end