Computer Science with Applications - Comprehensive Study
Guide
1. Getting Started
1.1 Computational Thinking
Core Principles:
Problem Decomposition: Break large problems into smaller, manageable pieces
Pattern Recognition: Identify similarities and recurring themes
Abstraction: Focus on essential features while hiding unnecessary details
Algorithm Design: Create step-by-step solutions
Example: Planning a dinner party
Decomposition: Guest list, menu planning, shopping, cooking, setup
Pattern recognition: Similar tasks for different courses
Abstraction: Focus on "prepare food" rather than every cooking detail
Algorithm: Specific sequence of tasks with timing
1.2 Programming Basics
Variables and Assignment:
python
# Variables store data
age = 25
name = "Alice"
height = 5.8
is_student = True
# Variables can be reassigned
age = age + 1 # or age += 1
Data Types:
Integers: Whole numbers ( 42 , -17 )
Floats: Decimal numbers ( 3.14 , -2.7 )
Strings: Text ( "Hello" , 'Python' )
Booleans: True/False values
Operators:
python
# Arithmetic
10 + 3 # 13 (addition)
10 - 3 # 7 (subtraction)
10 * 3 # 30 (multiplication)
10 / 3 # 3.333... (division)
10 // 3 # 3 (floor division)
10 % 3 # 1 (modulo - remainder)
10 ** 3 # 1000 (exponentiation)
# Comparison
x == y # Equal
x != y # Not equal
x < y # Less than
x <= y # Less than or equal
x > y # Greater than
x >= y # Greater than or equal
# Logical
True and False # False
True or False # True
not True # False
Input/Output:
python
name = input("Enter your name: ")
age = int(input("Enter your age: ")) # Convert string to int
print(f"Hello {name}, you are {age} years old")
1.3 Control Flow Statements
Conditional Statements:
python
temperature = 75
if temperature > 80:
print("It's hot!")
elif temperature > 60:
print("It's pleasant")
elif temperature > 40:
print("It's cool")
else:
print("It's cold!")
# Ternary operator (shorthand)
status = "hot" if temperature > 80 else "not hot"
For Loops:
python
# Iterate over a sequence
fruits = ["apple", "banana", "orange"]
for fruit in fruits:
print(f"I like {fruit}")
# Iterate with index
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# Iterate over range
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 8): # 2, 3, 4, 5, 6, 7
print(i)
for i in range(0, 10, 2): # 0, 2, 4, 6, 8
print(i)
While Loops:
python
count = 0
while count < 5:
print(f"Count: {count}")
count += 1
# Be careful of infinite loops!
# Always ensure the condition eventually becomes False
Loop Control:
python
for i in range(10):
if i == 3:
continue # Skip rest of this iteration
if i == 7:
break # Exit the loop entirely
print(i) # Prints: 0, 1, 2, 4, 5, 6
1.4 Introduction to Functions
Function Basics:
python
def greet(name, greeting="Hello"):
"""
Greet a person with a custom message.
Args:
name (str): The person's name
greeting (str): The greeting message (default: "Hello")
Returns:
str: The complete greeting
"""
return f"{greeting}, {name}!"
# Function calls
message = greet("Alice") # "Hello, Alice!"
message = greet("Bob", "Hi") # "Hi, Bob!"
message = greet(greeting="Hey", name="Charlie") # Keyword arguments
Variable Scope:
python
global_var = "I'm global"
def example_function():
local_var = "I'm local"
print(global_var) # Can access global variables
print(local_var) # Can access local variables
example_function()
print(global_var) # Can access global variables
# print(local_var) # ERROR: local_var not accessible here
Multiple Return Values:
python
def calculate_stats(numbers):
total = sum(numbers)
count = len(numbers)
average = total / count if count > 0 else 0
return total, count, average
total, count, avg = calculate_stats([1, 2, 3, 4, 5])
The Call Stack Concept: When functions call other functions, Python keeps track using a "call stack":
python
def function_a():
print("In function A")
function_b()
print("Back in function A")
def function_b():
print("In function B")
function_c()
print("Back in function B")
def function_c():
print("In function C")
function_a()
# Output:
# In function A
# In function B
# In function C
# Back in function B
# Back in function A
1.5 Code Organization
Modules and Imports:
python
# Import entire module
import math
result = math.sqrt(16)
# Import specific functions
from math import sqrt, pi
result = sqrt(16)
# Import with alias
import numpy as np
array = np.array([1, 2, 3])
# Your own modules (if you create file utils.py)
from utils import my_function
Documentation Best Practices:
python
def calculate_compound_interest(principal, rate, time, compounds_per_year=1):
"""
Calculate compound interest.
Formula: A = P(1 + r/n)^(nt)
Args:
principal (float): Initial investment amount
rate (float): Annual interest rate (as decimal, e.g., 0.05 for 5%)
time (float): Time in years
compounds_per_year (int): Number of times interest compounds per year
Returns:
float: Final amount after compound interest
Example:
>>> calculate_compound_interest(1000, 0.05, 10, 12)
1643.6194649423633
"""
return principal * (1 + rate/compounds_per_year) ** (compounds_per_year * time)
1.6 Understanding Errors and Catching Exceptions
Common Error Types:
python
# SyntaxError - Code structure is wrong
# if x = 5: # Should use == for comparison
# NameError - Variable not defined
# print(undefined_variable)
# TypeError - Wrong data type
# "hello" + 5 # Can't add string and int
# ValueError - Right type, wrong value
# int("hello") # Can't convert "hello" to integer
# IndexError - List index out of range
# my_list = [1, 2, 3]
# print(my_list[5]) # Index 5 doesn't exist
# KeyError - Dictionary key doesn't exist
# my_dict = {"a": 1}
# print(my_dict["b"]) # Key "b" doesn't exist
Exception Handling:
python
def safe_divide(a, b):
try:
result = a / b
return result
except ZeroDivisionError:
print("Error: Cannot divide by zero!")
return None
except TypeError:
print("Error: Please provide numbers only!")
return None
finally:
print("Division operation completed")
# Usage
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # Error message, returns None
print(safe_divide("10", 2)) # Error message, returns None
Advanced Exception Handling:
python
def process_file(filename):
try:
with open(filename, 'r') as file:
data = file.read()
# Process data here
return data
except FileNotFoundError:
print(f"File {filename} not found")
except PermissionError:
print(f"Permission denied to read {filename}")
except Exception as e:
print(f"Unexpected error: {e}")
finally:
print("File processing attempt completed")
2. Data Structures
2.1 Lists, Tuples, and Strings
Lists - Mutable Sequences:
python
# Creating lists
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True]
empty = []
# Accessing elements
print(numbers[0]) # 1 (first element)
print(numbers[-1]) # 5 (last element)
print(numbers[1:4]) # [2, 3, 4] (slicing)
print(numbers[:3]) # [1, 2, 3] (first 3)
print(numbers[2:]) # [3, 4, 5] (from index 2)
# Modifying lists
numbers.append(6) # Add to end: [1, 2, 3, 4, 5, 6]
numbers.insert(0, 0) # Insert at index 0: [0, 1, 2, 3, 4, 5, 6]
numbers.remove(3) # Remove first occurrence of 3
popped = numbers.pop() # Remove and return last element
popped_at_index = numbers.pop(1) # Remove and return element at index 1
# List operations
len(numbers) # Length
3 in numbers # Check membership (True/False)
numbers.count(2) # Count occurrences
numbers.index(4) # Find index of first occurrence
numbers.sort() # Sort in place
sorted_copy = sorted(numbers) # Return sorted copy
numbers.reverse() # Reverse in place
List Comprehensions:
python
# Traditional way
squares = []
for x in range(10):
squares.append(x**2)
# List comprehension way
squares = [x**2 for x in range(10)]
# With condition
even_squares = [x**2 for x in range(10) if x % 2 == 0]
# More complex example
words = ["hello", "world", "python", "programming"]
long_words = [word.upper() for word in words if len(word) > 5]
# Result: ['PYTHON', 'PROGRAMMING']
Memory and References:
python
# Important concept: Lists are objects in memory
list1 = [1, 2, 3]
list2 = list1 # list2 points to same object as list1
list2.append(4) # Modifies the shared object
print(list1) # [1, 2, 3, 4] - list1 is also changed!
# To create a copy:
list3 = list1.copy() # Shallow copy
list4 = list1[:] # Another way to shallow copy
list5 = list(list1) # Another way to shallow copy
# Deep copy for nested structures
import copy
nested_list = [[1, 2], [3, 4]]
deep_copied = copy.deepcopy(nested_list)
Tuples - Immutable Sequences:
python
# Creating tuples
coordinates = (3, 5)
single_item = (42,) # Note the comma for single-item tuple
empty_tuple = ()
# Tuple unpacking
x, y = coordinates # x = 3, y = 5
# Tuples are immutable
# coordinates[0] = 10 # This would cause an error
# When to use tuples:
# - Fixed collections (coordinates, RGB colors)
# - Dictionary keys (must be immutable)
# - Returning multiple values from functions
Strings - Immutable Text Sequences:
python
text = "Hello, World!"
# String methods
print(text.lower()) # "hello, world!"
print(text.upper()) # "HELLO, WORLD!"
print(text.strip()) # Remove whitespace from ends
print(text.replace("World", "Python")) # "Hello, Python!"
print(text.split(", ")) # ["Hello", "World!"]
# String formatting
name = "Alice"
age = 30
print(f"My name is {name} and I am {age} years old") # f-string (preferred)
print("My name is {} and I am {} years old".format(name, age)) # .format()
print("My name is %s and I am %d years old" % (name, age)) # % formatting (old)
# String operations
"apple" in "pineapple" # True
"hello" + " " + "world" # "hello world"
"ha" * 3 # "hahaha"
2.2 Dictionaries and Sets
Dictionaries - Key-Value Pairs:
python
# Creating dictionaries
student = {
"name": "Alice",
"age": 20,
"major": "Computer Science",
"gpa": 3.8
}
empty_dict = {}
dict_from_pairs = dict([("a", 1), ("b", 2)])
# Accessing and modifying
print(student["name"]) # "Alice"
print(student.get("age")) # 20
print(student.get("height", "N/A")) # "N/A" (default if key doesn't exist)
student["age"] = 21 # Update existing key
student["year"] = "Junior" # Add new key
del student["gpa"] # Remove key-value pair
removed_value = student.pop("major", "Unknown") # Remove and return value
# Dictionary methods
print(student.keys()) # dict_keys(['name', 'age', 'year'])
print(student.values()) # dict_values(['Alice', 21, 'Junior'])
print(student.items()) # dict_items([('name', 'Alice'), ('age', 21), ('year', 'Jun
# Iterating over dictionaries
for key in student:
print(f"{key}: {student[key]}")
for key, value in student.items():
print(f"{key}: {value}")
Dictionary Comprehensions:
python
# Create dictionary from lists
keys = ["a", "b", "c"]
values = [1, 2, 3]
my_dict = {k: v for k, v in zip(keys, values)}
# Square numbers
squares = {x: x**2 for x in range(1, 6)} # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# Conditional dictionary comprehension
even_squares = {x: x**2 for x in range(1, 11) if x % 2 == 0}
Sets - Unique Elements:
python
# Creating sets
numbers = {1, 2, 3, 4, 5}
empty_set = set() # Note: {} creates an empty dict, not set
from_list = set([1, 2, 2, 3, 3, 4]) # {1, 2, 3, 4} - duplicates removed
# Set operations
numbers.add(6) # Add element
numbers.remove(3) # Remove element (raises error if not found)
numbers.discard(10) # Remove element (no error if not found)
# Mathematical set operations
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
union = set1 | set2 # {1, 2, 3, 4, 5, 6}
intersection = set1 & set2 # {3, 4}
difference = set1 - set2 # {1, 2}
symmetric_diff = set1 ^ set2 # {1, 2, 5, 6}
# Set membership and comparisons
3 in set1 # True
set1.issubset({1, 2, 3, 4, 5}) # True
{1, 2}.issubset(set1) # True
2.3 Implementing Data Structures: Stacks and Queues
Stacks (LIFO - Last In, First Out):
python
class Stack:
def __init__(self):
self.items = []
def push(self, item):
"""Add item to top of stack"""
self.items.append(item)
def pop(self):
"""Remove and return top item"""
if self.is_empty():
raise IndexError("Stack is empty")
return self.items.pop()
def peek(self):
"""Return top item without removing it"""
if self.is_empty():
raise IndexError("Stack is empty")
return self.items[-1]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
# Usage example
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.pop()) # 3
print(stack.peek()) # 2
Stack Applications:
python
def is_balanced_parentheses(expression):
"""Check if parentheses are balanced using a stack"""
stack = []
pairs = {'(': ')', '[': ']', '{': '}'}
for char in expression:
if char in pairs: # Opening bracket
stack.append(char)
elif char in pairs.values(): # Closing bracket
if not stack:
return False
if pairs[stack.pop()] != char:
return False
return len(stack) == 0
print(is_balanced_parentheses("((()))")) # True
print(is_balanced_parentheses("([{}])")) # True
print(is_balanced_parentheses("((()]")) # False
Queues (FIFO - First In, First Out):
python
from collections import deque
class Queue:
def __init__(self):
self.items = deque()
def enqueue(self, item):
"""Add item to back of queue"""
self.items.append(item)
def dequeue(self):
"""Remove and return front item"""
if self.is_empty():
raise IndexError("Queue is empty")
return self.items.popleft()
def front(self):
"""Return front item without removing it"""
if self.is_empty():
raise IndexError("Queue is empty")
return self.items[0]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
# Usage example
queue = Queue()
queue.enqueue("first")
queue.enqueue("second")
queue.enqueue("third")
print(queue.dequeue()) # "first"
print(queue.front()) # "second"
2.4 Classes and Objects
Basic Class Definition:
python
class BankAccount:
# Class variable (shared by all instances)
bank_name = "Python Bank"
def __init__(self, account_holder, initial_balance=0):
"""Constructor - called when creating new instance"""
# Instance variables (unique to each instance)
self.account_holder = account_holder
self.balance = initial_balance
self.transaction_history = []
def deposit(self, amount):
"""Add money to account"""
if amount > 0:
self.balance += amount
self.transaction_history.append(f"Deposited ${amount}")
return True
return False
def withdraw(self, amount):
"""Remove money from account"""
if 0 < amount <= self.balance:
self.balance -= amount
self.transaction_history.append(f"Withdrew ${amount}")
return True
return False
def get_balance(self):
"""Return current balance"""
return self.balance
def __str__(self):
"""String representation of the object"""
return f"Account({self.account_holder}, ${self.balance})"
def __repr__(self):
"""Developer-friendly representation"""
return f"BankAccount('{self.account_holder}', {self.balance})"
# Creating and using objects
account1 = BankAccount("Alice", 1000)
account2 = BankAccount("Bob")
account1.deposit(500)
account1.withdraw(200)
print(account1.get_balance()) # 1300
print(account1) # Account(Alice, $1300)
Inheritance:
python
class SavingsAccount(BankAccount):
def __init__(self, account_holder, initial_balance=0, interest_rate=0.02):
super().__init__(account_holder, initial_balance) # Call parent constructor
self.interest_rate = interest_rate
def apply_interest(self):
"""Add interest to the account"""
interest = self.balance * self.interest_rate
self.balance += interest
self.transaction_history.append(f"Interest added: ${interest:.2f}")
def withdraw(self, amount):
"""Override parent method with additional fee"""
if super().withdraw(amount): # Call parent method
# Add withdrawal fee for savings accounts
fee = 2
self.balance -= fee
self.transaction_history.append(f"Withdrawal fee: ${fee}")
return True
return False
# Usage
savings = SavingsAccount("Charlie", 1000, 0.05)
savings.apply_interest()
print(savings.get_balance()) # 1050.0
Encapsulation with Properties:
python
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius # Private variable (by convention)
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature cannot be below absolute zero")
self._celsius = value
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
# Usage
temp = Temperature(25)
print(temp.celsius) # 25
print(temp.fahrenheit) # 77.0
temp.fahrenheit = 86
print(temp.celsius) # 30.0
3. Functional Programming and Recursion
3.1 Functional Programming
Higher-Order Functions: Functions that take other functions as arguments or return functions:
python
def apply_operation(numbers, operation):
"""Apply an operation to each number in the list"""
return [operation(num) for num in numbers]
def square(x):
return x ** 2
def cube(x):
return x ** 3
numbers = [1, 2, 3, 4, 5]
squared = apply_operation(numbers, square) # [1, 4, 9, 16, 25]
cubed = apply_operation(numbers, cube) # [1, 8, 27, 64, 125]
Anonymous Functions (Lambda):
python
# Lambda functions are short, one-line functions
square = lambda x: x ** 2
add = lambda x, y: x + y
is_even = lambda x: x % 2 == 0
# Often used with built-in functions
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
squared = list(map(lambda x: x**2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
Built-in Functional Programming Tools:
Map - Apply function to each element:
python
numbers = [1, 2, 3, 4, 5]
# Traditional approach
squared = []
for num in numbers:
squared.append(num ** 2)
# Using map
squared = list(map(lambda x: x**2, numbers)) # [1, 4, 9, 16, 25]
# Map with multiple iterables
nums1 = [1, 2, 3]
nums2 = [4, 5, 6]
sums = list(map(lambda x, y: x + y, nums1, nums2)) # [5, 7, 9]
Filter - Keep elements that meet a condition:
python
numbers = range(1, 11) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Traditional approach
evens = []
for num in numbers:
if num % 2 == 0:
evens.append(num)
# Using filter
evens = list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4, 6, 8, 10]
# Filter with more complex condition
words = ["hello", "world", "python", "programming", "code"]
long_words = list(filter(lambda word: len(word) > 5, words)) # ['python', 'programming
Reduce - Combine elements into single value:
python
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# Traditional approach to find product
product = 1
for num in numbers:
product *= num
# Using reduce
product = reduce(lambda x, y: x * y, numbers) # 120
# Find maximum
maximum = reduce(lambda x, y: x if x > y else y, numbers) # 5
# More practical example: flatten nested lists
nested = [[1, 2], [3, 4], [5, 6]]
flattened = reduce(lambda x, y: x + y, nested) # [1, 2, 3, 4, 5, 6]
Function Composition and Returning Functions:
python
def make_multiplier(n):
"""Return a function that multiplies by n"""
def multiplier(x):
return x * n
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(4)) # 12
# More complex example: function decorator
def timer_decorator(func):
"""Decorator to time function execution"""
import time
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
return result
return wrapper
@timer_decorator
def slow_function():
import time
time.sleep(1)
return "Done!"
slow_function() # Prints timing information
Practical Functional Programming Example:
python
# Data processing pipeline using functional programming
sales_data = [
{"product": "laptop", "price": 1000, "quantity": 2},
{"product": "mouse", "price": 25, "quantity": 10},
{"product": "keyboard", "price": 75, "quantity": 5},
{"product": "monitor", "price": 300, "quantity": 3},
]
# Calculate total revenue using functional approach
total_revenue = sum(map(lambda item: item["price"] * item["quantity"], sales_data))
# Find expensive items (>$100 total value)
expensive_items = list(filter(lambda item: item["price"] * item["quantity"] > 100, sale
# Transform data: add total_value field
enhanced_data = list(map(lambda item: {**item, "total_value": item["price"] * item["qua
3.2 Recursion
Understanding Recursion: Recursion is when a function calls itself. Every recursive function needs:
1. Base case: Condition where recursion stops
2. Recursive case: Function calls itself with simpler input
Classic Example - Factorial:
python
def factorial(n):
"""Calculate n! = n × (n-1) × (n-2) × ... × 1"""
# Base case
if n <= 1:
return 1
# Recursive case
return n * factorial(n - 1)
# Trace of factorial(4):
# factorial(4) = 4 * factorial(3)
# factorial(3) = 3 * factorial(2)
# factorial(2) = 2 * factorial(1)
# factorial(1) = 1 <- base case
# Working backwards:
# factorial(2) = 2 * 1 = 2
# factorial(3) = 3 * 2 = 6
# factorial(4) = 4 * 6 = 24
print(factorial(5)) # 120
Fibonacci Sequence:
python
def fibonacci(n):
"""Return the nth Fibonacci number"""
# Base cases
if n <= 1:
return n
# Recursive case
return fibonacci(n - 1) + fibonacci(n - 2)
# Generate first 10 Fibonacci numbers
fib_sequence = [fibonacci(i) for i in range(10)]
print(fib_sequence) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Optimized Fibonacci with Memoization:
python
def fibonacci_memo(n, memo={}):
"""Fibonacci with memoization to avoid recalculating values"""
if n in memo:
return memo[n]
if n <= 1:
return n
memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
return memo[n]
# Much faster for large values
print(fibonacci_memo(50)) # Calculates quickly
Binary Search - Recursive Implementation:
python
def binary_search(arr, target, left=0, right=None):
"""
Search for target in sorted array using binary search
Returns index if found, -1 if not found
"""
if right is None:
right = len(arr) - 1
# Base case: not found
if left > right:
return -1
# Find middle point
mid = (left + right) // 2
# Base case: found
if arr[mid] == target:
return mid
# Recursive cases
if target < arr[mid]:
return binary_search(arr, target, left, mid - 1)
else:
return binary_search(arr, target, mid + 1, right)
# Usage
sorted_list = [1, 3, 5, 7, 9, 11, 13, 15]
index = binary_search(sorted_list, 7)