viernes, 25 de diciembre de 2015

Serie de Fibonacci

Alejandro tiene un campo y su vecina, Selena, tiene una pareja de conejos. Selena quiere asociarse con Alejandro para la cría de conejos, le dice que en su campo pueden criarse juntos y multiplicarse. Le dice que al mes engendrarán una pareja de conejos y que esa pereja tardará un mes en engendrar otra pareja de conejos. A su vez la primer pareja seguirá engendrando parejas de conejos. Y así cada pareja de conejos seguirá teniendo parejas de conejos. Entonces Alejandro se pregunta: ¿Cuántos conejos tendrá al cabo de 12 meses? La respuesta está en la serie de Fibonacci.

Los números de Fibonacci Fk son una sucesión de números naturales definidos de la siguiente manera:
F0 = 0,
F1 = 1,
Fk = Fk−1 + Fk−2, cuando k≥2

En palabras simples, la sucesión de Fibonacci comienza con 0 y 1, y los siguientes términos siempre son la suma de los dos anteriores.



En la siguiente tabla, podemos ver los números de Fibonacci desde el 0-ésimo hasta el duodécimo.

n 0123456789101112...
Fn 01123581321345589144...


La sucesión puede utilizarse, de forma parecida, para contar el número de distintas rutas que puede seguir una abeja que va recorriendo las celdillas hexagonales de un panal.

Objetivo

Dado un número N obtener el número de Fibonacci n-ésimo.

La solución recursiva en Perl


La serie de Fibonacci puede definirse en forma recursiva, y es natural pensar una solución recursiva como eśta:

#!/usr/bin/perl
use strict;
use warnings;

print "Serie Fibonacci\n";
print "Por favor, ingrese n: ";
my $n = <stdin>;
chomp($n);
my $valor= fibonacci($n);
print "El valor F($n) es $valor \n";

sub fibonacci{
  my $n= shift;
  # F0 = 0,F1 = 1,
  if ($n<=1){ return $n; }
  return fibonacci($n-1) + fibonacci($n-2);
}

Pero si estudiamos lo que pasa con éstas llamadas recursivas vemos que se llaman múltiples veces con los mismos argumentos. Esto es ineficiente, además las múltiples llamadas son exponenciales, por ejemplo fibonacci(5) llama a fibonacci(4) y fibonacci(3); pero fibonacci(4) también llama a fibonacci(3) y a fibonacci(2); pero cada fibonacci(3) llama a fibonacci(2) y así hasta llegar al caso base.

La solución con memoización en Perl

La solución más obvia es ir guardando los resultados parciales en un array para poder utilizarlos luego. Cada resultado parcial es la respuesta a un subproblema del problema inicial.

#!/usr/bin/perl
use strict;
use warnings;

print "Serie Fibonacci\n";
print "Por favor, ingrese n: ";
my $n = <stdin>;
chomp($n);
my @serie;
$serie[0]= 0;
$serie[1]= 1;
my $valor= fibonacci($n);
print "El valor F($n) es $valor \n";

sub fibonacci{
  my $n= shift;
  if (defined($serie[$n])){ return $serie[$n]; }
  $serie[$n]= fibonacci($n-1) + fibonacci($n-2);
  return $serie[$n];
}


Uno podría pensar que la memoización es la manera más conveniente de resolver un problema de recursividad múltiple. Sin embargo, una función recursiva gasta un poco de memoria y tiempo en cada llamada recursiva. Por esta razón, la programación dinámica prescinde de la recursividad y resuelve el problema exclusivamente con la iteración.

La solución con programación dinámica en Perl

En general los pasos necesarios para resolver un problema con la programación dinámica son las siguientes:
  • Encontrar una definición recursiva para el problema (incluyendo los casos base)
  • Inicializar la estructura de memoria (normalmente un vector o una matriz)
  • Llenar el resultado para los casos base
  • Usar bucles para calcular el resultado para otros casos, empleando la definición recursiva (estrategia bottom-up)
La estrategia bottom-up consiste en resolver primero los subproblemas más pequeños, almacenar su solución, y luego resolver los problemas más complejos, usando los resultados almacenados.


La idea es llenar el vector o matriz de resultados de abajo arriba, empezando por los casos base. Al resolver un subproblema se aplica la misma idea recursiva para obtener el resultado. Sin embargo, si los subproblemas recursivos ya han sido resueltos no es necesario hacer ninguna llamada recursiva, sino que el resultado se puede calcular directamente a partir de los resultados guardados en memoria.

#!/usr/bin/perl
use strict;
use warnings;

