PYnative

Python Programming

  • Learn Python ▼
    • Python Tutorials
    • Python Basics
    • Python Interview Q&As
  • Exercises ▼
    • Python Exercises
    • C++ Exercises
    • C Programming Exercises
    • Java Exercises
    • C# Exercises
  • Quizzes
  • Code Editor ▼
    • Online Python Code Editor
    • Online C Compiler
    • Online C++ Compiler
    • Online Java Compiler
    • Online C# Compiler
Home » C# Exercises » C# Dictionary Exercises: 30 Coding Problems with Solutions

C# Dictionary Exercises: 30 Coding Problems with Solutions

Updated on: July 15, 2026 | Leave a Comment

Dictionary<TKey, TValue> is one of the most useful collections in C#, giving you fast, key-based lookups for everything from phonebooks to inventory systems.

This collection of 30 C# Dictionary exercises covers adding, updating, and removing entries, safely checking for keys, and iterating over key-value pairs to build real, practical mini-programs.

Each exercise includes a Practice Problem, Purpose, Hint, and a full Solution with Explanation, so you get comfortable using dictionaries the way they’re meant to be used.

Also, See: C# Exercises with  22 topic-wise sets and 620+ practice questions.

What You’ll Practice

  • Safe Lookups: TryGetValue(), ContainsKey(), and handling missing keys.
  • Iteration: Looping through KeyValuePair<TKey, TValue> entries.
  • Practical Use: Frequency counters, grouping, and simple lookup tables.
  • Fundamentals: Creating, adding, and accessing key-value pairs.
+ Table Of Contents (30 Exercises)

Table of contents

  • Exercise 1: Word Counter
  • Exercise 2: Phonebook Lookup
  • Exercise 3: Unique ID Manager
  • Exercise 4: Remove by Value
  • Exercise 5: Update Grades
  • Exercise 6: Dictionary Iteration
  • Exercise 7: Clear and Count
  • Exercise 8: Inventory Check
  • Exercise 9: Keys and Values Lists
  • Exercise 10: Case-Insensitive Search
  • Exercise 11: Invert a Dictionary
  • Exercise 12: Filter by Criteria
  • Exercise 13: Highest and Lowest
  • Exercise 14: Average Value Calculator
  • Exercise 15: Group by Length
  • Exercise 16: Dictionary Merging
  • Exercise 17: Transform Values
  • Exercise 18: Sort by Value
  • Exercise 19: Lookup Conversion
  • Exercise 20: Dictionary to JSON
  • Exercise 21: Nested Dictionaries (Multi-Key)
  • Exercise 22: Custom Equality Comparer
  • Exercise 23: Thread-Safe Dictionary Cache
  • Exercise 24: LRU (Least Recently Used) Cache
  • Exercise 25: Graph Representation (Adjacency List)
  • Exercise 26: State Machine
  • Exercise 27: Frequency Tracker with Priority
  • Exercise 28: Undo/Redo System Command History
  • Exercise 29: Sparse Matrix Representation
  • Exercise 30: Dependency Injection Container

Exercise 1: Word Counter

Practice Problem: Take a string of text and count the frequency of each word using a dictionary.

Purpose: This exercise helps you practice using a dictionary as a frequency counter, checking whether a key already exists with ContainsKey(), and updating or initializing a count depending on that check.

Given Input: string text = "the quick brown fox the lazy dog the fox";

Expected Output:

the: 3
quick: 1
brown: 1
fox: 2
lazy: 1
dog: 1
▼ Hint
  • Split the input text on spaces to get an array of words.
  • Loop through the words, and for each one check ContainsKey(word) on the dictionary.
  • If the word already exists, increment its count; otherwise add it with a starting count of 1.
  • Loop through the finished dictionary to print each word and its count.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public static class WordCounter
{
    public static Dictionary<string, int> CountWords(string text)
    {
        Dictionary<string, int> counts = new Dictionary<string, int>();
        string[] words = text.Split(' ');

        foreach (string word in words)
        {
            if (counts.ContainsKey(word))
                counts[word]++;
            else
                counts[word] = 1;
        }

        return counts;
    }
}

class Program
{
    static void Main()
    {
        string text = "the quick brown fox the lazy dog the fox";
        Dictionary<string, int> counts = WordCounter.CountWords(text);

        foreach (KeyValuePair<string, int> pair in counts)
        {
            Console.WriteLine(pair.Key + ": " + pair.Value);
        }
    }
}Code language: C# (cs)

Explanation:

  • text.Split(' '): Breaks the sentence into individual words wherever a space appears.
  • counts.ContainsKey(word): Checks whether this word has already been seen before deciding how to update the count.
  • counts[word]++: Increments the existing count for a word that has already appeared at least once.
  • counts[word] = 1: Adds a brand new entry to the dictionary the first time a word is encountered.

Exercise 2: Phonebook Lookup

Practice Problem: Create a simple phonebook where you add names (keys) and phone numbers (values), then search for a specific name.

Purpose: This exercise helps you practice the most basic dictionary workflow: adding key-value pairs, then safely looking a key up with TryGetValue() instead of risking an exception on a missing key.

Given Input: phonebook.Add("Alice", "555-1234"); phonebook.Add("Bob", "555-5678");, then search for "Bob".

Expected Output: Bob's number is 555-5678

▼ Hint
  • Use a Dictionary<string, string> with names as keys and phone numbers as values.
  • Use Add(name, number) to populate the phonebook.
  • Use TryGetValue(name, out string number) to search, so a missing name doesn’t throw an exception.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public static class Phonebook
{
    public static string Lookup(Dictionary<string, string> phonebook, string name)
    {
        if (phonebook.TryGetValue(name, out string number))
            return name + "'s number is " + number;

        return name + " was not found in the phonebook.";
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, string> phonebook = new Dictionary<string, string>();
        phonebook.Add("Alice", "555-1234");
        phonebook.Add("Bob", "555-5678");

        Console.WriteLine(Phonebook.Lookup(phonebook, "Bob"));
    }
}Code language: C# (cs)

Explanation:

  • phonebook.Add("Alice", "555-1234"): Inserts a new key-value pair into the dictionary.
  • phonebook.TryGetValue(name, out string number): Attempts to find the name and, if found, places the matching number into number while returning true.
  • return name + " was not found...": Handles the case where TryGetValue returns false, avoiding a KeyNotFoundException.

Exercise 3: Unique ID Manager

Practice Problem: Create a program that stores employee IDs and names. Ensure that trying to add a duplicate ID throws a friendly warning using ContainsKey().

Purpose: This exercise helps you practice guarding against duplicate keys before calling Add(), since Add() throws an exception on a duplicate key while a manual check lets you handle it gracefully instead.

Given Input: AddEmployee(employees, 1001, "Alice"); AddEmployee(employees, 1002, "Bob"); AddEmployee(employees, 1001, "Charlie");

Expected Output:

Added employee 1001: Alice
Added employee 1002: Bob
Warning: Employee ID 1001 already exists.
▼ Hint
  • Before adding, check employees.ContainsKey(id).
  • If the ID already exists, print a warning message and return without adding.
  • If the ID is new, call Add(id, name) and confirm with a message.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public static class EmployeeManager
{
    public static void AddEmployee(Dictionary<int, string> employees, int id, string name)
    {
        if (employees.ContainsKey(id))
        {
            Console.WriteLine("Warning: Employee ID " + id + " already exists.");
            return;
        }

        employees.Add(id, name);
        Console.WriteLine("Added employee " + id + ": " + name);
    }
}

class Program
{
    static void Main()
    {
        Dictionary<int, string> employees = new Dictionary<int, string>();

        EmployeeManager.AddEmployee(employees, 1001, "Alice");
        EmployeeManager.AddEmployee(employees, 1002, "Bob");
        EmployeeManager.AddEmployee(employees, 1001, "Charlie");
    }
}Code language: C# (cs)

Explanation:

  • employees.ContainsKey(id): Checks for an existing entry before attempting to add a new one.
  • Console.WriteLine("Warning: ...") then return: Reports the conflict and exits the method early, leaving the original entry untouched.
  • employees.Add(id, name): Only runs when the ID is confirmed to be new, so it never throws a duplicate-key exception.

