IP pública no es lo mismo que IP fija

noviembre 22, 2023

Es increible que existan personas de TI (tecnologías de la información), ya sea ingenieros en sistemas computacionales, licenciados en informática, etc. encargados de «asesorar» a empresas y no sepan algo tan básico como la diferencia entre IP pública, IP privada, IP fija e IP dinámica.

Debido a esta vergonzosa realidad, decidí escribir este post para aclarar esos términos, y así la próxima vez que me toque tratar con uno de estos incompetentes, lo mande a leer mi post para ver si por fin lo entiende.

IP fija

Como su nombre lo dice, es una IP que no cambia, es decir, una vez que se le asigna la IP a el dispositivo en cuestión, ese dispositivo siempre tendrá la misma IP.

IP dinámica

El que un dispositivo tenga asignada una IP dinámica significa que dicha IP puede cambiar la próxima vez que ese dispositivo se conecte a la red. Normalmente un dispositivo dentro de una red adquiere una IP dinámica cuando no tiene asignada un IP y se conecta a la red, en se momento el servidor DHCP le asigna una IP disponible. El dispositivo mantendrá esa IP mientras no se desconecte de la red, pero una vez desconectado, la próxima vez que se conecte puede que el servidor DHCP le asigne una IP diferente a la que le había asignado anteriormente.

El que un dispositivo tenga una IP fija o dinámica no tiene relación alguna con que dicha IP sea pública o privada. Es decir, un dispositivo puede tener una IP pública fija o una IP pública dinámica, de igual manera puede tener una IP privada fija o una IP privada dinámica. A continuación explico la diferencia entre una IP pública y una privada.

IP Pública

Una IP pública es la dirección IP con la que se identifica un dispositivo conectado a Internet.

IP Privada

Una IP privada es la dirección IP con la que se identifica un dispositivo conectado a una red de área local.

Hay dos formas muy fáciles de crear una red de área local:

  1. Usando un switch. En este caso, conectamos al switch los dispositivos que queramos que estén en la red y le asignamos a cada uno una IP. Con esto, cada dispositivo tendría una IP fija privada
  2. Usando un router. Conectamos al router los dispositivos que queramos que estén en la red y si en el router tenemos configurado el servidor DHCP, este asignará automáticamente una IP a cada uno de los dispositivos. Con esto, cada dispositivo tendría una IP dinámica privada.

    Aún cuando esté configurado el servidor DHCP de nuestro router, tenemos la posibilidad de asignar manualmente una IP a cualquier dispositivo, con lo cual dicho dispositivo tendrá una IP fija privada y los demás una IP dinámica privada. Por otro lado, si deshabilitamos el servidor DHCP de nuestro router, tendremos que asignar manualmente una dirección IP a cada uno de los dispositivos (como haríamos en el caso de usar un switch).

    Si tenemos más dispositivos que el número de puertos que tiene nuestro router, podemos conectar un switch al router y si está configurado el servidor DHCP del router, los dispositivos conectados al switch recibirán también una dirección IP automáticamente.

Cuando contratamos un servicio de internet con algún ISP (Internet Service Provider), este nos da un router con el que tenemos salida a Internet. Este router puede tener asignada una IP pública (visible desde Internet) ya sea fija (que no va a cambiar) o dinámica (si el router se desconecta de Internet, la próxima vez que se conecte el ISP puede asignarle una IP pública diferente) o usar CG-NAT (Carrier Grade Network Address Translation), en cuyo caso no tendremos una dirección IP pública, sino una IP privada dentro de la red de nuestro ISP; y esta IP privada puede ser fija o dinámica (espero que con todo lo explicado anteriormente eso quede claro).

En resumen, el ISP con el que contratamos el servicio de Internet puede darnos un router con una IP pública fija, una ip pública dinámica o un router que use CG-NAT, en cuyo caso tendrá una IP privada, que puede ser fija o dinámica.

Necesitamos que nuestro router tenga una IP pública si por ejemplo queremos instalar en nuestra red local un servidor WEB que podamos accesar por medio de Internet desde otra ubicación (obvio hay que abrir el puerto correspondiente en el router y asignarlo a la IP privada de nuestro dispositivo en donde instalamos el servidor WEB). Si nuestro router usa CG-NAT, no tenemos una dirección IP pública y por lo tanto no podemos accesar a dispositivo alguno de nuestra red local desde otra ubicación por medio de Internet.

¿Cómo saber si el router que nos proporcionó nuestro ISP tiene IP pública o usa CG-NAT?

Sin necesidad de accesar al router que te proporcionó tu ISP, puedes ir a páginas como https://whatismyipaddress.com/ y en donde dice «My IP Address is:» checa la dirección IPV4 que aparece.

Si usas alguna distribución GNU/Linux o algún BSD, en una terminal ejecuta el comando traceroute seguido de la dirección IP que te mostró el sitio de internet mencionado anteriormente y obtendrás algo parecido a la siguiente imagen (borré los sitios en donde aparece la IP intencionalmente)

