I'm working in JavaScript. I'd like to store a list of unique, unordered string values, with the following properties:
a fast way to ask 'is A in the list'?
a fast way to do 'delete A from the list if it exists in the list'
a fast way to do 'add A to the list if it is not already present'.
What I really want is a set. Any suggestions for the best way to mimic a set in JavaScript?
This question recommends using an Object, with the keys storing properties, and the values all set to true: is that a sensible way?
If you are programming in an ES6-capable environment (such as node.js, a specific browser with the ES6 capabilities you need or transpiling ES6 code for your environment), then you can use the Set object built into ES6. It has very nice capabilities and can be used as is right in your environment.
For many simple things in an ES5 environment, using an Object works very well. If obj is your object and A is a variable that has the value you want to operate on in the set, then you can do these:
Initialization code:
// create empty object
var obj = {};
// or create an object with some items already in it
var obj = {"1":true, "2":true, "3":true, "9":true};
Question 1: Is A in the list:
if (A in obj) {
// put code here
}
Question 2: Delete 'A' from the list if it's there:
delete obj[A];
Question 3: Add 'A' to the list if it wasn't already there
obj[A] = true;
For completeness, the test for whether A is in the list is a little safer with this:
if (Object.prototype.hasOwnProperty.call(obj, A))
// put code here
}
because of potential conflict between built-in methods and/or properties on the base Object like the constructor property.
Sidebar on ES6: The current working version of ECMAScript 6 or somethings called ES 2015 has a built-in Set object. It is implemented now in some browsers. Since browser availability changes over time, you can look at the line for Set in this ES6 compatibility table to see the current status for browser availability.
One advantage of the built-in Set object is that it doesn't coerce all keys to a string like the Object does so you can have both 5 and "5" as separate keys. And, you can even use Objects directly in the set without a string conversion. Here's an article that describes some of the capabilities and MDN's documentation on the Set object.
I have now written a polyfill for the ES6 set object so you could start using that now and it will automatically defer to the built-in set object if the browser supports it. This has the advantage that you're writing ES6 compatible code that will work all the way back to IE7. But, there are some downsides. The ES6 set interface takes advantage of the ES6 iterators so you can do things like for (item of mySet) and it will automatically iterate through the set for you. But, this type of language feature cannot be implemented via polyfill. You can still iterate an ES6 set without using the new ES6 languages features, but frankly without the new language features, it isn't as convenient as the other set interface I include below.
You can decide which one works best for you after looking at both. The ES6 set polyfill is here: https://github.com/jfriend00/ES6-Set.
FYI, in my own testing, I've noticed that the Firefox v29 Set implementation is not fully up-to-date on the current draft of the spec. For example, you can't chain .add() method calls like the spec describes and my polyfill supports. This is probably a matter of a specification in motion as it is not yet finalized.
Pre-Built Set objects: If you want an already built object that has methods for operating on a set that you can use in any browser, you can use a series of different pre-built objects that implement different types of sets. There is a miniSet which is small code that implements the basics of a set object. It also has a more feature rich set object and several derivations including a Dictionary (let's you store/retrieve a value for each key) and an ObjectSet (let's you keep a set of objects - either JS objects or DOM objects where you either supply the function that generates a unique key for each one or the ObjectSet will generate the key for you).
Here's a copy of the code for the miniSet (most up-to-date code is here on github).
"use strict";
//-------------------------------------------
// Simple implementation of a Set in javascript
//
// Supports any element type that can uniquely be identified
// with its string conversion (e.g. toString() operator).
// This includes strings, numbers, dates, etc...
// It does not include objects or arrays though
// one could implement a toString() operator
// on an object that would uniquely identify
// the object.
//
// Uses a javascript object to hold the Set
//
// This is a subset of the Set object designed to be smaller and faster, but
// not as extensible. This implementation should not be mixed with the Set object
// as in don't pass a miniSet to a Set constructor or vice versa. Both can exist and be
// used separately in the same project, though if you want the features of the other
// sets, then you should probably just include them and not include miniSet as it's
// really designed for someone who just wants the smallest amount of code to get
// a Set interface.
//
// s.add(key) // adds a key to the Set (if it doesn't already exist)
// s.add(key1, key2, key3) // adds multiple keys
// s.add([key1, key2, key3]) // adds multiple keys
// s.add(otherSet) // adds another Set to this Set
// s.add(arrayLikeObject) // adds anything that a subclass returns true on _isPseudoArray()
// s.remove(key) // removes a key from the Set
// s.remove(["a", "b"]); // removes all keys in the passed in array
// s.remove("a", "b", ["first", "second"]); // removes all keys specified
// s.has(key) // returns true/false if key exists in the Set
// s.isEmpty() // returns true/false for whether Set is empty
// s.keys() // returns an array of keys in the Set
// s.clear() // clears all data from the Set
// s.each(fn) // iterate over all items in the Set (return this for method chaining)
//
// All methods return the object for use in chaining except when the point
// of the method is to return a specific value (such as .keys() or .isEmpty())
//-------------------------------------------
// polyfill for Array.isArray
if(!Array.isArray) {
Array.isArray = function (vArg) {
return Object.prototype.toString.call(vArg) === "[object Array]";
};
}
function MiniSet(initialData) {
// Usage:
// new MiniSet()
// new MiniSet(1,2,3,4,5)
// new MiniSet(["1", "2", "3", "4", "5"])
// new MiniSet(otherSet)
// new MiniSet(otherSet1, otherSet2, ...)
this.data = {};
this.add.apply(this, arguments);
}
MiniSet.prototype = {
// usage:
// add(key)
// add([key1, key2, key3])
// add(otherSet)
// add(key1, [key2, key3, key4], otherSet)
// add supports the EXACT same arguments as the constructor
add: function() {
var key;
for (var i = 0; i < arguments.length; i++) {
key = arguments[i];
if (Array.isArray(key)) {
for (var j = 0; j < key.length; j++) {
this.data[key[j]] = key[j];
}
} else if (key instanceof MiniSet) {
var self = this;
key.each(function(val, key) {
self.data[key] = val;
});
} else {
// just a key, so add it
this.data[key] = key;
}
}
return this;
},
// private: to remove a single item
// does not have all the argument flexibility that remove does
_removeItem: function(key) {
delete this.data[key];
},
// usage:
// remove(key)
// remove(key1, key2, key3)
// remove([key1, key2, key3])
remove: function(key) {
// can be one or more args
// each arg can be a string key or an array of string keys
var item;
for (var j = 0; j < arguments.length; j++) {
item = arguments[j];
if (Array.isArray(item)) {
// must be an array of keys
for (var i = 0; i < item.length; i++) {
this._removeItem(item[i]);
}
} else {
this._removeItem(item);
}
}
return this;
},
// returns true/false on whether the key exists
has: function(key) {
return Object.prototype.hasOwnProperty.call(this.data, key);
},
// tells you if the Set is empty or not
isEmpty: function() {
for (var key in this.data) {
if (this.has(key)) {
return false;
}
}
return true;
},
// returns an array of all keys in the Set
// returns the original key (not the string converted form)
keys: function() {
var results = [];
this.each(function(data) {
results.push(data);
});
return results;
},
// clears the Set
clear: function() {
this.data = {};
return this;
},
// iterate over all elements in the Set until callback returns false
// myCallback(key) is the callback form
// If the callback returns false, then the iteration is stopped
// returns the Set to allow method chaining
each: function(fn) {
this.eachReturn(fn);
return this;
},
// iterate all elements until callback returns false
// myCallback(key) is the callback form
// returns false if iteration was stopped
// returns true if iteration completed
eachReturn: function(fn) {
for (var key in this.data) {
if (this.has(key)) {
if (fn.call(this, this.data[key], key) === false) {
return false;
}
}
}
return true;
}
};
MiniSet.prototype.constructor = MiniSet;
You can create an Object with no properties like
var set = Object.create(null)
which can act as a set and eliminates the need to use hasOwnProperty.
var set = Object.create(null); // create an object with no properties
if (A in set) { // 1. is A in the list
// some code
}
delete set[a]; // 2. delete A from the list if it exists in the list
set[A] = true; // 3. add A to the list if it is not already present
As of ECMAScript 6, the Set data-structure is a built-in feature. Compatibility with node.js versions can be found here.
In ES6 version of Javascript you have built in type for set (check compatibility with your browser).
var numbers = new Set([1, 2, 4]); // Set {1, 2, 4}
To add an element to the set you simply use .add(), which runs in O(1) and either adds the element to set (if it does not exist) or does nothing if it is already there. You can add element of any type there (arrays, strings, numbers)
numbers.add(4); // Set {1, 2, 4}
numbers.add(6); // Set {1, 2, 4, 6}
To check the number of elements in the set, you can simply use .size. Also runs in O(1)
numbers.size; // 4
To remove the element from the set use .delete(). It returns true if the value was there (and was removed), and false if the value did not exist. Also runs in O(1).
numbers.delete(2); // true
numbers.delete(2); // false
To check whether the element exist in a set use .has(), which returns true if the element is in the set and false otherwise. Also runs in O(1).
numbers.has(3); // false
numbers.has(1); // true
In addition to methods you wanted, there are few additional one:
numbers.clear(); would just remove all elements from the set
numbers.forEach(callback); iterating through the values of the set in insertion order
numbers.entries(); create an iterator of all the values
numbers.keys(); returns the keys of the set which is the same as numbers.values()
There is also a Weakset which allows to add only object-type values.
I have started an implementation of Sets that currently works pretty well with numbers and strings. My main focus was the difference operation, so I tried to make it as efficient as I could. Forks and code reviews are welcome!
https://github.com/mcrisc/SetJS
I just noticed that d3.js library has implementation of sets, maps and other data structures.
I can't argue about their efficiency but judging by the fact that it is a popular library it must be what you need.
The documentation is here
For convenience I copy from the link (the first 3 functions are those of interest)
d3.set([array])
Constructs a new set. If array is specified, adds the given array of string values to the returned set.
set.has(value)
Returns true if and only if this set has an entry for the specified value string.
set.add(value)
Adds the specified value string to this set.
set.remove(value)
If the set contains the specified value string, removes it and returns true. Otherwise, this method does nothing and returns false.
set.values()
Returns an array of the string values in this set. The order of the returned values is arbitrary. Can be used as a convenient way of computing the unique values for a set of strings. For example:
d3.set(["foo", "bar", "foo", "baz"]).values(); // "foo", "bar", "baz"
set.forEach(function)
Calls the specified function for each value in this set, passing the value as an argument. The this context of the function is this set. Returns undefined. The iteration order is arbitrary.
set.empty()
Returns true if and only if this set has zero values.
set.size()
Returns the number of values in this set.
Yes, that's a sensible way--that's all an object is (well, for this use-case)--a bunch of keys/values with direct access.
You'd need to check to see if it's already there before adding it, or if you just need to indicate presence, "adding" it again doesn't actually change anything, it just sets it on the object again.
Related
I want to have a list of strings that is unique and so everytime I get a new string that I should push onto the list I need to check if the list contains the item before pushing it on the list. This seems unperformant.
However, if I use a hash structure and store the items as keys, is there some way to make this more performant than a simple array?
I guess I am simply wondering what the most performant set data structure exists in JavaScript.
Yes, using a Set will be much faster than checking for an existing value (O(1) for set vs. O(n) for an array).
var s = Set();
s.add(1); // s is (1)
s.add(2);
s.add(3);
s.add(1)
s.add(1)
// s is now (1, 2, 3)
In modern browsers (Chrome 38+, IE11+) the Set type is defined, it is documented here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
Otherwise, in JavaScript, Object values (a fundamental type in ECMAScript) are internally implemented as hashtables - so the fastest conceptual "HashSet" structure would exist as a generalisation of of a hashtable with a disregarded value-type.
Here's how I'd do it (if Set was unavailable):
function StringSet() {
this.values = {};
this.add = function(value) {
value = value.toUpperCase(); // use UpperCase for string normalization because of how casing rules work in different languages, especially Turkish
this.values[ value ] = true; // use bool values as stubs
};
this.contains = function(value) {
value = value.toUpperCase();
return value in this.values; // JavaScript has a fast `in` operator which runs in `O(1)` time
}
}
var foo = new StringSet();
foo.add("bar");
assert( foo.contains("bar") );
I'm trying to implement a dictionary much like Python. So, I would like to have a keys() method that returns keys added to the subclass Dict, but not properties such as the Object's method "keys"
EDIT AGAIN
Basically, I'm making a class to pass settings to a function like function(arg1, arg2, myObj) where my object is {map: texMap, alphaMap: aTexMap}. It's for Three.js, and I have to wait on images to download before I can create settings on 3D objects. So, interface like one would expect with d in var d = { a: aData b: bData }, but hide the methods etc that are not added by the user.
ie don't return this.prototype.propertyName when own is passedHere's what I have so far:
function Dict(){
this.prototype = {};
var _keys = this.prototype.keys;
this.keys = function(own){
if(typeof own === 'undefined') { return _keys(); }
var ownKeys = [];
for(var key in _keys()){
if(this.hasOwnProperty(key)) {
ownKeys.push(key);
}
}
return ownKeys;
}
}
Will this work as follows? Is there a better or already existent way to do it?
save the overloaded keys() method to a private var
return everything as usual, unless own is something that resolves to true.
if own == true, get the usual keys and filter out those
belonging to the superclass.
On the subject, I'm likely most concerned about saving back the prototype method as a way to get all of the keys and filter out proto keys.
Also, I've read overloading isn't built into Javascript. But, much of what I've found deals with standalone functions such as this Q&A on best practices. I don't need a built in way, but I'll take advantage of whatever's available (Hence, using Object as a Dict).
Any feedback is appreciated!
EDIT
In Python, we get this:
In[2]: d = {}
In[3]: 'has_key' in d.keys()
Out[3]: False
In[7]: 'has_key' in d.__class__.__dict__.keys()
Out[7]: True
In[8]: d.has_key('has_key')
Out[8]: False
In[9]: d['newKey'] = 5
In[10]: d.newKey # ERROR
Python has a dict attribute contained in its class where the functions are accessed via a dot (see In[8]...). So, those standard {} or dict() functions and operators are hidden (not private) while keys/data are added to the user's dict are accessed via []. d['newKey'] = 5 adds a new key or overwrites the old and sets the data to 5.
I don't need all of that to work, though it would be great. keys() returning Python-like keys would be fine for now.
There seem to be multiple issues here.
You seem to want to pass variable arguments to a function:
I'm making a class to pass settings to a function like function(arg1, arg2, myObj) where my object is {map: texMap, alphaMap: aTexMap}.
JS function arguments are very flexible.
You can either set up names for every one of them:
function foo(arg1, arg2, map, alphaMap)
and pass values directly. This style is preferred for functions that work on a fixed set of arguments.
Or you can set up an "options" object that collects keys and values:
function foo(options)
and pass {arg1: val1, arg2: val2, map: valMap, alphaMap: valAlphaMap}. This style often occurs on constructor functions that initialize objects with a certain set configuration options.
Or you can set up an empty function signature
function foo()
and work with the arguments collection inside the function. This is found in functions that work with a variable number of uniform arguments (imagine add(1, 2, 3, 4, 6)) or strictly positional arguments instead of named ones.
In any case, passing arguments to a function is optional in JavaScript, even when there is an argument list in the function signature. You are free to pass none, less or more arguments. Of course all these approaches can be combined if it suits you.
It's for Three.js, and I have to wait on images to download before I can create settings on 3D objects.
This is a problem caused by the asynchronous nature of the web. The solution is to use event handlers. These are either callbacks or - as an abstraction over callbacks - promises.
So, interface like one would expect with d in var d = { a: aData b: bData }, but hide the methods etc that are not added by the user.
This can be solved by not adding methods etc to data objects, or at least not directly. Add them to the prototype if your data objects must have behavior.
The direct equivalent to a Python Dict is a plain object in JavaScript.
var dict = {};
The direct equivalent of Python's keys() method is the Object.keys() static method in JavaScript.
var keys = Object.keys(dict);
To iterate the keys you can either use an imperative approach:
var i, key;
for (i = 0; i < keys.length; i++) {
key = keys[i];
doSomething(key, dict[key]);
}
or a functional one
keys.forEach(function (key) {
doSomething(key, dict[key]);
});
The direct equivalent of Python's in is .hasOwnProperty() in JavaScript:
if ( dict.hasOwnProperty('foo') ) ...
or, if it is a pure data object with no prototype chain, you can use in as well.
if ('foo' in dict)
Using in in for loops is not recommendable because it iterates the prototype properties as well. The way to guard against this is by using Object.keys() instead or by combining it with .hasOwnProperty(), as you did.
var key;
for (key in dict) {
if ( dict.hasOwnProperty(key) ) ...
}
Your question indicates that you are missing basic puzzle pieces about JS and try to substitute them with more familiar Python constructs. I would recommend not doing that.
I also suspect that you try to shoehorn Python's class-based inhertiance pattern into JS' prototype-based inheritance pattern. I strongly recommend that you don't do that, either.
A JavaScript WeakMap does not allow you to get the key, or the length or size, by design.
Is it possible to nevertheless loop over entries in some way ?
If not .. how does the Chrome console do this ?
Is it possible to nevertheless loop over entries in some way?
No, as you say, the contents of a WeakMap are not accessible by design, and there is no iterability.
If not … how does the Chrome console do this?
The console uses the debugging API of the JS engine, which allows access to the internals of objects (also to promise states, wrapped primitives, etc.) and many more.
Things are moving and it will soon be possible to create iterable week maps thanks to weak refs.
See the iterable WeakMap example in the tc39 weakrefs proposal.
(note that it is already possible with nodejs v12.?.? using --harmony-weak-refs flag)
Running far afield with a qualifier in your (2015) question, specifically:
Is it possible to nevertheless loop over entries in some way ?
Yes.
In one ridiculous situation, it is possible to emulate and then iterate the keys and values of a WeakMap, and also to make a proper, independent copy of your WeakMap.
If the WeakMap you wish to clone was built in a very specific fashion by a constructor function, you can do it:
// Define a Constructor-Function
// that makes objects
// containing WeakMaps:
function makeWeakMapObject(){
this.wm1 = new WeakMap();
this.o1 = {};
this.o2 = {"orange":"orange"};
this.wm1.set(this.o1, 37);
this.wm1.set(this.o2, 'azerty');
}
// Construct a new object:
let constructedWeakMapObject = new makeWeakMapObject();
// Then set a new key-value pair
// on the WeakMap in your object;
// because, ya know, otherwise you'd
// just reuse the WeakMap constructor
// and wouldn't need to clone :D
constructedWeakMapObject.added = {"ya":"glad"};
constructedWeakMapObject.wm1.set(constructedWeakMapObject.added, 42);
// In preparation to clone your newly constructed object,
// get that newly constructed object's property descriptors:
let props = Object.getOwnPropertyDescriptors(constructedWeakMapObject);
// Have a gander at those props; just for fun:
console.log({"props":props});
// Attempt to clone the constructedWeakMapObject
// using its ownPropertyDescriptors
let weakClone = new cloneWeak(props);
// and then check out what you made:
console.log({"weakClone":weakClone});
// Verify that you've made an independent clone
// (even though this example is shallow)
// by altering the WeakMap in your weakClone:
weakClone.wm.delete(weakClone.o1);
// Make sure your clone was altered:
console.log(weakClone.wm.get(weakClone.o1));
// And then check to see that the
// changes to your clone
// don't appear on your constructed object:
console.log(constructedWeakMapObject);
console.log(constructedWeakMapObject.wm1.get(constructedWeakMapObject.o1));
// A support function to help you use fresh keys in your cloned WeakMap to keep it independent from your original WeakMap
function cloneObject(obj) { // use something more robust, like underscore: _.cloneDeep(obj); actually, you'll likely have to roll your own so you can make clones of functions... anywho
var clone = {};
for(var i in obj) {
if(typeof(obj[i])==="object" && obj[i] !== null)
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
// Called as a constructor function w/arguments
function cloneWeak(inco){ // a bit wonky, at least in the middle
this.wm = new WeakMap();
let tempMap;
for(key in inco){
// Build keys on 'this' that match the incoming keys
if(Object.prototype.toString.call(inco[key].value) !== "[object WeakMap]"){
this[key] = cloneObject(inco[key].value);
}
// Reference the incoming map from your temp map
// (this makes the following loop possible)
else{tempMap = inco[key].value;}
}
this.fakeForHack = {}; // no idea why this works
this.wm.set(this.fakeForHack, "ok"); // no idea why this works... without it, the WeakMap entry for made.wm1.get(made.added) won't transfer -> ???
for(key in inco){
if(Object.prototype.toString.call(inco[key].value) !== "[object WeakMap]"){
// Set values for 'this' WeakMap:
this.wm.set(this[key], tempMap.get(inco[key].value));
}
}
}
It's kinda ugly, it's brittle, and it only solves a ridiculous edge-case; you're welcome!
My Task
In my JavaScript code i'm often using objects to "map" keys to values so i can later access them directly through a certain value. For example:
var helloMap = {};
helloMap.de = "Hallo";
helloMap["en"] = "Hello";
helloMap.es = "Hola";
So i build up the map object step by step in my source code using the two available notations object style and array style.
Later i can then access the values i added through helloMap["de"] for example. So thats all fine if i don't have to care about the order in which the attributes has been set on the object.
If i want to iterate the objects properties now as far as i know there is no way to ensure that i'll iterate them in the order they have been added (insertion order).
Note: I can't use some wrapper object and simply hold a array in there and then use its methods to add the values so something like this:
var HelloMap = function(){
this.myMap = [];
this.addProperty = function(key, value){
this.myMap.push({key: key, value: value});
}
}
or something similar won't work for me. So the solution needs to be absolutely transparent to the programmer using the object.
That said the object i needed would be an empty object which maintains the order of the properties that were added to it. Something like this would do:
var helloMap = {};
helloMap = getOrderAwareObject(helloMap);
so that every further assignment of the form helloMap.xy = "foo" and helloMap["yz"] = "bar" would be tracked in the object "in order",
Possible Solutions
Since i did not find any solution in underscore or jQuery giving me such a special object i came across the possibility of defining getters and setters for properties in JavaScript objects with Object.defineProperty since i can rely on ECMAScript 5 standard i can use it.
The Problem with this one is, that you have to know all the possible properties that can be set on the object, before they are actually set. Since if you define it you got to name it.
What i am searching for is something like a Default Getter and Default Setter which applies on the object if no getter and setter has been defined for the property. So i could then hide the sorted map behind the object inteface.
Is there already a solution for this in any framework you know?
Is there a mechanism like "default getter/setter" ?
You'll need a wrapper of some kind using an array internally, I'm afraid. ECMAScript 5 (which is the standard on which current browser JavaScript implementations are based) simply doesn't allow for ordered object properties.
However, ECMAScript 6 will have a Map implementation that has ordered properties. See also http://www.nczonline.net/blog/2012/10/09/ecmascript-6-collections-part-2-maps/.
There may also be other options in ECMAScript 6. See the following question:
How can I define a default getter and setter using ECMAScript 5?
Adding a link to a custom javascript library which provides Sorted maps and other implementation, for future reference in this thread . Check out https://github.com/monmohan/dsjslib
-msingh
I don't know of a general solution but non-general solutions are very simple to construct.
Typically, you maintain an Array of objects, with several methods defined as properties of the Array. At least, that's my approach.
Here's an example, taken (in a modified form) from a larger application :
var srcs = [];
srcs.find = function(dist) {
var i;
for(i=0; i<this.length; i++) {
if(dist <= this[i].dist) { return this[i]; }
}
return null;
};
srcs.add = function(dist, src) {
this.push({ dist:dist, src:src });
}
srcs.remove = function(dist) {
var i;
for(i=0; i<this.length; i++) {
if(this[i].dist === dist) {
srcs.splice(i,1);
return true;
}
}
return false;
};
srcs.add(-1, 'item_0.gif' );
srcs.add(1.7, 'item_1.gif');
srcs.add(5, 'item_2.gif');
srcs.add(15, 'item_3.gif');
srcs.add(90, 'item_4.gif');
Unfortunately, you lose the simplicity of a plain js object lookup, but that's the price you pay for having an ordered entity.
If you absolutely must have order and dot.notation, then maintain a plain js Object for lookup and an Array for order. With care, the two can be maintained with total integrity.
See my answer to this question. I implemented an basic ordered hashtable (ES 5+ only, didn't bother to polyfill)
var put = function(k,v){
if(map[k]){
console.log("Key "+ k+" is already present");
}else
{
var newMap = {};
map[k] = v;
Object.keys(map).sort().forEach(function(key){
newMap[key] = map[key];
});
map = newMap;
//delete newMap; in case object memory need to release
return map;
}
}
Put method will always take a key-value pair, internally creates another map with sorted keys from the actual map, update the value and return the updated map with sorted keys.No external library need to includ.
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.