LMU ☀️ CMSI 3801
LANGUAGES AND AUTOMATA I
HOMEWORK #2 PARTIAL ANSWERS
exercises.py
import re
from operator import itemgetter
from heapq import nlargest
from typing import Tuple, List, Dict, Callable
from cryptography.fernet import Fernet
from dataclasses import dataclass


def change(amount: int) -> Tuple[int, int, int, int]:
    if type(amount) != int:
        raise TypeError('amount must be an integer')
    if amount < 0:
        raise ValueError('amount cannot be negative')
    change_counts = []
    for denomination in (25, 10, 5, 1):
        coins, amount = divmod(amount, denomination)
        change_counts.append(coins)
    return tuple(change_counts)


def stretched(s: str, /) -> str:
    return ''.join(c * (i+1) for (i, c) in enumerate(re.sub(r'\s', '', s)))


def powers(*, base: int, limit: int):
    power = 1
    while power <= limit:
        yield power
        power *= base


def say(word: str=None, /):
    if word is None:
        return ''
    return lambda next=None: word if next is None else say(f'{word} {next}')


def find_first_then_lower(predicate, strings: List[str]) -> str:
    for string in strings:
        if predicate(string):
            return string.lower()
    raise ValueError('No such element')


def top_ten_scorers(statistics: Dict[str, list]) -> List[str]:
    return [
        f'{name}|{ppg:.2f}|{team}' 
        for name, ppg, team in nlargest(10, (
            (name, points/games, team)
            for team, players in statistics.items()
            for name, games, points in players
            if games >= 15), key=itemgetter(1))]


def crypto_functions() -> Tuple[Callable[[bytes], bytes], Callable[[bytes], bytes]]:
    fernet = Fernet(Fernet.generate_key())
    return (
        lambda message: fernet.encrypt(message),
        lambda message: fernet.decrypt(message))


@dataclass(frozen=True)
class Quaternion:
    a: float
    b: float
    c: float
    d: float
    def __add__(self, q):
        return Quaternion(self.a + q.a, self.b + q.b, self.c + q.c, self.d + q.d)
    def __mul__(self, q):
        return Quaternion(
            q.a * self.a - q.b * self.b - q.c * self.c - q.d * self.d,
            q.a * self.b + q.b * self.a - q.c * self.d + q.d * self.c,
            q.a * self.c + q.b * self.d + q.c * self.a - q.d * self.b,
            q.a * self.d - q.b * self.c + q.c * self.b + q.d * self.a)
    @property
    def coefficients(self):
        return (self.a, self.b, self.c, self.d)