{
 "cells": [
  {
   "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, set, etc.) into a tuple. | `tuple([1, 2])` returns `(1, 2)`. |\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6200fc87",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "# Find project root by looking for _config.yml\n",
    "current = Path.cwd()\n",
    "for parent in [current, *current.parents]:\n",
    "    if (parent / '_config.yml').exists():\n",
    "        project_root = parent\n",
    "        break\n",
    "else:\n",
    "    project_root = Path.cwd().parent.parent\n",
    "\n",
    "# Add project root to path\n",
    "sys.path.insert(0, str(project_root))\n",
    "\n",
    "# Import shared teaching helpers and cell magics\n",
    "from shared import thinkpython, diagram, jupyturtle, structshape\n",
    "from shared.download import download\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6cb8476d",
   "metadata": {},
   "source": [
    "## Tuple Operators"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9944c879",
   "metadata": {},
   "source": [
    "The `+` operator concatenates tuples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "id": "34778c59",
   "metadata": {},
   "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": null,
   "id": "b3cf5a61",
   "metadata": {},
   "outputs": [],
   "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": 138,
   "id": "fd858bfe",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('Q1', 'Q2', 'Q3', 'Q4', 'Q1', 'Q2', 'Q3', 'Q4')"
      ]
     },
     "execution_count": 138,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "('Q1', 'Q2', 'Q3', 'Q4') * 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "92cae294",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuple Operators\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": 38,
   "id": "3e7afa90",
   "metadata": {
    "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": "c941fce0",
   "metadata": {},
   "source": [
    "Based on these examples, tuples can look a lot like lists."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d1aa8aa",
   "metadata": {},
   "source": [
    "## Tuple Methods\n",
    "\n",
    "Tuples have a small set of methods. Tuples are:\n",
    "\n",
    "- Immutable (cannot be changed)\n",
    "- Fixed-size\n",
    "- Designed for safety and structure\n",
    "\n",
    "Because you can't modify them, they don’t have methods like:\n",
    "- .append()\n",
    "- .remove()\n",
    "- .sort(). \n",
    " \n",
    "The most common are `count` and `index`, which return information rather than modifying the tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 139,
   "id": "17512c9d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 3)"
      ]
     },
     "execution_count": 139,
     "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": 40,
   "id": "382e406c",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuple Methods\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": 41,
   "id": "8bece928",
   "metadata": {
    "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": "fd7ae0d8",
   "metadata": {},
   "source": [
    "The `sorted` function works with tuples, but it returns a `list`, which is mutable for your manipulation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 150,
   "id": "82f42e9d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('RECEIVED', 'PENDING', 'SHIPPED', 'DELIVERED')\n",
      "<class 'tuple'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "['DELIVERED', 'PENDING', 'RECEIVED', 'SHIPPED']"
      ]
     },
     "execution_count": 150,
     "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": null,
   "id": "1dffc1ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%expect AttributeError\n",
    "orders.sort()  # this doesn't work because tuples are immutable\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": 146,
   "id": "373582a9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<reversed object at 0x10edf9a80>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "('DELIVERED', 'PENDING', 'SHIPPED', 'PENDING')"
      ]
     },
     "execution_count": 146,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(reversed(orders))\n",
    "tuple(reversed(orders))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 155,
   "id": "eff9f464",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('RECEIVED', 'PENDING', 'SHIPPED', 'DELIVERED')"
      ]
     },
     "execution_count": 155,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# original object is unchanged\n",
    "orders"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "019bbb85",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Tuple Functions\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": 48,
   "id": "123790d3",
   "metadata": {
    "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": "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 we record their actual sales alongside those targets in two separate lists."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 157,
   "id": "9cb8116e",
   "metadata": {},
   "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": [
    "Let's count how many weeks the team exceeded their target.\n",
    "We'll use `zip`, a built-in function that combines sequences and returns a **zip object**, pairing elements like the teeth of a zipper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 174,
   "id": "815cc4d3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<zip at 0x10eefc700>"
      ]
     },
     "execution_count": 174,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "zip(actual, target)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8d7649c",
   "metadata": {},
   "source": [
    "`zip` stops when the shortest input is exhausted. If you want to keep going, use `itertools.zip_longest`. The parameter **fillvalue** can be helpful filling the empty elements."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 176,
   "id": "eaf44ca6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('a', 1), ('b', 2), ('c', '-')]"
      ]
     },
     "execution_count": 176,
     "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": [
    "We can loop over the zip object to get pairwise values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 164,
   "id": "b4b7150e",
   "metadata": {},
   "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",
    "We can unpack those values and count the weeks where actual sales exceeded the target:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16642aa9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 53,
     "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": 169,
   "id": "114766fc",
   "metadata": {},
   "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": 169,
     "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 we can get the last week's data like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 224,
   "id": "79867eb7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(88, 120)"
      ]
     },
     "execution_count": 224,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "weekly_results[-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4bd9afd4",
   "metadata": {},
   "source": [
    "If you have a list of keys and a list of values, you can use `zip` and `dict` to build a dictionary.\n",
    "Here is a mapping from each letter to its position in the alphabet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 173,
   "id": "700e2ee1",
   "metadata": {},
   "outputs": [],
   "source": [
    "letters = 'abcdefghijklmnopqrstuvwxyz'\n",
    "numbers = range(len(letters))\n",
    "letter_map = dict(zip(letters, numbers))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d864de84",
   "metadata": {},
   "source": [
    "Now we can look up a letter and get its index in the alphabet.\n",
    "In this mapping, the index of `'a'` is `0` and the index of `'z'` is `25`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 172,
   "id": "60d074ea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0, 25)"
      ]
     },
     "execution_count": 172,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "letter_map['a'], letter_map['z']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bbe253a8",
   "metadata": {},
   "source": [
    "## `enumerate()`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74e320d9",
   "metadata": {},
   "source": [
    "If you need to loop through elements and their indices, use Python the built-in function `enumerate`. The result is an enumerate object that yields pairs containing an index (starting at 0) and the corresponding element, which you then **unpack** into variables to use."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 126,
   "id": "48785c37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 apple\n",
      "1 banana\n",
      "2 cherry\n"
     ]
    }
   ],
   "source": [
    "fruits = ['apple', 'banana', 'cherry']\n",
    "\n",
    "for i, fruit in enumerate(fruits):\n",
    "    print(i, fruit)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "823a10af",
   "metadata": {},
   "source": [
    "If you want indices to start at 1 (like line numbers), pass the optional `start` argument."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 124,
   "id": "6ce7cd97",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 a\n",
      "2 b\n",
      "3 c\n"
     ]
    }
   ],
   "source": [
    "for index, element in enumerate('abc', start=1):\n",
    "    print(index, element)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "7dc880c4",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Zip\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 dictionary from these two lists using zip and dict().\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": 67,
   "id": "5bda66b3",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alice 88\n",
      "Bob 95\n",
      "Carol 72\n",
      "{'Alice': 88, 'Bob': 95, '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",
    "scoreboard = dict(zip(names, scores))\n",
    "print(scoreboard)\n",
    "\n",
    "for rank, name in enumerate(names, start=1):\n",
    "    print(rank, name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be14b0aa",
   "metadata": {},
   "source": [
    "## Comparing and Sorting\n",
    "\n",
    "Relational operators 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, we can represent a reporting period as a `(year, quarter)` tuple and compare two periods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 177,
   "id": "6f143fb9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 177,
     "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": 178,
   "id": "38d79bd4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 178,
     "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.\n",
    "As an example, let's find the most common letter in a word — a step often used in text analytics.\n",
    "In the previous chapter, we wrote `value_counts`, which returns a dictionary mapping each letter to its count."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 190,
   "id": "64d8a296",
   "metadata": {},
   "outputs": [],
   "source": [
    "def value_counts(string):\n",
    "    counter = {}\n",
    "    for letter in string:\n",
    "        if letter not in counter:\n",
    "            counter[letter] = 1\n",
    "        else:\n",
    "            counter[letter] += 1\n",
    "    return counter"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34c6e9f1",
   "metadata": {},
   "source": [
    "Here is the result for the string `'mississippi'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 191,
   "id": "f3d047cf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'m': 1, 'i': 4, 's': 4, 'p': 2}"
      ]
     },
     "execution_count": 191,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counter = value_counts('mississippi')\n",
    "counter"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b74cddce",
   "metadata": {},
   "source": [
    "With eight distinct letters, it's not immediately obvious which one appears most often.\n",
    "Sorting the items makes the answer clear.\n",
    "\n",
    "We can get the items from `counter` like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 192,
   "id": "b38e0a7f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dict_items([('m', 1), ('i', 4), ('s', 4), ('p', 2)])"
      ]
     },
     "execution_count": 192,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "items = counter.items()\n",
    "items"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d388df19",
   "metadata": {},
   "source": [
    "The result is a `dict_items` object that behaves like a list of tuples, so we can sort it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 193,
   "id": "587d88e5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('i', 4), ('m', 1), ('p', 2), ('s', 4)]"
      ]
     },
     "execution_count": 193,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted(items)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "33d08ef3",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Comparing and Sorting\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": 75,
   "id": "5c7253c7",
   "metadata": {
    "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": "0d6a333e",
   "metadata": {},
   "source": [
    "## Sorting by Value\n",
    "\n",
    "Sometimes you want to sort dictionary items by their values rather than their keys.\n",
    "We can define a small helper that returns the second element of a `(key, value)` pair."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b533288e",
   "metadata": {},
   "source": [
    "(second_element)="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "c75fa385",
   "metadata": {},
   "outputs": [],
   "source": [
    "def second_element(t):\n",
    "    return t[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ee375ee",
   "metadata": {},
   "source": [
    "Then we pass that function as the optional `key` argument to `sorted`.\n",
    "The `key` function computes a **sort key** for each item."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "ae127f5d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('b', 1), ('n', 2), ('a', 3)]"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted_items = sorted(items, key=second_element)\n",
    "sorted_items"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a250cd2",
   "metadata": {},
   "source": [
    "The sort key determines the order.\n",
    "The letter with the lowest count appears first, and the highest count appears last.\n",
    "So we can find the most common letter like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "ca98f67c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('a', 3)"
      ]
     },
     "execution_count": 78,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted_items[-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "896af134",
   "metadata": {},
   "source": [
    "If we only want the maximum, we don't have to sort the list.\n",
    "We can use `max`, which also accepts a `key` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "6f8dc73d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('a', 3)"
      ]
     },
     "execution_count": 79,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "max(items, key=second_element)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "c1be6eb0",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Sorting by Value\n",
    "#   1. Count the letter frequencies in the word 'mississippi'.\n",
    "#   2. Sort the resulting items by frequency (value), ascending.\n",
    "#   3. Print the letter with the highest frequency using max() with a key function.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "2d68c835",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('m', 1), ('p', 2), ('i', 4), ('s', 4)]\n",
      "('i', 4)\n"
     ]
    }
   ],
   "source": [
    "### solution\n",
    "\n",
    "counter = value_counts('mississippi')\n",
    "items = counter.items()\n",
    "\n",
    "def second_element(t):\n",
    "    return t[1]\n",
    "\n",
    "print(sorted(items, key=second_element))   # sorted by frequency ascending\n",
    "print(max(items, key=second_element))      # ('i', 4) — most frequent letter"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28ce6533",
   "metadata": {},
   "source": [
    "To find the letter with the lowest count, we could use `min` the same way."
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
