How to use Math.max.apply on an array containing arrays - javascript

I have the following js functions:
function Weekday (name, traffic) {
this.name = name;
this.traffic = traffic;
}
function mostDays(){
var traffic=[];
traffic[0] = new Weekday('monday',6);
traffic[1] = new Weekday('tuesday',5);
return traffic;
}
function mostPopularDays(week) {
if(week.length == 0 || week === null) return null;
return Math.max.apply(Math, week['traffic']);
}
so if I say:
var x = mostDays();
var test = mostPopularDays(x);
I'm trying to get back 6 which is the max value of traffic amongst the Weekday objects . Is there a way to reference this correctly or do I need to do all this in a loop?

You could map the wanted property of the object and get then the max value.
function Weekday(name, traffic) {
this.name = name;
this.traffic = traffic;
}
function mostDays() {
var traffic = [];
traffic[0] = new Weekday('monday', 6);
traffic[1] = new Weekday('tuesday', 5);
return traffic;
}
function mostPopularDays(week) {
if (!week.length) return null;
return Math.max.apply(Math, week.map(function (o) { return o.traffic; }));
}
var x = mostDays();
var test = mostPopularDays(x);
console.log(test);
With ES6, you could spread the values for Math.max.
function Weekday(name, traffic) {
this.name = name;
this.traffic = traffic;
}
function mostDays() {
return [
new Weekday('monday', 6),
new Weekday('tuesday', 5)
];
}
function mostPopularDays(week) {
if (!week.length) return null;
return Math.max(...week.map(o => o.traffic));
}
var x = mostDays();
var test = mostPopularDays(x);
console.log(test);

If you want to return the day for which traffic is the most then Math.max cannot do it on its own, as it will just return the highest traffic (if you code it right), but not the day.
You could use find to do the lookup of the day with the highest traffic:
var maxTraffic = Math.max(...week.map(day => day.traffic));
return week.find(day => day.traffic === maxTraffic);

Another option can be to specify the default valueOf of the object:
function Weekday(name, traffic) {
this.name = name;
this.traffic = traffic;
}
Weekday.prototype.valueOf = function() { return this.traffic }
var days = [ new Weekday('monday', 6), new Weekday('tuesday', 5) ]
console.log( Math.max.apply(0, days) )
console.log( Math.max(...days) )
Otherwise, reduce can be used:
function Weekday(name, traffic) {
this.name = name;
this.traffic = traffic;
}
var days = [ new Weekday('monday', 6), new Weekday('tuesday', 5) ]
var max = days.reduce((m, d) => Math.max(m, d.traffic), 0)
console.log(max )

Related

Javascript: making functions at runtime

update
solution works in foreach loop but not in for loop
function x(number){
return number - 10;
}
var i = 0
var runtimefunctions = {};
var allLevels = {"1":"State","2":"Educational_Services","3":"Principal_Networks","4":"Schools"}
for (var key in allLevels) {
runtimefunctions[i] = function() { return x(i); };
i++;
};
console.log(runtimefunctions[1]()); // -6
console.log(runtimefunctions[2]()); // -6
console.log(runtimefunctions[3]()); // -6
tried hard to make functions but it's first time to create such thing so cant understand the proper way...
I have a function..
function x(number){
return number - 10;
}
runtimefunctions = {};
now I have a loop to run
[1,2,3].forEach(function(y){
//here I want to create a function.. which will make a function x(y) -- like this
runtimefunctions[x] = new Function("return function x_" + levelIterator + "(levelIterator){ console.log(levelIterator); x(" + y + ") }")();
});
so basically..want to make functions like this.
runtimefunctions= {
"1": x(1),
"2": x(2),
and so on
}
Is this what you need?
function x(number){
return number - 10;
}
var runtimefunctions = {};
[1,2,3].forEach(function(y){
runtimefunctions[y] = function() { return x(y); };
});
console.log(runtimefunctions[1]()); // -9
console.log(runtimefunctions[2]()); // -8
console.log(runtimefunctions[3]()); // -7
To satisfy your next (for-in) requirement, you need to closure the index variable with additional function call:
var runtimefunctions = {}, i = 0;
var allLevels = {"1":"State","2":"Educational_Services","3":"Principal_Networks","4":"Schools"}
for (var key in allLevels) {
runtimefunctions[i] = function(index){ return function() { return x(index); } }(i++);
};
It is much easier.
For example:
const createFunctionWith = (x) => {
return (param) => console.log(x, param)
}
let a = [1,2,3].map(x => createFunctionWith(x));
console.log(a[1]("bebe")); // 2, "bebe"
https://jsfiddle.net/muLxoxLd/
You could do something like this
// Found in your code
var x = (a) => {
console.log(a)
};
var runtimefunctions = {};
[1, 2, 3].forEach(function(y) {
//Create a function with a parameter named "levelIterator"
runtimefunctions[y] = Function("levelIterator", "{ console.log(levelIterator); x(" + y + ") }");
});
runtimefunctions[1]('test')

