Iteración

En este capítulo se repasa la tercera estructura de la programación estructurada: la Iteración.

La estructura "while" (mientras)

La estructura while, es la estructura iterativa por excelencia. Esta estructura repite la ejecución de una o más intrucciones mientras la condición del ciclo es verdadera.

El algoritmo de esta estructura, es:

gojsGraph({divi, modelo: {"class":"go.GraphLinksModel","linkFromPortIdProperty":"fromPort","linkToPortIdProperty":"toPort","nodeDataArray":[{"category":"Conditional","text":"condición","key":-5,"loc":"-46.00000000000003 -297.00000000000006"},{"category":"Process","text":"instrucciones","key":-2,"loc":"-46.000000000000085 -216.79999995231634"},{"category":"Connector","text":"...","key":-8,"loc":"-46 -358"},{"category":"Connector","text":"...","key":-4,"loc":"-46.409035948064144 -148.63614379225646"}],"linkDataArray":[{"from":-5,"to":-2,"fromPort":"B","toPort":"T","visible":true,"points":[-46.00000000000003,-276.96664276123056,-46.00000000000003,-266.96664276123056,-46.00000000000003,-255.51666066646584,-46.000000000000085,-255.51666066646584,-46.000000000000085,-244.06667857170112,-46.000000000000085,-234.06667857170112],"text":"Si"},{"from":-8,"to":-5,"fromPort":"B","toPort":"T","points":[-46,-343.3042461927547,-46,-333.3042461927547,-46,-330.16880171576213,-46.00000000000003,-330.16880171576213,-46.00000000000003,-327.0333572387696,-46.00000000000003,-317.0333572387696]},{"from":-2,"to":-5,"fromPort":"L","toPort":"L","points":[-97.29248046875009,-216.79999995231634,-107.29248046875009,-216.79999995231634,-144,-216.79999995231634,-144,-297.00000000000006,-120.49705505371097,-297.00000000000006,-110.49705505371097,-297.00000000000006]},{"from":-5,"to":-4,"fromPort":"R","toPort":"T","visible":true,"points":[18.49705505371091,-297.00000000000006,28.49705505371091,-297.00000000000006,59,-297.00000000000006,59,-189,-46.409035948064144,-189,-46.409035948064144,-173.33189759950176,-46.409035948064144,-163.33189759950176],"text":"No"}]} })

Es decir que si la condición es verdadera (Si) se ejecutan las instrucciones del ciclo, luego se vuelve a preguntar si la condición es verdadera y de ser así, se vuelven a ejecutar las instrucciones del ciclo, continuando así mientras la condición es verdadera (Si). Cuando la condición es falsa (No), el programa sale del ciclo y continúa con el resto de las instrucciones existentes.

La sintaxis de esta estructura en Javascript es:

while (condición) { instrucciones del ciclo; }

Por ejemplo, para generar y mostrar los números enteros comprendidos entre 3 y 9, se inicia una variable "i" (el contador) en 3 y mientras su valor es menor o igual a 9, se muestra e incrementa su valor en 1:

i = 3;
while (i<=9) {
print("i = "+i);
i++;
};

Es importante notar que si no se incrementa el valor de la variable dentro de la estructura, el ciclo se repite indefinidamente, porque "i" siempre es menor a 9 y en consecuencia la condición es siempre verdadera, dando lugar a lo que se conoce como un ciclo infinito.

Los ciclos infinitos normalmente son un error de programación y hacen que la página se congele, porque el procesador está ocupado repitiendo el ciclo. En esos casos no queda otra alternativa que cerrar la página.

Como se sabe, en programación siempre existe más de una forma de resolver el mismo problema, así si la variable inicia en 2 y en el ciclo se incrementa su valor antes de ser mostrado (cambiando la condición del ciclo a i<=8), se obtiene el mismo resultado:

i = 2;
while (i<=8) {
i++;
print("i = "+i);
};

Y, por supuesto (a pesar de ser un problema muy sencillo) se puede pensar en otras formas más de resolverlo.

El operador compuesto ++ (al igual que en C++) incrementa el valor de la variable en uno (en este caso es equivalente a i = i=1). Además de este operador, en JavaScript se pueden emplear los siguientes operadores compuestos:

Operadores Compuestos
OperadorDefinición
++Incremento
\((x\text{+}\text{+} \iff x=x+1)\)
−−Decremento
\((x-- \iff x=x-1)\)
+=Suma compuesta
\((x\text{+=}5 \iff x=x+5\))
−=Resta compuesta
\((x-=5 \iff x=x-5)\)
*=Multiplicación compuesta
\((x\text{*=}5 \iff x=x*5)\)
/=División compuesta
\((x\text{/=}5 \iff x=x/5)\)
%=Residuo compuesto
\((x\%= 5 \iff x=x\%5)\)
**=Potencia compuesta
\((x**= 5 \iff x=x**5)\)