Si el comando traceroute muestra que sólo hizo un salto (eso indica el número uno que encerré en un círculo rojo en la imagen) para que los paquetes llegaran a su destino, entonces tu router usa una IP pública, si muestra renglones adicionales (seguiría el número 2, etc.) significa que hizo más de un salto y por lo tanto tu router usa CG-NAT (una IP privada).

Si lamentablemente sigues usando Windows, el comando es tracert (en lugar de traceroute), pero el objetivo es el mismo.

Buscar palabras que cumplan ciertos patrones

marzo 15, 2022

Sólo por diversión puse en una base de datos hecha en SQLite las palabras en español según el diccionario de la real academia de la lengua española en su vigésimo tercera edición. La base de datos quedó con 105,278 registros.

Una vez teniendo la base de datos, hice un programa en Ruby que me mostrara en pantalla las palabras que cumplieran con ciertas características. El programa tiene tres opciones de búsqueda que se pueden combinar: 1. Palabras que empiecen con determinada letra o cadena, 2. Palabras que terminen con determinada letra o cadena y 3. Palabras que contengan ciertas letras (no importa si al principio, al final o en medio de la palabra).

Aquí dejo el código fuente por si a alguien le interesa.

#encoding: utf-8

#  Derechos reservados 2022 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 version 2 of the License.
#  
#  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.
#  
# El programa usa una base de datos llamada palabras.db creada en SQLite
# La base de datos contiene una tabla llamada words creada con las siguientes instrucciones
#   
#   CREATE TABLE words(
#       id INTEGER PRIMARY KEY NOT NULL,
#       palabra VARCHAR(35) NOT NULL
#   );
#   
#   CREATE INDEX indice ON words(palabra);
require 'sqlite3'

# Recibe como parámetros una cadena y un caracter
# Regresa verdadero si el caracter se encuentra en la cadena y falso en caso contrario
def incluyeCaracter(cadena, caracter)
    @encontrado = false
    @i=0
    @longitud = cadena.length
    # @aux será un duplicado de cadena, si no usamos .dup, al modificar @aux, se modificará también cadena
    @aux = cadena.dup    
    
    #Compara cada caracter de la cadena convertido a mayúscula con el caracter recibido también convertido a mayúscula
    while @i<@longitud and !@encontrado do   
          case @aux[@i]
               when 'Á','á'
                    @aux[@i] = 'a'
               when 'É','é'
                    @aux[@i] = 'e'
               when 'Í','í'
                    @aux[@i] = 'i'
               when 'Ó','ó'
                    @aux[@i] = 'o'
               when 'Ú','ú'
                    @aux[@i] = 'u'
          end
          if @aux[@i].upcase.strip == caracter.upcase.strip
             @encontrado = true
          else
              @i = @i+1
          end
    end
    
    return @encontrado
end

begin    
     contador = 0
     hay_parametro = false
     db = SQLite3::Database.open "palabras.db"
     db.results_as_hash = true
     
     puts "Buscar palabras que cumplan con las siguientes características:\n\n"
     print "Empieza con la letra o cadena: "
     empiezaCon = gets.chomp
     print "Termina con la letra o cadena: "
     terminaCon = gets.chomp
     print "Contiene las letras (no introducir signos de puntuación): "
     contieneLetras = gets.chomp
     
     # Sólo se hace la búsqueda si puso por lo menos alguna de las 3 condiciones
     if (!empiezaCon.strip.nil? and !empiezaCon.strip.empty?) or (!terminaCon.strip.nil? and !terminaCon.strip.empty?) or (!contieneLetras.strip.nil? and !contieneLetras.strip.empty?)
        puts "="*75
        puts "\nSe buscan palabras que:"
        if !empiezaCon.strip.nil? and !empiezaCon.strip.empty?
           puts "Empiecen con la letra #{empiezaCon.strip}"
        end
        if !terminaCon.strip.nil? and !terminaCon.strip.empty?
           puts "Terminen con la letra #{terminaCon.strip}"
        end
        if !contieneLetras.strip.nil? and !contieneLetras.strip.empty?
           puts "Contengan las letras: #{contieneLetras}\n\n"
        end
     
        if (!empiezaCon.strip.nil? and !empiezaCon.strip.empty?) and (terminaCon.strip.nil? or terminaCon.strip.empty?)
            parametro = "#{empiezaCon.strip}%"
            hay_parametro = true
        elsif (!empiezaCon.strip.nil? and !empiezaCon.strip.empty?) and (!terminaCon.strip.nil? and !terminaCon.strip.empty?)
               parametro = "#{empiezaCon.strip}%#{terminaCon.strip}"
               hay_parametro = true
        elsif (empiezaCon.strip.nil? or empiezaCon.strip.empty?) and (!terminaCon.strip.nil? and !terminaCon.strip.empty?)
               parametro = "%#{terminaCon.strip}"
               hay_parametro = true
        end
                
        if hay_parametro
            stm = db.prepare "SELECT id, palabra FROM words WHERE palabra like ?"        
            stm.bind_param 1, parametro
        else
            stm = db.prepare "SELECT id, palabra FROM words"
        end
        rs = stm.execute
        
        rs.each do |row|
           id = row['id']
           palabra = row['palabra']
           cumple_condiciones = true
           
           for x in 0..contieneLetras.length-1 do
               if !incluyeCaracter(palabra, contieneLetras[x])
                  cumple_condiciones = false
                  break
               end
           end
           if cumple_condiciones
              contador = contador+1
              print "Id: #{id} Palabra: #{palabra}\n"
           end
        end
        puts "\nNúmero de palabras encontradas que cumplen las condiciones especificadas: #{contador}\n"
     end
