{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1b32846a-7aea-4625-95a3-a34680ab6867",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Python Data Structures Summary\n",
    "\n",
    "This article covers Python's built-in data structures: lists (ordered, mutable collections), tuples (ordered, immutable collections), dictionaries (unordered key-value pairs), sets (unordered, unique items), and strings (immutable character sequences).  It also explores the collections module, detailing Counters, OrderedDicts, DefaultDicts, ChainMaps, NamedTuples, and Deques.  Advanced data structures such as linked lists, stacks, queues, heaps, binary trees, and graphs (with adjacency matrix and list representations) are explained with example code.  Finally, the article addresses graph traversal (BFS and DFS)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "935391fa-f54e-4377-ae86-4214a5ac83cf",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into graph traversal algorithms, let's solidify our understanding of fundamental data structures.  Implement a stack data structure using only lists, without using any external libraries. Your stack should have methods for `push` (adding an element to the top), `pop` (removing and returning the top element), `peek` (viewing the top element without removing it), and `is_empty` (checking if the stack is empty). Demonstrate its usage by pushing the numbers 1 through 5, then popping and printing the elements until the stack is empty.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def peek(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items[-1]\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "stack = Stack()\n",
    "for i in range(1, 6):\n",
    "    stack.push(i)\n",
    "\n",
    "while not stack.is_empty():\n",
    "    print(stack.pop())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3a2a488a-de7b-46e4-8bb5-3c886841f8f4",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack using a List\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into advanced data structures like graphs and trees, let's solidify our understanding of fundamental data structures.  Implement a stack data structure using only a Python list.  Your implementation should include methods for `push` (adding an element to the top), `pop` (removing and returning the top element), `peek` (viewing the top element without removing it), and `is_empty` (checking if the stack is empty).  Demonstrate the usage of your stack by pushing three elements, peeking at the top, popping an element, and then checking if the stack is empty.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def peek(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items[-1]\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "# Example usage\n",
    "stack = Stack()\n",
    "stack.push(10)\n",
    "stack.push(20)\n",
    "stack.push(30)\n",
    "print(f\"Top element: {stack.peek()}\")  # Output: Top element: 30\n",
    "stack.pop()\n",
    "print(f\"Stack is empty: {stack.is_empty()}\")  # Output: Stack is empty: False\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a70bdd09-b5d7-4251-b4f4-fb64499aad47",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into graph traversal algorithms, let's solidify our understanding of fundamental data structures.  Implement a stack data structure using only lists.  The stack should support the `push` (add an item to the top), `pop` (remove and return the top item), `peek` (return the top item without removing it), and `is_empty` (check if the stack is empty) operations.  Demonstrate its usage by pushing the numbers 1 through 5, then popping and printing the top three elements.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def peek(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items[-1]\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "stack = Stack()\n",
    "for i in range(1, 6):\n",
    "    stack.push(i)\n",
    "\n",
    "for _ in range(3):\n",
    "    print(stack.pop())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0226973-79f2-4fb2-a0e4-377a9c78ba0a",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into graph traversal algorithms, let's solidify our understanding of fundamental data structures.  Implement a stack data structure using only lists.  Your implementation should include methods for pushing an item onto the stack, popping an item from the stack, and checking if the stack is empty.  Demonstrate its usage by pushing the numbers 1 through 5 onto the stack, then popping and printing each element until the stack is empty.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "stack = Stack()\n",
    "for i in range(1, 6):\n",
    "    stack.push(i)\n",
    "\n",
    "while not stack.is_empty():\n",
    "    print(stack.pop())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94918de1-1d9d-43ab-8747-8f2183f0e13c",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Create a Python function that simulates a stack data structure using a list. The function should include methods to push (add) an element onto the stack, pop (remove) an element from the stack, and check if the stack is empty.  Demonstrate its usage by pushing three numbers onto the stack, popping one, and then printing the remaining elements.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "my_stack = Stack()\n",
    "my_stack.push(10)\n",
    "my_stack.push(20)\n",
    "my_stack.push(30)\n",
    "\n",
    "my_stack.pop()\n",
    "\n",
    "print(my_stack.items)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ecf0a326-e60d-474d-aca2-7c2d21214f55",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack using a List\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into more complex data structures like graphs, let's revisit the fundamental concept of stacks.  Implement a stack data structure using only Python lists, including methods for pushing (adding) elements onto the stack, popping (removing) elements from the stack, and checking if the stack is empty.  Demonstrate its usage by pushing three numbers onto the stack, then popping and printing the elements.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None  # or raise an exception\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "my_stack = Stack()\n",
    "my_stack.push(10)\n",
    "my_stack.push(20)\n",
    "my_stack.push(30)\n",
    "\n",
    "print(my_stack.pop())  # Output: 30\n",
    "print(my_stack.pop())  # Output: 20\n",
    "print(my_stack.pop())  # Output: 10\n",
    "print(my_stack.pop())  # Output: None\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22fe0e02-0afc-4a77-b633-c6589131669a",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Queue using a List\n",
    "\n",
    "Question:\n",
    "\n",
    "Building on our understanding of stacks, let's explore another fundamental data structure: queues.  Implement a queue data structure using only Python lists, ensuring it includes methods for enqueueing (adding) elements to the rear, dequeuing (removing) elements from the front, and checking if the queue is empty. Demonstrate its functionality by enqueuing the numbers 1 through 5, then dequeuing and printing each element until the queue is empty.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Queue:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def enqueue(self, item):\n",
    "        self.items.insert(0, item)\n",
    "\n",
    "    def dequeue(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "my_queue = Queue()\n",
    "for i in range(1, 6):\n",
    "    my_queue.enqueue(i)\n",
    "\n",
    "while not my_queue.is_empty():\n",
    "    print(my_queue.dequeue())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "158b9c21-526e-40ab-b178-501276dd8bc5",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Queue using a List\n",
    "\n",
    "Question:\n",
    "\n",
    "Building on our understanding of stacks, let's explore queues.  Implement a queue data structure using only Python lists, ensuring it supports enqueue (adding to the rear), dequeue (removing from the front), and checking for emptiness.  Push the numbers 1 through 5, then dequeue and print them, showing the FIFO behavior.  What is the primary difference between how a stack and a queue handles the order of elements?\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Queue:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def enqueue(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def dequeue(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop(0)\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "my_queue = Queue()\n",
    "for i in range(1, 6):\n",
    "    my_queue.enqueue(i)\n",
    "\n",
    "while not my_queue.is_empty():\n",
    "    print(my_queue.dequeue())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a2ac633-ef00-4616-959e-b09b3fa718f0",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Implement a stack data structure using only lists.  The stack should have methods for `push` (adding an element to the top), `pop` (removing and returning the top element), `peek` (returning the top element without removing it), and `is_empty` (checking if the stack is empty).  Demonstrate its use by pushing the numbers 1 through 5, then popping and printing the elements until the stack is empty.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def peek(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items[-1]\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "stack = Stack()\n",
    "for i in range(1, 6):\n",
    "    stack.push(i)\n",
    "\n",
    "while not stack.is_empty():\n",
    "    print(stack.pop())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc2c00b6-b997-4e07-af1a-5c97a49251fc",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack using a List\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into advanced data structures like graphs and trees, let's solidify our understanding of fundamental data structures.  Create a Python class representing a stack data structure using only a list.  The class should have methods for pushing elements onto the stack, popping elements from the stack, and checking if the stack is empty.  Demonstrate its use by pushing three integers onto the stack, popping one element, and then printing the remaining elements.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "    def peek(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items[-1]\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "stack = Stack()\n",
    "stack.push(10)\n",
    "stack.push(20)\n",
    "stack.push(30)\n",
    "\n",
    "stack.pop()\n",
    "\n",
    "print(stack.items)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b059de93-9152-4736-adef-5696adad8e24",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Consider the scenario where you need to manage a stack of tasks.  Design a Python class representing a stack using only lists, without using any external libraries.  The class should include methods for pushing (adding) elements onto the stack, popping (removing) elements from the stack, and checking if the stack is empty.  Demonstrate its usage by pushing three tasks ('Task A', 'Task B', 'Task C'), popping one, then printing the remaining stack.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "    def __str__(self):\n",
    "        return str(self.items)\n",
    "\n",
    "# Example usage\n",
    "task_stack = Stack()\n",
    "task_stack.push('Task A')\n",
    "task_stack.push('Task B')\n",
    "task_stack.push('Task C')\n",
    "print(f\"Stack before pop: {task_stack}\")\n",
    "task_stack.pop()\n",
    "print(f\"Stack after pop: {task_stack}\")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d53cc415-aea9-41c4-b62d-afcb666f6f91",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into graph traversal algorithms, let's solidify our understanding of fundamental data structures.  Create a Python function `create_stack` that simulates a stack using a list. The function should take an initial list as input and return a dictionary containing the stack's methods: `push` (adds an element), `pop` (removes and returns the top element), `peek` (returns the top element without removing it), and `is_empty` (checks if the stack is empty).  Demonstrate its usage by pushing three items, peeking at the top, and then popping them all.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "def create_stack(initial_list=[]):\n",
    "    stack = initial_list\n",
    "    return {\n",
    "        \"push\": lambda item: stack.append(item),\n",
    "        \"pop\": lambda: stack.pop() if not is_empty() else None,\n",
    "        \"peek\": lambda: stack[-1] if not is_empty() else None,\n",
    "        \"is_empty\": lambda: len(stack) == 0\n",
    "    }\n",
    "\n",
    "my_stack = create_stack([1,2])\n",
    "my_stack[\"push\"](3)\n",
    "my_stack[\"push\"](4)\n",
    "print(my_stack[\"peek\"()])\n",
    "print(my_stack[\"pop\"()])\n",
    "print(my_stack[\"pop\"()])\n",
    "print(my_stack[\"pop\"()])\n",
    "print(my_stack[\"is_empty\"()])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db2fb764-25a5-4f3c-a338-d9f5a269c8f9",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Create a Python function that implements a stack data structure using only lists.  The function should have methods to push an item onto the stack, pop an item from the stack, and check if the stack is empty.  Demonstrate its use by pushing three integers onto the stack, popping one, and then printing the remaining stack.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "# Example usage\n",
    "stack = Stack()\n",
    "stack.push(10)\n",
    "stack.push(20)\n",
    "stack.push(30)\n",
    "stack.pop()\n",
    "print(stack.items) # Output: [10, 20]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ce39e81-b560-4d28-ac53-9eb8f09fc202",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack using a List\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into more complex data structures like graphs, let's revisit the fundamental concept of a stack.  Can you implement a stack data structure using only a Python list, ensuring it supports the `push` (add element to top) and `pop` (remove element from top) operations?  Your implementation should handle potential errors gracefully (e.g., popping from an empty stack).\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None  # Or raise an exception\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "    def peek(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items[-1]\n",
    "        else:\n",
    "            return None #Or raise an exception\n",
    "\n",
    "# Example usage\n",
    "stack = Stack()\n",
    "stack.push(10)\n",
    "stack.push(20)\n",
    "stack.push(30)\n",
    "print(stack.pop())  # Output: 30\n",
    "print(stack.peek()) # Output: 20\n",
    "print(stack.is_empty()) # Output: False\n",
    "print(stack.pop())  # Output: 20\n",
    "print(stack.pop())  # Output: 10\n",
    "print(stack.pop())  # Output: None\n",
    "print(stack.is_empty()) # Output: True\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ce23c1c-8db9-4a3d-aeda-dc0c6563bf8c",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack using a List\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into more complex data structures, let's solidify our understanding of basic ones.  Create a Python class called `Stack` that mimics the behavior of a stack data structure using only a list.  This class should have methods `push(item)` to add an item to the top of the stack, `pop()` to remove and return the top item (raise an exception if the stack is empty), `peek()` to return the top item without removing it (raise an exception if the stack is empty), and `is_empty()` to check if the stack is empty.  Demonstrate its usage with a few push, pop, and peek operations.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if self.is_empty():\n",
    "            raise Exception(\"Stack is empty\")\n",
    "        return self.items.pop()\n",
    "\n",
    "    def peek(self):\n",
    "        if self.is_empty():\n",
    "            raise Exception(\"Stack is empty\")\n",
    "        return self.items[-1]\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "# Example usage\n",
    "stack = Stack()\n",
    "stack.push(10)\n",
    "stack.push(20)\n",
    "stack.push(30)\n",
    "print(stack.pop())  # Output: 30\n",
    "print(stack.peek()) # Output: 20\n",
    "print(stack.is_empty()) # Output: False\n",
    "stack.pop()\n",
    "stack.pop()\n",
    "print(stack.is_empty()) # Output: True\n",
    "#print(stack.pop()) # Raises Exception\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8ab95aa-8337-4a3d-8b7e-8168587cf3cd",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Create a Python function called `create_stack` that simulates a stack data structure using a list. The function should take an initial list as input and return a dictionary containing the stack's methods: `push` (adds an element), `pop` (removes and returns the top element), `peek` (returns the top element without removing), `is_empty` (checks if the stack is empty), and `size` (returns the number of elements).  Demonstrate its usage by adding elements 1, 2, and 3 and then popping one element, and printing the stack's size.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "def create_stack(initial_list=[]):\n",
    "    stack = initial_list\n",
    "    return {\n",
    "        'push': lambda item: stack.append(item),\n",
    "        'pop': lambda: stack.pop() if not is_empty() else None,\n",
    "        'peek': lambda: stack[-1] if not is_empty() else None,\n",
    "        'is_empty': lambda: len(stack) == 0,\n",
    "        'size': lambda: len(stack)\n",
    "    }\n",
    "\n",
    "my_stack = create_stack()\n",
    "my_stack['push'](1)\n",
    "my_stack['push'](2)\n",
    "my_stack['push'](3)\n",
    "my_stack['pop']()\n",
    "print(my_stack['size']())\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a4921a84-7fb3-4566-8a58-50f364b90fa8",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into graph traversal algorithms, let's solidify our understanding of fundamental data structures.  Create a Python function, `create_stack`, that simulates a stack using a list. The function should take no arguments and return an empty list representing the stack.  Then, write another function, `push_item`, that takes the stack (list) and an item as arguments and adds the item to the top of the stack.  Finally, write a third function `pop_item` that takes the stack as an argument and removes and returns the top item from the stack. If the stack is empty, it should return None.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "def create_stack():\n",
    "    return []\n",
    "\n",
    "def push_item(stack, item):\n",
    "    stack.append(item)\n",
    "\n",
    "def pop_item(stack):\n",
    "    if not stack:\n",
    "        return None\n",
    "    return stack.pop()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d5cf0ff-c572-464d-8c2d-4fb5631372ff",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Implementing a Stack Data Structure\n",
    "\n",
    "Question:\n",
    "\n",
    "Before we delve into graph traversal algorithms, let's solidify our understanding of fundamental data structures.  Create a Python function that simulates a stack using a list.  The function should have methods to push (add) an element onto the stack, pop (remove) an element from the stack, and check if the stack is empty.  Demonstrate its use by pushing three integers, popping one, and then checking if the stack is empty.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "class Stack:\n",
    "    def __init__(self):\n",
    "        self.items = []\n",
    "\n",
    "    def push(self, item):\n",
    "        self.items.append(item)\n",
    "\n",
    "    def pop(self):\n",
    "        if not self.is_empty():\n",
    "            return self.items.pop()\n",
    "        else:\n",
    "            return None\n",
    "\n",
    "    def is_empty(self):\n",
    "        return len(self.items) == 0\n",
    "\n",
    "# Example usage\n",
    "stack = Stack()\n",
    "stack.push(10)\n",
    "stack.push(20)\n",
    "stack.push(30)\n",
    "print(stack.pop())  # Output: 30\n",
    "print(stack.is_empty())  # Output: False\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "427fab88-0573-4227-b4e1-81a0b107ae25",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Strings in Computational and Inferential Thinking\n",
    "\n",
    "A string in a computer represents text (words, sentences, etc.).  String addition combines strings literally; adding spaces requires explicit inclusion.  Single or double quotes can create strings, with double quotes preferred for including apostrophes. The `str` function converts any value to its string representation, allowing embedding values within strings."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c37d319d-dcc3-4385-b184-b4bba00f972b",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### String Manipulation and Formatting\n",
    "\n",
    "Question:\n",
    "\n",
    "Extend the concept of string manipulation. Create a function `format_address` that takes two strings, `street` and `city`, and returns a formatted address string in the format \"Street: {street}, City: {city}\".  Demonstrate its usage with sample street and city values.  Handle potential errors gracefully, such as empty input strings.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "def format_address(street, city):\n",
    "    if not street or not city:\n",
    "        return \"Invalid address: Street and city cannot be empty.\"\n",
    "    return f\"Street: {street}, City: {city}\"\n",
    "\n",
    "street_address = \"123 Main Street\"\n",
    "city_name = \"Anytown\"\n",
    "formatted_address = format_address(street_address, city_name)\n",
    "print(formatted_address)\n",
    "\n",
    "#Example of error handling\n",
    "print(format_address(\"\", city_name))\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c58ab24f-2a5b-41b7-8e93-bc167e4f82e3",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### String Manipulation and Immutability\n",
    "\n",
    "Question: \n",
    "Given the string variable `text` used in the summarization pipeline (cell 6), demonstrate how to modify the string to replace the abbreviation 'OPHD' with its full name, 'Office for the Prevention of Harassment & Discrimination'. Explain why this modification requires creating a new string instead of modifying the original string in place.\n",
    "Answer: \n",
    "```python\n",
    "text = \"Office for the Prevention of Harassment & Discrimination (OPHD)\\nThe Office for the Prevention of Harassment & Discrimination (OPHD) provides assistance to \\nstudents, faculty, and staff regarding reports of bias, harassment, and discrimination. \\nOPHD's mission is to educate the UC San Diego community about these issues and to assist \\nwith the prevention and resolution of these issues in a fair and responsible manner.\"\n",
    "\n",
    "new_text = text.replace('OPHD', 'Office for the Prevention of Harassment & Discrimination')\n",
    "print(new_text)\n",
    "```\n",
    "This creates a new string `new_text` because strings in Python are immutable.  We cannot directly modify characters within an existing string.  The `replace()` method returns a *new* string with the replacements made. The original `text` variable remains unchanged."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "afa66ad2-498c-4e93-af13-cb7edb84fc64",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### String Manipulation and Immutability: Advanced Example\n",
    "\n",
    "Question:\n",
    "\n",
    "Given the same `text` variable from the summarization pipeline (cell 6), demonstrate how to replace all occurrences of 'OPHD' with its full name, but only if 'OPHD' is followed by a parenthesis.  Explain how this differs from a simple `replace()` and why this approach might be more robust.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "text = \"Office for the Prevention of Harassment & Discrimination (OPHD)\\nThe Office for the Prevention of Harassment & Discrimination (OPHD) provides assistance to \\nstudents, faculty, and staff regarding reports of bias, harassment, and discrimination. \\nOPHD's mission is to educate the UC San Diego community about these issues and to assist \\nwith the prevention and resolution of these issues in a fair and responsible manner.\"\n",
    "\n",
    "import re\n",
    "new_text = re.sub(r'OPHD\\)', 'Office for the Prevention of Harassment & Discrimination)', text)\n",
    "print(new_text)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7adf548d-07fd-4d53-9c68-09b71183af42",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "temporary": true
   },
   "source": [
    "### Python Data Structures: Lists, Dictionaries, Sets, Tuples (2023)\n",
    "\n",
    "This tutorial covers Python's built-in data structures: lists, dictionaries, sets, and tuples.  It explains mutable vs. immutable data structures, detailing the pros and cons of each. Lists are described as dynamic, mutable arrays holding ordered collections of items (potentially heterogeneous data types). Dictionaries are key-value pairs, useful for quick data access. Sets are mutable collections of unique, immutable elements, enabling efficient element checks and set operations (union, intersection, difference). Tuples, similar to lists, are immutable and can serve as dictionary keys if elements are immutable. The tutorial includes numerous examples demonstrating creation, access, modification (where applicable), and operations for each data structure, and compares the performance of sets and lists for element existence checks."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43a14002-3fff-44f1-9b35-14d9355709b4",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Mutability and Immutability in Python Data Structures\n",
    "\n",
    "Question: Explain the difference between mutable and immutable data structures in Python, providing examples of each from the data structures discussed in the notebook.\n",
    "Answer: In Python, mutable data structures can be modified after creation, while immutable data structures cannot.  Lists are mutable; you can add, remove, or change elements.  For example, `my_list = [1, 2, 3]; my_list.append(4)` modifies `my_list`.  Tuples, on the other hand, are immutable.  Once created (`my_tuple = (1, 2, 3)`), you cannot change their contents. Dictionaries are mutable (you can add, remove, or change key-value pairs), and sets are also mutable (you can add or remove elements). Strings are also immutable; you cannot change individual characters within a string.  The immutability of certain data structures has implications for their use as dictionary keys (only immutable types can be keys) and also affects performance in certain operations."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9254bc9-cdb6-4c50-9a3d-02e89285310c",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Mutability and Immutability:  Practical Example\n",
    "\n",
    "Question: \n",
    "\n",
    "Create a Python dictionary where keys are tuples representing coordinates (x, y) and values are lists of corresponding data points.  Demonstrate adding a new coordinate-data pair while respecting the immutability of tuple keys and mutability of list values. Explain why this approach is appropriate and how it differs from using only mutable data structures like lists for both keys and values.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "coordinate_data = {(1, 2): [10, 20, 30], (3, 4): [40, 50]}\n",
    "\n",
    "# Add a new coordinate-data pair. Tuples (keys) are immutable, but lists (values) are mutable.\n",
    "new_coordinate = (5,6)\n",
    "coordinate_data[new_coordinate] = [60, 70, 80]\n",
    "\n",
    "print(coordinate_data)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "203c9fba-71e7-45e8-b07d-552fc75ed8ae",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Mutability and Immutability:  Exercise\n",
    "\n",
    "Question:\n",
    "\n",
    "Create a Python function that takes a list of tuples as input. Each tuple contains a student's name and their grades as a list. The function should return a new list of tuples where each tuple contains the student's name and their average grade. Explain why creating a new list is necessary and relate your answer to the concepts of mutability and immutability.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "def calculate_average_grades(student_grades):\n",
    "    new_list = []\n",
    "    for name, grades in student_grades:\n",
    "        average = sum(grades) / len(grades) if grades else 0  #handle empty grade list\n",
    "        new_list.append((name, average))\n",
    "    return new_list\n",
    "\n",
    "student_data = [(\"Alice\", [85, 90, 78]), (\"Bob\", [92, 88, 95]), (\"Charlie\", [])]\n",
    "average_grades = calculate_average_grades(student_data)\n",
    "print(average_grades)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d399b24-6474-4f89-adde-178db7fa72b3",
   "metadata": {
    "newly_added": true,
    "temporary": true
   },
   "source": [
    "### Mutability and Immutability:  Practical Example\n",
    "\n",
    "Question:\n",
    "\n",
    "Create a Python dictionary where keys are immutable data types and values are mutable data types. Demonstrate the mutability of the values by modifying them after dictionary creation. Explain why using mutable data types as keys would be problematic.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "my_dict = {\n",
    "    (1, 2): [3, 4, 5],\n",
    "    'string': {'a':1, 'b':2},\n",
    "    123: {1: 1, 2:2}\n",
    "}\n",
    "\n",
    "#Modify the values\n",
    "my_dict[(1,2)].append(6)\n",
    "my_dict['string']['c'] = 3\n",
    "my_dict[123][3] = 3\n",
    "\n",
    "print(my_dict)\n",
    "\n",
    "# Trying to use a list as a key will result in an error because lists are mutable.\n",
    "#my_dict_error = {[1, 2]: 3}  # This will cause a TypeError\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6badeb0e-cc71-4240-9c5f-73e00acf115e",
   "metadata": {
    "newly_added": true,
    "temporary": true
   },
   "source": [
    "### Mutability and Immutability Exercise: Sets\n",
    "\n",
    "Question: \n",
    "\n",
    "Create a Python set containing the unique words from the text string below (case-insensitive).  Then, demonstrate how to add and remove elements from this set. Explain why using a set is efficient for this task compared to a list.  Also, try to add a list as an element to the set; what happens and why?\n",
    "\n",
    "```python\n",
    "text = \"The quick brown fox jumps over the lazy dog. The dog barks.\"\n",
    "\n",
    "word_set = set()\n",
    "\n",
    "# Convert text to lowercase and split into words\n",
    "words = text.lower().split()\n",
    "\n",
    "# Add words to the set\n",
    "for word in words:\n",
    "    word_set.add(word)\n",
    "\n",
    "print(\"Original set:\", word_set)\n",
    "\n",
    "# Add a new word\n",
    "word_set.add('cat')\n",
    "print(\"Set after adding 'cat':\", word_set)\n",
    "\n",
    "# Remove a word\n",
    "word_set.remove('dog')\n",
    "print(\"Set after removing 'dog':\", word_set)\n",
    "\n",
    "# Attempt to add a list (observe the outcome)\n",
    "my_list = ['this', 'is', 'a', 'list']\n",
    "word_set.add(my_list)\n",
    "print(\"Set after attempting to add a list:\", word_set)\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d19834e6-8a7d-4108-81fa-6cef031c877d",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Mutability and Immutability Exercise\n",
    "\n",
    "Question: \n",
    "\n",
    "Create a Python dictionary where keys are immutable data types (e.g., strings or tuples) and values are mutable lists.  Then, demonstrate adding an element to one of the lists.  Explain why this is allowed even though you are operating on values within the dictionary.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "my_dict = {\n",
    "    \"a\": [1, 2, 3],\n",
    "    (1, 2): [4, 5, 6],\n",
    "    \"b\": [7, 8, 9]\n",
    "}\n",
    "\n",
    "my_dict[\"a\"].append(4)\n",
    "\n",
    "print(my_dict)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f90908d1-5031-448f-9a29-5fb8d1b34b75",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Mutability and Immutability: Nested Structures\n",
    "\n",
    "Question:\n",
    "\n",
    "Create a Python list where each element is a dictionary.  Each dictionary should have an immutable key (e.g. a string) and a mutable value (a list). Demonstrate adding an element to one of the inner lists, and then removing one of the dictionaries from the outer list. Explain why these operations are allowed, given the nested structure and the mutability of involved data types.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "my_list = [\n",
    "    {\"a\": [1, 2, 3]},\n",
    "    {\"b\": [4, 5, 6]},\n",
    "    {\"c\": [7, 8, 9]}\n",
    "]\n",
    "\n",
    "my_list[0][\"a\"].append(4)\n",
    "del my_list[1]\n",
    "\n",
    "print(my_list)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cddc3974-ddb5-4100-aaa4-1865bdf46dca",
   "metadata": {
    "temporary": true
   },
   "source": [
    "### Mutability and Immutability: Nested Lists of Tuples\n",
    "\n",
    "Question:\n",
    "\n",
    "Create a Python list where each element is a tuple. Each tuple should contain immutable values (e.g., integers or strings) and represent a record in a dataset, let's say, student ID and scores in different subjects. Demonstrate adding a new record to this list (a new tuple), and then accessing a specific score of a specific student. Explain why you can add elements to the outer list, but not modify the tuples within the list.\n",
    "\n",
    "Answer:\n",
    "```python\n",
    "student_data = [\n",
    "    (123, 85, 92, 78),\n",
    "    (456, 90, 88, 95),\n",
    "    (789, 76, 80, 85)\n",
    "]\n",
    "\n",
    "new_student = (101, 90, 85, 92)\n",
    "student_data.append(new_student)\n",
    "\n",
    "math_score_student_123 = student_data[0][1]\n",
    "\n",
    "print(student_data)\n",
    "print(f\"Math score for student 123: {math_score_student_123}\")\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "21072e1a-00dc-43ab-b786-12064d8e8727",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/gabrielcha/miniconda3/envs/server-extension/lib/python3.13/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n",
      "Device set to use mps:0\n"
     ]
    }
   ],
   "source": [
    "from transformers import pipeline\n",
    "\n",
    "pipe = pipeline(\"summarization\", model=\"facebook/bart-large-cnn\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "a60acb9e-a8a0-4531-bf6f-43c121d5b9f2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The Office for the Prevention of Harassment & Discrimination\n"
     ]
    }
   ],
   "source": [
    "text = \"\"\"Office for the Prevention of Harassment & Discrimination (OPHD)\n",
    "The Office for the Prevention of Harassment & Discrimination (OPHD) provides assistance to \n",
    "students, faculty, and staff regarding reports of bias, harassment, and discrimination. \n",
    "OPHD's mission is to educate the UC San Diego community about these issues and to assist \n",
    "with the prevention and resolution of these issues in a fair and responsible manner.\"\"\"\n",
    "\n",
    "summary = pipe(text, max_length=13, min_length=4, do_sample=False)\n",
    "\n",
    "print(summary[0]['summary_text'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a4e8d991-ba29-4897-a797-9607196eadfd",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1b3d7e29-439b-4ff6-ae40-b429136f018e",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e16ddcb-4df1-49d5-b38d-05b5289213c8",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "5bb4fc50-fd12-4653-b5d9-dcbade1c58f8",
   "metadata": {
    "temporary": true
   },
   "source": []
  }
 ],
 "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.10.16"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
