{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "09472fdb",
   "metadata": {},
   "source": [
    "# Tuples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "711dcf82",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.270830Z",
     "iopub.status.busy": "2026-09-28T20:07:16.270682Z",
     "iopub.status.idle": "2026-09-28T20:07:16.762462Z",
     "shell.execute_reply": "2026-09-28T20:07:16.762068Z"
    },
    "tags": [
     "hide-input",
     "thebe-init"
    ]
   },
   "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": "66741054",
   "metadata": {},
   "source": [
    "```{contents}\n",
    ":local:\n",
    ":depth: 2\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "926aaf0b",
   "metadata": {},
   "source": [
    "Some values belong together and should not change: a location's latitude and longitude, a date's year, month, and day, or one row of a sales report. A **tuple** stores values like these as a single, fixed sequence. Section 6.2 showed that a list can change through any name that refers to it; a tuple cannot change at all, which makes it safe to share and a natural way to represent records.\n",
    "\n",
    "This section covers what tuples are, how to create and access them, what immutability does and does not guarantee, how to unpack tuples, the operations tuples support, how functions use tuples, and how to work with tuples as records."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97514024",
   "metadata": {},
   "source": [
    "## What Tuples Are"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-97514024",
   "metadata": {},
   "source": [
    "```{index} tuple\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "tuple-pronunciation-note",
   "metadata": {},
   "source": [
    "There are two common pronunciations of \"tuple\". Some people say \"tuh-ple\" (rhymes with \"supple\"), and many programmers say \"too-ple\" (rhymes with \"quadruple\")."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e26428d",
   "metadata": {},
   "source": [
    "- A tuple is a **sequence** of values.\n",
    "- The values can be **any type**.\n",
    "- The values in a tuple are **indexed** by integers, so tuples feel a lot like lists.\n",
    "- When a function returns multiple values, it returns a tuple.\n",
    "- Because tuples can't change, they can be used in places that require fixed values, such as dictionary keys (Chapter 7).\n",
    "\n",
    "For example, a latitude and longitude pair is a natural tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2f0a602e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.764558Z",
     "iopub.status.busy": "2026-09-28T20:07:16.764393Z",
     "iopub.status.idle": "2026-09-28T20:07:16.766180Z",
     "shell.execute_reply": "2026-09-28T20:07:16.765799Z"
    }
   },
   "outputs": [],
   "source": [
    "location = (42.3601, -71.0589)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07a05aab",
   "metadata": {},
   "source": [
    "Use a tuple when the values belong together and should not change; use a list when you need to add, remove, or change items. The table compares the two:\n",
    "\n",
    "| Feature    | Tuple          | List           |\n",
    "|------------|----------------|----------------|\n",
    "| Syntax     | `(1, 2, 3)`    | `[1, 2, 3]`    |\n",
    "| Mutable    | No             | Yes            |\n",
    "| Methods    | 2 (`.count`, `.index`) | Many  |\n",
    "| Use case   | Fixed data, records | Collections that change |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf60e652",
   "metadata": {},
   "source": [
    "## Creating and Accessing Tuples\n",
    "\n",
    "Tuples in Python can be created in several ways, primarily: \n",
    "- Using tuple literals: through the use of commas and optional parentheses\n",
    "- by using the built-in tuple() constructor\n",
    "\n",
    "| How to create | Method | Example | Result |\n",
    "|---|---|---|---|\n",
    "| Use parentheses with comma-separated values | Parentheses | `(1, 2, 3)` | `(1, 2, 3)` |\n",
    "| Write comma-separated values without parentheses | Tuple packing | `1, 2, 3` | `(1, 2, 3)` |\n",
    "| Use empty parentheses | Empty tuple | `()` | `()` |\n",
    "| Add a trailing comma for one value | Single-item tuple | `(1,)` or `1,` | `(1,)` |\n",
    "| Use the tuple constructor on an iterable | Constructor | `tuple([1, 2, 3])` | `(1, 2, 3)` |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-cf60e652",
   "metadata": {},
   "source": [
    "```{index} tuple\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e00a9e2",
   "metadata": {},
   "source": [
    "### Tuple Literals\n",
    "\n",
    "To create a tuple, you can write a comma-separated list of values."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-8e00a9e2",
   "metadata": {},
   "source": [
    "```{index} tuple literal\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "a4fbf044",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.767285Z",
     "iopub.status.busy": "2026-09-28T20:07:16.767208Z",
     "iopub.status.idle": "2026-09-28T20:07:16.769842Z",
     "shell.execute_reply": "2026-09-28T20:07:16.769462Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tuple"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "status = 'NEW', 'PROCESSING', 'SHIPPED', 'DELIVERED', 'CLOSED'\n",
    "type(status)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd01df23",
   "metadata": {},
   "source": [
    "Parentheses are optional, but it is common to use them with tuples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "facc9296",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.770793Z",
     "iopub.status.busy": "2026-09-28T20:07:16.770726Z",
     "iopub.status.idle": "2026-09-28T20:07:16.772503Z",
     "shell.execute_reply": "2026-09-28T20:07:16.772239Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('NEW', 'PROCESSING', 'SHIPPED', 'DELIVERED', 'CLOSED')\n",
      "<class 'tuple'>\n",
      "('NEW', 'PROCESSING', 'SHIPPED', 'DELIVERED', 'CLOSED')\n",
      "<class 'tuple'>\n"
     ]
    }
   ],
   "source": [
    "t1 = ('NEW', 'PROCESSING', 'SHIPPED', 'DELIVERED', 'CLOSED')\n",
    "print(t1)\n",
    "print(type(t1))\n",
    "\n",
    "t2 = 'NEW', 'PROCESSING', 'SHIPPED', 'DELIVERED', 'CLOSED'  # tuple packing\n",
    "print(t2)\n",
    "print(type(t2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a55573b5",
   "metadata": {},
   "source": [
    "### Single-Element Tuples"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-a55573b5",
   "metadata": {},
   "source": [
    "```{index} single-element tuple\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "601ec2fb",
   "metadata": {},
   "source": [
    "To make a one-element tuple, include a trailing **comma**. Parentheses are optional."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c9e90297",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.773717Z",
     "iopub.status.busy": "2026-09-28T20:07:16.773645Z",
     "iopub.status.idle": "2026-09-28T20:07:16.775558Z",
     "shell.execute_reply": "2026-09-28T20:07:16.775282Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'tuple'>\n",
      "URGENT\n",
      "<class 'tuple'>\n",
      "URGENT\n"
     ]
    }
   ],
   "source": [
    "t1 = ('URGENT',)\n",
    "print(type(t1))\n",
    "print(t1[0])\n",
    "\n",
    "t2 = 'URGENT',               # tuple packing with one element\n",
    "print(type(t2))\n",
    "print(t2[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d5feda0",
   "metadata": {},
   "source": [
    "However, a single value inside parentheses is not a tuple without a comma."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "b2405131",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.776706Z",
     "iopub.status.busy": "2026-09-28T20:07:16.776636Z",
     "iopub.status.idle": "2026-09-28T20:07:16.778311Z",
     "shell.execute_reply": "2026-09-28T20:07:16.777955Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "str"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t2 = ('URGENT')\n",
    "type(t2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73c45f96",
   "metadata": {},
   "source": [
    "### `tuple()` Constructor\n",
    "\n",
    "Another way to create a tuple is with the built-in function/type constructor `tuple`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-73c45f96",
   "metadata": {},
   "source": [
    "```{index} tuple() constructor\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09065ed6",
   "metadata": {},
   "source": [
    "If the argument is any iterable, such as a list, a string, or a range, `tuple` returns a tuple containing its elements. The `list` constructor works the same way in reverse, turning a tuple into a list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7aa729ac",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.779278Z",
     "iopub.status.busy": "2026-09-28T20:07:16.779213Z",
     "iopub.status.idle": "2026-09-28T20:07:16.780912Z",
     "shell.execute_reply": "2026-09-28T20:07:16.780513Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('Laptop', 'Mouse', 'Keyboard', 'Monitor')\n",
      "<class 'tuple'>\n",
      "['Laptop', 'Mouse', 'Keyboard', 'Monitor']\n",
      "<class 'list'>\n"
     ]
    }
   ],
   "source": [
    "t = tuple(['Laptop', 'Mouse', 'Keyboard', 'Monitor'])\n",
    "print(t)\n",
    "print(type(t))\n",
    "\n",
    "l = list(('Laptop', 'Mouse', 'Keyboard', 'Monitor'))\n",
    "print(l)\n",
    "print(type(l))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c83ec11e",
   "metadata": {},
   "source": [
    "With no argument, `tuple()` returns an empty tuple. Empty parentheses do the same:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "6e77fd12",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.781971Z",
     "iopub.status.busy": "2026-09-28T20:07:16.781888Z",
     "iopub.status.idle": "2026-09-28T20:07:16.783514Z",
     "shell.execute_reply": "2026-09-28T20:07:16.783239Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "() <class 'tuple'>\n",
      "() <class 'tuple'>\n"
     ]
    }
   ],
   "source": [
    "t = tuple()\n",
    "print(t, type(t))\n",
    "\n",
    "empty = ()\n",
    "print(empty, type(empty))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08a67131",
   "metadata": {},
   "source": [
    "### Nested Tuples\n",
    "\n",
    "A **nested tuple** is a tuple that contains one or more tuples as elements. This is useful for representing multi-dimensional or grouped data, such as a matrix or a list of coordinate pairs. You access elements using chained indexing: the first index selects the inner tuple, and the second index selects an element within it."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-08a67131",
   "metadata": {},
   "source": [
    "```{index} nested tuple\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "d9d799ab",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.784431Z",
     "iopub.status.busy": "2026-09-28T20:07:16.784370Z",
     "iopub.status.idle": "2026-09-28T20:07:16.786277Z",
     "shell.execute_reply": "2026-09-28T20:07:16.785957Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(4, 5, 6)\n",
      "6\n",
      "x=0, y=0\n",
      "x=1, y=2\n",
      "x=3, y=4\n"
     ]
    }
   ],
   "source": [
    "# A tuple containing other tuples\n",
    "matrix = ((1, 2, 3), (4, 5, 6), (7, 8, 9))\n",
    "\n",
    "# Access the second row\n",
    "print(matrix[1])        # (4, 5, 6)\n",
    "\n",
    "# Access the third element of the second row\n",
    "print(matrix[1][2])     # 6\n",
    "\n",
    "# Tuple of coordinate pairs\n",
    "points = ((0, 0), (1, 2), (3, 4))\n",
    "for x, y in points:\n",
    "    print(f'x={x}, y={y}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "3308eb8f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.787188Z",
     "iopub.status.busy": "2026-09-28T20:07:16.787124Z",
     "iopub.status.idle": "2026-09-28T20:07:16.788547Z",
     "shell.execute_reply": "2026-09-28T20:07:16.788251Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Creating Tuples *(basic)*\n",
    "#   1. Create a tuple named `colors` containing \"red\", \"green\", \"blue\" using parentheses.\n",
    "#   2. Create a single-element tuple named `one` containing the integer 42.\n",
    "#   3. Create a tuple named `letters` by passing the string \"hello\" to the tuple() constructor.\n",
    "#   4. Print each tuple and its type.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "19a73617",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.789430Z",
     "iopub.status.busy": "2026-09-28T20:07:16.789360Z",
     "iopub.status.idle": "2026-09-28T20:07:16.791148Z",
     "shell.execute_reply": "2026-09-28T20:07:16.790776Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('red', 'green', 'blue') <class 'tuple'>\n",
      "(42,) <class 'tuple'>\n",
      "('h', 'e', 'l', 'l', 'o') <class 'tuple'>\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "colors = (\"red\", \"green\", \"blue\")\n",
    "one = (42,)\n",
    "letters = tuple(\"hello\")\n",
    "\n",
    "print(colors, type(colors))\n",
    "print(one, type(one))\n",
    "print(letters, type(letters))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce1e4032",
   "metadata": {},
   "source": [
    "### Indexing and Slicing\n",
    "\n",
    "Tuples support indexing and slicing the same way lists do."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-ce1e4032",
   "metadata": {},
   "source": [
    "```{index} tuple indexing, tuple slicing\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "e6acd9a0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.792114Z",
     "iopub.status.busy": "2026-09-28T20:07:16.792039Z",
     "iopub.status.idle": "2026-09-28T20:07:16.793868Z",
     "shell.execute_reply": "2026-09-28T20:07:16.793585Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('Mon', 'Tue', 'Wed', 'Thu', 'Fri')\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'Mon'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri')\n",
    "print(t)\n",
    "t[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "714460b0",
   "metadata": {},
   "source": [
    "The slice operator selects a range of elements."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "66e9caad",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.794773Z",
     "iopub.status.busy": "2026-09-28T20:07:16.794719Z",
     "iopub.status.idle": "2026-09-28T20:07:16.796495Z",
     "shell.execute_reply": "2026-09-28T20:07:16.796206Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('Tue', 'Wed')"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t[1:3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "bbd11359",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.797539Z",
     "iopub.status.busy": "2026-09-28T20:07:16.797478Z",
     "iopub.status.idle": "2026-09-28T20:07:16.798989Z",
     "shell.execute_reply": "2026-09-28T20:07:16.798663Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Indexing and Slicing *(basic)*\n",
    "#   Given the tuple: t = ('a', 'b', 'c', 'd', 'e')\n",
    "#   1. Print the first and last elements using indexing.\n",
    "#   2. Print the middle three elements using slicing.\n",
    "#   3. Print the tuple in reverse using a slice.\n",
    "### Your code starts here.\n",
    "\n",
    "t = ('a', 'b', 'c', 'd', 'e')\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "390f1a74",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.799836Z",
     "iopub.status.busy": "2026-09-28T20:07:16.799784Z",
     "iopub.status.idle": "2026-09-28T20:07:16.801358Z",
     "shell.execute_reply": "2026-09-28T20:07:16.800996Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a e\n",
      "('b', 'c', 'd')\n",
      "('e', 'd', 'c', 'b', 'a')\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "t = ('a', 'b', 'c', 'd', 'e')\n",
    "\n",
    "print(t[0], t[-1])    # first and last\n",
    "print(t[1:4])          # middle three\n",
    "print(t[::-1])         # reversed"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a5748d2",
   "metadata": {},
   "source": [
    "## Immutability\n",
    "\n",
    "Tuples are **immutable**: once a tuple is created, its items cannot be added, removed, or replaced. Operations such as `+` build a new tuple instead of changing the old one, which is why tuples have no methods like `append` or `remove`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-0a5748d2",
   "metadata": {},
   "source": [
    "```{index} immutability\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d917d9e6",
   "metadata": {},
   "source": [
    "### Tuples Cannot Change\n",
    "\n",
    "If you try to replace an item in a tuple, Python raises a `TypeError`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-d917d9e6",
   "metadata": {},
   "source": [
    "```{index} immutability, TypeError\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "ca1483cf",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.802255Z",
     "iopub.status.busy": "2026-09-28T20:07:16.802189Z",
     "iopub.status.idle": "2026-09-28T20:07:16.804260Z",
     "shell.execute_reply": "2026-09-28T20:07:16.803934Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Invoice key[0]: INV-2026-0142\n"
     ]
    },
    {
     "ename": "TypeError",
     "evalue": "'tuple' object does not support item assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[31mTypeError\u001b[39m\u001b[31m:\u001b[39m 'tuple' object does not support item assignment\n"
     ]
    }
   ],
   "source": [
    "%%expect TypeError\n",
    "invoice_key = (\"INV-2026-0142\", \"ACME\", \"2026-02-28\")\n",
    "print(\"Invoice key[0]:\", invoice_key[0])\n",
    "\n",
    "invoice_key[0] = \"INV-2026-0143\" # Tuple does not support item assignment\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01445eb4",
   "metadata": {},
   "source": [
    "To get a tuple with a different value, build a new one. Slicing and `+` work on tuples the same way they work on lists:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "90f1dbc9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.805131Z",
     "iopub.status.busy": "2026-09-28T20:07:16.805080Z",
     "iopub.status.idle": "2026-09-28T20:07:16.806641Z",
     "shell.execute_reply": "2026-09-28T20:07:16.806383Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(3, 4, 5) (3, 4, 0)\n"
     ]
    }
   ],
   "source": [
    "point = (3, 4, 5)\n",
    "new_point = point[:2] + (0,)    # a new tuple; point is unchanged\n",
    "print(point, new_point)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "e7377bb0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.807641Z",
     "iopub.status.busy": "2026-09-28T20:07:16.807578Z",
     "iopub.status.idle": "2026-09-28T20:07:16.809090Z",
     "shell.execute_reply": "2026-09-28T20:07:16.808732Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuples Cannot Change *(basic)*\n",
    "#   Given date = (2026, 9, 28):\n",
    "#   1. Build a new tuple next_day with the same year and month and the day plus 1.\n",
    "#   2. Print both tuples to show that date is unchanged.\n",
    "date = (2026, 9, 28)\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "5a63c075",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.809873Z",
     "iopub.status.busy": "2026-09-28T20:07:16.809822Z",
     "iopub.status.idle": "2026-09-28T20:07:16.811314Z",
     "shell.execute_reply": "2026-09-28T20:07:16.811022Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(2026, 9, 28) (2026, 9, 29)\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "date = (2026, 9, 28)\n",
    "\n",
    "next_day = date[:2] + (date[2] + 1,)\n",
    "print(date, next_day)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "193980ac",
   "metadata": {},
   "source": [
    "### Tuples That Contain Lists\n",
    "\n",
    "Immutability applies to the tuple itself: which objects it holds, and in what order. If one of those objects is mutable, such as a list, that object can still change. This is the same idea as the shallow copies in Section 6.2: the outer container is fixed, but the list inside it is a shared object that can be modified."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-193980ac",
   "metadata": {},
   "source": [
    "```{index} tuple containing a list\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "b45c1404",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.812185Z",
     "iopub.status.busy": "2026-09-28T20:07:16.812119Z",
     "iopub.status.idle": "2026-09-28T20:07:16.813625Z",
     "shell.execute_reply": "2026-09-28T20:07:16.813369Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('Ana', [91, 84, 77])\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "record = ('Ana', [91, 84])     # a name and a list of scores\n",
    "scores = record[1]\n",
    "scores.append(77)              # changes the list inside the tuple\n",
    "\n",
    "print(record)\n",
    "print(scores is record[1])     # True: the same list object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "622d84d3",
   "metadata": {},
   "source": [
    "The tuple still holds the same two objects, but the list it holds has changed. Replacing the list itself is still an error:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "928bbd46",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.814626Z",
     "iopub.status.busy": "2026-09-28T20:07:16.814557Z",
     "iopub.status.idle": "2026-09-28T20:07:16.816519Z",
     "shell.execute_reply": "2026-09-28T20:07:16.816134Z"
    }
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'tuple' object does not support item assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[31mTypeError\u001b[39m\u001b[31m:\u001b[39m 'tuple' object does not support item assignment\n"
     ]
    }
   ],
   "source": [
    "%%expect TypeError\n",
    "record[1] = [100]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7aade269",
   "metadata": {},
   "source": [
    "If a record must never change, store only immutable values inside it, such as numbers, strings, and other tuples: `('Ana', (91, 84))`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "c84c4aac",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.817450Z",
     "iopub.status.busy": "2026-09-28T20:07:16.817398Z",
     "iopub.status.idle": "2026-09-28T20:07:16.818820Z",
     "shell.execute_reply": "2026-09-28T20:07:16.818511Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuples That Contain Lists *(applied)*\n",
    "#   Given team = ('Tigers', ['Ann', 'Bo']):\n",
    "#   1. Add 'Cy' to the member list inside team, then print team.\n",
    "#   2. In a comment, explain why team[1] = ['Di'] would raise a TypeError\n",
    "#      even though step 1 worked.\n",
    "#   3. Build frozen_team, a version of team whose members are stored in a\n",
    "#      tuple, so nothing in it can change. Print it.\n",
    "team = ('Tigers', ['Ann', 'Bo'])\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "6cb19990",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.819672Z",
     "iopub.status.busy": "2026-09-28T20:07:16.819621Z",
     "iopub.status.idle": "2026-09-28T20:07:16.821231Z",
     "shell.execute_reply": "2026-09-28T20:07:16.820907Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('Tigers', ['Ann', 'Bo', 'Cy'])\n",
      "('Tigers', ('Ann', 'Bo', 'Cy'))\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "team = ('Tigers', ['Ann', 'Bo'])\n",
    "\n",
    "team[1].append('Cy')\n",
    "print(team)\n",
    "\n",
    "# team[1] = ['Di'] would try to replace an item of the tuple, which is not allowed.\n",
    "# Step 1 did not replace anything; it changed the list that the tuple holds.\n",
    "\n",
    "frozen_team = (team[0], tuple(team[1]))\n",
    "print(frozen_team)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9db1874",
   "metadata": {},
   "source": [
    "### Safe to Share\n",
    "\n",
    "Section 6.2 showed that passing a list to a function passes a reference, so the function can change the caller's list. A tuple cannot be changed, so passing one to a function is safe: the function can read it but not modify it."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-c9db1874",
   "metadata": {},
   "source": [
    "```{index} tuple as function argument\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "0319e755",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.822142Z",
     "iopub.status.busy": "2026-09-28T20:07:16.822068Z",
     "iopub.status.idle": "2026-09-28T20:07:16.823691Z",
     "shell.execute_reply": "2026-09-28T20:07:16.823368Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[85, 90]\n"
     ]
    }
   ],
   "source": [
    "def add_bonus(scores):\n",
    "    scores[0] = scores[0] + 5      # works for a list, fails for a tuple\n",
    "    return scores\n",
    "\n",
    "list_scores = [80, 90]\n",
    "add_bonus(list_scores)\n",
    "print(list_scores)                 # the caller's list changed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "08170c0b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.824636Z",
     "iopub.status.busy": "2026-09-28T20:07:16.824581Z",
     "iopub.status.idle": "2026-09-28T20:07:16.826515Z",
     "shell.execute_reply": "2026-09-28T20:07:16.826184Z"
    }
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'tuple' object does not support item assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[31mTypeError\u001b[39m\u001b[31m:\u001b[39m 'tuple' object does not support item assignment\n"
     ]
    }
   ],
   "source": [
    "%%expect TypeError\n",
    "tuple_scores = (80, 90)\n",
    "add_bonus(tuple_scores)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c0176f4",
   "metadata": {},
   "source": [
    "To produce updated values from a tuple, a function returns a new tuple instead of changing the old one:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "a160706d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.827430Z",
     "iopub.status.busy": "2026-09-28T20:07:16.827377Z",
     "iopub.status.idle": "2026-09-28T20:07:16.829000Z",
     "shell.execute_reply": "2026-09-28T20:07:16.828669Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(80, 90) (85, 95)\n"
     ]
    }
   ],
   "source": [
    "def with_bonus(scores):\n",
    "    return tuple([score + 5 for score in scores])\n",
    "\n",
    "tuple_scores = (80, 90)\n",
    "new_scores = with_bonus(tuple_scores)\n",
    "print(tuple_scores, new_scores)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd44b6fc",
   "metadata": {},
   "source": [
    "This is a common design choice: when the code that receives some data should not change it, store the data in a tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "e8d9a5b1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.829902Z",
     "iopub.status.busy": "2026-09-28T20:07:16.829844Z",
     "iopub.status.idle": "2026-09-28T20:07:16.831244Z",
     "shell.execute_reply": "2026-09-28T20:07:16.830906Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Safe to Share *(applied)*\n",
    "#   1. Write a function scale(point, factor) that takes a tuple such as (2, 3)\n",
    "#      and returns a new tuple with each coordinate multiplied by factor.\n",
    "#   2. Call it with (2, 3) and 10, then print the original tuple and the result\n",
    "#      to show that the original is unchanged.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "f835a98f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.832205Z",
     "iopub.status.busy": "2026-09-28T20:07:16.832137Z",
     "iopub.status.idle": "2026-09-28T20:07:16.833703Z",
     "shell.execute_reply": "2026-09-28T20:07:16.833427Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(2, 3) (20, 30)\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "def scale(point, factor):\n",
    "    return tuple([coordinate * factor for coordinate in point])\n",
    "\n",
    "original = (2, 3)\n",
    "result = scale(original, 10)\n",
    "print(original, result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f246a4c3",
   "metadata": {},
   "source": [
    "## Unpacking and Iteration Helpers\n",
    "\n",
    "Tuples make it easy to pull values out of a sequence and to work with several sequences at once. This section covers unpacking a tuple into separate variables, starred unpacking, and two built-in helpers, `zip()` and `enumerate()`, that produce tuples as you loop."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cc0fd06",
   "metadata": {},
   "source": [
    "### Basic Unpacking\n",
    "\n",
    "You can assign multiple variables from a tuple or any sequence."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-8cc0fd06",
   "metadata": {},
   "source": [
    "```{index} tuple assignment, unpacking, swapping variables\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "e9d67087",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.834604Z",
     "iopub.status.busy": "2026-09-28T20:07:16.834553Z",
     "iopub.status.idle": "2026-09-28T20:07:16.835844Z",
     "shell.execute_reply": "2026-09-28T20:07:16.835573Z"
    }
   },
   "outputs": [],
   "source": [
    "a, b = 1, 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c0e86d9",
   "metadata": {},
   "source": [
    "Values are assigned left to right. In this example, `a` gets `1` and `b` gets `2`.\n",
    "You can display the results like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "bd0acf1f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.836829Z",
     "iopub.status.busy": "2026-09-28T20:07:16.836777Z",
     "iopub.status.idle": "2026-09-28T20:07:16.838442Z",
     "shell.execute_reply": "2026-09-28T20:07:16.838138Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 2)"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a, b"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3dedfbbf",
   "metadata": {},
   "source": [
    "More generally, if the left side is a tuple of variables, the right side can be any sequence - a string, list, or tuple.\n",
    "For example, to split an email address into a user name and domain:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "d3e0f3bc",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.839549Z",
     "iopub.status.busy": "2026-09-28T20:07:16.839476Z",
     "iopub.status.idle": "2026-09-28T20:07:16.840880Z",
     "shell.execute_reply": "2026-09-28T20:07:16.840568Z"
    }
   },
   "outputs": [],
   "source": [
    "email = 'monty@python.org'\n",
    "username, domain = email.split('@')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13eec68b",
   "metadata": {},
   "source": [
    "`split` returns a list with two elements; the first goes to `username`, the second to `domain`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "e6356b3b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.841764Z",
     "iopub.status.busy": "2026-09-28T20:07:16.841712Z",
     "iopub.status.idle": "2026-09-28T20:07:16.843431Z",
     "shell.execute_reply": "2026-09-28T20:07:16.843078Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('monty', 'python.org')"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "username, domain"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a12a2321",
   "metadata": {},
   "source": [
    "The number of variables on the left and values on the right must **match**; otherwise you get a `ValueError`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "03f37c17",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.844307Z",
     "iopub.status.busy": "2026-09-28T20:07:16.844254Z",
     "iopub.status.idle": "2026-09-28T20:07:16.845929Z",
     "shell.execute_reply": "2026-09-28T20:07:16.845628Z"
    }
   },
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "too many values to unpack (expected 2)",
     "output_type": "error",
     "traceback": [
      "\u001b[31mValueError\u001b[39m\u001b[31m:\u001b[39m too many values to unpack (expected 2)\n"
     ]
    }
   ],
   "source": [
    "%%expect ValueError\n",
    "a, b = 1, 2, 3\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ca9d55d",
   "metadata": {},
   "source": [
    "Tuple assignment is a clean way to swap two variables.\n",
    "With conventional assignments, you need a temporary variable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "c450eadc",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.846861Z",
     "iopub.status.busy": "2026-09-28T20:07:16.846795Z",
     "iopub.status.idle": "2026-09-28T20:07:16.848191Z",
     "shell.execute_reply": "2026-09-28T20:07:16.847880Z"
    }
   },
   "outputs": [],
   "source": [
    "temp = a\n",
    "a = b\n",
    "b = temp"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60780c74",
   "metadata": {},
   "source": [
    "That works, but tuple assignment does the same swap without a temporary variable. It works because Python evaluates the whole right side, `b, a`, before it assigns anything."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "88c040f3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.849058Z",
     "iopub.status.busy": "2026-09-28T20:07:16.849008Z",
     "iopub.status.idle": "2026-09-28T20:07:16.850331Z",
     "shell.execute_reply": "2026-09-28T20:07:16.850037Z"
    }
   },
   "outputs": [],
   "source": [
    "a, b = b, a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "162f280a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.851201Z",
     "iopub.status.busy": "2026-09-28T20:07:16.851148Z",
     "iopub.status.idle": "2026-09-28T20:07:16.852509Z",
     "shell.execute_reply": "2026-09-28T20:07:16.852242Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuple Assignment *(basic)*\n",
    "#   1. Unpack the tuple (10, 20, 30) into three variables x, y, z and print them.\n",
    "#   2. Split the email address 'ada@lovelace.org' into username and domain using\n",
    "#      split('@') and tuple assignment, then print both.\n",
    "#   3. Swap the values of two variables a=5, b=99 using tuple assignment.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "ce640574",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.853344Z",
     "iopub.status.busy": "2026-09-28T20:07:16.853282Z",
     "iopub.status.idle": "2026-09-28T20:07:16.854952Z",
     "shell.execute_reply": "2026-09-28T20:07:16.854627Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 20 30\n",
      "ada lovelace.org\n",
      "99 5\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "x, y, z = (10, 20, 30)\n",
    "print(x, y, z)                                  # 10 20 30\n",
    "\n",
    "username, domain = 'ada@lovelace.org'.split('@')\n",
    "print(username, domain)                          # ada lovelace.org\n",
    "\n",
    "a, b = 5, 99\n",
    "a, b = b, a\n",
    "print(a, b)                                      # 99 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9dc89e7",
   "metadata": {},
   "source": [
    "### Starred Unpacking\n",
    "\n",
    "Use a starred name to capture \"the rest\" of a sequence during assignment."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-d9dc89e7",
   "metadata": {},
   "source": [
    "```{index} starred unpacking\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "78dca9c7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.855911Z",
     "iopub.status.busy": "2026-09-28T20:07:16.855853Z",
     "iopub.status.idle": "2026-09-28T20:07:16.857604Z",
     "shell.execute_reply": "2026-09-28T20:07:16.857300Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0, [1, 2, 3, 4], 5)"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "first, *middle, last = range(6)\n",
    "first, middle, last"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0209e908",
   "metadata": {},
   "source": [
    "Tuple assignment is also handy in `for` loops. For example, here is a list of `(name, score)` records:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "a77dfb70",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.858502Z",
     "iopub.status.busy": "2026-09-28T20:07:16.858453Z",
     "iopub.status.idle": "2026-09-28T20:07:16.860051Z",
     "shell.execute_reply": "2026-09-28T20:07:16.859719Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ana -> 91\n",
      "Bob -> 84\n",
      "Chen -> 77\n"
     ]
    }
   ],
   "source": [
    "records = [('Ana', 91), ('Bob', 84), ('Chen', 77)]\n",
    "for record in records:\n",
    "    name, score = record\n",
    "    print(name, '->', score)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "912d40e4",
   "metadata": {},
   "source": [
    "Each time through the loop, `record` is a tuple with a name and a score. You can unpack it directly in the `for` statement:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "0e0c0e39",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.860888Z",
     "iopub.status.busy": "2026-09-28T20:07:16.860838Z",
     "iopub.status.idle": "2026-09-28T20:07:16.862229Z",
     "shell.execute_reply": "2026-09-28T20:07:16.861962Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ana -> 91\n",
      "Bob -> 84\n",
      "Chen -> 77\n"
     ]
    }
   ],
   "source": [
    "for name, score in records:\n",
    "    print(name, '->', score)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6070e933",
   "metadata": {},
   "source": [
    "Now `name` and `score` are assigned on each iteration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "2da83c89",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.863150Z",
     "iopub.status.busy": "2026-09-28T20:07:16.863090Z",
     "iopub.status.idle": "2026-09-28T20:07:16.864569Z",
     "shell.execute_reply": "2026-09-28T20:07:16.864238Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Starred Unpacking *(applied)*\n",
    "#   Given data = (1, 2, 3, 4, 5, 6)\n",
    "#   1. Unpack the first element into `head`, the last into `tail`,\n",
    "#      and everything in between into `body` using starred unpacking.\n",
    "#   2. Print all three.\n",
    "#   3. Loop over [('x', 10), ('y', 20), ('z', 30)] and unpack\n",
    "#      each pair directly in the for statement, printing each pair.\n",
    "### Your code starts here.\n",
    "\n",
    "data = (1, 2, 3, 4, 5, 6)\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "600f798c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.865694Z",
     "iopub.status.busy": "2026-09-28T20:07:16.865630Z",
     "iopub.status.idle": "2026-09-28T20:07:16.867368Z",
     "shell.execute_reply": "2026-09-28T20:07:16.867079Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 [2, 3, 4, 5] 6\n",
      "x -> 10\n",
      "y -> 20\n",
      "z -> 30\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "data = (1, 2, 3, 4, 5, 6)\n",
    "\n",
    "head, *body, tail = data\n",
    "print(head, body, tail)      # 1 [2, 3, 4, 5] 6\n",
    "\n",
    "for label, value in [('x', 10), ('y', 20), ('z', 30)]:\n",
    "    print(label, '->', value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "742f8b69",
   "metadata": {},
   "source": [
    "### `zip()`\n",
    "\n",
    "Tuples are useful for pairing elements from multiple sequences and working with them together.\n",
    "For example, suppose a sales team has weekly targets, and their actual sales and the targets are stored in two separate lists."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-742f8b69",
   "metadata": {},
   "source": [
    "```{index} zip(), zip object, itertools.zip_longest()\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "9cb8116e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.868250Z",
     "iopub.status.busy": "2026-09-28T20:07:16.868192Z",
     "iopub.status.idle": "2026-09-28T20:07:16.869635Z",
     "shell.execute_reply": "2026-09-28T20:07:16.869307Z"
    }
   },
   "outputs": [],
   "source": [
    "actual = [112, 98, 135, 144, 101, 129, 88]\n",
    "target = [120, 120, 120, 120, 120, 120, 120]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "462715d1",
   "metadata": {},
   "source": [
    "To count how many weeks the team exceeded its target, use `zip`, which you saw in Section 6.1. It combines sequences and returns a **zip object** that pairs elements like the teeth of a zipper, and each pair is a tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "815cc4d3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.870504Z",
     "iopub.status.busy": "2026-09-28T20:07:16.870453Z",
     "iopub.status.idle": "2026-09-28T20:07:16.872102Z",
     "shell.execute_reply": "2026-09-28T20:07:16.871832Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "zip"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(zip(actual, target))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8d7649c",
   "metadata": {},
   "source": [
    "`zip` stops when the shortest input runs out. To keep going, use `itertools.zip_longest`; its `fillvalue` argument sets the value used for the missing items."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "eaf44ca6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.873028Z",
     "iopub.status.busy": "2026-09-28T20:07:16.872955Z",
     "iopub.status.idle": "2026-09-28T20:07:16.874786Z",
     "shell.execute_reply": "2026-09-28T20:07:16.874521Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('a', 1), ('b', 2), ('c', '-')]"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from itertools import zip_longest\n",
    "\n",
    "list(zip('abc', [1, 2]))\n",
    "list(zip_longest('abc', [1, 2], fillvalue='-'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df01b614",
   "metadata": {},
   "source": [
    "You can loop over the zip object to get the pairs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "b4b7150e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.875817Z",
     "iopub.status.busy": "2026-09-28T20:07:16.875766Z",
     "iopub.status.idle": "2026-09-28T20:07:16.877285Z",
     "shell.execute_reply": "2026-09-28T20:07:16.877021Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(112, 120)\n",
      "(98, 120)\n",
      "(135, 120)\n",
      "(144, 120)\n",
      "(101, 120)\n",
      "(129, 120)\n",
      "(88, 120)\n"
     ]
    }
   ],
   "source": [
    "for pair in zip(actual, target):\n",
    "    print(pair)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55d41582",
   "metadata": {},
   "source": [
    "Each time through the loop, `pair` is a tuple of `(actual_sales, target_sales)`.\n",
    "You can unpack those values and count the weeks where actual sales exceeded the target:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "16642aa9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.878158Z",
     "iopub.status.busy": "2026-09-28T20:07:16.878108Z",
     "iopub.status.idle": "2026-09-28T20:07:16.879935Z",
     "shell.execute_reply": "2026-09-28T20:07:16.879583Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "weeks_above_target = 0\n",
    "for sale, goal in zip(actual, target):\n",
    "    if sale > goal:\n",
    "        weeks_above_target += 1\n",
    "\n",
    "weeks_above_target"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2e6a7bc",
   "metadata": {},
   "source": [
    "The team beat their weekly target three out of seven weeks.\n",
    "\n",
    "If you want a list of pairs, combine `zip` with `list`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "114766fc",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.880934Z",
     "iopub.status.busy": "2026-09-28T20:07:16.880870Z",
     "iopub.status.idle": "2026-09-28T20:07:16.883230Z",
     "shell.execute_reply": "2026-09-28T20:07:16.882867Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(112, 120),\n",
       " (98, 120),\n",
       " (135, 120),\n",
       " (144, 120),\n",
       " (101, 120),\n",
       " (129, 120),\n",
       " (88, 120)]"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "weekly_results = list(zip(actual, target))\n",
    "weekly_results"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "96db88c1",
   "metadata": {},
   "source": [
    "The result is a list of tuples, so you can get the last week's data like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "79867eb7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.884334Z",
     "iopub.status.busy": "2026-09-28T20:07:16.884262Z",
     "iopub.status.idle": "2026-09-28T20:07:16.886103Z",
     "shell.execute_reply": "2026-09-28T20:07:16.885773Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(88, 120)"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "weekly_results[-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bbe253a8",
   "metadata": {},
   "source": [
    "### `enumerate()`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-bbe253a8",
   "metadata": {},
   "source": [
    "```{index} enumerate()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74e320d9",
   "metadata": {},
   "source": [
    "Section 6.1 showed how `enumerate` pairs each element with its index in a loop. Each pair it produces is a tuple of `(index, element)`, which is why you can unpack it in the `for` statement. The index starts at `0` by default; to start at `1` (like line numbers), pass the optional `start` argument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "6ce7cd97",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.887236Z",
     "iopub.status.busy": "2026-09-28T20:07:16.887165Z",
     "iopub.status.idle": "2026-09-28T20:07:16.889055Z",
     "shell.execute_reply": "2026-09-28T20:07:16.888673Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(1, 'a'), (2, 'b'), (3, 'c')]"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(enumerate('abc', start=1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "7dc880c4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.890224Z",
     "iopub.status.busy": "2026-09-28T20:07:16.890155Z",
     "iopub.status.idle": "2026-09-28T20:07:16.891877Z",
     "shell.execute_reply": "2026-09-28T20:07:16.891529Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Zip *(applied)*\n",
    "#   1. Given names = ['Alice', 'Bob', 'Carol'] and scores = [88, 95, 72],\n",
    "#      use zip to print each name paired with their score.\n",
    "#   2. Build a list of (name, score) tuples using list(zip(...)),\n",
    "#      then print the last pair.\n",
    "#   3. Use enumerate (starting at 1) to print each name with its rank number.\n",
    "### Your code starts here.\n",
    "\n",
    "names = ['Alice', 'Bob', 'Carol']\n",
    "scores = [88, 95, 72]\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "5bda66b3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.892848Z",
     "iopub.status.busy": "2026-09-28T20:07:16.892780Z",
     "iopub.status.idle": "2026-09-28T20:07:16.894654Z",
     "shell.execute_reply": "2026-09-28T20:07:16.894288Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alice 88\n",
      "Bob 95\n",
      "Carol 72\n",
      "('Carol', 72)\n",
      "1 Alice\n",
      "2 Bob\n",
      "3 Carol\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "names = ['Alice', 'Bob', 'Carol']\n",
    "scores = [88, 95, 72]\n",
    "\n",
    "for name, score in zip(names, scores):\n",
    "    print(name, score)\n",
    "\n",
    "pairs = list(zip(names, scores))\n",
    "print(pairs[-1])\n",
    "\n",
    "for rank, name in enumerate(names, start=1):\n",
    "    print(rank, name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac911a12",
   "metadata": {},
   "source": [
    "## Tuple Operations\n",
    "\n",
    "Common tuple operations include: \n",
    "| Python Expression | Description | Example |\n",
    "|---|---|---|\n",
    "| `len(tuple)` | Returns the number of items in the tuple. | `len((1, 2, 3))` returns `3`. |\n",
    "| `tuple1 + tuple2` | Concatenation: combines two tuples into a new tuple. | `(1, 2) + (3, 4)` returns `(1, 2, 3, 4)`. |\n",
    "| `tuple * n` | Repetition: repeats tuple elements `n` times. | `('Hi!',) * 3` returns `('Hi!', 'Hi!', 'Hi!')`. |\n",
    "| `item in tuple` | Membership: checks whether a value exists in the tuple (`True`/`False`). | `3 in (1, 2, 3)` returns `True`. |\n",
    "| `tuple[index]` | Indexing: accesses an element using positive or negative index. | `('a', 'b', 'c')[0]` returns `'a'`. |\n",
    "| `tuple[start:stop]` | Slicing: extracts a range of elements as a new tuple. | `(1, 2, 3, 4)[1:3]` returns `(2, 3)`. |\n",
    "| `tuple(iterable)` | Constructor: converts an iterable (list, string, range, etc.) into a tuple. | `tuple([1, 2])` returns `(1, 2)`. |\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-ac911a12",
   "metadata": {},
   "source": [
    "```{index} tuple concatenation, tuple repetition\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6cb8476d",
   "metadata": {},
   "source": [
    "### Tuple Operators"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-6cb8476d",
   "metadata": {},
   "source": [
    "```{index} tuple concatenation, tuple repetition\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9944c879",
   "metadata": {},
   "source": [
    "The `+` operator concatenates tuples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "34778c59",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.895601Z",
     "iopub.status.busy": "2026-09-28T20:07:16.895553Z",
     "iopub.status.idle": "2026-09-28T20:07:16.897121Z",
     "shell.execute_reply": "2026-09-28T20:07:16.896841Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('Chicago', 'Detroit', 'Minneapolis', 'Atlanta', 'Dallas', 'Miami')\n",
      "<class 'tuple'>\n"
     ]
    }
   ],
   "source": [
    "north = ('Chicago', 'Detroit', 'Minneapolis')\n",
    "south = ('Atlanta', 'Dallas', 'Miami')\n",
    "\n",
    "print(north + south)\n",
    "print(type(north + south))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21462154",
   "metadata": {},
   "source": [
    "But you cannot concatenate a string and a tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "b3cf5a61",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.898233Z",
     "iopub.status.busy": "2026-09-28T20:07:16.898176Z",
     "iopub.status.idle": "2026-09-28T20:07:16.900358Z",
     "shell.execute_reply": "2026-09-28T20:07:16.900011Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Chicago-Detroit-Minneapolis\n",
      "<class 'str'>\n",
      "<class 'tuple'>\n"
     ]
    },
    {
     "ename": "TypeError",
     "evalue": "can only concatenate str (not \"tuple\") to str",
     "output_type": "error",
     "traceback": [
      "\u001b[31mTypeError\u001b[39m\u001b[31m:\u001b[39m can only concatenate str (not \"tuple\") to str\n"
     ]
    }
   ],
   "source": [
    "%%expect TypeError\n",
    "north_str = 'Chicago-Detroit-Minneapolis'   # a string, not a tuple\n",
    "print(north_str)\n",
    "print(type(north_str))          # A string is not a tuple, even if it looks like a list of items.\n",
    "\n",
    "south = ('Atlanta', 'Dallas', 'Miami')\n",
    "print(type(south))\n",
    "\n",
    "north_str + south   # TypeError: can only concatenate str to str, not tuple\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef3e843f",
   "metadata": {},
   "source": [
    "The `*` operator repeats a tuple a given number of times."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "fd858bfe",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.901257Z",
     "iopub.status.busy": "2026-09-28T20:07:16.901203Z",
     "iopub.status.idle": "2026-09-28T20:07:16.902838Z",
     "shell.execute_reply": "2026-09-28T20:07:16.902537Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('Q1', 'Q2', 'Q3', 'Q4', 'Q1', 'Q2', 'Q3', 'Q4')"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "('Q1', 'Q2', 'Q3', 'Q4') * 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "92cae294",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.903656Z",
     "iopub.status.busy": "2026-09-28T20:07:16.903607Z",
     "iopub.status.idle": "2026-09-28T20:07:16.904922Z",
     "shell.execute_reply": "2026-09-28T20:07:16.904628Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuple Operators *(basic)*\n",
    "#   1. Concatenate (1, 2, 3) and (4, 5, 6) into a new tuple and print it.\n",
    "#   2. Repeat the tuple ('ha',) three times and print the result.\n",
    "#   3. Check whether the value 7 is in (1, 3, 5, 7, 9) and print the boolean result.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "3e7afa90",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.905789Z",
     "iopub.status.busy": "2026-09-28T20:07:16.905716Z",
     "iopub.status.idle": "2026-09-28T20:07:16.907480Z",
     "shell.execute_reply": "2026-09-28T20:07:16.907091Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 2, 3, 4, 5, 6)\n",
      "('ha', 'ha', 'ha')\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "print((1, 2, 3) + (4, 5, 6))        # concatenation\n",
    "print(('ha',) * 3)                   # repetition\n",
    "print(7 in (1, 3, 5, 7, 9))         # membership"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d1aa8aa",
   "metadata": {},
   "source": [
    "### Tuple Methods\n",
    "\n",
    "Tuples have only two methods, `count` and `index`, and both return information without changing the tuple. Methods that change a list in place, such as `append`, `remove`, and `sort`, do not exist for tuples."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-5d1aa8aa",
   "metadata": {},
   "source": [
    "```{index} tuple.count(), tuple.index()\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "17512c9d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.908437Z",
     "iopub.status.busy": "2026-09-28T20:07:16.908383Z",
     "iopub.status.idle": "2026-09-28T20:07:16.910248Z",
     "shell.execute_reply": "2026-09-28T20:07:16.909917Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 3)"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "orders = ('PENDING', 'SHIPPED', 'PENDING', 'DELIVERED')\n",
    "orders.count('PENDING'), orders.index('DELIVERED')   # this returns a tuple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "382e406c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.911113Z",
     "iopub.status.busy": "2026-09-28T20:07:16.911064Z",
     "iopub.status.idle": "2026-09-28T20:07:16.912482Z",
     "shell.execute_reply": "2026-09-28T20:07:16.912175Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuple Methods *(basic)*\n",
    "#   Given t = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3)\n",
    "#   1. Count how many times 1 appears in t.\n",
    "#   2. Find the index of the first occurrence of 9.\n",
    "#   3. Count how many times 5 appears.\n",
    "### Your code starts here.\n",
    "\n",
    "t = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3)\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "8bece928",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.913271Z",
     "iopub.status.busy": "2026-09-28T20:07:16.913217Z",
     "iopub.status.idle": "2026-09-28T20:07:16.914849Z",
     "shell.execute_reply": "2026-09-28T20:07:16.914517Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2\n",
      "5\n",
      "2\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "t = (3, 1, 4, 1, 5, 9, 2, 6, 5, 3)\n",
    "\n",
    "print(t.count(1))     # 2\n",
    "print(t.index(9))     # 5\n",
    "print(t.count(5))     # 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31beb39a",
   "metadata": {},
   "source": [
    "### Tuple Functions\n",
    "\n",
    "In addition to these two methods, Python's built-in functions (e.g., `len`, `max`, `min`, `sum`) can be used with tuples, as they work with any `iterable`. These functions do not **modify** the original tuple, but return **new** values or objects: \n",
    "\n",
    "| Function | What it does | Notes |\n",
    "|---|---|---|\n",
    "| `len(tuple)` | Returns the total number of items in the tuple. | Works for any tuple. |\n",
    "| `max(tuple)` | Returns the largest item in the tuple. | Elements must be comparable. |\n",
    "| `min(tuple)` | Returns the smallest item in the tuple. | Elements must be comparable. |\n",
    "| `sum(tuple)` | Returns the sum of all numeric elements in the tuple. | Elements should be numbers. |\n",
    "| `sorted(tuple)` | Returns a new list with tuple elements in sorted order. | Output type is **`list`**, not `tuple`. |\n",
    "| `reversed(tuple)` | Returns an iterator over tuple elements in reverse order. | Convert with `tuple(...)` or `list(...)` if needed. |\n",
    "| `tuple(iterable)` | Converts another iterable (like a list or string) into a tuple. | Useful for type conversion. |"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-31beb39a",
   "metadata": {},
   "source": [
    "```{index} sorted(), reversed()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd7ae0d8",
   "metadata": {},
   "source": [
    "The `sorted` function works with tuples, but it returns a new `list`, not a tuple. The original tuple is unchanged."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "82f42e9d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.915921Z",
     "iopub.status.busy": "2026-09-28T20:07:16.915863Z",
     "iopub.status.idle": "2026-09-28T20:07:16.917767Z",
     "shell.execute_reply": "2026-09-28T20:07:16.917429Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('RECEIVED', 'PENDING', 'SHIPPED', 'DELIVERED')\n",
      "<class 'tuple'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "['DELIVERED', 'PENDING', 'RECEIVED', 'SHIPPED']"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "orders = ('RECEIVED', 'PENDING', 'SHIPPED', 'DELIVERED')\n",
    "\n",
    "print(orders)\n",
    "print(type(orders))\n",
    "\n",
    "sorted(orders)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "1dffc1ed",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.918650Z",
     "iopub.status.busy": "2026-09-28T20:07:16.918592Z",
     "iopub.status.idle": "2026-09-28T20:07:16.920404Z",
     "shell.execute_reply": "2026-09-28T20:07:16.920107Z"
    }
   },
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "'tuple' object has no attribute 'sort'",
     "output_type": "error",
     "traceback": [
      "\u001b[31mAttributeError\u001b[39m\u001b[31m:\u001b[39m 'tuple' object has no attribute 'sort'\n"
     ]
    }
   ],
   "source": [
    "%%expect AttributeError\n",
    "orders.sort()  # tuples have no sort method, because they cannot be changed\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "517af861",
   "metadata": {},
   "source": [
    "The `reversed` function also works with tuples. It returns a `reversed` object, which you can convert to a list or tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "373582a9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.921333Z",
     "iopub.status.busy": "2026-09-28T20:07:16.921274Z",
     "iopub.status.idle": "2026-09-28T20:07:16.923134Z",
     "shell.execute_reply": "2026-09-28T20:07:16.922851Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'reversed'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "('DELIVERED', 'SHIPPED', 'PENDING', 'RECEIVED')"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(type(reversed(orders)))\n",
    "tuple(reversed(orders))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "eff9f464",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.924054Z",
     "iopub.status.busy": "2026-09-28T20:07:16.923992Z",
     "iopub.status.idle": "2026-09-28T20:07:16.925779Z",
     "shell.execute_reply": "2026-09-28T20:07:16.925402Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('RECEIVED', 'PENDING', 'SHIPPED', 'DELIVERED')"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# original object is unchanged\n",
    "orders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "019bbb85",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.926630Z",
     "iopub.status.busy": "2026-09-28T20:07:16.926571Z",
     "iopub.status.idle": "2026-09-28T20:07:16.927952Z",
     "shell.execute_reply": "2026-09-28T20:07:16.927694Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuple Functions *(basic)*\n",
    "#   Given t = (4, 7, 2, 9, 1, 5)\n",
    "#   1. Print the length, max, min, and sum of t.\n",
    "#   2. Print a sorted version of t (as a list).\n",
    "#   3. Print t in reverse order as a tuple.\n",
    "### Your code starts here.\n",
    "\n",
    "t = (4, 7, 2, 9, 1, 5)\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "123790d3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.928789Z",
     "iopub.status.busy": "2026-09-28T20:07:16.928717Z",
     "iopub.status.idle": "2026-09-28T20:07:16.930266Z",
     "shell.execute_reply": "2026-09-28T20:07:16.929963Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "6 9 1 28\n",
      "[1, 2, 4, 5, 7, 9]\n",
      "(5, 1, 9, 2, 7, 4)\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "t = (4, 7, 2, 9, 1, 5)\n",
    "\n",
    "print(len(t), max(t), min(t), sum(t))  # 6 9 1 28\n",
    "print(sorted(t))                         # [1, 2, 4, 5, 7, 9]\n",
    "print(tuple(reversed(t)))                # (5, 1, 9, 2, 7, 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be14b0aa",
   "metadata": {},
   "source": [
    "### Comparing and Sorting\n",
    "\n",
    "Comparison operators such as `<` and `==` work with tuples and other sequences.\n",
    "Tuple comparison is lexicographic: it compares the first elements, then the next, and so on until it finds a difference.\n",
    "For example, you can represent a reporting period as a `(year, quarter)` tuple and compare two periods."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-be14b0aa",
   "metadata": {},
   "source": [
    "```{index} tuple comparison, lexicographic comparison\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "6f143fb9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.931350Z",
     "iopub.status.busy": "2026-09-28T20:07:16.931292Z",
     "iopub.status.idle": "2026-09-28T20:07:16.933073Z",
     "shell.execute_reply": "2026-09-28T20:07:16.932803Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 67,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(2026, 1) < (2026, 3)    # Q1 2026 is before Q3 2026"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "90d04fc6",
   "metadata": {},
   "source": [
    "Once a difference is found, later elements are not considered."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "38d79bd4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.933939Z",
     "iopub.status.busy": "2026-09-28T20:07:16.933887Z",
     "iopub.status.idle": "2026-09-28T20:07:16.935523Z",
     "shell.execute_reply": "2026-09-28T20:07:16.935224Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(2026, 1, 999_999) < (2026, 3, 62_000)   # Q1 beats Q3 in revenue, but Q3 comes later"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ae08e64",
   "metadata": {},
   "source": [
    "This comparison behavior is useful for sorting lists of tuples or finding minimum and maximum values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "33d08ef3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.936403Z",
     "iopub.status.busy": "2026-09-28T20:07:16.936353Z",
     "iopub.status.idle": "2026-09-28T20:07:16.937758Z",
     "shell.execute_reply": "2026-09-28T20:07:16.937487Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Comparing and Sorting *(basic)*\n",
    "#   1. Given a list of (last_name, first_name) tuples below,\n",
    "#      sort it lexicographically (default tuple sort) and print the result.\n",
    "#   2. What does Python compare first: last name or first name?\n",
    "people = [('Smith', 'John'), ('Adams', 'Zara'), ('Smith', 'Alice'), ('Adams', 'Alan')]\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "5c7253c7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.938573Z",
     "iopub.status.busy": "2026-09-28T20:07:16.938510Z",
     "iopub.status.idle": "2026-09-28T20:07:16.940166Z",
     "shell.execute_reply": "2026-09-28T20:07:16.939803Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('Adams', 'Alan'), ('Adams', 'Zara'), ('Smith', 'Alice'), ('Smith', 'John')]\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "\n",
    "people = [('Smith', 'John'), ('Adams', 'Zara'), ('Smith', 'Alice'), ('Adams', 'Alan')]\n",
    "print(sorted(people))\n",
    "# Python compares the first element (last name) first;\n",
    "# if equal, it compares the second element (first name).\n",
    "# Result: [('Adams', 'Alan'), ('Adams', 'Zara'), ('Smith', 'Alice'), ('Smith', 'John')]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0512b4f4",
   "metadata": {},
   "source": [
    "### Sorting with a Key Function\n",
    "\n",
    "Sorting a list of tuples compares the first elements first. To sort by a different field, pass a function as the optional `key` argument to `sorted`. The `key` function computes a **sort key** for each item."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-0512b4f4",
   "metadata": {},
   "source": [
    "```{index} key function, sort key\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "96593302",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.941118Z",
     "iopub.status.busy": "2026-09-28T20:07:16.941068Z",
     "iopub.status.idle": "2026-09-28T20:07:16.942790Z",
     "shell.execute_reply": "2026-09-28T20:07:16.942552Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('Ana', 91), ('Bob', 84), ('Chen', 77), ('Dee', 95)]"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "records = [('Dee', 95), ('Bob', 84), ('Ana', 91), ('Chen', 77)]\n",
    "sorted(records)          # sorts by name, the first element"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12a85957",
   "metadata": {},
   "source": [
    "(second_element)="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "id": "b8360c37",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.943621Z",
     "iopub.status.busy": "2026-09-28T20:07:16.943572Z",
     "iopub.status.idle": "2026-09-28T20:07:16.945366Z",
     "shell.execute_reply": "2026-09-28T20:07:16.945022Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('Chen', 77), ('Bob', 84), ('Ana', 91), ('Dee', 95)]"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def second_element(t):\n",
    "    return t[1]\n",
    "\n",
    "sorted(records, key=second_element)    # sorts by score"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "654c7083",
   "metadata": {},
   "source": [
    "The lowest score comes first and the highest comes last. If you only want the maximum, you don't need to sort: `max` also accepts a `key` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "id": "cce7e638",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.946236Z",
     "iopub.status.busy": "2026-09-28T20:07:16.946187Z",
     "iopub.status.idle": "2026-09-28T20:07:16.947691Z",
     "shell.execute_reply": "2026-09-28T20:07:16.947429Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('Dee', 95)"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "max(records, key=second_element)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "ce6f2abb",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.948640Z",
     "iopub.status.busy": "2026-09-28T20:07:16.948571Z",
     "iopub.status.idle": "2026-09-28T20:07:16.950098Z",
     "shell.execute_reply": "2026-09-28T20:07:16.949762Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Sorting with a Key Function *(applied)*\n",
    "#   Given the list of (product, price) tuples below:\n",
    "#   1. Sort the products by price, lowest first, and print the result.\n",
    "#   2. Print the most expensive product using max() with a key function.\n",
    "#   3. Print the cheapest product using min() with a key function.\n",
    "products = [('pen', 1.5), ('notebook', 3.25), ('stapler', 7.0), ('eraser', 0.75)]\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "73d2ebac",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.950913Z",
     "iopub.status.busy": "2026-09-28T20:07:16.950863Z",
     "iopub.status.idle": "2026-09-28T20:07:16.952481Z",
     "shell.execute_reply": "2026-09-28T20:07:16.952202Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('eraser', 0.75), ('pen', 1.5), ('notebook', 3.25), ('stapler', 7.0)]\n",
      "('stapler', 7.0)\n",
      "('eraser', 0.75)\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "products = [('pen', 1.5), ('notebook', 3.25), ('stapler', 7.0), ('eraser', 0.75)]\n",
    "\n",
    "def second_element(t):\n",
    "    return t[1]\n",
    "\n",
    "print(sorted(products, key=second_element))\n",
    "print(max(products, key=second_element))\n",
    "print(min(products, key=second_element))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67fd7e0c",
   "metadata": {},
   "source": [
    "## Tuples in Function Design\n",
    "\n",
    "Functions are one of the most common places to meet tuples. A function can return several results packed into one tuple, and it can accept any number of arguments, which Python collects into a tuple."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e647515",
   "metadata": {},
   "source": [
    "### Tuples as Return Values\n",
    "\n",
    "**A function returns one object**, but that object can be a tuple.\n",
    "Returning a tuple is a common way to provide **multiple** results.\n",
    "\n",
    "For example, a scheduling system may need to convert a total number of minutes into hours and minutes.\n",
    "The built-in function `divmod` combines `//` and `%`: it takes two numbers and returns a tuple with the quotient and the remainder."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-2e647515",
   "metadata": {},
   "source": [
    "```{index} multiple return values, divmod()\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "64b83062",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.953340Z",
     "iopub.status.busy": "2026-09-28T20:07:16.953289Z",
     "iopub.status.idle": "2026-09-28T20:07:16.954940Z",
     "shell.execute_reply": "2026-09-28T20:07:16.954607Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 15)"
      ]
     },
     "execution_count": 76,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "divmod(135, 60)    # 135 minutes \u2192 (2 hours, 15 minutes)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b5ce6cc",
   "metadata": {},
   "source": [
    "You can use tuple assignment to store the hours and minutes in separate variables."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "208be1eb",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.955862Z",
     "iopub.status.busy": "2026-09-28T20:07:16.955793Z",
     "iopub.status.idle": "2026-09-28T20:07:16.957479Z",
     "shell.execute_reply": "2026-09-28T20:07:16.957178Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hours, minutes = divmod(135, 60)\n",
    "hours"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "056b3902",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.958352Z",
     "iopub.status.busy": "2026-09-28T20:07:16.958301Z",
     "iopub.status.idle": "2026-09-28T20:07:16.960028Z",
     "shell.execute_reply": "2026-09-28T20:07:16.959691Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15"
      ]
     },
     "execution_count": 78,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "minutes"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b183272e",
   "metadata": {},
   "source": [
    "Here is a simple function that returns both the minimum and maximum from a list, which is useful for summarizing a range of values such as daily sales figures."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "a93d62d4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.960869Z",
     "iopub.status.busy": "2026-09-28T20:07:16.960818Z",
     "iopub.status.idle": "2026-09-28T20:07:16.962186Z",
     "shell.execute_reply": "2026-09-28T20:07:16.961874Z"
    }
   },
   "outputs": [],
   "source": [
    "def min_max(t):\n",
    "    return min(t), max(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "016fd796",
   "metadata": {},
   "source": [
    "`min` and `max` are built-in functions that find the smallest and largest elements of a sequence.\n",
    "`min_max` computes both and returns them as a tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "9c73a695",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.963031Z",
     "iopub.status.busy": "2026-09-28T20:07:16.962978Z",
     "iopub.status.idle": "2026-09-28T20:07:16.964687Z",
     "shell.execute_reply": "2026-09-28T20:07:16.964380Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(7200, 12400)"
      ]
     },
     "execution_count": 80,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "min_max([8500, 12400, 7200, 9900])    # daily sales figures"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9cd8b4e",
   "metadata": {},
   "source": [
    "You can unpack the result like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "c00f8d05",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.965581Z",
     "iopub.status.busy": "2026-09-28T20:07:16.965528Z",
     "iopub.status.idle": "2026-09-28T20:07:16.967205Z",
     "shell.execute_reply": "2026-09-28T20:07:16.966871Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(7200, 12400)"
      ]
     },
     "execution_count": 81,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "low, high = min_max([8500, 12400, 7200, 9900])\n",
    "low, high"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "id": "31aa277d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.967987Z",
     "iopub.status.busy": "2026-09-28T20:07:16.967938Z",
     "iopub.status.idle": "2026-09-28T20:07:16.969390Z",
     "shell.execute_reply": "2026-09-28T20:07:16.969008Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuples as Return Values *(applied)*\n",
    "#   Write a function called `stats` that takes a list of numbers and returns\n",
    "#   a tuple containing (min, max, sum, mean) of the list.\n",
    "#   Test it with [10, 20, 30, 40, 50] and unpack the result into four variables.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "id": "7f31585a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.970170Z",
     "iopub.status.busy": "2026-09-28T20:07:16.970118Z",
     "iopub.status.idle": "2026-09-28T20:07:16.971632Z",
     "shell.execute_reply": "2026-09-28T20:07:16.971379Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 50 150 30.0\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "def stats(numbers):\n",
    "    return min(numbers), max(numbers), sum(numbers), sum(numbers) / len(numbers)\n",
    "\n",
    "lo, hi, total, avg = stats([10, 20, 30, 40, 50])\n",
    "print(lo, hi, total, avg)    # 10 50 150 30.0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63cb9f0d",
   "metadata": {},
   "source": [
    "### Argument Packing\n",
    "\n",
    "Functions can take a variable number of arguments.\n",
    "A parameter name that begins with `*` **packs** extra arguments into a tuple.\n",
    "The following function takes any number of arguments and computes their arithmetic mean. (Python's `statistics` module already has a `mean` function; writing one here shows how packing works.)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-63cb9f0d",
   "metadata": {},
   "source": [
    "```{index} argument packing (*args), argument unpacking\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "id": "7a2beb89",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.972535Z",
     "iopub.status.busy": "2026-09-28T20:07:16.972474Z",
     "iopub.status.idle": "2026-09-28T20:07:16.973975Z",
     "shell.execute_reply": "2026-09-28T20:07:16.973570Z"
    }
   },
   "outputs": [],
   "source": [
    "def mean(*args):                # variable-length argument list\n",
    "    return sum(args) / len(args)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "514388b0",
   "metadata": {},
   "source": [
    "The parameter name can be anything, but `args` is conventional.\n",
    "Here is a call with four daily sales figures:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "047c3f54",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.974909Z",
     "iopub.status.busy": "2026-09-28T20:07:16.974859Z",
     "iopub.status.idle": "2026-09-28T20:07:16.976558Z",
     "shell.execute_reply": "2026-09-28T20:07:16.976283Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9500.0"
      ]
     },
     "execution_count": 85,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mean(8500, 12400, 7200, 9900)    # average of four daily sales figures"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc83e72d",
   "metadata": {},
   "source": [
    "If you have a sequence of values and want to pass them as separate arguments, use `*` to **unpack** the sequence.\n",
    "For example, `divmod` takes exactly two arguments - if you pass a tuple, it counts as a single argument and raises an error."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "4f91f0f5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.977397Z",
     "iopub.status.busy": "2026-09-28T20:07:16.977346Z",
     "iopub.status.idle": "2026-09-28T20:07:16.979049Z",
     "shell.execute_reply": "2026-09-28T20:07:16.978757Z"
    }
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "divmod expected 2 arguments, got 1",
     "output_type": "error",
     "traceback": [
      "\u001b[31mTypeError\u001b[39m\u001b[31m:\u001b[39m divmod expected 2 arguments, got 1\n"
     ]
    }
   ],
   "source": [
    "%%expect TypeError\n",
    "t = (135, 60)\n",
    "divmod(t)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17ad85eb",
   "metadata": {},
   "source": [
    "Even though the tuple contains two elements, it is still one argument.\n",
    "If you unpack it, the two elements are passed separately."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "id": "9390dce8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.979899Z",
     "iopub.status.busy": "2026-09-28T20:07:16.979840Z",
     "iopub.status.idle": "2026-09-28T20:07:16.981565Z",
     "shell.execute_reply": "2026-09-28T20:07:16.981211Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 15)"
      ]
     },
     "execution_count": 87,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "divmod(*t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18a060b9",
   "metadata": {},
   "source": [
    "Packing and unpacking are handy when you want to adapt an existing function.\n",
    "For example, this function takes any number of arguments, removes the lowest and highest, and computes the mean of the rest."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "ae6c283a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.982527Z",
     "iopub.status.busy": "2026-09-28T20:07:16.982462Z",
     "iopub.status.idle": "2026-09-28T20:07:16.983960Z",
     "shell.execute_reply": "2026-09-28T20:07:16.983660Z"
    }
   },
   "outputs": [],
   "source": [
    "def trimmed_mean(*args):\n",
    "    low, high = min_max(args)\n",
    "    trimmed = list(args)        # convert to a list for mutability\n",
    "    trimmed.remove(low)\n",
    "    trimmed.remove(high)\n",
    "    return mean(*trimmed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb767fee",
   "metadata": {},
   "source": [
    "First it uses `min_max` to find the lowest and highest values.\n",
    "Then it converts `args` to a list so it can use `remove`.\n",
    "Finally it **unpacks** the trimmed list so the elements are passed to `mean` as separate arguments.\n",
    "\n",
    "Here is an example using judge scores, where one score is unusually low:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "55f07796",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.984816Z",
     "iopub.status.busy": "2026-09-28T20:07:16.984766Z",
     "iopub.status.idle": "2026-09-28T20:07:16.986400Z",
     "shell.execute_reply": "2026-09-28T20:07:16.986138Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "7.75"
      ]
     },
     "execution_count": 89,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mean(8.5, 9.2, 8.8, 4.5)    # raw mean including the outlier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "id": "a655f1ea",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.987195Z",
     "iopub.status.busy": "2026-09-28T20:07:16.987145Z",
     "iopub.status.idle": "2026-09-28T20:07:16.988811Z",
     "shell.execute_reply": "2026-09-28T20:07:16.988516Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "8.65"
      ]
     },
     "execution_count": 90,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "trimmed_mean(8.5, 9.2, 8.8, 4.5)    # mean after dropping lowest (4.5) and highest (9.2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32edf06a",
   "metadata": {},
   "source": [
    "This kind of trimmed mean is used in sports with subjective judging (like diving and gymnastics) to reduce the influence of outlier scores."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8d915ad",
   "metadata": {},
   "source": [
    "You can also unpack sequences to build new tuples (or lists) without using `+`.\n",
    "For example, merging two quarterly sales tuples into a single half-year tuple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "id": "4a12c0ed",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.989833Z",
     "iopub.status.busy": "2026-09-28T20:07:16.989759Z",
     "iopub.status.idle": "2026-09-28T20:07:16.991530Z",
     "shell.execute_reply": "2026-09-28T20:07:16.991258Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(112, 98, 135, 144, 101, 129)"
      ]
     },
     "execution_count": 91,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q1 = (112, 98, 135)    # monthly sales for Q1\n",
    "q2 = (144, 101, 129)   # monthly sales for Q2\n",
    "h1 = (*q1, *q2)        # combined first-half data\n",
    "h1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "id": "c8ebef39",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.992373Z",
     "iopub.status.busy": "2026-09-28T20:07:16.992311Z",
     "iopub.status.idle": "2026-09-28T20:07:16.993797Z",
     "shell.execute_reply": "2026-09-28T20:07:16.993460Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Argument Packing *(applied)*\n",
    "#   1. Write a function total_sales(*amounts) that returns the sum of any\n",
    "#      number of sales amounts.\n",
    "#   2. Call it with 112, 98, and 135 and print the result.\n",
    "#   3. Call it again by unpacking the tuple q2 = (144, 101, 129) with *.\n",
    "q2 = (144, 101, 129)\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "id": "e74e2c98",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.994671Z",
     "iopub.status.busy": "2026-09-28T20:07:16.994609Z",
     "iopub.status.idle": "2026-09-28T20:07:16.996283Z",
     "shell.execute_reply": "2026-09-28T20:07:16.996009Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "345\n",
      "374\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "def total_sales(*amounts):\n",
    "    return sum(amounts)\n",
    "\n",
    "q2 = (144, 101, 129)\n",
    "print(total_sales(112, 98, 135))\n",
    "print(total_sales(*q2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79978386",
   "metadata": {},
   "source": [
    "## Tuples as Records\n",
    "\n",
    "A **record** is a group of related values that describe one thing, such as one sale or one student. Tuples fit records well: the fields stay together, their order is fixed, and the record cannot be changed by accident. A list of tuples works like a small table, with one tuple per row."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-79978386",
   "metadata": {},
   "source": [
    "```{index} record\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d62dcba6",
   "metadata": {},
   "source": [
    "### Lists of Tuples\n",
    "\n",
    "Data often arrives as lines of text. Each line below describes one sale: the region, the month, and the amount."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-d62dcba6",
   "metadata": {},
   "source": [
    "```{index} list of tuples\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "id": "5b0dc7b5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:16.997348Z",
     "iopub.status.busy": "2026-09-28T20:07:16.997283Z",
     "iopub.status.idle": "2026-09-28T20:07:16.999459Z",
     "shell.execute_reply": "2026-09-28T20:07:16.999083Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('North', 'Jan', 112),\n",
       " ('South', 'Jan', 98),\n",
       " ('North', 'Feb', 135),\n",
       " ('West', 'Jan', 144),\n",
       " ('South', 'Feb', 101)]"
      ]
     },
     "execution_count": 94,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lines = [\n",
    "    'North,Jan,112',\n",
    "    'South,Jan,98',\n",
    "    'North,Feb,135',\n",
    "    'West,Jan,144',\n",
    "    'South,Feb,101',\n",
    "]\n",
    "\n",
    "sales = []\n",
    "for line in lines:\n",
    "    region, month, amount = line.split(',')\n",
    "    sales.append((region, month, int(amount)))\n",
    "\n",
    "sales"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f998c26",
   "metadata": {},
   "source": [
    "Each row is now a `(region, month, amount)` tuple. You can loop over the rows with unpacking:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "id": "abbba63c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.000435Z",
     "iopub.status.busy": "2026-09-28T20:07:17.000321Z",
     "iopub.status.idle": "2026-09-28T20:07:17.001855Z",
     "shell.execute_reply": "2026-09-28T20:07:17.001591Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "North Jan 112\n",
      "South Jan 98\n",
      "North Feb 135\n",
      "West Jan 144\n",
      "South Feb 101\n"
     ]
    }
   ],
   "source": [
    "for region, month, amount in sales:\n",
    "    print(region, month, amount)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "deb02a93",
   "metadata": {},
   "source": [
    "You can also filter the rows with a list comprehension and sort them with a `key` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "id": "1439bc36",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.002735Z",
     "iopub.status.busy": "2026-09-28T20:07:17.002684Z",
     "iopub.status.idle": "2026-09-28T20:07:17.004328Z",
     "shell.execute_reply": "2026-09-28T20:07:17.003985Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('North', 'Jan', 112), ('North', 'Feb', 135)]\n",
      "[('North', 'Feb', 135), ('West', 'Jan', 144)]\n"
     ]
    }
   ],
   "source": [
    "north = [row for row in sales if row[0] == 'North']\n",
    "print(north)\n",
    "\n",
    "def amount_of(row):\n",
    "    return row[2]\n",
    "\n",
    "print(sorted(sales, key=amount_of)[-2:])    # the two largest sales, smallest first"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "id": "9e1933c6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.005118Z",
     "iopub.status.busy": "2026-09-28T20:07:17.005066Z",
     "iopub.status.idle": "2026-09-28T20:07:17.006641Z",
     "shell.execute_reply": "2026-09-28T20:07:17.006253Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Lists of Tuples *(basic)*\n",
    "#   Given lines = ['Ana,91', 'Bob,84', 'Chen,77', 'Dee,95']:\n",
    "#   1. Build a list of (name, score) tuples called students,\n",
    "#      converting each score to an int.\n",
    "#   2. Use a list comprehension to print the names of students\n",
    "#      who scored 85 or above.\n",
    "#   3. Print the student with the lowest score using min() with a key function.\n",
    "lines = ['Ana,91', 'Bob,84', 'Chen,77', 'Dee,95']\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "id": "409fe3bf",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.007570Z",
     "iopub.status.busy": "2026-09-28T20:07:17.007484Z",
     "iopub.status.idle": "2026-09-28T20:07:17.009511Z",
     "shell.execute_reply": "2026-09-28T20:07:17.009186Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Ana', 'Dee']\n",
      "('Chen', 77)\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "lines = ['Ana,91', 'Bob,84', 'Chen,77', 'Dee,95']\n",
    "\n",
    "students = []\n",
    "for line in lines:\n",
    "    name, score = line.split(',')\n",
    "    students.append((name, int(score)))\n",
    "\n",
    "print([name for name, score in students if score >= 85])\n",
    "\n",
    "def score_of(student):\n",
    "    return student[1]\n",
    "\n",
    "print(min(students, key=score_of))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23b626ca",
   "metadata": {},
   "source": [
    "### Named Tuples\n",
    "\n",
    "Positions such as `row[2]` are easy to mix up: nothing in the code says that index 2 is the amount. A **named tuple** is a tuple whose fields also have names. The `namedtuple` function from the `collections` module creates a new tuple type with the field names you choose."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-23b626ca",
   "metadata": {},
   "source": [
    "```{index} namedtuple()\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "id": "3b14265f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.010494Z",
     "iopub.status.busy": "2026-09-28T20:07:17.010445Z",
     "iopub.status.idle": "2026-09-28T20:07:17.012274Z",
     "shell.execute_reply": "2026-09-28T20:07:17.011958Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sale(region='North', month='Jan', amount=112)\n",
      "112\n",
      "112\n"
     ]
    }
   ],
   "source": [
    "from collections import namedtuple\n",
    "\n",
    "Sale = namedtuple('Sale', ['region', 'month', 'amount'])\n",
    "\n",
    "s = Sale('North', 'Jan', 112)\n",
    "print(s)\n",
    "print(s.amount)       # access by name\n",
    "print(s[2])           # access by position still works"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8443e06",
   "metadata": {},
   "source": [
    "A named tuple is still a tuple: it supports indexing, unpacking, and comparison. Converting the rows makes the code easier to read:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "id": "ac06c387",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.013145Z",
     "iopub.status.busy": "2026-09-28T20:07:17.013091Z",
     "iopub.status.idle": "2026-09-28T20:07:17.014831Z",
     "shell.execute_reply": "2026-09-28T20:07:17.014520Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "North Jan 112\n",
      "North Feb 135\n",
      "West Jan 144\n",
      "South Feb 101\n"
     ]
    }
   ],
   "source": [
    "sale_rows = [Sale(region, month, amount) for region, month, amount in sales]\n",
    "\n",
    "for s in sale_rows:\n",
    "    if s.amount > 100:\n",
    "        print(s.region, s.month, s.amount)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e3abd2d",
   "metadata": {},
   "source": [
    "Like any tuple, a named tuple cannot be changed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "id": "f798633d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.016017Z",
     "iopub.status.busy": "2026-09-28T20:07:17.015948Z",
     "iopub.status.idle": "2026-09-28T20:07:17.017796Z",
     "shell.execute_reply": "2026-09-28T20:07:17.017483Z"
    }
   },
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "can't set attribute",
     "output_type": "error",
     "traceback": [
      "\u001b[31mAttributeError\u001b[39m\u001b[31m:\u001b[39m can't set attribute\n"
     ]
    }
   ],
   "source": [
    "%%expect AttributeError\n",
    "s.amount = 200"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61b5f446",
   "metadata": {},
   "source": [
    "You will meet named tuples again in data work: the pandas method `DataFrame.itertuples()` returns each row of a table as a named tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "id": "375186cd",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.018762Z",
     "iopub.status.busy": "2026-09-28T20:07:17.018702Z",
     "iopub.status.idle": "2026-09-28T20:07:17.020225Z",
     "shell.execute_reply": "2026-09-28T20:07:17.019886Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Named Tuples *(applied)*\n",
    "#   1. Create a named tuple type Student with the fields name and score.\n",
    "#   2. Convert students = [('Ana', 91), ('Bob', 84), ('Chen', 77), ('Dee', 95)]\n",
    "#      into a list of Student named tuples.\n",
    "#   3. Print the name of the top student using max() with a key function\n",
    "#      that returns the score field.\n",
    "students = [('Ana', 91), ('Bob', 84), ('Chen', 77), ('Dee', 95)]\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "id": "7088d3df",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.021096Z",
     "iopub.status.busy": "2026-09-28T20:07:17.021046Z",
     "iopub.status.idle": "2026-09-28T20:07:17.022913Z",
     "shell.execute_reply": "2026-09-28T20:07:17.022613Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dee\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "from collections import namedtuple\n",
    "\n",
    "Student = namedtuple('Student', ['name', 'score'])\n",
    "students = [('Ana', 91), ('Bob', 84), ('Chen', 77), ('Dee', 95)]\n",
    "\n",
    "roster = [Student(name, score) for name, score in students]\n",
    "\n",
    "def score_of(student):\n",
    "    return student.score\n",
    "\n",
    "print(max(roster, key=score_of).name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04d3fc4b",
   "metadata": {},
   "source": [
    "### Worked Example: Summarizing Sales Records\n",
    "\n",
    "The task is to report the total and average sale, the smallest and largest sale, and the two largest sales from `sale_rows`, using a function that returns several results as a tuple."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "idx-04d3fc4b",
   "metadata": {},
   "source": [
    "```{index} namedtuple(), multiple return values, key function\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "id": "f81e144b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.023870Z",
     "iopub.status.busy": "2026-09-28T20:07:17.023795Z",
     "iopub.status.idle": "2026-09-28T20:07:17.026194Z",
     "shell.execute_reply": "2026-09-28T20:07:17.025826Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total: 590\n",
      "Average: 118.0\n",
      "Smallest: South Jan 98\n",
      "Largest: West Jan 144\n",
      "Top two: [('West', 'Jan'), ('North', 'Feb')]\n"
     ]
    }
   ],
   "source": [
    "def amount_key(sale):\n",
    "    return sale.amount\n",
    "\n",
    "def summarize(rows):\n",
    "    amounts = [sale.amount for sale in rows]\n",
    "    total = sum(amounts)\n",
    "    average = total / len(amounts)\n",
    "    smallest = min(rows, key=amount_key)\n",
    "    largest = max(rows, key=amount_key)\n",
    "    return total, average, smallest, largest\n",
    "\n",
    "total, average, smallest, largest = summarize(sale_rows)\n",
    "top_two = sorted(sale_rows, key=amount_key)[::-1][:2]\n",
    "\n",
    "print(f'Total: {total}')\n",
    "print(f'Average: {round(average, 1)}')\n",
    "print(f'Smallest: {smallest.region} {smallest.month} {smallest.amount}')\n",
    "print(f'Largest: {largest.region} {largest.month} {largest.amount}')\n",
    "print('Top two:', [(s.region, s.month) for s in top_two])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1354dadc",
   "metadata": {},
   "source": [
    "Each step uses a tool from this section:\n",
    "\n",
    "- Named tuples make fields such as `sale.amount` readable.\n",
    "- A list comprehension collects the amounts, and `sum()` and `len()` compute the total and average.\n",
    "- `min()` and `max()` with a `key` function find whole records, not just amounts.\n",
    "- `summarize` returns four results as one tuple, and tuple assignment unpacks them.\n",
    "- `sorted()` with a `key`, followed by slicing, ranks the records."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "id": "86500fb9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.027080Z",
     "iopub.status.busy": "2026-09-28T20:07:17.027017Z",
     "iopub.status.idle": "2026-09-28T20:07:17.028404Z",
     "shell.execute_reply": "2026-09-28T20:07:17.028138Z"
    },
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: When to Use Tuples *(applied)*\n",
    "#   For each scenario below, decide whether a tuple or a list is more appropriate\n",
    "#   and implement it:\n",
    "#   1. Store the (latitude, longitude) coordinates of a fixed location: 38.9, -77.0\n",
    "#   2. Build a collection of city names that will have cities added to it over time.\n",
    "#      Start with ['Paris', 'Tokyo'] and append 'Nairobi'.\n",
    "#   3. Store holidays as a list of (month, day, name) tuples:\n",
    "#      (1, 1, 'New Year') and (7, 4, 'Independence Day').\n",
    "#      Loop over the list with unpacking and print the name of the\n",
    "#      holiday on July 4.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "id": "a8f708f3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-28T20:07:17.029221Z",
     "iopub.status.busy": "2026-09-28T20:07:17.029172Z",
     "iopub.status.idle": "2026-09-28T20:07:17.030914Z",
     "shell.execute_reply": "2026-09-28T20:07:17.030539Z"
    },
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(38.9, -77.0)\n",
      "['Paris', 'Tokyo', 'Nairobi']\n",
      "Independence Day\n"
     ]
    }
   ],
   "source": [
    "### Solution\n",
    "# 1. Tuple: a fixed record that won't change\n",
    "location = (38.9, -77.0)\n",
    "print(location)\n",
    "\n",
    "# 2. List: it will grow over time\n",
    "cities = ['Paris', 'Tokyo']\n",
    "cities.append('Nairobi')\n",
    "print(cities)\n",
    "\n",
    "# 3. Tuples as fixed records, stored in a list\n",
    "holidays = [(1, 1, 'New Year'), (7, 4, 'Independence Day')]\n",
    "for month, day, name in holidays:\n",
    "    if (month, day) == (7, 4):\n",
    "        print(name)    # Independence Day"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7e41955",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "- A tuple is an ordered, immutable sequence. Create one with commas (a trailing comma for a single item) or with `tuple()`.\n",
    "- Indexing, slicing, `+`, `*`, `in`, `count()`, `index()`, and built-in functions such as `len()` and `sorted()` work with tuples.\n",
    "- A tuple's items cannot be replaced, but a mutable item inside it, such as a list, can still change.\n",
    "- Tuples are safe to pass to functions; to produce new values, return a new tuple.\n",
    "- Tuple assignment and starred unpacking assign several names at once, and `zip()` and `enumerate()` produce tuples.\n",
    "- Tuples compare element by element; `sorted()`, `min()`, and `max()` accept a `key` function to compare by a chosen field.\n",
    "- A function can return several results as a tuple, and `*args` packs any number of arguments into a tuple.\n",
    "- A list of tuples works like a table of records, and named tuples give each field a name."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7976b894",
   "metadata": {},
   "source": [
    "## Further Reading\n",
    "\n",
    "- [Tuples and Sequences](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences), Python Tutorial\n",
    "- [Sequence Types: list, tuple, range](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range), Python Standard Library documentation\n",
    "- [`collections.namedtuple`](https://docs.python.org/3/library/collections.html#collections.namedtuple), Python Standard Library documentation\n",
    "- [Sorting Techniques: Key Functions](https://docs.python.org/3/howto/sorting.html#key-functions), Python HOWTOs"
   ]
  }
 ],
 "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.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