Exercise 4: Remove by Value

Practice Problem: Create a dictionary of products and prices. Write a method to remove all products that cost less than $5.00.

Purpose: This exercise helps you practice filtering a dictionary by its values, and shows why keys marked for removal must be collected first, since modifying a dictionary while iterating over it directly throws an exception.

Given Input: products = { "Gum": 1.50, "Soda": 2.00, "Sandwich": 6.50, "Chips": 3.25, "Coffee": 5.00 }

Expected Output:

Sandwich: 6.5
Coffee: 5
▼ Hint
  • Loop through the dictionary and collect the keys of entries below the threshold into a separate list.
  • Do not call Remove() while iterating over the dictionary directly, this throws an InvalidOperationException.
  • After the first loop finishes, loop through the collected keys and call Remove(key) for each one.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public static class InventoryHelper
{
    public static void RemoveCheapItems(Dictionary<string, double> products, double threshold)
    {
        List<string> toRemove = new List<string>();

        foreach (KeyValuePair<string, double> pair in products)
        {
            if (pair.Value < threshold)
                toRemove.Add(pair.Key);
        }

        foreach (string key in toRemove)
        {
            products.Remove(key);
        }
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, double> products = new Dictionary<string, double>
        {
            { "Gum", 1.50 },
            { "Soda", 2.00 },
            { "Sandwich", 6.50 },
            { "Chips", 3.25 },
            { "Coffee", 5.00 }
        };

        InventoryHelper.RemoveCheapItems(products, 5.00);

        foreach (KeyValuePair<string, double> pair in products)
        {
            Console.WriteLine(pair.Key + ": " + pair.Value);
        }
    }
}Code language: C# (cs)

Explanation:

  • List<string> toRemove: Temporarily holds the keys that should be deleted, since the dictionary itself cannot be safely modified mid-iteration.
  • if (pair.Value < threshold): Flags any product priced below the cutoff for removal.
  • products.Remove(key): Runs in a second, separate loop, only after the first loop has finished reading the dictionary.

Exercise 5: Update Grades

Practice Problem: Given a dictionary of student names and their letter grades, write a program that updates a specific student’s grade, or adds them if they don’t exist, using TryGetValue().

Purpose: This exercise helps you practice the common “update or insert” pattern, using TryGetValue() to branch between updating an existing entry and adding a brand new one.

Given Input: grades.Add("Alice", "B"); grades.Add("Bob", "C"); UpdateGrade(grades, "Alice", "A"); UpdateGrade(grades, "Charlie", "B");

Expected Output:

Updated Alice's grade to A.
Added Charlie with grade B.
▼ Hint
  • Call grades.TryGetValue(student, out string currentGrade) first.
  • If it returns true, overwrite the value with grades[student] = newGrade.
  • If it returns false, call Add(student, newGrade) instead.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public static class GradeBook
{
    public static void UpdateGrade(Dictionary<string, string> grades, string student, string newGrade)
    {
        if (grades.TryGetValue(student, out string currentGrade))
        {
            grades[student] = newGrade;
            Console.WriteLine("Updated " + student + "'s grade to " + newGrade + ".");
        }
        else
        {
            grades.Add(student, newGrade);
            Console.WriteLine("Added " + student + " with grade " + newGrade + ".");
        }
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, string> grades = new Dictionary<string, string>();
        grades.Add("Alice", "B");
        grades.Add("Bob", "C");

        GradeBook.UpdateGrade(grades, "Alice", "A");
        GradeBook.UpdateGrade(grades, "Charlie", "B");
    }
}Code language: C# (cs)

Explanation:

  • grades.TryGetValue(student, out string currentGrade): Checks for an existing entry without risking an exception if the student is not yet in the dictionary.
  • grades[student] = newGrade: Overwrites the value for an existing key using the indexer.
  • grades.Add(student, newGrade): Runs only in the else branch, inserting a brand new student who was not previously tracked.

Exercise 6: Dictionary Iteration

Practice Problem: Create a dictionary of city names and their populations. Print them all out in the format: "City: [Name], Population: [Count]".

Purpose: This exercise helps you practice iterating over a dictionary with foreach using KeyValuePair<TKey, TValue>, and formatting each pair’s key and value into a readable string.

Given Input: cities.Add("Tokyo", 37400000); cities.Add("Delhi", 30300000); cities.Add("Shanghai", 27058000);

Expected Output:

City: Tokyo, Population: 37400000
City: Delhi, Population: 30300000
City: Shanghai, Population: 27058000
▼ Hint
  • Use a foreach (KeyValuePair<string, int> city in cities) loop.
  • Inside the loop, access city.Key and city.Value separately.
  • Build the output string in the exact requested format for each entry.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<string, int> cities = new Dictionary<string, int>();
        cities.Add("Tokyo", 37400000);
        cities.Add("Delhi", 30300000);
        cities.Add("Shanghai", 27058000);

        foreach (KeyValuePair<string, int> city in cities)
        {
            Console.WriteLine("City: " + city.Key + ", Population: " + city.Value);
        }
    }
}Code language: C# (cs)

Explanation:

  • foreach (KeyValuePair<string, int> city in cities): Iterates over every entry in the dictionary, exposing both the key and value together as one pair.
  • city.Key: Refers to the city name for the current entry.
  • city.Value: Refers to the population number for the current entry.

Exercise 7: Clear and Count

Practice Problem: Write a program that fills a dictionary with temporary configuration settings, prints the count, clears the dictionary, and verifies that the count is now 0.

Purpose: This exercise helps you practice the Count property and the Clear() method, useful whenever you need to reset a dictionary’s state without creating a brand new instance.

Given Input: settings.Add("Theme", "Dark"); settings.Add("Timeout", "30"); settings.Add("Language", "en-US");

Expected Output:

Count before clear: 3
Count after clear: 0
▼ Hint

Use the dictionary’s Count property before and after calling Clear() to compare the two values.

▼ Solution & Explanation
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<string, string> settings = new Dictionary<string, string>();
        settings.Add("Theme", "Dark");
        settings.Add("Timeout", "30");
        settings.Add("Language", "en-US");

        Console.WriteLine("Count before clear: " + settings.Count);

        settings.Clear();

        Console.WriteLine("Count after clear: " + settings.Count);
    }
}Code language: C# (cs)

Explanation:

  • settings.Count: Reports how many key-value pairs are currently stored in the dictionary.
  • settings.Clear(): Removes every entry from the dictionary in one call, without replacing the dictionary instance itself.
  • Second Console.WriteLine: Confirms the dictionary is now empty by reading Count again after clearing.

Exercise 8: Inventory Check

Practice Problem: Create a dictionary representing store inventory (Item -> Quantity). Check if a specific item is in stock and has a quantity greater than 10.

Purpose: This exercise helps you practice combining a TryGetValue() lookup with an additional condition on the retrieved value, a common pattern when a simple key check is not enough.

Given Input: inventory.Add("Widget", 25); inventory.Add("Gadget", 4);, then check "Widget", "Gadget", and "Gizmo".

Expected Output:

Widget is in stock with sufficient quantity (25).
Gadget is in stock but low (4).
Gizmo is not in the inventory.
▼ Hint
  • Use TryGetValue(item, out int quantity) to see if the item exists at all.
  • If it exists, compare quantity against 10 to decide which message to print.
  • If it does not exist, print a separate “not in inventory” message.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public static class InventoryChecker
{
    public static void CheckStock(Dictionary<string, int> inventory, string item)
    {
        if (inventory.TryGetValue(item, out int quantity))
        {
            if (quantity > 10)
                Console.WriteLine(item + " is in stock with sufficient quantity (" + quantity + ").");
            else
                Console.WriteLine(item + " is in stock but low (" + quantity + ").");
        }
        else
        {
            Console.WriteLine(item + " is not in the inventory.");
        }
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, int> inventory = new Dictionary<string, int>();
        inventory.Add("Widget", 25);
        inventory.Add("Gadget", 4);

        InventoryChecker.CheckStock(inventory, "Widget");
        InventoryChecker.CheckStock(inventory, "Gadget");
        InventoryChecker.CheckStock(inventory, "Gizmo");
    }
}Code language: C# (cs)

