113

I have a list of a few thousand integer keys. The only thing I need to do with this list is say whether or not a given value is in the list.

For C# I would use a HashSet to make that look-up fast. What's the JavaScript equivalent?


Minimal support level: IE 9+, jQuery (current)

Kees C. Bakker
  • 32,294
  • 27
  • 115
  • 203
Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447
  • 13
    Typically, an object. – p.s.w.g Jun 13 '14 at 00:40
  • 2
    possible duplicate of [How do I implement a Dictionary or Hashtable in Javascript?](http://stackoverflow.com/questions/1208222/how-do-i-implement-a-dictionary-or-hashtable-in-javascript) – Jonn Jun 13 '14 at 00:44
  • I'm in agreement that it's a duplicate (although not everyone picks up on using a Map as a Set..), but the answers in the linked question miss many of the idiosyncrasies and notes found below. – user2864740 Jun 13 '14 at 00:56
  • o=Object.create(null); "toString" in o == false; !!o.toString == false; – dandavis Jun 13 '14 at 01:32
  • 7
    Not a duplicate - a HashSet and a Dictionary are very different. A Dictionary let's you retrieve the object. A HashSet doesn't let you retrieve the object but only tells you if one is there. – Kees C. Bakker Oct 09 '14 at 20:46
  • 3
    How is it that literally none of these answers miss one of the main points of HashSet? It only allows unique items. – user9993 Mar 28 '17 at 13:08
  • 2
    @user9993 - If you *test* any of these answers, you will see that they behave correctly. What may have confused you is that with C#'s HashSet, there aren't keys, there are only values. Many of these answers simulate that behavior using *keys* of an Object or Map. Those *keys* are unique: setting a new value to an existing key overwrites that key. (The answers that use `Set` structure are more straightforward: A JS `Set` is like a C# HashSet.) – ToolmakerSteve Oct 27 '19 at 13:38

7 Answers7

159

Actually JavaScript provides a Set object, fairly simple to use:

var set = new Set();
set.add(1);
set.add(2);

set.has(1)    // true

Unfortunately, it is not compatible with IE9.

João Barbosa
  • 1,699
  • 1
  • 8
  • 3
88

Under the hood, the JavaScript Object is implemented with a hash table. So, your Key:Value pair would be (your integer):true

A constant-time lookup function could be implemented as:

var hash = {
  1:true,
  2:true,
  7:true
  //etc...
};

var checkValue = function(value){
  return hash[value] === true;
};


checkValue(7); // => true
checkValue(3); // => false
LexJacobs
  • 2,483
  • 15
  • 21
  • 5
    Nice. One more thing to pay attention though, if you would like to do **_for (num in hash)_** iteration, the num (stored as key) here is of string type. – Rongsir Mar 07 '16 at 03:12
  • also a (value in hash) would suffice with just registering the property with null value - not even true boolean value and remove with delete hash[value] – Demetris Leptos Dec 08 '16 at 10:10
  • 3
    There is a Set object in javascript that would better suit this person's needs. – Rashi Abramson Jan 31 '18 at 01:47
  • @momomo numerical keys work here. Because of the bracket notation, the number is coerced to a string prior to the lookup. If trying to access `hash.1` it will return undefined for the reason you mentioned. Here's the code shared in my answer. Open the console tab at the bottom to view the result https://codepen.io/lexjacobs/pen/BYNbdZ?editors=0011 – LexJacobs Feb 01 '18 at 00:48
  • 6
    @J.D.Sandifer: that same spec also states: "Set objects must be implemented using either hash tables or other mechanisms that, on average, provide access times that are sublinear on the number of elements in the collection. The data structures used in this Set objects specification is only intended to describe the required observable semantics of Set objects. It is not intended to be a viable implementation model." In modern JavaScript, `Set` and `Map` are the best things to use when you need a set or a map. – jmrk Jun 16 '19 at 14:42
  • @jmrk Thanks for pointing that out. I always thought that seemed too slow. Do you know why they write the spec that way? Why say it should iterate through the list when it's actually specified earlier to not work that way? – J.D. Sandifer Jun 24 '19 at 04:11
  • @J.D.Sandifer - the purpose of a spec is to define behavior, in as simple a way as possible. The goal is to avoid ambiguity. Its easy to write a "reference spec" that iterates over the members of a set, and to have confidence that you've done it correctly. Then when you write a higher-performance implementation, you can compare its behavior to the reference spec, to know that you haven't inadvertently introduced a bug. No need to introduce a more complex data structure, if a simpler one serves the purpose of the spec. – ToolmakerSteve Oct 27 '19 at 13:23
  • @ToolmakerSteve Thanks! I think I get it now: The spec does not describe how something should behave - as it seemed based on the prescriptive way it's written - but instead is a description of the simplest implementation that achieves the desired behavior (albeit, probably in a less performant way than an actual implementation should). – J.D. Sandifer Nov 04 '19 at 16:02
38

Use an object. To add a key to the set, do:

object[key] = true;

To test whether a key is in the set, do:

if (object.hasOwnProperty(key)) { ... }

To remove a key from the set, do:

delete object[key]
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • 2
    `unset`? The operator is `delete` in JS! – gsnedders Jun 13 '14 at 00:43
  • Thanks, was just about to verify the syntax – Barmar Jun 13 '14 at 00:44
  • 3
    The correct way to test if a key is in the set is to use `object.hasOwnProperty(key)`. Otherwise, `"toString" in object == true`. – Bart Jun 13 '14 at 00:45
  • 6
    hasOwnProperty is slow and what if there's a key called hasOwnProperty? use o=Object.create(null); to create the "hashset" instead of a literal and then you can use fast property access to check for key membership instead of the methodic hasOwnProperty() and not have to worry about any property collisions while doing so. – dandavis Jun 13 '14 at 01:42
9

You can use just a regular JavaScript object and the 'in' keyword to see if that object has a certain key.

var myObj = {
  name: true,
  age: true
}

'name' in myObj //returns true;
'height' in myObj // returns false;

Or if you know you're going to have keys in your object that might be built in JavaScript object properties use...

var myObj = {
  name: true,
  age: true
}

myObj.hasOwnProperty('name') //returns true;
myObj.hasOwnProperty('height') // returns false;
Tyler McGinnis
  • 34,836
  • 16
  • 72
  • 77
  • 'toString' in myObj // returns true – Barmar Jun 13 '14 at 00:47
  • 4
    and what if there is a key called "hasOwnProperty" ? – dandavis Jun 13 '14 at 01:43
  • 36
    Then you re-evaluate your life. – Tyler McGinnis Jun 13 '14 at 04:10
  • @TylerMcGinnis lol - I think it would still work - but yeah, reevaluate why you have a property called hasOwnProperty.. and just use "in" anyway – Demetris Leptos Dec 08 '16 at 10:12
  • @dandavis I don't know if you are joking but in any case, it would work - and Object.prototype.hasOwnProperty('hasOwnProperty'); would give true for the presence of the function and instance.hasOwnProperty('hasOwnProperty'); would give true only if the property is set.. – Demetris Leptos Dec 08 '16 at 10:17
  • 1
    we seem to have forgotten that the case of having an object as a key is not covered.. unless you identify all of your objects incrementally with '$id' (I'm newtonsoft inspired) or '__ id __' so you can then use that as a key when being an object.. hash[isNotPrimitive(key) ? key['$id'] : key] – Demetris Leptos Dec 08 '16 at 10:22
2

I've read the solutions and I tried some. After trying to use the object[key] method I realized that it wasn't going to work. I wanted a HashSet that could store HTML elements. When adding these objects the key was translated to a string, so I came up with my own set based on jQuery. It supports add, remove, contains and clear.

var HashSet = function () {

    var set = [];

    this.add = function (obj) {
        if (!this.contains(obj)) {
            set.push(obj);
        }
    };

    this.remove = function (obj) {
        set = jQuery.grep(set, function (value) {
            return value !== obj;
        });
    };

    this.clear = function () {
        set = [];
    };

    this.contains = function (obj) {
        return $.inArray(obj, set) > -1;
    };

    this.isEmpty = function () {
        return set.length === 0;
    };
};

Note
When adding something like $('#myElement') to the set, one should add the real HTML element $('#myElement')[0]. Oh... and if you want to keep a list of changed controls - use the name of the element (gave me a problem with :radio controls).

Note2
I think the object[key] might be faster for your integers.

Note3
If you are only going to store numbers or string, this set will be faster:

var HashSet = function () {

    var set = {};

    this.add = function (key) {
        set[key] = true;
    };

    this.remove = function (key) {
        delete set[key];
    };

    this.clear = function () {
        set = {};
    };

    this.contains = function (key) {
        return set.hasOwnProperty(key);
    };

    this.isEmpty = function () {
        return jQuery.isEmptyObject(set);
    };
};
Kees C. Bakker
  • 32,294
  • 27
  • 115
  • 203
  • 5
    This isn't really a HashSet at all... It doesn't meet any of the criteria that one would expect a HashSet to – Andrew Whitaker Oct 09 '14 at 20:47
  • It doesn't have the O1 performance of a HashSet, that's true. But it mimics C#'s HashSet functions. – Kees C. Bakker Oct 09 '14 at 20:50
  • 3
    No, it mimics the ICollection interface http://msdn.microsoft.com/en-us/library/92t2ye13%28v=vs.110%29.aspx which HashSet implements. The point of HashSet would typically be the O(1) lookup time, while the first implementation you provide is more similar in behavior to the List, being O(N). – Oskar Berggren Nov 10 '14 at 13:18
  • 1
    @OskarBerggren - The set provided at Note3 should have no problem with that and should be O1. – Kees C. Bakker Nov 10 '14 at 13:53
1

Map or if no need to iterate WeakMap

let m1=new Map();

m1.set('isClosed',false);
m1.set('isInitialized',false);

m1.set('isClosed',true);

m1.forEach(function(v,k)
{
    console.log(`${k}=${v}`);
});
Zen Of Kursat
  • 2,672
  • 1
  • 31
  • 47
1

Only Map in Javascript has faster look ups https://jsben.ch/RbLvM . if you want only for look ups you can create a lookup map out of the array you have like the below and use it


function createLookUpMap(arr = []) {
  return new Map(arr.map(item => [item, true]));
}

const lookupMap = createLookUpMap(['apple', 'orange', 'banana']);

lookupMap.has('banana'); // O(1)