Archive for the ‘Programación’ Category

Ordenar un arreglo de objetos con base en un campo específico en Ruby

julio 3, 2018

Decidí hacer este post a raíz de algo que tuve que hacer cuando desarrollé el sistema Clepsidra. Lo que aquí muestro no es exactamente lo mismo, aquí está el concepto básico esperando que pueda servir para solucionar casos similares que se le puedan presentar a alguien más.

Supongamos que tenemos la siguiente información en formato json:

'[
  {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P210","nombre_profesor":"Dennis Ritchie"},
  {"clave_asignatura":"U-101","nombre_asignatura":"Unix avanzado","clave_profesor":"P210","nombre_profesor":"Dennis Ritchie"},
  {"clave_asignatura":"DB-101","nombre_asignatura":"Introducción a las bases de datos relacionales","clave_profesor":"P205","nombre_profesor":"Edgar Frank Codd"},
  {"clave_asignatura":"SO-101","nombre_asignatura":"Sistemas operativos","clave_profesor":"P225","nombre_profesor":"Linus Torvalds"},
  {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P235","nombre_profesor":"Brian Kernighan"},
  {"clave_asignatura":"SL-101","nombre_asignatura":"Introducción al software libre","clave_profesor":"P215","nombre_profesor":"Richard M. Stallman"},
  {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P230","nombre_profesor":"Ken Thompson"},
  {"clave_asignatura":"SO-101","nombre_asignatura":"Sistemas operativos","clave_profesor":"P220","nombre_profesor":"Andrew S. Tanenbaum"},
  {"clave_asignatura":"L-101","nombre_asignatura":"Linux básico","clave_profesor":"P225","nombre_profesor":"Linus Torvalds"},
  {"clave_asignatura":"U-101","nombre_asignatura":"Unix avanzado","clave_profesor":"P230","nombre_profesor":"Ken Thompson"}
 ]'

Son 10 registros, cada uno con 4 campos: clave_asignatura, nombre_asignatura, clave_profesor y nombre_profesor.

Esta información en formato json la pudimos haber obtenido desde un archivo o desde una base de datos, etc. eso es irrelevante. Como se puede observar, hay asignaturas que aparecen más de una vez, ya que una misma asignatura puede ser impartida por diferentes profesores, y debido a que un profesor puede impartir diferentes asignaturas, también hay profesores que aparecen más de una vez.

Supongamos que a partir de esta información tenemos que obtener una lista de las diferentes asignaturas ordenada por clave de asignatura. Es decir, en la lista ordenada, cada asignatura debe aparecer sólo una vez.

Lo primero que tenemos que hacer es convertir la información a un arreglo de hashes.

@json_data = '[
               {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P210","nombre_profesor":"Dennis Ritchie"},
               {"clave_asignatura":"U-101","nombre_asignatura":"Unix avanzado","clave_profesor":"P210","nombre_profesor":"Dennis Ritchie"},
               {"clave_asignatura":"DB-101","nombre_asignatura":"Introducción a las bases de datos relacionales","clave_profesor":"P205","nombre_profesor":"Edgar Frank Codd"},
               {"clave_asignatura":"SO-101","nombre_asignatura":"Sistemas operativos","clave_profesor":"P225","nombre_profesor":"Linus Torvalds"},
               {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P235","nombre_profesor":"Brian Kernighan"},
               {"clave_asignatura":"SL-101","nombre_asignatura":"Introducción al software libre","clave_profesor":"P215","nombre_profesor":"Richard M. Stallman"},
               {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P230","nombre_profesor":"Ken Thompson"},
               {"clave_asignatura":"SO-101","nombre_asignatura":"Sistemas operativos","clave_profesor":"P220","nombre_profesor":"Andrew S. Tanenbaum"},
               {"clave_asignatura":"L-101","nombre_asignatura":"Linux básico","clave_profesor":"P225","nombre_profesor":"Linus Torvalds"},
               {"clave_asignatura":"U-101","nombre_asignatura":"Unix avanzado","clave_profesor":"P230","nombre_profesor":"Ken Thompson"}
              ]'

@materias_profesores = JSON.parse(@json_data)

En el código anterior, la información en formato json la tenemos en la variable @json_data y ya como un arreglo de hashes la tenemos en la variable @materias_profesores

Si lo deseamos, podemos recorrer nuestro arreglo de hashes con el siguiente código para mostrar la información

@materias_profesores.each do |registro|
   puts registro["clave_asignatura"]+": "+registro["nombre_asignatura"]
   puts registro["clave_profesor"]+": "+registro["nombre_profesor"]
end

Para obtener la lista de asignaturas sin repetir lo que vamos a hacer es recorrer nuestro arreglo de hashes e ir agragando la clave de asignatura y su nombre a otro arreglo. Este nuevo arreglo será un arreglo de objetos en donde cada objeto sólo tendra dos campos, la clave de asignatura y el nombre de asignatura. Así que creamos la siguiente clase

class Materia
   attr_accessor :clave_asignatura, :nombre_asignatura
   
   def initialize(clave, nombre)
       @clave_asignatura = clave
       @nombre_asignatura = nombre
   end
end

Crearemos un arreglo llamado @asignaturas, en éste arreglo iremos agregando objetos de la clase Materia.

Como ya comenté antes, recorreremos nuestro arreglo @materias_profesores y antes de crear e insertar un objeto de la clase Materia a nuestro arreglo llamado @asignaturas, verificamos que no exista ya un objeto con esa información, así garantizamos que cada asignatura sólo aparezca una vez.

@asignaturas = []
@materias_profesores.each do |registro|
    # Sólo agrega la asignatura si no se encuentra ya en el arreglo @asignaturas
    if @asignaturas.all? {|asignatura| asignatura.clave_asignatura != registro["clave_asignatura"]}
       @asignaturas.push(Materia.new(registro["clave_asignatura"],registro["nombre_asignatura"]))
    end
end

Hasta ahora ya tenemos un arreglo de objetos de la clase Materia (nuestro arreglo llamado @asignaturas) en donde existe un objeto por cada una de las diferentes asignaturas.

Podemos usar el siguiente código si queremos mostrar el contenido de nuestro arreglo @asignaturas

puts "\n"
puts "="*20
puts "ASIGNATURAS"
puts "="*20
@asignaturas.each do |asignatura|
   puts asignatura.clave_asignatura+": "+asignatura.nombre_asignatura
end

Ahora sólo falta ordenar nuestro arreglo de objetos por el campo clave_asignatura

@asignaturas.sort! {|a, b| a.clave_asignatura <=> b.clave_asignatura}

Programa completo

#encoding: utf-8
require 'json'

class Materia
   attr_accessor :clave_asignatura, :nombre_asignatura
   
   def initialize(clave, nombre)
       @clave_asignatura = clave
       @nombre_asignatura = nombre
   end
end

@json_data = '[
               {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P210","nombre_profesor":"Dennis Ritchie"},
               {"clave_asignatura":"U-101","nombre_asignatura":"Unix avanzado","clave_profesor":"P210","nombre_profesor":"Dennis Ritchie"},
               {"clave_asignatura":"DB-101","nombre_asignatura":"Introducción a las bases de datos relacionales","clave_profesor":"P205","nombre_profesor":"Edgar Frank Codd"},
               {"clave_asignatura":"SO-101","nombre_asignatura":"Sistemas operativos","clave_profesor":"P225","nombre_profesor":"Linus Torvalds"},
               {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P235","nombre_profesor":"Brian Kernighan"},
               {"clave_asignatura":"SL-101","nombre_asignatura":"Introducción al software libre","clave_profesor":"P215","nombre_profesor":"Richard M. Stallman"},
               {"clave_asignatura":"C-101","nombre_asignatura":"Introducción al lenguaje de programción C","clave_profesor":"P230","nombre_profesor":"Ken Thompson"},
               {"clave_asignatura":"SO-101","nombre_asignatura":"Sistemas operativos","clave_profesor":"P220","nombre_profesor":"Andrew S. Tanenbaum"},
               {"clave_asignatura":"L-101","nombre_asignatura":"Linux básico","clave_profesor":"P225","nombre_profesor":"Linus Torvalds"},
               {"clave_asignatura":"U-101","nombre_asignatura":"Unix avanzado","clave_profesor":"P230","nombre_profesor":"Ken Thompson"}
              ]'

@materias_profesores = JSON.parse(@json_data)

@materias_profesores.each do |registro|
   puts registro["clave_asignatura"]+": "+registro["nombre_asignatura"]
   puts registro["clave_profesor"]+": "+registro["nombre_profesor"]
end

@asignaturas = []
@materias_profesores.each do |registro|
   # Sólo agrega la asignatura si no se encuentra ya en el arreglo @asignaturas
   if @asignaturas.all? {|asignatura| asignatura.clave_asignatura != registro["clave_asignatura"]}
      @asignaturas.push(Materia.new(registro["clave_asignatura"],registro["nombre_asignatura"]))
   end
end

puts "\n"
puts "="*20
puts "ASIGNATURAS"
puts "="*20
@asignaturas.each do |asignatura|
   puts asignatura.clave_asignatura+": "+asignatura.nombre_asignatura
end

# Ordena el arreglo @asignaturas por el campo clave_asignatura
@asignaturas.sort! {|a, b| a.clave_asignatura <=> b.clave_asignatura}

puts "\n"
puts "="*20
puts "ASIGNATURAS ORDENADAS POR CLAVE"
puts "="*20
@asignaturas.each do |asignatura|
   puts asignatura.clave_asignatura+": "+asignatura.nombre_asignatura
end

Quien quiera practicar, puede hacer una versión de este programa en donde utilizando la misma información en formato json, obtenga una lista de los diferentes profesores ordenada por el campo nombre_profesor

 

Anuncios

Creación de un calendario en HTML usando Ruby

junio 22, 2018

En este post voy a mostrar un programa que desarrollé en Ruby que recibe por teclado un año y genera un archivo html que contiene el calendario correspondiente a dicho año.

Saber si un año es bisiesto

Para generar el calendario necesitamos saber si el año en cuestión es bisiesto o no para tomar en cuenta 28 o 29 días en el mes de Febrero.

Hay dos casos en los que un año es bisiesto:

  1. Si es divisible entre 4 pero no entre 100 (es decir, si dividimos el año entre 4 y el residuo es cero, pero si lo dividimos entre 100 y el residuo no es cero)
  2. Si es divisible entre 400

La siguiente función recibe como parámetro un año; regresa verdadero si dicho año es bisiesto y regresa falso en caso contrario.

def Bisiesto(year)
    es_bisiesto = nil
    
    if (year%4 == 0 and year%100 !=0) or year%400 == 0
        es_bisiesto = true
    else
        es_bisiesto = false
    end
    
    return es_bisiesto
end

A continuación muestro una función que recibe como parámetro un año y un número de mes (considerando Enero = 1, Febrero = 2, etc.) y regresa el número de días que contiene ese mes en ese año. Cuando el mes es Febrero, utiliza la función Bisiesto descrita anteriormente para saber si regresa 28 o 29.

def DiasMes(mes, year)
    dias = 30
    
    case mes
         when 1,3,5,7,8,10,12 then dias = 31
         when 2 then
                if Bisiesto(year)
                   dias = 29
                else
                    dias = 28
                end
    end

    return dias
end

Saber que día de la semana es una fecha específica

Con las dos funciones descritas hasta ahora, ya podemos crear un programa que solicite al usuario que teclee un año y mediante un ciclo del 1 al 12 (los meses del año) obtener el número de días que contiene cada mes en ese año (realmente para cualquier año el único mes que puede tener diferentes días es Febrero dependiendo de si es bisiesto o no). Pero necesitamos saber que día de la semana es el primer día de cada mes para ese año; para eso haremos un función en donde implementaremos la regla de Zeller.

Para saber más sobre los casos en que se da un año bisiesto y sobre la regla de Zeller les recomiendo consultar este enlace The Calendar and the Days of the Week

La función recibe como parámetros un número de día, número de mes y un año. Utilizando la regla de Zeller regresa el número del día de la semana empezando con Domingo = 0

def DiaSemana(dia, mes, year)   
    ajuste = (14-mes)/12
    mm = mes+(12*ajuste)-2
    yy = year-ajuste
    
    return (dia+(((13*mm)-1)/5)+yy+(yy/4)-(yy/100)+(yy/400))%7
end

Si a la función DiaSemana le paso como parámetros la fecha de hoy (22, 6, 2018) me regresa el número 5, lo que significa que el 22 de Junio del año 2018 es Viernes.

El archivo html que generará nuestro programa contendrá una tabla para cada uno de los meses; para tener como encabezado de cada tabla el nombre del mes, haremos una función más que reciba como parámetro el número del mes y regrese el nombre considerando 1 = Enero, 2 = Febrero, etc.

Convertir un número de mes a su nombre correspondiente

def MesNumero(numero)
    mes = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"]
    
    if (1..12).include?(numero)
        return mes[numero-1]
    else
        return "Número inválido"
    end
end

Programa completo

Con eso tenemos todas las funciones que necesitamos. A continuación muestro el programa completo. Después de las funciones está el código en donde se pide al usuario que teclee un año y después genera el archivo html que contendrá el calendario correspondiente a ese año.

Programa calendar.rb

#encoding: utf-8

# Recibe como parámetro un año
# Regresa verdadero si dicho año es bisiesto y regresa falso en caso contrario
# Hay dos casos en que un año es bisiesto:
# caso 1: Si es divisible entre 4 pero no entre 100
# caso 2: Si es divisible entre 400
def Bisiesto(year)
    es_bisiesto = nil

    if (year%4 == 0 and year%100 !=0) or year%400 == 0
        es_bisiesto = true
    else
        es_bisiesto = false
    end
    
    return es_bisiesto
end

# Recibe como parámetros un mes y año
# Regresa el número de días que contiene ese mes en ese año tomando en cuenta si es año bisiesto o no
def DiasMes(mes, year)
    dias = 30
    
    case mes
         when 1,3,5,7,8,10,12 then dias = 31
         when 2 then
              if Bisiesto(year)
                 dias = 29
              else
                  dias = 28
              end
         end
    
    return dias
end

# Recibe como parámetros un número
# Regresa el nombre del mes correspondiente al número que recibe (1 = Enero, 2 = Febrero, etc.)
def MesNumero(numero)
    mes = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"]
    
    if (1..12).include?(numero)
        return mes[numero-1]
    else
        return "Número inválido"
    end
end

# Recibe como parámetros un día, mes y año
# Utiliza la regla de Zeller para regresar el número del día de la semana empezando con Domingo = 0
def DiaSemana(dia, mes, year)
    ajuste = (14-mes)/12
    mm = mes+(12*ajuste)-2
    yy = year-ajuste
    
    return (dia+(((13*mm)-1)/5)+yy+(yy/4)-(yy/100)+(yy/400))%7
end

puts "Año:"
anio = gets.chomp.to_i

File.open('calendario'+anio.to_s+'.html','w') do |linea|
     dia_semana = nil
     dias_mes = nil
     
     linea.puts '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
     linea.puts '<html xmlns="http://www.w3.org/1999/xhtml">'
     linea.puts '<head>'
     linea.puts '<title>Calendario</title>'
     linea.puts '</head>'
     linea.puts '<body>'
     linea.puts '<h2>Calendario '+anio.to_s+'</h2>'
     (1..12).each do |mes|
             dia_semana = DiaSemana(1,mes,anio)
             dias_mes = DiasMes(mes, anio)
             
             linea.puts ' <table>'
             linea.puts ' <thead>'
             linea.puts ' <tr>'
             linea.puts ' <th colspan="7">'+MesNumero(mes)+'</th>'
             linea.puts ' </tr>'
             linea.puts ' <tr>'
             linea.puts ' <th>D</th>'
             linea.puts ' <th>L</th>'
             linea.puts ' <th>M</th>'
             linea.puts ' <th>M</th>'
             linea.puts ' <th>J</th>'
             linea.puts ' <th>V</th>'
             linea.puts ' <th>S</th>'
             linea.puts ' </tr>'
             linea.puts ' </thead>'
             linea.puts ' <tbody>'
             linea.puts ' <tr>'
             if dia_semana > 0
                for i in 1..dia_semana
                    linea.puts ' <td></td>'
                end
             end
             j = dia_semana
             for dia in 1..dias_mes
                 linea.puts ' <td style="text-align: right;">'+dia.to_s+'</td>'
                 j += 1
                 if j == 7
                    linea.puts ' </tr>'
                    j = 0
                    if dia < dias_mes
                       linea.puts ' <tr>'
                    end
                 end
             end
             if j != 0
                linea.puts ' </tr>'
             end
             linea.puts ' </tbody>'
             linea.puts ' </table>'
             linea.puts ' <br>'
     end
     linea.puts '</body>'
     linea.puts '</html>'
end

puts "\nSe generó el archivo calendario#{anio}.html"

 

El operador condicional en C

junio 20, 2018

El operador condicional ?: tiene tres operandos, lo que significa que es un operador ternario.

La forma general de éste operador es:

expresion1 ? expresion2 : expresion3

Funciona de la siguiente forma:

Se evalúa expresion1, si el resultado de la evaluación es verdadero (diferente de cero), se ejecuta expresion2; si el resultado de la evaluación de expresion1 es falso (igual a cero), se ejecuta expresion3.

Ejemplo:

printf("%s\n", calificacion >=70 ? "Aprobado" : "Reprobado");

En la instrucción anterior, si el valor de la variable calificacion es mayor o igual a 70, se imprime la cadena “Aprobado”, en caso contrario se imprime la cadena “Reprobado”.

El siguiente programa muestra otro ejemplo del uso del operador condicional

#include <stdio.h>
int main()
{
 int num1=0, num2=0, mayor=0;
 
 printf("Introduzca dos números enteros\n");
 scanf("%d%d",&num1,&num2);
 mayor = (num1 >= num2 ? num1 : num2);
 printf("El número mayor es %d\n",mayor);
 
 return 0;
}

 

Manejo de verdadero y falso en C

enero 24, 2018

En ANSI C e incluso en ISO C90, no existe el tipo bool (en ISO C99 y posteriores ya está definido y para utilizarlo hay que incluir la biblioteca stdbool.h); así que el compilador maneja verdadero y falso de la siguiente forma:

Cero es falso y cualquier valor diferente de cero es verdadero.

Tomaré el siguiente programa como base para que quede claro

#include <stdio.h>

int main()
{
 int x=0;
 
 if (x)
     printf("La expresión se evaluó como verdadera\n");
 else
     printf("La expresión se evaluó como falsa\n");
 
 return 0;
}

Al ejecutar este programa, se mostrará la leyenda “La expresión se evaluó como falsa”, ya que se está asignando a x el valor cero; si en lugar de int x=0 ponemos int x=1, entonces al ejecutar el programa se mostrará la leyenda “La expresión se evaluó como verdadera”, y lo mismo sucederá si ponemos int x=-4 ó int x=200, ya que 1, -4 y 200 son diferentes de cero (y anteriormente expliqué que en C, cero es falso y cualquier valor diferente de cero es verdadero).

Si queremos manejar en nuestros programas cero como falso y uno como verdadero (ya no culaquier valor diferente de cero, sino específicamente uno) podemos definir unas constantes llamadas FALSE y TRUE por ejemplo y nuestro programa podría quedar de la siguiente forma:

#include <stdio.h>
#define FALSE 0
#define TRUE 1

int main()
{
 int x=TRUE;
 
 if (x==TRUE)
     printf("x es verdadera\n");
 else
     if (x==FALSE)
         printf("x es falsa\n");
     else
         printf("x tiene un valor diferente a TRUE y a FALSE\n");
 
 return 0;
}

En el programa estamos verificando explícitamente si x tiene asignado el valor FALSE, con lo que nos aseguramos que no asuma que x es falsa si no tiene asignado el valor TRUE ya que podemos tener int x=-2 o int x=400 por ejemplo; casos en donde no queremos que se imprima la leyenda “x es falsa”.

Otra opción es que creemos el tipo boolean con un enum como se muestra en el siguiente programa:

#include <stdio.h>

typedef enum {FALSE, TRUE} boolean;
int main()
{
 boolean x=FALSE;
 
 if (x==TRUE)
     printf("x es verdadera\n");
 else
     if (x==FALSE)
         printf("x es falsa\n");
     else
         printf("x tiene un valor diferente a TRUE y a FALSE\n");
 
 return 0;
}

Ya definido el tipo boolean de la forma en que lo hicimos en este último programa, x=FALSE es equivalente a poner x=0, y x=TRUE es equivalente a poner x=1.

El operador % en C

noviembre 29, 2017

A los que están iniciando con la programación en C, les dejo un video sobre el uso del operador mod (módulo) en C.

Curso de arreglos en C usando Linux

noviembre 25, 2017

Un tema que muchas veces es complicado para quienes están aprendiendo a programar en C es el manejo de arreglos. Aquí les dejo un enlace a un curso que tiene un precio en pesos mexicanos bastante accesible (menos de 10 dólares americanos) y ofrece acceso a los primeros dos capítulos de forma gratuita, chequenlo

Curso de arreglos en C

Este es uno de los videos del curso

Capturar texto en mayúsculas en un objeto Memo en C++ Builder

septiembre 30, 2017

En C++ Builder, los objetos “Edit” tienen una propiedad llamada CharCase, esta propiedad tiene por defualt el valor ecNormal. Si queremos que todo lo que se capture en ese Edit esté en mayúsculas, basta conque cambies el valor de su propiedad CharCase de ecNormal a ecUpperCase.

Los objetos “Memo” no tienen la propiedad CharCase (no entiendo porque); así que si deseamos que todo lo que se capture en ese Memo esté en mayúsculas, debemos poner el siguiente código en el evento OnKeyPress:

if (Key >= 97 && Key <= 122)
    Key = Key - 32;

Sabemos que el código ASCII de la letra “a” minúscula es 97, el de la “b” es 98 y así sucesivamente hasta llegar a la letra “z” minúscula que es 122. El código ASCII de la letra “A” mayúscula es 65, el de la “B” es 66 y así sucesivamente hasta llegar a la letra “Z” mayúscula que es 90.

Esto significa que si restamos al código ASCII de cualquier letra minúscula el número 32, obtenemos la misma letra pero en mayúsculas. Por eso si la tecla pulsada está entre 97 y 122, la convertimos al código ASCII resultado de restarle 32. Esto no funciona para las vocales con acento, pero la solución es agregar un switch para cada uno de esos casos. La función completa del evento OnKeyPress del objeto Memo en el cual queremos que automáticamente se capture texto en mayúsculas incluidas las vocales con acento queda de la siguiente forma (asumimos que el objeto se llama Memo1)

void __fastcall TForm1::Memo1KeyPress(TObject *Sender, char &Key)
{
 if (Key >= 97 && Key <= 122)
     Key = Key - 32;
 else
     switch(Key)
           {
            case 'á': Key = 'Á'; break;
            case 'é': Key = 'É'; break;
            case 'í': Key = 'Í'; break;
            case 'ó': Key = 'Ó'; break;
            case 'ú': Key = 'Ú';
           }
}

 

Diferencia entre un contador y un acumulador

agosto 19, 2017

En programación es común el uso de contadores y acumuladores, en este post explico la diferencia porque frecuentemente quienes están aprendiendo a programar confunden unos con otros.

Un contador es una variable que se utiliza para contar algo. Normalmente usamos un contador dentro de un ciclo y cambiamos su valor sumándole o restándole una constante, es decir, siempre se le suma o resta la misma cantidad. El caso más utilizado es incrementar la variable en uno.

En el siguiente programa en C se tiene un arreglo de 10 números enteros y se utiliza un ciclo con un contador para ver cuántas veces aparece el número 3.

Programa contador.c
#include <stdio.h>

int main()
{
 int numero = 3;
 int x=0, contador=0;
 int arreglo[10]={3,7,1,2,7,3,5,6,-2,7};
 
 // Recorre el arreglo y cuenta cuántas veces aparece el valor que contiene la variable numero 
 for (x=0; x<10; ++x)
     {
      if (arreglo[x] == numero)
          /* El nuevo valor de la variable contador va a ser igual a su valor actual más uno
             Se puede escribir como preincremento ( ++contador ) o como postincremento ( contador++ )
             pero para que sea bastante evidente, lo escribí de forma explícita ( contador = contador+1 )
          */
          contador = contador+1; 
     }
 
 printf("\n");
 printf("El número %d aparece %d veces en el arreglo\n", numero, contador);
 
 return 0;
}

El siguiente programa en C es un ejemplo de un contador que se va decrementando en uno; simula una cuenta regresiva.

Programa cuenta_regresiva.c
#include <stdio.h>

int main()
{
 int contador=10;
 
 // Imprime los números del 10 al cero
 while (contador >= 0)
       {
        printf("Despegue en %d\n", contador);
        contador = contador-1;
       }
 
 return 0;
}

Un acumulador es una variable que se utiliza para sumar valores. Al igual que el contador, se utiliza normalmente dentro de un ciclo pero cambiamos su valor sumándole una variable, es decir, no siempre se le suma la misma cantidad.

En el siguiente programa, utilizamos el mismo arreglo del programa contador.c, pero ahora no vamos a contar cuántas veces aparece un número x, sino que vamos a sumar todos los valores que aparezcan en el arreglo y que sean mayores a un número determinado.

Programa acumulador.c
#include <stdio.h>

int main()
{
 int mayores_que = 3;
 int x=0, suma=0;
 int arreglo[10]={3,7,1,2,7,3,5,6,-2,7};
 
 // Recorre el arreglo y suma todos los números mayores al valor que contiene la variable mayores_que 
 for (x=0; x<10; ++x)
     {
      if (arreglo[x] > mayores_que)
          /* El nuevo valor de la variable suma va a ser igual a su valor actual más el número 
             que se encuentra en la posición actual del arreglo
          */
          suma = suma+arreglo[x]; 
     }
 
 printf("Todos los números mayores a %d en el arreglo suman %d\n", mayores_que, suma);
 
 return 0;
}

¿Cómo diferenciar un buen desarrollador de software en Ruby del resto?

abril 11, 2017

Rails != Ruby

La primer diferencia entre un buen desarrollador de software en Ruby de uno malo no tan bueno es que el primero sabe que una cosa es Ruby y otra cosa es Rails. Ruby es un lenguaje de programación y Rails es sólo un framework para desarrollo web con Ruby.

Si alguien usa indistintamente (como si fueran sinónimos) Ruby y Rails (o RoR) ya sabemos que no es un buen desarrollador de software en Ruby y desafortunadamente hay muchos. Si tú quieres ser un buen desarrollador de software en Ruby lo primero que debes tener claro es que Ruby existe sin Rails pero Rails no existe sin Ruby, es decir, puedes desarrollar software usando Ruby sin usar Rails (incluso puedes desarrollar una aplicación web usando Ruby sin usar Rails) pero no puedes usar Rails con un lenguaje de programación que no sea Ruby.

Sólo usa Rails para desarrollo de aplicaciones web si te ves obligado

Rails es sin duda el framework más famoso para desarrollo de aplicaciones web con Ruby pero eso no significa que sea el mejor, de hecho es un framework que definitivamente yo no recomiendo si se trata de hacer una aplicación web profesional y robusta

Si alguien quiere aprender Ruby, lo peor que puede hacer es empezar con Rails. ¿Por qué opino que Rails es tan malo?; voy a exponer sólo algunos puntos y se que a quienes son fans de Rails estos puntos (ni cualquier otro argumento) los va a convencer, pero este post no está dirigido a ellos, como dice el dicho “No hay peor ciego que el que no quiere ver”.

Muchos de los que usan Rails creen saber Ruby y en la mayoría de los casos no es así, ejemplo: en muchas aplicaciones desarrolladas con Rails encontrarán en el código el uso de blank?. Abran una sesión de Ruby y tecleen algo como “prueba”.blank? y verán algo como NoMethodError: undefined method `blank’ for “prueba”:String. Eso es porque blank? no existe en Ruby, es una extensión de Rails. Pero muchos de los que usan Rails no saben eso, porque creen que blank? es una instrucción válida en Ruby.

Otra de las cosas por las que Rails no es un framework recomendable es porque está muy ligado a un ORM llamado Active Record y es uno de los peores ORM que existen para Ruby, el mejor ORM para Ruby que yo he usado es Sequel, seguido de DataMapper.

Active Record no soporta llaves primarias compuestas, el default es que la llave primaria de las tablas en la base de datos este formada por un solo campo llamado id, de tipo entero y además autoincrementable. Si en la Universidad tuviste un profesor(a) de Bases de Datos que más o menos supiera de lo que estaba hablando sabrás que una base de datos en la cual la llave primaria de todas sus tablas esté formada por un solo campo que además se llame igual en todas y tenga que ser de tipo entero es una base de datos que no te encontrarás en producción en el mundo real; a menos que esa base de datos la haya creado alguien que no tiene idea de lo que es el diseño de base de datos, o que sea una base de datos que usa una aplicación hecha en Rails.

Supongamos que tienes que desarrollar una aplicación web para el área de control escolar de una institución educativa. Tienes una tabla de asignaturas en donde cada asignatura tiene una clave que es un campo alfanumérico, tienes otra tabla de estudiantes en donde cada estudiante tiene una matrícula que también es un campo alfanumérico. Lo lógico es que no existan dos asignaturas con la misma clave y tampoco existan dos estudiantes con la misma matrícula. ¿No sería razonable que la llave primaria en la tabla de asignaturas fuera la clave de asignatura y en la tabla estudiantes la llave primaria fuera la matrícula?. Aquí hay un post que ejemplifica lo que hay que hacer en Rails para usar una llave primaria que no sea de tipo entero Non-Integer primary keys in Rails

Y dado que la relación entre estudiantes y asignaturas es del tipo muchos a muchos, tendremos una tabla para saber qué estudiantes están inscritos en una asignatura o visto de otra forma, todas las asignaturas en las que está inscrito un estudiante; ¿no sería lo mejor que la llave primaria de esta tabla estuviera formada por los campos clave de asignatura y matrícula? y además esos campos serían también llaves foráneas ya que son heredados de las tablas de asignaturas y estudiantes respectivamente. Para cualquiera que sepa lo básico de bases de datos relacionales esto es obvio.

Aquí están los diagramas Entidad-Relación (obvio las tablas tendrían más campos además de los que se muestran en el ejemplo y la base de datos contendría otras tablas además de estas)

Base de datos bien diseñada

Base de datos estilo Rails

Hay una gema llamada composite_primary_keys cuyo propósito es solucionar esta situación, pero no funciona 100% como debería.

El argumento de los fans de Rails es que no “luches” en contra de las “convenciones” de Rails, el problema aquí es que esas “convenciones” están mal diseñadas, si para desarrollar una aplicación web usando rails tengo que seguir sus “convenciones”, lo que implica que haga un mal diseño de base de datos (entre otras malas prácticas) o si intento hacer un diseño de base de datos decente tengo que estarme peleando con la herramienta y “parches” para hacer que las cosas funcionen como deben entonces lo mejor es no usar Rails.

Existen otros frameworks para desarrollo web en Ruby, Sinatra por ejemplo es un framework que a diferencia de Rails, no se interpone en tu camino. Eso si, Rails puede hacer algunas cosas automáticamente por ti y en Sinatra tu tienes que programar todo, así que podemos elegir entre usar un framework con el que desarrollemos una aplicación en la que escribamos menos código pero muy probablemente esté mal hecha (si seguimos sus “convenciones”) o usar un framework en el que todo el código lo hayamos tecleado nosotros pero las cosas pueden estar bien hechas (si nosotros las hacemos bien).

Si eres flojo, no te gusta mucho programar, o si no te importa que la aplicación que vas a desarrollar utilice una base de datos mal diseñada por ejemplo, puedes usar Rails, o si en tu trabajo te obligan a usarlo porque son de los que se van con lo que la mayoría usa sin conocer realmente las implicaciones de esa decisión.

Otra muestra de porque en mi opinión Rails no es un buen framework: Estos son los archivos y directorios que tendrías si usas Rails para desarrollar una aplicación web que lo único que haga sea mostrar la hora actual

Para desarrollar la misma aplicación web usando Sinatra sólo necesitamos un archivo con el siguiente código

require 'sinatra'

get '/' do
“La hora acutal es: #{Time.now}”
end

Si usas Sinatra es más fácil que sepas exactamente qué código es Ruby y qué codigo pertenece a instrucciones del ORM (sin importar si el ORM que usas es Active Record, DataMapper, Sequel, etc.), un buen desarrollador de software en Ruby jamás intentará usar blank? si no está desarrollando en Rails; pero ningún buen desarrollador de software en Ruby usaría Rails, a menos que en su trabajo se vea obligado desde luego.

Si tú eres un buen desarrollador de software en Ruby pero te obligan a usar Rails en tu trabajo te aconsejo que busques otro trabajo o que trates de convencer a tus jefes de que usen otro framework, aunque eso muchas veces es más difícil.

Si tú eres un buen desarrollador de software en Ruby y por lo tanto no usas Rails, te felicito, no conozco a muchos

Si programas en Ruby y hasta ahora has estado usando Rails cuando necesitas desarrollar una aplicación web, espero que esta información te ayude a empezar a usar otro framework, yo uso Sinatra pero existen otros como Cuba, Padrino, etc. Te invito a que investigues y encontrarás muchas más buenas razones para no seguir usando Rails

Para algunos existen buenas razones para usar Rails, aunque esas razones para mi no son suficientes, algunas de estas razones son que hay mucha más documentación para Rails que para otros frameworks, la mayoría de las ofertas de trabajo para desarrolladores de software en Ruby son usando Rails, etc. Estos argumentos son ciertos, y el último de ellos de hecho es lamentable, pero si tu objetivo es aprender y ser un buen desarrollador de software en Ruby y no tanto usar x framework porque es el que la mayoría usa o porque con eso te será más fácil encontrar un trabajo, entonces probablemente tampoco para ti esos argumentos sean suficientes.

Procesamiento de archivos con Ruby

febrero 1, 2017

En este post voy a mostrar un ejemplo de procesamiento de archivos con Ruby haciendo referencia a un caso real que se me presentó.

Se tiene un archivo csv llamado datos_clientes.csv y un archivo txt llamado solo_correos.txt; el archivo csv contiene una lista de clientes con datos como nombre, correo electrónico, teléfono, etc. y el archivo txt sólo contiene direcciones de correo electrónico.

El contenido del archivo datos_clientes.csv puede ser algo como esto:

NAME,E-MAIL
SALOMON RINCON TORRES,rtmex@yahoo.com
TOP SYSTEMS S.A DE C.V.,ventas@topsystems.com.mx
SALOMON RINCON TORRES,salomonrincon@softwarelibrepuebla.org
JOHN DOE,ejemplo@gmail.com

y el contenido del archivo solo_correos.txt podría parecerse a esto:

ventas@topsystems.com.mx
ejemplo@gmail.com
rtmex@yahoo.com

Imaginemos que ambos registros tienen cientos o miles de líneas

El objetivo es leer cada uno de los correos del archivo solo_correos.txt y buscarlo en el archivo datos_clientes.csv, si se encuentra, guardar el correo y el nombre del cliente al que pertenece dicho correo en un nuevo archivo csv (en este caso el archivo se llamará resultado.csv).

Aquí el código

require 'csv'

# Declara el encabezado para el archivo que se creará
encabezado = ['correo', 'nombre']
correo = nil
nombre = nil
num_linea = 0

CSV.open('resultado.csv', 'w', write_headers: true, headers: encabezado) do |archivo_nuevo|
    # Abre el archivo que contiene cada una de las líneas que se van a buscar en otro archivo
    File.open("solo_correos.txt") do |archivo|
         archivo.each do |linea|
             num_linea += 1
             puts "Linea #{num_linea} del archivo solo_correoa.txt = "+linea.strip
             # Abre un archivo delimitado por comas
             # En la segunda columna de este archivo, se busca la línea que se leyó del primer archivo
             CSV.foreach('datos_clientes.csv', headers: true) do |linea2|
                 if linea2[1].strip == linea.strip
                    puts "El correo "+linea.strip+" pertenece a "+linea2[0].strip
                    correo = linea.strip
                    nombre = linea2[0].strip
                    archivo_nuevo << [correo, nombre]
                    break
                 end
             end
         end
    end
end

El programa va mostrando el número de línea y su contenido conforme va procesando el archivo solo_correos.txt. Cada vez que en el archivo datos_clientes.csv encuentra el correo que leyó del archivo solo_correos.txt, muestra a que cliente pertenece dicho correo.

Lo que iría mostrando el programa mientras va procesando el archivo solo_correos.txt sería algo como esto:

Linea 3 del archivo solo_correos.txt = rtmex@yahoo.com
El correo rtmex@yahoo.com pertenece a SALOMON RINCON TORRES

Cuando el programa termina, tenemos un archivo csv llamado resultado.csv con 2 columnas, la primera contiene el correo electrónico y la segunda el nombre del cliente al que pertenece dicho correo.