🎯 Learning Objectives: Master object-oriented programming principles, advanced SQL operations, and fundamental data structures through interactive visualizations and practical examples.
🎯 1. Object-Oriented Programming (OOP)
💡 Core Concept: OOP is a programming paradigm based on “objects” - entities that encapsulate data (attributes) and behavior (methods) together.
💻 1.1. Classes and Objects Fundamentals
Concept | Definition | Key Characteristics |
---|---|---|
Class | Blueprint for creating objects | Defines attributes and methods |
Object | Instance of a class | Contains actual data and can execute methods |
Attributes | Data stored in objects | Variables that hold object state |
Methods | Functions defined in classes | Operations that objects can perform |
🎨 Interactive Class-Object Relationship Visualization
💻 Practical Python Implementation
# Define a class named 'Dog'class Dog: # Class attribute (shared by all instances) species = "Canis familiaris"
# Initializer / Instance attributes def __init__(self, name, age): self.name = name # Public attribute self.age = age # Public attribute self._breed = None # Protected attribute self.__id = id(self) # Private attribute
# Instance method def describe(self): return f"{self.name} is {self.age} years old."
# Another instance method def speak(self, sound): return f"{self.name} says {sound}."
# Getter method def get_breed(self): return self._breed
# Setter method def set_breed(self, breed): self._breed = breed
# Create instances (objects) of the Dog classdog1 = Dog("Buddy", 5)dog2 = Dog("Lucy", 3)
# Accessing attributes and calling methodsprint(f"Name: {dog1.name}") # Output: Name: Buddyprint(f"Description: {dog1.describe()}") # Output: Description: Buddy is 5 years old.print(f"Sound: {dog2.speak('Woof!')}") # Output: Sound: Lucy says Woof!
# Working with breeddog1.set_breed("Golden Retriever")print(f"Breed: {dog1.get_breed()}") # Output: Breed: Golden Retriever
🔍 Key Insights: Notice how the class serves as a template, while each object (dog1, dog2) has its own unique data but shares the same structure and behavior.
🏗️ 1.2. Four Core Principles of OOP
🔒 1.2.1. Encapsulation
Definition: Encapsulation bundles data (attributes) and methods into a single unit while restricting direct access to internal components.
Access Level | Python Convention | Visibility | Usage |
---|---|---|---|
Public | attribute | Accessible everywhere | General use attributes/methods |
Protected | _attribute | Internal use (convention) | Subclass access intended |
Private | __attribute | Name mangled | Strictly internal use |
🎨 Interactive Encapsulation Visualization
💻 Practical Encapsulation Example
class BankAccount: def __init__(self, account_holder, initial_balance=0): # Public attributes self.account_holder = account_holder self.account_type = "Savings"
# Protected attributes (internal use) self._account_number = self._generate_account_number() self._creation_date = "2024-12-16"
# Private attributes (strictly internal) self.__balance = initial_balance self.__security_key = "SECRET_KEY_12345" self.__transaction_history = []
# Public methods (safe interface) def get_balance(self): """Provides controlled read-only access to balance""" return self.__balance
def deposit(self, amount): """Safe deposit method with validation""" if self._validate_transaction(amount): self.__balance += amount self.__log_transaction("DEPOSIT", amount) print(f"✅ Deposited: ${amount:.2f}") return True return False
def withdraw(self, amount): """Safe withdrawal with balance checking""" if self._validate_transaction(amount) and amount <= self.__balance: self.__balance -= amount self.__log_transaction("WITHDRAW", amount) print(f"✅ Withdrawn: ${amount:.2f}") return True print("❌ Insufficient funds or invalid amount") return False
def get_account_info(self): """Public method returning safe information""" return { "holder": self.account_holder, "type": self.account_type, "balance": self.__balance, "account_number": self._account_number[-4:] # Only last 4 digits }
# Protected methods (intended for subclasses) def _validate_transaction(self, amount): """Internal validation logic""" return isinstance(amount, (int, float)) and amount > 0
def _generate_account_number(self): """Internal account number generation""" import random return f"ACC{random.randint(100000, 999999)}"
# Private methods (strictly internal) def __log_transaction(self, transaction_type, amount): """Private transaction logging""" self.__transaction_history.append({ "type": transaction_type, "amount": amount, "timestamp": "2024-12-16 10:30:00" })
# Usage demonstrationaccount = BankAccount("Alice Smith", 1000)
# ✅ Public interface - Safe and controlled accessprint(f"Account Holder: {account.account_holder}") # Direct access OKprint(f"Balance: ${account.get_balance():.2f}") # Controlled accessaccount.deposit(500) # Safe methodaccount.withdraw(200) # Safe methodprint(account.get_account_info()) # Safe info retrieval
# 🔒 Protected access - Not recommended but possibleprint(f"Account Number: {account._account_number}") # Discouraged but works
# ❌ Private access - Will failtry: print(account.__balance) # AttributeError!except AttributeError as e: print(f"Error: {e}")
# ⚠️ Name mangling - Python's way of hiding private attributesprint(f"Actual private attribute name: {account._BankAccount__balance}") # Still accessible but discouraged
🎯 Key Benefits of Encapsulation:
- Data Security: Prevents accidental modification of critical data
- Controlled Access: Validates inputs before changing state
- Code Maintainability: Changes to internal implementation don’t break external code
- Error Prevention: Reduces bugs by limiting direct data manipulation
🎭 1.2.2. Abstraction
Abstraction means hiding the complex implementation details and showing only the essential features of the object. It helps in reducing programming complexity and effort. An abstract class is a class that contains one or more abstract methods. An abstract method is a method that is declared, but contains no implementation.
🎨 Interactive Abstraction Visualization
Example using abc module:
from abc import ABC, abstractmethod
class Shape(ABC): # Abstract class @abstractmethod def area(self): pass
@abstractmethod def perimeter(self): pass
class Square(Shape): def __init__(self, side): self.__side = side
def area(self): return self.__side * self.__side
def perimeter(self): return 4 * self.__side
🧬 1.2.3. Inheritance
Inheritance is a mechanism in which one class (child/derived) acquires the properties (methods and fields) of another class (parent/base). This promotes code reusability.
Types of Inheritance: Single, Multiple, Multilevel, Hierarchical.
🎨 Interactive Inheritance Types Visualization
Example:
# Parent classclass Animal: def __init__(self, name): self.name = name
def eat(self): print(f"{self.name} is eating.")
# Child class inheriting from Animalclass Dog(Animal): def __init__(self, name, breed): super().__init__(name) # Call parent constructor self.breed = breed
def bark(self): print(f"{self.name} says woof!")
my_dog = Dog("Rex", "German Shepherd")my_dog.eat() # Inherited methodmy_dog.bark() # Child's own method
🎭 1.2.4. Polymorphism
Polymorphism, which means “many forms,” allows us to perform a single action in different ways. It allows methods to do different things based on the object it is acting upon. Method overriding is a key example.
🎨 Interactive Polymorphism Visualization
Example (Method Overriding):
class Bird: def intro(self): print("There are many types of birds.")
def flight(self): print("Most of the birds can fly but some cannot.")
class Sparrow(Bird): # Overriding the flight method def flight(self): print("Sparrows can fly.")
class Ostrich(Bird): # Overriding the flight method def flight(self): print("Ostriches cannot fly.")
obj_bird = Bird()obj_sparrow = Sparrow()obj_ostrich = Ostrich()
obj_bird.flight()obj_sparrow.flight()obj_ostrich.flight()
🧠 1.3. Custom Class in PyTorch (nn.Module)
In PyTorch, all neural network modules, including layers and activation functions, are subclasses of torch.nn.Module
. To create a custom layer or model, you must inherit from this class.
__init__(self)
: Define the layers (e.g.,nn.Linear
,nn.Conv2d
) you will use. You must callsuper().__init__()
.forward(self, x)
: Defines the forward pass of the module. This is where the computation happens, connecting the defined layers.
Example: Custom Sigmoid Function
The sigmoid function is given by: \( \text{sigmoid}(x) = \frac{1}{1 + e^{-x}} \)
import torchimport torch.nn as nn
class CustomSigmoid(nn.Module): def __init__(self): super().__init__() # Call the constructor of the parent class (nn.Module)
def forward(self, x): # Define the forward pass computation return 1 / (1 + torch.exp(-x))
# Usagedata = torch.tensor([1.0, 5.0, -4.0])custom_sigmoid = CustomSigmoid()output = custom_sigmoid(data)print(output)# Expected output: tensor([0.7311, 0.9933, 0.0180])
Example: Custom Softmax Function
The softmax function is: \( \text{softmax}(x_i) = \frac{\exp(x_i)}{\sum_j \exp(x_j)} \)
A numerically stable version is: \( \text{softmax}_{\text{stable}}(x_i) = \frac{\exp(x_i - c)}{\sum_j \exp(x_j - c)} \) where \( c = \max(x) \).
import torchimport torch.nn as nn
class StableSoftmax(nn.Module): def __init__(self): super().__init__()
def forward(self, x): c = torch.max(x, dim=0).values exp_x_minus_c = torch.exp(x - c) sum_exp = torch.sum(exp_x_minus_c) return exp_x_minus_c / sum_exp
# Usagedata = torch.tensor([1.0, 2.0, 3.0])stable_softmax = StableSoftmax()output = stable_softmax(data)print(output)# Expected output: tensor([0.0900, 0.2447, 0.6652])
💾 2. Database - SQL Advanced Techniques
Structured Query Language (SQL) is used to communicate with a database. It is the standard language for relational database management systems.
🔗 2.1. SQL JOIN Clause
Joins are used to combine rows from two or more tables, based on a related column between them.
Join Type | Description |
---|---|
INNER JOIN | Returns records that have matching values in both tables. |
LEFT JOIN | Returns all records from the left table, and the matched records from the right table. |
RIGHT JOIN | Returns all records from the right table, and the matched records from the left table. |
FULL OUTER JOIN | Returns all records when there is a match in either left or right table. (MySQL doesn’t support this directly; it can be emulated with UNION) |
SELF JOIN | A regular join, but the table is joined with itself. |
CROSS JOIN | Creates the Cartesian product of the two tables, returning all possible combinations of rows. |
🎨 Interactive SQL JOINs Visualization
Example: Joining orders and customers tables
SELECT o.order_id, c.first_name, c.last_name, os.name AS statusFROM orders AS oJOIN customers AS c ON o.customer_id = c.customer_idJOIN order_statuses AS os ON o.status = os.order_status_idWHERE c.state = 'VA'; -- Example of filtering after joining
🔍 2.2. Subqueries (Nested Queries)
A subquery is a query nested inside another query. It can be used in SELECT, FROM, WHERE, or HAVING clauses.
Example: Find clients with an invoice total above the average.
SELECT *FROM invoicesWHERE invoice_total > ( -- Subquery to calculate the average invoice total SELECT AVG(invoice_total) FROM invoices);
📋 2.3. Common Table Expressions (CTEs)
A CTE provides a temporary, named result set that you can reference within another SELECT, INSERT, UPDATE, or DELETE statement. CTEs improve readability and help break down complex queries.
Example: Find the client with the highest total invoice amount.
WITH client_sales AS ( -- CTE to calculate total sales per client SELECT client_id, SUM(invoice_total) AS total_sales FROM invoices GROUP BY client_id)SELECT c.name, cs.total_salesFROM clients cJOIN client_sales cs ON c.client_id = cs.client_idORDER BY cs.total_sales DESCLIMIT 1;
🗃️ 2.4. Temporary Tables
A temporary table is a table that is created and exists only for the duration of a database session. It’s useful for storing intermediate results.
-- Create a temporary table to hold aggregated dataCREATE TEMPORARY TABLE temp_client_summary ASSELECT client_id, SUM(invoice_total) AS total_spent, COUNT(invoice_id) AS number_of_invoicesFROM invoicesGROUP BY client_id;
-- Now you can query this temporary table multiple timesSELECT * FROM temp_client_summary WHERE total_spent > 500;
⚙️ 2.5. Stored Procedures and Triggers
- Stored Procedure: A prepared SQL code that you can save, so the code can be reused over and over again. You can pass parameters to a stored procedure.
- Trigger: A special type of stored procedure that automatically runs when an event (INSERT, UPDATE, DELETE) occurs in a database table.
Example: Stored Procedure
DELIMITER $$
CREATE PROCEDURE get_clients_by_state(IN state_abbreviation CHAR(2))BEGIN SELECT * FROM clients WHERE state = state_abbreviation;END$$
DELIMITER ;
-- To use it:CALL get_clients_by_state('CA');
🏗️ 3. Data Structures Fundamentals
💡 Core Concept: Data structures are a way of organizing and storing data so that they can be accessed and worked with efficiently.
📚 3.1. Stack
A stack is a linear data structure that follows the LIFO (Last-In, First-Out) principle. The last element added to the stack will be the first one to be removed.
Key Operations:
- push: Adds an element to the top of the stack
- pop: Removes the top element from the stack
- peek/top: Returns the top element without removing it
- is_empty: Checks if the stack is empty
🎨 Interactive Stack Visualization
Python Implementation:
class MyStack: def __init__(self, capacity): self._capacity = capacity self._stack = []
def is_empty(self): return len(self._stack) == 0
def is_full(self): return len(self._stack) == self._capacity
def push(self, item): if self.is_full(): raise Exception("Stack Overflow") self._stack.append(item)
def pop(self): if self.is_empty(): raise Exception("Stack Underflow") return self._stack.pop()
def top(self): if self.is_empty(): return "Stack is empty" return self._stack[-1]
🚀 3.2. Queue
A queue is a linear data structure that follows the FIFO (First-In, First-Out) principle. The first element added to the queue will be the first one to be removed.
Key Operations:
- enqueue: Adds an element to the rear of the queue
- dequeue: Removes an element from the front of the queue
- front: Returns the first element
- rear: Returns the last element
- is_empty: Checks if the queue is empty
🎨 Interactive Queue Visualization
Python Implementation:
from collections import deque
class MyQueue: def __init__(self, capacity): self._capacity = capacity self._queue = deque()
def is_empty(self): return len(self._queue) == 0
def is_full(self): return len(self._queue) == self._capacity
def enqueue(self, item): if self.is_full(): raise Exception("Queue Overflow") self._queue.append(item)
def dequeue(self): if self.is_empty(): raise Exception("Queue Underflow") return self._queue.popleft()
def front(self): if self.is_empty(): return "Queue is empty" return self._queue[0]
🌳 3.3. Tree
A tree is a non-linear hierarchical data structure that consists of nodes connected by edges.
Key Terminology:
- Node: The fundamental part of a tree. It can have a name, which we call a ‘key’, and it holds the data
- Root: The topmost node in a tree
- Edge: The link between two nodes
- Parent: The node which has a branch from it to any other node
- Child: A node that is a descendant of another node
- Leaf Node: A node with no children
- Height: The length of the longest path to a leaf
- Depth: The length of the path to its root
🎨 Interactive Tree Structure Visualization
🔄 Tree Traversal Algorithms
Tree traversal is the process of visiting each node in a tree data structure exactly once. There are two main categories:
Breadth-First Search (BFS): Explores all nodes at the present depth prior to moving on to the nodes at the next depth level. It uses a queue.
Depth-First Search (DFS): Explores as far as possible along each branch before backtracking. It uses a stack.
DFS Variants:
- In-order: Left, Root, Right
- Pre-order: Root, Left, Right
- Post-order: Left, Right, Root
🎨 Interactive DFS Traversal Variants
🎨 Interactive BFS vs DFS Traversal Comparison
🌲 3.4. Binary Search Tree (BST)
A binary tree where for each node, all keys in the left subtree are less than the node’s key, and all keys in the right subtree are greater than the node’s key. This property makes searching very efficient.
Key Properties:
- Left Subtree: All values are smaller than the current node
- Right Subtree: All values are larger than the current node
- Search Time: \( O(\log n) \) average case, \( O(n) \) worst case
- Insertion/Deletion: \( O(\log n) \) average case
🎨 Interactive Binary Search Tree Visualization
📊 4. Interactive Learning Dashboard
🎯 Progress Tracker
🎯 5. Self-Assessment & Action Plan
✅ Knowledge Check
Topic | Confidence Level | Next Steps |
---|---|---|
OOP Principles | ⭐⭐⭐⭐⭐ | Practice advanced patterns |
PyTorch Custom Modules | ⭐⭐⭐⭐☆ | Build more complex architectures |
SQL Advanced Queries | ⭐⭐⭐⭐⭐ | Optimize query performance |
Data Structures | ⭐⭐⭐☆☆ | Implement algorithms |
💡 Key Takeaways
🌟 This Week’s Achievements:
- Mastered OOP fundamentals with practical examples
- Explored advanced SQL techniques for data manipulation
- Built solid foundation in data structures
- Created interactive visualizations for better understanding
📖 Keep Learning! The journey to AI mastery requires consistent practice and application. Use these concepts in your daily coding practice!