Optimize javascript loop on CSV data

I am plotting a graph using d3.js by loading an external .CSV file.
The code i have so far works fine with a small amount of data but when i load a larger file with thousands of lines then it kills the page.
The data has a usage column which is a value for every 30 mins throughout the day, which will go on over several months.
See Plunker example.
var avgClientArr = [];
var dateArr = [];
var dateGroupArr = [];
function csvParseClient() {
d3.xhr('client.csv').get(function(err, response) {
var dirtyCSV = response.responseText;
var initialClientKeys = /TYPE,DATE,START TIME,END TIME,USAGE,UNITS,NOTES/i;
var newClientKeys = "TYPE,x,startTime,endTime,y,UNITS,NOTES";
var csvDataClient = dirtyCSV.replace(initialClientKeys, newClientKeys);
var validData = csvDataClient.substr(csvDataClient.indexOf(newClientKeys));
var csvData = d3.csv.parse(validData);
csvData.customForEach(function(val, i) {
// filter data
//var keep = ['x', 'startTime', 'endTime', 'UNITS', 'y'];
//for (var key in val[i]) {
// if (keep.indexOf(key) === -1) {
// delete val[i][key];
// }
//}
// parse data
var date = val.x;
var usage = val.y;
var startTime = val.startTime;
var endTime = val.endTime;
var x = new Date(date);
var y = parseFloat(usage);
dateArr.push({
"date": x,
"usage": y
})
dateGroupArr = groupBy(dateArr, 'date');
})
console.log(dateGroupArr);
var objDates = objectValues(dateGroupArr);
objDates.customForEach(function(f) {
var avg = f.reduce(function(a, b) {
return a + b.usage;
}, 0) / f.length;
var date = f.reduce(function(a, b) {
return new Date(b.date);
}, 0);
avgClientArr.push({
"x": date,
"y": avg
})
})
//console.log("avgClientArr", avgClientArr);
document.getElementById('arrayDiv').innerHTML = '<pre>' + JSON.stringify(avgClientArr, null, 4) + '</pre>';
})
}
function groupBy(arr, key) {
var reducer = (grouped, item) => {
var group_value = item[key]
if (!grouped[group_value]) {
grouped[group_value] = []
}
grouped[group_value].push(item)
return grouped
}
return arr.reduce(reducer, {})
}
function objectValues(object) {
var values = []
for (var property in object) {
if (object.hasOwnProperty(property)) {
values.push(object[property])
}
}
return values
}
function foreach(fn) {
var arr = this;
var len = arr.length;
for (var i = 0; i < len; ++i) {
fn(arr[i], i);
}
}
Object.defineProperty(Array.prototype, 'customForEach', {
enumerable: false,
value: foreach
});
var t0 = performance.now();
csvParseClient();
var t1 = performance.now();
console.log("Call csvParseClient() " + (t1 - t0) + " milliseconds.");
What i need to happen
I need the average value of usage for the whole day returned as y and the date for that day returned as x for each day.
The slow process i have
Start the loop from a specified line in the CSV file as there is unwanted data on the first few lines.
Group unique date and store each usage value for that date in an object.
Average the usage values for each date.
Output an array of objects with property x being the date and y being the average usage value.
If you can give me any help on how to make this run faster that would be great!
I solved this by using the d3 nest() and rollup() functions, its simple and really fast.
d3.nest()
.key(function(d) {
return d.x;
})
.rollup(function(d) {
var avg = d3.mean(d, function(g) {return g.y; });
return avg;
}).entries(dateArr);

Filter Array on same name and date