rescue SQLite3::Exception => e 
       puts "Exception occurred"
       puts e
ensure
      stm.close if stm
      db.close if db
end

Generar todas las permutaciones posibles de una cadena en C

noviembre 20, 2021
El algoritmo de Heap genera todas las permutaciones posibles de N objetos; BR Heap lo creó en 1963. El número de permutaciones en un conjunto de n elementos viene dado por n! (n factorial).

En este programa lo utilizo para hacer las permutaciones de la cadena "aeiou". La constante LONGITUD debe ser igual a la longitud de la cadena más uno ya que las cadenas en C deben terminar con el carecter nulo representado por \0.

Si queremos las permutaciones de una cadena diferente, sólo debemos modificar el valor de la constante LONGITUD para que tenga el valor correspondiente y asignar la cadena al arreglo texto. Por ejemplo, si queremos las permutaciones de la cadena "abc", el valor de la constante LONGITUD debe ser 4.
/* Genera todas las permutaciones posibles de una cadena utilizando el Algoritmo de Heap
 * 
 * 
 * Copyright 2021 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>
#define LONGITUD 6
 
void Permutaciones(char cadena[])
{
 int i=0, contador=1;
 int c[LONGITUD-1] = {0};
 char aux;

 printf("%d: %s\n", contador, cadena);
 while (i < LONGITUD-1)
       {
        if (c[i] < i)
           { 
            if (i%2 == 0)
               {
                aux = cadena[0];
                cadena[0] = cadena[i];
                cadena[i] = aux;
               }
            else
                {
                 aux = cadena[c[i]];
                 cadena[c[i]] = cadena[i];
                 cadena[i] = aux;
			    }
            
            ++contador;
            printf("%d: %s\n", contador, cadena);
            c[i] += 1;
            i = 0;
           }
        else
            {
             c[i] = 0;
             ++i;
            }
       }
}

int main()
{
 char texto[LONGITUD] = {"aeiou"};
 texto[LONGITUD-1]='\0';
 
 Permutaciones(texto);
 printf("\n");
  
 return 0;
}

Sumar elementos de una matriz en C

noviembre 8, 2021

En esta ocasión muestro un programa que tiene funciones que permiten sumar los elementos de una matriz, ya sea sólo los elementos de una fila o columna determinada, los elementos de la diagonal principal o la diagonal secundaria o todos los elementos.

En el programa utilizo como ejemplo una matriz de enteros de 3×3 pero desde luego el código se puede adaptar para las necesidades específicas de cada quien.

#include <stdio.h>
 
void ImprimeMatriz(int m[][3], int filas)
{
 int i=0,j=0;
 
 for (i=0; i<filas; ++i)
     {
      for (j=0; j<3; ++j)
          {
           printf("%d ",m[i][j]);
          }
      printf("\n");
     }
}

int SumaFila(int m[][3], int sumarFila)
{
 int columna=0, resultado=0;

 for (columna=0; columna<3; ++columna)
     {
      resultado += m[sumarFila][columna];
     } 
 return resultado;
}

int SumaColumna(int m[][3], int sumarColumna)
{
 int fila=0, resultado=0;

 for (fila=0; fila<3; ++fila)
     {
      resultado += m[fila][sumarColumna];
     }
 return resultado;
}

int SumaDiagonalPrincipal(int m[][3], int filas)
{
 int i=0, j=0, resultado=0;
 
 for (i=0; i<filas; ++i)
     {
      resultado += m[i][j];
      ++j;
     }
 return resultado;
}

int SumaDiagonalSecundaria(int m[][3], int filas)
{
 int i=0, j=2, resultado=0;
 
 for (i=0; i<filas; ++i)
     {
      resultado += m[i][j];
      --j;
     }
 return resultado;
}

int SumaElementos(int m[][3], int filas)
{
 int i=0, j=0, resultado=0;
 
 for (i=0; i<filas; ++i)
     {
      for (j=0; j<3; ++j)
          {
           resultado += m[i][j];
          }
     }
 return resultado;
}

int main()
{
 int x=0,y=0;
 int matriz[3][3] = {{0},{0},{0}};
 
 printf("Introduzca los valores para la matriz\n");
 for (x=0; x<3; ++x)
     {
      for (y=0; y<3; ++y)
          {
           printf("Valor para el elemento [%d][%d]: ", x, y);
           scanf("%d",&matriz[x][y]);
          }
      printf("\n");
     }
 
 printf("Matriz\n");
 ImprimeMatriz(matriz, 3);
 printf("\n");
 printf("Suma fila 0: %d\n",SumaFila(matriz, 0));
 printf("Suma fila 1: %d\n",SumaFila(matriz, 1));
 printf("Suma fila 2: %d\n",SumaFila(matriz, 2));
 printf("Suma columna 0: %d\n",SumaColumna(matriz, 0));
 printf("Suma columna 1: %d\n",SumaColumna(matriz, 1));
 printf("Suma columna 2: %d\n",SumaColumna(matriz, 2));
 printf("Suma diagonal principal: %d\n",SumaDiagonalPrincipal(matriz, 3));
 printf("Suma diagonal secundaria: %d\n",SumaDiagonalSecundaria(matriz, 3));
 printf("La suma de todos los elementos es: %d\n",SumaElementos(matriz, 3));
 
 return 0;
}

Creador de cuadrados mágicos impares, pares y de doble paridad hecho en Go

agosto 13, 2020

Un cuadrado mágico es un conjunto de números dispuestos en un cuadrado de forma tal que la suma de cada fila, columna y diagonal es el mismo número, también llamado constante mágica.

16 3 2 13
5 10 11 8
9 6 7 12
4 15 14 1

Un cuadrado mágico puede ser impar, par o de doble paridad. La diferencia entre un cuadrado mágico par y uno de doble paridad es que el cuadrado mágico par tiene un número de casillas por lado que es divisible por 2 pero no por 4 y el de doble paridad tiene un número de casillas por lado que es divisible por 4.

El programa que hice genera cuadrados mágicos impares usando el método de Loubere también conocido como método siamés. El método que utilicé para crear cuadrados mágicos pares y el que utilicé para crear cuadrados mágicos de doble paridad vienen descritos en el código fuente (ignoro como se llaman esos métodos).

Apenas estoy aprendiendo Go y este es mi segundo programa así que perdón si hago cosas que probablemente se pudieran hacer con menos código


/*     cuadrado_magico.go
 *       
 *     Copyright 2020 SALOMON RINCON TORRES 
 *      
 *     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.
 *
 * Un cuadrado mágico es un conjunto de números dispuestos en un cuadrado de forma tal
 * que la suma de cada fila, columna y diagonal es el mismo número, también llamado "constante mágica"
 * Este programa resuelve cualquier tipo de cuadrado mágico, para los impares utiliza el método Loubere
 */

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "strconv"
    "math"
)

