Increasing performance of loops - javascript

I'm currently working on a script that helps to manage connections to phone numbers with different things like contacts, text messages, etc. The big problem with phone numbers in our data is that a phone number can be something like..
13334445555
3334445555
013334445555
444555
All of these would probably be the same number. So, I've got a 'lookupNumber' method on an object that contains all sorts of references (none of which are important for this question), and a hasVariant method on each 'cloud' - a name that I gave to an object that represents one or more variations of a phone number. The 'cloud' could start off with a shorter number than the ones given to it, so it has to be able to vary which number it is trying to place.
The object with all the 'clouds' has a relevant data structure of:
{
clouds: [], //0-n clouds
phoneNumberCache: {} //i.e. 333444555: {//ref to cloud}
}
The 'clouds' have the relevant data structure of:
{
numbers: [] //1-n numbers
}
lookupNumber method:
lookupNumber: function (number) {
if(this.phoneNumberCache[number] === undefined) {
var i, l, cloud;
for (i = 0, l = this.clouds.length; i < l; i++) {
cloud = this.clouds[i];
if(cloud.hasVariant(number)) {
this.phoneNumberCache[number] = cloud;
return cloud;
}
}
return false;
} else {
return this.phoneNumberCache[number];
}
}
hasVariant method:
hasVariant: function (testNumber) {
for(var i = 0, l = this.numbers.length; i < l; i++) {
var number = this.numbers[i], needle, haystack, index, needleLength, haystackLength;
if(testNumber.length < number.length) {
needle = testNumber;
haystack = number;
} else {
needle = number;
haystack = testNumber;
}
needleLength = needle.length;
haystackLength = haystack.length;
if(needleLength <= this.minimumLength && haystackLength != needleLength) {
continue;
}
index = haystack.indexOf(needle);
if(index != -1 && (index + needleLength) == haystackLength && (needleLength / haystackLength) > this.minimumMatch) {
return true;
}
}
return false;
}
On my testing account, the performance is 'meh' on firefox and terrible on IE8 (the minimum IE browser we support). Firebug is reporting the following times for the two methods:
Function Calls Percent Own Time Time Avg Min Max
hasVariant 1355484 32.1% 1653.577ms 1653.577ms 0.001ms 0.001ms 0.205ms
lookupNumber 2120 31.17% 1605.947ms 3259.524ms 1.538ms 0.001ms 4.736ms
The test data has 1674 unique phone numbers in it - about 450 of the 'lookupNumber' calls are going to return an already cached number.
Maybe I've been staring at the data too long, but it seems the number of times the 'hasVariant' gets called is way too high and they're both such simple methods.. I can't think of any way to make them faster. Because it's still very slow on IE, I'm still looking for a way to squeeze some more performance out of it. Any ideas on how to make this more efficient? Or do I have a bug in here that might be causing this to not cache a number?

I have an idea: don't use an array to store the numbers inside of the cloud. Instead, use an object as so:
cloud : {
numbers : {
"4445555" : true,
"3334445555" : true,
// Etc
}
}
This way, when you want to check if a number is in there, just do something like this:
hasVariant : function (number) {
while (number.length >= 7) {
if (this.numbers[number]) return true;
number = number.substring(1);
}
return false;
}
I think this should be faster than your version.

Related

Optimizing hash table implementation to accommodate large amount of elements