var Array = [{"Name":"Temp","Date":"2014-10-23"},
{"Name":"Temp","Date":"2014-10-22"},
{"Name":"Temp","Date":"2014-10-18"},
{"Name":"Temp","Date":"2014-10-19"},
{"Name":"Temp2","Date":"2014-10-12"},
{"Name":"Temp2","Date":"2014-06-12"}]
What would be the best way to filter the above array on the following condition.
*If the name is the same, then filter away all the objects with the same name and leave only the object with the latest date left.
All I can think of is to do for loops. Note that Date is a real dateObject and not a string as I have wrote above which means that you can do Date comparisons.
Using Array.reduce(), i was able to get it down to an object containing only Temp and Temp2 with their dates:
var obj = array.reduce(function(base,cur){
if (base[cur.Name]) {
if (base[cur.Name].Date < cur.Date) {
base[cur.Name].Date = cur.Date;
} else {
return base;
}
} else {
base[cur.Name] = cur;
return base;
}
},{});
From there, you can just get the object's values with Object.keys().map():
array = Object.keys(obj).map(function(k){
return obj[k];
});
var array = [{"Name":"Temp","Date":new Date("2014-10-23")},
{"Name":"Temp","Date":new Date("2014-10-22")},
{"Name":"Temp","Date":new Date("2014-10-18")},
{"Name":"Temp","Date":new Date("2014-10-19")},
{"Name":"Temp2","Date":new Date("2014-10-12")},
{"Name":"Temp2","Date":new Date("2014-06-12")}];
var obj = {};
for(var i in array){
if(!obj[array[i].Name] || obj[array[i].Name].getTime() < array[i].Date.getTime())
obj[array[i].Name] = array[i].Date;
}
then if you need it to be an array of objects:
array = [];
for(var name in obj){
array.push({Name: name, Date: obj[name]});
}
var Array = [{"Name":"Temp","Date":"2014-10-23"},
{"Name":"Temp","Date":"2014-10-22"},
{"Name":"Temp","Date":"2014-10-18"},
{"Name":"Temp","Date":"2014-10-19"},
{"Name":"Temp2","Date":"2014-10-12"},
{"Name":"Temp2","Date":"2014-06-12"}]
var result = {};
Array.forEach(function(item) {
var name = item['Name'];
if(result[name]) {
result[name].push(item['Date']);
} else {
result[name] = [item['Date']];
}
})
Object.keys(result).forEach(function(item) {
result[item] = Math.max.apply(null, result[item]);
})
aggragate the array by name to {Name: 'Temp', Date: ['2014-10-23', '2013-10-32', 'xxxx']}
then get the max date by Math.max.apply
Here you go, Works perfect for any random orders
var Array = [{"Name":"Temp","Date":"2014-10-03"},
{"Name":"Temp","Date":"2014-10-22"},
{"Name":"Temp","Date":"2014-10-18"},
{"Name":"Temp","Date":"2014-10-19"},
{"Name":"Temp2","Date":"2014-10-12"},
{"Name":"Temp2","Date":"2014-06-12"}];
var tempArray = []; //This array will hold your Result Set
var tempArrayName = [];
var tempDate = {};
$.each(Array, function(i, v) {
if(tempArrayName.indexOf(Array[i].Name) < 0){
tempArray.push({ "Name":Array[i].Name,"Date":Array[i].Date});
tempArrayName.push(Array[i].Name);
tempDate[Array[i].Name] = Array[i].Date;
}else{
if( new Date((Array[i].Date))> new Date(tempDate[Array[i].Name])){
$.each(tempArray, function(j, k) {
if(tempArray[j].Name == Array[i].Name){
tempArray[j].Date = Array[i].Date;
}
});
}
}
});
console.log(tempArray);
I'd advise against overwriting the Array object. Anyhow,
one way would be to group the dates using an object:
var data = [
{"Name":"Temp","Date": new Date("2014-10-23")},
{"Name":"Temp","Date": new Date("2014-10-22")},
{"Name":"Temp","Date": new Date("2014-10-18")},
{"Name":"Temp","Date": new Date("2014-10-19")},
{"Name":"Temp2","Date": new Date("2014-10-12")},
{"Name":"Temp2","Date": new Date("2014-06-12")}
];
var name2dates = Object.create(null);
data.forEach(function(thing){
if(!name2dates[thing.Name]){
name2dates[thing.Name] = thing.Date;
}else{
name2dates[thing.Name] = thing.Date > name2dates[thing.Name] ?
thing.Date : name2dates[thing.Name];
}
});
And to create a 'filtered' array from this, if that's what you
need, then:
Object.keys(name2dates).map(function(name){
return {Name: name, Date: name2dates[name]};
});
Is this helpful?
$.grep(Array, function(v) {
return v.Name === "Temp";
}).reduce(function (a, b) { return a.Date > b.Date ? a : b; });

JavaScript basics: array or arrays

I'm trying to learn JavaScript and am going through an exercise where I'm creating a grocery list that populates with a food, quantity, and cost. I cannot seem to pass in multiple variables or make an array of arrays. I tried some other options like "new Object" but I can't get anything off the ground. Give me a clue?
var groceryList = function(food, quantity, price) {
var theItem = [food, quantity, price]
var theList = new Array();
theList.push(theItem)
}
myList = new groceryList("cookie", 2, 1.00)
console.log(myList)
Use this
var groceryList = function(food, quantity, price) {
var theItem = [food, quantity, price]
var theList = new Array();
theList.push(theItem);
return theList;
}
myList = new groceryList("cookie", 2, 1.00)
console.log(myList)
If you want to use objects, then you need to change your thinking a little bit. When you create an object with new then the constructor gets called.
function GroceryList(food, quantity, price) {
this.food = food;
this.quantity = quantity;
this.price = price;
}
GroceryList.prototype.toString = function() {
return this.food + (this.quantity).toString() + (this.price).toString();
}
// lazy array syntax
var GroceryListPool = [];
// popular the array list pool
var list1 = new GroceryList("Butter", 2, 3.999);
GroceryListPool.push(list1);
To iterate the GroceryListPool array:
for(var i = 0; i < GroceryListPool.length; i++) {
var list = GroceryListPool[i];
// list is an object of type GroceryList
// technically it is not a "type", but you know what I mean.
alert(list);
}
That's not even really a Constructor, yet. Check this out.
function groceryList(food, quantity, price){
this.items = {};
if(food !== undefined){
this.items[food] = {quantity:quantity, price:price, total:quantity*price};
}
this.addItem = function(food, quantity, price){
this.items[food] = {quantity:quantity, price:price, total:quantity*price};
}
this.getFood(food){
return this.items[food];
}
this.getQuantity = function(food){
return this.items[food].quantity;
}
this.getTotal = function(food){
return this.items[food].total;
}
this.getItemsByPrice(low, high){
var r = {}, t = this.items;
for(var i in t){
var f = t[i], p = f.price;
if(p >= low && p <= high){
r[i] = f;
}
}
return r;
}
}
var groc = new groceryList('potato', 4, 0.89);
groc.addItem('orange', 10, 1);
console.log(groc.getQuantity('potato'));
console.log(groc.getTotal('orange'));
console.log(groc.getFood('orange').price);
// same as
console.log(groc.getPrice('orange'));
// or
console.log(groc.items.orange.price);
groc.addItem('pear', 200, 0.75);
console.log(groc.getItemsByPrice(0.25, 0.99)); // should be Object with 'potato' and 'pear'

Javascript HashTable use Object key

