Welcome to our next lesson in our TypeScript data structures revision course! Today, we will delve deeply into TypeScript Maps. Much like a bookshelf, Maps
allow you to quickly select the book (value) you desire by reading its label (key). They are vital to TypeScript for quickly accessing values using keys, with the added benefit of type safety for efficient key insertion and deletion. So, let's explore TypeScript Maps
for a clearer understanding of these concepts and leverage TypeScript's type-checking capabilities.
Our journey starts with TypeScript Maps, a pivotal data structure that holds data as key-value pairs. Imagine storing your friend's contact info in such a way that allows you to search for your friend's name (the key) and instantly find their phone number (the value). To begin working with TypeScript Maps, you initiate an empty Map using the new Map<>()
constructor with type annotations to specify the types of keys and values the Map will hold. The first type in the annotation represents the key type, and the second type represents the value type.
TypeScript1class PhoneBook { 2 private contacts: Map<string, string>; 3 4 constructor() { 5 // An empty Map with type annotations 6 this.contacts = new Map<string, string>(); 7 } 8 9 addContact(name: string, phoneNumber: string): void { 10 // Method to add a contact 11 this.contacts.set(name, phoneNumber); 12 } 13 14 getPhoneNumber(name: string): string | undefined { 15 // Method to retrieve contact's phone number, or undefined if it's not in contacts 16 return this.contacts.get(name); 17 } 18} 19 20// Create a PhoneBook instance 21const phoneBook = new PhoneBook(); 22 23// Add contacts 24phoneBook.addContact("Alice", "123-456-7890"); 25phoneBook.addContact("Bob", "234-567-8901"); 26console.log(phoneBook.getPhoneNumber("Alice")); // Output: "123-456-7890" 27console.log(phoneBook.getPhoneNumber("Bobby")); // Output: undefined
In the above code, we create a PhoneBook
class that uses a Map
to store contacts. As you can see, Maps
in TypeScript simplify the processes of adding, modifying, and accessing information with unique keys, all while maintaining type safety. The specific operations involved in utilizing these features will be discussed in the next section.
TypeScript Maps
enable a variety of operations for manipulating data, such as setting, getting, deleting key-value pairs, and more. Understanding these operations is crucial for efficient data handling in TypeScript, and type annotations further enhance safety and clarity.
-
Add or Update Entries: Use the
set
method to add a new key-value pair or update an existing key with a new value. This allows dynamic modifications without a predefined structure. -
Retrieve Values: The
get
method retrieves the value associated with a given key. It returnsundefined
if the key is not present. -
Check Key Existence: The
has
method checks whether a specific key exists in the Map, returningtrue
if it does andfalse
otherwise. -
Delete Entries: Utilize the
delete
method to remove a key-value pair from the Map, aiding in active management of its contents. If the key does not exist, the Map remains unchanged and no error occurs, allowing for safe management of its contents. -
Determine Map Size: The
size
property provides the total number of key-value pairs currently in the Map.
Let's see how these operations work in the context of a TaskManager
class:
TypeScript1class TaskManager { 2 private tasks: Map<string, string>; 3 4 constructor() { 5 // Initialize with an empty Map 6 this.tasks = new Map<string, string>(); 7 } 8 9 addUpdateTask(taskName: string, status: string): void { 10 // Add a new task or update an existing task 11 this.tasks.set(taskName, status); 12 } 13 14 getTaskStatus(taskName: string): string { 15 // Retrieve the status of a task; Returns "Not Found" if the task does not exist 16 return this.tasks.get(taskName) || "Not Found"; 17 } 18 19 deleteTask(taskName: string): void { 20 // Removes a task using its name 21 if (this.tasks.has(taskName)) { 22 this.tasks.delete(taskName); 23 } else { 24 console.log(`Task '${taskName}' not found.`); 25 } 26 } 27 28 getTaskCount(): number { 29 // Returns the number of tasks in the TaskManager 30 return this.tasks.size; 31 } 32} 33 34// Test the TaskManager class 35const myTasks = new TaskManager(); 36myTasks.addUpdateTask("Buy Milk", "Pending"); 37console.log(myTasks.getTaskStatus("Buy Milk")); // Output: Pending 38myTasks.addUpdateTask("Buy Milk", "Completed"); 39console.log(myTasks.getTaskStatus("Buy Milk")); // Output: Completed 40 41myTasks.deleteTask("Buy Milk"); 42console.log(myTasks.getTaskStatus("Buy Milk")); // Output: Not Found 43 44myTasks.addUpdateTask("Clean House", "In Progress"); 45console.log(myTasks.getTaskCount()); // Output: 1
This example showcases how to leverage Map operations in TypeScript to effectively manage data by adding, updating, retrieving, deleting entries, and checking the number of entries through a simulated Task Manager application.
TypeScript provides an elegant way to loop through Maps using for...of
loops. We can iterate through keys, values, or both simultaneously using specific functions provided by the Map object.
keys()
: This function returns an iterator object containing all the keys in the Map.values()
: This function returns an iterator object containing all the values in the Map.entries()
: This function returns an iterator object containing arrays of[key, value]
pairs for each entry in the Map.
Let's explore this in our Task Manager example:
TypeScript1class TaskManager { 2 private tasks: Map<string, string>; 3 4 constructor() { 5 this.tasks = new Map<string, string>(); 6 } 7 8 addTask(taskName: string, status: string): void { 9 this.tasks.set(taskName, status); 10 } 11 12 printAllTaskKeys(): void { 13 // Prints all tasks' keys 14 for (let taskName of this.tasks.keys()) { 15 console.log(taskName); 16 } 17 } 18 19 printAllTaskValues(): void { 20 // Prints all tasks' values (statuses) 21 for (let status of this.tasks.values()) { 22 console.log(status); 23 } 24 } 25 26 printAllEntries(): void { 27 // Prints all tasks as [key, value] pairs 28 for (let [taskName, status] of this.tasks.entries()) { 29 console.log(`${taskName}: ${status}`); 30 } 31 } 32} 33 34const myTasks = new TaskManager(); 35myTasks.addTask("Buy Milk", "Pending"); 36myTasks.addTask("Pay Bills", "Completed"); 37 38myTasks.printAllTaskKeys(); 39/* Output: 40Buy Milk 41Pay Bills 42*/ 43 44myTasks.printAllTaskValues(); 45/* Output: 46Pending 47Completed 48*/ 49 50myTasks.printAllEntries(); 51/* Output: 52Buy Milk: Pending 53Pay Bills: Completed 54*/
In this case, we demonstrate how to use for...of
loops with the keys()
, values()
, and entries()
functions to iterate over a Map's keys, values, and entries, respectively. This allows us to flexibly print all tasks in our task manager along with their statuses.
Nesting in Maps involves storing Maps within another Map. It's useful when associating multiple pieces of information with a key. Let's see how this works in a Student Database example.
TypeScript1class StudentDatabase { 2 private students: Map<string, Map<string, string>>; 3 4 constructor() { 5 this.students = new Map<string, Map<string, string>>(); 6 } 7 8 addStudent(name: string, subjects: Record<string, string>): void { 9 // Adds students and subjects 10 this.students.set(name, new Map<string, string>(Object.entries(subjects))); 11 } 12 13 getMark(name: string, subject: string): string { 14 const studentSubjects = this.students.get(name); 15 return studentSubjects ? (studentSubjects.get(subject) || "N/A") : "N/A"; 16 } 17 18 printDatabase(): void { 19 // Prints student and their subjects with grades 20 for (let [name, subjects] of this.students.entries()) { 21 console.log("Student:", name); 22 for (let [subject, grade] of subjects.entries()) { 23 console.log(` Subject: ${subject}, Grade: ${grade}`); 24 } 25 } 26 } 27} 28 29// Create a StudentDatabase instance 30const studentDb = new StudentDatabase(); 31studentDb.addStudent("Alice", { Math: "A", English: "B" }); 32 33console.log(studentDb.getMark("Alice", "English")); // Output: "B" 34console.log(studentDb.getMark("Alice", "History")); // Output: "N/A" 35studentDb.printDatabase(); 36/* Output: 37Student: Alice 38 Subject: Math, Grade: A 39 Subject: English, Grade: B 40*/
Let's shift our focus to a more interactive and familiar scenario: managing a shopping cart in an online store. This hands-on example will demonstrate how Maps
can be used to map product names to their quantities in a shopping cart. You will learn how to add products, update quantities, and retrieve the total number of items in the cart.
Here’s how you can implement and manipulate a shopping cart using a TypeScript Map:
TypeScript1class ShoppingCart { 2 private cart: Map<string, number>; 3 4 constructor() { 5 // Initialize cart as an empty Map 6 this.cart = new Map<string, number>(); 7 } 8 9 addProduct(productName: string, quantity: number): void { 10 // Add or update the quantity of a product in the cart 11 if (this.cart.has(productName)) { 12 this.cart.set(productName, this.cart.get(productName)! + quantity); 13 } else { 14 this.cart.set(productName, quantity); 15 } 16 } 17 18 removeProduct(productName: string): void { 19 // Remove a product from the cart 20 if (this.cart.has(productName)) { 21 this.cart.delete(productName); 22 } else { 23 console.log(`${productName} not found in your cart.`); 24 } 25 } 26 27 showCart(): void { 28 // Display the products and their quantities in the cart 29 if (this.cart.size === 0) { 30 console.log("Your shopping cart is empty."); 31 } else { 32 for (let [product, quantity] of this.cart.entries()) { 33 console.log(`${product}: ${quantity}`); 34 } 35 } 36 } 37} 38 39// Create an instance of ShoppingCart 40const myCart = new ShoppingCart(); 41 42// Add products and update their quantities 43myCart.addProduct("Apples", 5); 44myCart.addProduct("Bananas", 2); 45myCart.addProduct("Apples", 3); // Updates quantity of apples to 8 46 47// Display cart 48myCart.showCart(); 49/* Output: 50Apples: 8 51Bananas: 2 52*/ 53 54// Remove a product and show the updated cart 55myCart.removeProduct("Bananas"); 56myCart.showCart(); 57/* Output: 58Apples: 8 59*/
This example showcases the practical application of Maps
to manage a dynamic dataset, such as an online shopping cart. By using product names as keys and their quantities as values, we achieve efficient and flexible data manipulation. This exercise provides a solid foundation for understanding how to handle complex data structures in real-world TypeScript applications.
Well done! Today, we delved into TypeScript Maps and explored various operations, emphasizing the importance of type safety and TypeScript-specific features. We now invite you to get hands-on experience with the upcoming practice exercises. Mastering these concepts and honing your TypeScript Map
skills require regular practice. Happy learning!