Explanation:

  • inventory.TryGetValue(item, out int quantity): Checks for the item and, if present, retrieves its quantity in the same step.
  • quantity > 10: Applies the extra business rule on top of the existence check, distinguishing “in stock” from “in stock but low”.
  • else branch: Handles an item that was never added to the dictionary at all.

Exercise 9: Keys and Values Lists

Practice Problem: Extract all the keys of a dictionary into a List<string> and all the values into a List<int> without using a foreach loop.

Purpose: This exercise helps you practice the Keys and Values properties of a dictionary, and shows how to build a List<T> directly from them using a constructor instead of manually looping.

Given Input: scores = { "Alice": 90, "Bob": 85, "Charlie": 78 }

Expected Output:

Keys: Alice, Bob, Charlie
Values: 90, 85, 78
▼ Hint
  • A dictionary exposes its keys through the Keys property and its values through the Values property.
  • Pass dictionary.Keys directly into the List<string> constructor instead of looping manually.
  • Do the same for dictionary.Values with a List<int>.
  • Use string.Join(", ", list) to print each list on one line.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<string, int> scores = new Dictionary<string, int>
        {
            { "Alice", 90 },
            { "Bob", 85 },
            { "Charlie", 78 }
        };

        List<string> keys = new List<string>(scores.Keys);
        List<int> values = new List<int>(scores.Values);

        Console.WriteLine("Keys: " + string.Join(", ", keys));
        Console.WriteLine("Values: " + string.Join(", ", values));
    }
}Code language: C# (cs)

Explanation:

  • new List<string>(scores.Keys): Builds a new list directly from the dictionary’s key collection, with no explicit loop written.
  • new List<int>(scores.Values): Does the same thing for the values, producing an independent list of numbers.
  • string.Join(", ", keys): Combines every element of the list into a single comma-separated string for display.

Exercise 10: Case-Insensitive Search

Practice Problem: Create a dictionary where the keys are usernames. Configure it so that looking up "john_doe", "John_Doe", or "JOHN_DOE" all return the same user data.

Purpose: This exercise helps you practice supplying a custom IEqualityComparer<string> to a dictionary’s constructor, letting you change how keys are compared without changing any of the lookup code.

Given Input: users.Add("john_doe", "John Doe - Admin");, then look up "john_doe", "John_Doe", and "JOHN_DOE".

Expected Output:

john_doe -> John Doe - Admin
John_Doe -> John Doe - Admin
JOHN_DOE -> John Doe - Admin
▼ Hint
  • Pass StringComparer.OrdinalIgnoreCase into the Dictionary<string, string> constructor.
  • Add the username once, in whatever casing you like.
  • Look the same user up using different casings to confirm they all resolve to the same entry.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        Dictionary<string, string> users = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        users.Add("john_doe", "John Doe - Admin");

        Console.WriteLine("john_doe -> " + users["john_doe"]);
        Console.WriteLine("John_Doe -> " + users["John_Doe"]);
        Console.WriteLine("JOHN_DOE -> " + users["JOHN_DOE"]);
    }
}Code language: C# (cs)

Explanation:

  • new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase): Tells the dictionary to compare keys ignoring case, instead of the default case-sensitive comparison.
  • users.Add("john_doe", ...): Stores the entry once, under a single canonical casing.
  • users["John_Doe"] and users["JOHN_DOE"]: Both resolve to the same stored entry, since the comparer treats all three casings as equal keys.

Exercise 11: Invert a Dictionary

Practice Problem: Take a dictionary of English words and their Spanish translations, and swap the keys and values, assuming all translations are unique.

Purpose: This exercise helps you practice using LINQ’s ToDictionary() to rebuild a dictionary with the keys and values reversed, and highlights why the original values must be unique for this to work safely.

Given Input: translations = { "hello": "hola", "goodbye": "adios", "please": "por favor" }

Expected Output:

hola -> hello
adios -> goodbye
por favor -> please
▼ Hint
  • Use LINQ’s ToDictionary() on the source dictionary.
  • Supply pair => pair.Value as the new key selector.
  • Supply pair => pair.Key as the new value selector.
  • Remember this only works cleanly if the original values contain no duplicates.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

public static class DictionaryInverter
{
    public static Dictionary<string, string> Invert(Dictionary<string, string> source)
    {
        return source.ToDictionary(pair => pair.Value, pair => pair.Key);
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, string> translations = new Dictionary<string, string>
        {
            { "hello", "hola" },
            { "goodbye", "adios" },
            { "please", "por favor" }
        };

        Dictionary<string, string> inverted = DictionaryInverter.Invert(translations);

        foreach (KeyValuePair<string, string> pair in inverted)
        {
            Console.WriteLine(pair.Key + " -> " + pair.Value);
        }
    }
}Code language: C# (cs)

Explanation:

  • source.ToDictionary(pair => pair.Value, pair => pair.Key): Builds a brand new dictionary, using the old values as new keys and the old keys as new values.
  • First lambda, pair => pair.Value: Selects what becomes the key in the resulting dictionary.
  • Second lambda, pair => pair.Key: Selects what becomes the value in the resulting dictionary.

Exercise 12: Filter by Criteria

Practice Problem: Given a dictionary of car models and their top speeds, use LINQ to create a new dictionary containing only cars that can go over 150 mph.

Purpose: This exercise helps you practice chaining Where() and ToDictionary() together to filter a dictionary by its values while producing a brand new dictionary rather than mutating the original.

Given Input: cars = { "Civic": 124, "Corvette": 184, "Veyron": 254, "Model 3": 162, "Camry": 135 }

Expected Output:

Corvette: 184
Veyron: 254
Model 3: 162
▼ Hint
  • Call Where(pair => pair.Value > 150) on the dictionary first.
  • Chain ToDictionary(pair => pair.Key, pair => pair.Value) onto the filtered result.
  • Remember that Where() alone returns a sequence of pairs, not a dictionary, so it needs to be converted back.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

public static class CarFilter
{
    public static Dictionary<string, int> FilterFast(Dictionary<string, int> cars, int minSpeed)
    {
        return cars.Where(pair => pair.Value > minSpeed)
                    .ToDictionary(pair => pair.Key, pair => pair.Value);
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, int> cars = new Dictionary<string, int>
        {
            { "Civic", 124 },
            { "Corvette", 184 },
            { "Veyron", 254 },
            { "Model 3", 162 },
            { "Camry", 135 }
        };

        Dictionary<string, int> fastCars = CarFilter.FilterFast(cars, 150);

        foreach (KeyValuePair<string, int> pair in fastCars)
        {
            Console.WriteLine(pair.Key + ": " + pair.Value);
        }
    }
}Code language: C# (cs)

Explanation:

  • cars.Where(pair => pair.Value > minSpeed): Filters the dictionary down to only the pairs whose speed exceeds the threshold.
  • .ToDictionary(pair => pair.Key, pair => pair.Value): Converts the filtered sequence of pairs back into an actual dictionary.
  • Original cars dictionary: Remains completely unchanged, since a new dictionary is returned rather than modifying it in place.

Exercise 13: Highest and Lowest

Practice Problem: Find the key with the highest value and the key with the lowest value in a dictionary of stock names and current prices.

Purpose: This exercise helps you practice combining OrderBy() or OrderByDescending() with First() to pull out the single extreme entry from a dictionary, rather than manually looping and tracking a running maximum.

Given Input: stocks = { "AAPL": 190.50, "GOOG": 140.25, "AMZN": 178.90, "TSLA": 250.75 }

Expected Output:

Highest: TSLA at 250.75
Lowest: GOOG at 140.25
▼ Hint
  • Call OrderByDescending(pair => pair.Value).First() to get the pair with the highest price.
  • Call OrderBy(pair => pair.Value).First() to get the pair with the lowest price.
  • Each result is a full KeyValuePair, so you can read both .Key and .Value from it directly.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        Dictionary<string, double> stocks = new Dictionary<string, double>
        {
            { "AAPL", 190.50 },
            { "GOOG", 140.25 },
            { "AMZN", 178.90 },
            { "TSLA", 250.75 }
        };

        KeyValuePair<string, double> highest = stocks.OrderByDescending(pair => pair.Value).First();
        KeyValuePair<string, double> lowest = stocks.OrderBy(pair => pair.Value).First();

        Console.WriteLine("Highest: " + highest.Key + " at " + highest.Value);
        Console.WriteLine("Lowest: " + lowest.Key + " at " + lowest.Value);
    }
}Code language: C# (cs)