Así, para generar y mostrar los números impares comprendidos entre 5 y 17, donde el incremento aritmético es 2 (9-7=2, 7-5=2), se puede emplear el operador +=:

i = 5;
while (i<=17) {
print("i = "+i);
i += 2;
};

Igualmente, para generar y mostrar la serie 6, 10, 14, ..., 34, donde el incremento aritmético es 4 (10-6=4, 14-10=4), se escribe:

i = 6;
while (i<=34) {
print("i = "+i);
i += 4;
};

Y para generar y mostrar la serie 2187, 729, ..., 27, 9, 3, donde el decremento geométrico es 3 (27/9=3, 9/3=3), se escribe:

i = 2187;
while (i>=3) {
print("i = "+i);
i /= 3;
};

A partir de este capítulo, se deben emplear los operadores compuestos en todos los casos en los que sea posible.

La estructura do-while (hacer-mientras)

La estructura do-while es similar a la estructura while, sólo que la condición se encuentra al final del ciclo, por esa razón, en esta estructura las instrucciones se ejecutan por lo menos una vez.

El algoritmo, es:

gojsGraph({divi, modelo: {"class":"go.GraphLinksModel","linkFromPortIdProperty":"fromPort","linkToPortIdProperty":"toPort","nodeDataArray":[{"category":"Conditional","text":"condición","key":-5,"loc":"-46.00000000000003 -233.00000000000006"},{"category":"Process","text":"instrucciones","key":-2,"loc":"-46.000000000000085 -298.79999995231634"},{"category":"Connector","text":"...","key":-8,"loc":"-46 -358"},{"category":"Connector","text":"...","key":-4,"loc":"-46.409035948064144 -157.63614379225646"}],"linkDataArray":[{"from":-8,"to":-2,"fromPort":"B","toPort":"T","points":[-46,-343.3042461927547,-46,-333.3042461927547,-46,-329.6854623822279,-46.000000000000085,-329.6854623822279,-46.000000000000085,-326.0666785717011,-46.000000000000085,-316.0666785717011]},{"from":-2,"to":-5,"fromPort":"B","toPort":"T","points":[-46.000000000000085,-281.53332133293156,-46.000000000000085,-271.53332133293156,-46.000000000000085,-267.2833392858506,-46.00000000000003,-267.2833392858506,-46.00000000000003,-263.0333572387696,-46.00000000000003,-253.0333572387696]},{"from":-5,"to":-2,"fromPort":"L","toPort":"L","visible":true,"points":[-110.49705505371097,-233.00000000000009,-120.49705505371097,-233.00000000000009,-144,-233.00000000000009,-144,-298.79999995231634,-107.29248046875009,-298.79999995231634,-97.29248046875009,-298.79999995231634],"text":"Si"},{"from":-5,"to":-4,"fromPort":"B","toPort":"T","visible":true,"points":[-46.00000000000003,-212.96664276123056,-46.00000000000003,-202.96664276123056,-46.00000000000003,-192.64927018036616,-46.409035948064144,-192.64927018036616,-46.409035948064144,-182.33189759950176,-46.409035948064144,-172.33189759950176],"text":"No"}]} })

Su sintaxis, en Javascript, es:

do { instrucciones del ciclo; } while (condición);

Con frecuencia se pueden emplear las estructuras while y do-while indistintamente. Por ejemplo, se pueden generar los números impares del 5 al 17, pero incrementando el contador antes de que sea mostrado:

i = 3;
do {
i += 2;
print("i = "+i);
} while (i<17);

De la misma forma, para generar y mostrar la serie 6, 10, 14, ..., 34, donde el incremento aritmético es 4 (10-6=4, 14-10=4), se escribe:

i = 2;
do {
i += 4;
print("i = "+i);
} while(i<34);

Y para generar y mostrar la serie 2187, 729, ..., 27, 9, 3, donde el decremento geométrico es 3 (27/9=3, 9/3=3), se escribe:

i = 3*2187;
do {
i /= 3;
print("i = "+i);
} while(i>3);

Estructura "for"

El algoritmo de esta estructura es el siguiente:

