¿Podrías hacerlo sin if?
Parte 1

2011-08-31 por @joseanpg


Hoy hablaremos de un ejercicio extraño y de soluciones extrañas. Se trata del tercer ejercicio propuesto para la 5ª reunión del GEJS.

Tengo que aclarar que la propuesta de esta 5ª reunión me parece bastante correcta, siendo el ejercicio 3º el que, en mi opinión, resulta poco adecuado para un grupo estudio con participantes con diferentes niveles. Tal vez debería haber sido colocado al final como reto.

No soy el único que piensa que de cara al Grupo de Estudio no es un ejercicio muy adecuado. Pero sin esperarlo, al quedar abierta la puerta a cualquier solución extraña, comenzó una carrera de propuestas "imaginativas", de escaso valor didáctico, pero que nos dieron un rato de diversión. Voy a explicarlas aquí, siguiendo orden cronológico.

La solución de @Aijoona

Después de realizar unas cuantas de sugerencias respecto al enunciado, la verdad es que no pensaba dedicarle más tiempo. Pero tras ver que siguiente solución fue dada como aceptable era inevitable animarse: quedaba abierta la veda de lo estrambótico :)

//@Aijoona

(new Array(101)).toString().replace(/,/g, function(m, i) {
    console.log(i)
});

Valentín trama una increíblemente astuta y sorprendente solución: crea una array sin elementos pero con longitud 101, lo convierte en cadena obteniendo por tanto una serie de 100 valores undefined separados por comas, y ahora viene lo bueno: utiliza el bucle implícito en String.prototype.replace para recorrer la cadena contando las comas. Genial.

Hoy mismo, aNieto2k nos comenta que de forma independiente ha llegado a la misma solución.
//@aNieto2k

(Array(101)).toString().replace(/,/g, function () {
    console.log(arguments[1])
});

Nuestra primera propuesta

Después de ver la de Valentín no pude contenerme. Tocaba preparar otra propuesta absurda.
//@joseanpg (1ª solución)

(function (n) {
    (function f(m) {
        try {
            1..toExponential(m/n - 1);
            console.log(n - m + 1);
            f(m-1);
        } catch (rangeError) {}
    })(n)
})(100);

La idea era abandonar la recursión mediante una excepción y ya puestos que la excepción fuera rara. Pues nada, ahí va un RangeError en una función para dar formato a un número. Dentro de la función f la variable m ira recorriendo los valores desde n ("clausurado") hasta 1, ya que como explicaremos después para m=0 se lanza un RangeError. En consola mostramos el valor de la expresión n-m+1 que se moverá desde 1 hasta n.

Bien, pasemos a explicar el RangeError. Las funciones Number.prototype.toFixed y Number.prototype.toExponential convierten en entero a su argumento y si dicho valor es menor que 0 o mayor que 20 lanza RangeError. La expresión m/n - 1 va tomando valores desde 0 hasta -1 que cuando van siendo convertidos en entero serán todos 0 excepto el correspondiente a m=0 momento en el que el valor -1 dispara el RangeError.

Rebuscado, ¿verdad? :)

La solución de @genezeta

Después de manifestar sus dudas respecto a la utilidad didáctica del ejercicio (dudas que comparto), Gonzalo nos soltó esto:
//@genezeta

(function ping(i, yn) {
    yn[i < 100](i, ping, yn)
})(0, {
    true: function (n, f, yn) {//pong
        console.log(n);
        f(n + 1, yn);
    },
    false: function () {}
});

¡Tremendo! ¡CPS mediado por un objeto selector! La mejor propuesta para mi gusto desde el punto de vista Wooooow!!. La función ping comienza el proceso tomando como argumentos un objeto que contiene dos propiedades con los nombres "true" y "false" que contienen una función respectivamente. Lá única que tiene interés es "true" que en lo que sigue denominaré pong. La función ping también recibe el entero i que va indicando por donde vamos.

ping evalua la expresión i < 100 y el resultado boolean obtenido se convierte en cadena y es utilzado como selector de propiedad en el objeto. Mientrás que i sea menor que 100 se eligirá la propiedad con nombre "true", es decir pong.

pong es invocada recibiendo el índice, ping como continuación, y el objeto que hace de switch. Se ocupar de imprimir el número, y rebota hacia ping con el índice decrementado y por supuesto el objeto que porta a la misma pong.

El proceso se repite hasta llegar a 100, momento en el que no se llamará a pong, sino a su compañera "false", que es una función vacía. Fin.

Por cierto, esta solución da los valores entre 0 y 99 ambos inclusive, pero ¡cuidado con poner ésto como pega!

La solución de @SeRGiNaToR

//@SeRGiNaToR

var ej3 = [function () {}, function (n) {
    console.log(n);
    ej3[Math.ceil( n / 100)](n - 1);
}];
ej3[1](100);

Sergio prepara un array con una función vacia y otra con la función que hará el trabajo. Esta última función imprime el entero en curso en la consola y llama a la función del array seleccionada por la expresión Math.ceil(n/100). Para n > 0 y n ≤ 100 el valor de n/100 está en el intervalo (0,1), y al pasar por Math.ceil obtenemos 1, seleccionando esta misma función. Pero cuando n valga 0, Math.ceil nos dará 0, se selecciona la función vacía y se terminón. Desde un punto de vista didáctico esta es la solución más interesante para el grupo de estudio. Por este motivo me permito hacerle una pequeña modificación para que no imprima el 0

var ej3 = [function () {}, function (n) {
    console.log(n);
    ej3[Math.ceil( (n-1) / 100)](n - 1);
}];
ej3[1](100);

La solución de @pasku1

//@pasku1

(function print(i, max) {
    console.log(++i);
    ({
        0: print,
        1: function () {}
    })[(i / max) >> 0](i, max);
})(0, 100)

Guillermo utiliza una técnica parecida a la de Sergio. La diferencia principal está en que utiliza un objeto que se crea en cada paso del proceso. Hay que observar también que el objeto es un pseudo-array.

La solución de @scinos

//@scinos

(function f(i, max) {
    console.log(i);
    ([function(){}, f][+(i < max)])(i + 1, max);
})(1, 100)

Nuestra segunda ¿solución? :)

Esta es sólo una broma. No aparece ninguna keyword prohibido en el código fuente... entonces ... vale ¿no? :)
//@joseanpg (Versión broma)

(function (n) {
    var r = eval('(function(x,y){x ' + String.fromCharCode(38, 38) + ' y(x)})');
    f(n);

    function f(m) {
        console.log(n - m + 1);
        r(m - 1, f)
    };
})(100);

La segunda solución de @Aijona

//@Aijona (segunda solución)

(function loop(from, to, handler) {
	(function cycle(n) {
		[
			eval,
			function(n) {
				handler(n);
				cycle(n+1)
			}
		][1^(n/to)](n)
	})(from);
})(50, 100, console.log);

Nuestra tercera propuesta

En esta continuación intento formalizar la familia de soluciones basadas en la selección de rama mediante un array. La interfaz se inspira en Smalltalk
//@joseanpg (3ª solución, estilo Smalltalk)

function empty(){};

function bool(booleanValue) {
  var branchs = [empty,empty];
  return {
     ifTrue:  function (f){ branchs[1] = f; return this;},
     ifFalse: function (f){ branchs[0] = f; return this;},
     exec:    function(arg){return branchs[Number(booleanValue)](arg);}
  };
}

(function (n) {
  (function fun(m) {
    console.log(n-m+1);
    bool(m>1).ifTrue(fun).exec(m-1);
  })(n)
})(100);

La solución de @nestoralvaro

//@nestoralvaro

(function f(v, a) {
    try {
        a[v % v].lenght;
        f(v - 1, a);
        console.log(v);
    } catch (e) {}
}(100, ["a"]))

La solución de @boton

//@boton

