1

I tried to do a recursive function with clousure, but dosent work.

I tried to add each single number from array to finally return [1,2,3,4,5,6,7,8] but just return me [1,2,5] something weird is I put a console.log inside of else it's print me all numbers.

Could you explain me why happens this

var array = [1, 2, [3, 4], 5, [6, [7, 8]]]; // --> 1,2,3,4,5,6,7,8
function recursiveArrayPrint(array) {
  let resultado = []
  return function iterator() {
    for (let i = 0; array.length > i; i++) {
      if (Array.isArray(array[i])) {
        recursiveArrayPrint(array[i])()
      } else {
        console.log(array[i])
        resultado.push(array[i])
      }
    }
    return resultado
  }
}
const test = recursiveArrayPrint(array);
console.log('test: ',test())

enter image description here

7
  • 1
    You aren't doing anything with the return value when you call the function recursively Commented Mar 25, 2021 at 22:11
  • 2
    why don't you simply use array.flat method ? Commented Mar 25, 2021 at 22:16
  • 1
    because I would like to do this exercise by recursive and understand why doesn't work Commented Mar 25, 2021 at 22:27
  • 2
    Try resultado.push(...recursiveArrayPrint(array[i])()); where you call it inside the loop Commented Mar 25, 2021 at 22:30
  • 1
    You want to call iterator recursively (and give it a parameter for that), not recursiveArrayPrint. Commented Mar 25, 2021 at 23:39

5 Answers 5

2

the bug in your program

  1. resultado is declared inside of recursiveArrayPrint
  2. inside iterator you call recursiveArrayPrint, which creates a new resultado each time
  3. only the outermost resultado is returned
function recursiveArrayPrint(array) {
  let resultado = []                         // #1
  return function iterator() {
    for (let i = 0; array.length > i; i++) {
      if (Array.isArray(array[i])) {
        recursiveArrayPrint(array[i])()      // #2
      } else {
        console.log(array[i])
        resultado.push(array[i])
      }
    }
    return resultado                         // #3
  }
}

how to fix

function recursiveArrayPrint(array) {
                                               // no need for resultado
  function* iterator() {                       // use generator
    for (let i = 0; array.length > i; i++) {
      if (Array.isArray(array[i])) {
        yield *recursiveArrayPrint(array[i])   // yield
      } else {
        yield array[i]                         // yield, not console.log
                                               // no need for resultado
      }
    }                                          
                                               // no need for resultado
  }
  return iterator()
}

const arr = [1, 2, [3, 4], 5, [6, [7, 8]]]

for (const x of recursiveArrayPrint(arr))
  console.log(x)

1
2
3
4
5
6
7
8

we can do better

  1. recursiveArrayPrint is not a good name. We've disentangled the console.log effect from the iteration and the caller is free to decide what to do with the output. We can just call it recursiveArray instead
  2. Inner function iterator is a bit useless now. No need for this abstraction anymore
  3. for loop with i is prone to off-by-one errors. We can write this in a clearer way using for..of loop

function* recursiveArray(array)
{ for (const x of array)
    if (Array.isArray(x))
      yield *recursiveArray(x)
    else
      yield x
}

const arr = [1, 2, [3, 4], 5, [6, [7, 8]]]

for (const x of recursiveArray(arr))
  console.log(x)

If you are hoping to get the flattened array as a return value, JavaScript provides Array.from which turns any iterable into an array -

console.log(Array.from(recursiveArray(arr)))
[1,2,3,4,5,6,7,8]

not just for arrays

  1. We can rename recursiveArray to traverse and it can operate as a generic function
  2. Instead of assuming the first input is an array, we check isArray first and only then run the for..of loop. Otherwise simply yield the input

function* traverse(t)
{ if (Array.isArray(t))
    for (const x of t)
      yield *traverse(x)
  else
    yield t
}

const arr = [1, 2, [3, 4], 5, [6, [7, 8]]]

for (const x of traverse(arr))
  console.log(x)
 

1
2
3
4
5
6
7
8

Writing robust functions will help you in the long run. traverse could easily be expanded to step through Objects now or other iterables. Now when non-array input is given, instead of an error, we get a sensible result -

for (const x of traverse(123))
  console.log(x)
123
Sign up to request clarification or add additional context in comments.

1 Comment

The king of generators strikes again! As always, a really nice answer.
1

So what the recursiveArrayPrint function does, is that it creates a variable and returns a function that has closure over this variable. I think you get this part. The part that might confuse you, is that when you encounter an array, you call the recursiveArrayPrint function again, which will only create a new variable and return a new function that has closure over this variable, but this is in an entirely different scope.

It might make more sense to look at it this way.

var array = [1, 2, [3, 4], 5, [6, [7, 8]]]; // --> 1,2,3,4,5,6,7,8
function recursiveArrayPrint(array) {
  let resultado = []
  return function iterator() {
    for (let i = 0; array.length > i; i++) {
      if (Array.isArray(array[i])) {

        // This is where a new closure is created, but with reference to another variable
        let resultado1 = []
        return function iterator1() {
          for (let i = 0; array.length > i; i++) {
            if (Array.isArray(array[i])) {
              // This is where a new closure is created, but with reference to another variable
            }
            return resultado1
          }
        } 
      } else {
        console.log(array[i])
        resultado.push(array[i])
      }
    }
    return resultado
  }
}
const test = recursiveArrayPrint(array);
console.log('test: ',test())

I think what you intend to do is something more like this.

var array = [1, 2, [3, 4], 5, [6, [7, 8]]]; // --> 1,2,3,4,5,6,7,8
function recursiveArrayPrint() {
  let resultado = []
  return function iterator(array) {
    for (let i = 0; array.length > i; i++) {
      if (Array.isArray(array[i])) {
        iterator(array[i])
      } else {
        console.log(array[i])
        resultado.push(array[i])
      }
    }
    return resultado
  }
}
const test = recursiveArrayPrint();
console.log('test: ',test(array))

