Seek and Destroy explanation for code

Hey all!

Just wanting an explanation as to why one particular method works, whereas another did not. The object was to remove the values presented in the arguments from the array provided. I managed to figure out how to get it working after a while of searching on the net, however I don’t know why it worked!

The following did not work:


function destroyer(arr) {
  // Remove all the values
  arr = Array.prototype.slice.call(arguments);
  
  var newArr = arr.splice(arr[0],1);
  
    for (var i = 0; i < arr.length; i++){
    for (var x = 0; x < newArr[0].length; x++)
    {
      if (newArr[0][x] == arr[i]) newArr[0].splice(x,1);
    }
  }
  
  return newArr[0];
}

destroyer([3, 5, 1, 2, 2], 2, 3, 5);

where as this did:

function destroyer(arr) {
  // Remove all the values
  arr = Array.prototype.slice.call(arguments);
  
  var newArr = arr.splice(arr[0],1);
  
    for (var i = 0; i < arr.length; i++){
    for (var x = newArr[0].length-1; x >= 0; x--)
    {
      if (newArr[0][x] == arr[i]) newArr[0].splice(x,1);
    }
  }
  
  return newArr[0];
}

destroyer([3, 5, 1, 2, 2], 2, 3, 5);

Your browser information:

Your Browser User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36.

Link to the challenge:
https://www.freecodecamp.org/challenges/seek-and-destroy

As far as I can tell, both examples are identical.

var newArr = arr.splice(arr[0],1);

should be

var newArr = arr.splice(0,1);

That line is working by accident, since arr[0] will not coerce to a number and the parameter will be ignored.

Oh, sorry I’ve put in the working code twice. This was the code that did not work:

function destroyer(arr) {
  // Remove all the values
  arr = Array.prototype.slice.call(arguments);
  
  var newArr = arr.splice(arr[0],1);
  
    for (var i = 0; i < arr.length; i++){
    for (var x = 0; x < newArr[0].length; x++)
    {
      if (newArr[0][x] == arr[i]) newArr[0].splice(x,1);
    }
  }
  
  return newArr[0];
}

destroyer([3, 5, 1, 2, 2], 2, 3, 5);

Understanding the difference depends on understanding the fact that splice will change the length of the array you’re calling it on. This isn’t going to be easy for me to write about, and it’s going to be just as hard for you to understand, so you need to really think this through. Let’s first read the loop construct for the code that doesn’t work:

for (var x = 0; x < newArr[0].length; x++)

x starts at 0. While x is less than the length of the array at newArr[0], keep running the loop. At the end of each loop, increment x by 1. Note that the loop gets newArr[0].length at each iteration. If the length of the array changes, so do the loop’s parameters.

Now think about what happens in this loop the first time it gets a match. Let’s say we’ve called destroyer([2, 3, 2, 3], 2, 3), so newArr[0] is [2,3,2,3] and arr is [2,3]. We start both loops at 0, and the first number we want to destroy is 2 (arr[0] is 2);

// x === 0 and i === 0
if (newArr[0][x] == arr[i]) newArr[0].splice(x,1);
// same thing as
if(2 == 2) newArr[0].splice(x,1);

// so the array gets spliced
[2,3,2,3].splice(0, 1); // [3,2,3] <= newArr[0] has been changed.

After the first item has been destroyed, x increments to 1. The same thing happens because newArr[1] is 2, so it happens to delete another number. Now the array is [3,3]. The length of the array started at 4, but now it’s 2. x increments to 3, and while this wouldn’t have ended the loop when we started, since the array is shorter the inner loop ends.

The outer loop increments - i is 1. We’re now looking to destroy 3 since that’s the value at arr[1]. This is where the code breaks down.

// x === 0 and i === 1
if(newArr[0][x] == arr[i]) newArr[0].splice(x,1);
// same thing as
if(3 == 3) newArr[0].splice(x,1);

// the array gets spliced again
[3,3].splice(0, 1); // [3] <= newArr[0] has been changed

Now newArr's length is 1. The array only contains the number 3, which should be deleted, right?

WRONG

Because newArr has only one value, its index is 0. We just got done destroying the 0th item. The loop increments and we’re now looking at x === 1 and i === 1. Both loops are done and the function returns an array with a single value, [3].

The reason the other code works is because you’re editing newArr from the end and decrementing the inner loop. newArr's length still changes, but since we’re counting from the back and working towards 0, it doesn’t matter. Imagine chopping down a tree by chunks. The broken code is the same as starting from the roots and moving up. As you chop a chunk, you shorten the tree from the bottom. The code that works is like chopping chunks off the tree from the top.

You’re probably not going to understand the problem just by reading this mess. I know that I wouldn’t. The only way this is going to click is if you walk through to code yourself, methodically and slowly. Talk out loud. Pretend you’re explaining the code to a small child or a rubber duck. This is how you really learn to code, and there are no shortcuts.

2 Likes

Thank you SO MUCH for the detailed explanation! While I may not have fully wrapped my head around it all after my first read, I’ve got a much better understanding of why it hasn’t worked. I’ll keep thinking it through, and use the “rubber duck” method until it all clicks! :grinning::grinning::grinning:

Again thank you!!!