{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6e9781b0",
   "metadata": {},
   "source": [
    "# Aliasing and Copying\n",
    "\n",
    "When working with lists, it's crucial to understand the difference between aliasing, shallow copies, and deep copies. These concepts determine whether changes to one list affect another. The table below summarizes the key differences between aliasing, shallow copy, and deep copy:\n",
    "\n",
    "| Concept      | Outer Object | Inner Objects | Syntax                    | Changes affect original? |\n",
    "| ------------ | ------------ | ------------- | ------------------------- | ------------------------ |\n",
    "| Aliasing     | **Same**     | **Same**      | `b = a`                   | Yes                      |\n",
    "| Shallow Copy | **New**      | **Same**      | `[:]`, `copy()`, `list()` | Sometimes (when nested)  |\n",
    "| Deep Copy    | **New**      | **New**       | `copy.deepcopy()`         | No                       |\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0a0b30c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "# Find project root by looking for _config.yml\n",
    "current = Path.cwd()\n",
    "for parent in [current, *current.parents]:\n",
    "    if (parent / '_config.yml').exists():\n",
    "        project_root = parent\n",
    "        break\n",
    "else:\n",
    "    project_root = Path.cwd().parent.parent\n",
    "\n",
    "# Add project root to path\n",
    "sys.path.insert(0, str(project_root))\n",
    "\n",
    "# Import shared teaching helpers and cell magics\n",
    "from shared import thinkpython, diagram, jupyturtle, structshape\n",
    "from shared.download import download\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3383bdc",
   "metadata": {},
   "source": [
    "## Aliasing\n",
    "\n",
    "Aliasing occurs when two or more variables point to the same object in memory. When you do a **variable assignment** using `=` in Python, you're not **copying** the object—you're creating another variable that points to the **same object**. This second variable is an **alias**.\n",
    "\n",
    "Both variables refer to the same list object, so any modification through either variable affects the same underlying list:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "id": "59ba7aa8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original:\t [999, 2, 3, 4, 5]\n",
      "Alias:\t\t [999, 2, 3, 4, 5]\n",
      "ID of original:\t 4570906048\n",
      "ID of alias:\t 4570906048\n",
      "Same object?\t True\n"
     ]
    }
   ],
   "source": [
    "# Aliasing: both variables point to the same list\n",
    "original = [1, 2, 3, 4, 5]\n",
    "alias = original    ### NOT a copy!\n",
    "\n",
    "alias[0] = 999      ### Modify through the alias\n",
    "\n",
    "print(\"Original:\\t\", original)  # check to see if original is modified\n",
    "print(\"Alias:\\t\\t\", alias)        \n",
    "\n",
    "print(\"ID of original:\\t\", id(original))    ### check the memory address of original\n",
    "print(\"ID of alias:\\t\", id(alias))          ### check the memory address of alias\n",
    "\n",
    "print(\"Same object?\\t\", original is alias)  # True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48707a9d",
   "metadata": {},
   "source": [
    "Contrast with **immutable** strings:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "79650416",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Chen 4569344368\n",
      "Chen 4569344368\n",
      "Alice 4569343792\n",
      "Chen 4569344368\n",
      "Same ID? False\n"
     ]
    }
   ],
   "source": [
    "# Contrast with immutable strings\n",
    "from os import name\n",
    "\n",
    "\n",
    "name1 = \"Chen\"\n",
    "print(name1, id(name1))         # \"Chen\"\n",
    "\n",
    "name2 = name1                   # Alias created\n",
    "print(name2, id(name2))         # \"Chen\" - same object\n",
    "\n",
    "name2 = \"Alice\"                 # Creates NEW object, reassigns name2\n",
    "print(name2, id(name2))         # \"Alice\" - NEW object\n",
    "\n",
    "print(name1, id(name1))           # \"Chen\" - UNCHANGED!\n",
    "print(\"Same ID?\", id(name1) == id(name2))  # Check if name1 and name2 point to the same object (should be False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2816d85",
   "metadata": {},
   "source": [
    "Aliasing is useful for efficiency (no copying needed), but can cause unexpected behavior if you modify mutable objects, thinking you have independent copies."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47259663",
   "metadata": {},
   "source": [
    "## Shallow Copy\n",
    "\n",
    "To create a shallow copy, you can use \n",
    "- list slicing `[:]`, \n",
    "- the `.copy()` method, \n",
    "- the `list()` function, or \n",
    "- `copy.copy()`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "0084ced0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import copy\n",
    "\n",
    "a = [1, 2, 3]\n",
    "\n",
    "b = a[:]          # list slicing\n",
    "b = a.copy()      # copy() method\n",
    "b = list(a)       # list() function\n",
    "b = copy.copy(a)  # copy module"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b7bf643",
   "metadata": {},
   "source": [
    "All these methods create a new list object, but if the list contains other mutable objects (like nested lists), those nested objects are not copied—only their references are copied.\n",
    "\n",
    "A shallow copy creates a new object while retaining references to the objects contained in the original. It only copies the top-level structure without duplicating nested elements. \n",
    "\n",
    "For simple **1-D lists** (containing only **immutable** objects like numbers, strings, or booleans), shallow copying works perfectly fine and you shall see the new lists all have different ID's."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "5509ba45",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['a', 'b', 'c', 'd'] 4570940096\n",
      "['a', 'b', 'c', 'd'] 4570952128\n",
      "['a', 'b', 'c', 'd'] 4570941184\n",
      "['a', 'b', 'c', 'd'] 4570940288\n",
      "All copies have the same contents? True\n",
      "letters_copy1 is letters? False\n",
      "letters_copy2 is letters? False\n",
      "letters_copy3 is letters? False\n",
      "letters_copy4 is letters? False\n"
     ]
    }
   ],
   "source": [
    "# four ways to create a shallow copy\n",
    "import copy\n",
    "\n",
    "letters = ['a', 'b', 'c', 'd']\n",
    "\n",
    "letters_copy1 = letters[:]          # list slicing\n",
    "letters_copy2 = letters.copy()      # the copy() method\n",
    "letters_copy3 = list(letters)       # the list function\n",
    "letters_copy4 = copy.copy(letters)  # using the copy module\n",
    "    \n",
    "print(letters_copy1, id(letters_copy1))\n",
    "print(letters_copy2, id(letters_copy2))\n",
    "print(letters_copy3, id(letters_copy3))\n",
    "print(letters_copy4, id(letters_copy4))\n",
    "\n",
    "print(\"All copies have the same contents?\", letters_copy1 == letters_copy2 == letters_copy3 == letters_copy4)  # True, contents are the same\n",
    "print(\"letters_copy1 is letters?\", letters_copy1 is letters)  # False, different objects in memory\n",
    "print(\"letters_copy2 is letters?\", letters_copy2 is letters)  # False, different objects in memory\n",
    "print(\"letters_copy3 is letters?\", letters_copy3 is letters)  # False, different objects in memory\n",
    "print(\"letters_copy4 is letters?\", letters_copy4 is letters)  # False, different objects in memory"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "a95c3cea",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Same value?\t True\n",
      "Same object?\t False\n",
      "update shallow:\t [1, 2, 3, 4, 999]\n",
      "Original:\t [1, 2, 3, 4, 5]\n",
      "Shallow:\t [1, 2, 3, 4, 999]\n"
     ]
    }
   ],
   "source": [
    "### simpler example: shallow copy works fine for 1-D lists: Update\n",
    "original = [1, 2, 3, 4, 5]\n",
    "shallow = original[:]  # Creates a new list\n",
    "\n",
    "print(\"Same value?\\t\", original == shallow)   # True\n",
    "print(\"Same object?\\t\", original is shallow)  # False\n",
    "\n",
    "# Modify the shallow copy\n",
    "shallow[4] = 999\n",
    "print(\"update shallow:\\t\", shallow)\n",
    "\n",
    "print(\"Original:\\t\", original)  # Original is unchanged\n",
    "print(\"Shallow:\\t\", shallow)    # Only the copy is modified\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fb75f91",
   "metadata": {},
   "source": [
    "However, for nested lists (lists containing other lists), shallow copy shares references to the nested objects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "8a1cdeaa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "original-shallow same value?\t True\n",
      "original-shallow same object?\t False\n",
      "update shallow (99):\t\t [[1, 2, 99], [3, 4]]\n",
      "is original updated?\t\t [[1, 2, 99], [3, 4]]\n",
      "same nested object?\t\t True\n"
     ]
    }
   ],
   "source": [
    "# Using copy.copy() with nested lists\n",
    "import copy\n",
    "original = [[1, 2], [3, 4]]\n",
    "shallow = copy.copy(original)\n",
    "\n",
    "print(\"original-shallow same value?\\t\", original == shallow)   # True - contents are the same\n",
    "print(\"original-shallow same object?\\t\", id(original) == id(shallow))  # False - different list objects\n",
    "\n",
    "shallow[0].append(99)\n",
    "print(\"update shallow (99):\\t\\t\", shallow)\n",
    "print(\"is original updated?\\t\\t\", original)  # [[1, 2, 99], [3, 4]] - nested list affected!\n",
    "\n",
    "print(\"same nested object?\\t\\t\", shallow[0] is original[0])  # True - same nested list object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd8342d4",
   "metadata": {},
   "source": [
    "Now that shallow copy has given us two variable names referencing the same object, which we prefer not to happen in most cases. Same thing happens with using slicing for shallow copy with nested lists."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "f4a9d602",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original: [[999, 2, 3], [4, 5, 6]]\n",
      "Shallow: [[999, 2, 3], [4, 5, 6]]\n",
      "Same nested object? True\n"
     ]
    }
   ],
   "source": [
    "# Using slicing with nested lists\n",
    "original = [[1, 2, 3], [4, 5, 6]]\n",
    "shallow = original[:]  # or original.copy()\n",
    "\n",
    "# Modify the nested list\n",
    "shallow[0][0] = 999\n",
    "\n",
    "print(\"Original:\", original)  # Original is also modified!\n",
    "print(\"Shallow:\", shallow)\n",
    "print(\"Same nested object?\", shallow[0] is original[0])  # True - same nested list object"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d09c783a",
   "metadata": {},
   "source": [
    "As you can see, modifying the nested list in `shallow` also affects `original` because they share references to the same inner lists."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5db2e698",
   "metadata": {},
   "source": [
    "## Deep Copy\n",
    "\n",
    "To create a **deep copy** that copies all nested objects recursively, use the `copy` module's `deepcopy()` function. This creates completely independent copies of all nested structures. Deep copy creates a new object and recursively copies all nested objects—everything is independent."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "6167935c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original: [[1, 2, 3], [4, 5, 6]]\n",
      "Deep copy: [[1, 2, 3], [4, 5, 6]]\n",
      "original-deep same value? True\n",
      "original-deep same object? False\n",
      "same nested object? False\n",
      "Original: [[1, 2, 3], [4, 5, 6]]\n",
      "Deep copy: [[999, 2, 3], [4, 5, 6]]\n"
     ]
    }
   ],
   "source": [
    "import copy\n",
    "\n",
    "original = [[1, 2, 3], [4, 5, 6]]\n",
    "deep = copy.deepcopy(original)\n",
    "\n",
    "print(\"Original:\", original)  # Original is unchanged\n",
    "print(\"Deep copy:\", deep)    # Only the deep copy is modified\n",
    "\n",
    "print(\"original-deep same value?\", original == deep)   # True - contents are the same\n",
    "print(\"original-deep same object?\", id(original) == id(deep))  # False - different list objects\n",
    "print(\"same nested object?\", deep[0] is original[0])  # False - different nested list objects\n",
    "\n",
    "# Modify the nested list\n",
    "print\n",
    "deep[0][0] = 999\n",
    "\n",
    "print(\"Original:\", original)  # Original is unchanged\n",
    "print(\"Deep copy:\", deep)    # Only the deep copy is modified"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "e182a1b0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4570904832\n",
      "4570943680\n",
      "[[1, 2], [3, 4]]\n",
      "[[1, 2, 99], [3, 4]]\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "### simpler example with deep copy\n",
    "import copy\n",
    "original = [[1, 2], [3, 4]]\n",
    "deep = copy.deepcopy(original)\n",
    "\n",
    "print(id(original))\n",
    "print(id(deep))\n",
    "\n",
    "deep[0].append(99)\n",
    "print(original)  # [[1, 2], [3, 4]] - original unchanged!\n",
    "print(deep)      # [[1, 2, 99], [3, 4]] - only deep copy changed\n",
    "print(deep[0] is original[0])    # False - different nested list objects"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