gojsGraph({divi, modelo: {"class":"go.GraphLinksModel","linkFromPortIdProperty":"fromPort","linkToPortIdProperty":"toPort","nodeDataArray":[{"category":"For","text":"condición","key":-7,"loc":"-331 -366"},{"category":"Connector","text":"...","key":-9,"loc":"-330.66128284365425 -481.8306414218272"},{"category":"Connector","text":"...","key":-11,"loc":"-330.48113427607365 -227.26482979932814"},{"category":"Process","text":"instrucciones","key":-12,"loc":"-330.51030195035906 -292.8850396771728"},{"category":"Process","text":"inicialización","key":-5,"loc":"-330.94564206155894 -424.44777113776695"},{"category":"Process","text":"incremento","key":-6,"loc":"-447.20083360517805 -366.1182833922996"}],"linkDataArray":[{"from":-9,"to":-5,"fromPort":"B","toPort":"T","points":[-330.66128284365425,-467.1348876145819,-330.66128284365425,-457.1348876145819,-330.66128284365425,-454.4246686858668,-330.94564206155894,-454.4246686858668,-330.94564206155894,-451.7144497571517,-330.94564206155894,-441.7144497571517]},{"from":-5,"to":-7,"fromPort":"B","toPort":"T","points":[-330.94564206155894,-407.1810925183822,-330.94564206155894,-397.1810925183822,-330.94564206155894,-393.8822203963066,-331,-393.8822203963066,-331,-390.58334827423096,-331,-380.58334827423096]},{"from":-7,"to":-12,"fromPort":"B","toPort":"T","visible":true,"points":[-331,-348.91665172576904,-331,-338.91665172576904,-331,-329.53418501116334,-330.5103019503591,-329.53418501116334,-330.5103019503591,-320.15171829655765,-330.5103019503591,-310.15171829655765],"text":"Si"},{"from":-12,"to":-6,"fromPort":"L","toPort":"B","points":[-381.8027824191091,-292.88503967717287,-391.8027824191091,-292.88503967717287,-447.20083360517805,-292.88503967717287,-447.20083360517805,-315.8683222250438,-447.20083360517805,-338.8516047729148,-447.20083360517805,-348.8516047729148]},{"from":-6,"to":-7,"fromPort":"R","toPort":"L","points":[-401.7869847282249,-366.1182833922996,-391.7869847282249,-366.1182833922996,-389.6427561275402,-366.1182833922996,-389.6427561275402,-366,-387.49852752685547,-366,-377.49852752685547,-366]},{"from":-7,"to":-11,"fromPort":"R","toPort":"T","visible":true,"points":[-284.50147247314453,-366,-274.50147247314453,-365.4496644776554,-244.75865009681382,-365.4496644776554,-244.75865009681382,-263.4140350127294,-330.4811342760736,-263.4140350127294,-330.4811342760736,-251.9605836065734,-330.4811342760736,-241.9605836065734],"text":"No"}]} })

Es decir que primero se ejecuta el sector de inicialización (donde normalmente se asignan valores a las variables) luego, si la condición es verdadera (Si), se ejecutan las instrucciones del ciclo, después se ejecuta el sector de incremento (donde normalmente se incrementan las variables) y el ciclo se repite mientras la condición es verdadera. El ciclo concluye cuando la condición es falsa.

Su sintaxis en Javascript, es:

for (inicialización; condición; incremento) { instrucciones; }

De manera similar al operador ternario, cuando el sector de inicialización o de incremento (o inclusive el de condición) constan de dos o más instrucciones, dichas instrucciones deben ser separadas con comas (no con puntos y comas).

Se prefiere for cuando se conoce el número de veces que el ciclo debe ser repetido o los límites están claramente establecidos. Por ejemplo los números impares comprendidos entre 5 y 17 pueden ser generados (y mostrados) de manera más clara y sencilla con esta estructura:

for (let i=5; i<=17; i+=2) print("i = "+i);

Igualmente, para generar y mostrar la serie 6, 10, 14, ..., 34, se escribe:

for (let i=6; i<=34; i+=4) {
print("i = "+i);
};

Y para generar y mostrar la serie 2187, 729, ..., 27, 9, 3, se escribe:

for (let i=2187; i>=3; i/=3) {
print("i = "+i);
};

Note que con la estructura for, el contador se declara con let en los 3 ejemplos, sin embargo, JavaScript no lanza el error informando que la variable ya fue declarada.

Sucede así, porque el sector de inicialización es parte de la estructura for, por lo tanto el contador pertenece a la estructura for y toda variable que se declara dentro de una estructura (cualquier esctructura) sólo es válida dentro de esa estructura (y en las estructuras que están dentro de esa estructura). Cuando la estructura finaliza, las variables declaradas dentro de la misma son eliminadas (el contador "i" deja de existir). En consecuencia, los 3 contadores "i" declarados en los ejemplos, son 3 contadores diferentes (a pesar de tener el mismo nombre) y al final ninguno de ellos existe.

Modificación de las estructuras iterativas

Cuando la lógica que resuelve el problema no corresponde al flujo estándar de una estructura iterativa, es posible modificarla.

Los modificadores disponibles en JavaScript (y en la mayoría de los lenguajes) son break y continue. El comando break interrumpe la ejecución del ciclo y sale de la estructura iterativa (sale del ciclo). El comando continue interrumpe la ejecución del ciclo actual y pasa a ejecutar el siguiente ciclo.

