Cómo jugar al golf con recursión
La recursión, aunque no es la opción más rápida, es a menudo la más corta. En general, la recursividad es más corta si la solución puede simplificarse a una parte más pequeña del desafío, especialmente si la entrada es un número o una cadena. Por ejemplo, si f("abcd")
se puede calcular a partir de "a"
y f("bcd")
, generalmente es mejor usar la recursividad.
Tomemos, por ejemplo, factorial:
n=>[...Array(n).keys()].reduce((x,y)=>x*++y,1)
n=>[...Array(n)].reduce((x,_,i)=>x*++i,1)
n=>[...Array(n)].reduce(x=>x*n--,1)
n=>{for(t=1;n;)t*=n--;return t}
n=>eval("for(t=1;n;)t*=n--")
f=n=>n?n*f(n-1):1
En este ejemplo, la recursividad es obviamente mucho más corta que cualquier otra opción.
¿Qué tal la suma de los códigos de barras:
s=>[...s].map(x=>t+=x.charCodeAt(),t=0)|t
s=>[...s].reduce((t,x)=>t+x.charCodeAt())
s=>[for(x of(t=0,s))t+=x.charCodeAt()]|t // Firefox 30+ only
f=s=>s?s.charCodeAt()+f(s.slice(1)):0
Este es más complicado, pero podemos ver que cuando se implementa correctamente, la recursión ahorra 4 bytes .map
.
Ahora veamos los diferentes tipos de recursividad:
Pre-recursividad
Este suele ser el tipo de recursión más corto. La entrada se divide en dos partes a
y b
, y la función calcula algo con a
y f(b)
. Volviendo a nuestro ejemplo factorial:
f=n=>n?n*f(n-1):1
En este caso, a
es n , b
es n-1 y el valor devuelto es a*f(b)
.
Nota importante: Todas las funciones recursivas deben tener una forma de detener el recursivo cuando la entrada es lo suficientemente pequeña. En la función factorial, esto se controla con n? :1
, es decir, si la entrada es 0 , devuelve 1 sin f
volver a llamar .
Post-recursión
La post-recursión es similar a la pre-recursión, pero ligeramente diferente. La entrada se divide en dos partes a
y b
, y la función calcula algo con a
, luego llama f(b,a)
. El segundo argumento generalmente tiene un valor predeterminado (es decir f(a,b=1)
).
La pre-recursión es buena cuando necesitas hacer algo especial con el resultado final. Por ejemplo, si desea el factorial de un número más 1:
f=(n,p=1)=>n?f(n-1,n*p):p+1
Aun así, sin embargo, post- no siempre es más corto que usar pre-recursión dentro de otra función:
n=>(f=n=>n?n*f(n-1):1)(n)+1
Entonces, ¿cuándo es más corto? Puede notar que la post-recursión en este ejemplo requiere paréntesis alrededor de los argumentos de la función, mientras que la pre-recursión no. Generalmente, si ambas soluciones necesitan paréntesis alrededor de los argumentos, la post-recursión es alrededor de 2 bytes más corta:
n=>!(g=([x,...a])=>a[0]?x-a.pop()+g(a):0)(n)
f=([x,...a],n=0)=>a[0]?f(a,x-a.pop()+n):!n
(Programas tomados de esta respuesta )
Cómo encontrar la solución más corta
Por lo general, la única forma de encontrar el método más corto es probarlos todos. Esto incluye:
- Bucles
.map
(para cadenas, ya sea [...s].map
o s.replace
; para números, puede crear un rango )
- Matriz de comprensiones
- Pre-recursión (a veces dentro de otra de estas opciones)
- Post-recursión
Y estas son solo las soluciones más comunes; La mejor solución podría ser una combinación de estos, o incluso algo completamente diferente . La mejor manera de encontrar la solución más corta es probarlo todo .