(function (n) {
    (Array(n)).toString().split(',').map(function (a, i) {
        console.log(++i)
    })
})(100)

La solución de @javierbrea

//@javierbrea

Array(100).toString()
          .split(',')
          .map(function (x, i) {console.log(i + 1);});

La solución de @acido69

//@acido69

Array(100).toString()
          .replace(/$|,/g, function () {
             console.log(arguments[1]); 
             return ''
           })

Las soluciones de @eamodeorubio

//@eamodeorubio

(function(max) {
  var counter = 0
    , theEnd = function() {console.log(max);}
    , count = function() {
         console.log(counter);
         counter++;
         countOperations[counter] = count;
         countOperations[counter % max]();
    }
    , countOperations = [theEnd];
  count();
})(100);
Function.prototype.then = function(callback) {
  var branches = { false:function() {}, true:callback };
  var that = this;
  return function() {
    var r = that.apply(that, arguments);
    branches[r].apply(that, arguments);
    return r;
  }
}

Number.prototype.times = function(callback) {
  (function(i) { return i != 0; }).then(function(i) {(i - 1).times(callback);})(this);
  callback(this);
};

Number(100).times(function(i) { console.log(i.toString()); });

La segunda solución de @genezeta

//@genezeta (2ª solución)

(function(n) {
    function p() {console.log(n++)}
    function d(f,g) { return function() { f(g());} }
d(d(d(d(d(d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p))),d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p)))),d(d(p,p),d(p,p))),d(d(d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p))),d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p)))),d(d(p,p),d(p,p)))),d(d(d(d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p))),d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p)))),d(d(p,p),d(p,p))),d(d(d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p))),d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p)))),d(d(p,p),d(p,p))))),d(d(d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p))),d(d(d(p,p),d(p,p)),d(d(p,p),d(p,p)))),d(d(p,p),d(p,p))))();
})(0)

// Alternate...

(function(n) {
    function p() {console.log(n++)}
    function d(g) { return function() { g(g());} }
    function f(g) { return function() { d(d(g))(); g(); } }
    f(f(d(d(p))))();
})(0)

La solución de @jneira

Lo sé, no cumple con los requisitos del ejercicio, pero creo que merece la pena estudiarla. Y no, no es Clojure, es JavaScript :)
//@jneira

(function (ying,yang)
    {ying.yang=0;yang.ying=100;ying(yang)})
(function ying(yang) {
    console.log(ying.yang++); 
    yang.ying==ying.yang || yang(ying)}
,function yang(ying) {
    console.log(yang.ying--);
    ying.yang==yang.ying || ying(yang)})
Por cierto, los números siguen una hermosa secuencia en cremallera.

La solución de @juli0castill0

//@juli0castill0

(function (n, g) {
     f = {};
     f[0] = function (i) {
         g(i);
         f[parseInt((i) / n)](i + 1)
     };
     f[1] = function (i) {};
     f[0](1);
 })(100, console.log);

La solución de @panino5001

Andrés Fernández nos deja un otra propuesta absolutamente original de esas del tipo Wooooow!! :)

//@panino5001

(function (o) {
     var n = 0;

     function f() {
         ++n;
         console.log(n);
         clearInterval(o[n]);
     }
     o[100] = setInterval(f, 0);
 })(new Array())

Utiliza para iterar un ... ¡setInterval! La idea es la siguiente: lanza un setInterval y anota su handler en el elemento indexado por 100 en un array. Salvo ese elemento, el resto del array está vacio. La función cliente irá mostrando un contador en consola y aplicando clearInterval a los sucesivos elementos del array, utilizando el contador como índice. Como estos elementos son undefined el clearInterval falla en silencio hasta que se llega al único elemento que al que se le ha dado valor: el de índice 100. El temporizador es liquidado, fin de la historia.

La solución de @Osukaru80

//@Osukaru80


 (function print(i) {
     Array(i);
     console.log(100 - i);
     print(i - 1);
 })(99)