Executando verificação de segurança...
6

[DESAFIO] Como recriar o loop for usando recursividade ou outra forma de pensar?

Author: Ikidon


Será que você é capaz de recriar um loop for, sem precisar usar o for do javascript?

Um desafio simples, porém para mim foi bastante complicado pensar em um resultado, porque a cada vez que eu pensava em algo vinha um "porém", só que consegui criar, do meu jeito, então decidir vim propor esse desafio para mais pessoas, para ver soluções diferentes e interessantes, uma forma divertida e legal de descobrir soluções diferentes para o mesmo problema.

Então, topa o desafio?

Já irei deixar minha resolução aqui, podem usar como base, melhorar ou até recriar uma nova, deixe a sua imaginação brilhar. ^-^

function loop_for(initialization, condition, afterthought, callback) {
  callback(initialization)

  if(afterthought.method === "plus") {
    initialization = initialization + afterthought.increment
  } else if (afterthought.method === "minus") {
    initialization = initialization - afterthought.increment
  }


  const mergeCondition = () => {
    if(condition.method === "<") {
      return initialization < condition.condition
    } else if(codition.method === ">") {
      return initialization > condition.condition
    } else if(codition.method === "==") {
      return initialization == condition.condition
    } else if(codition.method === "===") {
      return initialization === condition.condition
    } else if(codition.method === "!=") {
      return initialization != condition.condition
    } else if(codition.method === "!==") {
      return initialization !== condition.condition
    }
  }

  const resultCondition = mergeCondition()

  if(resultCondition) {
    loop_for(initialization,condition,afterthought,callback)
  } else {
    return
  }
}


loop_for(index = 0, {method: "<", condition: "5"}, {method: "plus",increment: 1}, (index) => {
  console.log('loop ' + index)
})

Sugestão de editores online, caso queiram

Carregando publicação patrocinada...
5

Cara, eu amo o paradigma funcional e o teu desafio é super interessante. Sei que em linguagens funcionais tu consegue ir bem longe só com funções e recursão, até em Haskel, se não me engano, mas eu tentei fazer o seu desafio só usando funções puras e eu justamente passei pelo o que você disse, os "poréns" apareceram algumas vezes, mas consegui chegar num resultado razoavel e acho que da pra ir mais longe só com essa base.

Só pra contexto, uma função pura é uma função muito mimada e fresca, ela recebe só um parâmetro, fazem só uma coisa e retornam só um valor/estrutura. O primeiro problema é passar mais de um parâmetro porque um for loop que se preze precisa, no mímino, saber onde começar e onde parar. A gambiarra é fazer uma função que retorna outra função. Por exemplo, uma função de soma assim poderia ser definida como S = (a) => (b) => a + b; -- perceba como a função S na real ta retornando outra função, e essa função têm o parâmetro b --, e a emplementação seria S (1) (2), isso retornaria 3. Deu pra entender? É meio confuso mas é legal depois que tu entende.

O segundo problema, e o mais complicado, é fazer a função fazer duas coisas em sequência: somar um ao acumulador (a variável que a função vai ficar somando 1 pra saber quando parar) e executar a função que o usuário passou como parâmetro também (isso seria tipo o bloco entre {} do for loop).

Supondo que eu tenha uma função A e a função B que precisam ser executadas juntas, a minha primeira solução seria usar o operador & do JavaScript, eu achava que ele era tipo o & do Bash que te deixa executar dois comandos em paralelo, mas eu me enganei, ele mexe com o binário dos valores e tals... Eu não entendi direito kkkkkkk. Enfim, não parecia certo usar ele, pesquisei um pouco e descobri que dá pra fazer isso com (A(), B()), mas eu também não entendi direito como isso funciona, no Python essa expressão retornaria uma tupla, mas no JavaScript isso retorna o valor que a função B retornaria... De qualquer forma, achei um tanto quanto trapaça pros meus objetivos.

A minha solução também não é das melhores, mas parece estar certa tecnicamente. Pra executar essas duas funções eu posso fazer [A(), B()], e essa experssão vai retornar um array com o retorno de cada umas dessas duas funções individualmente, nada demais. E ainda, tecnicamente, o Array é apenas uma estrutura de dados como as outras, por isso acho que eu não tenha infringido nenhuma regra.