Explanation:

  • OrderByDescending(pair => pair.Value): Sorts the pairs from highest price to lowest.
  • .First(): Grabs just the top entry from the sorted sequence, avoiding the need to materialize the whole sorted list.
  • OrderBy(pair => pair.Value).First(): Mirrors the same idea in ascending order to find the lowest-priced stock.

Exercise 14: Average Value Calculator

Practice Problem: Given a dictionary of monthly expenses (Month to Amount), calculate the average monthly expenditure using LINQ.

Purpose: This exercise helps you practice using LINQ’s Average() directly on a dictionary’s Values collection, instead of manually summing and dividing by the count.

Given Input: expenses = { "Jan": 1200.00, "Feb": 950.50, "Mar": 1100.75, "Apr": 1300.25 }

Expected Output: Average expenditure = 1137.875

▼ Hint

Access the dictionary’s Values property and call .Average() directly on it, since Average() works on any sequence of numbers.

▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        Dictionary<string, double> expenses = new Dictionary<string, double>
        {
            { "Jan", 1200.00 },
            { "Feb", 950.50 },
            { "Mar", 1100.75 },
            { "Apr", 1300.25 }
        };

        double average = expenses.Values.Average();

        Console.WriteLine("Average expenditure = " + average);
    }
}Code language: C# (cs)

Explanation:

  • expenses.Values: Exposes just the numeric amounts from the dictionary, without the month names attached.
  • .Average(): Sums every value in the sequence and divides by the count in a single call.

Exercise 15: Group by Length

Practice Problem: Take a list of strings and group them into a Dictionary<int, List<string>> where the key is the length of the words and the value is a list of words of that length.

Purpose: This exercise helps you practice LINQ’s GroupBy(), which clusters items by a computed key, followed by ToDictionary() to turn those groups into an actual dictionary of lists.

Given Input: words = new List<string> { "cat", "dog", "fish", "ant", "lion", "owl" };

Expected Output:

Length 3: cat, dog, ant, owl
Length 4: fish, lion
▼ Hint
  • Call GroupBy(word => word.Length) on the list of words.
  • Each resulting group has a Key (the length) and can be enumerated like a list of matching words.
  • Chain ToDictionary(group => group.Key, group => group.ToList()) to convert the groups into the final dictionary.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

public static class WordGrouper
{
    public static Dictionary<int, List<string>> GroupByLength(List<string> words)
    {
        return words.GroupBy(word => word.Length)
                     .ToDictionary(group => group.Key, group => group.ToList());
    }
}

class Program
{
    static void Main()
    {
        List<string> words = new List<string> { "cat", "dog", "fish", "ant", "lion", "owl" };
        Dictionary<int, List<string>> grouped = WordGrouper.GroupByLength(words);

        foreach (KeyValuePair<int, List<string>> pair in grouped)
        {
            Console.WriteLine("Length " + pair.Key + ": " + string.Join(", ", pair.Value));
        }
    }
}Code language: C# (cs)

Explanation:

  • GroupBy(word => word.Length): Clusters every word into a group keyed by its character length.
  • group.Key: Refers to the shared length value for all words inside that particular group.
  • group.ToList(): Converts each group’s contents into a concrete List<string>, which becomes the dictionary’s value.

Exercise 16: Dictionary Merging

Practice Problem: Merge two dictionaries of store inventories. If an item exists in both, sum their quantities.

Purpose: This exercise helps you practice combining two dictionaries into one while resolving key conflicts, a common task when consolidating data from multiple sources.

Given Input: storeA = { "Apples": 10, "Bananas": 5, "Oranges": 8 } and storeB = { "Bananas": 7, "Oranges": 2, "Grapes": 12 }

Expected Output:

Apples: 10
Bananas: 12
Oranges: 10
Grapes: 12
▼ Hint
  • Start by copying the first dictionary into a new one using its constructor, so the original is left untouched.
  • Loop through the second dictionary’s entries.
  • If the key already exists in the merged dictionary, add the second dictionary’s quantity to it.
  • If the key does not exist yet, add it as a brand new entry.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public static class InventoryMerger
{
    public static Dictionary<string, int> Merge(Dictionary<string, int> storeA, Dictionary<string, int> storeB)
    {
        Dictionary<string, int> merged = new Dictionary<string, int>(storeA);

        foreach (KeyValuePair<string, int> pair in storeB)
        {
            if (merged.ContainsKey(pair.Key))
                merged[pair.Key] += pair.Value;
            else
                merged.Add(pair.Key, pair.Value);
        }

        return merged;
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, int> storeA = new Dictionary<string, int>
        {
            { "Apples", 10 },
            { "Bananas", 5 },
            { "Oranges", 8 }
        };

        Dictionary<string, int> storeB = new Dictionary<string, int>
        {
            { "Bananas", 7 },
            { "Oranges", 2 },
            { "Grapes", 12 }
        };

        Dictionary<string, int> merged = InventoryMerger.Merge(storeA, storeB);

        foreach (KeyValuePair<string, int> pair in merged)
        {
            Console.WriteLine(pair.Key + ": " + pair.Value);
        }
    }
}Code language: C# (cs)

Explanation:

  • new Dictionary<string, int>(storeA): Creates an independent copy of the first store, so merging never mutates the original dictionary passed in.
  • merged.ContainsKey(pair.Key): Checks whether an item from the second store already exists in the merged result.
  • merged[pair.Key] += pair.Value: Adds the second store’s quantity on top of the existing quantity when the item is shared between both stores.
  • merged.Add(pair.Key, pair.Value): Inserts items that only existed in the second store as brand new entries.

Exercise 17: Transform Values

Practice Problem: Given a dictionary of item prices, use LINQ to create a new dictionary where all prices have a 10% sales tax applied.

Purpose: This exercise helps you practice transforming a dictionary’s values while keeping its keys the same, using ToDictionary() with a computed value selector.

Given Input: prices = { "Book": 15.00, "Pen": 2.50, "Notebook": 4.75 }

Expected Output:

Book: 16.5
Pen: 2.75
Notebook: 5.225
▼ Hint
  • Call ToDictionary(pair => pair.Key, pair => pair.Value * (1 + taxRate)) on the original dictionary.
  • Keep the key selector unchanged, since only the prices need to be transformed.
  • Pass the tax rate as a decimal, such as 0.10 for 10 percent.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

public static class PriceTransformer
{
    public static Dictionary<string, double> ApplyTax(Dictionary<string, double> prices, double taxRate)
    {
        return prices.ToDictionary(pair => pair.Key, pair => pair.Value * (1 + taxRate));
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, double> prices = new Dictionary<string, double>
        {
            { "Book", 15.00 },
            { "Pen", 2.50 },
            { "Notebook", 4.75 }
        };

        Dictionary<string, double> taxedPrices = PriceTransformer.ApplyTax(prices, 0.10);

        foreach (KeyValuePair<string, double> pair in taxedPrices)
        {
            Console.WriteLine(pair.Key + ": " + pair.Value);
        }
    }
}Code language: C# (cs)

Explanation:

  • pair => pair.Key: Leaves the item name untouched in the resulting dictionary.
  • pair.Value * (1 + taxRate): Computes the new price by adding the tax percentage on top of the original price.
  • Original prices dictionary: Stays untouched, since ToDictionary() always produces a separate result.

Exercise 18: Sort by Value

Practice Problem: Take a dictionary of video game titles and their review scores, and sort it in descending order based on the scores.

Purpose: This exercise helps you practice sorting a dictionary’s entries with OrderByDescending(), and shows that a dictionary itself has no inherent order, so sorting always produces a separate ordered sequence.

Given Input: games = { "Game A": 85, "Game B": 92, "Game C": 78, "Game D": 95 }

