Recursividad en Javascript

Ya hace años hablábamos por aquí sobre la recursividad con ejemplos para C y C++. Es un buen momento para hacer otra entrada, pero ejemplificada en Javascript.

Pero antes de los ejemplos refresquemos la memoria ¿Qué es la recursividad? Se trata de un potente (y a veces peligroso) recurso en la programación. Como aquella primera vez os dejo la definición de la wikipedia:

“…la forma en la cual se especifica un proceso basado en su propia definición.”

“Un problema que pueda ser definido en función de su tamaño, sea este N, pueda ser dividido en instancias más pequeñas (< N) del mismo problema y se conozca la solución explícita a las instancias más simples, lo que se conoce como casos base, se puede aplicar inducción sobre las llamadas más pequeñas y suponer que estas quedan resueltas."

Esto dicho así puede sonar confuso o denso, por lo que podríamos resumirlo como que es una técnica de programación donde una función se llama a si misma.

En la publicación original os ponía dos ejemplos en C, aquí nos vamos a quedar solo con uno para javascript ¿Cómo se calcula el factorial de un número? La operación es la siguiente:

function factorial(num)
{
    // No acepta menores que cero.
    if (num < 0) {
        return 0;
    }
    // Si recibe 0 entonces devuelve 1.
    else if (num == 0) {
        return 1;
    }
    // Si es mayor que cero se llama a si misma.
    else {
        return (num * factorial(num - 1));
    }
}

Piedra-papel-tijera en Ruby

Un pequeño ejercicio de Ruby que he tenido que hacer en el curso de SaaS de Berkeley (en EdX) es un método que «juegue» al tres en raya. Recibiendo un array bidimimensional (la variable game en el ejemplo) debe devolver el resultado de qué jugador sería el ganador. Si hay más de dos jugadores devuelve una excepción, y si alguno mete una letra que no sea R (rock), P (paper), S (scissors) lanza otra excepción (ambas vienen ya dadas en el código de ejemplo). Finalmente, si ambos sacan lo mismo da como ganador al jugador 1 (¿por qué? pues porque es lo que pide el enunciado a reclamarle al maese armero)

En fin, el código sería tal que así:

class WrongNumberOfPlayersError < StandardError ; end
class NoSuchStrategyError < StandardError ; end

def rps_game_winner(game)
  raise WrongNumberOfPlayersError unless game.length == 2
  game[0][1] = game[0][1].downcase
  game[1][1] = game[1][1].downcase
  raise NoSuchStrategyError if game[0][1] != 'r' and game[0][1] != 'p' and game[0][1] != 's'
  raise NoSuchStrategyError if game[1][1] != 'r' and game[1][1] != 'p' and game[1][1] != 's'
  ganador = 0
  if game[0][1] == 'r'
   if game[1][1] == 'r'
   elsif game [1][1] == 'p'
   ganador = 1
   else
   end
  elsif game[0][1] == 'p'
   if game[1][1] == 'r'
   elsif game [1][1] == 'p'
   else
   ganador = 1
   end
  else
   if game[1][1] == 'r'
   ganador = 1
   elsif game [1][1] == 'p'
   else
   end
  end
  return game[ganador]
end

La chicha viene con la segunda parte del ejercicio, el modo tournament. Este recibe un array de arrays bidimensionales con todos los enfrentamientos (hay que suponer que están bien formados y bien anidados, y debe permitir cualquier cantidad de jugadores, siempre que crezcan en progresión de potencias de 2: 4,8,16,32… piensa el cualquier Play off de cualquier deporte) y los recorre como una eliminatoria. Ponen como ejemplo que reciba algo tal que así:

[
    [
        [ ["Armando", "P"], ["Dave", "S"] ],
        [ ["Richard", "R"],  ["Michael", "S"] ],
    ],
    [
        [ ["Allen", "S"], ["Omer", "P"] ],
        [ ["David E.", "R"], ["Richard X.", "P"] ]
    ]
]

Ok, ¿qué hacer frente al anidamiento? Pues recursividad, tema que ya explicamos. En este caso, el método comprobará que cada array a su vez contenga un array y si es así vuelve a llamarse a si mismo hasta que llegue a un enfretamiento, donde entonces llamará al método de la primera parte para devolver al ganador.

def rps_tournament_winner(tournament)
  if tournament[0][0].kind_of?(Array)
       rps_game_winner([rps_tournament_winner(tournament[0]), rps_tournament_winner(tournament[1])])
  else
   rps_game_winner(tournament)
  end
end

Y con esto y un bizcocho, ejercicio realizado. Lo he enviado y ya está corregido, y me ha dado el 100%, así que espero que os aclare las dudas. (Como dice un colega mío «no me copiar, me cago en Satán»… en fin, o sí, que yo tampoco soy ni profesor ni vuestro padre). En todo caso veis que con la recursividad un simple if/else resuelve toda la carga de trabajo en cinco líneas (bueno, realmente dos)

