{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "551a4ae6",
   "metadata": {},
   "source": [
    "# Algorithms\n",
    "\n",
    "This section introduces algorithm foundations using a simple input-process-output model and more."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02ab7628",
   "metadata": {},
   "source": [
    "## What Is an Algorithm?\n",
    "\n",
    "In short, an algorithm is a **finite**, **ordered set of steps** that transforms **input** into **output**. A useful mental model for algorithm therefore is:\n",
    "- **Input**: data provided to solve a problem\n",
    "- **Process**: the steps that operate on the data\n",
    "- **Output**: the result produced by the steps\n",
    "\n",
    "A useful everyday analogy is a **recipe**: it specifies ingredients (inputs), a sequence of steps (instructions), and produces a dish (output). Like a recipe, an algorithm must eventually stop — it cannot run forever."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16890bcd",
   "metadata": {},
   "source": [
    "For example, an algorithm can be designed to look through an array (in general sense) to find the largest value in an array; in this case, a Python `list`. Here we are pretending that the `max()` function does not exist. So, for the example, let's say we want to go through the values in the array one by one. Check if the current value is the highest so far, and if it is, store it, then keep going until the end of the array. After looking at all the values, the stored value will be the highest of all values in the array.\n",
    "\n",
    "A sketchy algorithm of this process as we just described may look like:\n",
    "\n",
    "1. Go through the elements and get the values in the array one by one.\n",
    "2. Check if the current value is the highest so far, and if it is, store it.\n",
    "3. After looking at all the values, the stored value will be the highest of all values in the array."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ea99a41",
   "metadata": {},
   "source": [
    ":::{admonition} **Why Algorithms in Python?**\n",
    "\n",
    "So by now you are probably thinking: Since we have many built-in methods (`max()` in this case) that serve as algorithms already, why do we still need to learn algorithms in Python? Here's why:\n",
    "\n",
    "**Built-in Algorithms**:\n",
    "Built-in methods like `max()`, `sorted()`, and `sum()` are convenient, but they are not a replacement for understanding algorithms. These methods are pre-implemented algorithms. Knowing how they work under the hood helps you understand their time and space complexity and recognize when they may be suboptimal or fail entirely. For example, `max(my_list)` still scans every element at $O(n)$ and it does not magically run faster just because Python provides it.\n",
    "\n",
    "**Problem Coverage**:\n",
    "Python built-ins do not cover all problem scenarios. Real-world challenges such as finding the shortest path in a network, scheduling tasks under constraints, recommending similar users, or detecting fraud patterns have no ready-made Python method. Solving them requires you to design and reason about algorithms from the ground up.\n",
    "\n",
    "**Algorithmic Thinking**:\n",
    "Even when built-ins exist, algorithmic thinking determines whether you can use them correctly and efficiently. Consider the difference between checking membership in a list versus a set: both work, but one runs in $O(n)$ and the other in $O(1)$. Without understanding the underlying algorithms, you cannot make that distinction, which matters enormously at scale. Similarly, calling `max()` inside a loop without realizing it re-scans the entire list each iteration can silently turn an $O(n)$ problem into an $O(n^2)$ one.\n",
    "\n",
    "**Problem-Solving**:\n",
    "Finally, algorithms are the foundation of problem-solving and computational thinking. Built-in methods are tools, and algorithmic knowledge is what tells you which tool to use, why it works, what its limits are, and what to do when no tool exists. This is why most technical interviews include algorithm questions.\n",
    "\n",
    "Overall, understanding algorithms therefore means understanding what those tools are actually doing, when they are fast or slow, and what to do when no built-in exists for your problem.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd2a65d0",
   "metadata": {},
   "source": [
    "With the algorithm formulated, you may implement the algorithm in a specific programming language such as Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "a5f03013",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-19T03:09:23.265246Z",
     "iopub.status.busy": "2026-03-19T03:09:23.265016Z",
     "iopub.status.idle": "2026-03-19T03:09:23.270602Z",
     "shell.execute_reply": "2026-03-19T03:09:23.270117Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input : [12, 4, 19, 7, 3]\n",
      "output: 19\n"
     ]
    }
   ],
   "source": [
    "def max_value(nums):            ### input the array\n",
    "    if not nums:                ### If the list is empty, return None\n",
    "        return None             ### a \"guard clause\" to handle empty lists\n",
    "\n",
    "    current_max = nums[0]       ### Initialize current_max to the first element of the list\n",
    "    for x in nums[1:]:          ### Iterate through the list starting from the second element\n",
    "        if x > current_max:     ### If the current element is greater than current_max, update current_max\n",
    "            current_max = x\n",
    "    return current_max          ### Return the maximum value found in the list\n",
    "\n",
    "sample = [12, 4, 19, 7, 3]\n",
    "print('input :', sample)\n",
    "print('output:', max_value(sample))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a489df5a",
   "metadata": {},
   "source": [
    "And this is probably how the Python `max()` function is defined. To make sure, the [Python Standard Library] has a [description](https://docs.python.org/3/library/functions.html#max) of the `max()` function and the implementation is in `C` and can be found at [github](https://github.com/python/cpython/blob/main/Python/bltinmodule.c#L2166). A emulation of that [implementation in Python](https://github.com/python/cpython/blob/main/Python/bltinmodule.c#L2166) looks like this below (partial code). As you can see, the code are very comparable. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24164987",
   "metadata": {},
   "source": [
    "```python\n",
    "def mymax_backend(      ### T is a generic type; works with any comparable type (int, str, etc.)  \n",
    "    first_value: T,     ### The first value to compare against (the \"current largest\")      \n",
    "    it: Iterator[T],    ### The remaining elements as an iterator \n",
    "    key: Optional[Callable[[T], Any]],\n",
    ") -> T:\n",
    "    largest = first_value\n",
    "    if key is None:\n",
    "        for x in it:\n",
    "            if x > largest:\n",
    "                largest = x\n",
    "        return largest\n",
    "    largest_key = key(largest)\n",
    "    for x in it:\n",
    "        kx = key(x)\n",
    "        if kx > largest_key:\n",
    "            largest = x\n",
    "            largest_key = kx\n",
    "    return largest\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54fb5ea0",
   "metadata": {},
   "source": [
    "However, you probably have noticed that the algorithm is really too sketchy to design both algorithms and we need something more elaborative."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30f6774d",
   "metadata": {},
   "source": [
    "### Properties of an Algorithm\n",
    "\n",
    "According [Donald Knuth](https://en.wikipedia.org/wiki/Donald_Knuth) in *The Art of Computer Programming*, an algorithm has certain properties:\n",
    "\n",
    "- **Input**: An algorithm will starts its initial state, which may be expressed as the zero or more input values given before the algorithm stars.\n",
    "- **Output**: An algorithm will produce zero or more outputs in relation to the inputs.\n",
    "- **Finiteness** steps: An algorithm must start and stop with the rules the algorithm applies concluded. It cannot run forever — every algorithm must have a clear termination condition. \n",
    "- **Definiteness**: Each instruction in an algorithm must be precise and unambiguous; they can not be open to interpretations. Every step must be clear enough that anyone or any machine can follow it without interpretation. For example, \"iterate a bunch of times\" is not well-defined; the number of times must be precisely expressed.\n",
    "- **Effectiveness**: The steps an algorithm takes must be sufficiently simple that they could be expressed; these steps must make sense for the quantities used.\n",
    "\n",
    "You may also be concerned about some issues about algorithms such as: \n",
    "- Algorithms are not tied to any specific programming language. They describe what to do, not how a particular language does it.\n",
    "- Given the same input, an algorithm always produces the same output; this property is called [**determinism**](https://en.wikipedia.org/wiki/Deterministic_algorithm). \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9f015ee",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Find the Minimum Value\n",
    "#   1. Write a function `find_min(nums)` that returns the minimum value\n",
    "#      in a list WITHOUT using Python's built-in min() function.\n",
    "#   2. Your algorithm should work for any non-empty list of numbers.\n",
    "#   3. Test with: find_min([5, 2, 8, 1, 9, 3])  -> 1\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0cfcaede",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "def find_min(nums):\n",
    "    result = nums[0]           # sequence: initialize result\n",
    "    for n in nums[1:]:         # repetition: visit each element\n",
    "        if n < result:         # decision: is this smaller?\n",
    "            result = n         # sequence: update result\n",
    "    return result\n",
    "\n",
    "print(find_min([5, 2, 8, 1, 9, 3]))   # Expected: 1\n",
    "print(find_min([10, 10, 10]))          # Expected: 10\n",
    "print(find_min([-3, 0, 5]))            # Expected: -3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ac75d5c",
   "metadata": {},
   "source": [
    "## Implementing Algorithms\n",
    "\n",
    "### The Design Process \n",
    "\n",
    "Now, looking at the `max_value()` example, there is a gap between having an algorithm figured out and having a piece of code to run and test the algorithm for **correctness** and **efficiency**. To jump from a sketchy algorithm to code may not be as easy as you want it to be as the steps are not \"**well-defined**\". Therefore, before implementing the algorithm using an actual programming language, it is usually smart to first write the algorithm as a step-by-step procedure.\n",
    "\n",
    "So, before coding, you might want to use a design checklist like this:\n",
    "\n",
    "1. Define **input** types and **output** format.\n",
    "2. Write **step-by-step** **logic** in plain language.\n",
    "3. Choose **data structures** that support the operations you need.\n",
    "4. Test **edge cases** first (empty input, single item, duplicates, min/max, out of bound (0, -1, ...), etc).\n",
    "5. Measure **performance** only after correctness is confirmed."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58ee7bca",
   "metadata": {},
   "source": [
    "For the design of the algorithm, if you can write down the algorithm in something between human language and programming language, the algorithm will be easier to implement later because we avoid drowning in all the details of the programming language syntax. The `max_value()` function algorithm can look like this:\n",
    "\n",
    "1. Create a variable `maxVal` and set it equal to the first value of the array. (**Variable**)\n",
    "2. Go through every element in the array. (**Iteration**)\n",
    "3. If the current element has a higher value than `maxVal`, update `maxVal` to this value. (**Update**)\n",
    "4. After looking at all the elements in the array, the `maxVal` variable now contains the highest value."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c9b9f88",
   "metadata": {},
   "source": [
    "In addition, you may choose to express this procedure in **pseudo code**, which should further define the steps of an algorithm.\n",
    "\n",
    "```\n",
    "Variable 'maxVal' = array[0]\n",
    "For each element in the array\n",
    "    If current element > maxVal\n",
    "        maxVal = current element\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07e451dc",
   "metadata": {},
   "source": [
    "### Control Patterns"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d71e14d",
   "metadata": {},
   "source": [
    "In computer science, algorithms are evaluated on two key dimensions:\n",
    "\n",
    "- **Correctness**: does it produce the right answer for all valid inputs, \n",
    "  including edge cases?\n",
    "- **Efficiency**: how much **time** and **memory** does it consume as the \n",
    "  input grows? This is formalized through complexity analysis, commonly \n",
    "  expressed using Big $O$ notation such as $O(n)$ or $O(n^2)$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5342eda",
   "metadata": {},
   "source": [
    "After we are able to obtain correctness, our attention shifts to efficiency. To achieve efficiency, the design involves decisions about data structures and performance measurement. While going through these steps of the checklist, you want to pay attention to the three **control patterns** that most algorithms use to achieve better efficiency:\n",
    "\n",
    "- **Sequence**: These steps run in order, one after another, with no branching or looping. This is the simplest pattern: each line executes exactly once, in the order it appears. For example, initializing a variable and then returning it are two sequential steps.\n",
    "\n",
    "- **Decision**: choose different paths based on a condition (`if` / `elif` / `else`). Decisions let an algorithm respond differently to different inputs. For example, returning `None` for an empty list (guard clause) or assigning a letter grade based on a score are both decisions.\n",
    "\n",
    "- **Repetition**: repeat a block of work for each item in a collection, or until a condition is met (`for`, `while`). Repetition lets an algorithm scale to any input size without duplicating code. For example, scanning every element in a list to find the maximum value uses repetition.\n",
    "\n",
    "These three building blocks are enough to express the logic of virtually any algorithm. In practice, most algorithms layer all three: **repetition** drives the overall traversal, **decisions** handle special cases inside, and **sequence** connects each step in the right order.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1423009",
   "metadata": {},
   "source": [
    "Observe the following code and see the three building blocks of algorithm."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "07a2d885",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-03-19T03:09:23.283182Z",
     "iopub.status.busy": "2026-03-19T03:09:23.283029Z",
     "iopub.status.idle": "2026-03-19T03:09:23.285910Z",
     "shell.execute_reply": "2026-03-19T03:09:23.285578Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['A', 'B', 'C', 'A']\n"
     ]
    }
   ],
   "source": [
    "def classify_scores(scores):\n",
    "    labels = []\n",
    "    for s in scores:                      # repetition\n",
    "        if s >= 90:                       # decision\n",
    "            labels.append('A')\n",
    "        elif s >= 80:\n",
    "            labels.append('B')\n",
    "        else:\n",
    "            labels.append('C')\n",
    "    return labels                         # sequence (line #2, 3, 10) ends with output\n",
    "\n",
    "print(classify_scores([95, 81, 72, 90]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6707fe0",
   "metadata": {},
   "source": [
    "Another example for observing the three building blocks of algorithm."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e6a61740",
   "metadata": {},
   "outputs": [],
   "source": [
    "def summarize_orders(orders, discount_threshold=100):\n",
    "    total_revenue = 0                           ### sequence: initialize output\n",
    "    discounted_count = 0                        ### sequence: initialize counter\n",
    "\n",
    "    for order in orders:                        ### repetition: visit every order\n",
    "        amount = order['amount']\n",
    "\n",
    "        if amount >= discount_threshold:        ### decision: qualify for discount?\n",
    "            amount *= 0.9                       ### sequence: apply 10% discount\n",
    "            discounted_count += 1               ### sequence: track how many\n",
    "\n",
    "        total_revenue += amount                 ### sequence: accumulate\n",
    "\n",
    "    return total_revenue, discounted_count      ### sequence: produce output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "65898727",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Count Passing Scores\n",
    "#   1. Write a function `count_passing(scores)` that returns the count of\n",
    "#      scores that are 60 or above.\n",
    "#   2. Your function must use all three control patterns:\n",
    "#      sequence, selection (if), and repetition (for).\n",
    "#   3. Test with: count_passing([45, 72, 60, 88, 55])  -> 3\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f16e653d",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "def count_passing(scores):\n",
    "    count = 0                  # sequence: initialize counter\n",
    "    for s in scores:           # repetition: visit each score\n",
    "        if s >= 60:            # decision: is the score passing?\n",
    "            count += 1         # sequence: increment counter\n",
    "    return count\n",
    "\n",
    "print(count_passing([45, 72, 60, 88, 55]))  # Expected: 3\n",
    "print(count_passing([30, 40, 50]))           # Expected: 0\n",
    "print(count_passing([100, 90, 80]))          # Expected: 3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11ae3abc",
   "metadata": {},
   "source": [
    "## Complexity and Big O\n",
    "\n",
    "When evaluating an algorithm, correctness always comes first: the algorithm must produce the right answer for all valid inputs, including edge cases, before efficiency is even worth considering. A fast algorithm that gives wrong answers is not useful.\n",
    "\n",
    "Once correctness is established, efficiency becomes the next concern. **Big $O$ notation** is the standard way to describe how an algorithm's runtime (or memory usage) grows as the input size $n$ increases. It captures the shape of the growth -- not the exact number of operations, but whether the algorithm scales **linearly**, **logarithmically**, **quadratically**, and so on. This allows you to compare algorithms independent of hardware or implementation details."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4735812c",
   "metadata": {},
   "source": [
    "### Complexity\n",
    "\n",
    "In algorithm analysis, Big $O$ notation is used to express the concept of **complexity**. We usually discuss two kinds: \n",
    "\n",
    "- **time complexity** (how runtime grows with input size) and \n",
    "- **space complexity** (how extra memory usage grows with input size). \n",
    "\n",
    "In this chapter, the primary focus is time complexity for search and sorting.\n",
    "\n",
    "We have briefly addressed the Big $O$ notation earlier. You already compared `item in list` (often $O(n)$) with `item in dict/set` (average $O(1)$); see {ref}`Big O Notation (dict) <dict-bigo>` and {ref}`Big O Notation (set) <set-bigo>`. Here we extend that foundation to $O(\\log n)$, $O(n \\log n)$, and $O(n^2)$ for search and sorting algorithms.\n",
    "\n",
    "The most common growth classes in algorithm analysis include:\n",
    "\n",
    "| Notation | Class | Behavior | Example |\n",
    "|---|---|---|---|\n",
    "| $O(1)$ | Constant | Runtime does not depend on input size | Dict/set lookup, list indexing |\n",
    "| $O(\\log n)$ | Logarithmic | Input is halved each step; doubles only when input squares | Binary search |\n",
    "| $O(n)$ | Linear | One pass through the data; grows proportionally with input | Linear search, `max()` |\n",
    "| $O(n \\log n)$ | Linearithmic | Repeated splitting and merging; typical of efficient sorts | Merge sort, Timsort |\n",
    "| $O(n^2)$ | Quadratic | Nested loops over the input; impractical for large $n$ | Insertion/bubble sort |\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd5968dd",
   "metadata": {},
   "source": [
    "The following examples illustrate each Big $O$ class with a concrete, runnable implementation, but we will focus on $O(n)$ vs $O(\\log n)$ in this section as the last two is an extension of $O(\\log n)$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0f83eabb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "O(1) \t\t dict lookup → 95\n",
      "O(n) \t\t linear max → 9\n",
      "O(log n) \t binary search → index 999999\n",
      "O(n log n) \t sorted → [1, 2, 3, 5, 7, 9]\n",
      "O(n²) \t\t bubble sort → [1, 2, 3, 5, 7, 9]\n"
     ]
    }
   ],
   "source": [
    "### O(1) — Constant: runtime does not depend on input size\n",
    "scores = {'Alice': 95, 'Bob': 82}\n",
    "grade = scores['Alice']                       # dict lookup: same cost for 10 or 10 million items\n",
    "print(f\"O(1) \\t\\t dict lookup → {grade}\")\n",
    "\n",
    "\n",
    "### O(n) — Linear: one pass through the data\n",
    "def linear_max(arr):\n",
    "    best = arr[0]\n",
    "    for val in arr:                           # visits every element once\n",
    "        if val > best:\n",
    "            best = val\n",
    "    return best\n",
    "\n",
    "print(f\"O(n) \\t\\t linear max → {linear_max([3, 7, 1, 9, 4])}\")\n",
    "\n",
    "\n",
    "### O(log n) — Logarithmic: binary search halves the search space each step\n",
    "def binary_search(arr, target):\n",
    "    lo, hi = 0, len(arr) - 1\n",
    "    while lo <= hi:\n",
    "        mid = (lo + hi) // 2\n",
    "        if arr[mid] == target:\n",
    "            return mid\n",
    "        elif arr[mid] < target:\n",
    "            lo = mid + 1\n",
    "        else:\n",
    "            hi = mid - 1\n",
    "    return -1\n",
    "\n",
    "sorted_nums = list(range(1_000_000))\n",
    "print(f\"O(log n) \\t binary search → index {binary_search(sorted_nums, 999_999)}\")\n",
    "\n",
    "\n",
    "### O(n log n) — Linearithmic: Python's built-in sort (Timsort)\n",
    "data = [5, 2, 9, 1, 7, 3]\n",
    "print(f\"O(n log n) \\t sorted → {sorted(data)}\")\n",
    "\n",
    "\n",
    "### O(n²) — Quadratic: bubble sort with nested loops over the same input\n",
    "def bubble_sort(arr):\n",
    "    arr = arr[:]\n",
    "    n = len(arr)\n",
    "    for i in range(n):                        # outer loop: n passes\n",
    "        for j in range(n - i - 1):           # inner loop: up to n comparisons each pass\n",
    "            if arr[j] > arr[j + 1]:\n",
    "                arr[j], arr[j + 1] = arr[j + 1], arr[j]\n",
    "    return arr\n",
    "\n",
    "print(f\"O(n²) \\t\\t bubble sort → {bubble_sort([5, 2, 9, 1, 7, 3])}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c8304c0",
   "metadata": {},
   "source": [
    "Let's say we want to design an algorithm to guess a number (`target`, e.g., 42) in an array (`arr`, let's say, 1~100). For example, you ask yourself: \"I'm thinking of a number between 1 and 100.\" Now what?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8048b12a",
   "metadata": {},
   "source": [
    "### Linear Search\n",
    "\n",
    "The $O(n)$ strategy (linear search) to guess a number in 1~100 would be: \n",
    "\n",
    "*Guess 1, then 2, then 3...* and in the worst case you need 100 guesses.\n",
    "\n",
    "And the code implementation of this idea would look like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "abb4db58",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "target index: 42\n",
      "element 42 is at index 41\n"
     ]
    }
   ],
   "source": [
    "target = 42\n",
    "arr_100 = list(range(1, 101))\n",
    "\n",
    "for i in arr_100:\n",
    "    if i == target:\n",
    "        print(f\"target index: {i}\")\n",
    "\n",
    "print(f\"element {target} is at index {arr_100.index(target)}\") ### unfixed; no need to\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7f57db26",
   "metadata": {},
   "source": [
    "Or, we can design a more general solution to that. But here, instead using index, we want to observe elements, just to be intuitive. Now, since we want to count how many steps, when the loop starts, step is `1` when index is `0`, so there's no sync between index and step as with element index and element value above.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "d0c3b947",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Linear search 100: value 42, steps 42\n"
     ]
    }
   ],
   "source": [
    "def linear_search(arr, target):     # O(n) linear search with step counting\n",
    "    steps = 0\n",
    "    for i, val in enumerate(arr):   # check every element\n",
    "        steps += 1\n",
    "        if val == target:\n",
    "            return (i+1, steps)     # return index+1 (value) and steps if found\n",
    "    return (-1, steps)              # return -1 if not found, along with steps taken\n",
    "\n",
    "linear_result = linear_search(arr_100, target)\n",
    "print(f\"Linear search 100: value {linear_result[0]}, steps {linear_result[1]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7856227a",
   "metadata": {},
   "source": [
    "Now, to further observe the behavior of $O(n)$, say, we have a `nums` array (`list`) as seen below. Assuming the **edge case** that the number to guess is the last number, let's see how many guesses you would need.\n",
    "\n",
    "For better intuition, we make the list range starts with 1, instead of 0, and make the range stop to be `n+1` so that we don't have to deal with the discrepancy. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "71538d1c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n:       2, \t steps:       2\n",
      "n:       4, \t steps:       4\n",
      "n:       8, \t steps:       8\n",
      "n:      16, \t steps:      16\n",
      "n:      32, \t steps:      32\n",
      "n:      64, \t steps:      64\n",
      "n:      10, \t steps:      10\n",
      "n:     100, \t steps:     100\n",
      "n:     128, \t steps:     128\n",
      "n:    1000, \t steps:    1000\n",
      "n:   10000, \t steps:   10000\n",
      "n:  100000, \t steps:  100000\n",
      "n: 1000000, \t steps: 1000000\n"
     ]
    }
   ],
   "source": [
    "nums = [2, 4, 8, 16, 32, 64, 10, 100, 128, 1000, 10_000, 100_000, 1000_000]\n",
    "\n",
    "w = len(str(max(nums)))  ### width based on largest number\n",
    "\n",
    "for n in nums:           ### n is the arr size and target\n",
    "    idx, steps = linear_search(list(range(1, n+1)), n )     # returns (index, steps) for each n\n",
    "    # print(f\"n: {n:>{w}}, \\t steps: {steps:>{w}}, \\t index: {idx:>{w}}\")\n",
    "    print(f\"n: {n:>{w}}, \\t steps: {steps:>{w}}\")           # printing steps only to show O(n) growth without index details"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75814bfd",
   "metadata": {},
   "source": [
    "In linear search, when the array grows in size, the number of tasks  grows accordingly and **linearly**. That's $O(n)$.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "980ccf67",
   "metadata": {},
   "source": [
    "### Binary Search\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16f4829b",
   "metadata": {},
   "source": [
    "Now let us take a look at $O(\\log n)$, the binary search, which will prove to be a smarter strategy for efficiency. With binary search, we do **halving**:\n",
    "\n",
    "*Always guess the **middle**. Each guess eliminates **half** the remaining numbers.*\n",
    "\n",
    "So, if we are trying to guess a number between 1 and 100 and the answer is 42, it will take only 6 guesses (note that we use 1-based indexing so that `low` and `high` will make better sense but the target is matched by `arr[mid]`, see code later):\n",
    "\n",
    "| Step | low | high | mid | `arr[mid]` | Result                    | Eliminated                  |\n",
    "|------|-----|------|-----|-----------|---------------------------|-----------------------------|\n",
    "| 1    | 1   | 100  | 50  | 50        | too high                  | 50–100 (51 numbers)         |\n",
    "| 2    | 1   | 49   | 25  | 25        | too low                   | 1–25 (25 numbers)           |\n",
    "| 3    | 26  | 49   | 37  | 37        | too low                   | 26–37 (12 numbers)          |\n",
    "| 4    | 38  | 49   | 43  | 43        | too high                  | 43–49 (7 numbers)           |\n",
    "| 5    | 38  | 42   | 40  | 40        | too low                   | 38–40 (3 numbers)           |\n",
    "| 6    | 41  | 42   | 41  | **42**    | **found 42** | —                           |\n",
    "\n",
    "\n",
    "The question we are asking here is: how many times can you **halve** *n*? And halving is what $log_2(n)$ means. In the case of guessing a number, you can guess and I'll say higher/lower. \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59c1c0d1",
   "metadata": {},
   "source": [
    "The halving logic can be implemented for the worse case scenario. Consider the \"guess my number\" game again. With a linear ($O(n)$) algorithm, the worst scenario will be that the answer is the last number and you end up parsing through the whole sequence. Now, consider the following comparison of linear vs logarithmic way of finding a number. \n",
    "\n",
    "| $n$ | $\\log_2 n$ (steps) |\n",
    "|---|---|\n",
    "| 4 | 2 |\n",
    "| 8 | 3 |\n",
    "| 16 | 4 |\n",
    "| 32 | 5 |\n",
    "| 64 | 6 |\n",
    "| 128 | 7 |\n",
    "| ... | ... |\n",
    "| 1,024 | 10 |\n",
    "| 1,000,000 | ~20 |\n",
    "| 1,000,000,000 | ~30 |\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ddac2db",
   "metadata": {},
   "source": [
    "The code implementation for binary search may look like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "62c86d52",
   "metadata": {},
   "outputs": [],
   "source": [
    "def binary_search(arr, target):\n",
    "    low, high = 1, len(arr)\n",
    "    step = 0\n",
    "    while low <= high:\n",
    "        step += 1\n",
    "        mid = (low + high) // 2     # always guess the middle\n",
    "        # print(mid)\n",
    "        if arr[mid] == target:\n",
    "            return mid + 1, step\n",
    "        elif arr[mid] < target:\n",
    "            low = mid + 1           # throw away left half\n",
    "        else:\n",
    "            high = mid - 1          # throw away right half\n",
    "    return -1, step                 # not found, but report steps taken\n",
    "                                    # -1 is outside of range; to indicate \"not found\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "8990dd26",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(42, 6)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "binary_search(list(range(1, 101)), 42)  ### uncomment print(mid) above to see the guesses made"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8560ce1c",
   "metadata": {},
   "source": [
    "Now, use the same `nums` to see the output of steps would take for finding the `target` in worst case scenario."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "37698565",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n:       2, \t steps:       1\n",
      "n:       4, \t steps:       2\n",
      "n:       8, \t steps:       3\n",
      "n:      16, \t steps:       4\n",
      "n:      32, \t steps:       5\n",
      "n:      64, \t steps:       6\n",
      "n:      10, \t steps:       3\n",
      "n:     100, \t steps:       6\n",
      "n:     128, \t steps:       7\n",
      "n:    1000, \t steps:       9\n",
      "n:   10000, \t steps:      13\n",
      "n:  100000, \t steps:      16\n",
      "n: 1000000, \t steps:      19\n"
     ]
    }
   ],
   "source": [
    "nums = [2, 4, 8, 16, 32, 64, 10, 100, 128, 1000, 10_000, 100_000, 1000_000]\n",
    "\n",
    "w = len(str(max(nums)))    \n",
    "\n",
    "for n in nums:\n",
    "    # w = max(len(str(n)), 5)  # Ensure minimum width of 5 characters\n",
    "    idx, steps = binary_search(list(range(1, n + 1)), n)\n",
    "    print(f\"n: {n:>{w}}, \\t steps: {steps:>{w}}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7a93fa45",
   "metadata": {},
   "source": [
    "### Comparing Linear and Binary Search\n",
    "\n",
    "Now let's compare the linear search and binary search steps:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "27fc05a9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Comparing linear and binary search step counts:\n",
      "      n   linear   binary       ratio\n",
      "      2        2        1         2.0x\n",
      "      4        4        2         2.0x\n",
      "      8        8        3         2.7x\n",
      "     16       16        4         4.0x\n",
      "     32       32        5         6.4x\n",
      "     64       64        6        10.7x\n",
      "    128      128        7        18.3x\n",
      "    256      256        8        32.0x\n",
      "    512      512        9        56.9x\n",
      "    100      100        6        16.7x\n",
      "   1000     1000        9       111.1x\n",
      "  10000    10000       13       769.2x\n",
      " 100000   100000       16      6250.0x\n",
      "1000000  1000000       19     52631.6x\n"
     ]
    }
   ],
   "source": [
    "# Compare linear and binary search step counts using nums as input sizes.\n",
    "nums = [2, 4, 8, 16, 32, 64, 128, 256, 512, 100, 1_000, 10_000, 100_000, 1_000_000]\n",
    "\n",
    "w = len(str(max(nums)))\n",
    "\n",
    "print(\"Comparing linear and binary search step counts:\")\n",
    "print(f\"{'n':>{w}}  {'linear':>{w}}  {'binary':>{w}}  {'ratio':>10}\")\n",
    "\n",
    "for n in nums:\n",
    "    arr_n = list(range(1, n + 1))\n",
    "    _, linear_steps = linear_search(arr_n, n)       ### _ is a common convention for \"I don't care about this value\"\n",
    "    _, binary_steps = binary_search(arr_n, n)\n",
    "    ratio = linear_steps / binary_steps\n",
    "    print(f\"{n:>{w}}  {linear_steps:>{w}}  {binary_steps:>{w}}  {ratio:>10.1f}x\")\n",
    "    # print(f\"{n:>{w}}  {linear_steps:>{w}}  {binary_steps:>{w}}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee7b907b",
   "metadata": {},
   "source": [
    "Just to clarify how many steps using edge cases because we always use 1000 instead of 1024, let's take a look at binary search from the aspect of the search tree to learn what $\\log n$ really means."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "d6aaeda5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1020 found at index 1020 in 10 steps\n",
      "1021 found at index 1021 in 8 steps\n",
      "1022 found at index 1022 in 10 steps\n",
      "1023 found at index 1023 in 9 steps\n",
      "1024 found at index 1024 in 10 steps\n"
     ]
    }
   ],
   "source": [
    "sorted_nums = list(range(1, 1025))\n",
    "\n",
    "x1 = binary_search(sorted_nums, 1020)\n",
    "x2 = binary_search(sorted_nums, 1021)\n",
    "x3 = binary_search(sorted_nums, 1022)\n",
    "x4 = binary_search(sorted_nums, 1023)\n",
    "x5 = binary_search(sorted_nums, 1024)\n",
    "\n",
    "print(f\"1020 found at index {x1[0]} in {x1[1]} steps\")\n",
    "print(f\"1021 found at index {x2[0]} in {x2[1]} steps\")\n",
    "print(f\"1022 found at index {x3[0]} in {x3[1]} steps\")\n",
    "print(f\"1023 found at index {x4[0]} in {x4[1]} steps\")\n",
    "print(f\"1024 found at index {x5[0]} in {x5[1]} steps\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7777c9e6",
   "metadata": {},
   "source": [
    "The number of steps would jump. This is a little odd. Let's think about the process in terms of a decision tree. You see that the list has 1024 elements -- and that 1024 = 2^10 (2**10 in Python), so the binary search tree has exactly 10 levels of depth. The decision tree presentation of this process looks like:\n",
    "\n",
    "```\n",
    "n = 1024, max depth = log₂(1024) = 10\n",
    "\n",
    "Level 1 (1 node):                              512\n",
    "                                          /            \\\n",
    "Level 2 (2 nodes):                       256            768\n",
    "                                        /  \\          /   \\\n",
    "Level 3 (4 nodes):                   128    384     640     896\n",
    "                                     /\\      /\\     /\\      /\\\n",
    "Level 4 (8 nodes):                 64 192 320 448 576 704 832 960\n",
    "                                   |   |   |   |   |   |   |   |\n",
    "Level 5–9 (16–512 nodes):          ·   ·   ·   ·   ·   ·   ·   ·\n",
    "                                   |   |   |   |   |   |   |   |\n",
    "Level 10 (1024 leaf nodes):    ... 1019 1020 1021 1022 1023 1024\n",
    "```\n",
    "\n",
    "The step counts vary because some targets are found (`mid` matched) earlier than the deepest level:\n",
    "\n",
    "| Target | Steps | Explanation |\n",
    "|---:|---:|---|\n",
    "| 1020 | 10 | Deep in the tree, takes full depth |\n",
    "| 1021 | 8 | Happens to land on a higher node |\n",
    "| 1022 | 10 | Deep leaf node |\n",
    "| 1023 | 9 | One level above a leaf |\n",
    "| 1024 | 10 | Last element, deepest path |\n",
    "\n",
    "The maximum steps =\n",
    "\n",
    "`⌈log₂(n)⌉ = ⌈log₂(1024)⌉ = 10` (`⌈x⌉`: ceiling operator; round up to next whole number)\n",
    "\n",
    "The takeaways is: no search ever takes more than 10 steps regardless of which element you're looking for; because the tree is only 10 levels deep. Each comparison cuts the remaining search space exactly in half, so you can never need more steps than the height of the tree. That's why binary search is $O(\\log n)$ — **the tree depth is the $\\log n$**."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e810459",
   "metadata": {},
   "source": [
    ":::{admonition} Binary Search Requires Sorted Input \n",
    "\n",
    "Binary search only works on sorted data: the sequence must be **sorted**, then with each step you will eliminate **half** the remaining possibilities. The halving logic depends on being able to say \"the target must be in the left or right half.\"\n",
    "\n",
    "That way, you need at most $\\lceil \\log_2 1000 \\rceil = 10$ guesses; not 1000 (because 2**10 is 1024, so $log_2 1000$ is less than 10). The key relationship here is: $\\log_2 n$ answers \"how many times can you halve $n$ before reaching 1?\" (e.g., 1000 → 500 → 250 → 125 → 62 → 31 → 15 → 7 → 3 → 1).\n",
    "\n",
    "Seeing from the other side, to add one step in binary search, you need to double $n$; to double the number of steps, you must **square** $n$ — this is what makes $O(\\log n)$ so powerful at scale.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00e36c76",
   "metadata": {},
   "source": [
    "Now let's compare linear search and binary search and also taking consideration of memory space complexity and average $O$. \n",
    "\n",
    "Space here means extra memory the algorithm needs beyond the input array itself. Here both are $O(1)$ — meaning they use a fixed, constant amount of extra memory regardless of how large $n$ is. \n",
    "\n",
    "For both linear and binary search (iterative), you only need a handful of variables. Since we always have just 4 variables,  `low`, `high`, `mid`, `step`, it doesn't matter if the array has 100 or 1,000,000,000 elements; you still only need those same 4 variables. That's $O(1)$ space. \n",
    "\n",
    "\\*Note that, if we use the the **recursive** version of binary search, it is $O(\\log n)$ space because each recursive call adds a frame to the call stack, and you can have up to $log n$ frames deep at once:\n",
    "```\n",
    "binary_search(arr, 1, 1024)\n",
    "  → binary_search(arr, 513, 1024)\n",
    "    → binary_search(arr, 769, 1024)\n",
    "      → ...  (up to 10 levels deep for n=1024)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a9dd07f",
   "metadata": {},
   "source": [
    "For binary search, if we calculate the average of time complexity (see the code below), it will still be $O(\\log n)$.  This happens because if you average the search depth across all possible targets in a sorted array, it works out to approximately log n - 1, which is still O(log n). The average is always slightly above log₂(n)-1, not exactly equal — but the difference (diff) shrinks toward zero as n grows. So \"approximately $\\log n - 1$\" is correct but it's an approximation that gets better with larger $n$. The intuition here is: for the last step, half the nodes live at the deepest level (worst case, log₂(n) steps), while the other half are spread across shallower levels. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "be1dbc91",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n=    8 | avg=2.625 | log2(n)=3.0 | log2(n)-1=2.0 | diff=0.625\n",
      "n=   16 | avg=3.375 | log2(n)=4.0 | log2(n)-1=3.0 | diff=0.375\n",
      "n=   32 | avg=4.219 | log2(n)=5.0 | log2(n)-1=4.0 | diff=0.219\n",
      "n=   64 | avg=5.125 | log2(n)=6.0 | log2(n)-1=5.0 | diff=0.125\n",
      "n=  128 | avg=6.070 | log2(n)=7.0 | log2(n)-1=6.0 | diff=0.070\n",
      "n=  256 | avg=7.039 | log2(n)=8.0 | log2(n)-1=7.0 | diff=0.039\n",
      "n=  512 | avg=8.021 | log2(n)=9.0 | log2(n)-1=8.0 | diff=0.021\n",
      "n= 1024 | avg=9.012 | log2(n)=10.0 | log2(n)-1=9.0 | diff=0.012\n"
     ]
    }
   ],
   "source": [
    "import math\n",
    "\n",
    "def binary_search_steps(arr, target):\n",
    "    low, high = 1, len(arr)\n",
    "    steps = 0\n",
    "    while low <= high:\n",
    "        steps += 1\n",
    "        mid = (low + high) // 2\n",
    "        if arr[mid-1] == target:\n",
    "            return steps\n",
    "        elif arr[mid-1] < target:\n",
    "            low = mid + 1\n",
    "        else:\n",
    "            high = mid - 1\n",
    "    return steps\n",
    "\n",
    "for n in [8, 16, 32, 64, 128, 256, 512, 1024]:\n",
    "    arr = list(range(1, n+1))\n",
    "    total_steps = sum(binary_search_steps(arr, t) for t in arr)\n",
    "    avg = total_steps / n\n",
    "    log_n = math.log2(n)\n",
    "    print(f\"n={n:5d} | avg={avg:.3f} | log2(n)={log_n:.1f} | log2(n)-1={log_n-1:.1f} | diff={avg-(log_n-1):.3f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e6b3b91",
   "metadata": {},
   "source": [
    "As a summary, the complexity for linear search and binary search:\n",
    "\n",
    "- Linear search: best $O(1)$, average $O(n)$, worst $O(n)$, space $O(1)$.\n",
    "- Binary search: best $O(1)$, average $O(\\log n)$, worst $O(\\log n)$, space $O(1)$ (iterative version), and it requires sorted input.\n",
    "\n",
    "\n",
    "| Algorithm | Best Time (found first) | Average Time | Worst Time (found last/at leaf) | Extra Space | Requirement |\n",
    "|---|---|---|---|---|---|\n",
    "| Linear search | $O(1)$ | $O(n)$ | $O(n)$ | $O(1)$ | Works on any list |\n",
    "| Binary search | $O(1)$ | $O(\\log n)$ | $O(\\log n)$ | $O(1)$ | Input must be sorted |\n",
    "\n",
    "In practice, this means linear search grows in direct proportion to $n$, while binary search grows very slowly as $n$ increases; as seen in the visualization below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6b7e8119",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: pandas in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (3.0.0)\n",
      "Requirement already satisfied: seaborn in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (0.13.2)\n",
      "Requirement already satisfied: matplotlib in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (3.10.7)\n",
      "Requirement already satisfied: numpy>=1.26.0 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from pandas) (2.3.4)\n",
      "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from pandas) (2.9.0.post0)\n",
      "Requirement already satisfied: contourpy>=1.0.1 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from matplotlib) (1.3.3)\n",
      "Requirement already satisfied: cycler>=0.10 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from matplotlib) (0.12.1)\n",
      "Requirement already satisfied: fonttools>=4.22.0 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from matplotlib) (4.60.1)\n",
      "Requirement already satisfied: kiwisolver>=1.3.1 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from matplotlib) (1.4.9)\n",
      "Requirement already satisfied: packaging>=20.0 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from matplotlib) (25.0)\n",
      "Requirement already satisfied: pillow>=8 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from matplotlib) (12.0.0)\n",
      "Requirement already satisfied: pyparsing>=3 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from matplotlib) (3.2.5)\n",
      "Requirement already satisfied: six>=1.5 in /Users/tcn85/workspace/py/.venv/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)\n",
      "\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.0.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
      "Note: you may need to restart the kernel to use updated packages.\n",
      "binary_search step range: 15 to 20; real max 19.93\n"
     ]
    },
    {
     "data": {
      "image/png": "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wiPqUaNG0YUXXki7d+/2+N7y5cvprLPOotGjR9OkSZPolltuoZqaGo91li1bxt+dOHEijRgxgrf93HPPkd1u58+LiorYtfPWW2/RkUceydv67LPPfO7n+vXr6dxzz6Xx48fT2LFj6bzzzqPVq1d7rLNgwQI67bTTuFObPn063XnnnR5uoP3tjy+2bt1Kl156KY0bN45fV155JRUWFnawlQVB6A0sXLiQzjjjDO5nJk+eTDfccAOVlpZ6rLNq1So688wzuZ85+OCD6b///S/3R225341GI9199900c+ZM7nvQ373xxhstBCL60alTp3Ifh/4Vv6WA/vWee+6hQw45hLeBfhf9EfrR1jCZTPToo4/SrFmz+DvHHnssff/99/ttB/TJWVlZ/B3w/vvvc1+9a9cuj/W++uorGjp0aIs28j72J554gubMmcPbQz96/vnn06ZNm/hzfBft7R6mgf2eO3cuHX300fx/7zCA9rRnamoqTZkyhV555ZX9Hq/QfkSsCgHDO++8wzE/Dz30ELtRIBTRibqLPnTOERER9PTTT9P//d//0dKlS+mcc87hTgRs3ryZ10lISKCnnnqKXnrpJZowYQKHHPzwww8evwfBePHFF3OnOm3atBb709TURBdddBElJibyuthec3MzC8/GxkZe548//mBRmZyczPt044030q+//krXXXddh/dHAR0zxG91dTWL9wceeICF6umnn87LBEEIHr788ku64IILKDMzk5588km67bbbWCyeeuqprvt9x44d3I8ArHPVVVexBXLFihVtbvvBBx+kP//8k/tRiCq4qNHfKYNzvV7P/QrE2E033cT9kk6n4/2BocDhcHD/BjGNvg3b+Pe//02LFi2iu+66y+dv4jsQsx9++CGLQ/R5EMHoE3GsbfHNN9/QEUcc4XoPkYv9gTj1bjOIa7RZa9x88818nLB0vvnmm9yu27Zt44EA9hHfhdDHM0RpD4jbgoIC/ovf7Wh7KkDE/v7779y+Qteg7qLtBB0YFf3999/07rvvduh7uInQiUBcwNWNG/uoo47qtv0MJuLi4ujFF18klUrF79FpQCTW1tayYEQH0rdvXz43yjqwimIUjM4CVgeIQ1hmH3vsMQoPd47FIETRcaBDxroKOC8nnXRSq/uzfft2/m2IYYzKlZgkxDChE4qNjeX9wwgfnXxYWBivo9Vq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b/H52WefTYHIK6+8st99C4Z7OhCRvjU0+lbpV0OvXw2ovtURIjz33HOOyZMnO/744w/Hpk2bHBdccIFjzpw5DpPJ5HP9s846y3HSSSc51q9f7/jnn38chxxyiOPmm292fb5o0SLH8OHDHf/9738d27dvdzz88MOOESNG8P974zHX1NQ4pk2b5rjqqqscW7Zscaxbt85x5plnOo466iiH0WjkdXBsgwYN4m1VVFS4Xk1NTY7efK4fffRRPt/ux4SX1WoNynON8+V9rI888ohjzJgxjs2bN7vWmzp1quODDz7wWK+2ttYRaLz33nuOIUOG8Dlsi2C4pwMR6VtDo2+VfjW0+tVA61tDQqziwho7dqzj/fffdy2rr693jBo1yvHNN9+0WH/lypXccbg35F9//eUYPHiwo6ysjN/jor3mmms8vnfqqac6/vOf/zh64zF//PHHvH5zc7NrWUlJCbcDLj7w3XffOcaNG+cIZDp63OCiiy5y3Hfffa1uM9jOtTcbNmzgjuTzzz93LauqquJzj88CFdyLl156KT8MjjzyyDY71GC4pwMR6VtDo2+VfjV0+tVA7VtDIgxg8+bNpNfr2f2iEBcXR8OGDaNly5a1WH/58uWUmppK/fv3dy2DaTssLIxWrFjB7pCVK1d6bA/AxeNre73hmLHeiy++SBEREa5l4eHOy6OhoYH/btmyxaNNApGOHvf+jisYz7U39957L02YMIFOOOEEjzbB9d63b18KVDZs2MBThCKMY/To0W2uGwz3dCAifWto9K3Sr4ZOvxqofauaQoCysjL+m5mZ6bE8LS3N9Zk75eXlLdbVarWUkJBApaWl3MEYDAbKyMho1/Z6wzHn5OTwy51XX32VO1jEoYCtW7fynOIXXnghd17p6el07rnn0rx58yhQ6Ohx19fX8/nGDffBBx9w7A1iam666SbuUILxXLvzxx9/0KpVq+jLL7/0WI5zHRsbyx3uwoULKSoqio488ki64oor+F4IBGbPns2v9hAM93QgIn1raPSt0q+GTr8aqH1rSIhVJdDX+2LQ6XQsVnyt7+vCwfomk4mMRmOr28PnvfGYvXn33XfpvffeozvuuIOSkpJcSQIYJSE5ABfeggULOHDcYrHQySefTL3xuHFMACExDz30EJ/bl156ic444wz65ptv+AESzOf6rbfeokMOOYSGDh3aolPF8UG4IyFg06ZN9Oijj3JSCP72NoLhng5EpG8Njb5V+tV9SL/qn741JMSq4n4xm80erhg0VGRkpM/1sa43WB8jITSysj3vz31trzccswJE2zPPPMOC7fLLL/fIAvz22285azU6OprfDxkyhMXLG2+8ERAdameOG24aZCUmJiay2wI8//zzdPDBB9Pnn39Op5xyimt7wXauce6WLFnCVh5vMPK/5ZZbKD4+nt8PGjSI3ULXXXcd3XzzzZSSkkK9iWC4pwMR6VtDo2+VflX6VX/3rSERs6qYqCsqKjyW4z3cLd5gZOu9Lhq6rq6OTdcwb+MktHd7veGYAUbxcH+//PLLPKq/9tprW1yUSmeqABETSG7Szhw3rBuKUAW4geC2g3sjWM81+PXXX/nYp02b1uIztVrtEqoKAwcO5L+BdL7bSzDc04GI9K2h0bdKv7oP6Vf907eGhFjFKDUmJoatSAqIo9i4caMrZsgdLEMnsWfPHteypUuX8t/x48ezsBk3bpxrmQK2D0tdbzxmAIvZjz/+SE888QSdd955Hp/huwiahrXRnXXr1rlETG887o8++ogDvRFTo9DU1ES7d++mAQMGBO25BojTxTmFMPUGVh/32oDKuYZ1NT8/n3obwXBPByLSt4ZG3yr9qhPpV/3YtzpChCeffNIxadIkx6+//upRh9JsNnM9TdQ6U0qL2O12x2mnneY44YQTHGvWrOEaYagbduutt3qUZhg6dKjjzTff5JINqKeGkhaBVJOxI8f82WefcfmJ119/vUWtOGUd1AmcPn26Y/78+Y5du3Y5XnnlFW6DP//80xFIdOS4UUJmwoQJjiuvvNKxdetWx9q1ax3nnXee47DDDnPVQAy2c61w6KGHOl588UWf23v33Xf5mFEPsKCggEvroI4rficQueWWWzzKqwTrPR2ISN8aGn2r9Kuh168GUt8aMmIVDYzi71OmTOHaYRdffLGjsLCQP8NfdCboVNzroaEDwbq4mO666y6XeFH44osvHIcffrhj5MiRfKKUmnm98ZjPP/98fu/rpazT2NjoePDBBx2zZs3igr7z5s1z/PLLL47efq5RyBjHP378eK51iPMOERus51oBnQU6zbYKQqNwOc41Op+XXnrJYbPZHL2hQw3WezoQkb41NPpW6VdDr18NpL41DP+03w4rCIIgCIIgCD1HSMSsCoIgCIIg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",
      "text/plain": [
       "<Figure size 700x400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Random targets on one large dataset: 1 .. 1,000,000\n",
    "import random\n",
    "# %pip install pandas seaborn matplotlib\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "N = 1_000_000\n",
    "arr = list(range(1, N + 1))\n",
    "rows = []\n",
    "targets = sorted(random.sample(arr, 50))    # sort for cleaner left-to-right plot ordering\n",
    "\n",
    "for target in targets:\n",
    "    linear_steps = target  # in sorted 1..N, linear search finds target in `target` steps\n",
    "    _, binary_steps = binary_search(arr, target)\n",
    "\n",
    "    rows.append({\"target\": target, \"search\": \"linear\", \"steps\": linear_steps})\n",
    "    rows.append({\"target\": target, \"search\": \"binary\", \"steps\": binary_steps})\n",
    "\n",
    "df_random = pd.DataFrame(rows).sort_values(\"target\")\n",
    "binary_only = df_random.loc[df_random[\"search\"] == \"binary\", \"steps\"]\n",
    "print(f\"binary_search step range: {binary_only.min()} to {binary_only.max()}; real max {math.log2(N):.2f}\")\n",
    "\n",
    "sns.set_theme(style=\"whitegrid\")\n",
    "fig, axes = plt.subplots(1, 2, figsize=(7, 4))\n",
    "\n",
    "# Left: linear scale — shows dramatic gap between O(n) and O(log n)\n",
    "sns.scatterplot(data=df_random, x=\"target\", y=\"steps\", hue=\"search\",\n",
    "                alpha=0.6, s=22, ax=axes[0])\n",
    "axes[0].set_title(\"Linear scale\")\n",
    "axes[0].set_xlabel(\"Target value\")\n",
    "axes[0].set_ylabel(\"Steps\")\n",
    "axes[0].legend(title=\"search\")\n",
    "\n",
    "# Right: log scale — makes binary search steps visible\n",
    "sns.scatterplot(data=df_random, x=\"target\", y=\"steps\", hue=\"search\",\n",
    "                alpha=0.6, s=22, ax=axes[1])\n",
    "axes[1].set_yscale(\"log\")\n",
    "axes[1].set_title(\"Log scale (y-axis)\")\n",
    "axes[1].set_xlabel(\"Target value\")\n",
    "axes[1].set_ylabel(\"Steps (log scale)\")\n",
    "axes[1].legend(title=\"search\")\n",
    "\n",
    "plt.suptitle(\"Random Targets on 1..1,000,000: Linear vs Binary Steps\")\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a281530",
   "metadata": {},
   "source": [
    "To restate the power of binary search: to add one step in binary search, you need to double $n$; to double the number of steps, you need to **square** $n$.\n",
    "\n",
    "| n | O(n) guesses | O(log₂n) guesses | O(n) / O(log₂n) |\n",
    "|--:|--:|--:|--:|\n",
    "| 4 | 4 | 2 | 2× |\n",
    "| 8 | 8 | 3 | 3× |\n",
    "| 10 | 10 | 4 | 3× |\n",
    "| 16 | 16 | 4 | 4× |\n",
    "| 32 | 32 | 5 | 6× |\n",
    "| 64 | 64 | 6 | 11× |\n",
    "| 100 | 100 | 7 | 14× |\n",
    "| 1,000 | 1,000 | 10 | 100× |\n",
    "| **1,000,000** | 1,000,000 | 20 | **50,000×** |\n",
    "| 1,000,000,000 | 1,000,000,000 | 30 | 33,333,333× |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "700cfb56",
   "metadata": {},
   "source": [
    "The theoretical values for the table above. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "3e729e43",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "n=           10  linear=           10  binary=   3.3  ratio=            3x\n",
      "n=          100  linear=          100  binary=   6.6  ratio=           15x\n",
      "n=        1,000  linear=        1,000  binary=  10.0  ratio=          100x\n",
      "n=    1,000,000  linear=    1,000,000  binary=  19.9  ratio=       50,172x\n",
      "n=1,000,000,000  linear=1,000,000,000  binary=  29.9  ratio=   33,447,777x\n"
     ]
    }
   ],
   "source": [
    "import math\n",
    "\n",
    "nums = [10, 100, 1000, 1_000_000, 1_000_000_000]\n",
    "w = len(f\"{max(nums):,}\")\n",
    "\n",
    "for n in nums:\n",
    "    linear = n\n",
    "    binary = math.log2(n)   ### binary search steps grow as log2(n)\n",
    "    ratio = n / binary\n",
    "    print(f\"n={n:{w},}  linear={linear:{w},}  binary={binary:6.1f}  ratio={ratio:>{w},.0f}x\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94123e34",
   "metadata": {},
   "source": [
    "For our examples here, use `random.choice` to help us compare linear and binary search. Run the following code several times, and you may see and feel the difference between binary and linear search results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "4752c330",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Linear search for 681194 → index (681195, 681195)  |  0.027301 s\n",
      "Binary search for 681194 → index (681195, 19)  |  0.000027 s\n",
      "Binary search was 1013.3x faster\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import random\n",
    "\n",
    "arr = list(range(1_000_000))\n",
    "target = random.choice(arr)\n",
    "\n",
    "start = time.time()\n",
    "idx = linear_search(arr, target)\n",
    "linear_time = time.time() - start\n",
    "print(f\"Linear search for {target} → index {idx}  |  {linear_time:.6f} s\")\n",
    "\n",
    "start = time.time()\n",
    "idx = binary_search(arr, target)\n",
    "binary_time = time.time() - start\n",
    "print(f\"Binary search for {target} → index {idx}  |  {binary_time:.6f} s\")\n",
    "\n",
    "print(f\"Binary search was {linear_time / binary_time:.1f}x faster\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f8c068b5",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Binary Search Implementation\n",
    "#   1. What is the Big O complexity of the function below?\n",
    "#      def count_evens(nums):\n",
    "#          return [n for n in nums if n % 2 == 0]\n",
    "#   2. Write a function `contains(arr, target)` that uses binary search\n",
    "#      (O(log n)) to check whether target is in a SORTED array.\n",
    "#   3. Test with: contains([1, 3, 5, 7, 9], 5)  -> True\n",
    "#                 contains([1, 3, 5, 7, 9], 4)  -> False\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18c1e634",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "# 1. count_evens is O(n) -- it visits every element once.\n",
    "\n",
    "# 2. Binary search -- O(log n)\n",
    "def contains(arr, target):\n",
    "    low, high = 0, len(arr) - 1\n",
    "    while low <= high:\n",
    "        mid = (low + high) // 2\n",
    "        if arr[mid] == target:\n",
    "            return True\n",
    "        elif arr[mid] < target:\n",
    "            low = mid + 1\n",
    "        else:\n",
    "            high = mid - 1\n",
    "    return False\n",
    "\n",
    "print(contains([1, 3, 5, 7, 9], 5))   # Expected: True\n",
    "print(contains([1, 3, 5, 7, 9], 4))   # Expected: False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41627da2",
   "metadata": {},
   "source": [
    "## Benchmarking\n",
    "\n",
    "**Benchmarking** measures how long your code actually takes to run. It complements Big O analysis: Big O tells you the *shape* of growth asymptotically, while benchmarking gives you concrete runtimes on real hardware with real data. Together they answer both \"will this scale?\" and \"is this fast enough today?\"\n",
    "\n",
    "Python provides three main tools for timing code, each suited to a different level of precision:\n",
    "\n",
    "- **`time.time()`**: Returns wall-clock time as a float (seconds since the Unix epoch). Acceptable for rough, one-off prints where precision doesn't matter, but it can be skewed by OS clock corrections.\n",
    "\n",
    "- **`time.perf_counter()`**: Returns a high-resolution performance counter with sub-microsecond precision. It is unaffected by OS clock changes, making it the preferred choice for timing short code blocks in instrumented scripts.\n",
    "\n",
    "- **`timeit.timeit()` / `timeit.repeat()`**: The right tool for microbenchmarking a single expression or small snippet. It disables garbage collection between runs to reduce noise, executes the code many times to average out fluctuations, and manages multiple trials automatically. Use this when you need a repeatable, statistically meaningful measurement of a specific line or expression.\n",
    "\n",
    "| Function | Module | Resolution | Use For | Notes |\n",
    "|---|---|---|---|---|\n",
    "| `time.time()` | `time` | Low (~10ms) | Timestamps, logging, wall-clock elapsed | Can go backward if system clock adjusted |\n",
    "| `time.perf_counter()` | `time` | Very high (ns) | Benchmarking code blocks | Best for measuring elapsed time; never goes backward |\n",
    "| `timeit.timeit()` | `timeit` | Very high (ns) | Microbenchmarks — single expression or function | Runs `number` times, returns total; divide for per-call avg |\n",
    "| `timeit.repeat()` | `timeit` | Very high (ns) | Microbenchmarks across multiple trials | Returns list of times; use `min()` to report best trial |\n",
    "\n",
    "The code below illustrates how the Big $O$ growth classes behave across different input sizes, so you can build intuition before profiling real algorithms."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "b1c4a649",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "time.time()        : 0.000565 s\n",
      "perf_counter()     : 0.000592 s\n",
      "timeit (1000 runs) : 0.5721 s total  |  572.10 µs per call\n",
      "timeit.repeat()    : best of 5 trials = 567.49 µs per call\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import timeit\n",
    "\n",
    "data = list(range(100_000))\n",
    "\n",
    "### --- time.time() --- for timestamps, logging,\n",
    "# Coarse wall-clock timing; fine for quick prints, not precision work.\n",
    "start = time.time()\n",
    "max(data)\n",
    "end = time.time()\n",
    "print(f\"time.time()        : {end - start:.6f} s\")\n",
    "\n",
    "### --- time.perf_counter() --- for benchmarking code performance.\n",
    "# High-resolution counter; preferred for timing instrumented code blocks.\n",
    "start = time.perf_counter()\n",
    "max(data)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(f\"perf_counter()     : {elapsed:.6f} s\")\n",
    "\n",
    "### --- timeit.timeit() ---\n",
    "# Runs the snippet many times and returns total time; best for microbenchmarks.\n",
    "t = timeit.timeit(lambda: max(data), number=1_000)\n",
    "print(f\"timeit (1000 runs) : {t:.4f} s total  |  {t/1000*1e6:.2f} µs per call\")\n",
    "\n",
    "### --- timeit.repeat() ---\n",
    "# Like timeit() but returns a list of times across multiple trials; use min() to report.\n",
    "trials = timeit.repeat(lambda: max(data), repeat=5, number=200)\n",
    "print(f\"timeit.repeat()    : best of 5 trials = {min(trials)/200*1e6:.2f} µs per call\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a45b06f",
   "metadata": {},
   "source": [
    ":::{admonition} Why Use `lambda` with `timeit`?\n",
    "\n",
    "Lambda functions are used here to defer the execution of `max(data)` until `timeit` calls it. \n",
    "If we had written \n",
    "```\n",
    "timeit.timeit(max(data), number=1000)\n",
    "```\n",
    "then `max(data)` runs once immediately and returns a number (e.g. 99999). Then `timeit` tries to call 99999 1000 times — which crashes. The lambda wraps it into a zero-argument function that timeit can call over and over:\n",
    "\n",
    "```\n",
    "t = timeit.timeit(lambda: max(data), number=1_000)\n",
    "#                 ^^^^^^\n",
    "#                 callable — timeit calls this 1000 times\n",
    "#                 each call runs max(data) fresh\n",
    "```\n",
    "\n",
    "This is the same as:\n",
    "\n",
    "```\n",
    "def run():\n",
    "    return max(data)\n",
    "\n",
    "t = timeit.timeit(run, number=1_000)   # same thing, no lambda\n",
    "```\n",
    "\n",
    "The lambda is just a compact way to write that one-liner inline. The pattern `lambda: <expression>` is the standard idiom for \"wrap this expression so it can be called later.\"\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c49a1d40",
   "metadata": {},
   "source": [
    "Of course you would think that, in binary search, you would have to sort the array first, and you would be right. That should be part of the considerations when designing your algorithm. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4b57aff",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Benchmark List vs. Set Membership\n",
    "#   1. Create a list `data` with integers 0 to 99,999.\n",
    "#   2. Create a set `data_set` from the same values.\n",
    "#   3. Use timeit to measure 1000 lookups of 99999 in each.\n",
    "#   4. Print both times and state which is faster.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "78a7f0f5",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "import timeit\n",
    "\n",
    "data     = list(range(100_000))\n",
    "data_set = set(data)\n",
    "\n",
    "t_list = timeit.timeit(lambda: 99999 in data,     number=1000)\n",
    "t_set  = timeit.timeit(lambda: 99999 in data_set, number=1000)\n",
    "\n",
    "print(f'List lookup: {t_list:.4f}s')\n",
    "print(f'Set  lookup: {t_set:.4f}s')\n",
    "print(f'Set is ~{t_list / t_set:.0f}x faster')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50a24779",
   "metadata": {},
   "source": [
    "## Algorithm Selection Guide\n",
    "\n",
    "Use this table as a quick reference when choosing a strategy:\n",
    "\n",
    "| Condition | Recommended Approach | Complexity |\n",
    "|---|---|---|\n",
    "| Unsorted data, few lookups | Linear search | $O(n)$ |\n",
    "| Sorted data, repeated lookups | Binary search | $O(\\log n)$ |\n",
    "| Heavy membership checks on static data | `set`/`dict` lookup | $O(1)$ average |\n",
    "| Nearly sorted input | Insertion sort | $O(n)$ best case |\n",
    "| General-purpose sort | Merge sort / Python Timsort | $O(n \\log n)$ |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "819e9d38",
   "metadata": {
    "tags": [
     "thebe-interactive"
    ]
   },
   "outputs": [],
   "source": [
    "### Exercise: Choose the Right Algorithm\n",
    "#   For each scenario, write (as a comment) the recommended approach\n",
    "#   and its Big O complexity.\n",
    "#   1. A sorted list of 1 million product IDs -- find a specific ID.\n",
    "#   2. An unsorted list of 10 names -- find one name.\n",
    "#   3. Repeated membership checks on a static collection of usernames.\n",
    "#   4. A nearly sorted list of timestamps -- sort them.\n",
    "### Your code starts here.\n",
    "\n",
    "\n",
    "\n",
    "### Your code ends here."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "89af4093",
   "metadata": {
    "tags": [
     "hide-input"
    ]
   },
   "outputs": [],
   "source": [
    "### Solution\n",
    "# 1. Sorted, large list       -> Binary search       O(log n)\n",
    "# 2. Unsorted, tiny list      -> Linear search       O(n)\n",
    "# 3. Static membership checks -> set lookup          O(1) average\n",
    "# 4. Nearly sorted input      -> Insertion sort      O(n) best case\n",
    "\n",
    "# Demonstration of scenario 3:\n",
    "members = {'alice', 'bob', 'charlie', 'dave'}\n",
    "for user in ['alice', 'eve', 'bob']:\n",
    "    status = 'member' if user in members else 'not a member'\n",
    "    print(f'{user}: {status}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98075734",
   "metadata": {},
   "source": []
  }
 ],
 "metadata": {
  "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
}
