{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Working with dictionaries\n",
    "\n",
    "In this tutorial, you will learn how to create, modify, and iterate over dictionaries — Python's key-value data structure.\n",
    "\n",
    "**Time commitment:** 15–20 minutes\n",
    "\n",
    "**Prerequisites:**\n",
    "\n",
    "- Python 3.12 or later installed on your machine\n",
    "- Completion of [Working with lists](https://agilearn.co.uk/guides/data-structures/learn/01-lists) and [Working with tuples](https://agilearn.co.uk/guides/data-structures/learn/02-tuples)\n",
    "\n",
    "## Learning objectives\n",
    "\n",
    "By the end of this tutorial, you will be able to:\n",
    "\n",
    "- Create dictionaries and access values by key\n",
    "- Add, update, and remove entries\n",
    "- Iterate over keys, values, and items\n",
    "- Work with nested dictionaries"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What is a dictionary?\n",
    "\n",
    "A **dictionary** is a mutable collection of key-value pairs. Each key maps to a value, allowing fast lookups by key. Since Python 3.7, dictionaries preserve the order in which items are inserted.\n",
    "\n",
    "Dictionaries are created using curly braces `{}`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30, \"city\": \"London\"}\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also create a dictionary using the `dict()` constructor:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = dict(name=\"Alice\", age=30, city=\"London\")\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Accessing values by key\n",
    "\n",
    "Use square brackets with the key to retrieve a value:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30, \"city\": \"London\"}\n",
    "\n",
    "print(person[\"name\"])\n",
    "print(person[\"city\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If the key does not exist, Python raises a `KeyError`. To avoid this, use the `get()` method, which returns `None` (or a default value you specify) when the key is missing:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30}\n",
    "\n",
    "print(person.get(\"name\"))         # \"Alice\"\n",
    "print(person.get(\"email\"))        # None\n",
    "print(person.get(\"email\", \"N/A\")) # \"N/A\" (default value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding and updating entries\n",
    "\n",
    "To add a new entry or update an existing one, assign a value using square brackets:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30}\n",
    "\n",
    "# Add a new entry\n",
    "person[\"email\"] = \"alice@example.com\"\n",
    "print(person)\n",
    "\n",
    "# Update an existing entry\n",
    "person[\"age\"] = 31\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also use `update()` to merge in values from another dictionary:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30}\n",
    "person.update({\"age\": 31, \"city\": \"Manchester\"})\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Removing entries\n",
    "\n",
    "Use `del` to remove an entry by key:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30, \"city\": \"London\"}\n",
    "del person[\"city\"]\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `pop()` to remove an entry and get its value back:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30, \"city\": \"London\"}\n",
    "city = person.pop(\"city\")\n",
    "print(f\"Removed: {city}\")\n",
    "print(f\"Remaining: {person}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can provide a default value to `pop()` to avoid a `KeyError` if the key does not exist:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\"}\n",
    "email = person.pop(\"email\", \"not found\")\n",
    "print(email)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Iterating over a dictionary\n",
    "\n",
    "By default, iterating over a dictionary gives you its **keys**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30, \"city\": \"London\"}\n",
    "\n",
    "for key in person:\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `values()` to iterate over the values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for value in person.values():\n",
    "    print(value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `items()` to iterate over both keys and values together:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for key, value in person.items():\n",
    "    print(f\"{key}: {value}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Checking membership\n",
    "\n",
    "The `in` keyword checks whether a **key** exists in the dictionary:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 30}\n",
    "\n",
    "print(\"name\" in person)   # True\n",
    "print(\"email\" in person)  # False"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To check whether a value exists, use `in` with `values()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(30 in person.values())  # True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nested dictionaries\n",
    "\n",
    "Dictionary values can themselves be dictionaries, allowing you to represent structured, hierarchical data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "school = {\n",
    "    \"Alice\": {\"age\": 15, \"subjects\": [\"Maths\", \"Science\"]},\n",
    "    \"Bob\": {\"age\": 16, \"subjects\": [\"English\", \"History\"]},\n",
    "}\n",
    "\n",
    "print(school[\"Alice\"][\"subjects\"])\n",
    "print(school[\"Bob\"][\"age\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can iterate over nested dictionaries using nested loops:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for name, details in school.items():\n",
    "    subjects = \", \".join(details[\"subjects\"])\n",
    "    print(f\"{name} (age {details['age']}): {subjects}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise: build a contact book\n",
    "\n",
    "Create a dictionary called `contacts` that stores three people. For each person, store their phone number and email address as a nested dictionary. Then:\n",
    "\n",
    "1. Print one contact's email address\n",
    "2. Add a new contact\n",
    "3. Print all contact names and their phone numbers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Write your code here\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Solution\n",
    "\n",
    "Here is one way to complete the exercise:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "contacts = {\n",
    "    \"Alice\": {\"phone\": \"07700 900001\", \"email\": \"alice@example.com\"},\n",
    "    \"Bob\": {\"phone\": \"07700 900002\", \"email\": \"bob@example.com\"},\n",
    "    \"Charlie\": {\"phone\": \"07700 900003\", \"email\": \"charlie@example.com\"},\n",
    "}\n",
    "\n",
    "# 1. Print one contact's email\n",
    "print(f\"Alice's email: {contacts['Alice']['email']}\")\n",
    "\n",
    "# 2. Add a new contact\n",
    "contacts[\"Diana\"] = {\"phone\": \"07700 900004\", \"email\": \"diana@example.com\"}\n",
    "\n",
    "# 3. Print all names and phone numbers\n",
    "for name, details in contacts.items():\n",
    "    print(f\"{name}: {details['phone']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "In this tutorial, you learned how to:\n",
    "\n",
    "- Create dictionaries using curly braces `{}` and `dict()`\n",
    "- Access values by key and use `get()` for safe access\n",
    "- Add, update, and remove entries\n",
    "- Iterate over keys, values, and items\n",
    "- Check for key membership with `in`\n",
    "- Work with nested dictionaries\n",
    "\n",
    "## What is next\n",
    "\n",
    "In the next tutorial, you will explore:\n",
    "\n",
    "- **Sets** — unordered collections of unique items"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}