Recursividad: Función que calcula el factorial y programa que imprime una sucesión de Fibonacci en C o C++.

Soy débil, lo reconozco. No se decirle que no a los imperativos de una dama, tal vez por un primitivo impulso machista de querer impresionar y mostrarme como «el que todo lo soluciona», pero eso lo dejo en manos de psicoanalistas. El caso es que me han pedido que explique la recursividad en la programación, y para qué voy a explicarlo a una sola persona si puedo ponerlo aquí y que lo lea todo el que lo necesite (aunque la verdad, si lo buscáis en Google hay miles de ejemplos).

La recursividad es un recurso (qué «rebuznante» suena ponerlo así) muy potente a la hora de programar. Una función recursiva no es otra cosa que una función que se define en función de si misma. Dicho a sí a muchos os ha sonado a chino, ya que es un concepto muy abstracto, no es algo en lo que uno piense de forma natural, no es algo intuitivo. Citando la wikipedia la recursividad es

«…la forma en la cual se especifica un proceso basado en su propia definición.»

«Un problema que pueda ser definido en función de su tamaño, sea este N, pueda ser dividido en instancias más pequeñas (< N) del mismo problema y se conozca la solución explícita a las instancias más simples, lo que se conoce como casos base, se puede aplicar inducción sobre las llamadas más pequeñas y suponer que estas quedan resueltas.»

Simplificando al máximo la definición, a nivel de programación: la recursividad es una función que se llama a si misma. Cada vez que se llama a una función, se crea un juego de variables locales, de este modo, si la función hace una llamada a si misma, se guardan sus variables y parámetros, usando la pila, y la nueva instancia de la función trabajará con su propia copia de las variables locales. Cuando esta segunda instancia de la función retorna, recupera las variables y los parámetros de la pila y continua la ejecución en el punto en que había sido llamada.

Cuando estudié la recursividad en primero de DAI, los ejemplos que tuvimos que programar fueron dos: Una función que calcula un factorial y una función que calcula una sucesión de Fibonacci. Ambos son ejemplos clásicos.

Seguramente Taboada, mi profesor de Fundamentos de la programación me mataría por resolver así la función del factorial, porque siempre insistía en que «una función sólo puede tener una salida», pero después de tanto tiempo programando en VB.NET y en Java ya no me rompo la cabeza con eso y le pongo varias salidas aunque el ejemplo esté en C.


/*Función para un factorial, que recibe un entero*/

int factorial(int n) {
  if(n < 0) return 0; /*Si el número es negativo no se puede calcular el factorial*/
  else if(n > 1) return n*factorial(n-1); /* Recursividad, la función se llama a si misma */
  return 1; /* Si es igual a uno, devuelve uno y se termina la recursividad */
}

Podéis obviar la primera comprobación (la de si n<0) si en el main (o donde llaméis a la función por primera vez) ya comprobáis que no le estáis pasando un negativo (no se puede hacer el factorial de números negativos).

Sobre la sucesión de Fibonacci podéis informaros en ese link a la wikipedia, que es un coñazo explicarla. El programa que la calcula en C (y C++) sería el siguiente (esta vez pondré el programa completo y no sólo la función, porque da la casualidad que lo llevaba dentro del pendrive):

#include<stdio.h>

/*Programa que calcula una sucesión de Fibonacci*/
 void main(void)
 {
 int num=0;/*Entero que almace la cantidad de números a calcular*/
 int resultado=0;/*entero que almacena el resultado*/

printf("SERIE DE FIBONACCI\n");
 printf("Introduce la cantidad de numeros: ");
 scanf("%i",&num);
 printf("\t");

for(int i=0;i<=num-1;i++)
 {
   resultado = fibonacci(i);
   printf("%i ", resultado);
 }

printf("\n");
 }

/*****************************************************/
 /*Función que calcula la serie de Fibonacci*/
 int fibonacci(int n)
 {
 if (n<2)
  return n;
 else
  return fibonacci(n-1) + fibonacci(n-2);
 }

De nuevo pedir perdón por el sangrado, como en otras ocasiones, pero cuando hago copypaste desde Notepad++ o desde Notepad el wordpress no me mantiene la sangría (curiosamente desde GEDIT sí que lo mantiene, un minipunto para Linux). En todo caso, si copiáis y pegáis debería furrular.

En fin, y con esto os lego aquí este conocimiento que a mi me transmitió en su día el profesor José Taboada (aunque no estaría de acuerdo con la función del factorial, como dije antes). Espero que os sirva de utilidad.