{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6b4fce18-128e-441d-9040-7d738b1ac1c8",
   "metadata": {},
   "source": [
    "## PDSP 2025, Lecture 12, 16 September 2025"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b9a5d845",
   "metadata": {},
   "source": [
    "### Scope and global variables\n",
    "- The scope of a variable refers to the portion of the program where its value is available\n",
    "- If we refer to a value that is not defined in a function, it is looked up in the global context"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f585472",
   "metadata": {
    "id": "k3jspHRc8oLq"
   },
   "source": [
    "### Arrays\n",
    "- Contiguous block of memory\n",
    "- Typically size is declared in advance, all values are uniform\n",
    "- `a[0]` points to first memory location in the allocated block\n",
    "- Locate `a[i]` in memory using index arithmetic\n",
    "  - Skip `i` blocks of memory, each block's size determined by value stored in array\n",
    "- **Random access** -- accessing the value at `a[i]` does not depend on `i`\n",
    "- Useful for procedures like sorting, where we need to swap out of order values `a[i]` and `a[j]`\n",
    "  - `a[i], a[j] = a[j], a[i]`\n",
    "  - Cost of such a swap is constant, independent of where the elements to be swapped are in the array\n",
    "- Inserting or deleting a value is expensive\n",
    "- Need to shift elements right or left, respectively, depending on the location of the modification"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fad3a11b",
   "metadata": {
    "id": "afE0tg-G9h6T"
   },
   "source": [
    "### Lists\n",
    "- Each location is a *cell*, consisiting of a value and a link to the next cell\n",
    "  - Think of a list as a train, made up of a linked sequence of cells\n",
    "- The name of the list `l` gives us access to `l[0]`, the first cell\n",
    "- To reach cell `l[i]`, we must traverse the links from `l[0]` to `l[1]` to `l[2]` $\\ldots$ to `l[i-1]`] to `l[i]`\n",
    "  - Takes time proportional to `i`\n",
    "- Cost of swapping `l[i]` and `l[j]` varies, depending on values `i` and `j`\n",
    "- On the other hand, if we are already at `l[i]` modifying the list is easy\n",
    "  - *Insert* - create a new cell and reroute the links\n",
    "  - *Delete* - bypass the deleted cell by rerouting the links\n",
    "- Each insert/delete requires a fixed amount of local \"plumbing\", independent of where in the list it is performed"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ecdb204",
   "metadata": {
    "id": "8yTypELQ-dcz"
   },
   "source": [
    "### Dictionaries\n",
    "- Values are stored in a fixed block of size $m$\n",
    "- Keys are mapped to $\\{0,1,\\ldots,m-1\\}$\n",
    "- Hash function $h: K \\to S$ maps a *large* set of keys $K$ to a *small* range $S$\n",
    "- Simple hash function: interpret $k \\in K$ as a bit sequence representing a number $n_k$ in binary, and compute $n_k \\bmod m$, where $|S| = m$\n",
    "- Mismatch in sizes means that there will be *collisions* -- $k_1 \\neq k_2$, but $h(k_1) = h(k_2)$\n",
    "- A good hash function maps keys \"randomly\" to minimize collisions\n",
    "- Hash can be used as a *signature* of authenticity\n",
    "  - Modifying $k$ slightly will drastically alter $h(k)$\n",
    "  - No easy way to reverse engineer a $k'$ to map to a given $h(k)$\n",
    "  - Use to check that large files have not been tampered with in transit, either due to network errors or malicious intervention\n",
    "- Dictionary uses a hash function to map key values to storage locations\n",
    "- Lookup requires computing $h(k)$ which takes roughly the same time for any $k$\n",
    "  - Compare with computing the offset `a[i]` for any index `i` in an array\n",
    "- Collisions are inevitable, different mechanisms to manage this, which we will not discuss now\n",
    "- Effectively, a dictionary combines flexibility with random access"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5718ce2",
   "metadata": {
    "id": "pmdHUp9catwl"
   },
   "source": [
    "### Lists in Python\n",
    "- Flexible size, allow inserting/deleting elements in between\n",
    "- However, implementation is an array, rather than a list\n",
    "- Initially allocate a block of storage to the list\n",
    "- When storage runs out, double the allocation\n",
    "- `l.append(x)` is efficient, moves the right end of the list one position forward within the array\n",
    "- `l.insert(0,x)` inserts a value at the start, expensive because it requires shifting all the elements by 1\n",
    "- We will run experiments to validate these claims"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9c10e9b",
   "metadata": {},
   "source": [
    "### Measuring execution time\n",
    "- Call `time.perf_counter()`\n",
    "- Actual return value is meaningless, but difference between two calls measures time in seconds"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "3ba6e7ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2f4e334c-a8a8-48f4-873a-6df829712c5d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "174232.591312591"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "time.perf_counter()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9404f16f",
   "metadata": {},
   "source": [
    "- $10^7$ appends to an empty Python list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "274f1c27",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "sJEWTNND7lNj",
    "outputId": "22c49e9c-4a48-4cda-b06f-cb65a29a73f3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.45830543397460133\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = []\n",
    "for i in range(10000000):\n",
    "    l.append(i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbc629f4",
   "metadata": {},
   "source": [
    "- Doubling the work approximately doubles the time, linear"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ab3672e6",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "sJEWTNND7lNj",
    "outputId": "22c49e9c-4a48-4cda-b06f-cb65a29a73f3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.9973017750016879\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = []\n",
    "for i in range(20000000):\n",
    "    l.append(i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "8c4133bf-d851-4435-8ae3-4b810054cb52",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "sJEWTNND7lNj",
    "outputId": "22c49e9c-4a48-4cda-b06f-cb65a29a73f3"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0422130890074186\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = []\n",
    "for i in range(40000000):\n",
    "    l.append(i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c01b9010",
   "metadata": {},
   "source": [
    "- $10^5$ inserts at the beginning of a Python list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c1324aa0",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NXfPd11Q7pon",
    "outputId": "7850cc4e-8087-41cd-de21-72f8ebf9a1ee"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.0743082059780136\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = []\n",
    "for i in range(100000):\n",
    "    l.insert(0,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "487c5a59",
   "metadata": {},
   "source": [
    "- Doubling and tripling the work multiplies the time by $4$ and $9$, respectively, so quadratic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c04aa692",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NXfPd11Q7pon",
    "outputId": "7850cc4e-8087-41cd-de21-72f8ebf9a1ee"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.0179102800029796\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = []\n",
    "for i in range(200000):\n",
    "    l.insert(0,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "520440ca",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NXfPd11Q7pon",
    "outputId": "7850cc4e-8087-41cd-de21-72f8ebf9a1ee"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "8.792953492986271\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = []\n",
    "for i in range(300000):\n",
    "    l.insert(0,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "47680763-71c8-431c-9160-2e0b5b43afa9",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "NXfPd11Q7pon",
    "outputId": "7850cc4e-8087-41cd-de21-72f8ebf9a1ee"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "19.935622199001955\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = []\n",
    "for i in range(400000):\n",
    "    l.insert(0,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37fe3ca2-4a23-4b12-b5cd-0e9cf905bee0",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "468142ee-71b3-412e-b912-ef93124c0868",
   "metadata": {},
   "source": [
    "- Another experiment\n",
    "- First create a list with 5000, 10000, ... items\n",
    "- Then do 10000, 20000, ... repetitions of del(l[0]) and l.insert(0,v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0b56f855-6146-4522-a400-82fc90cfa62f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10000 0.008301274996483698\n",
      "20000 0.03995348702301271\n",
      "30000 0.09486832498805597\n",
      "40000 0.17343379600788467\n",
      "50000 0.2747334270097781\n",
      "60000 0.3940669430012349\n",
      "70000 0.5423859239963349\n",
      "80000 0.7112683840095997\n",
      "90000 0.9038651129812934\n",
      "100000 1.124114845006261\n"
     ]
    }
   ],
   "source": [
    "for j in range(1,11):\n",
    "    l = []\n",
    "    for i in range(j*5000):\n",
    "        l.append(i)\n",
    "\n",
    "    start = time.perf_counter()\n",
    "    for i in range(j*10000):\n",
    "        del(l[0])\n",
    "        l.insert(0,i)\n",
    "    elapsed = time.perf_counter() - start\n",
    "    print(j*10000,elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e625faec",
   "metadata": {},
   "source": [
    "- Creating $10^7$ entries in an empty dictionary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "f234d292",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "U9nqv6Mq9_tY",
    "outputId": "6dc83f95-1dc6-47a5-dc86-ff3e6a1c5b22"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.8891144850058481\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "d = {}\n",
    "for i in range(10000000,0,-1):\n",
    "    d[i] = i\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55cda94c",
   "metadata": {},
   "source": [
    "- Doubling the operations, doubles the time, so linear\n",
    "- Dictionaries are effectively random access"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "d87da6f1",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "U9nqv6Mq9_tY",
    "outputId": "6dc83f95-1dc6-47a5-dc86-ff3e6a1c5b22"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.7162719670159277\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "d = {}\n",
    "for i in range(20000000,0 ,-1):\n",
    "    d[i] = i\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3aed8507",
   "metadata": {},
   "source": [
    "- Insert keys in random order\n",
    "- Use the library function `random.shuffle(l)` to permute the elements of `l`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "f9a581b8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6, 5, 37, 16, 9, 3, 99, 66, 13, 49, 60, 22, 36, 95, 2, 89, 53, 70, 26, 28, 74, 41, 44, 80, 79, 35, 78, 10, 29, 42, 59, 83, 64, 67, 30, 32, 96, 94, 27, 4, 71, 21, 62, 0, 7, 45, 39, 97, 12, 69, 40, 68, 91, 61, 17, 58, 76, 1, 88, 72, 50, 33, 19, 93, 14, 18, 11, 54, 63, 47, 85, 73, 8, 92, 56, 34, 43, 48, 55, 20, 75, 51, 23, 38, 65, 84, 31, 24, 46, 86, 57, 90, 81, 25, 52, 82, 15, 98, 87, 77]\n"
     ]
    }
   ],
   "source": [
    "import random\n",
    "lhundred = list(range(100))\n",
    "random.shuffle(lhundred)\n",
    "print(lhundred)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51dca1ee",
   "metadata": {},
   "source": [
    "- Insert $10^6$ keys in random order\n",
    "- Note that we start the counter *after* we shuffle the list of keys, so we count only the time required to populate the dictionary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "b82ed5d6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sequential keys: 0.06454657801077701\n",
      "Shuffled keys: 0.08987153199268505\n"
     ]
    }
   ],
   "source": [
    "import random\n",
    "keylist = list(range(1000000,0,-1))\n",
    "rndkeylist = keylist[:]  # Copy keylist into rndkeylis\n",
    "random.shuffle(rndkeylist)\n",
    "\n",
    "d = {}\n",
    "start = time.perf_counter()\n",
    "for i in keylist:\n",
    "    d[i] = i\n",
    "elapsed = time.perf_counter() - start\n",
    "print(\"Sequential keys:\", elapsed)\n",
    "\n",
    "d = {}\n",
    "start = time.perf_counter()\n",
    "for i in rndkeylist:\n",
    "    d[i] = i\n",
    "elapsed = time.perf_counter() - start\n",
    "print(\"Shuffled keys:\", elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3334277d",
   "metadata": {},
   "source": [
    "- Double the number of keys to $2 \\times 10^6$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "ddb36b00",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sequential keys: 0.1401691969949752\n",
      "Shuffled keys: 0.26284310102346353\n"
     ]
    }
   ],
   "source": [
    "import random\n",
    "keylist = list(range(2000000,0,-1))\n",
    "rndkeylist = keylist[:]\n",
    "random.shuffle(rndkeylist)\n",
    "\n",
    "d = {}\n",
    "start = time.perf_counter()\n",
    "for i in keylist:\n",
    "    d[i] = i\n",
    "elapsed = time.perf_counter() - start\n",
    "print(\"Sequential keys:\", elapsed)\n",
    "\n",
    "d = {}\n",
    "start = time.perf_counter()\n",
    "for i in rndkeylist:\n",
    "    d[i] = i\n",
    "elapsed = time.perf_counter() - start\n",
    "print(\"Shuffled keys:\", elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18bcd1db",
   "metadata": {},
   "source": [
    "- Triple the number of keys to $3 \\times 10^6$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "4fee22e2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sequential keys: 0.25086971500422806\n",
      "Shuffled keys: 0.5316497589810751\n"
     ]
    }
   ],
   "source": [
    "import random\n",
    "keylist = list(range(3000000,0,-1))\n",
    "rndkeylist = keylist[:]\n",
    "random.shuffle(rndkeylist)\n",
    "\n",
    "d = {}\n",
    "start = time.perf_counter()\n",
    "for i in keylist:\n",
    "    d[i] = i\n",
    "elapsed = time.perf_counter() - start\n",
    "print(\"Sequential keys:\", elapsed)\n",
    "\n",
    "d = {}\n",
    "start = time.perf_counter()\n",
    "for i in rndkeylist:\n",
    "    d[i] = i\n",
    "elapsed = time.perf_counter() - start\n",
    "print(\"Shuffled keys:\", elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1f3f386",
   "metadata": {},
   "source": [
    "- Using shuffled keys is slower than inserting keys in sequence\n",
    "- However, even after shuffling, the time taken grows approximately linearly"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6700b534",
   "metadata": {},
   "source": [
    "### Implementing a \"real\" list using dictionaries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "b8547085",
   "metadata": {
    "id": "_O05Oyg4-Sny"
   },
   "outputs": [],
   "source": [
    "def createlist():  # Equivalent of l = [] is l = createlist()\n",
    "  return({})\n",
    "\n",
    "def listappend(l,x):\n",
    "  if l == {}:\n",
    "    l[\"value\"] = x\n",
    "    l[\"next\"] = {}\n",
    "    return\n",
    "  \n",
    "  node = l\n",
    "  while node[\"next\"] != {}:\n",
    "    node = node[\"next\"]\n",
    "    \n",
    "  node[\"next\"][\"value\"] = x\n",
    "  node[\"next\"][\"next\"] = {}\n",
    "  return\n",
    "\n",
    "def listinsert(l,x):\n",
    "  if l == {}:\n",
    "    l[\"value\"] = x\n",
    "    l[\"next\"] = {}\n",
    "    return\n",
    "\n",
    "  newnode = {}\n",
    "  newnode[\"value\"] = l[\"value\"]\n",
    "  newnode[\"next\"] = l[\"next\"]\n",
    "  l[\"value\"] = x\n",
    "  l[\"next\"] = newnode\n",
    "  return\n",
    "\n",
    "\n",
    "def printlist(l):\n",
    "  print(\"{\",end=\"\")\n",
    "\n",
    "  if l == {}:\n",
    "    print(\"}\")\n",
    "    return\n",
    "  node = l\n",
    "\n",
    "  print(node[\"value\"],end=\"\")\n",
    "  while node[\"next\"] != {}:\n",
    "    node = node[\"next\"]\n",
    "    print(\",\",node[\"value\"],end=\"\")\n",
    "  print(\"}\")\n",
    "  return\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f0afda2",
   "metadata": {},
   "source": [
    "- Display a small list as nested dictionaries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "f0b25e24",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "vfWs_rvWJYm6",
    "outputId": "5a3b7a4c-6700-47d9-e6be-69041d2ef89c"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.00026201800210401416\n",
      "{'value': 0, 'next': {'value': 1, 'next': {'value': 2, 'next': {'value': 3, 'next': {'value': 4, 'next': {'value': 5, 'next': {'value': 6, 'next': {'value': 7, 'next': {'value': 8, 'next': {'value': 9, 'next': {}}}}}}}}}}}\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = createlist()\n",
    "for i in range(10):\n",
    "    listappend(l,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)\n",
    "print(l)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a03a55d8",
   "metadata": {},
   "source": [
    "- Insert $10^7$ elements at the beginning in this implementation of a list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "6cbb53df",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "RGBs8QLuJ4gU",
    "outputId": "ea35f5b9-cf23-432e-c541-c34439634c15"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.8786353190080263\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = createlist()\n",
    "for i in range(1000000):\n",
    "    listinsert(l,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c58ff972",
   "metadata": {},
   "source": [
    "- Doubling the work doubles the time, so linear"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "ac3e1183",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "RGBs8QLuJ4gU",
    "outputId": "ea35f5b9-cf23-432e-c541-c34439634c15"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.4174943980178796\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = createlist()\n",
    "for i in range(2000000):\n",
    "    listinsert(l,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e397e1a",
   "metadata": {},
   "source": [
    "- Append $10^4$ elements in this implementation of a list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "d8121d14",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.7873705030069686\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = createlist()\n",
    "for i in range(10000):\n",
    "    listappend(l,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17eaa284",
   "metadata": {},
   "source": [
    "- Halving the work takes 1/4 of the time, so quadratic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "3ae96032",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.4203911299991887\n"
     ]
    }
   ],
   "source": [
    "start = time.perf_counter()\n",
    "l = createlist()\n",
    "for i in range(5000):\n",
    "    listappend(l,i)\n",
    "elapsed = time.perf_counter() - start\n",
    "print(elapsed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "405608ea",
   "metadata": {
    "id": "gp6IQSsVmwlF"
   },
   "source": [
    "### Defining our own data structures\n",
    "- We have implemented a \"linked\" list using dictionaries\n",
    "- The fundamental functions like `listappend`, `listinsert`, `listdelete` modify the underlying list\n",
    "- Instead of `mylist = {}`, we wrote `mylist = createlist()`\n",
    "- To check empty list, use a function `isempty()` rather than `mylist == {}`\n",
    "- Can we clearly separate the **interface** from the **implementation**\n",
    "- Define the data structure in a more \"modular\" way\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
