ram
ram

Reputation: 690

Recursive function prints correct final result inside the loop but then is undefined outside

I need another set of eyes. There has to be something small I'm just missing here.

I'm inputting a string which is brokend into an array of words, then takes every 5 indexes (words) and joins them into their own index.

This is accomplished through a recursive call.

The final call of the recursion prints the correct result I'm looking for while inside, but the actual returned value outside is undefined..

Please help me out here.

const limit = str => {
  const arr = str.split(' ');
  function recursiveLimit(arr, acc = []) {
    if (arr.length !== 0) {
      const toSlice = arr.length < 5 ? arr.length : 5;
      const newArr = arr.slice(toSlice);
      const newAcc = [...acc, arr.slice(0, toSlice).join(' ')];
      recursiveLimit(newArr, newAcc);
    } else {
      console.log('final array: ', acc); // the array I want to return (looks good here)
      return acc; // return it
    }
  }
  return recursiveLimit(arr); // undefined
};

console.log(
  'OUTPUT: limit',
  limit(
    'one two three four five six seven eight nine ten eleven twelve thirteen fourteen'
  )
);

Upvotes: 4

Views: 253

Answers (5)

Scott Sauyet
Scott Sauyet

Reputation: 50797

As others have pointed out, you're right, it was a simple mistake: you missed an important return.

It might be cleaner, though, to build this function atop one which chunks an array into multiple parts. Then this code would be responsible only for splitting apart the initial string, calling chunk, and combining the resulting parts back into strings:

const chunk = (n) => (xs, res = []) =>
  xs.length ? chunk (n) (xs .slice (n), [... res, xs .slice (0, n)]) : res

const limit = (str) => 
  chunk (5) (str .split (' ')) .map (ss => ss .join (' '))

console .log (
  "OUTPUT: limit",
  limit ("one two three four five six seven eight nine ten eleven twelve thirteen fourteen")
)

Although chunk is written in a very different style, it proceeds exactly the same way that your recursiveLimit does, except that it does not join the resulting arrays back into strings, leaving that for the function limit. Note one simplification: Array.prototype.slice does exactly what you would hope if the limit is out of bounds. For instance, [1, 2, 3] .slice (0, 5) //=> [1, 2, 3] and [1, 2, 3] .slice (5) //=> []. So we don't need to capture toSlice and can just use our chunk length.

limit is a very simple function now, splitting the input string on " " into an array of strings, calling chunk (5) on that array, and mapping calls to join (" ") on the resulting array. If we wanted to make it more flexible and replace 5 with a parameter, it would be trivial:

const limit = (n) => (str) => 
  chunk (n) (str .split (' ')) .map (ss => ss .join (' '))

limit (5) ('one two three four...')

Upvotes: 2

redOctober13
redOctober13

Reputation: 3974

I'd skip recursion altogether (unless it was a requirement) and just use reduce()

const limit = str => {
  const splitBy = 5;
  const arr = str.split(' ');
  let splitArr = [];
  if (arr.length <= 5) return arr;

  arr.reduce( (acc, item, idx) => {
    if (idx % splitBy === 0) {
      //push the block and reset the accumulator
      splitArr.push(acc)
      acc = item
      return acc;
    } else if (idx === arr.length-1) {
      //the number of items wasn't evenly divisible by the splitter; push out the remainder
      splitArr.push(acc + " " + item)
    } 
    else {
      //add the current item to the accumulator
      return acc+= " " + item;
    }
  })
  
  return splitArr;
};

console.log(
  'OUTPUT: limit',
  limit(
    'one two three four five six seven eight nine ten eleven twelve thirteen fourteen'
  )
);

Upvotes: 2

Gopinath
Gopinath

Reputation: 4937

Here is the fix for the code:

function recursiveLimit(arr, acc = []) {
    if (arr.length === 0) {
        return acc
    }
    const toSlice = ((arr.length < 5) ? arr.length : 5)
    const newArr = arr.slice(toSlice)
    const newAcc = [...acc, arr.slice(0, toSlice).join(' ')]
    return recursiveLimit(newArr, newAcc)
  }

const limit = (str) => {
  return recursiveLimit(str.split(' '))
}

const input = ("one two three four five six seven eight nine ten eleven twelve thirteen fourteen")
console.log("OUTPUT: limit\n", limit(input))

Output:

$ node demo2.js 
OUTPUT: limit
 [ 'one two three four five',
  'six seven eight nine ten',
  'eleven twelve thirteen fourteen' ]

Summary of changes made to the code:

I just rearranged the code and made the following subtle changes to reduce the total number of lines and to improve the efficiency:

  1. Define the recursive fucntion outside 'limit()' function.

  2. Make sure the recursive function always returns a value (array size == 0 and otherwise).

  3. Remove semi-colons and add parentheses to make it easy to spot errors. (Javascript does not need semi-colons at the end of line)

Upvotes: 2

lawrencebensaid
lawrencebensaid

Reputation: 111

All you need to do is add a return before your recursive call of recursiveLimit on line 8.

Fixed code:

  const arr = str.split(' ');
  function recursiveLimit(arr, acc = []) {
    if (arr.length !== 0) {
      const toSlice = arr.length < 5 ? arr.length : 5;
      const newArr = arr.slice(toSlice);
      const newAcc = [...acc, arr.slice(0, toSlice).join(' ')];
      return recursiveLimit(newArr, newAcc);
    } else {
      console.log('final array: ', acc); // the array I want to return (looks good here)
      return acc; // return it
    }
  }
  return recursiveLimit(arr); // undefined
};

console.log(
  'OUTPUT: limit',
  limit(
    'one two three four five six seven eight nine ten eleven twelve thirteen fourteen'
  )
);```

Upvotes: 3

Robbie Milejczak
Robbie Milejczak

Reputation: 5770

To maintain the callstack you should always return recursive function calls. Inside your first if statement, you don't return a call to your function which leads to an undefined value. So while acc moves down the callstack and is properly logged, it doesn't get returned back up the callstack and stored in the limit variable. Simply adding a return fixes the problem

const limit = str => {
  const arr = str.split(' ');
  function recursiveLimit(arr, acc = []) {
    if (arr.length !== 0) {
      const toSlice = arr.length < 5 ? arr.length : 5;
      const newArr = arr.slice(toSlice);
      const newAcc = [...acc, arr.slice(0, toSlice).join(' ')];
      // missing return was here
      return recursiveLimit(newArr, newAcc);
    } else {
      console.log('final array: ', acc);
      return acc;
    }
  }
  return recursiveLimit(arr);
};

console.log(
  'OUTPUT: limit',
  limit(
    'one two three four five six seven eight nine ten eleven twelve thirteen fourteen'
  )
);

Upvotes: 5

Related Questions