Expected Output:

Game D: 95
Game B: 92
Game A: 85
Game C: 78
▼ Hint
  • Call OrderByDescending(pair => pair.Value) on the dictionary.
  • The result is an ordered sequence of pairs, not a dictionary, since dictionaries themselves don’t preserve a sort order.
  • Loop through that ordered sequence directly to print the results in order.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        Dictionary<string, int> games = new Dictionary<string, int>
        {
            { "Game A", 85 },
            { "Game B", 92 },
            { "Game C", 78 },
            { "Game D", 95 }
        };

        IOrderedEnumerable<KeyValuePair<string, int>> sorted = games.OrderByDescending(pair => pair.Value);

        foreach (KeyValuePair<string, int> pair in sorted)
        {
            Console.WriteLine(pair.Key + ": " + pair.Value);
        }
    }
}Code language: C# (cs)

Explanation:

  • OrderByDescending(pair => pair.Value): Produces a new sequence of the same pairs, arranged from the highest score to the lowest.
  • IOrderedEnumerable<KeyValuePair<string, int>>: The specific return type of an ordering operation, which still supports foreach like any other sequence.
  • Original games dictionary: Remains in whatever order it always had internally; only the separate sorted sequence reflects the new order.

Exercise 19: Lookup Conversion

Practice Problem: Convert a standard List<Employee>, where Employee has a Department property, into a lookup or dictionary grouped by department using LINQ’s .ToDictionary() or .GroupBy().

Purpose: This exercise helps you practice turning a flat list of objects into a dictionary of grouped lists, a very common step when preparing data for reporting or display by category.

Given Input: A list of five employees across the Engineering, Sales, and Marketing departments.

Expected Output:

Engineering: Alice, Charlie
Sales: Bob, Eve
Marketing: Diana
▼ Hint
  • Call GroupBy(emp => emp.Department) on the list of employees first.
  • Chain ToDictionary(group => group.Key, group => group.ToList()) to turn the groups into a dictionary of lists.
  • Use .Select(emp => emp.Name) when printing, to pull just the names out of each group’s employee list.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

public class Employee
{
    public string Name { get; set; }
    public string Department { get; set; }
}

class Program
{
    static void Main()
    {
        List<Employee> employees = new List<Employee>
        {
            new Employee { Name = "Alice", Department = "Engineering" },
            new Employee { Name = "Bob", Department = "Sales" },
            new Employee { Name = "Charlie", Department = "Engineering" },
            new Employee { Name = "Diana", Department = "Marketing" },
            new Employee { Name = "Eve", Department = "Sales" }
        };

        Dictionary<string, List<Employee>> byDepartment = employees
            .GroupBy(emp => emp.Department)
            .ToDictionary(group => group.Key, group => group.ToList());

        foreach (KeyValuePair<string, List<Employee>> pair in byDepartment)
        {
            List<string> names = pair.Value.Select(emp => emp.Name).ToList();
            Console.WriteLine(pair.Key + ": " + string.Join(", ", names));
        }
    }
}Code language: C# (cs)

Explanation:

  • GroupBy(emp => emp.Department): Clusters every employee into a group keyed by their department name.
  • ToDictionary(group => group.Key, group => group.ToList()): Converts each department group into a dictionary entry whose value is a list of the employees in it.
  • pair.Value.Select(emp => emp.Name): Pulls just the employee names out of each department’s list for display.

Exercise 20: Dictionary to JSON

Practice Problem: Manually convert a basic Dictionary<string, string> into a valid JSON string format, without using an external library like Json.NET.

Purpose: This exercise helps you practice building a formatted string by hand with a StringBuilder, and reinforces exactly what a simple JSON object literal looks like under the hood.

Given Input: data = { "name": "Alice", "city": "Paris" }

Expected Output: {"name":"Alice","city":"Paris"}

▼ Hint
  • Use a StringBuilder and start by appending an opening curly brace.
  • Loop through the dictionary, appending each pair as "key":"value".
  • Track whether you are on the first pair so you only add a comma separator before every pair after the first.
  • Finish by appending a closing curly brace.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Text;

public static class JsonConverter
{
    public static string ToJson(Dictionary<string, string> data)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("{");

        bool first = true;
        foreach (KeyValuePair<string, string> pair in data)
        {
            if (!first)
                sb.Append(",");

            sb.Append("\"" + pair.Key + "\":\"" + pair.Value + "\"");
            first = false;
        }

        sb.Append("}");
        return sb.ToString();
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, string> data = new Dictionary<string, string>
        {
            { "name", "Alice" },
            { "city", "Paris" }
        };

        Console.WriteLine(JsonConverter.ToJson(data));
    }
}Code language: C# (cs)

Explanation:

  • sb.Append("{"): Starts the JSON object with its required opening brace.
  • bool first: Tracks whether a comma separator is needed before the current pair, so there’s no trailing or leading comma.
  • "\"" + pair.Key + "\":\"" + pair.Value + "\"": Wraps both the key and the value in escaped double quotes, matching valid JSON string syntax.
  • sb.Append("}"): Closes the JSON object once every pair has been appended.

Exercise 21: Nested Dictionaries (Multi-Key)

Practice Problem: Create a nested dictionary structure to represent a university system: Dictionary<string, Dictionary<string, List<int>>> (Department to Course ID to List of Student Grades). Write a method to calculate the average grade for a specific course.

Purpose: This exercise helps you practice working with dictionaries nested several levels deep, and shows how to safely drill down through multiple TryGetValue() calls instead of risking an exception at any level.

Given Input: A university dictionary with a "ComputerScience" department containing course "CS101" with grades { 85, 90, 78, 92 }.

Expected Output: Average grade for CS101 = 86.25

▼ Hint
  • First call TryGetValue(department, ...) on the outer dictionary to get the inner course dictionary.
  • Then call TryGetValue(courseId, ...) on that inner dictionary to get the list of grades.
  • Chain both lookups together with && so the method only proceeds if both keys exist.
  • Use LINQ’s Average() on the resulting list of grades.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

public static class UniversityHelper
{
    public static double GetAverageGrade(Dictionary<string, Dictionary<string, List<int>>> university, string department, string courseId)
    {
        if (university.TryGetValue(department, out Dictionary<string, List<int>> courses) &&
            courses.TryGetValue(courseId, out List<int> grades) &&
            grades.Count > 0)
        {
            return grades.Average();
        }

        return 0;
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, Dictionary<string, List<int>>> university = new Dictionary<string, Dictionary<string, List<int>>>
        {
            {
                "ComputerScience", new Dictionary<string, List<int>>
                {
                    { "CS101", new List<int> { 85, 90, 78, 92 } },
                    { "CS201", new List<int> { 70, 88 } }
                }
            },
            {
                "Mathematics", new Dictionary<string, List<int>>
                {
                    { "MATH101", new List<int> { 95, 100, 89 } }
                }
            }
        };

        double average = UniversityHelper.GetAverageGrade(university, "ComputerScience", "CS101");
        Console.WriteLine("Average grade for CS101 = " + average);
    }
}Code language: C# (cs)

Explanation:

  • Dictionary<string, Dictionary<string, List<int>>>: Models a three-level hierarchy, department to course to grades, entirely with generic collections.
  • university.TryGetValue(department, ...) && courses.TryGetValue(courseId, ...): Drills down safely through both levels, short-circuiting if either the department or the course does not exist.
  • grades.Average(): Computes the mean of the retrieved grade list once both lookups succeed.

Exercise 22: Custom Equality Comparer

Practice Problem: Create a custom class Point with X and Y coordinates. Implement a custom IEqualityComparer<Point> so that a Dictionary<Point, string> treats points with the same coordinates as identical keys.

Purpose: This exercise helps you practice supplying a custom IEqualityComparer<T> to a dictionary, since without one, two separate Point objects with identical coordinates would be treated as different keys based on reference identity.

Given Input: labels.Add(new Point(1, 2), "Origin marker");, then check labels.ContainsKey(new Point(1, 2)) using a brand new Point instance.

Expected Output: Contains (1,2)? True

