Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions prep_exercise/1a_why_we_use_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ========== exercise 1 ==================================
# Predict what double("22") will do. Then run the code and check. Did it do what you expected? Why did it return the value it did?

def half(value):
return value / 2

def double(value):
return value * 2

def second(value):
return value[1]

print(double("22"))

# My prediction was that double("22") would print 44.
# After running the code, I realized it prints "2222" because
# multiplying a string by an integer in Python repeats the string.
#
# What I have learned is that in Python, the * operator behaves
# differently depending on the data type. For example:
# - with integers, it performs mathematical multiplication
# - with strings, it performs string repetition
15 changes: 15 additions & 0 deletions prep_exercise/1b_why_we_use_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# ================= exercise ===============================
# Read the code and write down what the bug is. How would you fix it?

def double(number):
return number * 3

print(double(10))

# The bug in the code is that the function is named double(), but it multiplies
# the number by 3 instead of 2. This makes the function behavior inconsistent
# with its name.

# To fix this, either:
# - we need to change the function to return number * 2, or
# - rename the function to triple() if multiplying by 3 is the intended behavior.
31 changes: 31 additions & 0 deletions prep_exercise/2_type_checking_with_mypy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

def open_account(balances, name, amount):
balances[name] = amount

def sum_balances(accounts):
total = 0
for name, pence in accounts.items():
print(f"{name} had balance {pence}")
total += int(pence)
return total

def format_pence_as_string(total_pence):
if total_pence < 100:
return f"{total_pence}p"
pounds = int(total_pence / 100)
pence = total_pence % 100
return f"£{pounds}.{pence:02d}"

balances = {
"Sima": 700,
"Linn": 545,
"Georg": 831,
}

open_account(balances, "Tobi", 913)
open_account(balances, "Olya", "713")

total_pence = sum_balances(balances)
total_string = format_pence_as_string(total_pence)

print(f"The bank accounts total {total_string}")
26 changes: 26 additions & 0 deletions prep_exercise/3_classes_and_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class Person:
def __init__(self, name: str, age: int, preferred_operating_system: str, address: str):
self.name = name
self.age = age
self.preferred_operating_system = preferred_operating_system
self.address = address

imran = Person("Imran", 22, "Ubuntu", "London street")
print(imran.name)
print(imran.address)

eliza = Person("Eliza", 34, "Arch Linux", "Hatfield town")
print(eliza.name)
print(eliza.address)

# ====function to check if imran is an adult===========
def is_adult(person: Person) -> bool:
return person.age >= 18

print(is_adult(imran))


def address(person: Person) -> str:
return person.city #city property does not exist

print(address(imran))
24 changes: 24 additions & 0 deletions prep_exercise/4_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# exercise
# Change the Person class to take a date of birth (using the standard library’s datetime.date class) and store it in a field instead of age.
# Update the is_adult method to act the same as before.


from datetime import date

class Person:
def __init__(self, name: str, date_of_birth: date, preferred_operating_system: str):
self.name = name
self.date_of_birth = date_of_birth
self.preferred_operating_system = preferred_operating_system

def is_adult(self):
today = date.today()
age = today.year - self.date_of_birth.year

if (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day):
age = age - 1

return age >= 18

imran = Person("Imran", date(2005, 5, 12), "Ubuntu")
print(imran.is_adult())
28 changes: 28 additions & 0 deletions prep_exercise/5_Dataclasses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# ✍️exercise
# Write a Person class using @datatype which uses a datetime.date for date of birth, rather than an int for age.
# Re-add the is_adult method to it.

from dataclasses import dataclass
from datetime import date

@dataclass(frozen=True)
class Person:
name: str
date_of_birth: date
preferred_operating_system: str


def is_adult(self):
today = date.today()
age = today.year - self.date_of_birth.year

if (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day):
age = age - 1

return age >= 18

imran = Person("Imran", date(2000,5,12), "Ubuntu")
print(imran)

imran2 = Person("Imran",date(2000,5,12), "Ubuntu")
print(imran == imran2) # Prints True
20 changes: 20 additions & 0 deletions prep_exercise/6_generics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from dataclasses import dataclass
from typing import List

@dataclass(frozen=True)
class Person:
name: str
age: int
children: List["Person"]

fatma = Person(name="Fatma", age= 22, children=[])
aisha = Person(name="Aisha", age =21, children=[])

imran = Person(name="Imran", age = 33, children=[fatma, aisha])

def print_family_tree(person: Person) -> None:
print(person.name)
for child in person.children:
print(f"- {child.name} ({child.age})")

print_family_tree(imran)
42 changes: 42 additions & 0 deletions prep_exercise/7_type_guided_refactoring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from dataclasses import dataclass
from typing import List

@dataclass(frozen=True)
class Person:
name: str
age: int
preferred_operating_systems: List[str]


@dataclass(frozen=True)
class Laptop:
id: int
manufacturer: str
model: str
screen_size_in_inches: float
operating_system: str


def find_possible_laptops(laptops: List[Laptop], person: Person) -> List[Laptop]:
possible_laptops: List[Laptop] = []
for laptop in laptops:
if laptop.operating_system in person.preferred_operating_systems:
possible_laptops.append(laptop)
return possible_laptops


people = [
Person(name="Imran", age=22, preferred_operating_systems=["ubuntu"]),
Person(name="Eliza", age=34, preferred_operating_systems=["Arch Linux"]),
]

laptops = [
Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13, operating_system="Arch Linux"),
Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system="Ubuntu"),
Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15, operating_system="ubuntu"),
Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13, operating_system="macOS"),
]

