Lesson 1
Introduction to Dictionaries in C#
Introduction to Dictionaries

Hi, and welcome! Today, we'll explore Dictionaries, a data structure that organizes data as key-value pairs, much like a treasure box with unique labels for each compartment.

Imagine dozens of toys in a box. If each toy had a unique label (the key), you could directly select a toy (the value) using the label. No rummaging required — that's the power of Dictionaries! Today, we'll understand Dictionaries and learn how to implement them in C#.

Understanding Dictionaries in C#

Dictionaries are special data structures that use unique keys instead of indices. Think of them as arrays where the indices can be of any type, provided they are hashable and comparable.

C# implements Dictionaries through the Dictionary<TKey, TValue> class in the System.Collections.Generic namespace. They hold data in key-value pairs.

Let's create a Dictionary, functioning as a catalog for a library:

C#
1using System; 2using System.Collections.Generic; 3 4class Solution { 5 public static void Main(string[] args) { 6 // Creating a catalog for the library using Dictionary with initialization 7 var libraryCatalog = new Dictionary<string, string> { 8 {"book1", "A Tale of Two Cities"}, 9 {"book2", "To Kill a Mockingbird"}, 10 {"book3", "1984"} 11 }; 12 } 13}

In this Dictionary, book1, book2, and book3 are keys, while the book titles serve as their respective values.

It's important to remember that the keys should be of a type that supports hashing and equality comparison. Examples include string, short, int, long, float, double, char, and bool. The values can be of any type.

Dictionary Operations: Accessing, Updating, and Removing Elements

Dictionary allows you to access, update, or remove elements:

  1. Accessing Elements: You can retrieve a book's title using its key straightforwardly: libraryCatalog["book1"] would return "A Tale of Two Cities." But what happens if you try to access a key that isn't present in the Dictionary? This would throw a KeyNotFoundException. To avoid such exceptions, a safer way to access values is with the TryGetValue() method. This method takes two parameters: one for the key and one for the value. If the key exists in the Dictionary, the method returns true and assigns the corresponding value to the value parameter. If the key does not exist, the method returns false.

    C#
    1using System; 2using System.Collections.Generic; 3 4class Solution { 5 public static void Main(string[] args) { 6 // Creating a catalog for the library using Dictionary with initialization 7 var libraryCatalog = new Dictionary<string, string> { 8 {"book1", "A Tale of Two Cities"}, 9 {"book2", "To Kill a Mockingbird"}, 10 {"book3", "1984"} 11 }; 12 13 // Accessing a book's title 14 if (libraryCatalog.TryGetValue("book1", out var title1)) 15 Console.WriteLine(title1); // Output: "A Tale of Two Cities" 16 else 17 Console.WriteLine("Key not found"); 18 19 // Accessing a nonexistent key 20 if (libraryCatalog.TryGetValue("book100", out var titleNonexistent)) 21 Console.WriteLine(titleNonexistent); 22 else 23 Console.WriteLine("Key not found"); // Output: "Key not found" 24 } 25}
  2. Adding or Updating Elements: When adding a new book, you can either use the Add() method, or index notation: libraryCatalog.Add("book4", "Pride and Prejudice"); or libraryCatalog["book4"] = "Pride and Prejudice";. When updating an existing book's title in the catalog, you can only use index notation: libraryCatalog["book1"] = "The Tell-Tale Heart".

    C#
    1using System; 2using System.Collections.Generic; 3 4class Solution { 5 public static void Main(string[] args) { 6 // Creating a catalog for the library using Dictionary with initialization 7 var libraryCatalog = new Dictionary<string, string> { 8 {"book1", "A Tale of Two Cities"}, 9 {"book2", "To Kill a Mockingbird"}, 10 {"book3", "1984"} 11 }; 12 13 // Updating an existing book's title 14 libraryCatalog["book1"] = "The Tell-Tale Heart"; 15 Console.WriteLine("Updated book1: " + libraryCatalog["book1"]); // Output: "Updated book1: The Tell-Tale Heart" 16 17 // Adding a new book to the catalog 18 libraryCatalog.Add("book4", "Pride and Prejudice"); 19 //libraryCatalog["book4"] = "Pride and Prejudice"; also works 20 Console.WriteLine("Added book4: " + libraryCatalog["book4"]); // Output: "Added book4: Pride and Prejudice" 21 } 22}
  3. Removing Elements: If book1 no longer exists in our library, you can remove it using libraryCatalog.Remove("book1").

    C#
    1using System; 2using System.Collections.Generic; 3 4class Solution { 5 public static void Main(string[] args) { 6 // Creating a catalog for the library using Dictionary with initialization 7 var libraryCatalog = new Dictionary<string, string> { 8 {"book1", "A Tale of Two Cities"}, 9 {"book2", "To Kill a Mockingbird"}, 10 {"book3", "1984"} 11 }; 12 13 // Removing an existing book from the catalog 14 libraryCatalog.Remove("book1"); 15 if (libraryCatalog.TryGetValue("book1", out var removedBook)) 16 Console.WriteLine("Removed book1: " + removedBook); 17 else 18 Console.WriteLine("Removed book1: null"); // Output: "Removed book1: null" 19 } 20}
Dictionary Methods: Iteration, Accessing Keys and Values

Dictionaries offer several useful methods to interact with and manage your data:

Iterating over the Dictionary: Loop through your Dictionary to access each key-value pair in turn. One common way is to use the foreach loop with KeyValuePair<TKey, TValue>.

C#
1using System; 2using System.Collections.Generic; 3 4class Solution { 5 public static void Main(string[] args) { 6 var libraryCatalog = new Dictionary<string, string> { 7 {"book1", "A Tale of Two Cities"}, 8 {"book2", "To Kill a Mockingbird"}, 9 {"book3", "1984"} 10 }; 11 12 // Looping over the Dictionary 13 foreach (var entry in libraryCatalog) 14 { 15 Console.WriteLine(entry.Key + " : " + entry.Value); 16 } 17 } 18}

When run, this code may output:

Plain text
1book1 : A Tale of Two Cities 2book2 : To Kill a Mockingbird 3book3 : 1984

Please note that the order of the output might differ because Dictionary does not maintain any order of keys. It could be in any sequence, such as:

Plain text
1book2 : To Kill a Mockingbird 2book3 : 1984 3book1 : A Tale of Two Cities

This unordered nature is a characteristic of the Dictionary class in C#.

Understanding Dictionaries: Hash Functions

Dictionaries can be visualized as arrays where any type of key can index values. Behind the scenes, however, a Dictionary is still an array with numerical-based indexing facilitated by hash functions. A hash function converts a key into a unique numeric value (hashcode) that maps to an index in the underlying array, allowing for efficient direct access to values without a linear search.

Here's how it works: a hash function takes a key and produces a hashcode, a fixed-size numerical value representing the key. For example, hash("book1") might yield 123, placing it at index 123 in the underlying array. The implementation of the hash function is crucial. If the hash function is too simplistic or poorly designed, it can lead to frequent collisions, where multiple keys generate the same hashcode. For instance, a trivial hash function that always returns the same value would place all keys at the same index, we would have to search through the entire array, effectively turning the Dictionary into a list and degrading performance to O(n). To avoid this, a good hash function should spread keys uniformly across the array, making use of mathematical techniques that distribute hashcodes as evenly as possible. This reduces the likelihood of collisions and ensures efficient access times for operations on the Dictionary.

By reducing collisions, hash functions optimize data retrieval and storage. Even in cases where collisions occur, C#'s Dictionary handles them internally to maintain efficient performance, ensuring quick access to values based on their keys.

Diving Deeper: Understanding Time Complexity

Dictionaries are popular because they save time! Thanks to hash functions, operations like adding, updating, and locating elements take average constant time, O(1), which means that the time to do these operations doesn't grow, even when the library size grows. For example, it is as fast to insert a new key at key count 4 as it is at key count 9000. This efficiency is what makes Dictionaries an excellent choice for applications requiring rapid access to data.

Average-Case Scenario

In the average-case scenario, the hash function distributes keys uniformly across the Dictionary. Thanks to this uniform distribution, the operations of adding, updating, or retrieving keys have a time complexity of O(1) because they involve a simple arithmetic operation and direct addressing. Under such conditions, Dictionaries perform exceptionally well in diverse scenarios.

Worst-Case Scenario

In the worst-case scenario, a poor hash function could cause too many collisions, making the Dictionary resemble a linked list. When this happens, operations degrade to O(n) time complexity because they involve traversing a list of n elements. However, C#'s Dictionary handles collisions internally to mitigate performance degradation. This built-in handling ensures that even in less ideal situations, performance remains reasonable.

Lesson Summary and Practice

Well done! You've mastered Dictionaries, understood C#'s implementation of Dictionaries through Dictionary<TKey, TValue>, learned their operations, and grasped the concept of time complexity. Now, gear up for some practice exercises to reinforce your learning. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.