Seek and Destroy - I don't understand at all!

Seek and Destroy - I don't understand at all!
0

#1

So after a few hours of trying to solve this, I gave up and looked up the solution on GitHub. Unfortunately, I am no closer to understanding this. I have watched youtube videos and read several webpages and I still can’t understand.

What does .call achieve here? How does it help to make args and array? Also, my main problem is that I just can’t understand .filter. So, you make a new function in .filter(), called element. then you return args.indexOf(element) ===-1; but how do you do that when element is nothing? How does it know what element is when we haven’t given it any meaning?

Any help would be so appreciated. If somebody could just walk me though this a bit, I would be so grateful!


function destroyer(arr) {
  var args = Array.prototype.slice.call(arguments);
  args.splice(0, 1); 
  return arr.filter(function(element) { 
    return args.indexOf(element) === -1;
  });
}


#2

Array.prototype.filter is what we call a “higher order function”. The idea is that programmers tend to do certain operations on arrays so often, that it makes sense to build helper functions so we can write less code. Imagine we have an array of numbers and we want another array with only the numbers between 5 and 10 (inclusive).

var firstArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var desiredArray = [5,6,7,8,9,10];

Think for a minute about how you would write a function that takes firstArray and produces desiredArray. Take the time to write out some code. It doesn’t need to actually work, but you should think this through for a few minutes. Use a for loop. and remember we want a new array.

Hint: remember Array.prototype.push.

Click on the spoiler when you’ve got something.

You should have something like…

var newArray = [];
for(var i = 0; i < firstArray.length; i++) {
    if(firstArray[i] >= 5 && firstArray[i] <= 10) {
      newArray.push(firstArray[i])
    }
}

I’m sure you can imagine lots of ways we’d want to filter an array. Seek and Destroy is a good example. No matter what criteria we use to filter the array, there will always be some code in common with other filters.

Click the spoiler only if you’ve completed the simple exercise above.

  1. Create a new array
  2. Iterate through the array
  3. Check some condition
  4. Add the current element to the new array if the condition is true

This is really easy to write into a function. Here’s an example in some pseudocode:

Example:

function filter(firstArray) {
   var newArray = [];
   for(var i = 0; i < firstArray.length; i++) {
      if(**some condition**) {
         newArray.push(firstArray[i]);
      }
    }
    return newArray;
}

This will give us a new, filtered array every time. But there are a couple of problems. First, how do we set the condition? Second, how can we keep from writing this function over and over again?

The first problem is solved using a callback function which we will pass as the second argument of our function. It will take the current element as an argument and must return either true or false (and this is up to the developer when they use our function).

Example:

function filter(firstArray, callbackFunction) {
    var newArray = [];
    for(var i = 0; i < firstArray.length; i++) {
        if(callbackFunction(firstArray[i])) { // if the result of callbackFunction is true...
            newArray.push(firstArray[i]); // ...add our current element to the new array
        }
    }
   return newArray;
}

Now we have something really useful. We can call it on any function, pass it any callback function and it will give us a new, filtered array without us having to write a for loop for the 984,751th time in our lives. But the syntax is a bit cumbersome, and it isn’t easy to chain together with other array functions:

Example:

var firstArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var desiredArray = filter(firstArray, function(element) { return element >= 5 && element <= 10 }); // Correct output, but not easy to use in practice.

To solve this problem, we put the function on the Array’s prototype so it can be used with dot notation on any function. The benefit here? When we call a prototype method, the object itself gets passed as the first argument. This means that when we attach the filter function to the prototype, we can call the function without having to pass the first argument (the array we want to modify), and we can call it from our array object. We don’t have to change our filter function at all.

Example:

var firstArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var desiredArray = firstArray.filter(function(element) { // here's our callback function.  We don't need to pass the array because it's being done for us
                                       return element >= 5 && element <= 5;
                                      }); //correct output!

In summary, filter is a function that automatically iterates through an array and compares each element using a callback function that you define.