func main() {
    var constanteMagica int = 0
    var numeroi int = 0
    var tipoCuadro string
    
    fmt.Println("CUADRADO MAGICO\n")
    fmt.Println("Un cuadrado mágico es un conjunto de números dispuestos en un cuadrado de forma tal")
    fmt.Println("que la suma de cada fila, columna y diagonal es el mismo número, también llamado constante mágica.\n")
    fmt.Println("Este programa resuelve cualquier tipo de cuadrado mágico, ya sea impar, par o de doble paridad")
    fmt.Println("Un cuadrado mágico par tiene un número de casillas por lado que es divisible por 2 pero no por 4")
    fmt.Println("El cuadrado mágico par más pequeño posible es 6 x 6, ya que los cuadrados mágicos de 2 x 2 no se pueden resolver")
    fmt.Println("Un cuadrado mágico de doble paridad tiene un número de casillas por lado que es divisible por 4")
    fmt.Println(strings.Repeat("*", 110))
    
    reader := bufio.NewReader(os.Stdin)
    for numeroi < 3 {
                     fmt.Print("\nCuadrado mágico de nxn, introduzca el valor de n (0 para terminar o mayor a 2 para continuar): ")
                     n, _ := reader.ReadString('\n')
    
                     // Elimina el caracter \n de la variable n
                     n = strings.TrimSuffix(n, "\n")
                     // Convierte n a tipo int64 y guarda el valor en la variable numero
                     numero, _ := strconv.ParseInt(n, 10, 64)
                     // Convierte numero de tipo int64 a numeroi de tipo int
                     numeroi = int(numero)
                     if (numeroi == 0) {
                         break;
                     }
    }
    
    if (numeroi > 0) {
        fmt.Printf("\nCuadrado mágico de %dx%d\n", numeroi, numeroi)    
        
        // Crea la matriz de nxn llamada cuadro
        cuadro := creaMatriz(numeroi)
        
        // Calcula el valor de la constante mágica
        constanteMagica = (numeroi * ((numeroi*numeroi) + 1)) / 2
	
        /* Determina si el cuadrado mágico es impar, par o de doble paridad */
        if (numeroi%2 != 0) {
            tipoCuadro = "IMPAR"
        } else if (numeroi%2 == 0 && numeroi%4 != 0) {		     
                   tipoCuadro = "PAR"
        } else if (numeroi%4 == 0) {
                   tipoCuadro = "DOBLE PARIDAD"
        }	
	
        fmt.Println("La constante mágica es",constanteMagica)
        fmt.Println("El cuadrado mágico es",tipoCuadro)
        fmt.Println(strings.Repeat("-", 50))
        
        // Genera la serie de números que se deben colocar en el cuadrado mágico (de 1 a nxn)
        serie := generaSerie(numeroi*numeroi)
	    
        switch tipoCuadro {
               case "IMPAR":
                             /* Llama a la función que resuelve cuadrados mágicos impares pasándole como parámetros
                                el cuadrado mágico vacío y la serie de números con que se debe llenar */
                             resuelveCuadradoImpar(cuadro, serie)
               case "PAR":   
                             /* Debemos dividir el cuadrado mágico en 4 secciones, cada sección será un cuadrado mágico impar
                                la esquina superior izquierda será la sección A, la esquina superior derecha será la sección C,
                                la esquina inferior izquierda será la sección D y la esquina inferior derecha serla la sección B.
                                
                                A C
                                D B
                                
                                Para saber de qué grado serán los cuadrados mágicos de cada sección, hacemos n/2, en donde n
                                es el grado de nuestro cuadrado mágico par. Si el cuadrado mágico es de grado 6, cada una de las
                                4 secciones será un cuadrado mágico de grado 3 
                             */
                             gradoSecciones := numeroi/2
                             cantNumerosxSeccion := len(serie)/4
                             
                             // Crea las cuatro secciones
                             seccionA := creaMatriz(gradoSecciones)
                             seccionB := creaMatriz(gradoSecciones)
                             seccionC := creaMatriz(gradoSecciones)
                             seccionD := creaMatriz(gradoSecciones)
		             
                             numerosSeccion := make([]int, cantNumerosxSeccion)
                             copy(numerosSeccion, serie[0:cantNumerosxSeccion])
                             /* Llama a la función que resuelve cuadrados mágicos impares pasándole como parámetros
                                el cuadrado mágico seccionA y la serie de números con que se debe llenar */
                             resuelveCuadradoImpar(seccionA, numerosSeccion)
	                     
                             copy(numerosSeccion, serie[cantNumerosxSeccion:(cantNumerosxSeccion*2)])
                             /* Llama a la función que resuelve cuadrados mágicos impares pasándole como parámetros
                                el cuadrado mágico seccionB y la serie de números con que se debe llenar */
                             resuelveCuadradoImpar(seccionB, numerosSeccion)
 	                     
                             copy(numerosSeccion, serie[cantNumerosxSeccion*2:(cantNumerosxSeccion*3)])
                             /* Llama a la función que resuelve cuadrados mágicos impares pasándole como parámetros
                                el cuadrado mágico seccionC y la serie de números con que se debe llenar */
                             resuelveCuadradoImpar(seccionC, numerosSeccion)
                             
                             copy(numerosSeccion, serie[cantNumerosxSeccion*3:(cantNumerosxSeccion*4)])
                             /* Llama a la función que resuelve cuadrados mágicos impares pasándole como parámetros
                                el cuadrado mágico seccionD y la serie de números con que se debe llenar */
                             resuelveCuadradoImpar(seccionD, numerosSeccion)
                             
                             /* Ya que se tienen resueltos los cuadrados mágicos correspondientes a cada sección,
                                determinadas casillas que caen en un patrón de la sección A se deben intercambiar con las
                                casillas que caen en ese mismo patrón en la sección D.
                                También determinadas casillas que caen en un patrón de la sección C se deben intercambiar 
                                con las casillas que caen en ese mismo patrón en la sección B.
		                
                                A continuación se muestran cuales serían los patrones en un cuadrado mágico de 10x10,
                                _________________________________________
                                |   |   |   |   |   |   |   |   |   |   |
                                | * | * | A | A | A | C | C | C | C | * |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | * | * | A | A | A | C | C | C | C | * |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | A | * | * | A | A | C | C | C | C | * |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | * | * | A | A | A | C | C | C | C | * |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | * | * | A | A | A | C | C | C | C | * |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | x | x | D | D | D | B | B | B | B | x |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | x | x | D | D | D | B | B | B | B | x |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | D | x | x | D | D | B | B | B | B | x |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | x | x | D | D | D | B | B | B | B | x |
                                |___|___|___|___|___|___|___|___|___|___|
                                |   |   |   |   |   |   |   |   |   |   |
                                | x | x | D | D | D | B | B | B | B | x |
                                |___|___|___|___|___|___|___|___|___|___|
	                
                                Los valores que se encuentran en las casillas marcadas con asterisco en la sección A se deben
                                intercambiar con los valores que se encuentran en las casillas marcadas con equis en la sección D
                                y los valores que se encuentran en las casillas marcadas con asterisco en la sección C se deben
                                intercambiar con los valores que se encuentran en las casillas marcadas con equis en la sección B.
                                
                                Para determinar cuantas columnas ocupará el patrón de la sección A utilizamos columnas = g/2, en donde
                                g es el grado correspondiente a los cuadrados mágicos que forman cada una de las secciones. En el
                                ejemplo del cuadrado mágico de orden 10, cada sección está formada por cuadrados mágicos de grado 5.
                                Por tal motivo, las columnas del patrón de la sección A en este caso son 5/2 = 2.5, Sólo se toma
                                en cuenta la parte entera (en este caso el 2), significa que tomamos 2 columnas y 2 filas, después de
                                eso, en la fila siguiente (la tercera fila) saltamos una casilla y tomamos las casillas de las 2 columnas
                                siguientes. Después se regresa a la primera columna y se toman como al inicio 2 columnas y filas.
                                
                                El patrón de la sección C está determinado por columnas = (g/2)-1. En el caso del cuadrado mágico
                                de 10x10 sería (5/2)-1 = 1.5. Igual sólo tomamos la parte entera, (es decir 1). Empezamos a contar
                                de derecha a izquierda el número de columnas que vamos a tomar, en este caso sería sólo la última columna.
                                
                                Para un cuadrado mágico de grado 6, el patrón quedaría de la siguiente forma
                                               
                                _________________________
                                |   |   |   |   |   |   |
                                | * | A | A | C | C | C |
                                |___|___|___|___|___|___|
                                |   |   |   |   |   |   |
                                | A | * | A | C | C | C |
                                |___|___|___|___|___|___|
                                |   |   |   |   |   |   |
                                | * | A | A | C | C | C |
                                |___|___|___|___|___|___|
                                |   |   |   |   |   |   |
                                | x | D | D | B | B | B |
                                |___|___|___|___|___|___|
                                |   |   |   |   |   |   |
                                | D | x | D | B | B | B |
                                |___|___|___|___|___|___|
                                |   |   |   |   |   |   |
                                | x | D | D | B | B | B |
                                |___|___|___|___|___|___|
               
                                Para la sección A columnas = 3/2 = 1.5. La parte entera es uno, así que tomamos una columna y una fila,
                                en la siguiente fila saltamos una casilla y tomamos la casilla de la columna siguiente. Regresamos a la
                                primera columna y tomamos una columna y una fila.
	                
                                Para la seccion C columnas = (3/2)-1 = 0.5. Sólo tomamos la parte entera, como es cero, significa que no
                                tomamos columna alguna de esta sección para intercambiar con la sección B.
                             */
                             
                             columnasPatron1 := gradoSecciones/2
                             columnasPatron2 := (gradoSecciones/2)-1
                             
                             /* Siguiendo el patrón descrito en los comentarios anteriores, intercambia las casillas de la sección A con
                                las casillas correspondientes de la sección D
                             */
                             temporal := 0
                             for i := 0; i < gradoSecciones; i++ {
                                 for j := 0; j < columnasPatron1; j++ {
                                     if (i == gradoSecciones/2) {
                                         temporal = seccionA[i][j+1]
                                         seccionA[i][j+1] = seccionD[i][j+1]
                                         seccionD[i][j+1] = temporal 
                                     } else {
                                             temporal = seccionA[i][j]
                                             seccionA[i][j] = seccionD[i][j]
                                             seccionD[i][j] = temporal 
                                     }
                                 }
                             }
    	                     
                             /* Siguiendo el patrón descrito en los comentarios anteriores, intercambia las casillas de la sección C con 
                                las casillas correspondientes de la sección B
                             */
                             if (columnasPatron2 > 0) {
                                 temporal = 0
                                 for i := 0; i < gradoSecciones; i++ { 
                                     for j := gradoSecciones-1; j >= gradoSecciones-columnasPatron2; j-- {
                                         temporal = seccionC[i][j]
                                         seccionC[i][j] = seccionB[i][j]
                                         seccionB[i][j] = temporal 
                                     }
                                 }
                             }
                             // Llena el cuadro mágico par copiando los valores de las 4 secciones
                             
                             // Copia los valores de la sección A
                             for i := 0; i < gradoSecciones; i++ { 
                                 for j := 0; j < gradoSecciones; j++ {
                                     cuadro[i][j] = seccionA[i][j] 
                                 }
                             }
                             
                             // Copia los valores de la sección B
                             for i := gradoSecciones; i < numeroi; i++ { 
                                 for j := gradoSecciones; j < numeroi; j++ {
                                     cuadro[i][j] = seccionB[i-gradoSecciones][j-gradoSecciones] 
                                 }
                             }
                          
                             // Copia los valores de la sección C
                             for i := 0; i < gradoSecciones; i++ { 
                                 for j := gradoSecciones; j < numeroi; j++ {
                                     cuadro[i][j] = seccionC[i][j-gradoSecciones] 
                                 }
                             }
                            
                             // Copia los valores de la sección D
                             for i := gradoSecciones; i < numeroi; i++ { 
                                 for j := 0; j < gradoSecciones; j++ {
                                     cuadro[i][j] = seccionD[i-gradoSecciones][j] 
                                 }
                             }
		case "DOBLE PARIDAD":
                        /* Se deben crear en la matriz 2 secciones de n/4 filas x n/2 columnas
                           en donde n es el grado del cuadrado mágico (si es de 4x4, el grado es 4, si es de 8x8 el grado es 8).
                           La primera sección abarca el centro de las filas superiores y la segunda sección
                           ocupa el centro de las filas inferiores.
                           Si tenemos un cuadrado mágico de 8x8 (filas de la 0 a la 7 y columnas de la 0 a la 7),
                           estas secciones serían de 2x4.
                           La sección de la parte superior ocupa las casillas 0,2 a la 0,5 y las casillas 1,2 a la 1,5.
                           La sección de la parte inferior ocupa las casillas 6,2 a la 6,5 y las casillas 7,2 a la 7,5.
                           
                           Debemos crear en la matriz otras 2 secciones ahora de n/2 filas x n/4 columnas
                           en donde n es el grado del cuadrado mágico (si es de 4x4, el grado es 4, si es de 8x8 el grado es 8).
                           La primera sección abarca el centro de las columnas más a la izauiqrda y la segunda sección
                           ocupa el centro de las columnas más a la derecha.
                           Si tenemos un cuadrado mágico de 8x8 (filas de la 0 a la 7 y columnas de la 0 a la 7),
                           estas secciones serían de 4x2.
                           La sección de la parte izquierda ocupa las casillas 2,0 a la 5,0 y las casillas 2,1 a la 5,1.
                           La sección de la parte derecha ocupa las casillas 2,6 a la 5,6 y las casillas 2,7 a la 5,7. 
	                   
                           Para identificar las casillas que pertenecen a cualquiera de las 4 secciones, las vamos a marcar
                           asignándoles el valor -1. 
                         */
                         filasH := numeroi/4
                         columnasH := numeroi/2
                         marcaSeccionesHorizontales(cuadro, filasH, columnasH, numeroi)
                         filasV := numeroi/2
                         columnasV := numeroi/4
                         marcaSeccionesVerticales(cuadro, filasV, columnasV, numeroi)
                         
                         /* Después, recorriendo la matriz de izquierda a derecha empezando en la
                            esquina superior izquierda simulamos ir llenando las casillas con la serie de números del 1 en
                            adelante pero sólo guardamos los números en aquellas casillas marcadas con -1. Siguiendo el
                            ejemplo del cuadro mágico de 8x8, el número uno iría en la casilla 0,0, pero como esa casilla no
                            contiene un -1 (no pertenece a ninguna de las 4 secciones mencionadas al principio), no guardamos
                            el uno, pasamos a la siguiente casilla que es la 0,1, ahí tendría que ir el número dos, pero como
                            tampoco contiene un -1, no lo guardamos; seguimos a la casilla 0,2 a donde corresponde poner el 
                            número tres. Esta casilla si tiene un -1 (si pertenece a una de las 4 secciones mencionadas al principio)
                            así que guardamos en esta casilla el número que corresponde (el 3). Y así continuamos sólo
                            guardando los números correspondientes en las casillas que contengan un -1.
                         */
                         elemento := 0
                         for i := 0; i < numeroi; i++ { 
                             for j := 0; j < numeroi; j++ {
                                 if (cuadro[i][j] == -1) {
                                     cuadro[i][j] = serie[elemento]
                                 }
                             elemento++
                             }
                         }
                         
                         /* Una vez que ya se recorrió la matriz completa y sólo se guardaron los números de la serie en las
                            casillas correspondientes a alguna de las 4 secciones, recorremos una vez más la matriz de izquierda a
                            derecha empezando en la esquina superior izquierda llenando las casillas que no se llenaron en el
                            recorrido anterior (tienen 0) con los números de la serie pero en orden descendente. En nuestro
                            ejemplo del cuadrado mágico de 8x8, en la casilla 0,0 pondráimos el número 64, en la casilla 0,1 el
                            número 63, como la casilla 0,2 no está vacía (ya está el número 3), no escribimos el número 62 que es
                            el número que sigue en la serie.
                            Y siguiendo el mismo procedimiento, la próxima casilla vacía sería la 0,6 en donde deberemos guardar el
                            número 58 y así sucesivamente.
                         */
                         elemento = (numeroi*numeroi)-1
                         for i := 0; i < numeroi; i++ { 
                             for j := 0; j < numeroi; j++ {
                                 if (cuadro[i][j] == 0) {
                                     cuadro[i][j] = serie[elemento]
                                 }
                             elemento--
	                     }
	                 }
        }
	    
        // Imprime el cuadrado mágico
        imprimeMatriz(cuadro)
    }
}

