last week's exercises / katas

This commit is contained in:
Michael Schären 2026-05-30 12:49:43 +02:00
parent eb6a242257
commit aea0c0173d
8 changed files with 376 additions and 4 deletions

View File

@ -2,7 +2,6 @@ import typing
class Router(object):
def __init__(self):
self.routes = {}

303
src/codewars/chemist.py Normal file
View File

@ -0,0 +1,303 @@
PERIODIC_TABLE: dict[str, tuple[int, float | int]] = {
"H": (1, 1.0),
"B": (3, 10.8),
"C": (4, 12.0),
"N": (3, 14.0),
"O": (2, 16.0),
"F": (1, 19.0),
"Mg": (2, 24.3),
"P": (3, 31.0),
"S": (2, 32.1),
"Cl": (1, 35.5),
"Br": (1, 80.0),
}
class LockedMolecule(Exception):
pass
class UnlockedMolecule(Exception):
pass
class InvalidBond(Exception):
pass
class EmptyMolecule(Exception):
pass
class Atom(object):
def __init__(self, elt: str, id_: int):
self.element = elt
self.id = id_
self.bounds = []
self.max_bounds = PERIODIC_TABLE[self.element][0]
self.weight = PERIODIC_TABLE[self.element][1]
def __hash__(self):
return self.id
def __eq__(self, other):
return self.id == other.id
def __str__(self):
bounds = []
bounds.extend(
list(sorted(Molecule.filter_element(self.bounds, "C"), key=lambda b: b.id))
)
bounds.extend(
list(sorted(Molecule.filter_element(self.bounds, "O"), key=lambda b: b.id))
)
for bound in list(
sorted(sorted(self.bounds, key=lambda b: b.id), key=lambda b: b.element)
):
if bound.element not in ["C", "H", "O"]:
bounds.append(bound)
bounds.extend(
list(sorted(Molecule.filter_element(self.bounds, "H"), key=lambda b: b.id))
)
return (
f"Atom({self.element}.{self.id}"
f"{': ' if bounds else ''}"
f"{','.join(bound.element + (str(bound.id) if bound.element != 'H' else '') for bound in bounds)})"
)
def add_bound(self, atom):
if self == atom:
raise InvalidBond
if len(self.bounds) >= self.max_bounds:
raise InvalidBond
self.bounds.append(atom)
@staticmethod
def remove_bounds(atom1, atom2):
if not (atom1 or atom2):
return
if atom1 in atom2.bounds:
atom2.bounds.remove(atom1)
if atom2 in atom1.bounds:
atom1.bounds.remove(atom2)
def mutate(self, element: str):
if len(self.bounds) > PERIODIC_TABLE[element][0]:
raise InvalidBond
self.element = element
self.max_bounds = PERIODIC_TABLE[self.element][0]
self.weight = PERIODIC_TABLE[self.element][1]
def unlock(self):
self.bounds = list(filter(lambda atom: atom.element != "H", self.bounds))
class Molecule(object):
name: str
_formula: str
_molecular_weight: float
atoms: list[Atom]
branches: dict[int, list[Atom]]
locked: bool = False
exception_thrown = False
def __init__(self, name=""):
self.name = name
self.atoms = []
self.branches = {}
@staticmethod
def filter_element(atoms: list[Atom], element: str) -> list[Atom]:
return list(filter(lambda atom: element == atom.element, atoms))
def brancher(self, *branches: int):
if self.locked:
raise LockedMolecule
for carbons in branches:
branch_id = len(self.branches) + 1
self.branches[branch_id] = []
for i in range(1, carbons + 1):
atom = Atom(elt="C", id_=len(self.atoms) + i)
self.branches[branch_id].append(atom)
if i > 1:
last_atom = self.branches[branch_id][i - 2]
try:
atom.add_bound(last_atom)
last_atom.add_bound(atom)
except InvalidBond as e:
Atom.remove_bounds(atom, last_atom)
raise e
self.atoms.extend(self.branches[branch_id])
return self
def bounder(self, *bounds: tuple[int, int, int, int]):
if self.locked:
raise LockedMolecule
for c1, b1, c2, b2 in bounds:
atom1 = self.branches[b1][c1 - 1]
atom2 = self.branches[b2][c2 - 1]
atom1_bounds = atom1.bounds[:]
atom2_bounds = atom2.bounds[:]
try:
atom1.add_bound(atom2)
atom2.add_bound(atom1)
except InvalidBond as e:
atom1.bounds = atom1_bounds
atom2.bounds = atom2_bounds
raise e
return self
def mutate(self, *mutations: tuple[int, int, str]):
if self.locked:
raise LockedMolecule
for nc, nb, element in mutations:
atom = self.branches[nb][nc - 1]
try:
atom.mutate(element)
except InvalidBond as e:
raise e
return self
def add(self, *additions: tuple[int, int, str]):
if self.locked:
raise LockedMolecule
for nc, nb, element in additions:
atom = Atom(elt=element, id_=len(self.atoms) + 1)
carbon = self.branches[nb][nc - 1]
try:
carbon.add_bound(atom)
atom.add_bound(carbon)
except InvalidBond as e:
Atom.remove_bounds(carbon, atom)
raise e
self.atoms.append(atom)
return self
def add_chaining(self, nc: int, nb: int, *elements: str):
if self.locked:
raise LockedMolecule
carbon = self.branches[nb][nc - 1]
chain = []
try:
last_atom = Atom(elt=elements[0], id_=len(self.atoms) + len(chain) + 1)
chain.append(last_atom)
for element in elements[1:]:
atom = Atom(elt=element, id_=len(self.atoms) + len(chain) + 1)
chain.append(atom)
last_atom.add_bound(atom)
atom.add_bound(last_atom)
last_atom = atom
except InvalidBond as e:
Atom.remove_bounds(atom, last_atom)
raise e
try:
carbon.add_bound(chain[0])
chain[0].add_bound(carbon)
except InvalidBond as e:
Atom.remove_bounds(carbon, chain[0])
raise e
self.atoms.extend(chain)
return self
def closer(self):
if self.locked:
raise LockedMolecule
try:
for atom in self.atoms:
while len(atom.bounds) < atom.max_bounds:
hydrogen = Atom(elt="H", id_=len(self.atoms) + 1)
atom.add_bound(hydrogen)
hydrogen.add_bound(atom)
self.atoms.append(hydrogen)
except InvalidBond as e:
Atom.remove_bounds(atom, hydrogen)
raise e
self.locked = True
return self
def unlock(self):
self.locked = False
self.atoms = list(filter(lambda atom: atom.element != "H", self.atoms))
for new_id, atom in enumerate(self.atoms, start=1):
atom.unlock()
atom.id = new_id
new_branches = {}
new_id = 1
for atoms in self.branches.values():
filtered = list(filter(lambda atom: atom.element != "H", atoms))
if len(filtered) > 0:
new_branches[new_id] = filtered
new_id += 1
self.branches = new_branches
if not self.branches:
raise EmptyMolecule
return self
@property
def formula(self) -> str:
if not self.locked:
raise UnlockedMolecule
elements = set(atom.element for atom in self.atoms)
formula = ""
if "C" in elements:
c_len = len(Molecule.filter_element(self.atoms, "C"))
formula += f"C{c_len if c_len > 1 else ''}"
if "H" in elements:
h_len = len(Molecule.filter_element(self.atoms, "H"))
formula += f"H{h_len if h_len > 1 else ''}"
if "O" in elements:
o_len = len(Molecule.filter_element(self.atoms, "O"))
formula += f"O{o_len if o_len > 1 else ''}"
for element in list(sorted(elements)):
if element not in ["C", "H", "O"]:
num = len(Molecule.filter_element(self.atoms, element))
formula += f"{element}{num if num > 1 else ''}"
return formula
@property
def molecular_weight(self) -> float:
if not self.locked:
raise UnlockedMolecule
return sum(atom.weight for atom in self.atoms)