Por ejemplo, el siguiente código sólo muestra los números del 7 al 19 (en lugar del 7 al 31) porque cuando el contador es mayor a 19 (cuando se cumple el if) el comando break hace que el ciclo termine:

for (let i=7; i<=31; i+=3) { if (i>19) break; print(i); };

El código implementado con la estructura while es:

i = 7; while (i<=31) { if (i>19) break; print("i = "+i); i+=3; };

Y con la estructura do-while:

i = 7; do { if (i>19) break; print("i = "+i); i+=3; } while (i<3);

Por otra parte, el siguiente código sólo imprime los números impares del 3 al 13 (no todos los enteros del 3 al 13) porque cuando el contador es par (cuando se cumple el if) el comando continue hace que el programa salte al siguiente ciclo, con lo que el contador no se imprime y e incrementa su valor en 1 (al siguiente número impar).

for (let i=3; i<=13; i++) { if (i%2===0) continue; print("i = "+i); };

El código implementado con la estructura while, es:

i = 2; while (i<=13) { i++; if (i%2===0) continue; print("i = "+i); };

En este caso el contador debe ser incrementado al principio del ciclo, para que también incremente cuando se salta al siguiente ciclo. Por esa razón el contador debe ser iniciado en el número anterior al primer valor a ser generado.

De manera similar, el código implementado con la estructura do-while es:

i = 2; do { i++; if (i%2===0) continue; print("i = "+i); } while (i<=13);

Por otra parte, aunque no son modificadores propiamente, los comandos return y throw, interrumpen la ejecución de las estructuras iterativas (y de cualquier otra estructura): return interrumpen la ejecución y sale de la función (devolviendo el resultado), mientras que throw interrumpe la ejecución de la estructura y lanza un error que puede ser atrapado con catch, en la misma estructura, fuera de la estructura o fuera de la función.

La modificación más frecuente corresponde a la siguiente lógica:

gojsGraph({divi, modelo: {"class":"go.GraphLinksModel","linkFromPortIdProperty":"fromPort","linkToPortIdProperty":"toPort","nodeDataArray":[{"category":"Conditional","text":"condición","key":-5,"loc":"60.00000000000001 -272.7375317152156"},{"category":"Process","text":"instrucciones a","key":-2,"loc":"60.00000000000001 -335.0375675733699"},{"category":"Connector","text":"...","key":-8,"loc":"60 -392"},{"category":"Connector","text":"...","key":-4,"loc":"59.99999999999999 -118.4083489528922"},{"category":"Process","text":"instrucciones b","key":-6,"loc":"60.00000000000001 -195.37078137952224"}],"linkDataArray":[{"from":-8,"to":-2,"fromPort":"B","toPort":"T","points":[60,-377.3042461927547,60,-367.3042461927547,60,-364.8042461927547,60,-364.8042461927547,60,-362.3042461927547,60,-352.3042461927547]},{"from":-2,"to":-5,"fromPort":"B","toPort":"T","points":[60,-317.77088895398515,60,-307.77088895398515,60,-305.27088895398515,60,-305.27088895398515,60,-302.77088895398515,60,-292.77088895398515]},{"from":-5,"to":-6,"fromPort":"B","toPort":"T","visible":true,"points":[60,-252.7041744764461,60,-242.7041744764461,60,-232.67081723767654,60,-232.67081723767654,60,-222.63745999890702,60,-212.63745999890702],"text":"No"},{"from":-5,"to":-4,"fromPort":"R","toPort":"T","visible":true,"points":[124.49705505371094,-272.7375317152156,134.49705505371094,-272.7375317152156,163,-272.7375317152156,163,-157.79999923706055,60,-157.79999923706055,60,-143.1041027601375,60,-133.1041027601375],"text":"Si"},{"from":-6,"to":-2,"fromPort":"L","toPort":"L","points":[3.1953659057617188,-195.37078137952224,-6.804634094238281,-196.58539030829138,-49,-196.58539030829138,-49,-335.0375675733699,-6.4234771728515625,-335.0375675733699,3.5765228271484375,-335.0375675733699]}]} })

Es decir que las "instrucciones a" y las instrucciones "b" se repiten mientras la condición es falsa (No), pero la condición de finalización no está ni al principio (while) ni al final (do-while), sino al medio de las instrucciones.

Para implementar esta modificación, se crea un ciclo infinito y en su interior se escribe la condición de finalización, donde se emplea uno de los modificadores: break, return o throw, para salir del ciclo. Un ciclo infinito se crea simplemente escribiendo true en su condición (de esa manera la condición es siempre verdadera y en consecuencia, el ciclo se repite siempre):

while(true) { instrucciones a if (condición) [break|return|throw] instrucciones b }
do { instrucciones a if (condición) [break|return|throw] instrucciones b } while(true);

La estructura for cuenta con una sintaxis abreviada para crear un ciclo infinito:

