Is there a way I can dynamically add data to a map in javascript. A map.put(key,value)? I am using the yui libraries for javascript, but didn't see anything there to support this.
Well any Javascript object functions sort-of like a "map"
randomObject['hello'] = 'world';
Typically people build simple objects for the purpose:
var myMap = {};
// ...
myMap[newKey] = newValue;
edit — well the problem with having an explicit "put" function is that you'd then have to go to pains to avoid having the function itself look like part of the map. It's not really a Javascripty thing to do.
13 Feb 2014 — modern JavaScript has facilities for creating object properties that aren't enumerable, and it's pretty easy to do. However, it's still the case that a "put" property, enumerable or not, would claim the property name "put" and make it unavailable. That is, there's still only one namespace per object.
Javascript now has a specific built in object called Map, you can call as follows :
var myMap = new Map()
You can update it with .set :
myMap.set("key0","value")
This has the advantage of methods you can use to handle look ups, like the boolean .has
myMap.has("key1"); // evaluates to false
You can use this before calling .get on your Map object to handle looking up non-existent keys
I like this way to achieve this
const M = new Map(Object.entries({
language: "JavaScript"
}));
console.log(M.size); // 1
console.log(...M); // ["language", "JavaScript"]
// (1) Add and update some map entries
M.set("year", 1991);
M.set("language", "Python");
console.log(M.size); // 2
console.log(...M); // \["language", "Python"\] ["year", 1991]
In Typescript
let ar = [1, 2, 3, 4, 5, 6];
let map = new Map<number, string>();
ar.forEach(value => {
map.set(value, 'value'+ value);
});
console.log(map, 'map data');
Related
How would one JSON.stringify() a Set?
Things that did not work in Chromium 43:
var s = new Set(['foo', 'bar']);
JSON.stringify(s); // -> "{}"
JSON.stringify(s.values()); // -> "{}"
JSON.stringify(s.keys()); // -> "{}"
I would expect to get something similar to that of a serialized array.
JSON.stringify(["foo", "bar"]); // -> "["foo","bar"]"
JSON.stringify doesn't directly work with sets because the data stored in the set is not stored as properties.
But you can convert the set to an array. Then you will be able to stringify it properly.
Any of the following will do the trick:
JSON.stringify([...s]);
JSON.stringify([...s.keys()]);
JSON.stringify([...s.values()]);
JSON.stringify(Array.from(s));
JSON.stringify(Array.from(s.keys()));
JSON.stringify(Array.from(s.values()));
You can pass a "replacer" function to JSON.stringify:
const fooBar = {
foo: new Set([1, 2, 3]),
bar: new Set([4, 5, 6])
};
JSON.stringify(
fooBar,
(_key, value) => (value instanceof Set ? [...value] : value)
);
Result:
"{"foo":[1,2,3],"bar":[4,5,6]}"
toJSON is a legacy artifact, and a better approach is to use a custom replacer, see https://github.com/DavidBruant/Map-Set.prototype.toJSON/issues/16
While all of the above work I suggest that you subclass set and add a toJSON method to make sure that it stringify's correctly. Especially if you are going to be stringifying often. I use sets in my Redux stores and needed to make sure this was never a problem.
This is a basic implementation. Naming is just to illustrate the point pick your own style.
class JSONSet extends Set {
toJSON () {
return [...this]
}
}
const set = new JSONSet([1, 2, 3])
console.log(JSON.stringify(set))
The problem with all the previous approaches is that they all convert the set into Array, which is missing the entire point of Set and indexes.
What you should do is to use an Object instead.
Either convert it with the following function or simply create it as Object instead of Set.
const mySet = new Set(['hello', 'world']);
const myObj = {};
for (let value of mySet.values()) {
myObj[value] = true;
}
Then instead of using mySet.has('hello')
Do myObj.hasOwnProperty('hello').
Then stringify it as an object without a problem.
Note:
The following method uses more memory because it needs to store the value as well as the key.
But performence wise it's still O(1) compared to Array.includes() which is O(n) and miss the point of even using a Set.
I have a custom class called Foo:
class Foo {
constructor(public v: any) {
}
}
And I have a map where Foo is my Key:
const map = new Map<Foo, string>();
AFAIK TypeScript doesn't have comparision overloading. How can I make sure that getting the key works properly?
const foo = new Foo(1234);
map.get(foo);
Here is the entire code:
class Foo {
constructor(public v: any) {
}
}
const map = new Map<Foo, string>();
const foo = new Foo(1234);
map.set(foo, "HELLO WORLD");
const foo2 = new Foo(1234);
console.log(map.get(foo2)); // DOESN'T work
console.log(map.get(foo)); // DOES work of course
You can find my problem here: https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8BQ0PWPAdhALgE4Cuw+8hAFAA7EBGIAlsNAG4Bc0YOAngJQpU6TAF9UYodjz5oAWzDVoAXmg4ApgHdoAWQUAeBPAA00AoUY4A5gD5KfANxDUUgtABmiZas1xElAIwATADMACwOqPLUAHQQaviUHsbQAEQAEgCiADJZAPLQAOq5AEpZACIpEZK4rkmBXupahgEh4Y4u8CBq0SDwlpRR0ZbxiYiBfA7QAPRT0AAm8GoQOADkMhoUANZOHV09fQMKQyNJE-bTs2W5GQDK0BuE26hAA
The problem you have isn't related to TypeScript, but rather JavaScript not having referential transparency. Therefore comparison like this new Foo(123) === new Foo(123) is always falsy.
Solution
The JavaScript's Map has a rather limited api. If you want to find specific entry using custom comparison function (in this case comparing by value), I'd suggest using .entries() method to get all the entries from the Map in form of an array of tuples [key, value][]. You can then find there whatever entry you want using Array.find(). The Map itself doesn't have any such method.
However, even better approach would be to rethink your data structure. Considering the limitations of the Map it would be better to have a primitive data type as a key in the Map. Since I don't know the specifics of your case I can only offer this general suggestion.
I am trying to get a set of classes associated with different window widths. These class-to-width pairs are set by the user. However, I can't find the correct data structure to store it. I believe a tuple would be the optimal type but apparently they don't exist in javascript. If they did my data would looks like this:
var pageBreaks = [(900, "foo"), (600, "bar")];
Where at 900px I could apply the "foo" class. 600px I could apply "bar". I need to be able to access both the "key" and "value" in an .each() loop.
I could use nested arrays such as var pageBreaks = [[900, "foo"], [600, "bar"]]; but I think it is very ugly especially trying to get users of my plugin to adopt the format.
An object is possible:
var pageBreaks = {
900 : "foo",
600 : "bar",
}
But it would be more messy to loop through and it would be easier to process if I keep it ordered greatest to least which isn't guaranteed in an object (right?).
So what would be the best data structure to handle this array of "value-value" pairs with both sides accessible in a loop?
A Map could be a good approach, assuming you have access to the newer JS syntax:
let pageWidths = new Map();
pageWidths.set(900, "foo");
pageWidths.set(600, "bar");
// ordered
const orderedWidths = new Map([...pageWidths.entries()].sort());
// iteration with forEach
orderedWidths.forEach(console.log.bind(console));
// iteration with for...of
for (var [key, value] of orderedWidths) {
console.log(key, value);
}
Even though it will not maintain order on its own (no built-in JS data structure will do that for you*), Maps are fairly easy to work with and easy to accomplish your goals with.
* unless you are using arrays with the pageWidth integer as the index, but even that would require a bit of work to filter out undefined elements between them
You can use a Hashmap data structure to achieve your goal here. I wrote (am writing) a data structure library in JavaScript, and have already finished the Hashmap portion if you're interested in using it.
You're able to store key-value pairs (or in your case, value-value pairs) and iterate through them in the same order that they were inserted.
const Hashmap = require('node-needle').Hashmap;
var map = new Hashmap();
map.put(900, "foo");
map.put(600, "bar");
// ...
// Iterate through map - insertion order is kept
for(var it = map.iterator(); it !== null; it = map.next()){
console.log(it); // 900 -> 600 -> ...
console.log(map.get(it)); // "foo" -> "bar" -> ...
}
I am serializing and storing an object that was created from a WinJS.Class like this:
var myClass = WinJS.Class.define(...);
var myObject = new myClass();
var serialized = JSON.stringify(myObject);
//store the object
And later I'm pulling the object out of storage and I want to deserialize it and cast it as a myClass. Is that possible with WinJS out of the box or do I need to create a constructor for my class that is capable of taking an object that can turn it into a new object?
I haven't broken into TypeScript yet, and I think that would help out in this situation, but until then I'm wondering how to do it with plain JavaScript/WinJS.
There are a few ways to handle this, and none are particularly special to WinJS. Simply put: JSON serialization only serializes and deserializes the obje values, not its methods, prototype, or other type information.
Option 1: Copy values to new instance of your class
This is usually best accomplished by having your constructor take the deserialized object as a parameter and copying the data to the new instance.
There are a variety of variations of this. Using the object constructor is generally the best for performance, as this typically enables the JS engine to apply the greater number of optimizations to the object.
WinJS.UI.setOptions can be helpful here, or you can just copy the data using a simple loop like this:
var keys = Object.keys(source);
for (var i = 0, len = keys.length; i < len; i++) {
var key = keys[i];
destination[key] = source[key];
}
Option 2: Setting __proto__
Warning: This can have significantly adverse performance effects, so it's not appropriate in some situations. But occasionally it can be handy.
Object.setPrototypeOf(myObject, myClass.prototype);
Note that setPrototypeOf is relatively new. It's there on Win8.1 for web apps (which I'm guessing this is about) and in IE 11, but not available in Safari, for example. On older browsers/ Safari, assigning to proto is the equivalent (but if available, setPrototypeOf is better).
This will attach methods from myClass to the object, but in addition to the negative performance effects, also does not run your constructor on the object - so it still may not be in exactly the same state as the object you originally serialized.
Other helpful thing: JSON "revivers"
JSON.parse takes an optional second parameter, called a "reviver". This lets you provide a function that gets the opportunity to transform each node of the JSON being deserialized. This can be useful for rehydrating serialized dates into JavaScript Date objects, for example. It also gets the opportunity to transform the top-most object, which could be useful in some cases to turn the deserialized object into the "class" you want.
Javascript is a dynamic language so I think you dont need to cast the deserialized object, just treat it as myClass type and that's it. Hope it helps you.
You should consider using the 'Options' constructor pattern, where the option value is the deserialized object:
// MovieModel Constructor
// ----------------------
function MovieModel(options) {
this._titleValue = options.title || "Sample Title";
}
Where the movie methods closure is something like this:
// MovieModel Methods
// ------------------
var movieModelMethods = {
title: {
get: function () {
return this._titleValue;
},
set: function (val) {
this._titleValue = val;
this.dispatchEvent("title");
}
}
};
Since WinJS class define can only specify one constructor function (as far as I understand it), you may use the static members to define a factory function that will take the serialized data as a parameter. This factory methdod will actually create a new instance and will set the values one by one and return the new object.
It as some advantages like the fact that you can actually manage the data structure changes over the time you enhance the app...
The drawback is that you cannot write new MySuperClass() all the time...
...
// let's suppose we already called JSON.parse(data);
create: function(serializedData) {
var newObj = new MySuperClass();
newObj.name = serializedData.name || "";
newObj.color = serializedData.color || "";
return newObj;
}
Then you will call somewhere else in the app :
var myInstance = MySuperClass.create(serializedDataFromfile);
You should just be able to call JSON.parse after pulling it out of local storage:
var myObject2;
myObject2 = JSON.parse(localStorage["mySeriazliedObject"];
In Eloquent JavaScript, Chapter 4, a set of values is created by creating an object and storing the values as property names, assigning arbitrary values (e.g. true) as property values. To check if the value is already contained in the set, the in operator is used:
var set = {};
if (!'Tom' in set) {
set.Tom = true;
}
Is this idiomatic JavaScript? Wouldn't be using an array even better?
var set = [];
if (!'Tom' in set) {
set.push = 'Tom';
}
Sets are now available in ES2015 (aka ES6, i.e. ECMAScript 6). ES6 has been the current standard for JavaScript since June 2015.
ECMAScript 6 has the data structure Set which works for arbitrary
values, is fast and handles NaN correctly. -Axel Rauschmayer, Exploring ES6
First two examples from Axel Rauschmayer's book Exploring ES6:
Managing single elements:
> let set = new Set();
> set.add('red')
> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false
Determining the size of a Set and clearing it:
> let set = new Set();
> set.add('red')
> set.add('green')
> set.size
2
> set.clear();
> set.size
0
I would check out Exploring ES6 if you want to learn more about Sets in JavaScript. The book is free to read online, but if you would like to support the author Dr. Axel Rauschmayer you can purchase the book for around $30.
If you want to use Sets and ES6 now you can use Babel, the ES6 to ES5 transpiler, and its polyfills.
Edit: As of June 6th, 2017 most of the major browsers have full Set support in their latest versions (except IE 11). This means you may not need babel if you don't care to support older browsers. If you want to see compatibility in different browsers including your current browser check Kangax's ES6 compatibility table.
EDIT:
Just clarification on initialization. Sets can take any synchronous iterable in their constructor. This means they can take not just arrays but also strings, and iterators. Take for example the following array and string initialization of a set:
const set1 = new Set(['a','a','b','b','c','c']);
console.log(...set1);
console.log(set1.size);
const set2 = new Set("aabbcc");
console.log(...set2);
console.log(set2.size);
Both outputs of the array and string are the same. Note that ...set1 is the spread syntax. It appears that each element of the iterable is added one by one to the set, so since both the array and string have the same elements and since the elements are in the same order the set is created the same. Another thing to note about sets is when iterating over them the iteration order follows the order that the elements were inserted into the set. Here's an example of iterating over a set:
const set1 = new Set(['a','a','b','b','c','c']);
for(const element of set1) {
console.log(element);
}
Since you can use any iterable to initialize a set you could even use a iterator from a generator function. Here is two such examples of iterator initializations that produce the same output:
// a simple generator example
function* getLetters1 () {
yield 'a';
yield 'a';
yield 'b';
yield 'b';
yield 'c';
yield 'c';
}
// a somewhat more commonplace generator example
// with the same output as getLetters1.
function* getLetters2 (letters, repeatTimes) {
for(const letter of letters) {
for(let i = 0; i < repeatTimes; ++i) {
yield letter;
}
}
}
console.log("------ getLetters1 ------");
console.log(...getLetters1());
const set3 = new Set(getLetters1());
console.log(...set3);
console.log(set3.size);
console.log("------ getLetters2 ------");
console.log(...getLetters2('abc', 2));
const set4 = new Set(getLetters2('abc', 2));
console.log(...set4);
console.log(set4.size);
These examples' generator functions could just be written to not repeat, but if the generator function is more complicated and as long as the following doesn't impact performance too negatively you could use the Set method to help get only values from a generator that don't repeat.
If you want to know more about sets without reading Dr. Rauschmayer's chapter of his book you can check out the MDN docs on Set. MDN also has more examples of iterating over a set such as using forEach and using the .keys, .values, and .entries methods. MDN also has examples such as set union, set intersection, set difference, symmetric set difference, and set superset checking. Hopefully most of those operations will become available in JavaScript without needing to build your own functions supporting them. In fact, there is this TC39 proposal for new Set methods which should hopefully add the following methods to Set in JavaScript at some future point in time if the proposal reaches stage 4:
Set.prototype.intersection(iterable) - method creates new Set instance by set intersection operation.
Set.prototype.union(iterable) - method creates new Set instance by set union operation.
Set.prototype.difference(iterable) - method creates new Set without elements present in iterable.
Set.prototype.symmetricDifference(iterable) - returns Set of elements found only in either this or in iterable.
Set.prototype.isSubsetOf(iterable)
Set.prototype.isDisjointFrom(iterable)
Set.prototype.isSupersetOf(iterable)
I use dict objects as sets. This works with strings and numbers, but I suppose would cause problems if you wanted to have a set of objects using custom equality and comparison operators:
Creating a set:
var example_set =
{
'a':true,
'b':true,
'c':true
}
Testing for inclusion in a set
if( example_set['a'] ){
alert('"a" is in set');
}
Adding an element to a set
example_set['d'] = true;
Removing an element from a set
delete example_set['a'];
Sets do not allow duplicate entries and don't typically guarantee predefined ordering. Arrays do both of these, thus violating what it means to be a set (unless you do additional checks).
The first way is idiomatic JavaScript.
Any time you want to store a key/value pair, you must use a JavaScript object. As for arrays, there are several problems:
The index is a numerical value.
No easy way to check to see if a value is in an array without looping through.
A set doesn't allow duplicates. An array does.
If you want to create a set from an array, simply do:
let arr = [1, 1, 2, 1, 3];
let mySet = new Set(arr); // Set { 1, 2, 3 }
This is a sugar syntax that I quite fancied when programming in Python, so glad that ES6 finally made it possible to do the same thing.
NOTE: then I realize what I said didn't directly answer your question. The reason you have this "hack" in ES5 is because lookup time in an object by keys is significantly faster (O(1)) than in an array (O(n)). In performance critical applications, you can sacrifice this bit of readability or intuition for better performance.
But hey, welcome to 2017, where you can use proper Set in all major modern browsers now!
Sets in ES6/ES2015:
ES6/ES2015 now has built in sets. A set is data structure which allows storage of unique values of any type, whether this are primitive values or object references. A set can be declared using the ES6 built in set constructor in the following manner:
const set = new Set([1, 2, 3, 4, 5]);
When creating a set using the Set constructor our newly created set object inherits from the Set.prototype. This has all sorts of auxiliary methods and properties. This allows you to easily do the following things:
Example:
const set = new Set([1, 2, 3, 4, 5]);
// checkout the size of the set
console.log('size is: ' + set.size);
// has method returns a boolean, true if the item is in the set
console.log(set.has(1));
// add a number
set.add(6);
// delete a number
set.delete(1);
// iterate over each element using a callback
set.forEach((el) => {
console.log(el);
});
// remove all the entries from the set
set.clear();
Browser compatibility:
All major browser now fully support sets except IE where some features are missing. For exact reference please refer to the mdn docs.
There are two problems with using bare javascript objects to emulate sets: first, an object can have an inherited property which would screw the "in" operator and second, you can only store scalar values in this way, making a set of objects is not possible. Therefore, a realistic implementation of Sets should provide methods add and contains instead of plain in and property assignments.
You can try Buckets, is a javascript data structure library and has everything you need to manipulate sets.
Basic creation and usage of Set object 🔷
let mySet = new Set()
mySet.add(2) // Set {2}
mySet.add(7) // Set {2, 7}
mySet.add(7) // Set {2, 7}
mySet.add('my text') // Set {2, 7, 'my text'}
let myObj = { a: 1, b: 2 }
mySet.add(myObj) // Set {2, 7, 'my text', {...}}
mySet.has(2) // true
mySet.has(myObj) // true
mySet.size // 4
Iteration
for (let item of mySet) console.log(item) // 2, 7, 'my text', {a:1, b:2}
mySet.forEach(value => console.log(value)) // 2, 7, 'my text', {a:1, b:2}
Convert to array
var myArr = Array.from(mySet) // [2, 7, 'my text', {a:1, b:2}]
❕ The most distinct feature Set offers is every value in Set object must be
unique. So you can not add duplicate values.