19831 words
99 minutes
📚 AIO2025 - Week 3: Object-Oriented Programming, SQL & Data Structures

🎯 Learning Objectives: Master object-oriented programming principles, advanced SQL operations, and fundamental data structures through interactive visualizations and practical examples.

📚 Table of Contents

🎯 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#

ConceptDefinitionKey Characteristics
ClassBlueprint for creating objectsDefines attributes and methods
ObjectInstance of a classContains actual data and can execute methods
AttributesData stored in objectsVariables that hold object state
MethodsFunctions defined in classesOperations 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 class
dog1 = Dog("Buddy", 5)
dog2 = Dog("Lucy", 3)
# Accessing attributes and calling methods
print(f"Name: {dog1.name}") # Output: Name: Buddy
print(f"Description: {dog1.describe()}") # Output: Description: Buddy is 5 years old.
print(f"Sound: {dog2.speak('Woof!')}") # Output: Sound: Lucy says Woof!
# Working with breed
dog1.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 LevelPython ConventionVisibilityUsage
PublicattributeAccessible everywhereGeneral use attributes/methods
Protected_attributeInternal use (convention)Subclass access intended
Private__attributeName mangledStrictly 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 demonstration
account = BankAccount("Alice Smith", 1000)
# ✅ Public interface - Safe and controlled access
print(f"Account Holder: {account.account_holder}") # Direct access OK
print(f"Balance: ${account.get_balance():.2f}") # Controlled access
account.deposit(500) # Safe method
account.withdraw(200) # Safe method
print(account.get_account_info()) # Safe info retrieval
# 🔒 Protected access - Not recommended but possible
print(f"Account Number: {account._account_number}") # Discouraged but works
# ❌ Private access - Will fail
try:
print(account.__balance) # AttributeError!
except AttributeError as e:
print(f"Error: {e}")
# ⚠️ Name mangling - Python's way of hiding private attributes
print(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 class
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating.")
# Child class inheriting from Animal
class 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 method
my_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 call super().__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 torch
import 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))
# Usage
data = 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 torch
import 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
# Usage
data = 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 TypeDescription
INNER JOINReturns records that have matching values in both tables.
LEFT JOINReturns all records from the left table, and the matched records from the right table.
RIGHT JOINReturns all records from the right table, and the matched records from the left table.
FULL OUTER JOINReturns 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 JOINA regular join, but the table is joined with itself.
CROSS JOINCreates 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 status
FROM orders AS o
JOIN customers AS c ON o.customer_id = c.customer_id
JOIN order_statuses AS os ON o.status = os.order_status_id
WHERE 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 invoices
WHERE 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_sales
FROM clients c
JOIN client_sales cs ON c.client_id = cs.client_id
ORDER BY cs.total_sales DESC
LIMIT 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 data
CREATE TEMPORARY TABLE temp_client_summary AS
SELECT
client_id,
SUM(invoice_total) AS total_spent,
COUNT(invoice_id) AS number_of_invoices
FROM invoices
GROUP BY client_id;
-- Now you can query this temporary table multiple times
SELECT * 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#

TopicConfidence LevelNext 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!

📚 AIO2025 - Week 3: Object-Oriented Programming, SQL & Data Structures
https://hung2124.github.io/blog/posts/week3/
Author
AI Blog Pro Team
Published at
2025-06-23
License
CC BY-NC-SA 4.0