I'm currently learning javascript by following the book "you dont know js".
In the section "type & grammer", when discussing implicit vs explicit boolean convertion, the author mentioned
//come up with a function that make sure only one argument is truthy
//implicit convertion
function onlyOne() {
var sum = 0;
for (var i=0; i < arguments.length; i++) {
// skip falsy values. same as treating
// them as 0's, but avoids NaN's.
if (arguments[i]) {
sum += arguments[i];
}
}
return sum == 1;
}
//explicit convertion
function onlyOne() {
var sum = 0;
for (var i=0; i < arguments.length; i++) {
sum += Number( !!arguments[i] );
}
return sum === 1;
}
Is the explicit coercion form of this utility "better"? It does avoid
the NaN trap as explained in the code comments. But, ultimately, it
depends on your needs. I personally think the former version, relying
on implicit coercion is more elegant (if you won't be passing
undefined or NaN), and the explicit version is needlessly more
verbose.
My question is, what NaN trap is the author talking about? I thought when undefined and NaN is converted to boolean value, regardless of whether it is converted implicitly or explicitly, they both results in false. And passing undefined and NaN to the implicit function is ok, right?
I think that an example of a real explicit check would be...
function onlyOne() {
var sum = 0;
for (var i=0; i < arguments.length; i++) {
sum += Boolean( arguments[i] );
}
return sum == 1;
}
This will of course avoid / guard against NaN and should return false if no arguments are present; if no arguments are truthy and of course -if more than one arguments are truthy.
the 2nd example always avoid NaN,because Number(!!string) and Number(!!object) are both converted to 1
enter image description here
Related
Is it safe to use this kind of loop in Javascript?
denseArray = [1,2,3,4,5, '...', 99999]
var x, i = 0
while (x = denseArray[i++]) {
document.write(x + '<br>')
console.log(x)
}
document.write('Used sentinel: ' + denseArray[i])
document.write('Size of array: ' + i)
It is shorter than a for-loop and maybe also more effective for big arrays, to use a built in sentinel. A sentinel flags the caller to the fact that something rather out-of-the-ordinary has happened.
The array has to be a dense array to work! That means there are no other undefined value except the value that come after the last element in the array. I nearly never use sparse arrays, only dense arrays so that's ok for me.
Another more important point to remember (thank to #Jack Bashford reminded) is that's not just undefined as a sentinel. If an array value is 0, false, or any other falsy value, the loop will stop. So, you must be sure that the data in the array does not have falsy values that is 0, "", '', ``, null, undefined and NaN.
Is there something as a "out of range" problem here, or can we consider arrays in Javascript as "infinite" as long memory is not full?
Does undefined mean browsers can set it to any value because it is undefined, or can we consider the conditional test always to work?
Arrays in Javascript is strange because "they are Objects" so better to ask.
I can't find the answer on Stackoverflow using these tags: [javascript] [sentinel] [while-loop] [arrays] . It gives zero result!
I have thought about this a while and used it enough to start to worry. But I want to use it because it is elegant, easy to see, short, maybe effective in big data. It is useful that i is the size of array.
UPDATES
#Barmar told: It's guaranteed by JS that an uninitialized array
element will return the value undefined.
MDN confirms: Using
an invalid index number returns undefined.
A note by #schu34: It is better to use denseArray.forEach((x)=>{...code}) optimized for it's use and known by devs. No need to encounter falsy values. It has good browser support.
Even if your code won't be viewed by others later on, it's a good idea to make it as readable and organized as possible. Value assignment in condition testing (except for the increment and decrement operators) is generally a bad idea.
Your check needs to be a bit more specific, too, as [0, ''] both evaluate to false.
denseArray = [1,2,3,4,5, '...', 99999]
for(let i = 0; i < denseArray.length; i++) {
let x = denseArray[i]
document.write(x + '<br>');
console.log(x);
if (/* check bad value */) break;
}
document.write('Used sentinel: ' + denseArray[i])
document.write('Size of array: ' + i)
From my experience it's usually not worth it to save a few lines if readability or even reliability is the cost.
Edit: here's the code I used to test the speed
const arr = [];
let i;
for (i = 0; i < 30000000; i++) arr.push(i.toString());
let x;
let start = new Date();
for(i = 0; i < arr.length; i++) {
x = arr[i];
if (typeof x !== 'string') break;
}
console.log('A');
console.log(new Date().getTime() - start.getTime());
start = new Date();
i = 0;
while (x = arr[i++]) {
}
console.log('B');
console.log(new Date().getTime() -start.getTime());
start = new Date();
for(i = 0; i < arr.length; i++) {
x = arr[i];
if (typeof x !== 'string') break;
}
console.log('A');
console.log(new Date().getTime() - start.getTime());
start = new Date();
i = 0;
while (x = arr[i++]) {
}
console.log('B');
console.log(new Date().getTime() -start.getTime());
start = new Date();
for(i = 0; i < arr.length; i++) {
x = arr[i];
if (typeof x !== 'string') break;
}
console.log('A');
console.log(new Date().getTime() - start.getTime());
start = new Date();
i = 0;
while (x = arr[i++]) {
}
console.log('B');
console.log(new Date().getTime() -start.getTime());
The for loop even has an extra if statement to check for bad values, and still is faster.
Searching for javascript assignment in while gave results:
Opinions vary from it looks like a common error where you try to compare values to If there is quirkiness in all of this, it's the for statement's wholesale divergence from the language's normal syntax. The for is syntactic sugar adding redundance. It has not outdated while together with if-goto.
The question in first place is if it is safe. MDN say: Using an invalid index number returns undefined in Array, so it is a safe to use. Test on assignments in condition is safe. Several assignments can be done in the same, but a declaration with var, let or const does not return as assign do, so the declaration has to be outside the condition. Have a comment abowe to explain to others or yourself in future that the array must remain dense without falsy values, because otherwise it can bug.
To allow false, 0 or "" (any falsy except undefined) then extend it to: while ((x = denseArray[i++]) !== undefined) ... but then it is not better than an ordinary array length comparision.
Is it useful? Yes:
while( var = GetNext() )
{
...do something with var
}
Which would otherwise have to be written
var = GetNext();
while( var )
{
...do something
var = GetNext();
}
In general it is best to use denseArray.forEach((x) => { ... }) that is well known by devs. No need to think about falsy values. It has good browser support. But it is slow!
I made a jsperf that showed forEach is 60% slower than while! The test also show the for is slightly faster than while, on my machine! See also #Albert answer with a test show that for is slightly faster than while.
While this use of while is safe it may not be bugfree. In time of coding you may know your data, but you don't know if someone copy-paste the code to use on other data.
I had this task: "Use the rest parameter to create an average() function that calculates the average of an unlimited amount of numbers". And wen I ran this code, I have 0 answer.
function average(...nums) {
let total = 0;
for (const num of nums) {
total += num;
len = nums.length;
}
total = total/len;
return total;
}
console.log(average());
But why? Why when I divide by the undeclared variable 0 the answer is 0 and not NaN? It's NaN if I run the following code (with declared variable).
function average(...nums) {
let total = 0,
len;
for (const num of nums) {
total += num;
len = nums.length;
}
total = total / len;
return total;
}
console.log(average());
P.s. the right answer in the task is 0.
The reason this happens is that in loose mode (i.e. not strict mode), assigning to undeclared variables makes them global.
So you run average() once with some arguments, which sets the global len to whatever number, and returns the correct average. (This is also why the udacity tests pass, the order of the tests matter. If they had run the empty input test first, it would have failed)
You then run it again with no arguments, since setting len happens inside the loop, it doesn't happen again because a for..of loop over an empty array doesn't even enter, so then the result of 0 (the sum) divided by whatever (the previous len, set to any number) will be 0.
If you had run it in strict mode, you would have gotten an error about assigning to undeclared variables, even when you run it with arguments.
This is why you always:
Use strict mode (add 'use strict' to your top level function or file, if you use webpack or a similar bundler, you have this by default)
Declare your variables.
A correct version of this, assuming they want an empty input to return 0, is as follows:
function average(...nums) {
'use strict'; // enter strict mode
if (nums.length === 0) { return 0; }
let total = 0;
for (const num of nums) {
total += num;
}
return total / nums.length;
}
console.log(average());
You must directly define the behavior for empty input, because strictly speaking, the average of a set of 0 numbers is undefined.
In the for loop: counter < (x.lenght) is spelled wrong, but the function returns zero. When corrected to x.length the function returns the correct number of Bs, 3. 1) Why is zero being returned? 2) Why does javascript not catch this error? 3) For the future, anything I can do to make sure these types of errors are caught?
function countBs(x){
var lCounter = 0;
for (var counter = 0; counter < (x.lenght); counter++){
if((x.charAt(counter)) == "B"){
lCounter++;
}
}
return lCounter;
}
console.log(countBs("BCBDB"));
Accessing x.lenght is returning undefined causing the for loop to terminate immediately. Therefore the initial value of lCounter is returned.
You can check for the existence of a property in an object by using the in keyword like so:
if ( 'lenght' in x ) {
...
x.lenght is returning undefined. Comparison operators perform automatic type juggling, so undefined is converted to a number to perform the comparison, and it converts to NaN. Any comparison with NaN returns false, so the loop ends.
Javascript doesn't catch this error because it uses loose typing, automatically converting types as needed in most cases.
There's no easy way to ensure that typos like this are caught. A good IDE might be able to detect it if you provide good type comments.
JavaScript does all kind of crazy coversions, instead of throwing an error: https://www.w3schools.com/js/js_type_conversion.asp
'undefined' in particular becomes NaN when necessary (very last line of the very last table), which results in 'false' when compared to a number (regardless of <, >, <=, >=, == or !=, they all fail, NaN does not even equal to itself).
If you want to catch or log an error to make sure your variable property is defined. Please see code below:
function countBs(x){
var lCounter = 0;
if(typeof x.lenght == 'undefined')
{
console.log('Undefined poperty lenght on variable x');
return 'Error catch';
}
for (var counter = 0; counter < (x.lenght); counter++){
if((x.charAt(counter)) == "B"){
lCounter++;
}
}
return lCounter;
}
console.log(countBs("BCBDB"));
To catch this particular error, set lCounter to -1 instead of 0.
That will ensure that the loop will run at least once if the for condition is correct.
You can return (or throw) an error if the loop isn't entered.
Otherwise, return lCounter + 1 to account for the initialization of -1.
function countBs(x) {
var lCounter = -1;
for (var counter = 0; counter < (x.lenght); counter++) {
if((x.charAt(counter)) == "B") {
lCounter++;
}
}
if(lCounter == -1) {
return 'Error';
} else {
return lCounter + 1;
}
}
var avg = function()
{
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++)
{
sum += arguments[i];
}
return sum / arguments.length;
}
When I try to call this like:
var average = avg(2,3,5);
average; // It works fine;
But how do I call it without assigning to a variable?
If anybody can give any suggestion it will be delightful..Thanks.
You'd simply call it like this:
avg(2, 3, 5);
If you want to see the result, put it in an alert call:
alert( avg(2, 3, 5) );
You don't need to put the result from calling the function in a variable, you can do whatever you like with it.
For example, use it as the value in an alert:
alert(avg(2,3,5));
You can use it in another expression:
var message = "The average is " + avg(2,3,5);
You can use it directly in another call:
someFunction(avg(2,3,5));
You can even throw the result away by not doing anything with it, even if that's not useful in this specific situation:
avg(2,3,5);
If you don't put the result into a variable or in a compatible context, this function cannot output anything, which makes it difficult to use unless you make it output the result. Try this :
var avg = function()
{
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++)
{
sum += arguments[i];
}
var retvalue = sum / arguments.length;
consoloe.log("avg: "+retvalue);
return retvalue ;
}
Then it may help you to see whenever the function is called or not.
You need to understand the concept of expressions.
Every expression as a whole represents one value. An expression can be made up of multiple subexpressions that are combined in some manner (for example with operators) to yield a new value.
For instance:
3 is an expression (a literal, to be specific) that denotes the numeric value three.
3 + 4 is an expression, made up of two literal expressions, that as a whole yields the value 7
When you assign a value to a variable – as in var average = – the right hand side of the =-operator needs to be an expression, i.e. something that yields a value.
As you have observed, average will have been assigned the value five. It thus follows, that avg(2, 3, 5) must itself be an expression that evaluated to the value 5.
average itself is an expression, denoting the current value of said variable.
The most important thing to take away from this is, that avg() is in no way connected to var average =. avg() stands on its own, you can just think it like an ordinary value such as 5 (there are other differences of course).
If you have understood the concept of expressions and values, it should be clear that if you can do
var average = avg(2,3,5);
average; // It works fine;
You can also do
avg(2,3,5);
I have an array of arrays. The inner array is 16 slots, each with a number, 0..15. A simple permutation.
I want to check if any of the arrays contained in the outer array, have the same values as
a test array (a permutation of 16 values).
I can do this easily by something like so:
var containsArray = function (outer, inner) {
var len = inner.length;
for (var i=0; i<outer.length; i++) {
var n = outer[i];
var equal = true;
for (var x=0; x<len; x++) {
if (n[x] != inner[x]) {
equal = false;
break;
}
}
if (equal) return true;
}
return false;
}
But is there a faster way?
Can I assign each permutation an integral value - actually a 64-bit integer?
Each value in a slot is 0..15, meaning it can be represented in 4 bits. There are 16 slots, which implies 64 total bits of information.
In C# it would be easy to compute and store a hash of the inner array (or permutation) using this approach, using the Int64 type. Does Javascript have 64-bit integer math that will make this fast?
That's just about as fast as it gets, comparing arrays in javascript (as in other languages) is quite painful. I assume you can't get any speed benefits from comparing the lengths before doing the inner loop, as your arrays are of fixed size?
Only "optimizations" I can think of is simplifying the syntax, but it won't give you any speed benefits. You are already doing all you can by returning as early as possible.
Your suggestion of using 64-bit integers sounds interesting, but as javascript doesn't have a Int64 type (to my knowledge), that would require something more complicated and might actually be slower in actual use than your current method.
how about comparing the string values of myInnerArray.join('##') == myCompareArray.join('##'); (of course the latter join should be done once and stored in a variable, not for every iteration like that).
I don't know what the actual performance differences would be, but the code would be more terse. If you're doing the comparisons a lot of times, you could have these values saved away someplace, and the comparisons would probably be quicker at least the second time round.
The obvious problem here is that the comparison is prone to false positives, consider
var array1 = ["a", "b"];
var array2 = ["a##b"];
But if you can rely on your data well enough you might be able to disregard from that? Otherwise, if you always compare the join result and the lengths, this would not be an issue.
Are you really looking for a particular array instance within the outer array? That is, if inner is a match, would it share the same reference as the matched nested array? If so, you can skip the inner comparison loop, and simply do this:
var containsArray = function (outer, inner) {
var len = inner.length;
for (var i=0; i<outer.length; i++) {
if (outer[i] === inner) return true;
}
return false;
}
If you can't do this, you can still make some headway by not referencing the .length field on every loop iteration -- it's an expensive reference, because the length is recalculated each time it's referenced.
var containsArray = function (outer, inner) {
var innerLen = inner.length, outerLen = outer.length;
for (var i=0; i<outerLen; i++) {
var n = outer[i];
var equal = true;
for (var x=0; x<innerLen; x++) {
if (n[x] != inner[x]) {
equal = false;
}
}
if (equal) return true;
}
return false;
}
Also, I've seen claims that loops of this form are faster, though I haven't seen cases where it makes a measurable difference:
var i = 0;
while (i++ < outerLen) {
//...
}
EDIT: No, don't remove the equal variable; that was a bad idea on my part.
the only idea that comes to me is to push the loop into the implementation and trade some memory for (speculated, you'd have to test the assumption) speed gain, which also relies on non-portable Array.prototype.{toSource,map}:
var to_str = function (a) {
a.sort();
return a.toSource();
}
var containsString = function (outer, inner) {
var len = outer.length;
for (var i=0; i<len; ++i) {
if (outer[i] == inner)
return true;
}
return false;
}
var found = containsString(
outer.map(to_str)
, to_str(inner)
);
var containsArray = function (outer, inner) {
var innerLen = inner.length,
innerLast = inner.length-1,
outerLen = outer.length;
outerLoop: for (var i=0; i<outerLen; i++) {
var n = outer[i];
for (var x = 0; x < innerLen; x++) {
if (n[x] != inner[x]) {
continue outerLoop;
}
if (x == innerLast) return true;
}
}
return false;
}
Knuth–Morris–Pratt algorithm
Rumtime: O(n), n = size of the haystack
http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm