Javascript dictionary performance question - javascript

Right now I have the following javascript dictionary
var a = {};
a['SVG343'] = 1942;
a['UAL534'] = 2153;
Those numbers on the right represent times, and the keys are unique ids. I wanted to make the ids the keys since they are unique. My problem is given a time find the corresponding id. How was I going to do this was go through each entry in the dictionary until I find the correct time and use the current key to get the id.
However I'm worried about performance, my question is, does going through each entry in the dictionary (O(n)) significantly slower than any other method?

You could build an index from the times:
var indexByTimes = {};
for (var prop in a) {
if (a.hasOwnProperty(prop)) {
indexByTimes[a[prop]] = prop;
}
}
And for multiple time values, use an array for the IDs:
for (var prop in a) {
if (a.hasOwnProperty(prop)) {
if (indexByTimes.hasOwnProperty(a[prop])) {
indexByTimes[a[prop]].push(prop);
} else {
indexByTimes[a[prop]] = [prop];
}
}
}
Then you can access all IDs corresponding to the time 1942 with indexByTimes['1942'] in O(1).

Iterating through all the keys of a dictionary is O(n). So also iterating through a.items() is also going to be O(n). But iterating through all keys and then looking up the value (i.e. for(var key in a) { x += a[key]; }) is O(n*log(n)). So it would be preferable to either iterate though a.items() or just use a list

Related

How to iterate a Javascript Map or Object without allocating?

I'm developing a game in Javascript and that involves running the render loop as quickly as possible. I've noticed GC (Garbage Collector) spikes interrupt the smooth frame-rate and after profiling I've discovered that my entity querying system is generating a ton of garbage.
In doing so, I've found that iterators cause allocations in Javascript. In one section of my code I'm doing something like this:
let minimumSet = this.getSmallestEntitySet(componentTypes);
for (let entity of minimumSet) {
// handle entity
}
Unfortunately just doing a for of loop causes allocations (because it creates a new iterator object every time the loop is run), and since this is run so often, a ton of garbage is generated. I was looking around and I couldn't find out whether or not there was a way to iterate a Set, Map, or Object without performing allocations. For example, with a Javascript array you can iterate it while avoiding allocations like this:
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
// handle item
}
But I couldn't find a way to do iterate Map or Set like this with just a regular for loop. Is it possible?
Assuming this is not possible, I assume the work-around for this is to instead use a sorted array instead of a map, right? The only issue with that is that insertion and deletion is O(log n) instead of O(1), but it might be worth it I suppose just to avoid allocations.
Is that my best option is there a way to iterate these collections without performing allocations?
(V8 developer here.)
As the question you've linked to points out, JavaScript engines (at least V8) do optimize away the temporary objects that the spec text for the iteration protocol talks about; though the full answer (as so often) is "it depends".
One effect is that optimizing things away is something that optimizing compilers do, and those don't kick in right away (because more often than not, that would be a waste of effort). So if you only run a function a few times, you'll see its unoptimized version allocate short-lived garbage. But that'll change soon (specifics depend on the engine), as soon as the function is deemed "hot" and gets optimized.
Another issue you might be running into is that iterating over a Map directly (as in: let map = new Map(); ...; for (let e of map) {...} is specified to return e as an array [key, value] every time. This array is not optimized away; it has to be allocated on every iteration. But if you're only interested in processing the values anyway, you can avoid it being created by iterating over just the values: for (let v of map.values()) {...} does not allocate any short-lived objects. The same is true for iterating over map.keys().
If you need both key and value, you could combine an iteration over the keys with a map lookup: for (let key of map.keys()) { let value = map.get(key); ...}, however this is quite a bit slower than iterating over the values. If your objects implement interface Entry as in your answer, i.e. they have a property carrying their key, then you can use that instead: for (let value of map.values()) { let key = value.id; ...}.
All that said: if the SparseSet solution works for you, then of course that's cool too. You can even make it a little more efficient: if you change add to add(item: T) { let key = item.id; ...} and update delete to include this.sparse.delete(key), then the set itself can guarantee that its internal data is always consistent, and then contains can be as simple as return this.sparse.get(key) !== undefined;.
I assume the work-around for this is to instead use a sorted array instead of a map, right? The only issue with that is that insertion and deletion is O(log n)
Insertion and deletion on sorted arrays are O(n), because you may have to shift the entire contents around. (Looking up an element by its sorted key is what takes O(log n) if you use binary search for that.) However, using unsorted arrays can be faster than one might expect intuitively, as long as they remain small-ish (a couple dozen entries or so). Something like:
class UnsortedArray<T extends Entry> {
contents: Array<T> = [];
add(item: T) { contents.push(T); } // optional: check for duplicates first
private find(key: number) {
for (let i = 0; i < contents.length; i++) {
if (contents[i].id == key) return i;
}
return -1;
}
size() { return contents.length; }
get_by_index(index: number) { return contents[index]; }
get_by_key(key: number) { return contents[find(key)]; }
has(item: T) { return find(T.id) >= 0; }
delete(item: T) {
let index = find(T.id);
if (index < 0) return;
let last = contents.pop();
if (index !== contents.length) contents[index] = last;
}
}
That gives you insertion in O(1), classic iteration without overhead (for (let i = 0; i < unsorted_array.size(); i++) { let entry = unsorted_array.get_by_index(i); ...}), deletion and has in O(n). I expect has will only actually be slower than doing binary search on an ordered array once you exceed 30-50 elements; and of course it depends on your use case whether has performance matters at all.
Temporary work-around I thought of until I find something better. Basically, just using a Sparse Set.
interface Entry {
id: number;
}
export class SparseSet<T extends Entry> {
dense: Array<T>;
sparse: Map<number, number>;
constructor() {
this.dense = [];
this.sparse = new Map<number, number>();
}
get size() {
return this.dense.length;
}
contains(key: number) {
let denseIndex = this.sparse.get(key);
if (denseIndex === undefined || denseIndex >= this.dense.length) {
return false;
}
let denseItem = this.dense[denseIndex];
return denseItem.id === key;
}
delete(key: number) {
if (!this.contains(key)) { return; }
let denseIndex = this.sparse.get(key);
let lastItem = this.dense.pop();
if (this.dense.length > 0) {
this.dense[denseIndex] = lastItem;
this.sparse.set(lastItem.id, denseIndex);
}
}
add(key: number, item: T) {
if (this.contains(key)) { return; }
this.sparse.set(key, this.dense.length);
this.dense.push(item);
}
}
Then you can add and delete like this:
let entity = new Entity();
let items = new SparseSet<Entity>();
items.add(entity.id, entity);
items.delete(entity.id);
And iterate without performing allocations like this:
for (let i = 0; i < items.size; i++) {
let item = items.dense[i];
}

What is the best way to check for the object with the most keys in a list?

I have a list that has objects with a varying amount of keys. I want to make sure that I get the index from the list of the object with the most keys OR the reference to the object itself. What is the best way to do this?
My current approach is:
let index = -1;
let numKeys = 0;
for(var i = 0; i < mylistofobjects.length; i++) {
if(Object.keys(mylistofobjects[i]).length > numKeys) {
index = i;
}
}
// by the end, index has the most keys
Is there a smarter/shorter way to do this that would require less code in this day and age? If the way to get the object reference is shorter than the way to get the index number.. I would prefer the object reference.
One option is to reduce, keeping in the accumulator the object with the most keys found so far:
const objWithMostKeys = mylistofobjects.reduce((bestSoFar, thisObj) => (
Object.keys(bestSoFar).length >= Object.keys(thisObj).length ? bestSoFar : thisObj
));
It's not entirely efficient because it checks the accumulator's number of keys on every iteration, rather than caching it, but caching it will require a bit more code:
let maxKeyCount = Object.keys(mylistofobjects[0]).length;
const objWithMostKeys = mylistofobjects.reduce((bestSoFar, currObj) => {
const currKeyCount = Object.keys(currObj).length;
if (currKeyCount > maxKeyCount) {
maxKeyCount = currKeyCount;
return currObj;
}
return bestSoFar;
});
This assumes that the mylistofobjects isn't empty. If that's a possibility, probably add a .length check beforehand, and return early / throw an error (or whatever you need to do) instead of proceeding.

Loop through json object and sort in order

Currently I have a small piece of code which loops through a json object:
for (var key in json_object) {
if (json_object.hasOwnProperty(key)) {
var value = key; //e.g. 50_100, 1000_2000, 20_50 etc
}
}
I'm going to be outputting these values into a list later on. But the problem is that these values aren't in any order right now.
I'd like to be able to have these values sorted out in order. So my question is, is this possible and if so how?
Thanks!
In javascript, object properties are not guaranteed a specific order, so if you want to maintain order, you would likely need to write the object properties into an array of objects.
That could look like this:
var object_array = [];
// map properties into array of objects
for (var key in json_object) {
if (json_object.hasOwnProperty(key)) {
object_array.push({
'key': key;
'value': value;
});
}
}
// sort array of objects
object_array.sort(function(a,b) {
// note that you might need to change the sort comparison function to meet your needs
return (a.value > b.value);
}
After reading all solutions, I realized my solution was wrong. Object.keys is good for getting an array of keys only, so Mike Brant's solution is correct, although a little fix is needed since value is not a variable there. Finally here's a fixed solution based on his solution + the requested sorting from the comments:
var arr = [];
for (var key in json_object) {
if (json_object.hasOwnProperty(key)) {
arr.push({
'key': key;
'value': json_object[key];
});
}
}
// this will sort by the first part of the string
arr.sort(function(a, b) {
return a.key.split('_')[0] - b.key.split('_')[0];
}
Hope this helps... :-)

Merge array into a single array

If this were .NET, I'd ask how to convert List<List<MyClass> to List<MyClass>. However, I'm not very good with javascript and don't know how to ask that as a question using Javascript terminology!
My javascript object comes through like
And is created as:
js_datasets.push({
"DataItem0": {
lzabel: "This",
data: [[1408710276000, null],[1408710276000, 15]]
},
"DataItem1": {
lzabel: "That",
data: [[1408710276000, null],[1408710276000, 15]]
},
});
js_datasets.push({
"DataItem22": {
lzabel: "And other",
data: [[1408710276000, null],[1408710276000, 5]]
},
"DataItem23": {
lzabel: "And lastly",
data: [[1408710276000, null],[1408710276000, 1]]
},
});
Each object is the same "type" (if it matters).
I'd like to create a single list but I am failing to do so. My efforts are
var myDataSet = []; //this is the results of what I want, ideally as a single list
for (var i = 0; i < js_datasets.length; i++) {
if (i==0) {
myDataSet.push(js_datasets[i]);
}
else {
myDataSet.concat(js_datasets[i]);//does nothing
myDataSet.join(js_datasets[i]);//does nothing
}
...more logic
As you can see with the above, I've tried using push, concat and join.
If I update the code to only use push (and never use concat and join) then I get all the values I want, but again, as an array within an array.
Using concat and join do not add to the list.
So, if we can assume the 12 items in the array (pictured) all contain 10 items, I'd like to have a single list of the 120 items!
How can I simply convert this multidimension array (is it multidimension) to a single dimension array.
This will be a bit complicated, as the items in your Array js_datasets are not Arrays, but a more generic Object. This means you can't assume the keys will be in order if you try to read them
Lets write some helper functions to account for this;
function dataItemCollectionToArray(o) {
var keys = Object.keys(o);
// assuming no non-DataItem keys, so next line commented out
// keys = keys.filter(function (e) {return e.indexOf("DataItem") === 0;});
keys.sort(function (a, b) { // ensure you will get the desired order
return +a.slice(8) - +b.slice(8);
});
return keys.map(function (e) {return o[e];});
}
Now you can loop over js_datasets performing this conversion
var myDataSet = [], i;
for (i = 0; i < js_datasets.length; ++i) {
// assuming no gaps, if you need to add gaps, also find min, max indices
// in `dataItemCollectionToArray`, and check them in each iteration here
myDataSet.push.apply(myDataSet, dataItemCollectionToArray(js_datasets[i]));
}
Please note that Object.keys and Array.prototype.map may require polifills if you wish to support old browsers, i.e. IE<=8
An easier solution however, may be to re-write how js_datasets is constructed so that the Objects you are pushing are more Array-like or indeed pushing true Arrays, perhaps with a couple extra properties so you know the offset for the first index. This would mean you can use flatten methods that you'll find around the internet

Javascript sort multi-dimensional array - a complete example?

I'm no Javascript expert and I'm having problems trying to glue together the various nuggets I find here and elsewhere regarding multi-dimensional arrays and sorting and wondered if someone could help me with a complete example?
I have managed to get to the point that I can populate a localStorage with data read in via Ajax.
The format of the rows is ...
(msgXXX) (Key1:Value1|Key2:Value2|Key3:Value3|...etc)
where
(msgXXX) is the localStorage key; and
(Key1:Value1|Key2:Value2|Key3:Value3|...etc) is the single concatenated localStorage data string
What I want to be able to do is convert all this to a multi-dimensional array to which I can apply various sorts. For example, one of the Keys is called "Timestamp" and the value is an integer representing seconds since the Unix epoch. I would like to sort all rows based on this Timestamp value (in descending order - ie latest first). Right now the dataset is just over 600 rows.
I'm comfortable I can do the extraction and slicing and dicing to get the data out of the localStorage, but I'm not even sure what I'm aiming for with regards to populating an array and then setting up the sort.
Can someone point me in the right direction?
You can go with something like this:
function create(line) {
var tokens = line.split("|");
var obj = {};
for (var i = 0; i < tokens.length; i++) {
tokens[i] = tokens[i].split(":");
obj[tokens[i][0]] = tokens[i][1];
}
return obj;
}
var arr = [];
for (....) { // iterate over the input that each line is of key/value format
arr.push(create(line));
}
function timestampSort(a, b) {
if (a == b)
return 0;
return a.timestamp < b.timestamp ? -1 : 1;
}
// to sort by timestamp
arr.sort(timestampSort);
This code creates an object per key/value line, in the format you gave. The object will have the keys as attributes. All of those objects are being pushed into an array, which is then being sorted by passing a compare function to the native sort method of array.
You can of course make as many compare functions as you want, each comparing by a different attribute/criteria.
You can read more about the sort method here: http://www.w3schools.com/jsref/jsref_sort.asp
EDIT
The sort method both changes the array itself and returns the array, so doing something like:
console.log(arr.sort(timestampSort));
Will both change the actual array and return it, and so the console.log will print it.
If you don't want to change the original array and have a copy of it that will get sorted you can:
var arr2 = arr.slice();
arr2.sort(timestampSort);
As for the keys in the array, what I wrote was intended to work only with this part of the line: Key1:Value1|Key2:Value2|Key3:Value3|...etc
So, to add support for the entire format, here's the modification:
function create(line) {
var parts = line.match(/^\(msg(\d+)\) \((.+)\)$/);
var tokens = parts[2].split("|");
var obj = { msgID: parts[1] };
for (var i = 0; i < tokens.length; i++) {
tokens[i] = tokens[i].split(":");
obj[tokens[i][0]] = tokens[i][1];
}
return obj;
}
If you apply this to the example you gave you'll get this:
arr is: [{
msgID: XXX,
Key1: Value1,
Key2: Value2,
Key3: Value3
}]
Hope this clears things for you.

Categories