Any time we call slice, it returns a new array. If we call slice with no arguments, it returns the entire array. That sounds like a cool way to turn arguments into an array, but the arguments object doesn’t contain the slice method. The solution is related to what we saw when filter became a prototype method.

[spoiler]
Once we are able to call filter from an array, that array gets passed in as the first argument.

[spoiler]

The call method allows us to call prototype methods and pass in that first argument.

Example:

var firstArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var desiredArray = firstArray.filter(**callback**);
var alsoDesiredArray = Array.prototype.filter.(**callback**).call(firstArray); // Same thing as above

var args = arguments.slice(); // ERROR!  Method not found
var args = Array.prototype.slice.call(arguments);

#3

Hi @preencater,

@PortableStick did a good job of explaining filter so let me try to address the rest of your question.

According to MDN:

The arguments object is an Array-like object corresponding to the arguments passed to a function.

The key words here are Array-like object. This means that arguments is NOT an Array, but rather an Object that LOOKS like an Array.

Real Arrays give us all kinds of awesome functions like slice, and splice, and filter. This arguments object, on the other hand, gives us none of that.

What a bummer.

Wouldn’t it be nice if we could turn our arguments object into a REAL Array? That’s where this line comes in:

var args = Array.prototype.slice.call(arguments);

Let’s work backwards from right to left. We already know what arguments is, so what’s call?

Back to MDN:

The call() method calls a function with a given this value and arguments provided individually.

The tricky bit here is “with a given this value”. Another way to say it is that call() will call a function against whatever object you pass it.

So this code:

var arr = []
arr.slice()

is the same as:

var arr = []
Array.prototype.slice.call(arr)

Because arguments is not a REAL Array, we can’t do arguments.slice() so, we resort to Array.prototype.slice.call(arguments)

Okay, cool, but what does slice do?

MDN:

The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.

Essentially, Array.prototype.slice.call(arguments) just makes a copy of arguments that is a REAL Array.

So, arguments is NOT an Array, but args IS an Array. We did that with the line:

var args = Array.prototype.slice.call(arguments);

Next, we look at spliceMDN:

The splice() method changes the contents of an array by removing existing elements and/or adding new elements.

splice() takes three parameters:

  • the first parameter tells splice where to start removing/or adding elements to the array
  • the second parameter (optional) tells splice how many elements to remove
  • the third parameter is a list of elements to add to the array

so args.splice(0, 1) just removes the first element from args which in the case of our destroyer() function is the original Array from which we will be destroying our elements.

Suppose we call out destroyer function like so:

destroyer(['A','B','C'], 'C', 'D')

Using the given solution:

var args = Array.prototype.slice.call(arguments);

Will set args equal to [['A','B','C'], 'C', 'D'] which is an array containing three elements, the original array, and the two values to be destroyed.

Then:

args.splice(0, 1); 

will CHANGE the value of args to ['C','D']. It removed the first element.

Hope that helps.

Regards,
Bill Sourour


#4

In javascript, all functions have a built-in object called the “arguments” object. “Arguments” object is an “Array-like” object, not a real Array.

Array-like objects look like arrays. They have various numbered elements and a length property. But that’s where the similarity stops. Array-like objects do not have any of Array’s functions.

Here is where Array.prototype.slice.call() comes in.

The Array.slice() method returns the selected elements in an array, as a new array object.

Example:

var arr1 = ['free', 'code', 'camp'];
var arr2 = arr1.slice(1, 2);

The result of arr2 will be [‘code’, ‘camp’].

call() and apply() are prototype methods of the Function object, meaning that they can be called on every function in javascript.

We use the method call() of Array.prototype.slice, with “arguments” as “this” parameter, to convert an Array-like object to a real Array.

var args = Array.prototype.slice.call(arguments);

args is now a real Array, and we can use filter(), join(), splice(), etc on it.

SPOILER - Seek and destroy challenge related:

[spoiler]At the time your destroy function is called, you use 3 parameters:

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