print "Serie Fibonacci\n";
print "Por favor, ingrese n: ";
my $n = <stdin>;
chomp($n);
my $valor= fibonacci($n);
print "El valor F($n) es $valor \n";

sub fibonacci{
  my $n= shift;
  my @serie;
  $serie[0]= 0;
  $serie[1]= 1;
  for (my $i= 2; $i <= $n; $i++){
    $serie[$i] = $serie[$i-2] + $serie[$i-1];
  }
  return $serie[$n];
}



viernes, 18 de diciembre de 2015

Números primos

Primo o compuesto


Luis invitó a su primo Miguel a su casa. Mientras la mamá de Luis preparaba unas galletas caseras al horno para comer con la leche chocolatada, los primos revolvían unos libros de matemática y se encontraron con este teorema:

Para todo número primo p > 3, se tiene que p = 6k+1 ó p = 6k-1 

Luego de pensar un rato se dieron cuenta de que todos los números enteros pueden expresarse exactamente de un de las 6 posibles formas:
6k, 6k+1, 6k+2, 6k+3, 6k-2, ó 6k-1
-Claro! -dijo Luis- 6k es divisible por 6, por lo que no es primo.
-6k+2 es par, por lo que no es primo -agregó Miguel.
-6k-2 es par, así que tampoco es primo -se apuró a conjeturar Luis.
-Pero qué pasa con 6k+3?
-6k+3 es igual a 3(2k+1) que es divisible por 3, por lo que no es primo
-Por lo tanto, los números primos tienen que expresarse de la forma 6k+1 o 6k-1.
-Pero no todos los números de esa forma son primos, por ejemplo...
-Ya están las galletitas! A tomar la leche! -dijo la mamá.



Objetivo

Desarrolle un programa cuya entrada sea un entero positivo n, y cuya salida sea:

    primo, si el número es primo, y
    compuesto, si el número es compuesto.

Por ejemplo, si la entrada es 29, el programa debe decir primo. Si la entrada es 27, el programa debe decir compuesto.


Solución en Perl


#!/usr/bin/perl
use strict;
use warnings;

print "Por favor, ingrese un número mayor a 0: ";
my $n = <stdin>;
chomp($n);
if (primo($n)){ print "Primo!"; }
else{ print "Compuesto!"; }
print "\n";

sub primo{
  my $n= shift;
  if (1==$n){ return 0; }
  if (2==$n){ return 1; }
  if (3==$n){ return 1; }
  return teorema($n);
}
sub teorema{
  my $n= shift;
  my $resto= $n % 6;
  if (($resto != 1) && ($resto != 5)){ return 0; }
  my $raiz= sqrt($n);
  my $i= 1;
  while ((6*$i - 1) <= $raiz){
    if ( !($n % (6*$i + 1)) ){ return 0; }
    if ( !($n % (6*$i - 1)) ){ return 0; }
    $i++;
  }
  return 1;
}

Primeros m primos

Usando como base el programa diseñado en el ejercicio anterior, desarrolle otro programa que reciba como entrada un número entero positivo m y cuya salida sean los m primeros números primos.

Por ejemplo, si la entrada es 12, la salida del programa debe ser:

2
3
5
7
11
13
17
19
23
29
31
37

Solución en Perl


#!/usr/bin/perl
use strict;
use warnings;

print "Por favor, ingrese un número mayor a 0: ";
my $n = <stdin>;
chomp($n);
my $cantidad= 0;
my $i= 1;
while ($cantidad < $n){
  if (primo($i)){ print "$i\n"; $cantidad++; }
  $i++;
}
print "\n";

sub primo{
  my $n= shift;
  if (1==$n){ return 0; }
  if (2==$n){ return 1; }
  if (3==$n){ return 1; }
  return teorema($n);
}
sub teorema{
  my $n= shift;
  my $resto= $n % 6;
  if (($resto != 1) && ($resto != 5)){ return 0; }
  my $raiz= sqrt($n);
  my $i= 1;
  while ((6*$i - 1) <= $raiz){
    if ( !($n % (6*$i + 1)) ){ return 0; }
    if ( !($n % (6*$i - 1)) ){ return 0; }
    $i++;
  }
  return 1;
}

Primos hasta m


Modifique el programa del ejercicio anterior para que muestre los números primos menores o iguales a m.

Por ejemplo, si la entrada es 12, la salida debe ser:

2
3
5
7
11

Solución en Perl


#!/usr/bin/perl
use strict;
use warnings;

print "Por favor, ingrese un número mayor a 0: ";
my $n = <stdin>;
chomp($n);
my $i= 1;
while ($i <= $n){
  if (primo($i)){ print "$i\n"; }
  $i++;
}
print "\n";