// Crea la matriz de nxn
func creaMatriz(n int) [][]int {
    matriz := make([][]int, n)
    filas := make([]int, n*n)
    for i := 0; i < n; i++ {
        matriz[i] = filas[i*n : (i+1)*n]
    }
    return matriz
}

// Regresa un arreglo de enteros de uno hasta elementos
func generaSerie(elementos int) []int {
    arreglo := make([]int, elementos)
    
    for i := range arreglo {
        arreglo[i] = i + 1
    }
    return arreglo
}

// Imprime la matriz que recibe como parámetro
func imprimeMatriz(matriz[][] int) {
    n := len(matriz[0])
    for i := 0; i < n; i++ { 
        for j := 0; j < n; j++ {
            fmt.Printf("%3d", matriz[i][j]) 
        }
    fmt.Println()
    }
    fmt.Println()
}

/* Resuelve un cuadrado mágico impar
   Recibe como parámetro el cuadrado mágico y la serie de números que debe utilizar
*/
func resuelveCuadradoImpar(matriz[][] int, numeros[] int) {
    n := len(numeros)
    grado := int(math.Sqrt(float64(n)))
    // Pone el primer número de la seire en la casilla central de la fila superior
    centro := grado/2
    matriz[0][centro] = numeros[0]
		             
    /* A partir del segundo número de la serie, los números se van colocando
       moviendonos una casilla hacia arriba y luego una casilla a la derecha
       siguiendo las siguientes reglas:
          1. Si un movimiento lleva a una casilla por encima de la fila superior del cuadrado mágico,
             permanece en esa columna, pero se ubica en la fila inferior de dicha columna.
          2. Si el movimiento lleva a una casilla fuera del límite derecho del cuadrado mágico,
             permanece en la fila de dicha casilla, pero se ubica en la columna más a la izquierda de esa fila.
          3. Si el movimiento lleva a una casilla que ya está ocupada, regresa a la última casilla que
             se llenó y se ubica debajo.
    */
    filaActual := 0
    columnaActual := centro
    
    for i := 1; i < len(numeros); i++ {
        arriba := filaActual-1
        if (arriba < 0) {
            arriba = grado-1
        }
                          
        derecha := columnaActual+1
        if (derecha > grado-1) {
            derecha = 0
        }
                          
        // Checa si la casilla propuesta para colocar el número en turno no está ocupada
        if (matriz[arriba][derecha] == 0) {
            filaActual = arriba
            columnaActual = derecha
        } else if (filaActual < grado-1) {
                   filaActual++
        } else {
                filaActual = 0
        }
        matriz[filaActual][columnaActual] = numeros[i]
    }
}