Ok, dito isso tudo a minha solução para uma função F que se comporta como um for loop com os parâmetros s de start (ela será o accumulator da minha função, você também pode entender assim), e de end (o número que o for loop vai usar pra saber quando parar) e T que será a função que o próprio usuário irá passar, ela recebe um parâmetro só e esse parâmetro vai ser o valor do s atual, o famoso iterator -- Na minha cabeça esse T significa target.

F = (s) => (e) => (T) =>
    s >= e ? null : [T (s), F (s + 1) (e) (T)];

F (0) (10) ((i) =>
    console.log(`contador: ${i}`));

Apesar de printar bunitinho essa string com o contador na ordem, ela ainda está retornando uma matríz multidimencional, onde o primeiro valor é o retorno da função que o usuário passou e o segundo valor é outro Array do mesmo jeito recursivamente, a profundiade dessa matríz será a diferença entre o s e o e, só por curiosidade. Além do que, essa estrutura acaba ocupando espaço em memória, mas acho que isso não tenha problema nenhum porque a própria recursão vai ocupar espaço na stack, então isso não iria funcionar pra loops muito grandes de uma forma ou de outra.

Essa matríz consegue até ser útil do sei jeito, se você ir mais longe nesse coisa de programação funcional. Por exemplo, se você quiser um array que conta de 10 a 99 você pode iterar por esses números e passar uma função que não faz nada a não ser retornar o índice atual, depois você torna essa matríz num Array plano e tira o null no final.

F(10)(100)((i) => i).flat(Infinity).filter((i) => i !== null);

Mas a verdade é que essa função poderia ser melhor, o próprio usuário poderia passar mais duas funções extras nos parâmetros pra ele poder ter controle de como a comparação que vai terminar o loop vai funcionar ou como ele deve somar/subtrair o valor do s -- coisa que é o que sua solução faz, então parabéns!

Isso ficaria mais complicadinho, mas acho que vale a pena tentar mostrar isso:

F = (s) => (e) => (T) => (C) => (A) =>
    C (s) (e) ? null : [T (s), F (A (s)) (e) (T) (C) (A)];

F (0) (10) ((i) =>
    console.log(`contador: ${i}`)) ((s) => (e) =>
        s >= e) ((s) => s + 1);

Terrivelmente complicado, mas assim... Hummmmmm.... Enquanto estive escrevendo esse comentário percebi que... Talvez... Eu esqueci que é possível concatenar dois Arrays com a sintaxe de ... do JavaScript. Ok, vo tentar reescrever isso com essa estratégia e retornar um Array já plano, isso seria mil vezes mais fácil de trabalhar.

Hell yeah, eu tava certo. Era só eu ter pensado mais um pouquinho que eu chegava numa solução bem mais clean:

F = (s) => (e) => (T) =>
    s >= e ? [] : [T s), ...F s + 1) e) (T)];

F (10) (100) ((i) => i); 

Nesse exemplo a função F ta retornando os números de 10 à 99 como esperado, mas aqui não precisa rodar nenhuma função extra pra limpar o Array. Realmente muito melhor. Agora, só pra deixar ela completinha, vo por as funções extras pro usuário ter controlde de como o loop irá funcionar.

F = (s) => (e) => (T) => (C) => (A) =>
    C (s) (e) ? [] : [T (s), ...F (A (s)) (e) (T) (C) (A)];

F (10) (100) ((i) => i) ((i) => (e) => i >= e) ((i) => i + 1);

Agora estou satisfeito. Ufa... Mas ainda assim, se alguém que estiver lendo esse meu mini artigo não estiver entendendo nada, vale a pena reescrever essa minha solução final de um jeito mais procedural, mais fáicl de entender que não use tanto essa sintaxe estranha do JavaScript -- Que é algo bem mais próximo de Elixir ou qualquer linguagem funcional da moda eu acho.