View File

@ -0,0 +1,32 @@
class VigenereCipher(object):
def __init__(self, key: str, alphabet: str):
self.key = key
self.alphabet = alphabet
def encode(self, text):
encoded_text = ""
for idx, c in enumerate(text):
alph_position = self.alphabet.find(c)
if alph_position == -1:
encoded_text += c
continue
shift = self.alphabet.find(self.key[idx % len(self.key)])
encoded_text += self.alphabet[(alph_position + shift) % len(self.alphabet)]
return encoded_text
def decode(self, text):
encoded_text = ""
for idx, c in enumerate(text):
alph_position = self.alphabet.find(c)
if alph_position == -1:
encoded_text += c
continue
shift = self.alphabet.find(self.key[idx % len(self.key)])
encoded_text += self.alphabet[(alph_position - shift) % len(self.alphabet)]
return encoded_text

View File

@ -72,3 +72,5 @@ print(dog2.bark(3))
print(dog2.is_puppy())
print(Dog.amount_of_dogs)
print(dog2 == dog1)
print("is a dog", isinstance(dog1, Dog))

View File

@ -0,0 +1,39 @@
from dataclasses import dataclass
from typing import Protocol
@dataclass
class CodeWarsUser:
username: str
class UserFetcher(Protocol):
@staticmethod
def get_user(username: str) -> CodeWarsUser:
return CodeWarsUser(username)
class CodewarsAdapter:
@staticmethod
def get_user(username: str) -> CodeWarsUser:
return CodeWarsUser(username)
class FakeAdapter:
@staticmethod
def get_user(username: str) -> CodeWarsUser:
return CodeWarsUser(username)
class WrongAdapter:
pass
fetchers: list = []
fetchers.append(CodewarsAdapter())
fetchers.append(FakeAdapter())
fetchers.append(WrongAdapter())
for fetcher in fetchers:
print(fetcher.get_user("user name"))

View File

@ -208,7 +208,6 @@ def main() -> None:
]
for filename in files:
BASE_DIR = Path(__file__).parent
file_path = BASE_DIR / "data" / filename

View File

@ -23,7 +23,6 @@ def double_integers(data: list[int]) -> list[int]:
if __name__ == "__main__":
data = [1, 2, 3]
result = double_integers(data)
print(result)

View File

@ -27,7 +27,6 @@ def filter_dataframe(df, col, func):
if __name__ == "__main__":
df = pd.DataFrame(
{
"A": list(range(0, 10)),