▼ Hint
  • Implement IEqualityComparer<Point> with an Equals(Point a, Point b) method that compares X and Y on both objects.
  • Implement GetHashCode(Point point) so that points with equal coordinates always produce the same hash code.
  • Pass an instance of your comparer into the Dictionary<Point, string> constructor.
  • Without the custom comparer, two different Point objects with the same coordinates would never be considered equal keys.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

public class PointComparer : IEqualityComparer<Point>
{
    public bool Equals(Point a, Point b)
    {
        if (a == null || b == null)
            return false;

        return a.X == b.X && a.Y == b.Y;
    }

    public int GetHashCode(Point point)
    {
        return HashCode.Combine(point.X, point.Y);
    }
}

class Program
{
    static void Main()
    {
        Dictionary<Point, string> labels = new Dictionary<Point, string>(new PointComparer());
        labels.Add(new Point(1, 2), "Origin marker");

        bool found = labels.ContainsKey(new Point(1, 2));
        Console.WriteLine("Contains (1,2)? " + found);
    }
}Code language: C# (cs)

Explanation:

  • Equals(Point a, Point b): Defines coordinate equality explicitly, since Point itself doesn’t override the default reference-based equality.
  • GetHashCode(Point point): Must agree with Equals, so two points considered equal always hash to the same bucket inside the dictionary.
  • new Dictionary<Point, string>(new PointComparer()): Tells the dictionary to use the custom comparer for every key lookup instead of default reference equality.
  • labels.ContainsKey(new Point(1, 2)): Succeeds even though this is a completely different object instance, because the comparer only checks coordinates.

Exercise 23: Thread-Safe Dictionary Cache

Practice Problem: Simulate a multi-threaded environment where multiple threads read and write to a shared cache. Implement ConcurrentDictionary<TKey, TValue> and demonstrate the use of GetOrAdd().

Purpose: This exercise helps you practice using ConcurrentDictionary<TKey, TValue> instead of a plain Dictionary, since a plain dictionary is not safe to read and write from multiple threads at once without external locking.

Given Input: Five parallel tasks each call cache.GetOrAdd(i, key => "Value-" + key) for keys 0 through 4.

Expected Output:

Total cached entries: 5
Value for key 3: Value-3
▼ Hint
  • Use ConcurrentDictionary<int, string> instead of a plain Dictionary<int, string>.
  • Use Parallel.For() to simulate several threads running at the same time.
  • Inside the loop body, call cache.GetOrAdd(key, factory), which atomically adds a value only if the key is not already present.
  • Since threads may finish in any order, only check things that don’t depend on timing, like the final count or a specific key’s value.
▼ Solution & Explanation
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

public static class CacheDemo
{
    public static ConcurrentDictionary<int, string> BuildCache()
    {
        ConcurrentDictionary<int, string> cache = new ConcurrentDictionary<int, string>();

        Parallel.For(0, 5, i =>
        {
            cache.GetOrAdd(i, key => "Value-" + key);
        });

        return cache;
    }
}

class Program
{
    static void Main()
    {
        ConcurrentDictionary<int, string> cache = CacheDemo.BuildCache();

        Console.WriteLine("Total cached entries: " + cache.Count);
        Console.WriteLine("Value for key 3: " + cache[3]);
    }
}Code language: C# (cs)

Explanation:

  • ConcurrentDictionary<int, string>: Handles its own internal locking, so multiple threads can safely read and write without corrupting the collection.
  • Parallel.For(0, 5, i => ...): Runs the loop body across multiple threads at once, simulating concurrent access.
  • cache.GetOrAdd(i, key => "Value-" + key): Atomically checks for the key and adds it if missing, all in one thread-safe operation, avoiding the check-then-add race condition a plain dictionary would have.
  • cache[3]: Reads back a specific entry after all parallel work has finished, which is safe to check regardless of the order threads actually ran in.

Exercise 24: LRU (Least Recently Used) Cache

Practice Problem: Build a simple LRU cache using a combination of a Dictionary, for O(1) lookups, and a LinkedList, to track usage order.

Purpose: This exercise helps you practice combining two different collections to get properties neither one offers alone: a dictionary gives fast lookups but no ordering, while a linked list gives ordering but slow lookups by key.

Given Input: A cache with capacity 2. Put “a”, put “b”, access “a”, then put “c”.

Expected Output:

Contains a? True
Contains b? False
Contains c? True
▼ Hint
  • Store a Dictionary<TKey, LinkedListNode<...>> mapping each key directly to its node in the linked list.
  • Keep the linked list ordered so the front represents the most recently used entry and the back represents the least recently used.
  • On every access or update, remove the node from its current position and re-insert it at the front.
  • When adding a new key would exceed capacity, remove the node at the back of the list, along with its dictionary entry.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public class LRUCache<TKey, TValue>
{
    private readonly int _capacity;
    private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> _map;
    private readonly LinkedList<KeyValuePair<TKey, TValue>> _order;

    public LRUCache(int capacity)
    {
        _capacity = capacity;
        _map = new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
        _order = new LinkedList<KeyValuePair<TKey, TValue>>();
    }

    public void Put(TKey key, TValue value)
    {
        if (_map.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>> existingNode))
        {
            _order.Remove(existingNode);
        }
        else if (_map.Count >= _capacity)
        {
            LinkedListNode<KeyValuePair<TKey, TValue>> oldest = _order.Last;
            _order.RemoveLast();
            _map.Remove(oldest.Value.Key);
        }

        LinkedListNode<KeyValuePair<TKey, TValue>> newNode = new LinkedListNode<KeyValuePair<TKey, TValue>>(new KeyValuePair<TKey, TValue>(key, value));
        _order.AddFirst(newNode);
        _map[key] = newNode;
    }

    public bool TryGet(TKey key, out TValue value)
    {
        if (_map.TryGetValue(key, out LinkedListNode<KeyValuePair<TKey, TValue>> node))
        {
            _order.Remove(node);
            _order.AddFirst(node);
            value = node.Value.Value;
            return true;
        }

        value = default(TValue);
        return false;
    }
}

class Program
{
    static void Main()
    {
        LRUCache<string, int> cache = new LRUCache<string, int>(2);

        cache.Put("a", 1);
        cache.Put("b", 2);
        cache.TryGet("a", out int _);
        cache.Put("c", 3);

        Console.WriteLine("Contains a? " + cache.TryGet("a", out int aValue));
        Console.WriteLine("Contains b? " + cache.TryGet("b", out int bValue));
        Console.WriteLine("Contains c? " + cache.TryGet("c", out int cValue));
    }
}Code language: C# (cs)

Explanation:

  • _map: Gives instant O(1) access to any node by key, avoiding a slow linear scan through the linked list.
  • _order.AddFirst(newNode): Places the most recently touched entry at the front of the list every time it is put or accessed.
  • _order.Last and _order.RemoveLast(): Identify and evict the least recently used entry once the cache is full.
  • TryGet: Not only retrieves a value, but also refreshes its position at the front, marking it as recently used.

Exercise 25: Graph Representation (Adjacency List)

Practice Problem: Represent a social network graph using a Dictionary<string, HashSet<string>> where the key is a person and the value is the set of their friends. Write a function to find mutual friends.

Purpose: This exercise helps you practice pairing a dictionary with HashSet<T> values, and shows how Intersect() makes finding common elements between two sets straightforward.

Given Input: "Alice" is friends with Bob, Charlie, and Diana. "Bob" is friends with Alice, Charlie, and Eve.

Expected Output: Mutual friends: Charlie

▼ Hint
  • Look up both people’s friend sets with TryGetValue(), returning an empty result if either person is missing from the graph.
  • Call LINQ’s Intersect() on the two friend sets to find the friends they have in common.
  • Wrap the result back into a HashSet<string> so duplicates are impossible and set operations remain available.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

public static class SocialGraph
{
    public static HashSet<string> FindMutualFriends(Dictionary<string, HashSet<string>> graph, string personA, string personB)
    {
        if (!graph.TryGetValue(personA, out HashSet<string> friendsA) || !graph.TryGetValue(personB, out HashSet<string> friendsB))
            return new HashSet<string>();

        return new HashSet<string>(friendsA.Intersect(friendsB));
    }
}

