What work is wasFound doing in this version of contains? - javascript

In the following code, I'm not sure how the parameter wasFound is doing its work:
_.contains = function(collection, target) {
return _.reduce(collection, function(wasFound, item) {
if (wasFound) {
return true;
}
return item === target;
}, false);
};
I would expect wasFound to be initialized at undefined (although I've seen in comments that its initialized at false; how does that happen). I also can't see how wasFound itself gets updated.
I've seen a lot of discussion on SO that discusses this version of contains, but nothing that parses this particular piece. Help?

"wasfound" is the accumulator value of the reduce. In your code, it is initialized with "false" since this is the third argument to the "_.reduce" method, indicating that the "target" element has not been found before the loop starts. Then, the "reduce" iterates over the collection and assigns the current element to "item". On every iteration step, it checks if "wasFound" is true, which would indicate that we already found the element in a previous iteration step and return "true" to pass that information to the next iteration. Else, we compare the current collection item to the "target" item. If they are of the same type and value, we return "true" to indicate that to the next iteration, else "false". This has the effect, that after having iterated over the whole collection, we know if the target element is contained, since one of the iteration steps will have set "wasFound" to true and this is what is returned. If it is not contained, the value stays false.
In general, the reduce accumulator (here "wasFound") is set to the return value of the previous iteration step. And since the first step has no previous one, the third argument to "._reduce" is used as initializer.

Related

forEach/for...in not returning values? [duplicate]

This question already has answers here:
Function with forEach returns undefined even with return statement
(5 answers)
Closed 3 years ago.
So I have a little bit of confusion, I'm solving a challenge on freeCodeCamp.
The challenge reads as follows
Everything Be True
Check if the predicate (second argument) is truthy on all elements of a collection (first argument).
It's solved but I don't understand why I had to take an extra step. My code is as such:
function truthCheck(collection, pre) {
collection.forEach(function(element) {
for (key in element) {
if (!element.hasOwnProperty(pre)) {
return false;
} else if (key === pre) {
if (!Boolean(element[key])) {
return false;
}
}
}
});
return true;
}
truthCheck([
{"user": "Tinky-Winky", "sex": "male"},
{"user": "Dipsy"},
{"user": "Laa-Laa", "sex": "female"},
{"user": "Po", "sex": "female"}
], "sex");
so in this instance it should fail because the 2nd element within collection does not have the sex property. Also you will receive a fail if the pre argument, or in this case sex is not a truthy value.
When these get hit (Which they are, I'm able to tell through console logs) but I figure it would break out of the loop and return from the truthCheck function.....but it doesn't, and it will eventually return true.
I was able to circumvent this by defining a variable and then setting that value to false and then returning the variable at the end. Is there a better way? It seems like these returns should break out of the truthCheck function? Am I missing something?
As the other answers explain, this is meaningless:
collection.forEach(function () {
// do something
return false;
});
because array#forEach simply does not care for the return value of its worker function. It just executes the worker function for each array element.
You could use the worker function to set an outer variable:
function truthCheck(collection, pre) {
var allAreTruthy = true;
collection.forEach(function (elem) {
// if this ever flips allAreTruthy to false, it will stay false
allAreTruthy = allAreTruthy && elem[pre];
});
return allAreTruthy;
}
But there are better ways to express this.
Check if the predicate (second argument) is truthy on all elements of a collection (first argument).
Could be paraphrased as "Every element of the collection has a truthy value at a particular key."
function truthCheck(collection, pre) {
return collection.every(function (elem) { return elem[pre]; });
}
Could be paraphrased as "None of the elements of the collection have a falsy value at a particular key (or are missing the key entirely)."
Or, since an Array#none method does not actually exist, "There aren't some elements of the collection that have a falsy value at a particular key."
function truthCheck(collection, pre) {
return !collection.some(function (elem) { return !elem[pre]; });
}
The advantage of using Array#some is that it stops iterating the array as soon as the condition it seeks for is fulfilled. If your array had many elements this would mean improved performance. For short arrays there's not a lot of difference to using Array#every or Array#forEach.
The above is semantically equivalent to
function truthCheck(collection, pre) {
var i;
for (i = 0; i < collection.length; i++) {
if (!collection[i][pre]) return false;
}
return true;
}
Since JS objects simply return undefined when you access a key that has not been set, a check for hasOwnProperty is superfluous here.
You can't return anything from a ForEach loop. It will return undefined by default.
As the official documentation, Array.prototype.forEach() - JavaScript | MDN, says:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool, use a plain loop instead. If you are testing the array elements for a predicate and need a Boolean return value, you can use every() or some() instead.
So you can use a very simple for..in loop, for example:
for(var c in collection){
// Do whatever you want
}
[collection].forEach of javascript does not work like an ordinary loop. There is no way to prematurely end it not unless you make it throw an exception.
The behavior that you are expecting is what you would expect out of a javascript for loop but since forEach uses callback functions for each looped object, then you are only exiting the callback function instead of the forEach. Also it is worth noting that in your code, the you have a for loop which has a return in it. The return block in this loop only breaks this loop and not the forEach (which I have mentioned earlier, cannot be prematurely terminated unless otherwise)
As you can see forEach is mostly meant to iterate all elements instead of conditional checks per iterated element.
function truthCheck(collection, pre) {
return collection.every(function (person) { return !!person[pre]; });
}
you execute a function on each element of the collection. This function checks that element an returns something. But that returned value does not affect the result of the outer function. Since the outer function does not depend on the inner function your result is allways true.
if you define a variable, set this to false and return that variable at the end would works but it would be inefficient. Lets think of the following scenario. You found one element which does not has the target key. So right now you should return but you can't. You have to working yourself through the whole collection. The forEach loop does not give you the chance to exit without a mess. Thus a better idea is the for loop. Your can exit exit the for loop if found what you were looking for
a slightly easier way would be:
function truthCheck(collection, pre) {
//iterate thrugh your collection
for (var c in collection){
//get keys of element as an array and check if pre is in that array
if( Object.keys(collection[c]).indexOf(pre) == -1){
// pre was not found
return false;
}
}
return true;
}

Why does my code not detect a match?

I have some code that is comparing a string against values in an array:
var blacklistedSites = ['https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8'];
//Returns true if the current site is blacklisted, false otherwise
function isBlacklistedSite(url) {
console.log('Site is ' + url);
blacklistedSites.forEach(function(entry) {
console.log('testing ' + entry);
if (entry == document.URL) {
return true;
}
});
return false;
}
console.log(isBlacklistedSite('https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8'));
This outputs:
Site is https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8
testing https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8
false
Why does isBlacklistedSite() not detect a match?
The reason your code doesn't work is that your:
return true;
effectively does nothing. It just returns from the forEach function, which happens anyway regardless of there being a match, or not. Your return true; does not return from your isBlacklistedSite() function. Your isBlacklistedSite() function always exits with:
return false;
While you could do this using .forEach(), it is a poor choice. The .forEach() method always iterates over every member of the array, regardless of any return value you provide in the forEach function. You would only use it if you were also doing some other operation on every element of the array at the same time. Even then, it might be better to separate out the two different tasks. If you did use it, you would have to keep the detection in a variable defined external to the .forEach(function(){....
Use .indexOf() to test an exact match to an array element
If you want to test for exact matches of Array elements, you can use .indexOf() and test for a value > -1.
For instance:
var blacklistedSites = ['https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8'];
function isBlacklistedSite(url) {
console.log('Site is ' + url);
return blacklistedSites.indexOf(url) > -1;
}
//Test with a known matching value.
console.log(isBlacklistedSite(blacklistedSites[0]));
//Test with a known failing value.
console.log(isBlacklistedSite('foo'));
If you need a more complex test, you can use .some()
var blacklistedSitesRegExes = [/(?:https?:)?\/\/[^/]*www\.google\.com\/.*espv=2/];
function isBlacklistedSite(url) {
console.log('Site is ' + url);
return blacklistedSitesRegExes.some(function(regex){
regex.lastIndex = 0; //Prevent contamination from prior tests
return regex.test(url);
});
}
//Test with a known matching value.
console.log(isBlacklistedSite('https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8'));
//Test with a known failing value.
console.log(isBlacklistedSite('foo'));
With limited availability: .includes() (not for production code)
.includes() does exactly what you want (return a Boolean true/false for an exact match). However, it is not as generally available as .indexOf(). It is recommended not to use it in production code. For Arrays, it does not add much benefit over .indexOf(url) > -1.
Additional methods
There are many additional methods available to Arrays which could be used to determine that you have a match. What you use will depend on your specific needs. As always, you should be mindful of compatibility issues for any method you choose to use. Some of the available methods are (text from MDN):
Array.prototype.every()
Returns true if every element in this array satisfies the provided testing function.
Array.prototype.filter()
Creates a new array with all of the elements of this array for which the provided filtering function returns true.
Array.prototype.find() (no IE)
Returns the found value in the array, if an element in the array satisfies the provided testing function or undefined if not found.
Array.prototype.findIndex() (no IE)
Returns the found index in the array, if an element in the array satisfies the provided testing function or -1 if not found.
Array.prototype.includes() (compatibility issues, including no IE)
Determines whether an array contains a certain element, returning true or false as appropriate.
Array.prototype.indexOf()
Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.
Array.prototype.lastIndexOf()
Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
Array.prototype.some()
Returns true if at least one element in this array satisfies the provided testing function.
Could be used, but not very appropriate:
Array.prototype.forEach()
Calls a function for each element in the array.
Array.prototype.map()
Creates a new array with the results of calling a provided function on every element in this array.
Array.prototype.reduce()
Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
Array.prototype.reduceRight()
Apply a function against an accumulator and each value of the array (from right-to-left) as to reduce it to a single value.

How does this unique() function work

As I was looking at a unique() function I found
which takes an array as argument and returns a new array which contains the unique elements of this array (which means no duplicated items). However I cannot understand the logic of this function. Can somebody explain it to me?
Here is the function:
function unique ( array ) {
return array.filter(function(a){
return !this[a] ? this[a] = true : false;
}, {});
}
I can't really understand the whole code especially the !this[a] ? this[a] = true : false; and the new object ({}) that is passed as the second argument to filter.
Let's start with the filter:
The filter() method creates a new array with all elements that pass
the test implemented by the provided function.
The a is the random number of the array to which you apply the filter. The whole essence is in the following statement:
return !this[a] ? this[a] = true : false;
If the this[a] is true, a has been already processed once and it has been added to this as one of its properties. Otherwise, this[a] is false. So taking its negation result in true and the current a should be returned. Furthermore this[a] would be set to true and then we proceed to the next a.
The following snippet will help you grasp what filter does:
var numbers = [1,2,3,4,5];
var filteredNumbers = numbers.filter(function(number){
console.log(number);
return number > 2;
});
console.log(filteredNumbers);
And the following snippet will show you in action what happens in unique function:
function unique ( array ) {
return array.filter(function(a){
console.log(this);
return !this[a] ? this[a] = true : false;
}, {});
}
var array = [1,2,3,1,2,3,4,5,5,6];
console.log(unique(array));
I understand the basic logic of filter but what i dont is the {}
passed as a 2nd argument and how each value is added to a new array
with !this[a]
The second argument is an optional value that you can pass to the filter method and it can be used as the this, when your callback would be executed (check the link I mentioned at the beginning about filter). You pass there an empty object. When you use the keyword this inside your callback, your refer this object. This is why the first time that code gets in this method returns {}. Check the first line of the output of the second snippet.
I will explain the second part of your question based on the second snippet. The first time you get in you have an empty object (I refer to this) and the first number processed is 1. So this1 would be undefined. So !this[1] would be true. Hence the first part after the ? is executed which is an assignment
this[1] = true.
Now this acquired its first key, 1, with value true. Furthermore, 1 would be returned from filter. The same happens with 2 and 3. When we arrive at 1 the
!this[1]
is false, since this[1] is true. So false is returned and the 1 now would not be added to the array that would be returned after all elements of array have been processed.
Basically, .filter would call the callBack function by supplying the individual values of the iterating array. If the callBack returns a value that resolves to true then that value will be collected, else that particular value will be ignored.
Here the second argument of filter has been used. That second argument will be used as a context(this) while calling the callBack internally. So here in your code, the passed object will be added with the array's value as property for each iteration. And in the consecutive iterations, the code will check the current value is available as a property in the initially passed object. If available then that ternary operator would return false, otherwise true.
Hence the unique values will be returned from the filter function.
Array.filter will only grab elements of the array when the function passed return truthy.
For each element of the array it is doing
return !this[a] // if value is not yet on this
? this[a] = true // add value to this and return true (grab element)
: false; // value was already cached, so return false (don't grab)
So it will only return 1 of each
Other answers have explained basically how this works. But a couple of points:
First, instead of return !this[a] ? this[a] = true : false;, it would be more concise to write
!this[a] && (this[a] = true)
Second, this code has the flaw that it only works on elements which can serve as keys into an object--basically strings or numbers. That is why using Set is better:
Array.from(new Set(array))
The above will work on any primitive or object.
Third, this approach does not work properly with strings and numbers. If the number 1 is present, it will filter out the string "1".
const uniq = a => a.filter(x => !this[x] && (this[x] = true), {});
console.log(uniq([1, '1']));
The reason is that object keys are string-valued.
Finally, IMHO this code is a bit too tricky for its own good. Most developers, even experienced ones, would stop and scratch their heads for a minute before figuring it out. Less experienced developers would have to consult the documentation for the thisArg parameter to Array#filter before being able to understand it. The ternary operator with the assignment inside is also a bit cryptic. I'd go ahead and write it out as
if (this[x]) return false;
this[x] = true;
return true;

JS - Using .every method on an array of objects

I need to know if it's possible to use the array.every method on multidimensional arrays.
My array looks like this:
tabs=[
{label: string, icon: icon, routerLink: link},
{label: string, icon: icon, routerLink: link},
{label: string, icon: icon, routerLink: link}]
I need to know whether every LABEL inside tabs is unequal to a specific label.
I'd really appreciate it, if you could offer a detailed answer, since I'm a newbie-programmer and want to understand what I'm doing! But feel free to answer anyway. :)
Thanks so far!
EDIT: I'm using this method to add Tabs to my Tabmenu(ng2, primeng):
addTab(id: string) {
if (id === 'linechart') {
this.tab = {
label: 'NW-Details',
icon: 'fa-area-chart',
routerLink: ['/nwdetails']
}
TABS.push(this.tab);
}
if (id === 'piechart') {
this.tab = {
label: 'IO-Details',
icon: 'fa-pencil',
routerLink: ['/iodetails']
}
TABS.push(this.tab)
}
}
Whereas TABS is typeof MenuItem[] offered by primeng, tab is any.
Every time I doubleclick a chart, this one gets invoked an a new tab is pushed into my menu. Now I want to check wheter a tab with certain label is already opened, so that it does not open again.
I tried using for loops combined with if
for (i = 0; i < TABS.length; i++) {
if (TABS[i].label !== 'NW-Details') {
this.tab = {
label: 'NW - Details',
icon: 'fa - area - chart'
TABS.push(this.tab)
}
}
But this opens a new Tab every time it is unequal, so that actually more then one tab gets opened on dblclick if there are more tabs already opened.
You can use Array#every method.
tabls.every(function(v){
return v.label !== someValue
})
Check MDN docs :
The every method executes the provided callback function once for each element present in the array until it finds one where callback returns a falsy value (a value that becomes false when converted to a Boolean). If such an element is found, the every method immediately returns false. Otherwise, if callback returned a true value for all elements, every will return true. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.
or use Array#some method
!tabls.some(function(v){
return v.label === someValue
})
Check MDN docs:
some() executes the callback function once for each element present in the array until it finds one where callback returns a truthy value (a value that becomes true when converted to a Boolean). If such an element is found, some() immediately returns true. Otherwise, some() returns false. callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.
Signature of Array functions is something like this:
Array.prototype.functionName = function(callback){
for (var i = 0; i< this.length; i++){
if(callback(this[i]))
// do something.
else
// do something.
}
}
It expects a callback which returns boolean value i.e. true or false(default).
every vs some
Though both can be used in a similar fashion, they should be used in a way they convey proper meaning.
var d = [1,2,3,4,5];
var valid = d.every(function(num){ return !isNaN(num); });
console.log(valid)
Above code means "valid if all elements are numbers". You can use .some but then that will change the meaning to "invalid if anyone is not number". You should use them based on the current context of code to improve readability.
References
Array.every - Polyfill
Array.every - Browser Compatibility

a few questions when iterator the arraylike object in javascript

In my application,I need to do some iteration with the array-like object.
So I write a custom util method:
Util.forEach=function(iterator,callback,context){
if(iterator.length){
for(var i=0;i<iterator.length;i++){
var val=iterator[i];
callback && callback.call(context||null,val,i);
}
}
}
Then I meet some problems:
Suppose I have an array: var ary=[1,2,3,4,5];
1. how to break the loop?
For example, if I want to find if value '2' is included in the array,I may try this:
Util.forEach(ary,function(value,index){
if(value==2){
//find mached value,now I should break and return this value.
//but I can not use 'break' in this context.
}
});
2. remove value of the array when iterator
If I want to remove value '2' of the array,I may try this:
Util.forEach(ary,function(value,index){
if(value==2){
//delete it.
ary.splice(i,1);
// I wonder if this is the safe way?
}
});
Since in java we can not do this when iterate an array,it will cause the concurrency exception.
Though I run the above code without any error,but I am not sure if it is the best practice?
Generally, you can break out from a forEach-like method by using try-catch and throw.
var value = 'Not Found';
try {
Util.forEach(ary, function(value, index) {
if(value == 2) {
throw value; //<-- Throw the found result: Terminates current forEach
}
});
} catch (found) { // Catch the thrown result
value = found;
}
The first issue can be solved by checking the return value from the callback and stopping the iteration if it returns false. Then you can stop the iteration by returning false from the callback. Returning anything other than false (including returning nothing) will continue the iteration. jQuery uses this return value technique to cancel the iteration in their .each() iterator. Adding that into your code, it would look like this:
Util.forEach=function(iterator,callback,context){
if (iterator.length && callback) {
for(var i = 0; i < iterator.length; i++){
var val=iterator[i];
if (callback.call(context||window,val,i) === false) {
break;
}
}
}
}
In the MDN documentation for forEach, you can see a sample implementation.
On the second issue, this type of implementaation does not permit insertion or deletion of elements at or before the iteration point because that will cause some elements to get skipped in the iteration or some objects to get iterated multiple times. The obvious way around that would involve making a copy of the object before iterating which is not efficient when not needed.
how to break the loop?
You're not supposed to break from forEach. That's why it's called "for each", and not "for some". "Breakable" JS iterators are every() (stops when the callback returns false) and some() (stops when the callback returns true).
Looking at your code once again it makes me think you actually want an indexOf kind of method, and not an iterator.
remove value of the array when iterator
Iterators shouldn't make any changes of the underlying array. You have to implement filter() and use it to generate a new array.
See js iteration methods for more details.

Categories

Resources