for person in people:
possible_laptops = find_possible_laptops(laptops, person)
print(f"Possible laptops for {person.name}: {possible_laptops}")
104 changes: 104 additions & 0 deletions prep_exercise/8_enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from dataclasses import dataclass
from enum import Enum
from typing import List, Dict
import sys

class OperatingSystem(Enum):
MACOS = "macOS"
ARCH = "Arch Linux"
UBUNTU = "Ubuntu"

@dataclass(frozen=True)
class Person:
name: str
age: int
preferred_operating_system: OperatingSystem


@dataclass(frozen=True)
class Laptop:
id: int
manufacturer: str
model: str
screen_size_in_inches: float
operating_system: OperatingSystem

# =====define parse age======
def parse_age(raw: str) -> int:
try:
age= int(raw)
except ValueError:
print("Error: age must be asn integer.", file=sys.stderr)
sys.exit(1)

if age <= 0:
print("Error: age must be a positive integer.", file=sys.stderr)
sys.exit(1)

return age

# =====define parse operating system======
def parse_operating_system(raw: str) -> OperatingSystem:
normalized = raw.strip().lower()

aliases = {
"macos": OperatingSystem.MACOS,
"mac": OperatingSystem.MACOS,
"arch": OperatingSystem.ARCH,
"arch linux": OperatingSystem.ARCH,
"ubuntu": OperatingSystem.UBUNTU,
}

if normalized not in aliases:
valid = ", ".join(sorted(aliases.keys()))
print(f"Error: invalid operating system. Try one of: {valid}", file=sys.stderr)
sys.exit(1)

return aliases[normalized]


# =======count laptops by os============
def count_laptops_by_os(laptops: List[Laptop]) -> Dict[OperatingSystem, int]:
counts: Dict[OperatingSystem, int] = {os: 0 for os in OperatingSystem}
for laptop in laptops:
counts[laptop.operating_system] +=1
return counts


# ======define main ================
def main() -> None:
laptops = [
Laptop(id=1, manufacturer="Dell", model="XPS", screen_size_in_inches=13.0, operating_system=OperatingSystem.ARCH),
Laptop(id=2, manufacturer="Dell", model="XPS", screen_size_in_inches=15.0, operating_system=OperatingSystem.UBUNTU),
Laptop(id=3, manufacturer="Dell", model="XPS", screen_size_in_inches=15.0, operating_system=OperatingSystem.UBUNTU),
Laptop(id=4, manufacturer="Apple", model="macBook", screen_size_in_inches=13.0, operating_system=OperatingSystem.MACOS),
]

name = input("Enter your name: ").strip()
if not name:
print("Error: name cannot be empty.", file=sys.stderr)
sys.exit(1)

age = parse_age(input("Enter your age: "))
preferred_os = parse_operating_system(input("preferred OS(Ubuntu / Arch / macOS): "))

person = Person(name=name, age=age, preferred_operating_system=preferred_os)

counts = count_laptops_by_os(laptops)
preferred_count = counts[person.preferred_operating_system]

print(f"\nHi {person.name} (age {person.age})")
print(f"Laptops available with {person.preferred_operating_system.value}: {preferred_count}")

# Find the OS with maximum availability
best_os = max(counts, key=lambda os:counts[os])
best_count = counts[best_os]

if best_os != person.preferred_operating_system and best_count > preferred_count:
print(
f"If you are willing to accept {best_os.value} instead,"
f"You're more likely to get a laptop. {best_count} available)."
)

if __name__ == "__main__":
main()
52 changes: 52 additions & 0 deletions prep_exercise/9_inheritance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# play computer game with the code below and predict what you expect each line will do. Then run the code and check your prediction (If any lines cause errors, you may need to comment them out to check later lines).

class Parent:
def __init__(self, first_name: str, last_name: str):
self.first_name = first_name
self.last_name = last_name

def get_name(self) -> str:
return f"{self.first_name} {self.last_name}"


class Child(Parent):
def __init__(self, first_name: str, last_name: str):
super().__init__(first_name, last_name)
self.previous_last_names: list[str] = []

def change_last_name(self, last_name) -> None:
self.previous_last_names.append(self.last_name)
self.last_name = last_name

def get_full_name(self) -> str:
suffix = ""
if len(self.previous_last_names) > 0:
suffix = f" (née {self.previous_last_names[0]})"
return f"{self.first_name} {self.last_name}{suffix}"

person1 = Child("Elizaveta", "Alekseeva")
print(person1.get_name()) #works because get_name is inherited from Parent.
print(person1.get_full_name()) # get_full_name() works, exist in child.
person1.change_last_name("Tyurina") #change_last_name("Tyurina") works, exist in child..
print(person1.get_name()) # works because it comes from Parent.get_name.
print(person1.get_full_name())# works because it comes from Parent.get_name.


person2 = Parent("Elizaveta", "Alekseeva")
print(person2.get_name()) # works because it is in the Parent class.
print(person1.get_full_name())
# print(person2.get_full_name()) # crashes the program as get_full_name is defined only in child
# person2.change_last_name("Tyurina") # crashes the program as change_last_name is defined only in child not in parent
print(person2.get_name())
# print(person2.get_full_name()) #get_full_name is defined only in child not in parent


# ===============What I learned from playing computer game with above code.

# 1. Objects can only call methods they actually have inside them.
# - Creating a parent object does not automatically give the child its behavior

# =====================================================================
#2. Inheritance is "is-a", not "might-be" i.e
# A child is a parent(here the child can inherit freely from the father.)
# A parent is NOT a child(i.e a parent does not inherit from the child)
Loading