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.
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. Returnsfalse
if the contact already exists; otherwise, adds the contact and returnstrue
. 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 givenname
. Returns an empty string if the contact does not exist.bool delete_contact(const std::string& name)
: Deletes a contact with the givenname
. Returnstrue
if the contact exists and is deleted,false
otherwise.
Let's break down each method in detail in the next sections.
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 returntrue
.
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 theunordered_map
to retrieve the phone number. - If the name doesn't exist in the
unordered_map
,find
will returncontacts.end()
, and we returnstd::nullopt
. - If the name exists, we return the phone number encapsulated in
std::optional<std::string>
.
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 returntrue
. - 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!
Unordered maps are particularly efficient for managing an address book due to several reasons:
- Efficient Lookups: Unordered maps provide average 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.
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!