sub primo{
  my $n= shift;
  if (1==$n){ return 0; }
  if (2==$n){ return 1; }
  if (3==$n){ return 1; }
  return teorema($n);
}
sub teorema{
  my $n= shift;
  my $resto= $n % 6;
  if (($resto != 1) && ($resto != 5)){ return 0; }
  my $raiz= sqrt($n);
  my $i= 1;
  while ((6*$i - 1) <= $raiz){
    if ( !($n % (6*$i + 1)) ){ return 0; }
    if ( !($n % (6*$i - 1)) ){ return 0; }
    $i++;
  }
  return 1;
}

 Más información

Un ejemplo de algoritmo eficiente y su explicación se puede encontrar en la Criba de Atkin

miércoles, 16 de diciembre de 2015

PI

Un poco de historia

El valor de π se ha obtenido con diversas aproximaciones a lo largo de la historia, siendo una de las constantes matemáticas que más aparece en las ecuaciones de la física, junto con el número e. Por ello, tal vez sea la constante que más pasiones desata entre los matemáticos profesionales y aficionados.
Tomando en cuenta que el número pi forma un decimal infinito, lo habitual es usar una aproximación del mismo.

Los matemáticos han encontrado varias series matemáticas que si se repiten infinitamente pueden calcular con precisión el valor de Pi con una gran cantidad de decimales. Algunas de estas series son tan complejas que se necesitan supercomputadoras para procesarlas. Sin embargo, una de las más simples, es la serie Gregory-Leibniz. Aunque no es muy eficiente, se acerca cada vez más al valor de Pi en cada repetición, produciendo con precisión hasta cinco mil decimales de Pi con 500000 repeticiones.

Objetivo I

Desarolle un programa para estimar el valor de π usando la siguiente suma infinita:
π = 4*(1 − 1/3 + 1/5 − 1/7 + ···)

La entrada del programa debe ser un número entero n que indique cuántos términos de la suma se utilizará.

Por ejemplo, si la entrada es 3, el programa debe entregar como salida:

3.466666666666667

Si la entrada es 1000, la salida debe ser:

3.140592653839794

Mientras más veces repitas la serie, más te acercarás al valor de Pi.


Solución en Perl


#!/usr/bin/perl
use strict;
use warnings;

my $suma= 0;
print "Serie Gregory-Leibniz\n";
print "Por favor, ingrese la cantidad de terminos: ";
my $n = <stdin>;
chomp($n);
my $fin_iteracion= $n-1;
foreach my $i (0..$fin_iteracion){
  my $sumando= 1/($i*2+1);
  if ($i%2){ $suma-= $sumando; }
  else{ $suma+= $sumando; }
}
my $aproximacion= 4 * $suma;
print "El valor aproximado de Pi es $aproximacion \n";



Objetivo II

Utilice la serie Nilakantha. Esta es otra serie infinita que sirve para calcular Pi y que además es bastante fácil de entender. Aunque es más complicada que la fórmula de Gregory-Leibniz, converge en los valores de Pi mucho más rápido.

π = 3 + 4/(2*3*4) - 4/(4*5*6) + 4/(6*7*8) - 4/(8*9*10) + 4/(10*11*12) - 4/(12*13*14) ...
Para esta fórmula, toma un tres y empieza a alternar entre suma y resta de fracciones con un numerador de 4 y un denominador que sea el producto de tres enteros consecutivos que vayan aumentando con cada nueva fracción. El denominador de cada nueva fracción empieza con el mayor entero utilizado en la fracción anterior. Repite la serie aunque sea solo un par de veces y verás que el resultado se acerca bastante a Pi.



Solución en Perl


#!/usr/bin/perl
use strict;
use warnings;

my $suma= 0;
print "Serie Nilakantha\n";
print "Por favor, ingrese la cantidad de terminos: ";
my $n = <stdin>;
chomp($n);
foreach my $i (1..$n){
  my $denominador= ($i*2)*($i*2+1)*($i*2+2);
  my $sumando= 4/$denominador;
  if ($i%2){ $suma+= $sumando; }
  else{ $suma-= $sumando; }
}
my $aproximacion= 3 + $suma;
print "El valor aproximado de Pi es $aproximacion \n";

lunes, 14 de diciembre de 2015

Paradoja de la dicotomía

Problema

Zenón está a ocho metros de un árbol. Llegado un momento, lanza una piedra, tratando de dar al árbol. La piedra, para llegar al objetivo, tiene que recorrer antes la primera mitad de la distancia que le separa de él, es decir, los primeros cuatro metros, y tardará un tiempo (finito) en hacerlo. Una vez llegue a estar a cuatro metros del árbol, deberá recorrer los cuatro metros que le quedan, y para ello debe recorrer primero la mitad de esa distancia. Pero cuando esté a dos metros del árbol, tardará tiempo en recorrer el primer metro, y luego el primer medio metro restante, y luego el primer cuarto de metro... De este modo, la piedra nunca llegará al árbol. Es posible utilizar este razonamiento, de forma análoga, para «demostrar» que la piedra nunca llegará a salir de la mano de Zenón.

Potencias fraccionales de 2


Desarrolle un programa que tabule las potencias fraccionales de 2 (1/2, 1/4, 1/8, 1/16, ...) y sus sumas parciales en forma decimal.

La salida del programa debe comenzar así:

Potencia  Fraccion  Suma
1          0.5       0.5
2          0.25      0.75
3          0.125     0.875
4          0.0625    0.9375
...       ...

El programa debe terminar cuando la fracción sea menor o igual que 0.00001.

La tercera columna contiene la suma de todas las fracciones calculadas hasta esa fila.

Solución en Perl

Usamos printf para imprimir las fracciones con una cantidad específica de decimales por un motivo estético

#!/usr/bin/perl
use strict;
use warnings;

my $n= 1;
my $fraccion= 1/(2**$n);
my $suma= $fraccion;
print "Potencia \t\tFraccion \t\tSuma\n";
while ($fraccion>0.00001){
  print "$n \t\t";
  printf "%.16f \t\t%.5f \n",$fraccion,$suma;
  $n++;
  $fraccion= 1/(2**$n);
  $suma+= $fraccion;
}

martes, 8 de diciembre de 2015

Largo y corto

100 Nombres

Daniel y Cristina van a tener un bebé. Todavía no saben si será nene o nena pero tienen una lista con los posibles nombres. Como les cuesta decidirse por alguno quieren divertirse diseñando un programa de computadora que les diga el nombre más corto y el más largo.

Objetivo

Desarrolle un programa que tenga la siguiente entrada:
  • primero, el usuario ingresa un número entero n, que indica cuántas palabras ingresará a continuación;
  • después el usuario ingresa n palabras.

La salida del programa deben ser la palabra más larga y la más corta que ingresó el usuario.

Por ejemplo, si el usuario ingresa:

5
Diego
Andrea
Marcelino
Azul
Ana

la salida debe ser:

Mas larga: Marcelino
Mas corta: Ana

Solución en Perl


#!/usr/bin/perl
use strict;
use warnings;

my $mas_corto= '';
my $mas_largo= '';
my $size_corto= 99;
my $size_largo= 0;
print "Por favor, ingrese la cantidad de nombres: ";
my $n = <stdin>;
chomp($n);
foreach my $i (1..$n){
  print "Nombre $i?: ";
  my $nombre = <stdin>;
  chomp($nombre);
  if (length($nombre) > $size_largo){ $mas_largo= $nombre; $size_largo= length($nombre); }
  if (length($nombre) < $size_corto){ $mas_corto= $nombre; $size_corto= length($nombre); }
}
print "Gracias por los valores ingresados \n";
print "El más corto: $mas_corto \n";
print "El más largo: $mas_largo \n";


Contador de signos

Signos del clima

Ramón está preocupado por el cambio climático y decide empezar a revisar cifras que tengan que ver con el clima y su evolución. Para empezar toma un listado de temperaturas alrededor del mundo y quiere contar las que están sobre cero y las que están bajo cero. Como el trabajo es muy repetitivo y propenso a errores se propone automatizarlo con un programa de computadora.

Objetivo

Desarrolle un programa que pida al usuario que ingrese varios números, uno por uno. El ingreso de números termina cuando el usuario ingresa el 0, entonces el programa debe entregar como salida la cantidad de números positivos y negativos que ingresó el usuario.

Por ejemplo, si el usuario ingresa los números 37, —4, —22, —7, 17, —1 y 0, el programa debe entregar la salida:

Positivos: 2
Negativos: 4


Solución en Perl

#!/usr/bin/perl
use strict;
use warnings;

my $positivos= 0;
my $negativos= 0;
print "Por favor, ingrese un valor: ";
my $temperatura = <stdin>;
chomp($temperatura);
while($temperatura){
  if ($temperatura > 0){ $positivos++; }
  if ($temperatura < 0){ $negativos++; }
  print "Por favor, ingrese un valor: ";
  $temperatura = <stdin>;
  chomp($temperatura);
}
print "Gracias por los valores ingresados \n";
print "Positivos: $positivos \n";
print "Negativos: $negativos \n";