for(;;) { instrucciones a if (condición) [break|return|throw] instrucciones b }

Tratamiento de errores

Hasta ahora los errores han sido atrapados y mostrados por la calculadora JavaScript. En la práctica, esos errores son atrapados en la función donde se generan o más frecuentemente, en las funciones que llaman a la función donde se generan (donde se lanzan con throw).

Los errores lanzados con throw son atrapado con catch, empleando la estructura try-catch-finally, que tiene la siguiente sintaxis:

try { instrucciones 1; } catch(e) { instrucciones 2; } finally { instrucciones 3; }

Donde instrucciones 1 son las instrucciones con las que se resuelve el problema; catch inicia el bloque donde se atrapa el error, siendo "e" el nombre de la variable temporal que guarda el error generado, instrucciones 2 son las instrucciones que se ejecutan cuando se atrapa el error y donde se puede emplear la variable temporal "e"; finally inicia el bloque de instrucciones (instrucciones 3) que se ejecutan siempre, sin importar si se ha lanzado o no un error.

La forma en que opera esta estructura es la siguiente: Si ocurre un error al ejecutar las instrucciones 1 o se lanza un error con throw, la ejecución se detiene y el programa salta al bloque catch, donde el error es atrapado y guardado en la variable "e", entonces se ejecutan las instrucciones 2.

Si no se produce ningún error al ejecutar las instrucciones 1, el bloque catch (las instrucciones 2) es ignorado.

Finalmente, sin importar si se ha producido o no un error, el programa salta al bloque finally y ejecuta las instrucciones 3.

Es importante tomar en cuenta que en la estructura try-catch-finally no se requiren los tres bloques, sólo es imprescindible el bloque try y uno de los bloques: o catch o finally, así es válida la forma:

try { instrucciones 1; } catch(e) { instrucciones 2; }

Y también la forma:

try { instrucciones 1; } finally { instrucciones 3; }

Se recalca que si existe el bloque finally, las instrucciones de ese bloque se ejecutan siempre, sin importar si se produce o no un error en el bloque try, mientras que las instrucciones del bloque catch sólo se ejecutan si se produce (o lanza) un error en el bloque try.

A continuación se resuelve algunos problemas empleando estas estructuras.

Ejemplos

Sumatoria de los primeros "n" números pares

En este ejemplo se elabora una función que calcula la sumataria de los primeros "n" números pares, así si "n" es 5, la función devuelve el resultado de: 2+4+6+8+10.

La lógica es simple y directa: Se inicia el acumulador "s" (la variable donde se guarda el resultado) en 0, luego en un ciclo iterativo se generan y suman los primeros "n" números impares. El límite superior del ciclo es 2*n, porque, si por ejemplo son 5 números pares el último número par es 10 (2*5), si son 15 números pares, el último número par es 30 (2*15), etc.

Empleando la estructura for (la estructura más adecuada para este tipo de problemas) la solución (como una función anónima) es:

sumPares = function(n) { let s = 0; for (let i=2; i<=2*n; i+=2) { s += i; } return s; };

Probando la función con algunos valores, se obtiene:

sumPares(0)
sumPares(5)
sumPares(12)

Por supuesto (aunque no es la mejor alternativa) puede ser resuelto también con la estructura while e inclusive con la estructura do-while, sin embargo, con esta estructura (debido a que sus instrucciones se ejecutan por lo menos una vez) para obtener el resultado correcto cuando "n" es 0, el contador debe ser iniciado en 0, no en 2 y debido a ello, la estructura se ejecuta una vez más:

sumPares2 = function(n) { let s = 0, i = 0; do { s += i; i += 2; } while(i<=2*n); return s; };

Con la cual se obtienen los mismos resltados que con la versión anterior.

Invertir los dígitos de un número

En este ejemplo se elabora una función que devuelve el número que se le manda con sus dígitos invertidos. Así, si el método es 12345 la función debe devolver 54321.

El problema se resuelve notando que en la división de un número entero entre 10, el resídulo es el último dígito del número, mientras que el cociente es el número sin ese último dígito, así para 12345, la división es:

\[ \begin{array}{c} 12345\\ (5) \end{array} \begin{array}{|c} \phantom{12}10\phantom{12} \\ \hline 1234 \end{array} \]

Si el cociente se vuelve a dividir entre 10, se obtiene el penúltio dígito dígito y el cociente disminuye en un dígito. Repitiendo el procedimiento hasta que el cociente es 0, se obtienen (uno a uno) todos los dígitos del número a invertir y en el orden en que deben estar en el número invertido:

