简体   繁体   中英

tail call optimization javascript

NOTE: This is just for studying and bettering myself. I know of the available sort method for arrays. I'm just trying to get the basics of TCO down.

Currently trying to work on a sort algorithm using recursion. However when I try to process large datasets (+4000 objects), I am still getting a stack overflow error. I'm trying to implement TCO. I'm fairly new to the concept but I think I have the gist of it. However, I'm still receiving a stack overflow error.

const sort = (arr, counter) => {
  if (!counter) {
    counter = arr.length - 1;
  }
  for (let n = 1; n <= counter; n++) {
    if(arr[n - 1] < arr[n]) {
      let placeHolder = arr[n];
      arr[n] = arr[n - 1];
      arr[n - 1] = placeHolder;
    }
  }
  counter -= 1;
  return counter === 0 ? arr : sort(arr, counter);
};

function sortRecursive(arr) {
  return sort(arr);
}

UPDATE:

I managed to get it working, but I don't quite understand why. I managed to handle 100,000 recursions with no problems. I had to move the boolean that checks if counter is defined. However, I do not quite understand why that made it work.

const sort = (arr, counter) => {
  if (!counter) {
    counter = arr.length - 1;
  }
  for (let n = 1; n <= counter; n++) {
    if(arr[n - 1] < arr[n]) {
      let placeHolder = arr[n];
      arr[n] = arr[n - 1];
      arr[n - 1] = placeHolder;
    }
  }
  counter -= 1;
  if (counter === 0) {
    return arr;
  } else {
    return sort(arr, counter);
  }
};

function sortRecursive(arr) {
  return sort(arr, arr.length - 1);
}

OUTPUT:

let firstArr = [];
let secondArr = [];

for (let x = 0; x < 100000; x++) {
  firstArr.push(Math.ceil(Math.random() * 100000));
  secondArr.push(Math.ceil(Math.random() * 100000));
}

sortRecursive(firstArr);
//Array[100000]

As you likely know, tail call optimization is a compiler technique that can allow a program to recurse infinitely by not allocating more memory per each recursion call.

Javascript is not currently tail call optimized, but the ES2015 standard of the language specification includes TCO. Every time a function calls itself in Javascript, a new stack frame is created, allocating new memory, so it will eventually run out and crash.

There are techniques to avoid this, including trampolines and not using a recursive loop. But at the moment you can not recursive infinitely in Javascript.

Why do you need recursion ( it will never work with 4000+ elems as this exceeds every call stack)?? cant you do:

const sort = (arr) => {
     var counter = arr.length;
     while(counter-->0){
       for (let n = 1; n <= counter; n++) {
           if(arr[n - 1] < arr[n]) {
              let placeHolder = arr[n];
              arr[n] = arr[n - 1];
              arr[n - 1] = placeHolder;
           }
        }
     }
     return arr;
}

If you want kind of recursion, you could use the qeue to keep the stack empty (requires passing a callback):

setTimeout(sort,0,arr,counter);//instead of sort(arr,counter);

By the way, easier and much faster ( because its natively implemented):

arr.sort((a,b)=>a-b);

Are you sure you're getting tail call optimization?

Here's a test with your updated code. The only things I changed are:

  1. Added 'use strict'; to put the code in strict mode. Some browsers that support TCO in the future may require strict mode to use TCO.
  2. Added console.trace() to print the call stack on each sort() call.
  3. Changed the test array setup to use Math.floor() instead of Math.ceil() for the reason mentioned in my comment above.
  4. Changed the array length to 10.

Open the developer console before running the snippet and observe the call stack traces.

I tested this in the latest versions of Chrome 59.0.3071.109, Firefox 54.0, and Edge 15.15063. The stack traces from all of them show the call stack growing on each call, indicating that there is no tail call optimization.

Just for kicks, I also tried it in Chrome with length = 100000 . It ran for a very long time, probably a minute or so, and then failed with a stack overflow when the stack reached a depth of about 10257 calls. For comparison, the standard sort( function( a, b ) { return b - a; } ) completed in about 5 seconds.

Here is a good article about JavaScript tail call optimization and related topics . One of the things the article mentions is that you can get TCO in node.js by using the --harmony_tailcalls command line switch along with 'use strict'; .

 'use strict'; const sort = (arr, counter) => { console.trace(); if (!counter) { counter = arr.length - 1; } for (let n = 1; n <= counter; n++) { if(arr[n - 1] < arr[n]) { let placeHolder = arr[n]; arr[n] = arr[n - 1]; arr[n - 1] = placeHolder; } } counter -= 1; if (counter === 0) { return arr; } else { return sort(arr, counter); } }; function sortRecursive(arr) { return sort(arr, arr.length - 1); } let firstArr = []; let length = 10; for (let x = 0; x < length; x++) { firstArr.push(Math.floor(Math.random() * length)); } console.clear(); sortRecursive(firstArr); //firstArr.sort( function( a, b ) { return b - a } ); console.log( firstArr ); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM