PUBLISHED JUNE 14, 2024
How to Use the Set and Map Objects in JavaScript
Learn how to effectively use JavaScript's Set and Map objects for efficient data management. This guide covers basic and advanced operations, practical examples, and best practices
Prerequisite
Before diving into the usage of Set
and Map
objects in JavaScript, it's helpful to have a basic understanding of the following concepts:
- 1.JavaScript Fundamentals: Familiarity with JavaScript syntax and basic programming constructs such as variables, loops, and functions.
- 2.Arrays and Objects: Knowledge of how to work with arrays and objects in JavaScript, as they are often used in conjunction with
Set
andMap
objects.
With these prerequisites in place, you'll be well-prepared to explore and follow through smoothly.
Brief Overview of JavaScript Collections
JavaScript provides several types of collections for managing and storing data.
The most common collections are `arrays` and `objects`.
Arrays are ordered collections of values that can be accessed by their index, while objects are collections of key-value pairs where each key is a unique identifier.
While arrays and objects are fundamental and versatile, they have some limitations when it comes to managing unique values or key-value pairs efficiently.
This is where `Set` and `Map` objects come into play, offering more specialized and optimized solutions for these use cases.
Importance of Understanding Set and Map Objects
Understanding `Set` and `Map` objects is crucial for any JavaScript developer aiming to write clean, efficient, and optimized code.
`Set` objects allow for the storage of unique values, eliminating duplicates automatically, making them ideal for tasks such as tracking unique items or ensuring that no value is repeated.
`Map` objects, on the other hand, provide a more flexible and powerful way to handle key-value pairs compared to plain objects.
They allow any type of value to be used as a key, including functions, objects, and primitive values.
This flexibility can simplify the handling of complex data structures and improve the readability and performance of your code.
In this guide, we'll explore the basics of Set and Map objects, including their creation, manipulation, and use cases, helping you leverage these powerful tools in your JavaScript projects.
What is a Set?
A Set
is a collection of unique values. Unlike arrays, a Set
ensures that no value occurs more than once.
This makes Set
ideal for tasks where uniqueness is required, such as filtering duplicates from an array.
Basic Operations with Set
1. Creating a Set
To create a Set
, you can use the Set
constructor. Here's how you do it:
// Creating an empty Set
const mySet = new Set();
console.log(mySet); // Output: Set(0) {}
// Creating a Set with initial values
const randomArray = ["value1", 1, 2.44, false, null, new Date(), function fn(){}, {a:"a1", b: "b1"}];
const mySetWithValues = new Set([...randomArray, ...randomArray]); // try duplicating the array's content while creating the set.
console.log(mySetWithValues);
/* Output: Set(9) {
'value1',
1,
2.44,
false,
null,
2024-06-14T04:23:38.316Z,
[Function: fn],
{ a: 'a1', b: 'b1' },
8
}
*/
In the example above, we created an empty Set
and a Set
initialized with values from randomArray
. The newly created Set
automatically filters out any duplicate values.
2. Adding Elements
You can add elements to a Set
using the `add` method.
const dataArray = ["email", "name", "phone", {lat: 1000, log: -9999}];
const extraDataArray2 = ["age", "address", "name", ];
//create the set
const mySet = new Set(dataArray);
console.log(mySet); // Output: Set(4) { 'email', 'name', 'phone', { lat: 1000, log: -9999 } }
// add elements to the set
extraDataArray2.forEach(ele => {
mySet.add(ele);
});
console.log(mySet); // Output: Set(4) { 'email', 'name', 'phone', { lat: 1000, log: -9999 }, 'age', 'address' }
// NB: "name" from extraDataArray2 isn't duplicated
Here, even though we tried to add the value `name` twice, it only appears once in the Set
.
3. Removing Elements
To remove an element from a Set
, use the `delete` method.
const dataArray = ["email", "name", "phone", "age", "address", {lat: 1000, log: -9999}];
//create the set
const mySet = new Set(dataArray);
// log the set after creation
console.log(mySet); // output: Set(6) { 'email', 'name', 'phone', 'age', 'address', { lat: 1000, log: -9999 }}
// remove elements from set
console.log(mySet.delete("play")); // output: false
["phone", "address", "id"].forEach( ele => {
console.log(mySet.delete(ele) ? ` ${ele} was deleted successfully` : `${ele} doesn\'t exixt in the set.`)
});
/* output:
phone was deleted successfully
address was deleted successfully
id doesn't exixt in the set.
*/
// log the set after removing elements
console.log(mySet); // output: Set(4) { 'email', 'name', 'age', { lat: 1000, log: -9999 } }
4. Checking for Existence
You can check if an element exists in a Set
using the `has` method.
const dataArray = ["email", "name", "phone", "age", "address", {lat: 1000, log: -9999}];
//create the set
const mySet = new Set(dataArray);
// log the set after creation
console.log(mySet); // output: Set(6) { 'email', 'name', 'phone', 'age', 'address', { lat: 1000, log: -9999 }}
// checking for existence
["phone", "fax", "address", "id", {lat: 1000, log: -9999}].forEach( ele => {
console.log(mySet.has(ele) ? `Set contains ${ele}` : `Set doesn\'t contain ${ele}`)
});
/* output:
Set contains phone
Set doesn't contain fax
Set contains address
Set doesn't contain id
Set doesn't contain [object Object]
*/
The `has` method returns true
if the element exists in the Set
and false
otherwise.
{lat: 1000, log: -9999}
wasn't found because it a reference to a different object.
5. Iterating Over a Set
You can iterate over the elements of a Set
using a `for...of` loop or the `forEach` method.
const dataArray = ["email", "name", "phone", "age", "address", {lat: 1000, log: -9999}];
//create the set
const mySet = new Set(dataArray);
// log the set after creation
console.log(mySet); // output: Set(6) { 'email', 'name', 'phone', 'age', 'address', { lat: 1000, log: -9999 }}
console.log('\n ************* \n');
// using for...of loop
for (const ele of mySet) {
console.log(ele);
}
console.log('\n ************* \n');
//using forEach loop
mySet.forEach(ele => {
console.log(ele);
});
console.log('\n ************* \n');
// using keys and values
for (const ele of mySet.keys()) {
console.log(ele);
}
console.log('\n ************* \n');
for (const ele of mySet.values()) {
console.log(ele);
}
console.log('\n ************* \n');
for (const [key, value] of mySet.entries()) {
console.log(`key: ${key} && value: ${value}`);
}
All the above methods allow you to traverse the Set
and access each element.
Common Use Cases for Set
1. Removing Duplicates from an Array:
This in probabbly one of the common use case
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // Output: [1, 2, 3, 4, 5]
By converting an array to a Set
and then back to an array, you can remove any duplicate values.
2. Checking for Unique Values:
const names = ['Teghen', 'Yohan', 'Donald', 'Zem', "Donald", "Yohan"];
const uniqueNamesSet = new Set(names);
if (uniqueNamesSet.size !== names.length) {
console.log('The array contains duplicate names.');
} else {
console.log('All names are unique.');
}
// Output: The array contains duplicate names.
Using the `size` property of a Set
, you can easily determine if there are any duplicate values in an array.
3. Tracking Unique Events:
For this case, let's use node.js event API to illustrate the idea.
const event = require('events');
const ee = new event();
const addEventTracker = new Set();
const emitEventTracker = new Set();
function addUniqueEvent(eventName) {
if (!addEventTracker.has(eventName)) {
addEventTracker.add(eventName);
ee.on(eventName, data => {
handleUniqueEvents(eventName, data);
});
console.log(`Event ${eventName} tracked.`);
}
else {
console.log(`Event ${eventName} already tracked.`);
}
}
function handleUniqueEvents(eventName, data) {
if (!addEventTracker.has(eventName)) {
console.log(`The event ${eventName} wasn\'t registered!`);
}
else {
if (!emitEventTracker.has(eventName)) {
console.log(`Data recieved from ${eventName} event :`, data);
emitEventTracker.add(eventName);
}
else {
console.log(`Event ${eventName} has already been received!`);
}
}
}
// create event list and add to addEventTracker set
['greet', 'question1', 'question2', 'questionN'].forEach(ele => {
addUniqueEvent(ele);
});
console.log('\naddEventTracker set: ', addEventTracker);
// emit events with data
['greeting', 'question1', 'question2', 'questionN'].forEach((ele, index) => {
setTimeout(() => {
ee.emit(ele, `data from ${ele}`);
}, 200 * (index + 1))
});
// delayed the execution for convinience
setTimeout(() => {
// try registering more event
['greeting', 'question1', 'question2'].forEach(ele => {
addUniqueEvent(ele);
});
console.log('\naddEventTracker set: ', addEventTracker);
// emit same events as above but this time greetings is registered so we should get the log too.
['greet', 'greeting', 'question1', 'question2', 'questionN'].forEach((ele, index) => {
setTimeout(() => {
ee.emit(ele, `data from ${ele}`);
}, 200 * (index + 1))
});
}, 2000);
This example is contrived just for the sake of fun🤓. Run the code above and as expected, you should get something similar to this in the console.
Event greet tracked.
Event question1 tracked.
Event question2 tracked.
Event questionN tracked.
addEventTracker set: Set(4) { 'greet', 'question1', 'question2', 'questionN' }
Data recieved from question1 event : data from question1
Data recieved from question2 event : data from question2
Data recieved from questionN event : data from questionN
Event greeting tracked.
Event question1 already tracked.
Event question2 already tracked.
addEventTracker set: Set(5) { 'greet', 'question1', 'question2', 'questionN', 'greeting' }
Data recieved from greet event : data from greet
Data recieved from greeting event : data from greeting
Event question1 has already been received!
Event question2 has already been received!
Event questionN has already been received!
A Set
can be used to track unique events or actions, ensuring that each event is only registered and listerned too once.
Another good example could be tracking a unique List of Users. Imagine a chat application where you need to keep track of unique users who have joined a chat room.
Let's setup a ChatRoom class for this.
class ChatRoom {
constructor() {
this.users = new Set();
}
join(user) {
if (!this.users.has(user)) {
this.users.add(user);
console.log(`${user} joined the chat.`);
} else {
console.log(`${user} is already in the chat.`);
}
}
leave(user) {
if (this.users.has(user)) {
this.users.delete(user);
console.log(`${user} left the chat.`);
} else {
console.log(`${user} is not in the chat.`);
}
}
listUsers() {
console.log('Current User list: ', Array.from(this.users));
}
}
const chatRoom = new ChatRoom();
chatRoom.join('Yohan'); // Ouput: Yohan joined the chat.
chatRoom.join('Lucky'); // Ouput: Lucky joined the chat.
chatRoom.join('Yohan'); // Ouput: Yohan is already in the chat.
chatRoom.listUsers(); // Ouput: Current User list: [ 'Yohan', 'Lucky' ]
chatRoom.leave('Lucky'); // Ouput: Lucky left the chat.
chatRoom.join('Trevoh'); // Ouput: Trevoh joined the chat.
chatRoom.listUsers(); // Ouput: Current User list: [ 'Yohan', 'Trevoh' ]
Here, we use Set
to ensure that each visitor is counted only once, providing an accurate count of unique visits.
Performance Considerations
When working with Set
, it is important to understand the performance implications:
- ➢Insertion and Deletion: Operations like adding or deleting elements from a
Set
have average time complexities of O(1). This makesSet
highly efficient for scenarios involving frequent insertions and deletions. - ➢Membership Checking: The
has
method also operates in O(1) time, allowing quick checks for the existence of an element. - ➢Iteration: Iterating over a
Set
is O(n), where n is the number of elements. This is generally fast but can be slower compared to arrays if order matters, asSet
maintains insertion order but doesn't support indexed access.
Overall, Set
is a powerful and efficient collection for managing unique elements, and understanding its methods, properties, and performance characteristics can help you leverage its full potential in your JavaScript applications.
What is a Map?
A `Map` is a collection of key-value pairs where both keys and values can be of any type.
Unlike objects, which only allow strings and symbols as keys, `Map` objects allow keys of any type, including functions, objects, and primitive types.
Basic Operations with Map
1. Creating a Map
To create a new Map
, you can use the `Map` constructor. You can also initialize it with an iterable of key-value pairs.
// Creating an empty Map
const emptyMap = new Map();
console.log(emptyMap); // Output : Map(0) {}
// Creating a Map with initial key-value pairs
const initializedMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
console.log(initializedMap); // Output: Map { 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' }
2. Setting Key-Value Pairs
You can add key-value pairs to a Map
using the `set` method.
const myMap = new Map();
myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Enjoyment');
console.log(myMap); // Map(3) { 'name' => 'Yohan', 'age' => 1, 'occupation' => 'Enjoyment' }
3. Getting Values
You can retrieve values from a Map
using the `get` method.
const myMap = new Map();
myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');
console.log(myMap.get('name')); // Output : Yohan
console.log(myMap.get('age')); // Output : 1
console.log(myMap.get('occupation')); // Output : Pro Crier
console.log(myMap.get('weight')); // Output : undefined
4. Checking for Existence
To check if a key exists in a Map
, use the `has` method.
const myMap = new Map();
myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');
console.log(myMap.has('name')); // Output : true
console.log(myMap.has('age')); // Output : true
console.log(myMap.has('weight')); // Output : false
5. Removing Key-Value Pairs
You can remove key-value pairs from a Map
using the `delete` method.
It returns a true if element exists and is removed, else returns false
const myMap = new Map();
myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');
console.log(myMap.delete('occupation')); // Output : true
console.log(myMap.delete('age')); // Output : true
console.log(myMap); // Map(1) { 'name' => 'Yohan' }
To remove all key-value pairs, use the `clear` method.
const myMap = new Map();
myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');
console.log(myMap.clear('occupation'));
console.log(myMap); // Output : Map(0) {}
6. Iterating over a Map
Just like with Sets, you can iterate over a Map
using various methods such as forEach
, for...of
, or by using its built-in iterators.
const myMap = new Map();
myMap.set('name', 'Yohan');
myMap.set('age', 1);
myMap.set('occupation', 'Pro Crier');
myMap.set("weight", 8);
// Using forEach
console.log("\n******* using forEach ******");
myMap.forEach((value, key) => {
console.log(`${key}: ${key === 'weight' ? value + 'kg' : value}`);
});
// Using for...of
console.log("\n******* using for...of ******");
for (const [key, value] of myMap) {
console.log(`${key}: ${key === 'weight' ? value + 'kg' : value}`);
}
// Using keys, values, and entries iterators
console.log("\n******* using keys method ******");
for (const key of myMap.keys()) {
console.log(key);
}
console.log("\n******* using values method ******");
for (const value of myMap.values()) {
console.log(value);
}
console.log("\n******* using entries method ******");
for (const [key, value] of myMap.entries()) {
console.log(`${key}: ${key === 'weight' ? value + 'kg' : value}`);
}
The result from the above looks something like:
******* using forEach ******
name: Yohan
age: 1
occupation: Pro Crier
weight: 8kg
******* using for...of ******
name: Yohan
age: 1
occupation: Pro Crier
weight: 8kg
******* using keys method ******
name
age
occupation
weight
******* using values method ******
Yohan
1
Pro Crier
8
******* using entries method ******
name: Yohan
age: 1
occupation: Pro Crier
weight: 8kg
Common Use Cases for Map
Maps are particularly useful in scenarios where you need a dictionary-like structure with keys that can be of any type.
There are many use cases where Maps out perform standard objects. Let's look at a use case.
- ➢Implementing LRU Cache
Let's use a Map
to implement a Least Recently Used (`LRU`) cache.
class LRUCache {
constructor(limit) {
this.limit = limit;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) {
return null
};
const value = this.cache.get(key);
// Move the accessed key to the end to show that it was recently used
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key, value) {
if (this.cache.size >= this.limit) {
// Remove the first key-value pair (least recently used)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
const lruCache = new LRUCache(3);
lruCache.set('name', "Yohan");
lruCache.set('age', 1);
lruCache.set('weight', "8kg");
console.log(lruCache.get('name')); // Output: Yohan
lruCache.set("nickName", "Nyucks"); // This should remove 'age' because 'name' was recently accessed
console.log(lruCache.get('age')); // Output: null (Removed!)
console.log(lruCache.get('weight')); // Output: 8kg
console.log(lruCache.get('nickName')); // Output: Nyucks
console.log(lruCache.cache); // Output : Map(3) { 'name' => 'Yohan', 'weight' => '8kg', 'nickName' => 'Nyucks' }
Performance Considerations
When working with Map
, it is important to consider its performance characteristics.
1. Time Complexity
- ➢Insertion and Deletion: The
set
anddelete
operations typically have an average time complexity of `O(1)`, makingMap
very efficient for adding and removing key-value pairs. - ➢Access: The
get
andhas
methods also have an average time complexity of `O(1)`, ensuring fast access to values.
2. Iteration
Iterating over a Map
is generally linear with respect to the number of entries (`O(n)`).
This makes operations like forEach
or using iterators efficient even for large collections.
3. Memory Usage
Map
can use more memory compared to plain objects, especially when storing a large number of key-value pairs, due to its underlying data structures.
However, the performance benefits often outweigh the additional memory usage.
Set vs Array
Set:
- ➢Use Case: Use
Set
when you need to store unique values and quickly check for existence without duplicates. - ➢Pros: Automatically handles uniqueness, fast membership testing with
has
method, supports Mathematical set operations (`union`, `intersection`, etc.). - ➢Cons: Ordered iteration is not guaranteed, does not support indexing.
Array:
- ➢Use Case: Use
Array
when you need ordered collection of elements with indexed access. - ➢Pros: Ordered collection, supports indexing and efficient iteration, wide range of built-in methods (
map
,filter
,reduce
). - ➢Cons: Checking for existence of an element is slower compared to
Set
, requires manual handling of duplicates.
Map vs Object
Map:
- ➢Use Case: Use
Map
when you need to associate keys with values and the keys can be of different types (including objects). - ➢Pros: Efficient key-value pair management, preserves key insertion order, built-in methods for iteration and manipulation (
set
,get
,delete
). - ➢Cons: More memory overhead compared to plain objects, may be slower for small-scale key access compared to objects.
Object:
- ➢Use Case: Use plain objects (
{}
) when you need simple key-value pairs and keys are always strings or symbols. - ➢Pros: Lightweight, fast property access and manipulation, widely supported and optimized for common use cases.
- ➢Cons: Limited to string or symbol keys, does not preserve key insertion order (for-in loop order is not guaranteed), no built-in methods for size or iteration order.
Conclusion
In this comprehensive guide, we've explored JavaScript's Set
and Map
objects and their functionalities. Here are the key takeaways:
- ➢Set Objects: Ideal for managing collections of unique values efficiently.
They offer automatic handling of duplicates, fast membership testing, and support for set operations like union and intersection. - ➢Map Objects: Perfect for associating keys with values, especially when keys can be of various types, including objects.
They preserve insertion order, provide efficient key-value pair management, and offer a range of methods for manipulation and iteration.
Explore Further
To deepen your understanding and mastery of Set
and Map
objects:
- ➢Practice: Implement these concepts in your projects to see firsthand how they enhance data management and performance.
- ➢Explore Further: Dive deeper into advanced operations, performance optimizations, and real-world applications of
Set
andMap
in JavaScript.
By leveraging Set
and Map
effectively, you can write cleaner, more efficient JavaScript code that meets the demands of modern web development.
This concludes our guide on Set
and Map
objects in JavaScript.
Happy coding💻🤓!