CS1602Introduction to Computation
Lab 10Part 4 Abstraction and OrganizationAI Level 1

Inheritance, Operators and Modules · Solo Project B Opens

Five problems on inheritance, special methods and module organization, then solo project B — arbitrary-precision arithmetic, where you have to build the machinery yourself.

Due:Homework 4 · Tue 8 Dec, 23:59
Contents

What this session is for

  • Organize a family of related classes with inheritance and polymorphism
  • Make your classes fit Python’s syntax through special methods
  • Split code into modules and guard the entry point with if __name__ == "__main__":
  • Start solo project B

Problems

10-1 A family of shapes

Write a Shape base class and three subclasses:

class Shape:
    def area(self) -> float: ...          # base raises NotImplementedError
    def name(self) -> str: ...            # returns the class name

class Circle(Shape):    def __init__(self, r: float) -> None: ...
class Rectangle(Shape): def __init__(self, w: float, h: float) -> None: ...
class Triangle(Shape):  def __init__(self, a: float, b: float, c: float) -> None: ...

Use Heron’s formula for the triangle. Then a module-level function:

def total_area(shapes: list[Shape]) -> float: ...

10-2 Account inheritance

Building on L9’s BankAccount, write two subclasses:

  • SavingsAccount: adds an interest_rate and an add_interest() method
  • CheckingAccount: permits an overdraft down to -overdraft_limit

Both __init__ methods must use super().__init__(...), and CheckingAccount must override withdraw.

Answer in your notes: did you call super().withdraw() inside CheckingAccount.withdraw? Why or why not?

10-3 A Money class

Implement Money, storing the amount as an integer number of cents (L2 explained why money must not live in a float):

class Money:
    def __init__(self, amount: float) -> None: ...   # convert to cents
    def __repr__(self) -> str: ...                   # e.g. Money(12.50)
    def __add__(self, other: "Money") -> "Money": ...
    def __sub__(self, other: "Money") -> "Money": ...
    def __eq__(self, other: object) -> bool: ...
    def __lt__(self, other: "Money") -> bool: ...

All of these must work:

Money(10.50) + Money(0.25)          # Money(10.75)
sorted([Money(3), Money(1), Money(2)])
Money(0.1) + Money(0.2) == Money(0.3)   # must be True

10-4 A container you can iterate

Implement a Deck class for a 52-card deck (no jokers):

class Deck:
    def __init__(self) -> None: ...
    def __len__(self) -> int: ...
    def __getitem__(self, i: int) -> str: ...
    def __contains__(self, card: str) -> bool: ...
    def shuffle(self) -> None: ...
    def deal(self, n: int) -> list[str]: ...    # deal n cards, removing them

The card representation is yours to choose ("AH", "Ace of Hearts", whatever) — state it in your notes.

Once __getitem__ exists, all of these should work:

len(deck)
deck[0]
"AH" in deck
for card in deck: ...

10-5 Split it into modules

Reorganize 10-1 through 10-4 into three files:

shapes.py      # 10-1
accounts.py    # 10-2
cards.py       # 10-3, 10-4
main.py        # a demonstration importing the other three

Every module needs:

  • a module docstring at the top
  • an if __name__ == "__main__": block holding that module’s self-tests

Then verify: running python3 main.py must not execute any of the three modules’ self-tests. Describe how you verified it in your notes.


Solo Project B: Arbitrary-Precision Integers

Worth 7.5% of the final grade Due Sunday, two weeks from now, 23:59

Background

L2 noted that Python integers have no upper limit — 2 ** 2000 simply works. Most languages cannot do that; their integers have a fixed width.

How does Python manage it? By storing the digits in an array and implementing the arithmetic itself. This project has you build that wheel once.

What to build

A BigInt class that must not use Python’s own big-integer arithmetic internally — you may not convert to int, add, and convert back.

class BigInt:
    def __init__(self, value: str) -> None:
        """Construct from a decimal string, e.g. BigInt("123456789012345678901234567890").
        A leading minus sign must be supported."""

    def __repr__(self) -> str: ...
    def __str__(self) -> str: ...
    def __eq__(self, other: object) -> bool: ...
    def __lt__(self, other: "BigInt") -> bool: ...
    def __add__(self, other: "BigInt") -> "BigInt": ...
    def __sub__(self, other: "BigInt") -> "BigInt": ...
    def __mul__(self, other: "BigInt") -> "BigInt": ...

Marks

ComponentWeightNotes
addition20%carries, and operands of different lengths
subtraction20%borrows, and negative results
multiplication20%long multiplication is fine; no advanced algorithms required
comparison (__eq__, __lt__)10%must sort correctly, negatives included
edge cases and robustness15%see below
code quality5%annotations, decomposition, naming
report10%see below

Edge cases you must handle

  • BigInt("0"), and operations whose result is zero
  • leading zeros: BigInt("00123") must equal BigInt("123")
  • negatives: the sign rules for addition, subtraction and multiplication
  • operands of different lengths
  • subtraction producing a negative: BigInt("5") - BigInt("8")

The report

At most one page, answering three questions:

  1. What is your internal representation? One digit per slot, or several? Why that choice?
  2. How did you handle signs? Stored separately, or per digit? How did you work out the sign rules for addition and subtraction?
  3. How did you test it? Name the specific edge cases you used, and say whether you cross-checked against Python’s int on random inputs.

Before you submit

  • Everything in 10-1 to 10-4 is annotated
  • 10-3’s Money(0.1) + Money(0.2) == Money(0.3) is True
  • 10-4’s for card in deck works
  • 10-5 verified that self-tests do not run on import
  • Project B: all five categories of edge case tested
  • Project B: the report answers all three questions
  • AI usage declaration written