{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "53f15908",
   "metadata": {},
   "source": [
    "# Core Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0a1cc10",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "# Find project root by looking for _config.yml\n",
    "current = Path.cwd()\n",
    "for parent in [current, *current.parents]:\n",
    "    if (parent / '_config.yml').exists():\n",
    "        project_root = parent\n",
    "        break\n",
    "else:\n",
    "    project_root = Path.cwd().parent.parent\n",
    "\n",
    "# Add project root to path\n",
    "sys.path.insert(0, str(project_root))\n",
    "\n",
    "# Import shared teaching helpers and cell magics\n",
    "from shared import thinkpython, diagram, jupyturtle, structshape\n",
    "from shared.download import download\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "253c1a59",
   "metadata": {},
   "source": [
    "Common dictionary operations involve the following methods. \n",
    "\n",
    "| Group | Method | Description |\n",
    "|---|---|---|\n",
    "| **Reading** | `d.get(key, default)` | Get value safely, no KeyError |\n",
    "| | `d.keys()` | All keys |\n",
    "| | `d.values()` | All values |\n",
    "| | `d.items()` | All key-value pairs |\n",
    "| **Adding/Updating** | `d.update({...})` | Add or overwrite from another dict |\n",
    "| | `d.setdefault(key, default)` | Set value only if key doesn't exist |\n",
    "| **Removing** | `d.pop(key, default)` | Remove & return a specific key |\n",
    "| | `d.popitem()` | Remove & return last inserted pair |\n",
    "| | `d.clear()` | Remove everything |\n",
    "| **Copying/Creating** | `d.copy()` | Shallow copy |\n",
    "| | `dict.fromkeys(keys, value)` | Create new dict from a list of keys |\n",
    "| **Checking** | `'key' in d` | Check if key exists |\n",
    "| | `len(d)` | Count of key-value pairs |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "278901e5",
   "metadata": {},
   "source": [
    "## Accessing Items\n",
    "\n",
    "Common ways to access values in a dictionary include:\n",
    "\n",
    "- `dict[key]` \n",
    "- `dict.get(key)`\n",
    "- `dict.get(key, default)`\n",
    "\n",
    "We use square brackets `[]` to access and modify items in a Python dictionary by **key**. Reading a value looks like `dict[key]`, which returns the value stored under `key`.\n",
    "  \n",
    "If a key you a key you try to access is missing, you will receive an error. To avoid the error, use `dict.get(key)`, which returns `None` or a default value you provide when error. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "8df66a13",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alice\n",
      "{'name': 'Alice', 'age': 28, 'city': 'Rolla'}\n"
     ]
    }
   ],
   "source": [
    "person = {\n",
    "    \"name\": \"Alice\",\n",
    "    \"age\": 28,\n",
    "    \"city\": \"Rolla\"\n",
    "}\n",
    "\n",
    "# Access an item\n",
    "print(person[\"name\"])      # Alice\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "966d157c",
   "metadata": {},
   "source": [
    "`KeyError` when trying to access a nonexistent key. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a572dad5",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%expect KeyError\n",
    "print(person[\"phone\"])      # KeyError\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee517266",
   "metadata": {},
   "source": [
    "When accessing, it is safer to use `dict.get()` to avoid the error because it\n",
    "\n",
    "- returns `None` when the key does not exist\n",
    "- returns a default value you specify when the key does not exist"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "66f12238",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "None\n",
      "N/A\n"
     ]
    }
   ],
   "source": [
    "print(person.get(\"phone\"))          # None\n",
    "print(person.get(\"phone\", \"N/A\"))   # N/A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22f85a93",
   "metadata": {},
   "outputs": [],
   "source": [
    "numbers = {'zero': 0, 'one': 1, 'two': 2}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfe9aba3",
   "metadata": {},
   "source": [
    "To access all the keys and values at once:\n",
    "\n",
    "- `keys()` returns a view of all the dictionary's  keys\n",
    "- `values()` returns a view of all the dictionary's values.\n",
    "- `items()` returns a view of all key-value pairs as tuples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "e841ea8c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "zero one two \n",
      "0 1 2 \n",
      "zero 0 one 1 two 2 "
     ]
    }
   ],
   "source": [
    "for k in numbers.keys():\n",
    "    print(k, end=' ')\n",
    "print()\n",
    "\n",
    "for v in numbers.values():\n",
    "    print(v, end=' ')\n",
    "print()\n",
    "\n",
    "for k, v in numbers.items():\n",
    "    print(k, v , end=' ')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a027a6b",
   "metadata": {},
   "source": [
    "The `len` function works on dictionaries; it returns the number of items."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "1b4ea0c2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(numbers)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "75edaa2a",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Accessing Dictionary Values\n",
    "# Given the dictionary below:\n",
    "person = {\"name\": \"Bob\", \"age\": 25, \"city\": \"Chicago\"}\n",
    "\n",
    "# 1. Print the value associated with \"name\".\n",
    "# 2. Use .get() to retrieve \"email\" with a default of \"N/A\".\n",
    "# 3. Print all keys and values using .items().\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "1c7bf62e",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Bob\n",
      "N/A\n",
      "name Bob\n",
      "age 25\n",
      "city Chicago\n"
     ]
    }
   ],
   "source": [
    "person = {\"name\": \"Bob\", \"age\": 25, \"city\": \"Chicago\"}\n",
    "\n",
    "# 1. Access \"name\"\n",
    "print(person[\"name\"])\n",
    "\n",
    "# 2. Get \"email\" safely with a default\n",
    "print(person.get(\"email\", \"N/A\"))\n",
    "\n",
    "# 3. Iterate with .items()\n",
    "for key, value in person.items():\n",
    "    print(key, value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53355982",
   "metadata": {},
   "source": [
    "## Adding/Updating Items"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "767dd9a6",
   "metadata": {},
   "source": [
    "You also use the square brackets to update values. An item assignment like `person[\"age\"] = 30` will either \n",
    "  - updates an existing item, or \n",
    "  - creates a new item if the key does not already exist. \n",
    "\n",
    "Note that, since dictionary keys are immutable, you can not modify it directly. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1364437e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': 'Bob', 'age': 29, 'city': 'Chicago', 'email': 'alice@rolla.com'}\n"
     ]
    }
   ],
   "source": [
    "# Modify an existing item\n",
    "person[\"age\"] = 29\n",
    "\n",
    "# Add a new item\n",
    "person[\"email\"] = \"alice@rolla.com\"\n",
    "\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c98292a",
   "metadata": {},
   "source": [
    "`d.update()` is used to add new key-value pairs or change existing ones in a dictionary. `d.update()` behaves like above but can work with multiple key-value pairs:\n",
    "\n",
    "- if the key already exists, its value is replaced\n",
    "- if the key does not exist, it is added"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "ee77f927",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'a': 1, 'b': 20, 'c': 3}\n"
     ]
    }
   ],
   "source": [
    "d = {\"a\": 1, \"b\": 2}\n",
    "d.update({\"b\": 20, \"c\": 3})\n",
    "\n",
    "print(d)   # {'a': 1, 'b': 20, 'c': 3}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9878c801",
   "metadata": {},
   "source": [
    "An alternative syntax for `d.update()` is as follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "53c15a08",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'a': 10, 'b': 20, 'c': 3, 'd': 4}\n"
     ]
    }
   ],
   "source": [
    "d.update(a=10, d=4)\n",
    "print(d)   # {'a': 10, 'b': 20, 'c': 3, 'd': 4}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73968a37",
   "metadata": {},
   "source": [
    "You cannot rename or update a key in place because dictionary keys are immutable. Instead, you could:\n",
    "\n",
    "1. Add a new key\n",
    "2. Copy the value\n",
    "3. Delete the old key\n",
    "\n",
    "You can do all three steps in one line of code, because `pop()` will return the value that's been removed, as shown below. I think that's pretty neat."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "c60b6dac",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': 'Doris', 'years': 29}\n"
     ]
    }
   ],
   "source": [
    "student = {\n",
    "    \"name\": \"Doris\",\n",
    "    \"age\": 29\n",
    "}\n",
    "\n",
    "# Rename key \"age\" to \"years\"\n",
    "student[\"years\"] = student.pop(\"age\")\n",
    "\n",
    "print(student)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "a48929a3",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Adding and Updating Dictionary Items\n",
    "# Start with this dictionary:\n",
    "person = {\"name\": \"Alice\", \"age\": 28}\n",
    "\n",
    "# 1. Add a new key \"email\" with value \"alice@example.com\".\n",
    "# 2. Update \"age\" to 30.\n",
    "# 3. Use d.update() to add \"city\": \"Boston\" and change \"name\" to \"Alicia\".\n",
    "# 4. Print the final dictionary.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "39ca8124",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': 'Alicia', 'age': 30, 'email': 'alice@example.com', 'city': 'Boston'}\n"
     ]
    }
   ],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 28}\n",
    "\n",
    "# 1. Add \"email\"\n",
    "person[\"email\"] = \"alice@example.com\"\n",
    "\n",
    "# 2. Update \"age\"\n",
    "person[\"age\"] = 30\n",
    "\n",
    "# 3. Use update()\n",
    "person.update({\"city\": \"Boston\", \"name\": \"Alicia\"})\n",
    "\n",
    "# 4. Print\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7d350b0",
   "metadata": {},
   "source": [
    "## Deleting Items\n",
    "\n",
    "You can delete items from a dictionary when you no longer need a key-value pair. \n",
    "\n",
    "- `del`: The `del` statement removes an item by its key, such as del person[\"city\"]. \n",
    "- `d.pop()`: Another option is `pop()`, which removes the item and also returns its value, which is useful if you want to use that value later. \n",
    "- `d.popitem()`: removes and returns the last inserted key–value pair from a dictionary.\n",
    "- `d.clear()`: removes everything in the dictionary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "2d66454f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "person:\t\t {'name': 'Alice', 'age': 29, 'city': 'Rolla', 'email': 'alice@rolla.com'}\n",
      "city deleted:\t {'name': 'Alice', 'age': 29, 'email': 'alice@rolla.com'}\n",
      "x_city_email:\t {'name': 'Alice', 'age': 29}\n",
      "email_popped:\t alice@rolla.com\n",
      "person_updated\t {'name': 'Alice', 'age': 29}\n",
      "popped_k_v:\t ('age', 29)\n",
      "after_popitem:\t {'name': 'Alice'}\n",
      "after_clear:\t {}\n"
     ]
    }
   ],
   "source": [
    "person = {\n",
    "    \"name\": \"Alice\",\n",
    "    \"age\": 29,\n",
    "    \"city\": \"Rolla\",\n",
    "    \"email\": \"alice@rolla.com\"\n",
    "}\n",
    "print(\"person:\\t\\t\", person)\n",
    "\n",
    "# Delete an item using del\n",
    "del person[\"city\"]                          # in place\n",
    "print(\"city deleted:\\t\", person)\n",
    "\n",
    "# Delete an item using pop()\n",
    "email_popped = person.pop(\"email\")\n",
    "\n",
    "print(\"x_city_email:\\t\", person)            # {'name': 'Alice', 'age': 29}\n",
    "print(\"email_popped:\\t\", email_popped)      # alice@rolla.com\n",
    "\n",
    "print(\"person_updated\\t\", person)\n",
    "\n",
    "# Delete the last added item using popitem() \n",
    "key, value = person.popitem()\n",
    "print(\"popped_k_v:\\t\", (key, value))        # ('age', 29) or ('name', 'Alice') depending on the order of items in the dict\n",
    "print(\"after_popitem:\\t\", person)           # {'name': 'Alice'}\n",
    "\n",
    "# Clear all items from the dictionary\n",
    "person.clear()\n",
    "print(\"after_clear:\\t\", person)  # {}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "421def9d",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Deleting Dictionary Items\n",
    "# Start with this dictionary:\n",
    "person = {\"name\": \"Bob\", \"age\": 25, \"city\": \"Boston\", \"email\": \"bob@email.com\"}\n",
    "\n",
    "# 1. Delete \"city\" using del.\n",
    "# 2. Remove and capture \"email\" using pop(). Print the popped value.\n",
    "# 3. Print the remaining dictionary.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "1954cc0b",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Popped email: bob@email.com\n",
      "{'name': 'Bob', 'age': 25}\n"
     ]
    }
   ],
   "source": [
    "person = {\"name\": \"Bob\", \"age\": 25, \"city\": \"Boston\", \"email\": \"bob@email.com\"}\n",
    "\n",
    "# 1. Delete \"city\"\n",
    "del person[\"city\"]\n",
    "\n",
    "# 2. Pop \"email\"\n",
    "email_val = person.pop(\"email\")\n",
    "print(\"Popped email:\", email_val)\n",
    "\n",
    "# 3. Print remaining\n",
    "print(person)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bafae76",
   "metadata": {},
   "source": [
    "## Membership Testing\n",
    "\n",
    "The `in` membership operator works on dictionaries, too; it tells you whether a **key** exists in the dictionary, which returns `True` if the key is present and `False` if it is not. You can also use `not in` to check that a key is missing. \n",
    "\n",
    "Member testing is useful when you want to safely decide whether to access, update, or delete a dictionary item.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "a50e687e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "False\n",
      "True\n",
      "Age exists: 29\n"
     ]
    }
   ],
   "source": [
    "person = {\n",
    "    \"name\": \"Alice\",\n",
    "    \"age\": 29,\n",
    "    \"city\": \"Rolla\"\n",
    "}\n",
    "\n",
    "print(\"name\" in person)        # True\n",
    "print(\"email\" in person)       # False\n",
    "print(\"email\" not in person)   # True\n",
    "\n",
    "if \"age\" in person:             # safety: Check if key exists before accessing\n",
    "    print(\"Age exists:\", person[\"age\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91cba20b",
   "metadata": {},
   "source": [
    "The `in` operator does *not* check whether something appears as a value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "79ff562e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"Alice\" in person"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93e478fe",
   "metadata": {},
   "source": [
    "To see whether something appears as a **value** in a dictionary, you can use the method `values`, which returns a sequence of values, and then use the `in` operator."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "5b24c03a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"Alice\" in person.values()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "f77a0477",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Membership Testing\n",
    "# Use the dictionary below:\n",
    "person = {\"name\": \"Alice\", \"age\": 29, \"city\": \"Rolla\"}\n",
    "\n",
    "# 1. Check if \"name\" is in the dictionary and print the result.\n",
    "# 2. Check if \"phone\" is NOT in the dictionary and print the result.\n",
    "# 3. Check if \"Alice\" appears as a value and print the result.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "1dc0509f",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "True\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "person = {\"name\": \"Alice\", \"age\": 29, \"city\": \"Rolla\"}\n",
    "\n",
    "# 1. \"name\" in person\n",
    "print(\"name\" in person)              # True\n",
    "\n",
    "# 2. \"phone\" not in person\n",
    "print(\"phone\" not in person)         # True\n",
    "\n",
    "# 3. \"Alice\" in values\n",
    "print(\"Alice\" in person.values())    # True"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