But the function only listen to 1 of them:

function destroyer(arr) {
  ...
}

You can do a console.log(arr) here, and the result will be “[1, 2, 3, 1, 2, 3]”.

So how can we access to “2, 3”, the other two parameters?
We can use the “arguments” object.

The output of console.log(arguments) will be “[1, 2, 3, 1, 2, 3], 2, 3”, who is exactly all given parameters.

Since we want to substract all elements form “[1, 2, 3, 1, 2, 3]” who equals any of the other parámeters, and we know the value of “arr” is “[1, 2, 3, 1, 2, 3]”, we can isolate the remaining “2, 3” to do the filter:

args.splice(0, 1);

The output of console.log(args) is now “2, 3”.

We have now our “arr” ([1, 2, 3, 1, 2, 3]) and our args([2, 3]), so we can apply the filter:

return arr.filter(function(element) {
  return args.indexOf(element) === -1;
});

Which means:

For every member (element) of my “arr” ([1, 2, 3, 1, 2, 3]), return only whose not equals any of my “args” ([2, 3]);

And the final result will be [1, 1];[/spoiler]

Hope that helps.


#5

Also, FWIW, in ES2015 you can use Rest parameters, wich ARE real Arrays.

More info:


#6

I found my answer here at Array.prototype.filter()

var fruits = [‘apple’, ‘banana’, ‘grapes’, ‘mango’, ‘orange’];

/**

  • Array filters items based on search criteria (query)
    */
    function filterItems(query) {
    return fruits.filter(function(el) {
    return el.toLowerCase().indexOf(query.toLowerCase()) > -1;
    })
    }

console.log(filterItems(‘ap’)); // [‘apple’, ‘grapes’]
console.log(filterItems(‘an’)); // [‘banana’, ‘mango’, ‘orange’]

Looking at that you will figure out how to use the arrayToBeFiltered and the items you will search and destroy on that array.

Here’s my answer and lots of comments. This is a spoiler so only click if you really need it

[spoiler]function destroyer(arr) {
/* Separate the listToBeFiltered from arguments*/
var arrList=arguments[0];

/* Separate the listOfItemYouWantDestroyed from arguments*/
var destroyList=[];
for(a=1;a<arguments.length;a++) destroyList.push(arguments[a]);

/* Use filter to get all items from listToBeFiltered that is not in listOfItemYouWantDestroyed */
var finalArray = arrList.filter(function(arrItem) {
	
	/* Filter function returns an array which was the original array but items filtered removed 
	   given
			listToBeFiltered = [1, 2, 3, 1, 2, 3]
			destroyList = [2,3]
	   
	   arrItem will first be 1
			then if it finds it on destroyList return false; so it will not be included on finalArray
			if it doesnt find it returns true; therefore it will be added to finalArray
	   then it will move on to the next index on listToBeFiltered
			it will then again try to find it on destroyList just like what it did on first one
		this will be done till all on listToBeFiltered are checked if they exist in destroyList
	*/
	
	/* 
		This is how I checked if the item was in the destroyList 
		it loops and compare arrItem to destroyList returns false if it is found
		if it exits the loop without being found it returns true;
	*/
	for(b=0;b<destroyList.length;b++) {
		if(arrItem == destroyList[b]) {
			return false;
		}			
	}
	
	return true;
});

/* Return the array */
return finalArray;

}[/spoiler]


#10

@PortableStick - Thank you so much for this incredible explanation! This is so helpful for me. I have read through this two times and I might read over it again to make sure I’ve got it. I can’t tell you how much I appreciate you breaking this down for me! Thank you!!!

@BillSourour - Thank you so much, Bill! That was very helpful. You are very good at explaining this! I have watched videos and read some of the articles online, but it’s better to have it broken down piece by piece. Thank you thank you thank you!

@bigbelman - I was actually having a very difficult time with this:

return args.indexOf(element) === -1;

Thanks so much for clearing that up. I am starting to understand now.