class Program
{
    static void Main()
    {
        Dictionary<string, HashSet<string>> graph = new Dictionary<string, HashSet<string>>
        {
            { "Alice", new HashSet<string> { "Bob", "Charlie", "Diana" } },
            { "Bob", new HashSet<string> { "Alice", "Charlie", "Eve" } },
            { "Charlie", new HashSet<string> { "Alice", "Bob" } }
        };

        HashSet<string> mutual = SocialGraph.FindMutualFriends(graph, "Alice", "Bob");

        Console.WriteLine("Mutual friends: " + string.Join(", ", mutual));
    }
}Code language: C# (cs)

Explanation:

  • Dictionary<string, HashSet<string>>: Models each person as a key whose value is the unique set of their friends, a natural fit for an adjacency list.
  • !graph.TryGetValue(...) || !graph.TryGetValue(...): Bails out early with an empty set if either person doesn’t exist in the graph at all.
  • friendsA.Intersect(friendsB): Returns only the friends that appear in both sets, which is exactly the definition of a mutual friend.

Exercise 26: State Machine

Practice Problem: Implement a simple turn-based game state machine using a Dictionary<Tuple<State, Trigger>, State> to define valid state transitions.

Purpose: This exercise helps you practice using a Tuple as a composite dictionary key, letting a single lookup capture “given this state and this trigger, what state comes next” in one step.

Given Input: Starting from State.Idle, fire the triggers Start, Pause, Resume, then End in sequence.

Expected Output:

State: Playing
State: Paused
State: Playing
State: GameOver
▼ Hint
  • Define State and Trigger as enums representing the possible states and actions.
  • Build a dictionary keyed by Tuple.Create(currentState, trigger), with the resulting next state as the value.
  • To fire a trigger, build the same tuple key from the current state and look it up with TryGetValue().
  • Throw an exception if the combination of current state and trigger has no valid transition defined.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public enum State
{
    Idle,
    Playing,
    Paused,
    GameOver
}

public enum Trigger
{
    Start,
    Pause,
    Resume,
    End
}

public static class GameStateMachine
{
    private static Dictionary<Tuple<State, Trigger>, State> _transitions = new Dictionary<Tuple<State, Trigger>, State>
    {
        { Tuple.Create(State.Idle, Trigger.Start), State.Playing },
        { Tuple.Create(State.Playing, Trigger.Pause), State.Paused },
        { Tuple.Create(State.Paused, Trigger.Resume), State.Playing },
        { Tuple.Create(State.Playing, Trigger.End), State.GameOver }
    };

    public static State Fire(State current, Trigger trigger)
    {
        Tuple<State, Trigger> key = Tuple.Create(current, trigger);

        if (_transitions.TryGetValue(key, out State nextState))
            return nextState;

        throw new InvalidOperationException("Invalid transition from " + current + " on " + trigger);
    }
}

class Program
{
    static void Main()
    {
        State current = State.Idle;

        current = GameStateMachine.Fire(current, Trigger.Start);
        Console.WriteLine("State: " + current);

        current = GameStateMachine.Fire(current, Trigger.Pause);
        Console.WriteLine("State: " + current);

        current = GameStateMachine.Fire(current, Trigger.Resume);
        Console.WriteLine("State: " + current);

        current = GameStateMachine.Fire(current, Trigger.End);
        Console.WriteLine("State: " + current);
    }
}Code language: C# (cs)

Explanation:

  • Dictionary<Tuple<State, Trigger>, State>: Uses a two-part tuple as a single composite key, so a lookup only succeeds for an exact, valid state-and-trigger combination.
  • Tuple.Create(current, trigger): Builds the same kind of key used when the transition table was defined, so the lookup lines up correctly.
  • throw new InvalidOperationException(...): Rejects any trigger that has no defined transition from the current state, preventing the game from entering an undefined state.

Exercise 27: Frequency Tracker with Priority

Practice Problem: Create a data structure that tracks the frequency of elements added to it, and can return the top K most frequent elements using a dictionary paired with a sorting step.

Purpose: This exercise helps you practice using a dictionary purely as a counter, then layering LINQ’s OrderByDescending() and Take() on top of it whenever a ranked view of the data is needed.

Given Input: { "apple", "banana", "apple", "cherry", "apple", "banana", "date" }, requesting the top 2 most frequent items.

Expected Output: Top 2: apple, banana

▼ Hint
  • Keep a Dictionary<string, int> where each Add(item) call increments that item’s count.
  • For GetTopK(k), call OrderByDescending(pair => pair.Value) on the dictionary.
  • Chain Take(k) to limit the result, then Select(pair => pair.Key) to pull out just the item names.
▼ Solution & Explanation
using System;
using System.Collections.Generic;
using System.Linq;

public class FrequencyTracker
{
    private Dictionary<string, int> _counts = new Dictionary<string, int>();

    public void Add(string item)
    {
        if (_counts.ContainsKey(item))
            _counts[item]++;
        else
            _counts[item] = 1;
    }

    public List<string> GetTopK(int k)
    {
        return _counts
            .OrderByDescending(pair => pair.Value)
            .Take(k)
            .Select(pair => pair.Key)
            .ToList();
    }
}

class Program
{
    static void Main()
    {
        FrequencyTracker tracker = new FrequencyTracker();

        string[] items = { "apple", "banana", "apple", "cherry", "apple", "banana", "date" };
        foreach (string item in items)
        {
            tracker.Add(item);
        }

        List<string> top2 = tracker.GetTopK(2);
        Console.WriteLine("Top 2: " + string.Join(", ", top2));
    }
}Code language: C# (cs)

Explanation:

  • _counts[item]++ and _counts[item] = 1: Increment an existing count or start a new one at 1, exactly like a basic frequency counter.
  • OrderByDescending(pair => pair.Value): Ranks every tracked item from most frequent to least frequent.
  • Take(k): Trims the ranked sequence down to just the top k entries.

Exercise 28: Undo/Redo System Command History

Practice Problem: Use a dictionary to map specific command string names to executable Action delegates, implementing a dynamic command pattern execution engine with undo support.

Purpose: This exercise helps you practice storing behavior itself, not just data, as dictionary values, and shows how pairing a command history stack with matching undo delegates enables reverting the most recent action.

Given Input: Register "increment" and "addTen" commands, execute both, then call Undo() once.

Expected Output:

Counter after commands: 11
Counter after undo: 1
▼ Hint
  • Keep two dictionaries: one mapping command names to their “do” Action, and one mapping them to their matching “undo” Action.
  • Keep a Stack<string> of executed command names, pushing a new name every time a command runs.
  • For Undo(), pop the most recent command name off the stack and invoke its matching undo action.
  • Register commands as pairs of lambdas, one for the forward action and one that reverses it.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public class CommandEngine
{
    private Dictionary<string, Action> _commands = new Dictionary<string, Action>();
    private Dictionary<string, Action> _undoCommands = new Dictionary<string, Action>();
    private Stack<string> _history = new Stack<string>();

    public void Register(string name, Action execute, Action undo)
    {
        _commands[name] = execute;
        _undoCommands[name] = undo;
    }

    public void Execute(string name)
    {
        if (_commands.TryGetValue(name, out Action action))
        {
            action.Invoke();
            _history.Push(name);
        }
    }

    public void Undo()
    {
        if (_history.Count == 0)
            return;

        string lastCommand = _history.Pop();

        if (_undoCommands.TryGetValue(lastCommand, out Action undoAction))
        {
            undoAction.Invoke();
        }
    }
}

class Program
{
    static void Main()
    {
        int counter = 0;
        CommandEngine engine = new CommandEngine();

        engine.Register("increment", () => counter++, () => counter--);
        engine.Register("addTen", () => counter += 10, () => counter -= 10);

        engine.Execute("increment");
        engine.Execute("addTen");
        Console.WriteLine("Counter after commands: " + counter);

        engine.Undo();
        Console.WriteLine("Counter after undo: " + counter);
    }
}Code language: C# (cs)