1 Comment

SyntaxError: unexpected token: keyword 'else'
1

I think the main problem in your code is not just a difficulty in mastering the different elements of the JavaScript language or simply knowing how to play with the scope of variables for example.

No, it's just algorithmic, and that's arguably the hardest thing to do. We all fell into the trap of doing complicated things by trying in vain to make them simple. It takes time to experience, to practice incessantly to accustom our brain to handling concepts in order to know how to put them together without fail.

Recursive solution:

const arr1 = [1, 2, [3, 4], 5, [6, [7, 8]]];

function recursiveflat(arr)
  {
  let res = []
  for (let el of arr)
    if (Array.isArray(el)) res.push(...recursiveflat(el) )
    else                   res.push(el)
  return res
  }

console.log( JSON.stringify( recursiveflat( arr1 )))
.as-console-wrapper {max-height: 100%!important;top:0}

The idea of a closure as a recursive function seems incompatible to me, the only thing that can be done is to have a recursive function in a closure.

const arr1 = [1, 2, [3, 4], 5, [6, [7, 8]]]
    , arr2 = ['a','b',[['c','d'],'e','f'],'g',['h',['i',['j'],'k'],'l'],'m']

function recursiveflatClosure(arr_in)
  {
  let resFLat = []   // global array is more efficient in this case
  recursiveflat(arr_in)  // builder

  return ()=>resFLat    // closure answer

  function recursiveflat(arr)
    {
    for (let el of arr ) 
      if (Array.isArray(el)) recursiveflat(el)
      else                   resFLat.push(el)
    }
  }
  
let test1 = recursiveflatClosure( arr1 )
  , test2 = recursiveflatClosure( arr2 )
 
console.log('test 1:', JSON.stringify( test1() ))
console.log('test 2:', JSON.stringify( test2() ))
.as-console-wrapper {max-height: 100%!important;top:0}

4 Comments

I'm afraid this is still confused. The parameter resFlat is in the closure of the recursive function recursiveFlat. That's all the OP meant, I believe. So you can skip the additional function and simply return recursiveFlat (arr).
@ScottSauyet You are completely wrong, it is impossible to return the recursiveFlat function as the response of the closure because a closure must return a reference to a function, not its execution
My previous comment was naïve as I assumed that the internal recursive function returned a result. But you can still do let resFLat = []; recursiveflat(arr_in); return resFLat. This answers the OP's question in some sense, although Bergi's comment to the question do so more succinctly. The point of a closure here is that resFLat is in the closure of recursiveflat, and the recursive calls will update that same array. See link.sauyet.com/17
Sorry, I realized that I was misreading the original question, not you. That's actually a bizarre request, now that I look more closely. I apologize for the confusion.
1

This is a somewhat strange requirement. It seems that the desired API is for a function that accepts a nested array and returns a function that returns the flattened version of it. It's unusual as generally we would not have any need for that intermediate function.

But we can update your code to get this to work. The biggest problem is that your internal recursive iterator function takes no argument. When you make a recursive call to it, there is no place to store the data you want to process. So let's make this accept an array argument:

      function iterator(array) {

Now we note that this internal function is supposed to be recursive, but you're trying to call the outer function from inside. Let's call this instead:

      if (Array.isArray(array[i])) {
        iterator(array[i])
      } else {

And now, we can't return that iterator function directly. We need to add a level of indirection, one that passes our original array into it.

  function iterator(array) {
    // ...
  }
  
  return function() {return iterator (array)}

Together, these make for what seems to be the smallest change to make your function work correctly:

const array = [1, 2, [3, 4], 5, [6, [7, 8]]]; // --> 1,2,3,4,5,6,7,8

function recursiveArrayPrint(array) {
  let resultado = []
  function iterator(array) {
    for (let i = 0; array.length > i; i++) {
      if (Array.isArray(array[i])) {
        iterator(array[i])
      } else {
        console.log(array[i])
        resultado.push(array[i])
      }
    }
    return resultado
  }
  
  return function() {return iterator (array)}
}

const test = recursiveArrayPrint(array);
console.log('test: ',test())
.as-console-wrapper {max-height: 100% !important; top: 0}

However

This does not sound like a good idea. If you want a flattened version of an array, other answers and comments here suggest better ways, including better recursive solutions.

Moreover, recursion is a technique strongly associated with functional programming, and a fundamental tenet of functional programming is to avoid data mutation. But this solution is built around mutating your temporary array, resultado. I would look to the other answers for better ways of doing this.

Comments

0

A simple and clean flattening approach based on a reduce task and a recursively used reducer function ... no need of a closure either ...

function collectItemFlattened(list, item) {
  return list.concat(
    Array.isArray(item)
    ? item.reduce(collectItemFlattened, [])
    : item
  );
}

console.log(
  [1, 2, [3, 4], 5, [6, [7, 8]]].reduce(collectItemFlattened, [])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

3 Comments

I think this answer could use a wrapper function like function flattenArray (arr) {return arr .reduce (collectItemFlattened, [])}. But do note that the question explicitly described an attempt to do this recursively. It sounds more like an attempt to learn recursion than one to reimplement Array.prototype.flat.
@ScottSauyet ... collectItemFlattened is a recursive reducer (thus the OP can learn from a straightforwardly implemented recursive approach). It is a piece of reusable code, a tool that one uses directly with Array.reduce. If one wraps something around it, it looses its sexiness.
While it has some utility beyond the wrapper I suggest (just to extend an existing accumulator, as far as I see), is there any reason not to expose both this and a wrapper which simplifies the common case?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.