Assignment 01: Programming

Published

July 31, 2025

AI Notice: I expect that this entire assignment can be readily solved by today’s AI systems. I would encourage you to attempt the assignment yourself first, but if you find yourself stuck, you can use AI tools to help you. I have to emphasize that if you do so, it is crucial that you take the time to understand the AI’s solution and not just copy it. The goal is to learn and practice the concepts, not just to complete the assignment. If you don’t need AI to solve the assignment – great! It can still help – ask it to review your solution, critique it, or suggest improvements. This will help you learn even more about how to write good code.

Problem 1: Robust addition

Write a function that takes in a list of numbers and returns their sum. However, if the list contains any non-numeric values, the function should ignore those values and only sum the numeric ones. The exception is if the non-numeric values can be converted to numeric types (like strings representing numbers), in which case they should be converted and included in the sum. If the list is empty or contains no numeric values, the function should return 0.

def robust_addition(numbers : list) -> float:
    # TODO: Implement the function to sum only numeric values in the list.
    pass

Problem 2:

You have a 1D NumPy array of student scores (0–100).

  1. Write a function pass_rate(scores, threshold) that returns the fraction of scores ≥ threshold.
  2. Write a function top_percentile(scores, p) that returns the score value at the p-th percentile (e.g. p=0.9 ⇒ 90th percentile).

Constraints:

  • Use no Python loops or list-comprehensions.
  • Leverage NumPy’s casting behavior (boolean \(\to\) integer).
import numpy as np
from typing import Union

Array1D = Union[np.ndarray, list[float]]

def pass_rate(scores: Array1D, threshold: float) -> float:
    """
    Return the proportion of entries in `scores` >= threshold.
    """
    arr = np.asarray(scores)
    # TODO: compute proportion without loops
    ...

def top_percentile(scores: Array1D, p: float) -> float:
    """
    Return the p-th percentile of `scores`, where 0 < p < 1.
    """
    arr = np.asarray(scores)
    # TODO: use a single np function
    ...

Problem 3: Palindrome Checker

Write a function that checks whether a given string is a palindrome (a word, phrase, number, or other sequence of characters that reads the same forward and backward, ignoring spaces, punctuation, and capitalization). The function should return True if the string is a palindrome and False otherwise.

def is_palindrome(s: str) -> bool:
    # TODO: Implement the function to check if the string is a palindrome.
    # Ignore spaces, punctuation, and capitalization.
    pass

Problem 4: Multiple data tables and Pandas operations

We did not cover how to do all this in class, but here are some exercises to practice merging and querying data with Pandas. Please refer to the Pandas documentation, the links below, and feel free to ask AI assistants to point you to the right functions and methods.

Some related reading: - Blog post on relational databases - Joins in SQL / relational databases - Joins in Pandas

Below we define the data for a set of tables related to car registrations and traffic violations. You should be able to use the Pandas library to perform various queries on this data. Try the following exercises:

  1. Show all violations for “Alice Smith” (D001).
    Hint: Join driversregistrationsviolations and filter where dl_number == "D001".

  2. Find violations on cars older than 2018.
    Hint: Join carsviolations and filter where year < 2018.

  3. Which plates have zero recorded violations?
    Hint: Anti-join registrations (or cars) against violations.

  4. Count total violations per driver (include drivers with none).
    Hint: Merge all tables, then .groupby("dl_number") + .size() (or .count()).

  5. Compute average fine per driver, sorted descending.
    Hint: Group by dl_number and use .mean() on the fine column.

  6. For each car make (e.g. Ford, Toyota), what is the total number of violations?
    Hint: Join carsviolations → group by make and use .size().

  7. What’s the average fine by vehicle year?
    Hint: Merge carsviolations, group by year, and use .mean() on fine.

import pandas as pd
from pandas import DataFrame

# — Driver info — (10 drivers)
drivers = pd.DataFrame([
    {"dl_number": "D001", "name": "Alice Smith",   "age": 34},
    {"dl_number": "D002", "name": "Bob Jones",     "age": 28},
    {"dl_number": "D003", "name": "Carol Diaz",    "age": 45},
    {"dl_number": "D004", "name": "David Lee",     "age": 52},
    {"dl_number": "D005", "name": "Eva Chen",      "age": 23},
    {"dl_number": "D006", "name": "Frank Moore",   "age": 36},
    {"dl_number": "D007", "name": "Grace Patel",   "age": 41},
    {"dl_number": "D008", "name": "Henry Zhao",    "age": 29},
    {"dl_number": "D009", "name": "Ivy Nguyen",    "age": 50},
    {"dl_number": "D010", "name": "Jack O'Connor", "age": 31},
])

# — Car registrations (DL → plate) — (15 registrations)
registrations = pd.DataFrame([
    {"dl_number": "D001", "plate": "ABC-123"},
    {"dl_number": "D001", "plate": "XYZ-999"},
    {"dl_number": "D002", "plate": "JKL-456"},
    {"dl_number": "D003", "plate": "MNO-321"},
    {"dl_number": "D004", "plate": "PQR-654"},
    {"dl_number": "D005", "plate": "STU-111"},
    {"dl_number": "D005", "plate": "VWX-222"},
    {"dl_number": "D006", "plate": "YZA-333"},
    {"dl_number": "D007", "plate": "BCD-444"},
    {"dl_number": "D007", "plate": "EFG-555"},
    {"dl_number": "D008", "plate": "HIJ-666"},
    {"dl_number": "D009", "plate": "KLM-777"},
    {"dl_number": "D009", "plate": "NOP-888"},
    {"dl_number": "D010", "plate": "QRS-999"},
])

# — Car info (plate → make/model/year) — (14 cars)
cars = pd.DataFrame([
    {"plate": "ABC-123", "make": "Toyota",  "model": "Camry",   "year": 2018},
    {"plate": "XYZ-999", "make": "Honda",   "model": "Civic",   "year": 2020},
    {"plate": "JKL-456", "make": "Ford",    "model": "Escape",  "year": 2019},
    {"plate": "MNO-321", "make": "Tesla",   "model": "Model 3", "year": 2021},
    {"plate": "PQR-654", "make": "Nissan",  "model": "Altima",  "year": 2017},
    {"plate": "STU-111", "make": "Chevy",   "model": "Malibu",  "year": 2016},
    {"plate": "VWX-222", "make": "Kia",     "model": "Soul",    "year": 2022},
    {"plate": "YZA-333", "make": "BMW",     "model": "X3",      "year": 2015},
    {"plate": "BCD-444", "make": "Audi",    "model": "A4",      "year": 2019},
    {"plate": "EFG-555", "make": "Hyundai", "model": "Elantra", "year": 2020},
    {"plate": "HIJ-666", "make": "Subaru",  "model": "Outback", "year": 2018},
    {"plate": "KLM-777", "make": "Ford",    "model": "Focus",   "year": 2017},
    {"plate": "NOP-888", "make": "Mazda",   "model": "CX-5",    "year": 2021},
    {"plate": "QRS-999", "make": "Chevy",   "model": "Impala",  "year": 2014},
])

# — Traffic violations (plate → violation) — (20 entries)
violations = pd.DataFrame([
    {"plate": "ABC-123", "date": "2025-06-01", "type": "speeding",   "fine": 150},
    {"plate": "ABC-123", "date": "2025-06-07", "type": "red_light",  "fine": 200},
    {"plate": "XYZ-999", "date": "2025-06-11", "type": "seatbelt",  "fine":  75},
    {"plate": "JKL-456", "date": "2025-06-05", "type": "parking",    "fine":  40},
    {"plate": "MNO-321", "date": "2025-06-09", "type": "speeding",   "fine": 120},
    {"plate": "PQR-654", "date": "2025-06-10", "type": "dui",         "fine": 500},
    {"plate": "STU-111", "date": "2025-06-12", "type": "speeding",   "fine": 130},
    {"plate": "VWX-222", "date": "2025-06-14", "type": "parking",    "fine":  55},
    {"plate": "YZA-333", "date": "2025-06-15", "type": "red_light",  "fine": 250},
    {"plate": "BCD-444", "date": "2025-06-16", "type": "speeding",   "fine": 160},
    {"plate": "EFG-555", "date": "2025-06-17", "type": "parking",    "fine":  35},
    {"plate": "HIJ-666", "date": "2025-06-18", "type": "seatbelt",  "fine":  80},
    {"plate": "KLM-777", "date": "2025-06-19", "type": "speeding",   "fine": 140},
    {"plate": "NOP-888", "date": "2025-06-20", "type": "dui",         "fine": 600},
    {"plate": "QRS-999", "date": "2025-06-21", "type": "parking",    "fine":  45},
    {"plate": "ABC-123", "date": "2025-06-22", "type": "speeding",   "fine": 155},
    {"plate": "ZZZ-000", "date": "2025-06-12", "type": "speeding",   "fine": 130}, 
    {"plate": "UNKNOWN", "date": "2025-06-23", "type": "red_light",  "fine": 220}, 
    {"plate": "NOP-888", "date": "2025-06-24", "type": "seatbelt",  "fine":  85},
    {"plate": "STU-111", "date": "2025-06-25", "type": "parking",    "fine":  50},
])