\[ \def\arraystretch{1} \begin{array}{c} \begin{array}{c} 1234\\ (4) \end{array}\\ \\ \\ \\ \end{array} \begin{array}{c} \begin{array}{|c} \phantom{12}10\phantom{12} \\ \hline 123 \end{array}\\ (3)\\ \\ \\ \end{array} \begin{array}{c} \\ \begin{array}{|c} \phantom{12}10\phantom{12} \\ \hline 12 \end{array}\\ (2)\\ \\ \end{array} \begin{array}{c} \\ \\ \begin{array}{|c} \phantom{12}10\phantom{12} \\ \hline 1 \end{array}\\ (1)\\ \end{array} \begin{array}{c} \\ \\ \\ \begin{array}{|c} \phantom{12}10\phantom{12} \\ \hline 0 \end{array}\\ \end{array} \]

Entonces, para obtener el número con los dígitos invertidos, simplemente se deben añadir los residuos (uno a uno) a un acumulador. Sin embargo, antes de añadir los residuos, es necesario multiplicar el acumulador por 10, para añadir un 0 al final del número, de manera que el residuo añadido reemplace a ese 0. Así, para 12345, siendo "ni" el acumulador (inicialmente igual a 0) en la primera iteración se obtiene: ni = ni*10+5 = 0*10+5 = 0+5 = 5; en la segunda: ni = ni*10+4 = 5*10+4 = 50+4 = 54; en la tercera: ni = ni*10+3 = 54*10+3 = 540+3 = 543; en la cuarta: ni = ni*10+2 = 543*10+2 = 5430+2 = 5432 y en la quinta: ni = ni*10+1 = 5432*10+1 = 54320+1 = 54321 (que es el número con los dígitos invertidos).

El proceso concluye cuando el cociente es igual a 0. El cociente se calcula restando al número el residuo y dividiendo el resultado entre 10. Así el cociente de 12345 entre 10 es: (12345-5)/10 = 1234

Con las anteriores consideraciones, el código que resuelve el problema es:

invertirDigitos = n => { if (n%1!==0) throw "El número debe ser entero"; let ni = 0; while (n!==0) { ni= ni*10+n%10; n = (n-n%10)/10; } return ni; };

Con el cual se obtienen los resultados esperados:

invertirDigitos(12345)
invertirDigitos(-65432)
invertirDigitos(8)
invertirDigitos(23.456)

Número sin su primer dígito

En este ejemplo se programa una función que devuelve el número desde el cual es llamado, sin su primer dígito. Así si el número es 12345, la función debe devolver 2345.

Si el número es 56, sin su primer dígito es 6 y ese resulado se obtiene calculando su cociente entre 10 (56%10 = 6). Si el número es 784, sin su primer dígito es 84 y ese resultado se obtiene calculando su cocientre entre 100 (784%100 = 84). Entonces, el número sin su primer dígito es el residuo del número entre el múltiplo de 10 que tiene el mismo número de dígitos que el número.

Por lo tanto el problema se reduce a calcular ese múltiplo. El múltiplo se obtiene en un ciclo iterativo que se repite una vez menos que el número de dígitos del número y en cada repetición se multiplica el acumulador "m" (que se inicia en 1) por 10.

Para que el ciclo se repita una vez menos que el número de dígitos del número, debe ser repetido mientras que el valor absoluto de su división entre el múltiplo "m" sea mayor o igual a 10, es decir mientras tenga más de un dígito. Se debe trabajar con el valor absoluto porque si el número es negativo, la condición sería falsa desde la primera iteración.

La función que resuelve el problema, tomando en cuenta el anterior razonamiento, es:

function spd(n) {   if (n%1!==0) throw "El número debe ser entero";   let m = 1;   while (Math.abs(n/m)>=10) m *= 10;   return n%m; };

Con el cual se obtienen los resultados esperados:

spd(12345)
spd(-45678)
spd(7)
spd(45.67)

Raíz cuadrada

En este ejemplo se programa una función que recibe un número "n" y devuelve la raíz cuadrada de ese número.

Numéricamente, la raíz cuadrada se calcula con al ecuación deducida con el método de Newton - Raphson:

\[ x_2 = \dfrac{\left( x_1 + \dfrac{n}{x_1}\right)}{2} \]

Donde x1 es un valor que se sume para la solución (para la raíz cuadrada) y x2 es el valor calculado con la ecuación para ese valor asumido. La solución de este tipo de ecuaciones es iterativa: a) Se asume un valor para la solución (para x1); b) Con x1 y la ecuación de Newton-Raphson, se calcula el valor de x2; c) Se compara x1 con x2; d) Si x1 y x2 son iguales en un determinado número de dígitos (normalmente entre 9 y 14 dígitos), el proceso concluye, siendo la solución el valor de x2; e) Si no son iguales en el número de digitos establecido, x1 toma el valor de x2 (x1=x2) y el proceso se repite desde el paso "b".

Para determinar si dos números: x1 y x2, son iguales en "n" o más dígitos (precisión), debe ser verdadera (true) la siguiente expresión relacional:

\[ \left| \dfrac{x_1}{x_2}-1\right| < 1 \times 10^{-n} \]