func marcaSeccionesHorizontales(matriz[][] int, numFilas int, numColumnas int, gradoCuadrado int) {  
    columnaInicial := (gradoCuadrado-numColumnas)/2
    columnaFinal := (numColumnas+columnaInicial)-1
    
    // Pone -1 en todas las casillas que corresponderán a la sección superior
    for i := 0; i < numFilas; i++ { 
        for j := columnaInicial; j <= columnaFinal; j++ {
            matriz[i][j] = -1
        }
    }
    // Pone -1 en todas las casillas que corresponderán a la sección inferior
    for i := gradoCuadrado-numFilas; i < gradoCuadrado; i++ { 
        for j := columnaInicial; j <= columnaFinal; j++ {
            matriz[i][j] = -1
        }
    }
}  

func marcaSeccionesVerticales(matriz[][] int, numFilas int, numColumnas int, gradoCuadrado int) {  
    filaInicial := (gradoCuadrado-numFilas)/2
    filaFinal := (numFilas+filaInicial)-1
    
    // Pone -1 en todas las casillas que corresponderán a la sección izquierda
    for i := filaInicial; i <= filaFinal; i++ { 
        for j := 0; j < numColumnas; j++ {
            matriz[i][j] = -1
        }
    }
    // Pone -1 en todas las casillas que corresponderán a la sección derecha
    for i := filaInicial; i <= filaFinal; i++ { 
        for j := gradoCuadrado-numColumnas; j < gradoCuadrado; j++ {
            matriz[i][j] = -1
        }
    }
}

Aquí una captura de pantalla de una ejecución del programa

Arreglos de estructuras en C

May 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 estructura 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

May 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