Como jogar golfe com recursão
A recursão, embora não seja a opção mais rápida, é muitas vezes a mais curta. Geralmente, a recursão é mais curta se a solução puder ser simplificada para uma parte menor do desafio, especialmente se a entrada for um número ou uma sequência. Por exemplo, se f("abcd")
pode ser calculado a partir de "a"
e f("bcd")
, geralmente é melhor usar recursão.
Tomemos, por exemplo, fatorial:
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
Neste exemplo, a recursão é obviamente muito menor do que qualquer outra opção.
Que tal a soma dos códigos:
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
Essa é mais complicada, mas podemos ver que, quando implementada corretamente, a recursão economiza 4 bytes .map
.
Agora vamos ver os diferentes tipos de recursão:
Pré-recursão
Este é geralmente o tipo mais curto de recursão. A entrada é dividida em duas partes a
e b
, e a função calcula algo com a
e f(b)
. Voltando ao nosso exemplo fatorial:
f=n=>n?n*f(n-1):1
Nesse caso, a
é n , b
é n-1 e o valor retornado é a*f(b)
.
Nota importante: Todas as funções recursivas devem ter uma maneira de parar de repetir quando a entrada é pequena o suficiente. Na função fatorial, isso é controlado com o n? :1
, ou seja, se a entrada for 0 , retorne 1 sem chamar f
novamente.
Pós-recursão
Pós-recursão é semelhante à pré-recursão, mas um pouco diferente. A entrada é dividida em duas partes a
e b
, e a função calcula algo com a
, depois chama f(b,a)
. O segundo argumento geralmente tem um valor padrão (ou seja f(a,b=1)
).
A pré-recursão é boa quando você precisa fazer algo especial com o resultado final. Por exemplo, se você deseja o fatorial de um número mais 1:
f=(n,p=1)=>n?f(n-1,n*p):p+1
Mesmo assim, no entanto, pós- nem sempre é mais curto do que usar pré-recursão dentro de outra função:
n=>(f=n=>n?n*f(n-1):1)(n)+1
Então, quando é mais curto? Você pode perceber que a pós-recursão neste exemplo requer parênteses em torno dos argumentos da função, enquanto a pré-recursão não. Geralmente, se as duas soluções precisarem de parênteses em torno dos argumentos, a pós-recursão será cerca de 2 bytes mais curta:
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 aqui retirados desta resposta )
Como encontrar a solução mais curta
Geralmente, a única maneira de encontrar o método mais curto é tentar todos eles. Isso inclui:
E essas são apenas as soluções mais comuns; a melhor solução pode ser uma combinação desses, ou até algo completamente diferente . A melhor maneira de encontrar a solução mais curta é tentar de tudo .