La función programada siguiendo el algoritmo descrito, terminando las iteraciones cuando x1 y x2 son iguales en 12 dígitos (cuando la precisión es 12), es:

function rCuad(n, x1=1.1) { if (n<0) throw "El número debe ser positivo"; if (n===0 || n===1) return n; let x2; while (true) { x2 = (x1+n/x1)/2; if (Math.abs(x1/x2-1)<1e-12) break; x1 = x2; } return x2; };

Con la cual se obtienen los resultados esperados:

rCuad(2)
rCuad(9.34)
rCuad(1)
rCuad(-7.45)

Como se puede ver, la solución se ha implementado en un ciclo infinito (creado con while y break). En la función se valida que el número sea positivo (porque los números negativos tienen soluciones complejas) y si el "n" es cero o uno, se devuelve directamente el resultado (porque la raíz de 0 es 0 y la de 1 es 1).

El valor asumido x1 es un parámetro por defecto con un valor inicial igual a 1.1. Se ha elegido ese valor, porque con el mismo, casi siempre, se encuentra la solución (la raíz). Sin embargo, como es un parámetro por defecto, se puede mandar otros valores (más cercanos a la solución) para obtener el resultado en un menor número de iteraciones.

Al ser un ciclo infinito, puede ser programado también con do-while o for y salir del ciclo con return o throw:

Así, con la estructura do-while y el comando return, la solución, es:

rCuad2 = function(n, x1=1.1) { if (n<0) throw "El número debe ser positivo"; if (n===0 || n===1) return n; let x2; do { x2 = (x1+n/x1)/2; if (Math.abs(x1/x2-1)<1e-12) return x2; x1 = x2; } while (true); };

Y con la estructura for y el comando throw (atrapando el error con try-catch), la solución es:

var rCuad = (n, x1=1.1) => { if (n<0) throw "El número debe ser positivo"; if (n===0 || n===1) return n; try { let x2; for (;;) { x2 = (x1+n/x1)/2; if (Math.abs(x1/x2-1)<1e-12) throw x2; x1 = x2; } } catch(e) { return e; } };

En este caso, el comando throw lanza como error la solución (la raíz: x2). Esa solución es atrapada con catch en la variable "e", por esa razón, en el bloque catch se devuelve como resultado de la función el valor de la variable "e".

Si bien en los ejemplos y ejercicios de la asignatura se emplea throw, try-catch (o try-finally ) para romper estructuras y atrapar resultados, en la práctica se emplean para lanzar, atrapara y tratar errores. Sin embargo, el resolver este tipo de ejercicios ayuda a comprender su funcionamiento, por lo que resultan de utilidad práctica.

Número primo

En este ejemplo se programa una función que recibe un número entero "n" y devuelve true si es un número primo y false en caso contrario.

Un número es primo si sólo es divisible entre 1 y entre sí mismo, exceptuando el número 1 que por definición no es primo.

Para determinar si un número es o no primo, se debe comprobar que no sea divisible entre 2, 3, ..., etc., hasta la mitad del número que se está probando, porque ningún número es divisible entre un número mayor a su mitad (más precisamente mayor a su raíz cuadrada).

Si el número es divisible entre cualquiera número menor o igual a su mitad, se sabe que no es primo (false), caso contrario (si se llega hasta la mitad del número) se sabe que es primo (true).

Entonces el problema se resuelve generando los divisores "i" (2, 3, 4, ....) hasta la mitad de "n". Se verifica si "n" es divisible entre los divisores "i" que se van generando. Si "n" es divisible entre uno de los divisores "i", se terminan inmediatamente las iteraciones (porque ya se sabe que el número no es primo). Si terminan las iteraciones (si "i" llega a la mitad de "n") se sabe que el número es primo.

El código que resuelve el problema, siguiendo el razonamiento del párrafo anterior, es:

esPrimo = n => { if (n%1!==0) throw "El número debe ser entero"; if (n<0) throw "El número debe ser positivo"; if (n<2) return false; for (let i=2; i<=n/2; i++) if (n%i===0) return false; return true; };

Con el cual se obtienen los resultados esperados:

esPrimo(7)
esPrimo(8)
esPrimo(21)
esPrimo(157)
esPrimo(159)
esPrimo(67.32)
esPrimo(-121)

Fibonacci de un número

En este ejemplo se programa una función que recibe un número entero "n" y devuelve el Fibonacci de ese número.

La ecuación de definición del Fibonacci, es:

\[ \begin{aligned} F_n &= F_{n-1}+F_{n-2}\\[2mm] F_1 &= 1\\[2mm] F_2 &= 1 \end{aligned} \]

Como se conocen los valores del primer y segundo Fibonacci, el cálculo comienza a partir del tercero: el Fibonacci de 3 (F2+F1), luego el de 4 (F3+F2) y así sucesivamente. Por ejemplo, con las siguientes operaciones se calcula el Fibonacci de 8:

