Curriculum created and presented by

Jeff Lee

@RebootJeff

in JavaScript

Recursion

Who is Jeff?

Full-Stack JavaScriptivore

@RebootJeff

Class Format

  • Silence your phones please!
  • Ask questions (between slides if possible)
  • Answer questions
  • Show thumbs
  • Ask for help during exercises
    • But don't expect fast solutions
    • TAs will guide you through debugging and help you ask the right questions

Basics

Call Yourself

function callMyself() {

  callMyself();

}

What if you do a Google search for recursion?

General Algorithm

function callMyself() {

  if() {
    // base case
    return;
  } else {
    // recursive case
    callMyself();
  }
    
  return;
}
  1. Identify base case
  2. Identify recursive cases
  3. Write procedures for each case

Advice: Use a bottom-up approach by looking for patterns by starting with small or first few cases

Example: Factorial

function computeFactorial(num) {
  var result = 1;

  for(var i = 2; i <= num; i++) {
    result *= i;
  }

  return result;
}
results *= 2;
results *= 3;
results *= 4;
results *= 5;

If we call computeFactorial(5), then the loop will run:

Example: Factorial

function computeFactorial(num) {
  if(num === 1) {
    return 1;
  } else {
    return num * computeFactorial(num - 1);
  }
}
5 * computeFactorial(4)
    4 * computeFactorial(3)
        3 * computeFactorial(2)
            2 * computeFactorial(1)
                1

If we call computeFactorial(5), then the recursion will run:

function logNumbers(start, end) {
  for(var i = start; i <= end; i++) {
    console.log(i);
  }
}

function logNumbersRecursively(start, end) {
  function recurse(i) {
    console.log(i);

    if(i < end) {
      recurse(i + 1);
    }
  }

  recurse(start);
}

Converting a loop into recursion

Exercises!

(but first, a quick overview)

Common
Patterns

function runRecursiveLoop(start, end) {
  function recurse(i) {
    console.log(i);
    if(i < end) {
      recurse(i + 1);
    }
  }

  recurse(start);
}

function runLoopAsMyself(i, end) {
  console.log(i);
  if(i < end) {
    runLoopAsMyself(i + 1, end);
  }
}

Wrappers

function runRecursiveLoop(start, end) {
  (function recurse(i) {
    console.log(i);
    if(i < end) {
      recurse(i + 1);
    }
  })(start);
}

Wrappers (condensed)

function joinElements(array, joinString) {
  var result = '';
  var lastElement = array[array.length - 1];

  for(var i = 0; i < array.length - 1; i++) {
    result += array[i] + joinString;
  }

  return result + lastElement;
}

Accumulators

(without recursion)

Accumulators

function joinElements(array, joinString) {

  function recurse(index, resultSoFar) {
    resultSoFar += array[index];

    if(index === array.length - 1) {
      return resultSoFar;
    } else {
      return recurse(index + 1, resultSoFar + joinString);
    }
  }

  return iterate(0, '');
}

(with recursion)

Mutual (aka Indirect) Recursion

Memoization

Tail-Call Optimization

The Call Stack

RangeError: Maximum Call Stack Exceeded

Tail Recursion

Tail Recursion in the Call Stack

What is TCO like in JS?

ES6

Example: Factorial

function computeFactorial(num) {
  if(num <= 1) {
    return 1;
  } else {
    return num * computeFactorial(num - 1);
  }
}
function computeFactorial(num, resultSoFar) {
  resultSoFar = resultSoFar || 1;
  resultSoFar *= num;

  if(num <= 1) {
    return resultSoFar;
  } else {
    return computeFactorial(num - 1, resultSoFar);
  }
}

The recursive call isn't the final step of this function:

Use an accumulator to convert to tail recursion!

Made with Slides.com