Lesson 2

Creating an Address Book Application Using C++ Unordered Maps

Introduction

Welcome! Today, we will explore creating a simple address book application using C++ and its std::unordered_map class. This task will help you understand how to manipulate unordered maps in C++, focusing on adding, retrieving, and deleting entries. By the end of this lesson, you'll have a solid grasp of these fundamental operations.

Introducing Methods to Implement

In this task, we will implement three methods to manage our address book:

  • bool add_contact(const std::string& name, const std::string& phone_number): Adds a new contact. Returns false if the contact already exists; otherwise, adds the contact and returns true. In this task, let's assume phone numbers do not change, so it's not allowed to overwrite the existing contact's number.
  • std::string get_contact(const std::string& name): Retrieves the phone number for a given name. Returns an empty string if the contact does not exist.
  • bool delete_contact(const std::string& name): Deletes a contact with the given name. Returns true if the contact exists and is deleted, false otherwise.

Let's break down each method in detail in the next sections.

Step 1: Implementing `add_contact`

This method adds a new contact to the address book with the given name and phone_number. If the contact already exists, it returns false. Otherwise, it adds the contact and returns true.

Question: Why do you think we need to check if the contact already exists?

Answer: To avoid duplicating existing entries. If a contact with the same name already exists, we shouldn't allow overwriting its phone number in this method, as it's only for creation.

Here is the method implementation:

C++
1#include <iostream> 2#include <string> 3#include <unordered_map> 4 5class AddressBook { 6public: 7 bool add_contact(const std::string& name, const std::string& phone_number) { 8 if (contacts.find(name) != contacts.end()) { 9 return false; 10 } 11 contacts[name] = phone_number; 12 return true; 13 } 14 15 // To display the current contacts 16 void print_contacts() const { 17 for (const auto& [name, phone_number] : contacts) { 18 std::cout << name << ": " << phone_number << '\n'; 19 } 20 } 21 22private: 23 std::unordered_map<std::string, std::string> contacts; 24}; 25 26// Example usage: 27int main() { 28 AddressBook address_book; 29 std::cout << std::boolalpha << address_book.add_contact("Alice", "123-456-7890") << '\n'; // true 30 std::cout << address_book.add_contact("Alice", "098-765-4321") << '\n'; // false 31 address_book.print_contacts(); // Alice: 123-456-7890 32 return 0; 33}

In this method:

  • We verify if the contact already exists using if (contacts.find(name) != contacts.end()).
  • If it exists, we return false.
  • If it doesn't exist, we add it to our unordered_map and return true.
Step 2: Implementing `get_contact`

This method retrieves the phone number associated with a given name. If the contact does not exist, it returns an empty std::optional<std::string> (that is, std::nullopt).

Question: What do we gain by using std::optional<std::string> when a contact doesn't exist?

Answer: Using std::optional<std::string> provides a clear and explicit indicator that the contact is not in the address book. It allows us to handle such cases gracefully and makes the absence of a contact more evident in the code.

Here is the method implementation:

C++
1#include <iostream> 2#include <string> 3#include <unordered_map> 4#include <optional> 5 6class AddressBook { 7public: 8 bool add_contact(const std::string& name, const std::string& phone_number) { 9 if (contacts.find(name) != contacts.end()) { 10 return false; 11 } 12 contacts[name] = phone_number; 13 return true; 14 } 15 16 std::optional<std::string> get_contact(const std::string& name) const { 17 auto it = contacts.find(name); 18 if (it != contacts.end()) { 19 return it->second; 20 } 21 return std::nullopt; 22 } 23 24 // To display the current contacts 25 void print_contacts() const { 26 for (const auto& [name, phone_number] : contacts) { 27 std::cout << name << ": " << phone_number << '\n'; 28 } 29 } 30 31private: 32 std::unordered_map<std::string, std::string> contacts; 33}; 34 35// Example usage: 36int main() { 37 AddressBook address_book; 38 address_book.add_contact("Alice", "123-456-7890"); 39 auto contact = address_book.get_contact("Alice"); 40 if (contact) { 41 std::cout << *contact << '\n'; // 123-456-7890 42 } else { 43 std::cout << "Contact not found\n"; 44 } 45 46 contact = address_book.get_contact("Bob"); 47 if (contact) { 48 std::cout << *contact << '\n'; 49 } else { 50 std::cout << "Contact not found\n"; // Contact not found 51 } 52 53 return 0; 54}