\[ \begin{alignedat}{1} F_3 &= F_2+F_1 = 1+1 = 2\\ F_4 &= F_3+F_2 = 2+1 = 3\\ F_5 &= F_4+F_3 = 3+2 = 5\\ F_6 &= F_5+F_4 = 5+3 = 8\\ F_7 &= F_6+F_5 = 8+5 = 13\\ F_8 &= F_7+F_6 = 13+8 = 21 \end{alignedat} \]

De estas operaciones se deduce que el problema se resuelve repitiendo el proceso desde 3 hasta "n". En cada repetición se calculan sucesivamente los Fibonacci de 3, 4, 5, ..., hasta "n". Pero como la función solo debe devolver el último Fibonacci calculado, únicamente se guarda ese valor y los dos anteriores (porque son necesarios para calcular el siguiente Fibonacci) como se muestra en las siguientes operaciones, donde se vuelve a calcular el Fibonacci de 8, pero conservando sólo los dos últimos valores:

\[ \begin{array}{l | l | l} F_3 = F_2+F_1 = 1+1 = 2 & F_1=F_2=1 & F_2=F_3=2\\ F_4 = F_2+F_1 = 2+1 = 3 & F_1=F_2=2 & F_2=F_3=3\\ F_5 = F_2+F_1 = 3+2 = 5 & F_1=F_2=3 & F_2=F_3=5\\ F_6 = F_2+F_1 = 5+3 = 8 & F_1=F_2=5 & F_2=F_3=8\\ F_7 = F_2+F_1 = 8+5 = 13 & F_1=F_2=8 & F_2=F_3=13\\ F_8 = F_2+F_1 = 13+8 = 21 \end{array} \]

La función que resuelve el problema, tomando en cuenta las consideraciones anteriores, es:

fibonacci = function(n) { if (n%1!==0 || n<=0) throw "El número debe ser entero positivo"; if (n<3) return 1; let f3, f1=1, f2=1, i=3; while (true) { f3 = f2+f1; if (i===n) break; f1 = f2; f2 = f3; i++; } return f3; };

Con el cual se obtienen los resultados esperados:

fibonacci(5)
fibonacci(8)
fibonacci(15)
fibonacci(25)
fibonacci(75)
fibonacci(4.56)
fibonacci(0)
fibonacci(-34)

Note que se ha empleado un ciclo infinito, porque una vez calculado el Fibonacci de "n" (cuando el contador "i" es igual a "n") se debe salir del ciclo, porque ya se tiene la solución y no es lógico continuar con un cambio de variables e incremento del contador, cuando esos valores no se emplean nunca.

Legendre enésimo de un número

En este ejemplo se programa una función que recibe un número real "x" y un entero positivo "n" y devuelve el legendre enésimo "n" del número real "x".

La ecuación de definición del Legendre, es:

\[ \begin{aligned} \text{Le}_n(x) &= \left( 2-\dfrac{1}{n}\right)\cdot x\cdot \text{Le}_{n-1}(x)-\left( 1-\dfrac{1}{n}\right)\cdot\text{Le}_{n-2}(x)\\ \text{Le}_0(x) &= 1\\ \text{Le}_1(x) &= x \end{aligned} \]

Aunque aparentemente este es un problema muy diferente al del Fibonacci, la lógica que resuelve el problema es esencialmente la misma, sólo que ahora la ecuación es diferente, se tiene una variable adicional (el número real "x") y el orden (el número entero "n") es parte de la ecuación.

En un ciclo que va desde 2 hasta el orden "n", se calcula en cada iteración los Legendre de 2, 3, 4, etc, hasta llegar al Legendre de "n". Al igual que en el Fibonacci, sólo es necesario conservar el último valor calculado y los dos anteriores, sin embargo, dado que "n" forma parte de la ecuación, para facilitar la programación de la ecuación y evitar confusiones, en lugar de recibir el orden en la variable "n" se recibe en la variable "m", mientras que "n" se emplea para el contador del ciclo.

La función que resuelve el problema, tomando en cunenta las anteriores consideraciones, es:

function legendre(x, m) { if (m%1!==0 || m<0) throw "El orden debe ser entero positivo"; try { if (m===0) return 1; if (m===1) return x; let le2, le0=1, le1=x, n=2; while (true) { le2 = (2-1/n)*x*le1-(1-1/n)*le0; if (n===m) throw le2; le0 = le1; le1 = le2; n++; } } catch(e) { return e; } }

Con el cual se obtienen los resultados esperados:

legendre(1.3, 2)
legendre(1.5, 3)
legendre(3.9, 7)
legendre(2.1, 0)
legendre(13.1, 1)
legendre(6.1, 4.5)
legendre(1.1, -3)