So basically i need to use the each function that i implemented in the filter function so that the filter function return only item in myArray that is greater than 2. i m totally stuck.
var myArray = [1, 2, 3, 4, 5];
function each(collection, callback) {
for (var i = 0; i < collection.length; i++) {
callback(collection[i]);
}
}
function filter(collection, test) {
var returnAr = [];
for (var i = 0; i < collection.length; i++) {
if (test(collection[i])) {
returnAr.push(collection[i])
}
}
//instead of using "for(var i = 0; i < collection.length; i++)"
// how can i use the each function i implemented above?
return returnAr;
}
filter(myArray, function(n) {
return n > 2;
}
The each function calls its callback by passing each item in the array as a function argument. Therefore the correct way to call it would be:
each(collection, function(item) {
if (test(item)) {
// ...
}
}
Javascript doesn't quite provide a nice self-documenting way of specifying the function signature[1]. Normally this is solved by reading the API documentation. But if this is your own code and it doesn't come with API documentation then you need to look at how the callback is invoked. In this case it is called as:
callback(collection[i]);
Which tells us that the callback function should accept one argument which is a single item from the array.
[1]: also called prototype, no, not in the javascript sense of the word "prototype", in the C sense of the word "prototype"
You can't. The 'each' function above executes a callback on all items of a collection. But in 'filter' you are deciding whether you want an item in the resultant array based on result of the callback. So 'filter' and 'each' are both different operations. Whatever you are doing in the current function is right way of filtering.
The signature for each takes an array like thing, and a function that gets called with the element.
function each(collection, callback) {
for (var i = 0; i < collection.length; i++) {
callback(collection[i]);
}
}
It seems like your callback should just do what this does:
if (test(collection[i])) {
returnAr.push(collection[i])
}
Note that collection[i] is the first argument to the callback.
Your callback will have the form:
function(element) {
// do stuff
}
If you really only want to filter the array on the condition you give, this is all the code you need:
myArray.filter(function(item) { return item > 2 })
Related
Background (You might want to skip this)
I'm working on a web app that animates the articulation of English phonemes, while playing the sound. It's based on the Interactive Sagittal Section by Daniel Currie Hall, and a first attempt can be found here.
For the next version, I want each phoneme to have it's own animation timings, which are defined in an array, which in turn, is included in an object variable.
For the sake of simplicity for this post, I have moved the timing array variable from the object into the function.
Problem
I set up a for loop that I thought would reference the index i and array t to set the milliseconds for each setTimeout.
function animateSam() {
var t = [0, 1000, 2000, 3000, 4000];
var key = "key_0";
for (var i = 0; i < t.length; i++) {
setTimeout(function() {
console.log(i);
key = "key_" + i.toString();
console.log(key);
//do stuff here
}, t[i]);
}
}
animateSam()
However, it seems the milliseconds are set by whatever i happens to be when the function gets to the top of the stack.
Question: Is there a reliable way to set the milliseconds from the array?
The for ends before the setTimeout function has finished, so you have to set the timeout inside a closure:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
for (var i = 0; i < t.length; i++) {
(function(index) {
setTimeout(function() {
alert (index);
key = "key_" + index.toString();
alert (key);
//do stuff here
}, t[index]);
})(i);
}
}
Here you have the explanation of why is this happening:
https://hackernoon.com/how-to-use-javascript-closures-with-confidence-85cd1f841a6b
The for loop will loop all elements before the first setTimeout is triggered because of its asynchronous nature. By the time your loop runs, i will be equal to 5. Therefore, you get the same output five times.
You could use a method from the Array class, for example .forEach:
This ensures that the function is enclosed.
[0, 1000, 2000, 3000, 4000].forEach((t, i) => {
setTimeout(function() {
console.log(i);
console.log(`key_${i}`);
//do stuff here
}, t)
});
Side note: I would advise you not to use alert while working/debugging as it is honestly quite confusing and annoying to work with. Best is to use a simple console.log.
Some more clarifications on the code:
.forEach takes in as primary argument the callback function to run on each of element. This callback can itself take two arguments (in our previous code t was the current element's value and i the current element's index in the array):
Array.forEach(function(value, index) {
});
But you can use the arrow function syntax, instead of defining the callback with function(e,i) { ... } you define it with: (e,i) => { ... }. That's all! Then the code will look like:
Array.forEach((value,index) => {
});
This syntax is a shorter way of defining your callback. There are some differences though.
I would suggest using a function closure as follows:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
var handleAnimation = function (idx) {
return function() {
alert(idx);
key = "key_" + idx.toString();
alert(key);
//do stuff here
};
}
for (var i = 0; i < t.length; i++) {
setTimeout(handleAnimation(i), t[i]);
}
}
I this example you wrap the actual function in a wrapper function which captures the variable and passes on the value.
In a functional programming exercise I found online, the instructions are as follows:
"Implement JavaScript’s native ‘filter’ method that takes two arguments (a collection and a test function that returns either a ‘true’ or a ‘false’) and iterates over the collection using the ‘each’ function you wrote earlier and returns the resultant array."
I've already completed the first part of the exercise and created a function that implements JS' forEach method:
var each = function(collection, iterator) {
if(Array.isArray(collection)){
for(var i = 0; i < collection.length; i++){
// value, key/property, collection
iterator(collection[i],i,collection);
}
} else if (typeof collection === "object"){
for(var property in collection){
iterator(collection[property], property, collection);
}
}else{
console.log("you messed up!");
}
};
Test my function with:
function returnOdds(currentEl) {
return currentEl % 2 !== 0;
}
console.log(filter([1, 2, 3], returnOdds)); // output: 1, 3
I'm not sure how to call my 'each' function on the 'collection' parameter inside my filter function.
Is this legal to do?
function filter(collection, test) {
each(collection);
}
Or perhaps I can call the 'test' parameter as a function that checks to see if collection[i] is not an even/odd number?
function filter(collection, test) {
var odd = function(each()){
each(collection){
if(collection[i] !== 0){
return odd;
}
}
}
}
I am wondering if any of this even makes sense or can be done.
The filter() function that you are being asked to implement needs to somehow build up a new array of values that pass the test, and then return that new array. Here's one way to do it that uses your existing each() method:
var filter = function(collection, test) {
var results = [];
each(collection, function(val) {
if (test(val)) {
results.push(val);
}
});
return results;
};
You can then call this function in the way you showed in the middle of your question, passing in an array and a reference to some other function that will return true or false for a given item:
var oddNumbers = filter([1, 2, 3], returnOdds);
Demo: https://jsfiddle.net/4rj7phot/
So this works as follows:
Create an empty array for the results.
Use your each() function to execute an anonymous function for each item in the collection argument.
Within the anonymous function, call the function provided in the test argument, passing the current item value, and if that function returns true (or a truthy value) add the item to the results array.
Return the results.
I'll leave any validation of the arguments (e.g., checking that test actually is a function) to you.
I am learning JavaScript and got a bit confused with the following exercise. I did had to create a filter that would accept another functions as a sorting method. The thing that I cant understand a bit is how exactly the for loop passes the value to the x. Can you please explain?
function filter(arr, func) {
var result = [];
for (var i = 0; i < arr.length; i++) {
var value = arr[i];
if (func(value)) {
result.push(value);
}
}
return result
}
function inBetween(a, b) {
return function(x) {
return a <= x && x <= b;
}
}
function inArray(arr) {
return function(x) {
console.log(x)
return arr.indexOf(x) != -1;
}
}
var arr = [1, 2, 3, 4, 5, 6, 7];
alert(filter(arr, function(a) {
return a % 2 == 0
})); // 2,4,6
alert( filter(arr, inBetween(3, 6)) ); // 3,4,5,6
alert( filter(arr, inArray([1, 2, 10])) ); // 1,2
I'll take this line as example:
filter(arr, inArray([1, 2, 10])) );
inArray is called with arr = [1, 2, 10].
It returns the following (anonymous) function, for that particular arr:
function (x) {
return arr.indexOf(x) != -1;
}
So the original line now can be pictured as:
filter(arr, function (x) {
return [1, 2, 10].indexOf(x) != -1;
});
Now filter is called with func set to that anonymous function. The following code calls that function:
if (func(value)) {
result.push(value);
}
So when this func is called, this really means the above anonymous function is called, and the parameter x gets set to value at the moment of the call. It is just like with any other function call, the parameters of the function get the values of the arguments in the actual call.
Your filter function accepts the array to filter, and a function that does the filtering. In this part:
for (var i = 0; i < arr.length; i++) {
var value = arr[i];
if (func(value)) { //here, you invoke the function on the array's values
result.push(value);
}
}
So if, for instance, we look at the inBetween(3, 6) function, which is this:
return function(x) {
return 3 <= x && x <= 6;
}
(If you don't understand this part, read some more about Closures)
So now that function is simply a function that accepts a value and returns true/false on whether or not it's between 3 & 6.
You eventually get an invocation on each of the array's values (1, 2, 3 ... 7) with the above function.
Same goes for all other functions you listed.
In this line:
filter(arr, inBetween(3, 6))
You are not "passing a function", but passing the result from a function call. The result is another (anonymous/unnamed) function.
From this line:
function filter(arr, func) {
We know name the function passed as an argument to filter is named func, then:
if (func(value)) {
executes this function.
In JavaScript, a function is just an object like other objects. To differentiate "function object reference" from "function calls", you can look for ().
inBetween is a function object reference, inBetween(x, y) means calling this function.
Anonymous functions can be defined, assigned to a variable, and passed as an argument as well. It may be executed instantly after definition as well:
function() {
alert("called!");
}();
This may look exotic to people new to JavaScript though.
I have this code, it's independent and isolated. The problem I am having is that the index i is starting at 1 instead of starting at 0. I have no idea why this could be, and doesn't seem to have anything to do with the closure that I am pushing into the deletes array...but I can't be sure, no idea what the issue is.
onClickResetAll: function (event) {
event.preventDefault();
var deletes = [];
Object.keys(collections).forEach(function (key) {
if (collections.hasOwnProperty(key)) {
var coll = collections[key];
for (var i = 0; i < coll.models.length; i++) {
deletes.push(function (callback) {
var index = i; //i starts at 1, not 0 !!!
coll.models[index].deleteModel(function (err, resp, x) {
console.log(err, resp, x);
if(err){
callback(err);
}
else{
callback(null,null);
}
});
});
}
}
});
async.parallel(deletes,function(err,results){
Backbone.Events.trigger('bootRouter', '+refreshCurrentPage');
});
}, //end of onClickResetAll callback function
//end
The problem isn't really that i starts at one, the problem is that i will be coll.models.length for every function in deletes. Why would that be? Well, each function is sharing the same i and i won't be evaluated until the functions inside deletes are actually called.
The solution is to force i to be evaluated when it has the value you want (i.e. evaluated i when you're building the callback function). There are various solutions and they're all variations on the "wrap it in a function to break the reference" theme:
Use an iterator with a callback function instead of a plain for loop:
coll.each(function(model, i) {
// `model` is the model from the collection, `i` is the loop index.
});
You can use each here because Backbone collections have a bunch of Underscore functions built in.
Wrap the loop body in an SIF:
for(var i = 0; i < coll.models.length; ++i)
(function(i) {
//...
})(i);
Use a separate function to build your functions:
function make_deleter(coll, i) {
return function(callback) {
coll.models[i].deletedModel(function(err, resp, x) {
//...
}
}
}
//...
for(var i = 0; i < coll.models.length; ++i)
deletes.push(make_deleter(coll, i));
They all do pretty much the same thing: add an extra function call into the mix to force i to be evaluated (rather than just referenced) on each iteration of the loop.
In a Backbone situation, 1 would probably be the most natural and you wouldn't even need your troublesome i with that approach.
another solution to this is using async.each or async.eachSeries instead of async.parallel. using the first 2 avoids pushing to an array of functions altogether.
I have a function "add_floor(xxx)" in a loop (see bellow) that take some time to complete, so i would like to know how to wait for the return of this function before moving to the next step which is in my case "add dawfeature points". I understand that I could use a callback method but then I would need to restart the complete loop that goes over the json elements in the calledback function. Not really sexy, so is there a way to avoid this ?
function add_maps_from_json(json) {
for (var i = 0; i < json.maps.length; i++) {
// add the maps to the combox box and display last one
add_floor(json.maps[i].mapName);
// add the dawfeature points to the map
for (var j = 0; i < json.maps[i].APs.length; j++) {
var idx = find_map_index();
console.log(idx);
var map = mapObjectArray[idx];
etc...
etc...
}
}
}
You can make the function return a value to a variable. That way the program will pause until the function as run. Like this:
var maps = add_maps_from_json(json);
Or you can use plugin.js and do module calls.
http://requirejs.org/docs/plugins.html
as you have said you have to add a callback parameter to add_floor and for managing your loop you can do async loop, like this:
//change the add_floor function
function add_floor(mapName, callback){
//your code
//you probably have some kind of ajax call or setTimeout
//then call your callback after your ajax response or timer callback
//....
callback();
//....
}
function add_maps_from_json(json) {
doLoop(json, 0)
}
function doLoop(json, i) {
if(i < json.maps.length) return;
add_floor(json.maps[i].mapName, function () {
// add the dawfeature points to the map
for (var j = 0; i < json.maps[i].APs.length; j++) {
var idx = find_map_index();
console.log(idx);
var map = mapObjectArray[idx];
//etc...
//etc...
}
doLoop(json, i + 1);
});
}
If add_floor doesn't do an ajax call, then your code will work fine as it is now.
If add_floor DOES do an ajax call, then you can put the inner for loop inside a separate function that you then use as the callback for the ajax call in add_floor.