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.
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.
What I did so far was the following:
function randomSelectObjects(randObjects, countShow){
var i = 0;
var countRandObjects = randObjects.length;
var preselectedObj = false;
randObjects.hide(); // hide all items
while (i < countShow) { // while until we found enough items we can show
preselectedObj = randObjects.eq(Math.floor(Math.random()*countRandObjects)); // random select an object
if(preselectedObj.is(':hidden')){ // make sure it is not already unhidden
preselectedObj.show(); // show the object
i++; // up the counter – done only in case it was not already visible
}
}
}
Usage:
var randObjects = $('.items');
randomSelectObjects(randObjects, 1);
The problem is that I will run into selecting an already revealed (show()) item inside while from time to time. I would love to remove that unnecessary overhead.
Unfortunately there seems to be no way to remove an object from a cached selection. remove() also removes the object from the DOM which is not (always) what I want.
Cloning the selection of objects first and then using remove() would work for the selection process but then there would be the overhead to match the selected items with the live DOM for actually show() them.
My suggestion is create a unique random array first. Hide all elements then loop through the array of random inidices and show matching elements
// wrap in simple jQuery plugin
$.fn.randomDisplay = function(max_items) {
max_items = max_items || 5;
//create array of unique random indices
var randArr = randArray(this.length, max_items);
// hide all then filter matches to show
this.hide().filter(function(i){
return randArr.indexOf(i) >-1
}).show();
// creates unique array
function randArray(max, len) {
var arr = [], rand;
for (var i = 0; i < len; i++) {
rand = getRand(max)
while (arr.indexOf(rand) > -1) {
rand = getRand(max)
}
arr.push(rand);
}
return arr;
}
// random number helper
function getRand(max) {
return Math.floor(Math.random() * max)
}
}
// use
$(function(){
$('.item').randomDisplay(7)
})
DEMO
Here's an interesting task that I faced today and I cannot think of any easy way to achieve the desired result.
Let's suppose we have a database with the following fields (columns): A,B,C,D,E,F,G but we don't know the names nor the count of the fields.
We receive a set of records from this database in the following format: {A:value1, B:value2, ...}.
If a value is not set for the current record the key will be missing too. This means I can receive {A:value} or {C:value1, D:value2} as valid records. The order of the keys will always stay the same. This means {D:value, C:value} is not a valid record.
I'm trying to recover the field names based on the returned records and keep the order of the keys.
For example I can receive records with the following keys:
A,C,D,E,F
D,F,G
A,B,F
From the example above I should be able to restore the original sequence which is A,B,C,D,E,F,G.
The first record gives us A,C,D,E,F.
The second one tells us that G is after F so now we have A,C,D,E,F,G
The third record gives us that B is after A so now we have A,B,C,D,E,F,G
If the order cannot be determined for sure we can use alphabetical order. Example for this is:
A,B
A,C
In the example above we cannot determine if the original order is A,B,C or A,C,B.
Any ideas how to implement this to work in the general case?
I will be implementing this algorithm using JavaScript but PHP, C++ or Java are also welcome.
EDIT: Do not think of the objects as standart JSON objects. In the real environment the structure is much more complex and the language is not pure JavaScript, but a modified version of ECMAScript. If it will be easier to understand - think only of the keys as an array of values ['A','B','C',...] and try to merge them, keeping the order.
EDIT 2: After struggling for some time and reading some ideas I came with the following solution:
Create an object that holds all relations - which column comes after which from each database record.
Create a relation between each a->b, b->c => a->c (inspired by Floyd–Warshall where each distance is considered as 1 if exists).
Create a sorting function (comparator) that will check if two elements can be compared. If not - alphabetical order will be used.
Get only the unique column names and sort them using the comparator function.
You can find the source-code attached below:
var allComparators = {};
var knownObjects = ['A,C,D,E,F','D,F,G','A,B,F'];
var allFields = knownObjects.join(',').split(',');
for (var i in knownObjects) {
var arr = knownObjects[i].split(',');
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
allComparators[arr[i]+'_'+arr[j]] = 1;
}
}
}
allFields = allFields.filter(function(value, index, self) {
return self.indexOf(value) === index;
});
for (var i in allFields) {
for (var j in allFields) {
for (var k in allFields) {
if (allComparators[allFields[i]+'_'+allFields[j]] && allComparators[allFields[j]+'_'+allFields[k]]) {
allComparators[allFields[i]+'_'+allFields[k]] = 1;
}
}
}
}
allFields.sort(function(a, b) {
if (typeof allComparators[a + '_' + b] != 'undefined') {
return -1;
}
if (typeof allComparators[b + '_' + a] != 'undefined') {
return 1;
}
return a > b;
});
console.log(allFields);
I give you the algorithm in a very direct and understandable way but the code! please try yourself and ask for help if required.
I express myself in two ways
In technical terms :
Generate a precedence graph (that is a directed graph)
Topological sort it
In more details :
Graph : Map(String, ArrayList< String >) = [Map(key,value)]
each key in the map corresponds to an element (A,B,C,...)
each value contains the elements that should place after the key,e.g for A it is {B,C,D,...}
How to fill the graph :
for each row:
for each element inside the row:
if the element is already as a key in the map
just add its immediate next item to the list*
else
add the element to the map and set the value to immediate next element of it**
* if the element is the last one in the row don't add anything to the map
** if the element is the last one in the row use {}, an empty list, as the value
Topological sort:
List sortedList;
for each key in the map:
if value.size() == 0 {
remove key from the map
add it the key to the sortedList
for each key' in the map:
if value'.contains(key)
value'.remove(key) (and update the map)
}
invert the sortedList
Test case :
the map for your first input will be:
{ A:{C,B} , C:{D} , D:{E,F} , E:{F} , F:{G} , G:{} , B:{F} }
Sort :
1 - G -> sortedList, map = { A:{C,B} , C:{D} , D:{E,F} , E:{F} , F:{} , B:{F} }
2 - F -> sortedList, map = { A:{C,B} , C:{D} , D:{E} , E:{} , B:{} }
3 - E -> sortedList, map = { A:{C,B} , C:{D} , D:{} }
4 - D -> sortedList, map = { A:{C,B} , C:{} }
5 - C -> sortedList, map = { A:{B} , B:{} }
6 - B -> sortedList, map = { A:{} }
6 - A -> sortedList, map = { }
sortedList = {G,F,E,D,C,B,A}
Invert - > {A,B,C,D,E,F,G}
do you think something like this would work?
var oMergedList = [];
function indexOfColumn(sColumnName)
{
for(var i = 0 ; i < oMergedList.length;i++)
if(oMergedList[i]==sColumnName)
return i;
return -1;
}
function getOrdinalIndex(sColumnName)
{
var i = 0;
for( ; i < oMergedList.length;i++)
if(oMergedList[i]>sColumnName)
break;
return i;
}
function merge(oPartial)
{
var nPreviousColumnPosition = -1;
for(var i = 0 ; i < oPartial.length;i++)
{
var sColumnName = oPartial[i] ;
var nColumnPosition = indexOfColumn(sColumnName);
if(nColumnPosition>=0)//already contained
{
if(nPreviousColumnPosition>=0 && nColumnPosition!=(nPreviousColumnPosition+1))//but inserted on wrong place
{
oMergedList.splice(nColumnPosition, 1);
nColumnPosition = nPreviousColumnPosition
oMergedList.splice(nColumnPosition, 0, sColumnName);
}
nPreviousColumnPosition = nColumnPosition;
}
else //new
{
if(nPreviousColumnPosition<0)//no reference column
{
nPreviousColumnPosition = getOrdinalIndex(sColumnName);
}
else// insert after previous column
nPreviousColumnPosition++;
oMergedList.splice(nPreviousColumnPosition, 0, sColumnName);
}
}
}
/* latest sample
merge(['A','C','E','G']);
merge(['A','D']);
merge(['C','D']);
*/
/* default sample
merge(['A','C','D','E','F']);
merge(['D','F','G']);
merge(['A','B','F']);
*/
/* fix order
merge(['A','B']);
merge(['A','C']);
merge(['A','B','C']);
*/
/* insert alphabetically
merge(['B']);
merge(['A']);
merge(['C']);
*/
document.body.innerHTML = oMergedList.join(',');
the only "undefined" parts are where to insert if you have no previous columns (I putted in firt position)
and second in the case A,B.. A,C the columns will be inserted when first seen
means A,B..A,C will give A,C,B .. and means A,C..A,B will give A,B,C
edited to use the current array position to fix
previous addition so if you add [A,C][A,B] you will get [A,C,B] but if you then pass [A,B,C]
the array will be fixed to reflect the new order
also when new columns appears and there is no reference column appends in alphabetical order
fixed the column correctioning par.. should now give you the correct result..
As described by JSON.org there is not such thing as a Json ordered keys:
An object is an unordered set of name/value pairs.
That being said, it becomes quite easy to merge objects as you don't need the order.
for (var attrname in obj2) { obj1[attrname] = obj2[attrname]; }
Source: How can I merge properties of two JavaScript objects dynamically?
I have a JSON response like this:
var errorLog = "[[\"comp\",\"Please add company name!\"],
[\"zip\",\"Please add zip code!\"],
...
Which I'm deserializing like this:
var log = jQuery.parseJSON(errorLog);
Now I can access elements like this:
log[1][1] > "Please add company name"
Question:
If I have the first value comp, is there a way to directly get the 2nd value by doing:
log[comp][1]
without looping through the whole array.
Thanks for help!
No. Unless the 'value' of the first array (maybe I should say, the first dimension, or the first row), is also it's key. That is, unless it is something like this:
log = {
'comp': 'Please add a company name'
.
.
.
}
Now, log['comp'] or log.comp is legal.
There are two was to do this, but neither avoids a loop. The first is to loop through the array each time you access the items:
var val = '';
for (var i = 0; i < errorLog.length; i++) {
if (errorLog[i][0] === "comp") {
val = errorLog[i][1];
break;
}
}
The other would be to work your array into an object and access it with object notation.
var errors = {};
for (var i = 0; i < errorLog.length; i++) {
errors[errorLog[i][0]] = errorLog[i][1];
}
You could then access the relevant value with errors.comp.
If you're only looking once, the first option is probably better. If you may look more than once, it's probably best to use the second system since (a) you only need to do the loop once, which is more efficient, (b) you don't repeat yourself with the looping code, (c) it's immediately obvious what you're trying to do.
No matter what you are going to loop through the array somehow even it is obscured for you a bit by tools like jQuery.
You could create an object from the array as has been suggested like this:
var objLookup = function(arr, search) {
var o = {}, i, l, first, second;
for (i=0, l=arr.length; i<l; i++) {
first = arr[i][0]; // These variables are for convenience and readability.
second = arr[i][1]; // The function could be rewritten without them.
o[first] = second;
}
return o[search];
}
But the faster solution would be to just loop through the array and return the value as soon as it is found:
var indexLookup = function(arr, search){
var index = -1, i, l;
for (i = 0, l = arr.length; i<l; i++) {
if (arr[i][0] === search) return arr[i][1];
}
return undefined;
}
You could then just use these functions like this in your code so that you don't have to have the looping in the middle of all your code:
var log = [
["comp","Please add company name!"],
["zip","Please add zip code!"]
];
objLookup(log, "zip"); // Please add zip code!
indexLookup(log, "comp"); // Please add company name!
Here is a jsfiddle that shows these in use.
Have you looked at jQuery's grep or inArray method?
See this discussion
Are there any jquery features to query multi-dimensional arrays in a similar fashion to the DOM?