I want to create a hash table with Object keys that are not converted into String.
Some thing like this:
var object1 = new Object();
var object2 = new Object();
var myHash = new HashTable();
myHash.put(object1, "value1");
myHash.put(object2, "value2");
alert(myHash.get(object1), myHash.get(object2)); // I wish that it will print value1 value2
EDIT: See my answer for full solution
Here is a simple Map implementation that will work with any type of key, including object references, and it will not mutate the key in any way:
function Map() {
var keys = [], values = [];
return {
put: function (key, value) {
var index = keys.indexOf(key);
if(index == -1) {
keys.push(key);
values.push(value);
}
else {
values[index] = value;
}
},
get: function (key) {
return values[keys.indexOf(key)];
}
};
}
While this yields the same functionality as a hash table, it's not actually implemented using a hash function since it iterates over arrays and has a worst case performance of O(n). However, for the vast majority of sensible use cases this shouldn't be a problem at all. The indexOf function is implemented by the JavaScript engine and is highly optimized.
Here is a proposal:
function HashTable() {
this.hashes = {};
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
this.hashes[ JSON.stringify( key ) ] = value;
},
get: function( key ) {
return this.hashes[ JSON.stringify( key ) ];
}
};
The API is exactly as shown in your question.
You can't play with the reference in js however (so two empty objects will look like the same to the hashtable), because you have no way to get it. See this answer for more details: How to get javascript object references or reference count?
Jsfiddle demo: http://jsfiddle.net/HKz3e/
However, for the unique side of things, you could play with the original objects, like in this way:
function HashTable() {
this.hashes = {},
this.id = 0;
}
HashTable.prototype = {
constructor: HashTable,
put: function( obj, value ) {
obj.id = this.id;
this.hashes[ this.id ] = value;
this.id++;
},
get: function( obj ) {
return this.hashes[ obj.id ];
}
};
Jsfiddle demo: http://jsfiddle.net/HKz3e/2/
This means that your objects need to have a property named id that you won't use elsewhere. If you want to have this property as non-enumerable, I suggest you take a look at defineProperty (it's not cross-browser however, even with ES5-Shim, it doesn't work in IE7).
It also means you are limited on the number of items you can store in this hashtable. Limited to 253, that is.
And now, the "it's not going to work anywhere" solution: use ES6 WeakMaps. They are done exactly for this purpose: having objects as keys. I suggest you read MDN for more information: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/WeakMap
It slightly differs from your API though (it's set and not put):
var myMap = new WeakMap(),
object1 = {},
object2 = {};
myMap.set( object1, 'value1' );
myMap.set( object2, 'value2' );
console.log( myMap.get( object1 ) ); // "value1"
console.log( myMap.get( object2 ) ); // "value2"
Jsfiddle demo with a weakmap shim: http://jsfiddle.net/Ralt/HKz3e/9/
However, weakmaps are implemented in FF and Chrome (only if you enable the "Experimental javascript features" flag in chrome however). There are shims available, like this one: https://gist.github.com/1269991. Use at your own risk.
You can also use Maps, they may more suit your needs, since you also need to store primitive values (strings) as keys. Doc, Shim.
I took #Florian Margaine's suggestion to higher level and came up with this:
function HashTable(){
var hash = new Object();
this.put = function(key, value){
if(typeof key === "string"){
hash[key] = value;
}
else{
if(key._hashtableUniqueId == undefined){
key._hashtableUniqueId = UniqueId.prototype.generateId();
}
hash[key._hashtableUniqueId] = value;
}
};
this.get = function(key){
if(typeof key === "string"){
return hash[key];
}
if(key._hashtableUniqueId == undefined){
return undefined;
}
return hash[key._hashtableUniqueId];
};
}
function UniqueId(){
}
UniqueId.prototype._id = 0;
UniqueId.prototype.generateId = function(){
return (++UniqueId.prototype._id).toString();
};
Usage
var map = new HashTable();
var object1 = new Object();
map.put(object1, "Cocakola");
alert(map.get(object1)); // Cocakola
//Overriding
map.put(object1, "Cocakola 2");
alert(map.get(object1)); // Cocakola 2
// String key is used as String
map.put("myKey", "MyValue");
alert(map.get("myKey")); // MyValue
alert(map.get("my".concat("Key"))); // MyValue
// Invalid keys
alert(map.get("unknownKey")); // undefined
alert(map.get(new Object())); // undefined
Here is a proposal, combining #Florian's solution with #Laurent's.
function HashTable() {
this.hashes = [];
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
this.hashes.push({
key: key,
value: value
});
},
get: function( key ) {
for( var i = 0; i < this.hashes.length; i++ ){
if(this.hashes[i].key == key){
return this.hashes[i].value;
}
}
}
};
It wont change your object in any way and it doesn't rely on JSON.stringify.
I know that I am a year late, but for all others who stumble upon this thread, I've written the ordered object stringify to JSON, that solves the above noted dilemma: http://stamat.wordpress.com/javascript-object-ordered-property-stringify/
Also I was playing with custom hash table implementations which is also related to the topic: http://stamat.wordpress.com/javascript-quickly-find-very-large-objects-in-a-large-array/
//SORT WITH STRINGIFICATION
var orderedStringify = function(o, fn) {
var props = [];
var res = '{';
for(var i in o) {
props.push(i);
}
props = props.sort(fn);
for(var i = 0; i < props.length; i++) {
var val = o[props[i]];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val, fn);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += '"'+props[i]+'":'+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+'}';
};
//orderedStringify for array containing objects
var arrayStringify = function(a, fn) {
var res = '[';
for(var i = 0; i < a.length; i++) {
var val = a[i];
var type = types[whatis(val)];
if(type === 3) {
val = orderedStringify(val, fn);
} else if(type === 2) {
val = arrayStringify(val);
} else if(type === 1) {
val = '"'+val+'"';
}
if(type !== 4)
res += ''+ val+',';
}
return res.substring(res, res.lastIndexOf(','))+']';
}
Based on Peters answer, but with proper class design (not abusing closures), so the values are debuggable. Renamed from Map to ObjectMap, because Map is a builtin function. Also added the exists method:
ObjectMap = function() {
this.keys = [];
this.values = [];
}
ObjectMap.prototype.set = function(key, value) {
var index = this.keys.indexOf(key);
if (index == -1) {
this.keys.push(key);
this.values.push(value);
} else {
this.values[index] = value;
}
}
ObjectMap.prototype.get = function(key) {
return this.values[ this.keys.indexOf(key) ];
}
ObjectMap.prototype.exists = function(key) {
return this.keys.indexOf(key) != -1;
}
/*
TestObject = function() {}
testA = new TestObject()
testB = new TestObject()
om = new ObjectMap()
om.set(testA, true)
om.get(testB)
om.exists(testB)
om.exists(testA)
om.exists(testB)
*/
When you say you don't want your Object keys converted into Strings, I'm going to assume it's because you just don't want the entire code contents of your Objects being used as keys. This, of course, makes perfect sense.
While there is no "hash table" in Javascript per-se, you can accomplish what you're looking for by simply overriding your Object's prototype.toString and returning a valid key value that will be unique to each instance. One way to do this is with Symbol():
function Obj () {
this.symbol = Symbol() // Guaranteed to be unique to each instance
}
Obj.prototype.toString = function () {
return this.symbol // Return the unique Symbol, instead of Obj's stringified code
}
let a = new Obj()
let b = new Obj()
let table = {}
table[a] = 'A'
table[b] = 'B'
console.log(table) // {Symbol(): 'A', Symbol(): 'B'}
console.log(table[a]) // A
console.log(table[b]) // B
Using JSON.stringify() is completely awkward to me, and gives the client no real control over how their keys are uniquely identified. The objects that are used as keys should have a hashing function, but my guess is that in most cases overriding the toString() method, so that they will return unique strings, will work fine:
var myMap = {};
var myKey = { toString: function(){ return '12345' }};
var myValue = 6;
// same as myMap['12345']
myMap[myKey] = myValue;
Obviously, toString() should do something meaningful with the object's properties to create a unique string. If you want to enforce that your keys are valid, you can create a wrapper and in the get() and put() methods, add a check like:
if(!key.hasOwnProperty('toString')){
throw(new Error('keys must override toString()'));
}
But if you are going to go thru that much work, you may as well use something other than toString(); something that makes your intent more clear.
So a very simple proposal would be:
function HashTable() {
this.hashes = {};
}
HashTable.prototype = {
constructor: HashTable,
put: function( key, value ) {
// check that the key is meaningful,
// also will cause an error if primitive type
if( !key.hasOwnProperty( 'hashString' ) ){
throw( new Error( 'keys must implement hashString()' ) );
}
// use .hashString() because it makes the intent of the code clear
this.hashes[ key.hashString() ] = value;
},
get: function( key ) {
// check that the key is meaningful,
// also will cause an error if primitive type
if( !key.hasOwnProperty( 'hashString' ) ){
throw( new Error( 'keys must implement hashString()' ) );
}
// use .hashString() because it make the intent of the code clear
return this.hashes[ key.hashString() ];
}
};
Inspired by #florian, here's a way where the id doesn't need JSON.stringify:
'use strict';
module.exports = HashTable;
function HashTable () {
this.index = [];
this.table = [];
}
HashTable.prototype = {
constructor: HashTable,
set: function (id, key, value) {
var index = this.index.indexOf(id);
if (index === -1) {
index = this.index.length;
this.index.push(id);
this.table[index] = {};
}
this.table[index][key] = value;
},
get: function (id, key) {
var index = this.index.indexOf(id);
if (index === -1) {
return undefined;
}
return this.table[index][key];
}
};
I took #Ilya_Gazman solution and improved it by setting '_hashtableUniqueId' as a not enumerable property (it won't appear in JSON requests neither will be listed in for loops). Also removed UniqueId object, since it is enough using only HastTable function closure. For usage details please see Ilya_Gazman post
function HashTable() {
var hash = new Object();
return {
put: function (key, value) {
if(!HashTable.uid){
HashTable.uid = 0;
}
if (typeof key === "string") {
hash[key] = value;
} else {
if (key._hashtableUniqueId === undefined) {
Object.defineProperty(key, '_hashtableUniqueId', {
enumerable: false,
value: HashTable.uid++
});
}
hash[key._hashtableUniqueId] = value;
}
},
get: function (key) {
if (typeof key === "string") {
return hash[key];
}
if (key._hashtableUniqueId === undefined) {
return undefined;
}
return hash[key._hashtableUniqueId];
}
};
}
The best solution is to use WeakMap when you can (i.e. when you target browsers supporting it)
Otherwise you can use the following workaround (Typescript written and collision safe):
// Run this in the beginning of your app (or put it into a file you just import)
(enableObjectID)();
const uniqueId: symbol = Symbol('The unique id of an object');
function enableObjectID(): void {
if (typeof Object['id'] !== 'undefined') {
return;
}
let id: number = 0;
Object['id'] = (object: any) => {
const hasUniqueId: boolean = !!object[uniqueId];
if (!hasUniqueId) {
object[uniqueId] = ++id;
}
return object[uniqueId];
};
}
Then you can simply get a unique number for any object in your code (like if would have been for pointer address)
let objectA = {};
let objectB = {};
let dico = {};
dico[(<any>Object).id(objectA)] = "value1";
// or
dico[Object['id'](objectA);] = "value1";
// If you are not using typescript you don't need the casting
dico[Object.id(objectA)] = "value1"
I know I'm late, but here's a simple HashMap implementation:
Function.prototype.toJSON = Function.prototype.toString;
//taken from https://stackoverflow.com/questions/1249531/how-to-get-a-javascript-objects-class
function getNativeClass(obj) {
if (typeof obj === "undefined") return "undefined";
if (obj === null) return "null";
return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
}
function globals() {
if (typeof global === "object") //node
return global;
return this;
}
function lookup(x) {
return globals()[x];
}
function getAnyClass(obj) {
if (typeof obj === "undefined") return "undefined";
if (obj === null) return "null";
return obj.constructor.name;
}
//taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#examples
var getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(value);
}
return value;
};
};
function encode(x) {
if (typeof x === "object" && x !== null) {
var y = myClone(x);
x = Object.getPrototypeOf(x);
for (var i = 0; i < Object.getOwnPropertyNames(y).length; i++) { //Make enumerable
x[Object.getOwnPropertyNames(y)[i]] = y[Object.getOwnPropertyNames(y)[i]];
}
}
return getAnyClass(x) + " " + JSON.stringify(x, getCircularReplacer());
}
function decode(x) {
var a = x.split(" ").slice(1).join(" "); //OBJECT
if (typeof lookup(x.split(" ")[0])) {
return new (lookup(x.split(" ")[0]))(JSON.parse(a))
} else {
return JSON.parse(a);
}
}
//taken from https://github.com/feross/fromentries/blob/master/index.js
/*! fromentries. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
function fromEntries(iterable) {
return [...iterable].reduce((obj, [key, val]) => {
obj[key] = val
return obj
}, {})
}
var toEnumerable = (obj) => {
return fromEntries(
Object.getOwnPropertyNames(obj).map(prop => [prop, obj[prop]])
);
};
//taken from https://stackoverflow.com/questions/41474986/how-to-clone-a-javascript-es6-class-instance
function myClone(instanceOfBlah) {
if (typeof instanceOfBlah !== "object" || !instanceOfBlah) { return instanceOfBlah; }
const clone = Object.assign({}, toEnumerable(instanceOfBlah));
const Blah = instanceOfBlah.constructor;
Object.setPrototypeOf(clone, Blah.prototype);
return clone;
}
function HashMap(a) {
if (typeof a === "undefined") {
a = [];
}
a = Array.from(a);
a = a.map((e) => [encode(e[0]), e[1]]);
this.a = a;
}
HashMap.from = function (a) {
var temp = myClone(a);
//convert to array
a = [];
for (var i = 0; i < Object.getOwnPropertyNames(temp).length; i++) {
a.push([Object.getOwnPropertyNames(temp)[i], temp[Object.getOwnPropertyNames(temp)[i]]]);
}
return new HashMap(a);
}
HashMap.prototype.put = function (x, y) {
this.a.push([encode(x), y]);
}
HashMap.prototype.get = function (x) {
var t1 = this.a.map((e) => e[0]);
return this.a[t1.indexOf(encode(x))][1];
}
HashMap.prototype.length = function () {
return this.a.length;
}
HashMap.prototype.toString = function () {
var result = [];
for (var i = 0; i < this.length(); i++) {
result.push(JSON.stringify(decode(this.a[i][0]), getCircularReplacer()) + " => " + this.a[i][1]);
}
return "HashMap {" + result + "}";
}
var foo = new HashMap();
foo.put("SQRT3", Math.sqrt(3));
foo.put({}, "bar");
console.log(foo.get({}));
console.log(foo.toString());
Note that it is ordered.
Methods:
put: Adds an item
get: Access an item
from (static): Convert from a JavaScript object
toString: Convert to string
Minified and without the test:
function getNativeClass(t){return void 0===t?"undefined":null===t?"null":Object.prototype.toString.call(t).match(/^\[object\s(.*)\]$/)[1]}function globals(){return"object"==typeof global?global:this}function lookup(t){return globals()[t]}function getAnyClass(t){return void 0===t?"undefined":null===t?"null":t.constructor.name}Function.prototype.toJSON=Function.prototype.toString;var getCircularReplacer=()=>{const t=new WeakSet;return(e,r)=>{if("object"==typeof r&&null!==r){if(t.has(r))return"[Circular]";t.add(r)}return r}};function encode(t){if("object"==typeof t&&null!==t){var e=myClone(t);t=Object.getPrototypeOf(t);for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t[Object.getOwnPropertyNames(e)[r]]=e[Object.getOwnPropertyNames(e)[r]]}return getAnyClass(t)+" "+JSON.stringify(t,getCircularReplacer())}function decode(t){var e=t.split(" ").slice(1).join(" ");return lookup(t.split(" ")[0]),new(lookup(t.split(" ")[0]))(JSON.parse(e))}function fromEntries(t){return[...t].reduce((t,[e,r])=>(t[e]=r,t),{})}var toEnumerable=t=>fromEntries(Object.getOwnPropertyNames(t).map(e=>[e,t[e]]));function myClone(t){if("object"!=typeof t||!t)return t;const e=Object.assign({},toEnumerable(t)),r=t.constructor;return Object.setPrototypeOf(e,r.prototype),e}function HashMap(t){void 0===t&&(t=[]),t=(t=Array.from(t)).map(t=>[encode(t[0]),t[1]]),this.a=t}HashMap.from=function(t){var e=myClone(t);t=[];for(var r=0;r<Object.getOwnPropertyNames(e).length;r++)t.push([Object.getOwnPropertyNames(e)[r],e[Object.getOwnPropertyNames(e)[r]]]);return new HashMap(t)},HashMap.prototype.put=function(t,e){this.a.push([encode(t),e])},HashMap.prototype.get=function(t){var e=this.a.map(t=>t[0]);return this.a[e.indexOf(encode(t))][1]},HashMap.prototype.length=function(){return this.a.length},HashMap.prototype.toString=function(){for(var t=[],e=0;e<this.length();e++)t.push(JSON.stringify(decode(this.a[e][0]),getCircularReplacer())+" => "+this.a[e][1]);return"HashMap {"+t+"}"};
Also, you can customize the encoder and decoder by changing encode and decode functions.
As in florian's answer, you can't play with the reference in js however (so two empty objects will look like the same to the hashtable).
class Dict{
constructor(){
this.keys = [];
this.values = [];
this.set = this.set.bind(this);
}
set(key, value){
this.keys.push(key);
this.values.push(value);
}
get(key){
return this.values[this.keys.indexOf(key)];
}
all(){
return this.keys.map((kk, ii)=>[kk, this.values[ii]]);
}
}
let d1 = new Dict();
let k1 = {1: 'a'};
d1.set(k1, 2);
console.log(d1.get(k1)); // 2
let k2 = {2: 'b'};
d1.set(k2, 3);
console.log(d1.all());
// [ [ { '1': 'a' }, 2 ], [ { '2': 'b' }, 3 ] ]
Just use the strict equality operator when looking up the object: ===
var objects = [];
objects.push(object1);
objects.push(object2);
objects[0] === object1; // true
objects[1] === object1; // false
The implementation will depend on how you store the objects in the HashTable class.

Categories