In this method:

  • We use the find method of the unordered_map to retrieve the phone number.
  • If the name doesn't exist in the unordered_map, find will return contacts.end(), and we return std::nullopt.
  • If the name exists, we return the phone number encapsulated in std::optional<std::string>.
Step 3: Implementing `delete_contact`

This method deletes a contact with the given name. If the contact exists and is deleted, it returns true. If the contact does not exist, it returns false.

Question: What could be a real-world consequence of not checking if the contact exists before deletion?

Answer: Attempting to delete a non-existent contact could result in errors or unintended behavior.

Here is the method implementation:

C++
1#include <iostream> 2#include <string> 3#include <unordered_map> 4#include <optional> 5 6class AddressBook { 7public: 8 bool add_contact(const std::string& name, const std::string& phone_number) { 9 if (contacts.find(name) != contacts.end()) { 10 return false; 11 } 12 contacts[name] = phone_number; 13 return true; 14 } 15 16 std::optional<std::string> get_contact(const std::string& name) const { 17 auto it = contacts.find(name); 18 if (it != contacts.end()) { 19 return it->second; 20 } 21 return std::nullopt; 22 } 23 24 bool delete_contact(const std::string& name) { 25 auto it = contacts.find(name); 26 if (it != contacts.end()) { 27 contacts.erase(it); 28 return true; 29 } 30 return false; 31 } 32 33 // To display the current contacts 34 void print_contacts() const { 35 for (const auto& [name, phone_number] : contacts) { 36 std::cout << name << ": " << phone_number << '\n'; 37 } 38 } 39 40private: 41 std::unordered_map<std::string, std::string> contacts; 42}; 43 44// Example usage: 45int main() { 46 AddressBook address_book; 47 address_book.add_contact("Alice", "123-456-7890"); 48 std::cout << std::boolalpha << address_book.delete_contact("Alice") << '\n'; // true 49 std::cout << address_book.delete_contact("Bob") << '\n'; // false 50 address_book.print_contacts(); // (empty) 51 return 0; 52}

In this method:

  • We verify if the contact exists using if (contacts.find(name) != contacts.end()).
  • If it exists, we delete it using contacts.erase(it) and return true.
  • If the contact doesn't exist, we return false.

That's it! We now have the AddressBook class fully implemented, and all three methods are working and functional!

Why Unordered Maps are Efficient for These Tasks

Unordered maps are particularly efficient for managing an address book due to several reasons:

  • Efficient Lookups: Unordered maps provide average O(1)O(1) time complexity for lookups. This means retrieving a contact's phone number by name is very fast, even with a large number of contacts.
  • Uniqueness: Unordered maps inherently ensure that keys (in this case, contact names) are unique. This prevents duplicate entries and simplifies data integrity management.
  • Readability: The syntax for accessing unordered map entries is straightforward and highly readable. This makes the code easier to understand and maintain.
  • Flexibility: Unordered maps in C++ are versatile and can hold a variety of data types as values. This flexibility allows for easily extending the address book to store additional information, such as email addresses or physical addresses, if needed in the future.

These characteristics make std::unordered_map in C++ an ideal choice for implementing an address book.

Lesson Summary

In this lesson, we created a simple address book application using C++'s std::unordered_map. We implemented methods to add, retrieve, and delete contacts. Each step was built upon the previous one, enhancing our understanding of manipulating unordered maps in C++. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!

Practice is how you turn knowledge into actual skills.