"""Exercise 6 - translate between Ada's notation and ours (CS extension). Optional. Unit 8, CS path. YOUR TASK --------- Note G numbers only the Bernoulli numbers that are not zero. Lovelace's ``B1`` is the modern ``B2``; her ``B7`` -- the one Note G actually computes -- is the modern ``B8``. Implement ``ada_bernoulli(ada_index)`` so it returns the exact value Lovelace would have written under that label, and rejects indices that have no Note G label at all. ada_bernoulli(1) -> Fraction(1, 6) ada_bernoulli(7) -> Fraction(-1, 30) ada_bernoulli(2) -> raises ValueError (she has no B2) ada_bernoulli(0) -> raises ValueError You may use ``reference_bernoulli.bernoulli`` for the values. The exercise is the index translation, not the arithmetic. WHY THIS IS THE POINT --------------------- This is the smallest possible version of a real and common software problem: two systems that use the same symbol for different things. The bug it produces is invisible in code review -- both sides look right -- and appears only when you compare against a reference that states which convention it is using. RUN IT ------ python3 exercises/ex06_ada_translation.py """ from __future__ import annotations import sys from fractions import Fraction from pathlib import Path LAB_DIR = Path(__file__).resolve().parent.parent sys.path.insert(0, str(LAB_DIR)) from reference_bernoulli import bernoulli # noqa: E402 def ada_bernoulli(ada_index: int) -> Fraction: """Return the value Lovelace labels B, in exact arithmetic.""" # TODO: implement the index translation, then look up the modern value. raise NotImplementedError("Write the translation, then run this file.") # --------------------------------------------------------------------------- # Self-grading. Nothing below needs changing. # --------------------------------------------------------------------------- VALID = {1: Fraction(1, 6), 3: Fraction(-1, 30), 5: Fraction(1, 42), 7: Fraction(-1, 30)} INVALID = [0, 2, 4, -1] def _main() -> int: try: ada_bernoulli(1) except NotImplementedError as exc: print(exc) return 1 except Exception: # noqa: BLE001 - keep going, the report below is the point pass problems = [] print("Checking your translation against the Note G mapping.") print("-" * 62) for ada_index, expected in sorted(VALID.items()): try: got = ada_bernoulli(ada_index) except Exception as exc: # noqa: BLE001 print(f" ERROR Ada B{ada_index}: raised {type(exc).__name__}: {exc}") problems.append(f"B{ada_index} raised") continue if got == expected and isinstance(got, Fraction): print(f" ok Ada B{ada_index} = {got}") elif got == expected: print(f" TYPE Ada B{ada_index} = {got} but is {type(got).__name__}, not Fraction") problems.append(f"B{ada_index} wrong type") else: print(f" WRONG Ada B{ada_index} = {got}, expected {expected}") problems.append(f"B{ada_index} wrong value") for ada_index in INVALID: try: got = ada_bernoulli(ada_index) except ValueError: print(f" ok Ada B{ada_index} correctly rejected") continue except Exception as exc: # noqa: BLE001 print(f" WEAK Ada B{ada_index} raised {type(exc).__name__}, expected ValueError") problems.append(f"B{ada_index} wrong exception") continue print(f" WRONG Ada B{ada_index} returned {got}; she has no B{ada_index}") problems.append(f"B{ada_index} not rejected") print("-" * 62) if not problems: print("PASS - the translation is right in both directions.") print() print("Note that rejecting Ada B2 mattered as much as computing Ada B7. A") print("translation layer that silently accepts nonsense from the other side") print("is how convention bugs travel.") return 0 print(f"Not yet - {len(problems)} problem(s): {', '.join(problems)}") print("Her labels run B1, B3, B5, B7 and the modern ones run B2, B4, B6, B8.") return 1 if __name__ == "__main__": raise SystemExit(_main())