/**
 * Função que simula um *for loop* mas usando apenas recursão, nem vale a pena usar
 * essa função em contexto nenhum, é só um exercício legal tentar entender ela.
 *
 * @param {start} number: Índice inicial, esse valor será encrementado na recursão.
 * @param {end} number: Índice final, será usado pra saber quando o loop precisa parar.
 * @param {usercb} callback(Any): *User callback*, será executada a cada iteração.
 * @param {compare} callback(bool): Compara o índice atual e final pra paarar o loop.
 * @param {update} callback(number): Atualiza o índice atual pra próxima iteração.
 *
 * @returns Array<Any>: Resultados acumulados da função `usercb`.
 */
function each(start, end, usercb = (i) => i, compare = (i, end) => i >= end,
              update = (i) => i + 1) {
    if (compare(start, end)) {
        return [];

    } else {
        const res = usercb(start);
        const updated = update(start);
        const next = each(updated, end, callback, compare, update);

        return [res, ...next];
    }
}

console.log(each(10, 99));  // exemplo anterior, array de 10 à 99
console.log(each(0, 10, (i) => `contador: ${i}`));  // callback customizado
console.log(each(5, 0, (i) => i, (i, e) => i < e, (i) => i - 1));  // conta de 5 à 0
2

Yo yo, caraca, extremamente completinha, de fato, estou realmente surpreso com sua solução, se preocupou até com alguns mínimos detalhes, buscou direitinho sobre o máximo que conseguiria fazer usando o que o JS disponibiliza, e buscando se manter na ideia inicial do desafio. Parabéns ^-^

4

Usando callbacks fica mais simples:

const _for = (step) => (cond, cb, init = 0) => {
  const iterate = (i) => {
    if (cond(i)) return
    cb(i)
    iterate(step(i))
  }
  iterate(init)
}

const times = _for(i => i + 1)
const until = n => i => i >= n

times(until(10), i => times(until(10), j => console.log(i, j)))
2

Boa, a ideia de usar callbacks real ajuda mt, usei bastante pra compor a minha solução aki:

F = (s, e, T=(i)=>i, C=(i,e)=>i>=e, A=(i)=>++i) =>
	C(s, e) ? [] : [T(s), ...F(A(s), e, T, C, A)];

console.log(F(0,5));
console.log(F(100, 0, (i)=>i**2, (i,e)=>i<=e, (i)=>i-10));

Eu dei mais detalhes no outro comentário ali, é massa q chegamos em soluções parecidas em essência. O meme eh q a minha devolve um array no final, o que não eh mt necessário. Good job maninho(a)! 💪

1

Yo yo, a sua resolução é bastante interessante, gostei da implementação e da forma que fez a sua solução para esse desafio, mas senti falta de alguns pontos, como:

  1. Explicação de como chegou nesse resultado.
  2. Explicação de como funciona de fato essa função.

Só senti falta desses pontos, teria sido mais interessante os ter adicionado, mas realizou o desafio e de uma forma criativa, meus parabéns. ^-^

2

Nao sou muito bom em Javascript mas eu faria assim:

// ### Meu for loop em funcao recursiva ### //
function for_loop(
  action = (i) => {},
  index = 0,
  proceed = (i) => true,
  increment = (i) => i + 1
) {
  if (proceed(index)) {
    action(index);
    for_loop(action, increment(index), proceed, increment);
  }
}

Aqui algums exemplos:


// ### Exemplo 1 ### //

// For loop tradicional
for(let i = 0; i <= 5; i++) {
  console.log(`Primeiro exemplo ${i}`)
}

// For loop em funcao
for_loop(
  i => console.log(`Primeiro exemplo ${i}`),
  0,
  i => i <= 5
)

// ### Exemplo 2 ### //

// For loop tradicional
for(let i = 0; i <= 5; i+=2) {
  console.log(`Segundo exemplo ${i}`)
}

// For loop em funcao
for_loop(
  i => console.log(`Segundo exemplo ${i}`),
  0,
  i => i <= 5,
  i => i += 2
)
1

Yo yo, de antemão, parabêns ^-^, conseguiu criar uma solução bem clean e fácil de interpretar, porém, como mencionado anteriormente sobre a resolução do ayni, sintir falta de uma explicação sobre a função, e como foi seu percuso para chegar nela.