Explanation:

  • Dictionary<string, Action> _commands: Stores actual executable behavior keyed by a friendly command name, instead of a long if-else chain.
  • _history.Push(name): Records every executed command in order, so the most recent one can be found again for undoing.
  • _history.Pop(): Retrieves and removes the most recently executed command’s name when Undo() is called.
  • _undoCommands.TryGetValue(lastCommand, ...): Looks up and runs the specific action that reverses whatever the last command did.

Exercise 29: Sparse Matrix Representation

Practice Problem: Represent a massive, mostly empty 10,000 by 10,000 grid using a Dictionary<Tuple<int, int>, double> to efficiently save memory, and write a method to calculate the dot product of two rows.

Purpose: This exercise helps you practice a sparse representation pattern, storing only the non-zero cells of a huge grid instead of allocating a full two-dimensional array that would waste enormous amounts of memory.

Given Input: Row 0 has values at columns 5 and 9998. Row 1 has values at the same two columns.

Expected Output: Dot product of row 0 and row 1 = 12.5

▼ Hint
  • Use Tuple.Create(row, col) as the dictionary key, storing only cells that have a non-zero value.
  • Write a Get(row, col) helper that returns 0 for any coordinate not present in the dictionary.
  • For the dot product, only loop through the entries that actually belong to one of the two rows, instead of looping through all 10,000 columns.
  • For each stored entry in that row, multiply its value by the corresponding column’s value in the other row and add it to a running total.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public class SparseMatrix
{
    private Dictionary<Tuple<int, int>, double> _values = new Dictionary<Tuple<int, int>, double>();

    public void Set(int row, int col, double value)
    {
        if (value == 0)
        {
            _values.Remove(Tuple.Create(row, col));
        }
        else
        {
            _values[Tuple.Create(row, col)] = value;
        }
    }

    public double Get(int row, int col)
    {
        return _values.TryGetValue(Tuple.Create(row, col), out double value) ? value : 0;
    }

    public double DotProductOfRows(int rowA, int rowB)
    {
        double sum = 0;

        foreach (KeyValuePair<Tuple<int, int>, double> entry in _values)
        {
            if (entry.Key.Item1 == rowA)
            {
                int col = entry.Key.Item2;
                sum += entry.Value * Get(rowB, col);
            }
        }

        return sum;
    }
}

class Program
{
    static void Main()
    {
        SparseMatrix matrix = new SparseMatrix();

        matrix.Set(0, 5, 2.0);
        matrix.Set(0, 9998, 3.0);
        matrix.Set(1, 5, 4.0);
        matrix.Set(1, 9998, 1.5);

        double dotProduct = matrix.DotProductOfRows(0, 1);
        Console.WriteLine("Dot product of row 0 and row 1 = " + dotProduct);
    }
}Code language: C# (cs)

Explanation:

  • Dictionary<Tuple<int, int>, double> _values: Stores only the coordinates that actually have a value, instead of the ten billion cells a full array would require.
  • Get(row, col): Returns 0 by convention for any coordinate that was never explicitly set, matching how a mostly-empty grid should behave.
  • entry.Key.Item1 == rowA: Filters down to only the stored cells belonging to the row being multiplied, avoiding a full 10,000-column scan.
  • sum += entry.Value * Get(rowB, col): Multiplies matching column values from each row together and accumulates the running total, exactly like a standard dot product.

Exercise 30: Dependency Injection Container

Practice Problem: Build a bare-bones Dependency Injection container using a Dictionary<Type, Func<object>> to register and resolve service instances dynamically.

Purpose: This exercise helps you practice using Type objects as dictionary keys and storing factory delegates as values, the core mechanism behind most real-world DI containers.

Given Input: container.Register<IGreeter>(() => new EnglishGreeter());, then resolve IGreeter and call its method.

Expected Output: Hello!

▼ Hint
  • Store registrations in a Dictionary<Type, Func<object>>, using typeof(TService) as the key.
  • Register<TService>(Func<TService> factory) should wrap the typed factory in a Func<object> before storing it.
  • Resolve<TService>() should look up typeof(TService), invoke the stored factory, and cast the result back to TService.
  • Throw a clear exception if someone tries to resolve a type that was never registered.
▼ Solution & Explanation
using System;
using System.Collections.Generic;

public interface IGreeter
{
    string Greet();
}

public class EnglishGreeter : IGreeter
{
    public string Greet()
    {
        return "Hello!";
    }
}

public class DIContainer
{
    private Dictionary<Type, Func<object>> _registrations = new Dictionary<Type, Func<object>>();

    public void Register<TService>(Func<TService> factory)
    {
        _registrations[typeof(TService)] = () => factory();
    }

    public TService Resolve<TService>()
    {
        if (_registrations.TryGetValue(typeof(TService), out Func<object> factory))
        {
            return (TService)factory();
        }

        throw new InvalidOperationException("No registration found for " + typeof(TService).Name);
    }
}

class Program
{
    static void Main()
    {
        DIContainer container = new DIContainer();
        container.Register<IGreeter>(() => new EnglishGreeter());

        IGreeter greeter = container.Resolve<IGreeter>();
        Console.WriteLine(greeter.Greet());
    }
}Code language: C# (cs)

Explanation:

  • Dictionary<Type, Func<object>> _registrations: Maps each service type to a factory function capable of producing an instance of it.
  • Register<TService>(Func<TService> factory): Wraps the strongly typed factory in a generic Func<object> so it can be stored alongside factories for completely different types.
  • (TService)factory(): Casts the resolved object back to the specific type the caller asked for.
  • throw new InvalidOperationException(...): Gives a clear error message instead of a confusing null reference if a type was never registered.

Filed Under: C# Exercises

Did you find this page helpful? Let others know about it. Sharing helps me continue to create free Python resources.

TweetF  sharein  shareP  Pin

About Vishal

I’m Vishal Hule, the Founder of PYnative.com. As a Python developer, I enjoy assisting students, developers, and learners. Follow me on Twitter.

Related Tutorial Topics:

C# Exercises

All Coding Exercises:

C Exercises
C++ Exercises
Python Exercises
Java Exercises
C# Exercises

Python Exercises and Quizzes

Free coding exercises and quizzes cover Python basics, data structure, data analytics, and more.

  • 15+ Topic-specific Exercises and Quizzes
  • Each Exercise contains 25+ questions
  • Each Quiz contains 25 MCQ
Exercises
Quizzes

Leave a Reply Cancel reply

your email address will NOT be published. all comments are moderated according to our comment policy.

Use <pre> tag for posting code. E.g. <pre> Your entire code </pre>

In: C# Exercises
TweetF  sharein  shareP  Pin

  C# Exercises

  • C# Exercise for Beginners
  • C# Loops Exercise
  • C# Array Exercise
  • C# String Exercise
  • C# OOP Exercise
  • C# Structs, Records and Enums Exercise
  • C# Collections Exercise
  • C# List Exercise
  • C# Dictionary Exercise
  • C# LINQ Exercise
  • C# Exception Handling Exercise
  • C# File Handling Exercise
  • C# Date and Time Exercise
  • C# Generics Exercise
  • C# Lambda Expressions Exercise
  • C# Delegates and Events Exercise
  • C# Extension Methods Exercise
  • C# Regex Exercise
  • C# Pattern Matching Exercise
  • C# Iterators and Yield Exercise
  • C# Indexers and Operator Overloading Exercise
  • C# Random Data Generation Exercise

All Coding Exercises

Python Exercises C Exercises C++ Exercises Java Exercises C# Exercises

About PYnative

PYnative.com is for Python lovers. Here, You can get Tutorials, Exercises, and Quizzes to practice and improve your Python skills.

Follow Us

To get New Python Tutorials, Exercises, and Quizzes

  • Twitter
  • Facebook
  • Sitemap

Explore Python

  • Learn Python
  • Python Basics
  • Python Databases
  • Python Exercises
  • Python Quizzes
  • Online Python Code Editor
  • Python Tricks

Coding Exercises

  • C Exercises
  • C++ Exercises
  • Python Exercises
  • Java Exercises
  • C# Exercises

Legal Stuff

  • About Us
  • Contact Us

We use cookies to improve your experience. While using PYnative, you agree to have read and accepted our:

  • Terms Of Use
  • Privacy Policy
  • Cookie Policy

Copyright © 2018–2026 pynative.com