Consider the following scenario:
One million clients visit a store and pay an amount of money using their credit card. The credit card codes are generated using a 16-digit number, and replacing 4 of its digits (randomly) with the characters 'A', 'B', 'C', 'D'. The 16-digit number is generated randomly once, and is used for every credit card, the only change between cards being the positions in the string of the aforementioned characters (that's ~40k possible distinct codes).
I have to organize the clients in a hash table, using a hash function of my choosing and also using open addressing (linear probing) to deal with the collisions. Once organized in the table, I have to find the client who
paid the most money during his purchases.
visited the store the most times.
My implementation of the hash table is as follows, and seems to be working correctly for the test of 1000 clients. However once I increase the number of clients to 10000 the page never finishes loading. This is a big issue since the total number of "shopping sessions" has to be one million, and I am not even getting close to that number.
class HashTable{
constructor(size){
this.size = size;
this.items = new Array(this.size);
this.collisions = 0;
}
put(k, v){
let hash = polynomial_evaluation(k);
//evaluating the index to the array
//using modulus a prime number (size of the array)
//This works well as long as the numbers are uniformly
//distributed and sparse.
let index = hash%this.size;
//if the array position is empty
//then fill it with the value v.
if(!this.items[index]){
this.items[index] = v;
}
//if not, search for the next available position
//and fill that with value v.
//if the card already is in the array,
//update the amount paid.
//also increment the collisions variable.
else{
this.collisions++;
let i=1, found = false;
//while the array at index is full
//check whether the card is the same,
//and if not then calculate the new index.
while(this.items[index]){
if(this.items[index] == v){
this.items[index].increaseAmount(v.getAmount());
found = true;
break;
}
index = (hash+i)%this.size;
i++;
}
if(!found){
this.items[index] = v;
}
found = false;
}
return index;
}
get(k){
let hash = polynomial_evaluation(k);
let index = hash%this.size, i=1;
while(this.items[index] != null){
if(this.items[index].getKey() == k){
return this.items[index];
}
else{
index = (hash+i)%this.size;
i++;
}
}
return null;
}
findBiggestSpender(){
let max = {getAmount: function () {
return 0;
}};
for(let item of this.items){
//checking whether the specific item is a client
//since many of the items will be null
if(item instanceof Client){
if(item.getAmount() > max.getAmount()){
max = item;
}
}
}
return max;
}
findMostFrequentBuyer(){
let max = {getTimes: function () {
return 0;
}};
for(let item of this.items){
//checking whether the specific item is a client
//since many of the items will be null
if(item instanceof Client){
if(item.getTimes() > max.getTimes()){
max = item;
}
}
}
return max;
}
}
To key I use to calculate the index to the array is a list of 4 integers ranging from 0 to 15, denoting the positions of 'A', 'B', 'C', 'D' in the string
Here's the hash function I am using:
function polynomial_evaluation(key, a=33){
//evaluates the expression:
// x1*a^(d-1) + x2*a^(d-2) + ... + xd
//for a given key in the form of a tuple (x1,x2,...,xd)
//and for a nonzero constant "a".
//for the evaluation of the expression horner's rule is used:
// x_d + a*(x_(d-1) + a(x_(d-2) + .... + a*(x_3 + a*(x_2 + a*x1))... ))
//defining a new key with the elements of the
//old times 2,3,4 or 5 depending on the position
//this helps for "spreading" the values of the keys
let nKey = [key[0]*2, key[1]*3, key[2]*4, key[3]*5];
let sum=0;
for(let i=0; i<nKey.length; i++){
sum*=a;
sum+=nKey[i];
}
return sum;
}
The values corresponding to the keys generated by the hash function are instances of a Client class which contains the fields amount (the amount of money paid), times (the times this particular client shopped), key (the array of 4 integers mentioned above), as well as getter functions for those fields. In addition there's a method that increases the amount when the same client appears more than once.
The size of the hash table is 87383 (a prime number) and the code in my main file looks like this:
//initializing the clients array
let clients = createClients(10000);
//creating a new hash table
let ht = new HashTable(N);
for(let client of clients){
ht.put(client.getKey(), client);
}
This keeps running until google chrome gives a "page not responding" error. Is there any way I can make this faster? Is my approach on the subject (perhaps even my choice of language) correct?
Thanks in advance.
The page is not responding since the main (UI) thread is locked. Use a WebWorker or ServiceWorker to handle the calculations, and post them as messages to the main thread.
Regarding optimizing your code, one thing I see is in findBiggestSpender. I'll break it down line-by-line.
let max = {getAmount: function () {
return 0;
}};
This is a waste. Just assign a local variable, no need to keep calling max.getAmount() in every iteration.
for(let item of this.items){
The fastest way to iterate a list in Javascript is with a cached length for loop: for (let item, len = this.items.length; i < len; i ++)
if(item instanceof Client){
This is slower than a hard null check, just use item !== null.

Using an array of numbers to add up to a specific number

I've been trying to find out an efficient method of finding multiple numbers from an array that add up to a given number. In this instance I'm trying to find 3 numbers that total a target number.
I've got a basic working example below but unfortunately the recursive loop fails, it looks like there's an issue with it constantly looping. Ideally it would find the first possible answer and return it, but when it can't find an answer it gets stuck in the loop and breaks the browser.
Warning: the below code will break due to a memory leak:
let array = [5,6,3,3,6,67,2,2,6,7,7,2,1,3,4,5,67,7,4,2,5,6,3,3,6,67,2,2,6,7,7,2,1,3,4,5,67,7,4,2,5,6,3,3,6,67,2,2,6,7,7,2,1,3,4,5,67,7,4,2];
function findSums(arr, target, count) {
var result = [];
function recurse(start, leftOver, selection) {
if (leftOver < 0) return; // failure
if (leftOver === 0 && selection.length == count) {
result.push(selection); // add solution
return;
}
for (var i = start; i < arr.length; i++) {
recurse(i, leftOver-arr[i], selection.concat(arr[i]));
}
}
recurse(0, target, []);
return result;
}
// Demo
$('#input').on('blur', function(e){
let value = parseInt(e.target.value);
let result = findSums(array, value, 3);
if(result.length > 0){
$('#output').text(result[0]);
} else {
$('#output').text('Nothing found');
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>Input Number Below</h1>
<input id="input" type="number" />
<code id="output" ></code>
Well, it didn't break, when I tested, but still here are some tips:
You should set additional limitation to the count. You are making to much extra calls. When your function deals with really big sum, small numbers and small count it will call itself again until it reaches or overflows the desired sum, and only after that it will check current count. So you should add
if (selection.length > count) return;
Also. As I see there are many duplicates in your array, so I assume, that usage of the same number is allowed, but only if it is taken from another index. In your loop you are calling next recurse with the same start index. I think, you need
for (var i = start; i < arr.length; i++) {
recurse(i + 1, leftOver-arr[i], selection.concat(arr[i]));
}
And finally. This will not influence the recursive part of an algorithm, but maybe you'd like to filter out same results, or filter out your array to remove all duplicates.
Hope this helps.
Edit: sorry, missed the part about first possible solution. Here is the way to achieve this:
function recurse(start, leftOver, selection) {
if (leftOver < 0) return false; // failure
if (selection.length > count) return false;
if (leftOver === 0 && selection.length == count) {
result.push(selection); // add solution
return true;
}
for (var i = start; i < arr.length; i++) {
var res = recurse(i + 1, leftOver-arr[i], selection.concat(arr[i]));
if (res) return true;
}
}

How to generate a new random number (that's different from the previous random number)

I'm trying to change the following (that currently returns a random number from an array), so that each random number is different from the last one chosen.
function randomize(arr) {
return arr[Math.floor(Math.random()*arr.length)];
}
oracleImg = [];
for (var i=1;i<=6;i++) {
oracleImg.push(i);
}
randOracleImg = randomize(oracleImg);
I tried the following, but it's not always giving me a number different from the last number.
function randomize(arr) {
var arr = Math.floor(Math.random()*arr.length);
if(arr == this.lastSelected) {
randomize();
}
else {
this.lastSelected = arr;
return arr;
}
}
How can I fix this?
Your existing function's recursive randomize() call doesn't make sense because you don't pass it the arr argument and you don't do anything with its return value. That line should be:
return randomize(arr);
...except that by the time it gets to that line you have reassigned arr so that it no longer refers to the original array. Using an additional variable as in the following version should work.
Note that I've also added a test to make sure that if the array has only one element we return that item immediately because in that case it's not possible to select a different item each time. (The function returns undefined if the array is empty.)
function randomize(arr) {
if (arr.length < 2) return arr[0];
var num = Math.floor(Math.random()*arr.length);
if(num == this.lastSelected) {
return randomize(arr);
} else {
this.lastSelected = num;
return arr[num];
}
}
document.querySelector("button").addEventListener("click", function() {
console.log(randomize(["a","b","c","d"]));
});
<button>Test</button>
Note that your original function seemed to be returning a random array index, but the code shown in my answer returns a random array element.
Note also that the way you are calling your function means that within the function this is window - not sure if that's what you intended; it works, but basically lastSelected is a global variable.
Given that I'm not keen on creating global variables needlessly, here's an alternative implementation with no global variables, and without recursion because in my opinion a simple while loop is a more semantic way to implement the concept of "keep trying until x happens":
var randomize = function () {
var lastSelected, num;
return function randomize(arr) {
if (arr.length < 2) return arr[0];
while (lastSelected === (num = Math.floor(Math.random()*arr.length)));
lastSelected = num;
return arr[num];
};
}();
document.querySelector("button").addEventListener("click", function() {
console.log(randomize(["a","b","c","d"]));
});
<button>Test</button>
Below code is just an example, it will generate 99 numbers and all will be unique and random (Range is 0-1000), logic is simple just add random number in a temporary array and compare new random if it is already generated or not.
var tempArray = [];
var i=0;
while (i != 99) {
var random = Math.floor((Math.random() * 999) + 0);
if (tempArray.indexOf(random)==-1) {
tempArray.push(random);
i++;
} else {
continue;
}
}
console.log(tempArray);
here is a version which will ensure a random number that is always different from the last one. additionally you can control the max and min value of the generated random value. defaults are max: 100 and min: 1
var randomize = (function () {
var last;
return function randomize(min, max) {
max = typeof max != 'number' ? 100 : max;
min = typeof min != 'number' ? 1 : min;
var random = Math.floor(Math.random() * (max - min)) + min;
if (random == last) {
return randomize(min, max);
}
last = random;
return random;
};
})();
If you want to ALWAYS return a different number from an array then don't randomize, shuffle instead!*
The simplest fair (truly random) shuffling algorithm is the Fisher-Yates algorithm. Don't make the same mistake Microsoft did and try to abuse .sort() to implement a shuffle. Just implement Fisher-Yates (otherwise known as the Knuth shuffle):
// Fisher-Yates shuffle:
// Note: This function shuffles in-place, if you don't
// want the original array to change then pass a copy
// using [].slice()
function shuffle (theArray) {
var tmp;
for (var i=0; i<theArray.length;i++) {
// Generate random index into the array:
var j = Math.floor(Math.random()*theArray.length);
// Swap current item with random item:
tmp = theArray[i];
theArray[j] = theArray[i];
theArray[i] = tmp;
}
return theArray;
}
So just do:
shuffledOracleImg = shuffle(oracleImg.slice());
var i=0;
randOracleImg = shuffledOracleImg[i++]; // just get the next image
// to get a random image
How you want to handle running out of images is up to you. Media players like iTunes or the music player on iPhones, iPads and iPods give users the option of stop playing or repeat from beginning. Some card game software will reshuffle and start again.
*note: One of my pet-peeves is music player software that randomize instead of shuffle. Randomize is exactly the wrong thing to do because 1. some implementations don't check if the next song is the same as the current song so you get a song played twice (what you seem to want to avoid) and 2. some songs end up NEVER getting played. Shuffling and playing the shuffled playlist from beginning to end avoids both problems. CD player manufacturers got it right. MP3 player developers tend to get it wrong.

Debugging loop and function - javascript

My browser is crashing from this loop that doesn't appear to be unterminated.
function checkLetters(word){
for(i=0;i<5;i++){
for(j=i+1;j<5;j++){
if(word.charAt(i) == word.charAt(j)){
return false;
break;
}
}
}
return true;
}
var compLibrary = [];
for (var k=0;k<library.length;k++) {
if(checkLetters(library[k]) == true) {
compLibrary.push(library[k]);
}
}
I am trying to search the library for words with no repeating letters and pushing it into a new array
The whole library is five letter words.
It's not an infinite loop, but it does look like a pretty expensive operation. There's not any really elegant way to detect an infinite loop (or recursion) so most engines just resort to either
Not trying to detect it, and running forever until some lower-level controller (like, the kernel) kills it.
Automatically killing itself when it gets to a certain recursion depth, loop count, or run time.
Your algorithm loops 5 * 4 * library.length times, so depending on how long your library actually is, your code could certainly trigger #2. But there are faster ways to find duplicate letters:
function checkLetters(word) {
var i=word.length;
var seenChars={};
var c;
while (i-->0) {
c = word.CharAt(i); # The current character
if (c in seenChars) return false;
seenChars[c] = 1;
}
return true;
}
var compLibrary = [];
for (var k=0; k < library.length; k++) {
if (checkLetters(library[k]) == true) {
compLibrary.push(library[k]);
}
}
Shouldn't this loop
for(i=0;i<5;i++){
for(j=i+1;j<5;j++){
be something in these lines
for(var i=0; i< word.length ;i++){
for(j=i+1; j<word.length/2; j++){
Can't see what your issue is but here's the solution I suggest for your problem:
var words = ['hello', 'bye', 'foo', 'baz'];
function hasUniqLetters(word){
var uniq = word.split('').filter(function(letter, idx){
return word.indexOf(letter) == idx;
});
return uniq.length == word.length;
}
var result = words.filter(hasUniqLetters);
console.log(result); //=> ["bye","baz"]
function checkLetters(word){
for(i=0;i<5;i++){ //both i and j were not instantiated
for(j=i+1;j<5;j++){
if(word.charAt(i) == word.charAt(j)){ //It could be crashing if
return false; //word <= 5
break;//a break after a return
} //statement is redundant.
}
}
return true;
}
You must put var before declaring a variable.
word.charAt(i) can be written like word[i]
Try this:
function checkLetters(word){
for(var i=0,j=1,l=word.length-1; i<l; i++,j++){
if(word.charAt(i) == word.charAt(j)){
return false;
}
}
return true;
}
var compLibrary = [];
for(var i=0,l=library; i<l; i++){
if(checkLetters(library[i]) == true){
compLibrary.push(library[i]);
}
}
tldr; The code originally posted should not crash the browser.
The following explains why nested loops are not always bad for efficiency and shows a counter-example where the original code works successfully without crashing the browser when running over 100,000 simulated words.
The complexity of the posted code is low and it should run really fast. It executes here in a fraction of a second (under 20ms!), even at all "20 * 8000" - i.e. C * O(n). Note that the time complexity is linear because the nested loops in checkLetters have a constant time: due to this small fixed limit ("20 loops" max each call) it does not represent a performance problem here.
As such, I maintain that it is not an issue wrt it being an efficiency problem. I assert that the original code will not "crash" a modern browser environment. For longer or unbound words then using a (presumably) lower complexity probe attempt may pay off - but the inner loop runs in small constant time here. (Actually, due to distribution of letters within words and word lengths I would imagine that the constant rarely exceeds "90 loops" for a natural language like English.)
See http://jsfiddle.net/FqdX7/6/
library = []
for (w = 0; w < 100000; w++) {
library.push((w + "12345").slice(0,5))
}
function checkLetters(word){
for(i=0;i<5;i++){
for(j=i+1;j<5;j++){
if(word.charAt(i) == word.charAt(j)){
return false;
}
}
}
return true;
}
$('#time').text("running")
start = +(new Date)
var compLibrary = [];
for (var k=0;k<library.length;k++) {
if(checkLetters(library[k]) == true) {
compLibrary.push(library[k]);
}
}
time = +(new Date) - start
$('#time').text(time + "ms")
On my machine (in Safari) the code runs in ~30 milliseconds (~40ms if the "return false" is removed) for an input of 100,000 words!
In comparison, the answer with a probe (seenChars lookup) actually runs worse in Safari/Chrome. See http://jsfiddle.net/Hw2wr/5/, where for 100k words it takes about 270ms - or about 9x slower. However, this is highly browser dependent and the jsperf in the comments shows that in Firefox the probing approach is faster (by about 2x) but is slower again in IE (say 4-5x).
YMMV. I think the original code is acceptable for the given situation and the "crashing" problem lies elsewhere.

Explode a string into a hashmap for searching?

I have a string like this being returned from my backend:
"1,2,3,4,5,6"
I have a large array locally and want to display only those items not in this list, so I was thinking of exploding this string into an array but how can I search efficiently? As far as I know there are no hashmaps in JS so how does one do this? I just need to check for key existence.
All Javascript objects are also hash tables that can store string or numeric keys:
var x = {};
x["foo"] = 1;
if("foo" in x) { alert("hello!"); }
if("bar" in x) { alert("should never see this"); }
"1,2,3,4,5,6".split(",").some(function(letter) {
return letter === '2'
});
Warning: Might not work in IE (or other crappy browser)
Cross browser version (that relies on native code for performance):
var arr = "1,2,3,4,5,6".split(",");
if(arr.some)
{
arr.some(function(letter) {
return letter === '2'
});
}
else
{
for(var i = 0 ; i < arr.length ; i++ )
{
if(arr[i] === '2') return true;
}
}

Categories