I recently was told the triple equal operator is more efficient because it will not try and convert any of the variables before comparing. Never thought about performance implications.
Does anyone have information on the performance of various comparison operators or other common JS operators that may slow a framework down?
Are there complex object types where this is a factor?
I realize this may be relevant to edge cases only, but without concrete data, it's hard to tell.
Should be, since it performs less operations. == performs type conversions before comparing if both arguments have different types. Pseudocode below.
function doubleEquals(a, b) {
[a, b] = coerceTypes(a, b)
return compareSameType(a, b)
}
function tripleEquals(a, b) {
if (!areSameType(a, b)) {
return false
}
return compareSameType(a, b)
}
If you compare == and === with two inputs of the same type though, the difference should be very small to none since no conversion would be required.
This is not really an answer to my question, but only one edge example:
For 100K or more comparisons between a string and a float (ex: 0.035 == "0.035") it seems there is certainly a noticeable performance difference of around 1000%, or 10x. As tested on Chrome v77.
If there is a framework doing a lot of comparisons behind the scenes, I could see how this could effect how "crisp" it feels. But it could also be completely irrelevant. I don't have any data for framework use.
See the code below for a an example.
print('string vs float comparison operator performance')
let start = Date.now();
let loopCount = 10000000;
let appxLoopCostRatio = 2857;
for (let i=0; i < loopCount; i++) {
let x = Math.random();
let y = Math.random().toString();
if (x == y) count++;
}
let mid = Date.now();
for (let i=0; i < loopCount; i++) {
let x = Math.random();
let y = Math.random().toString();
if (x === y) count++;
}
let mid2 = Date.now();
//no comparison
for (let i=0; i < loopCount; i++) {
let x = Math.random();
let y = Math.random().toString();
}
let finish = Date.now();
let a, b, cost;
a = mid-start;
b = mid2-mid;
cost = finish-mid2;
print('runtime == comparison:', mid-start);
print('runtime === comparison:', mid2-mid);
print('appx cost of variable generation:', finish-mid2);
let expectedCost = (loopCount / appxLoopCostRatio);
let TcCost = (b-cost < (expectedCost * 0.05)) ? (expectedCost * 0.05) : b-cost;
print(` === is more efficient by ${Math.floor((a-cost)/TcCost) * 100 }%`)
Related
I'm having performance issues when trying to check whether integer n is a perfect square (sqrt is a whole number) when using BigInt. Using normal numbers below Number.MAX_SAFE_INTEGER gives reasonable performance, but attempting to use BigInt even with the same number range causes a huge performance hit.
The program solves the Battle of Hastings perfect square riddle put forth by Sam Loyd whereby my program iterates over the set of real numbers n (in this example, up to 7,000,000) to find instances where variable y is a whole number (perfect square). I'm interested in the original square root of one of the 13 perfect squares where this condition is satisfied, which is what my code generates (there's more than one).
Assuming y^2 < Number.MAX_SAFE_INTEGER which is 2^53 – 1, this can be done without BigInt and runs in ~60ms on my machine:
const limit = 7_000_000;
var a = [];
console.time('regular int');
for (let n = 1; n < limit; n++) {
if (Math.sqrt(Math.pow(n, 2) * 13 + 1) % 1 === 0)
a.push(n);
}
console.log(a.join(', '));
console.timeEnd('regular int');
Being able to use BigInt would mean I could test for numbers much higher than the inherent number variable limit 2^53 - 1, but BigInt seems inherently slower; unusably so. To test whether a BigInt is a perfect square, I have to use a third party library as Math.sqrt doesn't exist for BigInt such that I can check if the root is perfect, as all sqrt returns a floor value. I adapted functions for this from a NodeJS library, bigint-isqrt and bigint-is-perfect-square.
Thus, using BigInt with the same limit of 7,000,000 runs 35x slower:
var integerSQRT = function(value) {
if (value < 2n)
return value;
if (value < 16n)
return BigInt(Math.sqrt(Number(value)) | 0);
let x0, x1;
if (value < 4503599627370496n)
x1 = BigInt(Math.sqrt(Number(value))|0) - 3n;
else {
let vlen = value.toString().length;
if (!(vlen & 1))
x1 = 10n ** (BigInt(vlen / 2));
else
x1 = 4n * 10n ** (BigInt((vlen / 2) | 0));
}
do {
x0 = x1;
x1 = ((value / x0) + x0) >> 1n;
} while ((x0 !== x1 && x0 !== (x1 - 1n)));
return x0;
}
function perfectSquare(n) {
// Divide n by 4 while divisible
while ((n & 3n) === 0n && n !== 0n) {
n >>= 2n;
}
// So, for now n is not divisible by 2
// The only possible residual modulo 8 for such n is 1
if ((n & 7n) !== 1n)
return false;
return n === integerSQRT(n) ** 2n;
}
const limit = 7_000_000;
var a = [];
console.time('big int');
for (let n = 1n; n < limit; n++) {
if (perfectSquare(((n ** 2n) * 13n) + 1n))
a.push(n);
}
console.log(a.join(', '));
console.timeEnd('big int');
Ideally I'm interested in doing this with a much higher limit than 7 million, but I'm unsure whether I can optimise the BigInt version any further. Any suggestions?
You may be pleased to learn that some recent improvements on V8 have sped up the BigInt version quite a bit; with a recent V8 build I'm seeing your BigInt version being about 12x slower than the Number version.
A remaining challenge is that implementations of BigInt-sqrt are typically based on Newton iteration and hence need an estimate for a starting value, which should be near the final result, so about half as wide as the input, which is given by log2(X) or bitLength(X). Until this proposal gets anywhere, that can best be done by converting the BigInt to a string and taking that string's length, which is fairly expensive.
To get faster right now, #Ouroborus' idea is great. I was curious how fast it would be, so I implemented it:
(function betterAlgorithm() {
const limit = 7_000_000n;
var a = [];
console.time('better algorithm');
let m = 1n;
let m_squared = 1n;
for (let n = 1n; n < limit; n += 1n) {
let y_squared = n * n * 13n + 1n;
while (y_squared > m_squared) {
m += 1n;
m_squared = m * m;
}
if (y_squared === m_squared) {
a.push(n);
}
}
console.log(a.join(', '));
console.timeEnd('better algorithm');
})();
As a particular short-term detail, this uses += 1n instead of ++, because as of today, V8 hasn't yet gotten around to optimizing ++ for BigInts. This difference should disappear eventually (hopefully soon).
On my machine, this version takes only about 4x as much time as your original Number-based implementation.
For larger numbers, I would expect some gains from replacing the multiplications with additions (based on the observation that the delta between consecutive square numbers grows linearly), but for small-ish upper limits that appears to be a bit slower. If you want to toy around with it, this snippet describes the idea:
let m_squared = 1n; // == 1*1
let m_squared_delta = 3n; // == 2*2 - 1*1
let y_squared = 14n; // == 1*1*13+1
let y_squared_delta = 39n; // == 2*2*13+1 - 1*1*13+1
for (let n = 1; n < limit; n++) {
while (y_squared > m_squared) {
m_squared += m_squared_delta;
m_squared_delta += 2n;
}
if (y_squared === m_squared) {
a.push(n);
}
y_squared += y_squared_delta;
y_squared_delta += 26n;
}
The earliest where this could possibly pay off is when the results exceed 2n**64n; I wouldn't be surprised if it wasn't measurable before 2n**256n or so.
Thanks to Nina I have a code to compare two sentences word by word and return the number of word matches like this:
function includeWords(wanted, seen) {
var wantedMap = wanted.split(/\s+/).reduce((m, s) => m.set(s, (m.get(s) || 0) + 1), new Map),
wantedArray = Array.from(wantedMap.keys()),
count = 0;
seen.split(/\s+/)
.forEach(s => {
var key = wantedArray.find(t => s === t || s.length > 3 && t.length > 3 && (s.startsWith(t) || t.startsWith(s)));
if (!wantedMap.get(key)) return;
console.log(s, key)
++count;
wantedMap.set(key, wantedMap.get(key) - 1);
});
return count;
}
let matches = includeWords('i was sent to earth to protect you introduced', 'they\'re were protecting him i knew that i was aware introducing');
console.log('Matched words: ' + matches);
The code works fine, but there is still one issue:
What if we want to return a match for introduced and introducing too?
If you want the program to consider the words 'introduce' and 'introducing' as a match, it would amount to a "fuzzy" match (non binary logic). One simple way of doing this would require more code, the algorithm of which would possibly resemble
Take 2 words that you wish to match, tokenize into ordered list
of letters
Compare positionally the respective letters, i.e
match a[0]==b[0]? a[1]==b[1] where a[0] represents the first letter
of the first word and b[0] represents the first tokenized
letter/character potential match candidate
KEep a rolling numeric count of such positional matches. In this case it is 8 (introduc).
divide by word length of a = 8/9 call this f
divide by word length of b = 8/11 call this g
Provide a threshold value beyond which the program will consider it a match. eg. if you say anything above 70% in BOTH f and g can be
considered a match - viola, you have your answer!
Please note that there is some normalization also needed to prevent low length words from becoming false positives. you can add a constraint that the aforementioned calculation applies to words with at least 5 letters(or something to that effect!
Hope this helps!!
Regards,
SR
You could calculate similarites for a word pair and get a relation how many characters are similar bei respecting the length of the given word and the wanted pattern.
function getSimilarity(a, b) {
var i = 0;
while (i < a.length) {
if (a[i] !== b[i]) break;
i++;
}
return i / Math.max(a.length, b.length);
}
console.log(getSimilarity('abcdefghij', 'abc')); // 0.3
console.log(getSimilarity('abcdefghij', 'abcdef')); // 0.6
console.log(getSimilarity('abcdefghij', 'abcdefghij')); // 1
console.log(getSimilarity('abcdef', 'abcdefghij')); // 0.6
console.log(getSimilarity('abcdefghij', 'abcdef')); // 0.6
console.log(getSimilarity('abcdefghij', 'xyz')); // 0
console.log(getSimilarity('introduced', 'introducing')); // 0.7272727272727273
Here's a quick fix solution.
It's not intended as a complete solution.
Since the English language has more than a few quirks that would almost require an AI to understand the language.
First add a function that can compare 2 words and returns a boolean.
It'll also make it easier to test for specific words, and adapt to what's really needed.
For example, here's a function that does the simple checks that were already used.
Plus an '...ed' versus '...ing' check.
function compareWords (word1, word2) {
if (word1 === word2)
return true;
if (word1.length > 3 && word2.length > 3) {
if (word1.startsWith(word2) || word2.startsWith(word1))
return true;
if (word1.length > 4 && word2.length > 4) {
if (/(ing|ed)$/.test(word1) && word1.replace(/(ing|ed)$/, 'inged') === word2.replace(/(ing|ed)$/, 'inged'))
return true;
}
}
return false;
}
//
// tests
//
let words = [
["same", "same"],
["different", "unsame"],
["priced", "pricing"],
["price", "priced"],
["producing", "produced"],
["produced", "producing"]
];
words.forEach( (arr, idx) => {
let word1= arr[0];
let word2= arr[1];
let isSame = compareWords(word1, word2);
console.log(`[${word1}] ≈ [${word2}] : ${isSame}`);
});
Then use it in the code you already have.
...
seen.split(/\s+/)
.forEach(s => {
var key = wantedArray.find(t => compareWords(t, s));
...
Regarding string similarity, here's f.e. an older SO post that has some methods to compare strings : Compare Strings Javascript Return %of Likely
I have implemented this, it seems to work fine. any suggestions would be appreciated..
let speechResult = "i was sent to earth to introducing protect yourself introduced seen";
let expectSt = ['they were protecting him knew introducing that you i seen was aware seen introducing'];
// Create arrays of words from above sentences
let speechResultWords = speechResult.split(/\s+/);
let expectStWords = expectSt[0].split(/\s+/);
function includeWords(){
// Declare a variable to hold the count number of matches
let arr = [];
for(let a = 0; a < speechResultWords.length; a++){
for(let b = 0; b < expectStWords.length; b++){
if(similarity(speechResultWords[a], expectStWords[b]) > 69){
arr.push(speechResultWords[a]);
console.log(speechResultWords[a] + ' includes in ' + expectStWords[b]);
}
} // End of first for loop
} // End of second for loop
let uniq = [...new Set(arr)];
return uniq.length;
};
let result = includeWords();
console.log(result)
// The algorithmn
function similarity(s1, s2) {
var longer = s1;
var shorter = s2;
if (s1.length < s2.length) {
longer = s2;
shorter = s1;
}
var longerLength = longer.length;
if (longerLength == 0) {
return 1.0;
}
return (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength)*100;
}
function editDistance(s1, s2) {
s1 = s1.toLowerCase();
s2 = s2.toLowerCase();
var costs = new Array();
for (var i = 0; i <= s1.length; i++) {
var lastValue = i;
for (var j = 0; j <= s2.length; j++) {
if (i == 0)
costs[j] = j;
else {
if (j > 0) {
var newValue = costs[j - 1];
if (s1.charAt(i - 1) != s2.charAt(j - 1))
newValue = Math.min(Math.min(newValue, lastValue),
costs[j]) + 1;
costs[j - 1] = lastValue;
lastValue = newValue;
}
}
}
if (i > 0)
costs[s2.length] = lastValue;
}
return costs[s2.length];
}
Problem Set- Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string.
My algorithm seems to work fine for all but one test case of palindrome from 1-9
UPDATE
Javascript has a parse method but I don't want to use that as the problem is from leetcode or a matter of fact from any such site and the problem sets says that explicitly.
//**Input:**
var n1 = "123456789"
var n2 = "987654321"
var multiply = function(str1, str2) {
var sum = 0, k = 1;
for( var i = str1.length - 1; i>=0; i--){
var val1 = parseInt(str1[i], 10) * k;
k *= 10;
var d = 1;
for(var j = str2.length - 1; j >=0; j--){
var val2 = parseInt(str2[j], 10) * d;
d *= 10;
sum += val1 * val2;
}
}
return sum.toString();
};
console.log(multiply(n1,n2))
I cannot understand what's going wrong. Other palindromes work fine though.
The purpose of such an exercise is probably that you implement your own multiplication algorithm for big numbers. When an integer (the product in this case) needs more than 15-16 digits, JavaScript number type cannot store that with enough precision, and so the outcome will be wrong if you just use the multiplication operator on the inputs.
Even if you sum up smaller products in a number variable, that sum will eventually cross the limit of Number.MAX_SAFE_INTEGER. You need to store the result of smaller calculations in another data structure, like an array or a string.
Here is a simple implementation of the long multiplication algorithm:
function multiply(a, b) {
const product = Array(a.length+b.length).fill(0);
for (let i = a.length; i--; null) {
let carry = 0;
for (let j = b.length; j--; null) {
product[1+i+j] += carry + a[i]*b[j];
carry = Math.floor(product[1+i+j] / 10);
product[1+i+j] = product[1+i+j] % 10;
}
product[i] += carry;
}
return product.join("").replace(/^0*(\d)/, "$1");
}
console.log(multiply("123456789", "987654321"));
Since ECMAScript 2020, JavaScript has the bigint data type, with which you can do such multiplications out of the box (note the n suffix):
console.log((123456789n * 987654321n).toString());
NB: In a browser you don't need to call toString() explicitly, but the above Stack Snippet console implementation is limited, so it needs it.
With Chrome you have access to BigInt arithmetic with arbitrarily (usual disclaimers apply) large integers. Run the example to see that it does make a difference.
let p1 = BigInt(1000000000) * BigInt(123456789) + BigInt(123456789)
, p2 = BigInt(1000000000) * BigInt(987654321) + BigInt(987654321)
;
console.log(`standard: =${123456789 * 987654321}`);
console.log(`standard large: =${123456789123456789 * 987654321987654321}`);
console.log(`BigInt: =${p1 * p2}`);
I have a 2D array, something like the following:
[1.11, 23]
[2.22, 52]
[3.33, 61]
...
Where the array is ordered by the first value in each row.
I am trying to find a value within the array that is close to the search value - within a certain sensitivity. The way this is set up, and the value of the sensitivity, ensure only one possible match within the array.
The search value is the current x-pos of the mouse. The search is called on mousemove, and so is being called often.
Originally I had the following (using a start-to-end for loop):
for(var i = 0; i < arr.length; i++){
if(Math.abs(arr[i][0] - x) <= sensitivity){
hit = true;
break;
}
}
And it works like a charm. So far, I've only been using small data sets, so there is no apparent lag using this method. But, eventually I will be using much larger data sets, and so want to switch this to a Binary Search:
var a = 0;
var b = arr.length - 1;
var c = 0;
while(a < b){
c = Math.floor((a + b) / 2);
if(Math.abs(arr[c][0] - x) <= sensitivity){
hit = true;
break;
}else if(arr[c][0] < x){
a = c;
}else{
b = c;
}
}
This works well, for all of 2 seconds, and then it hangs to the point where I need to restart my browser. I've used binary searches plenty in the past, and cannot for the life of me figure out why this one isn't working properly.
EDIT 1
var sensitivity = (width / arr.length) / 2.001
The points in the array are equidistant, and so this sensitivity ensures that there is no ambiguous 1/2-way point in between two arr values. You are either in one or the other.
Values are created dynamically at page load, but look exactly like what I've mentioned above. The x-values have more significant figures, and the y values are all over the place, but there is no significant difference between the small sample I provided and the generated one.
EDIT 2
Printed a list that was dynamically created:
[111.19999999999999, 358.8733333333333]
[131.4181818181818, 408.01333333333326]
[151.63636363636363, 249.25333333333327]
[171.85454545454544, 261.01333333333326]
[192.07272727272726, 298.39333333333326]
[212.29090909090908, 254.2933333333333]
[232.5090909090909, 308.47333333333324]
[252.72727272727272, 331.1533333333333]
[272.94545454545454, 386.1733333333333]
[293.16363636363633, 384.9133333333333]
[313.3818181818182, 224.05333333333328]
[333.6, 284.53333333333325]
[353.81818181818187, 278.2333333333333]
[374.0363636363637, 391.63333333333327]
[394.25454545454556, 322.33333333333326]
[414.4727272727274, 300.9133333333333]
[434.69090909090926, 452.95333333333326]
[454.9090909090911, 327.7933333333333]
[475.12727272727295, 394.9933333333332]
[495.3454545454548, 451.27333333333326]
[515.5636363636366, 350.89333333333326]
[535.7818181818185, 308.47333333333324]
[556.0000000000003, 395.83333333333326]
[576.2181818181822, 341.23333333333323]
[596.436363636364, 371.47333333333324]
[616.6545454545459, 436.9933333333333]
[636.8727272727277, 280.7533333333333]
[657.0909090909096, 395.4133333333333]
[677.3090909090914, 433.21333333333325]
[697.5272727272733, 355.09333333333325]
[717.7454545454551, 333.2533333333333]
[737.963636363637, 255.55333333333328]
[758.1818181818188, 204.7333333333333]
[778.4000000000007, 199.69333333333327]
[798.6181818181825, 202.63333333333327]
[818.8363636363644, 253.87333333333328]
[839.0545454545462, 410.5333333333333]
[859.272727272728, 345.85333333333324]
[879.4909090909099, 305.11333333333323]
[899.7090909090917, 337.8733333333333]
[919.9272727272736, 351.3133333333333]
[940.1454545454554, 324.01333333333326]
[960.3636363636373, 331.57333333333327]
[980.5818181818191, 447.4933333333333]
[1000.800000000001, 432.3733333333333]
As you can see, it is ordered by the first value in each row, ascending.
SOLUTION
Changing the condition to
while(a < b)
and
var b = positions.length;
and
else if(arr[c][0] < x){
a = c + 1;
}
did the trick.
Your binary search seems to be a bit off: try this.
var arr = [[1,0],[3,0],[5,0]];
var lo = 0;
var hi = arr.length;
var x = 5;
var sensitivity = 0.1;
while (lo < hi) {
var c = Math.floor((lo + hi) / 2);
if (Math.abs(arr[c][0] - x) <= sensitivity) {
hit = true;
console.log("FOUND " + c);
break;
} else if (x > arr[c][0]) {
lo = c + 1;
} else {
hi = c;
}
}
This is meant as a general reference to anyone implementing binary search.
Let:
lo be the smallest index that may possibly contain your value,
hi be one more than the largest index that may contain your value
If these conventions are followed, then binary search is simply:
while (lo < hi) {
var mid = (lo + hi) / 2;
if (query == ary[mid]) {
// do stuff
else if (query < ary[mid]) {
// query is smaller than mid
// so query can be anywhere between lo and (mid - 1)
// the upper bound should be adjusted
hi = mid;
else {
// query can be anywhere between (mid + 1) and hi.
// adjust the lower bound
lo = mid + 1;
}
I don't know your exact situation, but here's a way the code could crash:
1) Start with an array with two X values. This array will have a length of 2, so a = 0, b = 1, c = 0.
2) a < b, so the while loop executes.
3) c = floor((a + b) / 2) = floor(0.5) = 0.
4) Assume the mouse is not within sensitivity of the first X value, so the first if branch does not hit.
5) Assume our X values are to the right of our mouse, so the second if branch enters. This sets a = c, or 0, which it already is.
6) Thus, we get an endless loop.
It's very close but just one number off. If you can change anything here to make it better it'd be appreciated. I'm comparing my number with Math.E to see if I'm close.
var e = (function() {
var factorial = function(n) {
var a = 1;
for (var i = 1; i <= n; i++) {
a = a * i;
}
return a;
};
for (var k = 0, b = []; k < 18; k++) {
b.push(b.length ? b[k - 1] + 1 / factorial(k) : 1 / factorial(k));
}
return b[b.length - 1];
})();
document.write(e);document.write('<br />'+ Math.E);
My number: 2.7182818284590455
Math.E: 2.718281828459045
Work from higher numbers to lower numbers to minimize cancellation:
var e = 1;
for(var k = 17; k > 0; --k) {
e = 1 + e/k;
}
return e;
Evaluating the Taylor polynomial by Horner's rule even avoids the factorial and allows you to use more terms (won't make a difference beyond 17, though).
As far as I can see your number is the same as Math.E and even has a better precision.
2.7182818284590455
2.718281828459045
What is the problem after all?
With javascript, you cannot calculate e this way due to the level of precision of javascript computations. See http://www.javascripter.net/faq/accuracy.htm for more info.
To demonstrate this problem please take a look at the following fiddle which calculates e with n starting at 50000000, incrementing n by 1 every 10 milliseconds:
http://jsfiddle.net/q8xRs/1/
I like using integer values to approximate real ones.
Possible approximations of e in order of increasing accuracy are:
11/4
87/32
23225/8544
3442297523731/1266350489376
That last one is fairly accurate, equating to:
2.7182818284590452213260834432
which doesn't diverge from wikipedia's value till the 18th:
2.71828182845904523536028747135266249775724709369995
So there's that, if you're interested.