{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6b4fce18-128e-441d-9040-7d738b1ac1c8",
   "metadata": {},
   "source": [
    "## PDSP 2025, Lecture 07, 28 August 2025"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b097078-2da4-4f18-9f0e-ff16d591e9bb",
   "metadata": {},
   "source": [
    "### Dictionaries\n",
    "- A list is a collection indexed by position \n",
    "- A list can be thought of as a function $f: \\{0,1,\\ldots,n-1\\} \\to \\{v_0,v_1,\\ldots,v_{n-1}\\}$\n",
    "  - A list maps positions to values\n",
    "- Generalize this to a function $f: \\{k_0,k_1,\\ldots,k_{n-1}\\} \\to \\{v_0,v_1,\\ldots,v_{n-1}\\}$\n",
    "  - Instead of positions, index by an abstract *key*\n",
    "- **dictionary**: maps keys, rather than positions, to values\n",
    "- Notation:\n",
    "  - `d = {k1:v1, k2:v2}`, enumerate a dictionary explicitly\n",
    "  - `d[k1]`, value in dictionary `d1` corresponding to key `k1`\n",
    "  - `{}`, empty dictionary (`[]` for lists, `()` for tuples)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d8fc9e79-badf-4882-ac76-f1f3f8f377fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {'a':1,'b':17,'c':0}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "bef0ef37-d2ea-4b0d-9a69-b466d9191575",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "17"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d['b']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "7fa68be6-5640-4943-b725-04d403fae808",
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyError",
     "evalue": "'d'",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mKeyError\u001b[39m                                  Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[3]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43md\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43md\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m]\u001b[49m  \u001b[38;5;66;03m# Invalid key\u001b[39;00m\n",
      "\u001b[31mKeyError\u001b[39m: 'd'"
     ]
    }
   ],
   "source": [
    "d['d']  # Invalid key"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "bf5a300b-7f80-4942-a030-b856cee1b545",
   "metadata": {},
   "outputs": [],
   "source": [
    "d['d'] = 17"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ed814d6f-c80f-48b1-9840-b1137544108f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "17"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d['d']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d45aada7-b69f-45a8-9476-71f22e9e5039",
   "metadata": {},
   "source": [
    "- An assignment `d[k] = v` serves two purposes\n",
    "    - If there is no key `k`, create the key and assign it the value `v`\n",
    "    - If there is already a key `k`, replace its current value by `v`\n",
    "- In a list, we cannot create a value at a new position through an assignment\n",
    "    - If `l` is `[0,1,2,3]`, `l[4] = 4` generate `IndexError`\n",
    "    - If `d = {'a':1,'b':17,'c':0}`, `d['d'] = 19` extends `d` with a new key-value pair"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e34224d1-dc91-4e8c-bcb3-a3dfa8d89d8f",
   "metadata": {},
   "source": [
    "**Iteration**\n",
    "- `d.keys()` generates a sequence of all keys in `d`\n",
    "    - Iterate over keys using `for k in d.keys():`\n",
    "    - `for k in d:` also works --- `d` is implicitly intepreted as `d.keys()`\n",
    "    - Though the keys do not form a sequence, Python will generate them in the order in which they were created\n",
    "- Similarly, `d.values()` is the sequence of values present\n",
    "  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "a1aa6272-8938-41c8-ac6b-f2f58825f7a2",
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {'a':1,'b':17,'c':0}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "0383ea4d-5073-45a8-b645-955b9544207b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(['a', 'b', 'c'], [1, 17, 0])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(d.keys()), list(d.values())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "38ea0aed-88ca-41a3-bfc0-3b64e54c1677",
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {'b':17,'c':0,'a':1}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ef844f01-bbfb-43ba-8f72-97a3b85c2e79",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(['b', 'c', 'a'], [17, 0, 1])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(d.keys()), list(d.values())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b52eaa2d-dd8b-4896-abe5-bbdabe4ec31e",
   "metadata": {},
   "source": [
    "**Example**\n",
    "\n",
    "- Count frequency of numbers in a list\n",
    "- Maintain a counter for each value that appears in the list\n",
    "- Dictionary `freqd` where each key is a number `v` and `freqd[v]` is a positive integer\n",
    "    - The first time we see a number, need to create a key and assign it the value 1\n",
    "    - If there is already a key for the current number, increment its count\n",
    "    - Test if a key `k` is present using `k in d.keys()` (or, shorter, `k in d`)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "9b583044-1bf4-440a-a8d5-259bc274b34e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def frequency(l):\n",
    "    freqd = {}\n",
    "    for v in l:\n",
    "        if v in freqd:  # Check if v is already a key\n",
    "            freqd[v] = freqd[v] + 1\n",
    "        else:           # Create a new key with count 1\n",
    "            freqd[v] = 1\n",
    "    return(freqd)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "123efc15-6c7a-4be7-9e37-e810fdc68b12",
   "metadata": {},
   "outputs": [],
   "source": [
    "d = frequency([1,2,1,3,1,4,2,3,1,5,6,2,7])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "8918abd1-6f52-4a32-b030-df111773ba07",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{1: 4, 2: 3, 3: 2, 4: 1, 5: 1, 6: 1, 7: 1}"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f4341c5-7b90-482a-99e0-f3ebf8feaadc",
   "metadata": {},
   "source": [
    "- Keys are listed in the order they are inserted\n",
    "- This is guaranteed by current versions of Python, need not hold for dictionaries in general"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "bf57db3b-c302-4173-b22f-8100dc4eddc5",
   "metadata": {},
   "outputs": [],
   "source": [
    "d2 = frequency([1,2,1,3,1,4,2,3,1,5,7,2,6])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "cdc2730e-e7c1-483e-8965-42208c5df0f0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{1: 4, 2: 3, 3: 2, 4: 1, 5: 1, 7: 1, 6: 1}"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd4722c7-df92-40da-8128-0bb0d2d37e43",
   "metadata": {},
   "source": [
    "### List membership\n",
    "- `v in l` returns `True` iff value `v` is in `l`\n",
    "- Implicit iteration, same as\n",
    "\n",
    "```\n",
    "def element(l,v):\n",
    "  for x in l:\n",
    "    if x == v:\n",
    "      return(True)\n",
    "  return(False)\n",
    "```\n",
    "\n",
    "- Linear scan of the list, examine all elements (worst case) if `v` is not in `l`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f6b6186-6978-42fe-bc59-3e5616c1445b",
   "metadata": {},
   "source": [
    "### Extract unique elements from a list\n",
    "- Standard loop builds a new list of unique elements\n",
    "- Check if each element in the original list is already in the new list before adding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "ece74d64-ebab-4ae6-9a12-63ab889bb444",
   "metadata": {},
   "outputs": [],
   "source": [
    "def uniq(l):\n",
    "    uniqlist = []\n",
    "    for x in l:\n",
    "        if not (x in uniqlist):  # Implicit nested loop\n",
    "            uniqlist.append(x)\n",
    "    return(uniqlist)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0082342c-06e8-443b-bb61-6932a56ea98f",
   "metadata": {},
   "source": [
    "### Complexity\n",
    "- Worst case is when original list has no duplicates\n",
    "- `l[k]` will be compared to `k` elements in `newl` before being added to `newl`\n",
    "- Takes $1 + 2 + \\cdots n-1$ steps, which is $\\displaystyle \\frac{n(n-1)}{2}$\n",
    "    - Proportional to $n^2$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7045458-a046-4c38-b418-825451888dca",
   "metadata": {},
   "source": [
    "### Using a dictionary\n",
    "- Cannot have duplicate keys in a dictionary\n",
    "- Create a dictionary whose keys are values in the original list\n",
    "    - Value associated with key is not important\n",
    "    - If we see the same value twice, the key will be updated, not duplicated\n",
    "- In the end, return the list of keys"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "6638ea8a-8203-4353-93c5-305021093606",
   "metadata": {},
   "outputs": [],
   "source": [
    "def uniqd(l):\n",
    "    uniqdict = {}\n",
    "    for x in l:\n",
    "        uniqdict[x] = 1\n",
    "    return(list(uniqdict))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "893ac1de-80a0-4a53-9309-10939a930d9c",
   "metadata": {},
   "source": [
    "### Complexity\n",
    "- Creating/updating a key in a dictionary takes a fixed amount of time, independent of the size of the dictionary\n",
    "    - Assuming the hash function works well and there are no (or very few) collisions\n",
    "- This works effectively in time proportional to $n$, the length of the list"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c951133f-4efe-4283-aa5e-922da2f34cf0",
   "metadata": {},
   "source": [
    "- We can experimentally verify this by applying both functions to a large list without duplicates\n",
    "    - In the examples below, we have asked for the length of the list rather than the list itself to avoid large outputs cluttering the page"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "c589ded9-7389-4901-afa3-2bd2493f7f43",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50000"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(uniq(list(range(50000)))) # Takes a long time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "2a4939be-23df-41f5-a6b9-dd21f4ca51ef",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "100000"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(uniqd(list(range(100000))))  # Almost instantaneous"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "5fc9c038-35cc-431d-a7ed-798293d529e0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10000000"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(uniqd(list(range(10000000)))) # Python can do about 10^7 ops/sec"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "926cddb7-63cc-49b2-9aea-3b1450118b29",
   "metadata": {},
   "source": [
    "- \"Classical\" solution is to sort the list\n",
    "- In the sorted list, duplicates are bunched together\n",
    "- Scan the sorted list and retain an element if it is different from the previous one\n",
    "- Sorting takes time $n \\log n$ -- we will see this later\n",
    "- Scanning for duplicates takes time $n$\n",
    "- Overall $n \\log n$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "f03c411f-ef8c-46c9-afc2-835db9c86def",
   "metadata": {},
   "outputs": [],
   "source": [
    "def uniqs(l):\n",
    "    if l == []:\n",
    "        return([])\n",
    "    lsorted = sorted(l)\n",
    "    uniqlist,previous = [l[0]],l[0]\n",
    "    for x in lsorted[1:]:\n",
    "        if x != previous:\n",
    "            uniqlist.append(x)\n",
    "        previous = x\n",
    "    return(uniqlist)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "5c3258cb-1e8b-4f89-8b3e-d6cc8cf55ff4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50000"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(uniqs(list(range(50000))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "f366740e-907d-459b-811b-3ac5d8b93e52",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10000000"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(uniqs(list(range(10000000))))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38bcc28f-3dc7-43bd-b528-ff3d818a9fb5",
   "metadata": {},
   "source": [
    "- Only marginally slower than `uniqd`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a874779-3793-47f6-a4c8-a0ba7e51bbbc",
   "metadata": {},
   "source": [
    "## Intersection of two lists"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "901ec604-eee9-4820-a07d-f6d757893aa6",
   "metadata": {},
   "source": [
    "- For each element `v` of `l1` check if `v` occurs in `l2`\n",
    "- Nested loop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "5539c8d5-73df-4028-8d7f-4b05b3841884",
   "metadata": {},
   "outputs": [],
   "source": [
    "def intersect(l1,l2):\n",
    "    commonlist = []\n",
    "    for x in l1:\n",
    "        for y in l2:\n",
    "            if x == y:\n",
    "                if not(x in commonlist):\n",
    "                    commonlist.append(x)\n",
    "    return(commonlist)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "816ad5b4-ded7-4041-b6df-56362ef10677",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(intersect(list(range(0,100)),list(range(50,150))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "8e8fc584-03b5-4cf9-881f-394786a0b5ee",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15000"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(intersect(list(range(0,30000)),list(range(15000,45000))))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3290dc8-557f-4f0f-94f4-4195b58ef9ad",
   "metadata": {},
   "source": [
    "- While we scan `l1` we can check if the element is in `l2`\n",
    "- `x in l2` is an implicit nested iteration\n",
    "- Performance is same as the explicit nested loop above"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "921fd31b-761d-4fce-a349-e0f051bb207f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def intersect2(l1,l2):\n",
    "    commonlist = []\n",
    "    for x in l1:\n",
    "        if x in l2: # Hidden nested loop\n",
    "            if not(x in commonlist):\n",
    "                commonlist.append(x)\n",
    "    return(commonlist)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "c386dae3-2886-45aa-b25e-e4d2bc68b7cc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(intersect2(list(range(0,100)),list(range(50,150))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "27cd0248-9dbe-428c-9fcd-e8ccd51e5c51",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15000"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(intersect2(list(range(0,30000)),list(range(15000,45000))))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c747609-aecf-4e99-90ec-87ff58e0cc75",
   "metadata": {},
   "source": [
    "- Using dictionaries\n",
    "    - Create a dictionary whose keys are the elements in `l1`\n",
    "    - Check each element in `l2` against the keys of this dictionary\n",
    "- Checking `x in l` takes time proportional to `len(l)`\n",
    "- Checking `y in d.keys()` takes constant time\n",
    "- Overall time is proportional to `len(l1) + len(l2)`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "f0082296-c5d8-4dab-b008-7a3267675b1d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def intersectd(l1,l2):\n",
    "    commondict = {}\n",
    "    l1dict = {}\n",
    "    for x in l1:\n",
    "        l1dict[x] = 1\n",
    "    for y in l2:\n",
    "        if y in l1dict:\n",
    "            commondict[y] = 1\n",
    "    return(list(commondict.keys()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "4a348da3-b914-4ff9-97b2-8b9ecf3f9826",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "150000"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(intersectd(list(range(0,300000)),list(range(150000,450000))))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1bb8b585-5a3a-441d-914f-b303aa56d88f",
   "metadata": {},
   "source": [
    "- \"Classical\" solution is to sort and merge\n",
    "    - Sort both lists\n",
    "    - Scan the two sorted lists in a single pass and identify common elements\n",
    "- Sorting takes time $n \\log n$\n",
    "- Merging takes time $n$\n",
    "- Overall $n \\log n$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "08c2753e-4c92-41a7-9e7e-8b7ff5ce008f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def intersectm(l1,l2):\n",
    "    l1sort = sorted(l1)\n",
    "    l2sort = sorted(l2)\n",
    "    commonlist = []\n",
    "    i,j = 0,0\n",
    "    while i < len(l1sort) and j < len(l2sort):\n",
    "        if l1sort[i] < l2sort[j]:\n",
    "            i += 1\n",
    "        elif l1sort[i] > l2sort[j]:\n",
    "            j += 1\n",
    "        elif l1sort[i] == l2sort[j]:\n",
    "            if (not l1sort[i] in commonlist):\n",
    "                commonlist.append(l1sort[i])\n",
    "            i += 1\n",
    "            j += 1\n",
    "    return(commonlist)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "e32c4800-add5-4726-8e88-d6df4cf18b10",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "25000"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(intersectm(list(range(0,50000)),list(range(25000,75000))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "a018d3f5-4ab4-4704-8d7c-a8e7f5811543",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "50000"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(intersectm(list(range(0,100000)),list(range(50000,150000))))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a948ba6-1eb7-4c3a-a85c-91236eba1da8",
   "metadata": {},
   "source": [
    "- Noticeably slower than `intersectd`"
   ]
  }
 ],
 "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
}