Problem 5: List Comprehension and Lambda Functions

Read about two advanced Python features we did not cover in class: - List comprehensions - Lambda functions

They are very useful for writing concise code, since they allow you to create lists and functions in a single line.

  1. Write a list comprehension that builds a staircase pattern of the word “Staircases” with 10 steps, letter by letter.

    • Example output:
    ['S',
     'St',
     'Sta',
     'Stai',
     'Stair',
     'Stairc',
     'Stairca',
     'Staircas',
     'Staircase',
     'Staircases']
  2. Write a lambda function that takes a list of numbers and returns a new list containing only the numbers from the original list that are divisible by 3.

  3. Write a lambda function that takes a string and returns the string reversed. (e.g. “shalom” becomes “molahs”). Then, apply this function to the column of names in the drivers table to create a new column called reversed_name. (You can use the apply method on a Pandas DataFrame to apply a function to each element in a column.)

Problem 6: Object-Oriented Programming (OOP) with Inheritance

Objective
Practice defining base classes, subclasses, method overriding, etc.

Build a mini sports‐league system. You’ll model TeamMember, Player, Coach, and Team, then extend with sport‐specific players.

Complete the class definitions below:

from typing import List

class TeamMember:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age

    def role(self) -> str:
        """Return the member’s role name."""
        ...

class Player(TeamMember):
    def __init__(self, name: str, age: int, position: str, number: int) -> None:
        super().__init__(name, age)
        self.position = position
        self.number = number

    def role(self) -> str:
        ...

    def play(self) -> str:
        """Return a generic “is playing” message."""
        ...

class Coach(TeamMember):
    def __init__(self, name: str, age: int, experience_years: int) -> None:
        super().__init__(name, age)
        self.experience_years = experience_years

    def role(self) -> str:
        ...

    def train(self, player: Player) -> str:
        """Return a training message."""
        ...

class Team:
    def __init__(self, name: str) -> None:
        self.name = name
        self.members: List[TeamMember] = []

    def add_member(self, member: TeamMember) -> None:
        ...

    def lineup(self) -> List[str]:
        """List ‘Name – Role’ for each member."""
        ...

    def game_day(self) -> None:
        """
        For each Player, call play();
        for each Coach, call train() on every Player.
        Print each message.
        """
        ...

Once the methods are all defined, you can create instances of these classes and test the functionality.

Instantiate two teams, “HaPoel” and “Maccabi”. Try adding players and coaches to each team, and then print out the team lineups with .lineup() and simulate a gameday preparation with .game_day().

Next: Define two subclasses of Player: BasketballPlayer and FootballPlayer (European football, not American). Each subclass should have a unique play() method that prints a sport-specific message. For example, BasketballPlayer might print “Nailing a three-pointer!” while FootballPlayer might print “Goooooooooal!”.

Problem 7: Functional vs. Object-Oriented Programming for team statistics

This exervise is designed to emphasize the differences between functional and object-oriented programming paradigms.

We’ll define a dataset with recorded game statistics for a team of players, and then implement two different approaches to compute the team’s statistical leaders: one using functional programming and the other using object-oriented programming.

data = pd.DataFrame({
    "game_id": ["001", "001", "002", "002", "002"],
    "team": ["HaPoel", "Maccabi", "HaPoel", "Maccabi", "HaPoel"],
    "player": ["Messi", "Ronaldo", "Messi", "Wagner", "Neymar"],
    "goals": [1, 2, 1, 0, 1],
    "assists": [0, 1, 0, 2, 1],
    "minutes_played": [90, 90, 90, 90, 90]
})

# compute the scoring leader for a given team
def compute_scoring_leader(data, team: str) -> str:
    ...
    
# compute the scoring leader for a given team using OOP
class TeamStats:
    def __init__(self, data: pd.DataFrame, team: str) -> None:
        ...
    
    def scoring_leader(self) -> str:
        ...