Intense loop freezing browser; how to make server do it? - javascript

Basic outline; I am retrieving ~100 Facebook statuses, and running a search of these posts to check if it contains one of ~20 search terms. When I try to run it in the browser it locks up, which makes me apprehensive to try and run this on the client side.
What would be the easiest way to have the server run this operation and return the results? Can I do it in Python? Here is the loop, for reference.
function Filter() {
console.log("running Filter")
//loop through fbObj.posts
for (i = 0, l = fbObj.length; i < l; i++){
var post = fbObj[i].post
console.log("Checking " + post + " for search terms")
//loop through searchTerms
for (j = 0; j<searchTermObj.length; j= j++) {
searchTerm_variants = searchTermObj[j].words
for (x = 0; x < searchTerm_variants.length; x++){
if (post.indexOf(searchTerm_variants[x]) !== 0){
foundPosts[i] = {}
foundPosts[i] = fbObj[i]
}
}
}
console.log(foundPosts)
}
}

You have an error in your code that might explain the lockup :
//loop through searchTerms
for (j = 0; j<searchTermObj.length; j= j++) {
j is never incremented (so it is always 0) :
j++ increments j, but evaluates to j which is assigned to j. So it's a null operation.
Therefore your code gets stuck in the loop.

Have you thought of using worker threads to do this on the client? However this might be available only in the latest builds of various browsers?
https://developer.mozilla.org/en-US/docs/DOM/Worker

You could build an all-encompassing regex like this, I think:
var regexWords = [], variants;
for (var i = 0; i < searchTermObj.length; ++i) {
variants = searchTermObj[i].words;
for (var j = 0; j < variants.length; ++j)
regexWords.push(variants[j]);
}
var regex = new RegExp(regexWords.join("|"));
Then you can test each post like this:
for (i = 0, l = fbObj.length; i < l; i++){
var post = fbObj[i].post
if (regex.test(post)) {
// found a naughty word in the post
}
}
Now this works so long as your word lists are just alphabetic words, with no funny characters like "*" or "." in them. You could still make this work if they did contain special characters, but it'd be a little more complex.
Doing it with a big regex like that allows the regex engine to employ smart sub-linear searching techniques, so that each post only needs to be searched once. It still may not be extremely fast.

Related

Bug in leetcode, javascript 219. Contains Duplicate II

This is the question: https://leetcode.com/problems/contains-duplicate-ii/
Given an integer array nums and an integer k, return true if there are two distinct indices i and j in the array such that nums[i] == nums[j] and abs(i - j) <= k.
My code:
var containsNearbyDuplicate = function(nums, k) {
for(let i = 0; i < nums.length; i++) {
for(let j = i+1; j < nums.length; j++) {
console.log([i, j])
if(nums[i] == nums[j] && Math.abs(i-j) <= k){
return true;
}
}
}
return false;
};
On submission, I can pass 20/51 cases with status being 'Time Limit Exceeded'.
I can pass the following example inputs:
Example 1:
Input: nums = [1,2,3,1], k = 3
Output: true
Example 2:
Input: nums = [1,0,1,1], k = 1
Output: true
Example 3:
Input: nums = [1,2,3,1,2,3], k = 2
Output: false
I can't think of any fringe cases which is causing the submission to exceed time limit. I'm aware that there are other ways to solve this problem, but I would like to know what's wrong with my code.
EDIT:
I realised the problem is with this line: console.log([i, j]). If I comment it out, there is no problem with submission. But I'm not quite sure why that line is causing the time limit exceeded error.
Leetcode and similar sites often provide huge data sets as input. In such cases, an unnecessarily computationally complex algorithm can take too much processing time to complete. That may be what's happening here.
You have a nested loop - if the input array contains 1000 items, that's on the order of 1000 * 1000 iterations. Use a different, less expensive algorithm - such as by iterating over the input only once. One possible approach is
var containsNearbyDuplicate = function(nums, k) {
const numsByLastIndex = {};
for(let i = 0; i < nums.length; i++) {
const num = nums[i];
if (numsByLastIndex[num] !== undefined && i - numsByLastIndex[num] <= k) {
return true;
}
numsByLastIndex[num] = i;
}
return false;
};
When I try the above code, the time required has changed from on the order of 9 seconds (which may be close to the limit) down to 1/4 of a second.
Another issue is that logging in the Node CLI, if you do a ton of logging, can slow things down. Sometimes, logging can even take up most of the processing time of your script. It's not needed to perform the task, so feel free to remove it.

How can I stop a script from shuffling the words (questions) every time I start the exercise?

I am an English teacher and I created an exercise, using Hotpotatoes, to demonstrate to my student how to make sentences from different words. The words are in alphabetical order so I can easily find the word I want to use.
But there is a code in the script that shuffles them every time I start or restart the HTML file. I have no experience in coding. I have been trying to change the two shuffle functions to make them stop from shuffling. Deleting them did not work, the whole exercise disappeared. By the way, the file is not a test; it's just me demonstrating to the kids over the Internet how to make sentences by dragging the words and putting them together.
The file was too big for here. Here are the two codes that I believe are doing the shuffling:
function Shuffle(InArray){
var Num;
var Temp = new Array();
var Len = InArray.length;
var j = Len;
for (var i=0; i<Len; i++){
Temp[i] = InArray[i];
}
for (i=0; i<Len; i++){
Num = Math.floor(j * Math.random() *1);
InArray[i] = Temp[Num];
for (var k=Num; k < (j-1); k++) {
Temp[k] = Temp[k+1];
}
j--;
}
return InArray;
}
Segments = Shuffle(Segments);

The answer should be valid for any given input

Write a function which takes a sentence as an input and output a sorted sentence.
1.Each character of the word should be arranged in alphabetical order
Words should be arranged in ascending order depending on its character count
Note: - Word only can have lowercase letters
Example :
Inputs str = "she lives with him in a small apartment"
Output = "a in ehs him hitw eilsv allms aaemnprtt"
function makeAlphabetSentenceSort(str) {
var word = str.split(' ');
for (var j = 0; j < word.length; j++) {
word[j] = word[j].split('').sort().join('');
}
for (var h = 0; h < word.length - 1; h++) {
for (var i = 0; i < word.length - h - 1; i++) {
if (String(word[i]).length > String(word[i + 1]).length) {
var temp = word[i];
word[i] = word[i + 1];
word[i + 1] = temp;
}
}
}
return word.join(' ');
}
console.log(makeAlphabetSentenceSort("she lives with him in a small apartment"));
ERROR message is "The answer should be valid for any given input."
Upon further reflection, it's clear that their challenge isn't following their own rules. It clearly states:
Word only can have lowercase letters
Yet, it runs this through the code (with punctuation):
he was curious about how it would taste, so he took a small bite.
To make matters worse, it expects the following output, which places how before saw, even though how appears after saw:
a eh it os eh how asw koot .beit allms dlouw abotu ,aestt ciorsuu
So, to be able to pass this challenge, you're also going to need to swap words of the same length with each other.
NodeJS Solution
The following produces the correct results in NodeJS, but not in the browser, in fact, sort() gave me different results in different browsers!
function makeAlphabetSentenceSort(str) {
var word = str.split(' ');
for (var j = 0; j < word.length; j++) {
word[j] = word[j].split('').sort().join('');
}
return word.sort((a, b) => a.length - b.length).join(' ');
}
console.log(makeAlphabetSentenceSort('he was curious about how it would taste, so he took a small bite.'));
I then read about why sort is so unreliable, and this article is a great resource, unfortunately, the solution in it provides reliable results, but not in the way this challenge expects it.
https://medium.com/#fsufitch/is-javascript-array-sort-stable-46b90822543f
Challenge
http://ccc.fidenz.com/en/challenges/challenges/challenge--86
Takeaway
This is a horribly designed challenge, you really should focus your efforts on a better website to practice programming, such as HackerRank, instead of this hodgepodge mess.

For-loop performance: storing array length in a variable

Consider two versions of the same loop iteration:
for (var i = 0; i < nodes.length; i++) {
...
}
and
var len = nodes.length;
for (var i = 0; i < len; i++) {
...
}
Is the latter version anyhow faster than the former one?
The accepted answer is not right because any decent engine should be able to hoist the property load out of the loop with so simple loop bodies.
See this jsperf - at least in V8 it is interesting to see how actually storing it in a variable changes the register allocation - in the code where variable is used the sum variable is stored on the stack whereas with the array.length-in-a-loop-code it is stored in a register. I assume something similar is happening in SpiderMonkey and Opera too.
According to the author, JSPerf is used incorrectly, 70% of the time. These broken jsperfs as given in all answers here give misleading results and people draw wrong conclusions from them.
Some red flags are putting code in the test cases instead of functions, not testing the result for correctness or using some mechanism of eliminating dead code elimination, defining function in setup or test cases instead of global.. For consistency you will want to warm-up the test functions before any benchmark too, so that compiling doesn't happen in the timed section.
Update: 16/12/2015
As this answer still seems to get a lot of views I wanted to re-examine the problem as browsers and JS engines continue to evolve.
Rather than using JSPerf I've put together some code to loop through arrays using both methods mentioned in the original question. I've put the code into functions to break down the functionality as would hopefully be done in a real world application:
function getTestArray(numEntries) {
var testArray = [];
for (var i = 0; i < numEntries; i++) {
testArray.push(Math.random());
}
return testArray;
}
function testInVariable(testArray) {
for (var i = 0; i < testArray.length; i++) {
doSomethingAwesome(testArray[i]);
}
}
function testInLoop(testArray) {
var len = testArray.length;
for (var i = 0; i < len; i++) {
doSomethingAwesome(testArray[i]);
}
}
function doSomethingAwesome(i) {
return i + 2;
}
function runAndAverageTest(testToRun, testArray, numTimesToRun) {
var totalTime = 0;
for (var i = 0; i < numTimesToRun; i++) {
var start = new Date();
testToRun(testArray);
var end = new Date();
totalTime += (end - start);
}
return totalTime / numTimesToRun;
}
function runTests() {
var smallTestArray = getTestArray(10000);
var largeTestArray = getTestArray(10000000);
var smallTestInLoop = runAndAverageTest(testInLoop, smallTestArray, 5);
var largeTestInLoop = runAndAverageTest(testInLoop, largeTestArray, 5);
var smallTestVariable = runAndAverageTest(testInVariable, smallTestArray, 5);
var largeTestVariable = runAndAverageTest(testInVariable, largeTestArray, 5);
console.log("Length in for statement (small array): " + smallTestInLoop + "ms");
console.log("Length in for statement (large array): " + largeTestInLoop + "ms");
console.log("Length in variable (small array): " + smallTestVariable + "ms");
console.log("Length in variable (large array): " + largeTestVariable + "ms");
}
console.log("Iteration 1");
runTests();
console.log("Iteration 2");
runTests();
console.log("Iteration 3");
runTests();
In order to achieve as fair a test as possible each test is run 5 times and the results averaged. I've also run the entire test including generation of the array 3 times. Testing on Chrome on my machine indicated that the time it took using each method was almost identical.
It's important to remember that this example is a bit of a toy example, in fact most examples taken out of the context of your application are likely to yield unreliable information because the other things your code is doing may be affecting the performance directly or indirectly.
The bottom line
The best way to determine what performs best for your application is to test it yourself! JS engines, browser technology and CPU technology are constantly evolving so it's imperative that you always test performance for yourself within the context of your application. It's also worth asking yourself whether you have a performance problem at all, if you don't then time spent making micro optimizations that are imperceptible to the user could be better spent fixing bugs and adding features, leading to happier users :).
Original Answer:
The latter one would be slightly faster. The length property does not iterate over the array to check the number of elements, but every time it is called on the array, that array must be dereferenced. By storing the length in a variable the array dereference is not necessary each iteration of the loop.
If you're interested in the performance of different ways of looping through an array in javascript then take a look at this jsperf
According to w3schools "Reduce Activity in Loops" the following is considered bad code:
for (i = 0; i < arr.length; i++) {
And the following is considered good code:
var arrLength = arr.length;
for (i = 0; i < arrLength; i++) {
Since accessing the DOM is slow, the following was written to test the theory:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>my test scripts</title>
</head>
<body>
<button onclick="initArray()">Init Large Array</button>
<button onclick="iterateArraySlowly()">Iterate Large Array Slowly</button>
<button onclick="iterateArrayQuickly()">Iterate Large Array Quickly</button>
<p id="slow">Slow Time: </p>
<p id="fast">Fast Time: </p>
<p id="access"></p>
<script>
var myArray = [];
function initArray(){
var length = 1e6;
var i;
for(i = 0; i < length; i++) {
myArray[i] = i;
}
console.log("array size: " + myArray.length);
}
function iterateArraySlowly() {
var t0 = new Date().getTime();
var slowText = "Slow Time: "
var i, t;
var elm = document.getElementById("slow");
for (i = 0; i < myArray.length; i++) {
document.getElementById("access").innerHTML = "Value: " + i;
}
t = new Date().getTime() - t0;
elm.innerHTML = slowText + t + "ms";
}
function iterateArrayQuickly() {
var t0 = new Date().getTime();
var fastText = "Fast Time: "
var i, t;
var elm = document.getElementById("fast");
var length = myArray.length;
for (i = 0; i < length; i++) {
document.getElementById("access").innerHTML = "Value: " + i;
}
t = new Date().getTime() - t0;
elm.innerHTML = fastText + t + "ms";
}
</script>
</body>
</html>
The interesting thing is that the iteration executed first always seems to win out over the other. But what is considered "bad code" seems to win the majority of the time after each have been executed a few times. Perhaps someone smarter than myself can explain why. But for now, syntax wise I'm sticking to what is more legible for me:
for (i = 0; i < arr.length; i++) {
if nodes is DOM nodeList then the second loop will be much much faster because in the first loop you lookup DOM (very costly) at each iteration. jsperf
This has always been the most performant on any benchmark test that I've used.
for (i = 0, val; val = nodes[i]; i++) {
doSomethingAwesome(val);
}
I believe that the nodes.length is already defined and is not being recalculated on each use. So the first example would be faster because it defined one less variable. Though the difference would be unnoticable.

Javascript: matching a dynamic string against an array

I'm attempting to teach myself javascript. I chose something I assumed was simple, but ran into problems relatively quickly.
I'm attempting to search a string for another string given by the user.
My code so far is:
var source = "XREs2qqAQfjr6NZs6H5wkZdOES5mikexRkOPsj6grQiYNZfFoqXI4Nnc1iONKVrA";
var searchString = []; //the users input
searchString = prompt("Enter search string");
var hits = [];
var one = 0;
var two = 0;
var k = 0;
var sourceSearch = function(text) {
for(i = 0; i < source.length; i++) { //for each character in the source
if(source[i] === searchString[0]) { //if a character in source matches the first element in the users input
one = source.indexOf(i); //confused from here on
for(p = searchString.length; p > 0; p--) {
}
}
}
};
sourceSearch(searchString);
My idea was:
check to see if the first loop finds a character that matches the first character in the user input
if it matches, check to see if the next X characters after the first match the next X characters in the source string
if they all match, push them to the hits array
My problem: I have no idea how to iterate along the arrays without nesting quite a few if statements, and even then, that wouldn't be sufficient, considering I want the program to work with any input.
Any ideas would be helpful. Thanks very much in advance.
Note: There are a few un-used variables from ideas I was testing, but I couldn't make them work.
You can try:
if (source.indexOf(searchString) !== -1) {
// Match!
}
else
{
//No Match!
}
As the other answers so far point out, JavaScript strings have an indexOf function that does what you want. If you want to see how it's done "by hand", you can modify your function like this:
var sourceSearch = function(text) {
var i, j, ok; // always declare your local variables. globals are evil!
// for each start position
for(i = 0; i < source.length; i++) {
ok = true;
// check for a match
for (j = searchString.length - 1; ok && j >= 0; --j) {
ok = source[i + j] === searchString[j];
}
if (ok) {
// searchString found starting at index i in source
}
}
};
This function will find all positions in source at which searchString was found. (Of course, you could break out of the loop on the first success.) The logic is to use the outer loop to advance to each candidate start position in source and use the inner loop to test whether that position actually is the position of a match to searchString.
This is not the best algorithm for searching strings. The built-in algorithm is much faster (both because it is a better algorithm and because it is native code).
to follow your approach, you can just play with 2 indexes:
var sourceSearch = function(text) {
j = 0;
for(i = 0; i < source.length; i++) {
if(source[i] === text[j]) {
j++;
} else {
j = 0;
}
if (j == text.length) {
console.log(i - j); //this prints the starting index of the matching substring
}
}
};
These answers are all pretty good, but I'd probably opt for something like this:
var source = "XREs2qqAQfjr6NZs6H5wkZdOES5mikexRkOPsj6grQiYNZfFoqXI4Nnc1iONKVrA";
var searchString = []; //the users input
searchString = prompt("Enter search string");
var hits = source.split(searchString);
var hitsCount = hits.length - 1;
This way you have all of the data you need to figure out where each hit occurred in he source, if that's important to you.

Categories