Compare commits

..

No commits in common. "6b28a6ab1d4407e563be5578e8cb49a6282e4a1b" and "f0af4d7dd223c4fe74d91cb36f0ad75b6bc5448c" have entirely different histories.

56 changed files with 17 additions and 18479 deletions

View File

@ -15,9 +15,9 @@ Repository for CDS-2020 Programming and Promt Engineering II
|build|Build-System, Dependencies|build: update requirements.txt|
# Codewars
|Title|Source (src/codewars/)|Test (test/test_codewars/)|URL|
|Title|Source (src/codewars/)|Test (test/codewars/)|URL|
|-|-|-|-|
|Find the force of gravity between two objects|kata_force_of_gravity.py|test_force_of_gravity.py|[5b609ebc8f47bd595e000627](https://www.codewars.com/kata/5b609ebc8f47bd595e000627)|
|Find the force of gravity between two objects|kata_force_of_gravity.py|test_force_of_gravity.py|[5b609ebc8f47bd595e000627](https://www.codewars.com/kata/5b609ebc8f47bd595e000627/)|
|The Lamp: Revisited|kata_the_lamp.py|test_the_lamp.py|[570e6e32de4dc8a8340016dd](https://www.codewars.com/kata/570e6e32de4dc8a8340016dd)|
|OOP: Object Oriented Piracy|kata_object_oriented_piracy.py|test_object_oriented_piracy.py|[54fe05c4762e2e3047000add](https://www.codewars.com/kata/54fe05c4762e2e3047000add)|
|Vigenère Cipher Helper|kata_vigenere_cipher_helper.py|test_vigenere_cipher_helper.py|[52d1bd3694d26f8d6e0000d3](https://www.codewars.com/kata/52d1bd3694d26f8d6e0000d3)|
@ -26,8 +26,3 @@ Repository for CDS-2020 Programming and Promt Engineering II
|Thinkful - Object Drills: Quarks|kata_thinkful_quarks.py|test_thinkful_quarks.py|[5882b052bdeafec15e0000e6](https://www.codewars.com/kata/5882b052bdeafec15e0000e6)|
|Thinkful - Object Drills: Vectors|kata_thinkful_vectors.py|test_thinkful_vectors.py|[587f1e1f39d444cee6000ad4](https://www.codewars.com/kata/587f1e1f39d444cee6000ad4)|
|Building blocks|kata_building_blocks.py|test_building_blocks.py|[55b75fcf67e558d3750000a3](https://www.codewars.com/kata/55b75fcf67e558d3750000a3)|
|PaginationHelper|kata_pagination_helper.py|test_pagination_helper.py|[515bb423de843ea99400000a](https://www.codewars.com/kata/515bb423de843ea99400000a)|
|Who has the most money?|kata_who_the_most_money.py|test_who_the_most_money.py|[528d36d7cc451cd7e4000339](https://www.codewars.com/kata/528d36d7cc451cd7e4000339)|
|Next bigger number with the same digits|kata_next_bigger_number_same_digits.py|test_next_bigger_number_same_digits.py|[55983863da40caa2c900004e](https://www.codewars.com/kata/55983863da40caa2c900004e)|
|Snail|kata_snail.py|test_snail.py|[521c2db8ddc89b9b7a0000c1](https://www.codewars.com/kata/521c2db8ddc89b9b7a0000c1)|
|Chinese Numeral Encoder|kata_chinese_numeral_encoder.py|test_chinese_numeral_encoder.py|[52608f5345d4a19bed000b31](https://www.codewars.com/kata/52608f5345d4a19bed000b31)|

View File

@ -1,24 +0,0 @@
class Block:
def __init__(self, dimensions: list):
self.width = dimensions[0]
self.length = dimensions[1]
self.height = dimensions[2]
def get_width(self):
return self.width
def get_length(self):
return self.length
def get_height(self):
return self.height
def get_volume(self):
return self.width * self.length * self.height
def get_surface_area(self):
return (
2 * self.length * self.height
+ 2 * self.width * self.height
+ 2 * self.width * self.length
)

View File

@ -1,109 +0,0 @@
numerals = {
"-": "",
".": "",
0: "",
1: "",
2: "",
3: "",
4: "",
5: "",
6: "",
7: "",
8: "",
9: "",
10: "",
100: "",
1000: "",
10000: "",
}
# "Ten^x" dictonary
T = {1: 0, 2: 10, 3: 100, 4: 1000, 5: 10000}
def to_chinese_numeral(n):
# Check if negative, turn positive and remeber state
if n < 0:
n = abs(n)
is_negative = True
else:
is_negative = False
# return numerals[n] if n in numerals dictonary
if n % 100 != 0:
try:
if is_negative:
return "".join([numerals["-"], numerals[n]])
else:
return numerals[n]
except KeyError:
pass
# Split number in iteger list and fractional (None if not exists)
if type(n) is not int:
n = str(n).split(".")
integer = [int(x) for x in n[0]]
fractional = [int(x) for x in n[1]]
else:
integer = [int(x) for x in str(n)]
fractional = None
# Blueprint list will contain building instruction on how to encode the number: 90090 -> [9, 10000, 0, 9, 10]
blueprint = []
blueprint.append(integer[0]) # First digit won't be 0 it can just be appended
blueprint.append(T[len(integer)]) # Length of integer determines power of ten
integer.pop(0) # Pop first digit
# create rest of blueprint by looping rest of integer
for _ in range(0, len(integer)):
# 0 needs no power of ten
if integer[0] == 0:
blueprint.append(0)
else:
blueprint.append(integer[0])
blueprint.append(T[len(integer)])
integer.pop(0)
# Pop last digit in blueprint if it's a 0
for i in range(len(blueprint) - 1, 0, -1):
if blueprint[i] == 0:
blueprint.pop(i)
else:
break
# Remove grouped zeros
was_zero = False
index_to_remove = []
for j, i in enumerate(blueprint):
if i == 0 and not was_zero:
was_zero = True
elif i == 0 and was_zero:
index_to_remove.append(j)
elif i != 0 and was_zero:
was_zero = False
index_to_remove.sort(
reverse=True
) # Reverse indices to avoid index errors when applying pop()
[blueprint.pop(x) for x in index_to_remove]
# remove first item in blueprint if second item in blueprint is between 10 and 19. Skip step if index error
try:
if sum(blueprint[0:2]) == 11:
blueprint.pop(0)
except IndexError:
pass
# Add symbols - if n was negative and . if it was a fraction
if is_negative:
blueprint.insert(0, "-")
if fractional is not None:
blueprint.append(".")
[blueprint.append(x) for x in fractional]
# Apply chinese encoding to blueprint and fractional
encoding = [numerals[x] for x in blueprint]
return "".join(encoding)

View File

@ -1,31 +0,0 @@
def next_bigger(n):
# get number as digits in list
digits = [int(x) for x in list(str(n))]
# find index of pivot. return -1 if there is no pivot, meaning number is alreadig biggest
pivot = -1
for i in range(1, len(digits)):
if digits[-i] > digits[-i - 1]:
pivot = -i - 1
break
if pivot == -1:
return -1
# find the smallest digit to the right that is bigger than the pivot
right = digits[pivot + 1 :]
swap = right.index(min([x for x in right if x > digits[pivot]]))
# swap pivot with found digit
digits[pivot], digits[len(digits) - len(right) + swap] = (
right[swap],
digits[pivot],
)
# sort right side of new swapped pivot and replace it at the end
right = digits[pivot + 1 :]
right.sort()
digits[pivot + 1 :] = right
# return number
return int("".join([str(x) for x in digits]))

View File

@ -1,46 +0,0 @@
class PaginationHelper:
# The constructor takes in an array of items and an integer indicating
# how many items fit within a single page
def __init__(self, collection, items_per_page):
self.collection = collection
self.items_per_page = items_per_page
# returns the number of items within the entire collection
def item_count(self):
return len(self.collection)
# returns the number of pages
def page_count(self):
return int(len(self.collection) / self.items_per_page) + (
len(self.collection) % self.items_per_page > 0
)
# returns the number of items on the given page. page_index is zero based
# this method should return -1 for page_index values that are out of range
def page_item_count(self, page_index):
if page_index < 0 or self.page_count() - 1 < page_index:
return -1
if page_index == 0:
return len(self.collection[: self.items_per_page])
return len(
self.collection[
self.items_per_page * page_index : self.items_per_page * page_index
+ self.items_per_page
]
)
# determines what page an item at the given index is on. Zero based indexes.
# this method should return -1 for item_index values that are out of range
def page_index(self, item_index):
try:
self.collection[item_index]
if item_index < 0:
raise IndexError
except IndexError:
return -1
return item_index // self.items_per_page

View File

@ -1,86 +0,0 @@
def snail(snail_map):
# first list in array is always first. So add it to new list snail and pop it from snail_map
snail = [x for x in snail_map[0]]
snail_map.pop(0)
# transpone list and append first list to snail. Then pop firt item of snail_map. Loop till snail_map is empty
while len(snail_map) > 0:
snail_map = [list(reversed(x)) for x in snail_map]
snail_map = [list(row) for row in zip(*snail_map)]
[snail.append(x) for x in snail_map[0]]
snail_map.pop(0)
return snail
array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
array = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]
print(snail(array))
"""
Konzept:
Ziel: 1 2 3 6 9 8 7 4 5
1 2 3
4 5 6
7 8 9
1 2 3
4 5 6
7 8 9
6 9
5 8
4 7
1 2 3 6 9
5 8
4 7
8 7
5 4
1 2 3 6 9 8 7
5 4
4
5
1 2 3 6 9 8 7 4
5
1 2 3 6 9 8 7 4 5
Ziel: 1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
8 12 16
7 11 15
6 10 14
5 9 13
1 2 3 4 8 12 16
7 11 15
6 10 14
5 9 13
...
"""

View File

@ -1,8 +0,0 @@
class Quark(object):
def __init__(self, color, flavor):
self.color = color
self.flavor = flavor
self.baryon_number = 1 / 3
def interact(self, other) -> None:
self.color, other.color = other.color, self.color

View File

@ -1,7 +0,0 @@
class Vector(object):
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def add(self, other):
return Vector(self.x + other.x, self.y + other.y)

View File

@ -1,47 +1,3 @@
class VersionManager:
def __init__(self, version=None):
if not version:
version = "0.0.1"
parts = version.split(".")[:3]
if not all(p.isdecimal() for p in parts):
raise ValueError("Error occured while parsing version!")
nums = [int(p) for p in parts]
while len(nums) < 3:
nums.append(0)
self.major_v, self.minor_v, self.patch_v = nums
self.history = []
def _save(self):
self.history.append((self.major_v, self.minor_v, self.patch_v))
def major(self):
self._save()
self.major_v += 1
self.minor_v = 0
self.patch_v = 0
return self
def minor(self):
self._save()
self.minor_v += 1
self.patch_v = 0
return self
def patch(self):
self._save()
self.patch_v += 1
return self
def rollback(self):
if not self.history:
raise Exception("Cannot rollback!")
self.major_v, self.minor_v, self.patch_v = self.history.pop()
return self
def release(self):
return f"{self.major_v}.{self.minor_v}.{self.patch_v}"
def __init__(self, version):
pass

View File

@ -1,15 +0,0 @@
class Student:
def __init__(self, name, fives, tens, twenties):
self.name = name
self.fives = fives
self.tens = tens
self.twenties = twenties
def most_money(students):
student_names = [x.name for x in students]
student_money = [x.fives * 5 + x.tens * 10 + x.twenties * 20 for x in students]
if len(set(student_money)) == 1 and len(student_money) > 1:
return "all"
return max(list(zip(student_names, student_money)), key=lambda x: x[1])[0]

View File

@ -1,88 +1,41 @@
from abc import ABC, abstractmethod
class Hund(ABC):
class Hund:
anzahl_hunde = 0
def __init__(self, name: str, alter: int, gewicht: float, bellgeraeusch: str):
def __init__(self, name: str, rasse: str, alter: int, gewicht: float):
self.name = name
self.rasse = rasse
self.alter = alter
self.gewicht = gewicht
self.bellgeraeusch = bellgeraeusch
Hund.anzahl_hunde += 1
def __repr__(self) -> str:
return f"Hund(name={self.name!r}, rasse={self.__class__.__name__}, alter={self.alter}, gewicht={self.gewicht}"
def __repr__(self):
return f"Hund(name={self.name!r}, rasse={self.rasse}, alter={self.alter}, gewicht={self.gewicht}"
def __str__(self) -> str:
return f"{self.name} ist ein {self.alter}-jähriger {self.__class__.__name__}"
def __str__(self):
return f"{self.name} ist ein {self.alter}-jähriger {self.rasse}"
@abstractmethod
def bellen(self, n: int = 1) -> None:
print(" ".join([self.bellgeraeusch] * n))
def bellen(self, n=1) -> int:
print(n * "Woof! ")
def geburtstag(self) -> None:
def geburtstag(self):
self.alter += 1
print(
f"Alles Gute zum Geburtstag, {self.name}! Du bist jetzt {self.alter} Jahre alt."
)
def ist_welpe(self) -> None:
def ist_welpe(self):
if self.alter < 2:
print(f"{self.name} ist ein {self.alter}-jähriger Welpe")
else:
print(f"{self.name} ist ein {self.alter}-jähriger erwachsener Hund")
def __lt__(self, other): # less than: self < other
return self.alter < other.alter
def __le__(self, other): # less equal: self <= other
return self.alter <= other.alter
def __gt__(self, other): # greater than: self > other
return self.alter > other.alter
def __ge__(self, other): # greater equal: self >= other
return self.alter >= other.alter
def __eq__(self, other): # equal: self == other
return self.alter == other.alter
class Pudel(Hund):
def __init__(self, name, alter, gewicht):
super().__init__(name, alter, gewicht, "wau")
def bellen(self, n=1):
super().bellen(n)
class Labrador(Hund):
def __init__(self, name, alter, gewicht):
super().__init__(name, alter, gewicht, "wuff")
def bellen(self, n=1):
super().bellen(n)
class Bulldog(Hund):
def __init__(self, name, alter, gewicht):
super().__init__(name, alter, gewicht, "woff")
def bellen(self, n=1):
super().bellen(n)
hund1 = Labrador(name="Bello", alter=33, gewicht=27)
hund2 = Pudel(name="Dewy", alter=6, gewicht=1)
hund3 = Bulldog(name="Stone", alter=15, gewicht=1000)
hund1 = Hund(name="Bello", rasse="Pudel", alter=99, gewicht=357)
hund2 = Hund(name="Dewy", rasse="Labrador", alter=-6, gewicht=1)
print(repr(hund1))
print(hund2)
hund2.bellen(3)
hund1.bellen(2)
hund1.geburtstag()
hund2.ist_welpe()
hund1.ist_welpe()
hund3.bellen(4)
print(hund3 > hund2)

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +0,0 @@
import json
import logging
from pathlib import Path
logger = logging.getLogger(f"orders.{__name__}") # → "orders.load_files"
# ══════════════════════════════════════════════════════════════════════════════
# JSON-File einlesen
# ══════════════════════════════════════════════════════════════════════════════
def load_orders(path: str | Path) -> list[dict] | None:
"""
Liest eine JSON-Datei mit Bestellungen ein.
Behandelte Fehler
-----------------
UnicodeDecodeError Falsche Kodierung (z. B. Latin-1 statt UTF-8)
json.JSONDecodeError Ungültiges JSON (Syntaxfehler)
Rückgabe
--------
Liste der Bestellungen bei Erfolg, None bei Fehler.
"""
path = Path(path)
logger.info("Lese Datei: %s", path)
try:
with path.open("r", encoding="utf-8") as f:
loaded_file = json.load(f)
logger.info(f"{path} erfolgreich eingelesen")
return loaded_file
except FileNotFoundError:
logger.warning("Datei nicht gefunden: %s", path)
return None
except json.JSONDecodeError:
logger.warning(f"Konnte Datei {path} nicht decodieren")
return None
except UnicodeDecodeError as e:
logger.warning(f"Datei {path} scheint ein falsches Coding zu haben")
logger.debug(
f"UnicodeDecodeError-Details: "
f"encoding={e.encoding}, "
f"reason={e.reason}, "
f"start={e.start}, "
f"end={e.end}, "
f"bad_bytes={hex(e.object[e.start])}"
)
return None

View File

@ -1,45 +0,0 @@
"""
main.py Bestellungen einlesen und validieren
Demonstriert:
- Standard-Logging (logging-Modul) mit FileHandler + StreamHandler
- Sauberes Exception-Handling für UnicodeDecodeError & json.JSONDecodeError
- Eigene Exception-Klasse InvalidOrderError (erbt von ValueError)
"""
from pathlib import Path
from utils import setup_logger_extended
from load_files import load_orders
from validation import process_orders
def main() -> None:
logger = setup_logger_extended("orders")
files = [
"orders_1_valid.json",
"orders_5_non_existing_file.json",
"orders_2_parse_error.json",
"orders_3_encoding_error.json",
"orders_4_invalid_order.json",
]
for filename in files:
BASE_DIR = Path(__file__).parent
file_path = BASE_DIR / "data" / filename
logger.info("=" * 60)
logger.info("Verarbeite: %s", filename)
orders = load_orders(file_path)
if orders is not None:
process_orders(orders)
logger.info("=" * 60)
logger.info(f"Alle {len(files)} Dateien verarbeitet. Details siehe orders.log")
if __name__ == "__main__":
main()

View File

@ -1,225 +0,0 @@
2026-03-23 17:54:26 | INFO | orders | ============================================================
2026-03-23 17:54:26 | INFO | orders | Verarbeite: orders_1_valid.json
2026-03-23 17:54:26 | INFO | orders.load_files | Lese Datei: /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_1_valid.json
2026-03-23 17:54:26 | INFO | orders.load_files | /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_1_valid.json erfolgreich eingelesen
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00001
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00002
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00003
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00004
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00005
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00006
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00007
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00008
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00009
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00010
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00011
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00012
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00013
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00014
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00015
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00016
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00017
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00018
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00019
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00020
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00021
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00022
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00023
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00024
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00025
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00026
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00027
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00028
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00029
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00030
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00031
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00032
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00033
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00034
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00035
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00036
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00037
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00038
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00039
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00040
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00041
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00042
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00043
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00044
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00045
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00046
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00047
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00048
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00049
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00050
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00051
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00052
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00053
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00054
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00055
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00056
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00057
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00058
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00059
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00060
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00061
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00062
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00063
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00064
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00065
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00066
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00067
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00068
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00069
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00070
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00071
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00072
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00073
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00074
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00075
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00076
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00077
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00078
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00079
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00080
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00081
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00082
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00083
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00084
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00085
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00086
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00087
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00088
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00089
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00090
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00091
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00092
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00093
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00094
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00095
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00096
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00097
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00098
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00099
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00100
2026-03-23 17:54:26 | INFO | orders.validation | Validierung abgeschlossen: 100 gültig, 0 ungültig.
2026-03-23 17:54:26 | INFO | orders | ============================================================
2026-03-23 17:54:26 | INFO | orders | Verarbeite: orders_5_non_existing_file.json
2026-03-23 17:54:26 | INFO | orders.load_files | Lese Datei: /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_5_non_existing_file.json
2026-03-23 17:54:26 | WARNING | orders.load_files | Datei nicht gefunden: /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_5_non_existing_file.json
2026-03-23 17:54:26 | INFO | orders | ============================================================
2026-03-23 17:54:26 | INFO | orders | Verarbeite: orders_2_parse_error.json
2026-03-23 17:54:26 | INFO | orders.load_files | Lese Datei: /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_2_parse_error.json
2026-03-23 17:54:26 | WARNING | orders.load_files | Konnte Datei /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_2_parse_error.json nicht decodieren
2026-03-23 17:54:26 | INFO | orders | ============================================================
2026-03-23 17:54:26 | INFO | orders | Verarbeite: orders_3_encoding_error.json
2026-03-23 17:54:26 | INFO | orders.load_files | Lese Datei: /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_3_encoding_error.json
2026-03-23 17:54:26 | WARNING | orders.load_files | Datei /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_3_encoding_error.json scheint ein falsches Coding zu haben
2026-03-23 17:54:26 | DEBUG | orders.load_files | UnicodeDecodeError-Details: encoding=utf-8, reason=invalid start byte, start=118, end=119, bad_bytes=0xfc
2026-03-23 17:54:26 | INFO | orders | ============================================================
2026-03-23 17:54:26 | INFO | orders | Verarbeite: orders_4_invalid_order.json
2026-03-23 17:54:26 | INFO | orders.load_files | Lese Datei: /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_4_invalid_order.json
2026-03-23 17:54:26 | INFO | orders.load_files | /Users/s/workspace/gittea.fhgr.ch/zimmersandro/ppe2/src/tutorial/files_and_path/order/data/orders_4_invalid_order.json erfolgreich eingelesen
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00001
2026-03-23 17:54:26 | WARNING | orders.validation | Ungültige Bestellung — order_id=ORD-00002, feld=qty, wert=-3: Negative Menge (-3) ist nicht erlaubt.
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00003
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00004
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00005
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00006
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00007
2026-03-23 17:54:26 | WARNING | orders.validation | Ungültige Bestellung — order_id=ORD-00008, feld=qty, wert=-4: Negative Menge (-4) ist nicht erlaubt.
2026-03-23 17:54:26 | WARNING | orders.validation | Ungültige Bestellung — order_id=ORD-00009, feld=total_chf, wert=-12.75: Negativer Gesamtbetrag (-12.75 CHF) ist nicht erlaubt.
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00010
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00011
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00012
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00013
2026-03-23 17:54:26 | WARNING | orders.validation | Ungültige Bestellung — order_id=ORD-00014, feld=qty, wert=-2: Negative Menge (-2) ist nicht erlaubt.
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00015
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00016
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00017
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00018
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00019
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00020
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00021
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00022
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00023
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00024
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00025
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00026
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00027
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00028
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00029
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00030
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00031
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00032
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00033
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00034
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00035
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00036
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00037
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00038
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00039
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00040
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00041
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00042
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00043
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00044
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00045
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00046
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00047
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00048
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00049
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00050
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00051
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00052
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00053
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00054
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00055
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00056
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00057
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00058
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00059
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00060
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00061
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00062
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00063
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00064
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00065
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00066
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00067
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00068
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00069
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00070
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00071
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00072
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00073
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00074
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00075
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00076
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00077
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00078
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00079
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00080
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00081
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00082
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00083
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00084
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00085
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00086
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00087
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00088
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00089
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00090
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00091
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00092
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00093
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00094
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00095
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00096
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00097
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00098
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00099
2026-03-23 17:54:26 | DEBUG | orders.validation | OK: ORD-00100
2026-03-23 17:54:26 | INFO | orders.validation | Validierung abgeschlossen: 96 gültig, 4 ungültig.
2026-03-23 17:54:26 | INFO | orders | ============================================================
2026-03-23 17:54:26 | INFO | orders | Alle 5 Dateien verarbeitet. Details siehe orders.log

View File

@ -1,45 +0,0 @@
import sys
import logging
# ══════════════════════════════════════════════════════════════════════════════
# Logging-Konfiguration
# ══════════════════════════════════════════════════════════════════════════════
LOG_FORMAT = "%(asctime)s | %(levelname)-8s | %(name)-20s | %(message)s"
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
def setup_logger_extended(name: str, log_file: str = "orders.log") -> logging.Logger:
"""
Erstellt und konfiguriert einen Logger mit zwei Handlern:
- StreamHandler Ausgabe auf die Konsole (ab INFO)
- FileHandler Ausgabe in eine Log-Datei (ab DEBUG)
"""
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG) # Root-Level: alles durchlassen
# Konsole: INFO und höher
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.INFO)
stream_handler.setFormatter(logging.Formatter(LOG_FORMAT, DATE_FORMAT))
# Log-File: DEBUG und höher (detaillierter)
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(LOG_FORMAT, DATE_FORMAT))
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
return logger
def setup_logger(name: str = None, log_file: str = "orders.log") -> logging.Logger:
logging.basicConfig(
level=logging.INFO,
format="%(levelname)s %(name)s %(message)s",
)
logger = logging.getLogger(__name__)
return logger

View File

@ -1,115 +0,0 @@
import logging
logger = logging.getLogger(f"orders.{__name__}") # → "orders.validation"
# ══════════════════════════════════════════════════════════════════════════════
# Eigene Exception-Klasse
# ══════════════════════════════════════════════════════════════════════════════
class InvalidOrderError(ValueError):
"""
Wird ausgelöst, wenn eine Bestellung ungültige Geschäftsdaten enthält.
Erbt von ValueError, weil es sich um einen inhaltlichen Wertfehler handelt
(kein technisches I/O-Problem).
Attribute
---------
order_id : str
Die ID der fehlerhaften Bestellung.
field : str
Der Name des ungültigen Feldes (z. B. "qty").
value : object
Der tatsächliche (ungültige) Wert.
message : str
Lesbare Fehlerbeschreibung (auch als str(e) verfügbar).
"""
def __init__(self, order_id: str, field: str, value: object, message: str):
self.order_id = order_id
self.field = field
self.value = value
self.message = message
super().__init__(message)
def __str__(self) -> str:
return (
f"InvalidOrderError | order_id={self.order_id!r} "
f"| field={self.field!r} | value={self.value!r} "
f"| {self.message}"
)
# ══════════════════════════════════════════════════════════════════════════════
# Geschäftslogik: Validierung einer einzelnen Bestellung
# ══════════════════════════════════════════════════════════════════════════════
def validate_order(order: dict) -> None:
"""
Prüft eine einzelne Bestellung auf Plausibilität.
Wirft InvalidOrderError, sobald ein Regelverstoß entdeckt wird.
Regeln (erweiterbar):
- qty darf nicht negativ sein
- total_chf darf nicht negativ sein
- order_id und status müssen vorhanden sein
"""
order_id = order.get("order_id", "<unbekannt>")
for item in order.get("items", []):
qty = item.get("qty")
if qty is not None and qty < 0:
raise InvalidOrderError(
order_id=order_id,
field="qty",
value=qty,
message=f"Negative Menge ({qty}) ist nicht erlaubt.",
)
total = order.get("total_chf")
if total is not None and total < 0:
raise InvalidOrderError(
order_id=order_id,
field="total_chf",
value=total,
message=f"Negativer Gesamtbetrag ({total} CHF) ist nicht erlaubt.",
)
if not order.get("order_id"):
raise InvalidOrderError(
order_id="<unbekannt>",
field="order_id",
value=order.get("order_id"),
message="Pflichtfeld 'order_id' fehlt oder ist leer.",
)
# ══════════════════════════════════════════════════════════════════════════════
# Validierungs-Durchlauf über alle Bestellungen
# ══════════════════════════════════════════════════════════════════════════════
def process_orders(orders: list[dict]) -> None:
"""
Iteriert über alle Bestellungen und validiert jede einzelne.
Ungültige Bestellungen werden geloggt und übersprungen (kein Abbruch).
"""
valid_count = 0
invalid_count = 0
for order in orders.get("orders"):
try:
validate_order(order)
valid_count += 1
logger.debug("OK: %s", order.get("order_id"))
except InvalidOrderError as e:
invalid_count += 1
logger.warning(
f"Ungültige Bestellung — order_id={e.order_id}, feld={e.field}, wert={e.value}: {e.message}"
)
logger.info(
f"Validierung abgeschlossen: {valid_count} gültig, {invalid_count} ungültig."
)

View File

@ -1,6 +0,0 @@
from pathlib import Path
base_dir = Path("data")
file_path = base_dir / "raw" / "processed.json"
file_path.mkdir(parents=True, exist_ok=True)

View File

@ -1,33 +0,0 @@
# Ziel dieses Skripts:
#
# 1. Das Produkt von 6 * 7 berechnen → Erwartet: 42.0
# 2. Die Kreisfläche für Radius 5 berechnen → Erwartet: ~78.54
# 3. Den genauen Wert von PI ausgeben → Erwartet: 3.141592653589793
#
# ----------------------------------------------------------------------
# AUFGABE: Das Skript liefert falsche Ergebnisse. Finde den Fehler und
# korrigiere die Import-Anweisungen ohne den restlichen Code
# zu verändern!
# ----------------------------------------------------------------------
# from src.mathematik import berechne, PI # (1) Import aus mathematik
# from src.geometrie import berechne, PI # (2) Import aus geometrie
import src.geometrie as geo
import src.mathematik as mathe
def main():
# --- Aufgabe 1: Produkt berechnen (nutzt mathematik.berechne) ---
produkt = mathe.berechne(6, 7) # Erwartet: 42.0 — bekommt aber einen Fehler!
print(f"6 × 7 = {produkt}")
# --- Aufgabe 2: Kreisfläche (nutzt geometrie.berechne) ---
flaeche = geo.berechne(5) # Erwartet: ~78.54
print(f"Kreisfläche (r=5): {flaeche:.4f}")
# --- Aufgabe 3: PI-Wert ---
print(f"PI = {mathe.PI}") # Erwartet: 3.141592653589793
if __name__ == "__main__":
main()

View File

@ -1,14 +0,0 @@
# geometrie.py
# Modul für geometrische Berechnungen (Kreisflächen, Umfang usw.)
PI = 3.14 # Vereinfachter Wert (nur 2 Nachkommastellen!)
def berechne(radius: float) -> float:
"""Berechnet die Fläche eines Kreises: A = PI * r²"""
return PI * radius**2
def umfang(radius: float) -> float:
"""Berechnet den Umfang eines Kreises: U = 2 * PI * r"""
return 2 * PI * radius

View File

@ -1,14 +0,0 @@
# mathematik.py
# Modul für allgemeine mathematische Berechnungen
PI = 3.141592653589793 # Hochpräziser Wert
def berechne(a: float, b: float) -> float:
"""Berechnet das Produkt zweier Zahlen."""
return a * b
def potenz(basis: float, exponent: int) -> float:
"""Berechnet basis hoch exponent."""
return basis**exponent

View File

@ -1,59 +0,0 @@
# Ziel dieses Skripts:
#
# Testergebnisse einer Klasse auswerten und formatiert ausgeben.
#
# Gegeben: Punkte von 5 Studierenden (von max. 100 Punkten)
# Erwartet:
# Mittelwert → 73.5 (kaufm. gerundet → 74 → "Gut")
# Max. Punkte → 100 (aus statistik.MAX_WERT)
#
# ----------------------------------------------------------------------
# AUFGABE: Das Skript gibt falsche Werte aus. Es gibt keine Fehlermeldung
# vom Interpreter. Finde heraus, warum die Ergebnisse falsch sind
# und korrigiere die Import-Anweisungen!
# ----------------------------------------------------------------------
from src.statistik import (
runde,
mittelwert,
bewerte,
MAX_WERT,
) # importiert: runde, mittelwert, bewerte, MAX_WERT, MIN_WERT
from src.formatierung import (
trennlinie,
) # importiert: runde, als_prozent, trennlinie, MAX_WERT, MIN_WERT
PUNKTE = [92, 85, 61, 48, 82]
def auswertung():
print(trennlinie("="))
print(" TESTERGEBNISSE")
print(trennlinie("="))
# --- Mittelwert berechnen und benoten ---
mw = mittelwert(PUNKTE)
mw_gerundet = runde(mw) # Soll kaufm. auf int runden → Erwartet: 74
note = bewerte(mw)
print(f" Mittelwert : {mw}")
print(f" Gerundet : {mw_gerundet} ← Erwartet: 74 (int)")
print(f" Durchschnittsnote: {note}")
print(trennlinie())
# --- Maximale Punktzahl aus dem Modul ---
print(f" Max. Punkte : {MAX_WERT} ← Erwartet: 100")
print(trennlinie())
# --- Einzelne Ergebnisse ---
print(" Einzelergebnisse:")
for p in PUNKTE:
print(f" {p:>3} Punkte → {bewerte(p)}")
print(trennlinie("="))
if __name__ == "__main__":
auswertung()

View File

@ -1,21 +0,0 @@
# formatierung.py
# Modul für Ausgabe-Formatierung und Darstellung
MAX_WERT = 255 # Maximaler RGB-Farbwert (0255)
MIN_WERT = 0 # Minimaler RGB-Farbwert
def runde(wert: float, stellen: int = 2) -> float:
"""Rundet auf 'stellen' Nachkommastellen (für Anzeige-Formatierung)."""
faktor = 10**stellen
return int(wert * faktor + 0.5) / faktor
def als_prozent(wert: float, gesamt: float) -> str:
"""Gibt einen Wert als formatierten Prozentstring zurück."""
return f"{runde(wert / gesamt * 100)} %"
def trennlinie(zeichen: str = "-", laenge: int = 40) -> str:
"""Erzeugt eine Trennlinie für die Konsolenausgabe."""
return zeichen * laenge

View File

@ -1,30 +0,0 @@
# statistik.py
# Modul für statistische Berechnungen
MAX_WERT = 100 # Maximale Punktzahl in einem Test (0100)
MIN_WERT = 0 # Minimale Punktzahl
def runde(wert: float) -> int:
"""Rundet kaufmännisch auf ganze Zahlen (für Testergebnisse)."""
return int(wert + 0.5)
def mittelwert(werte: list[float]) -> float:
"""Berechnet den arithmetischen Mittelwert einer Liste."""
return sum(werte) / len(werte)
def bewerte(punkte: float) -> str:
"""Gibt eine Note zurück basierend auf Punktzahl (0100)."""
p = runde(punkte)
if p >= 90:
return "Sehr gut"
elif p >= 75:
return "Gut"
elif p >= 60:
return "Befriedigend"
elif p >= 45:
return "Ausreichend"
else:
return "Ungenügend"

View File

@ -1,27 +0,0 @@
# random.py
#
# Dieses Modul wurde angelegt, um eigene Hilfsfunktionen für
# "Zufallsentscheidungen" in der Lotterie-Logik bereitzustellen.
#
# Es definiert einige der gleichen Funktionen wie das Standardmodul
# aber mit vereinfachter (fehlerhafter) Implementierung.
def randint(a: int, b: int) -> int:
"""Gibt eine 'zufällige' Ganzzahl zwischen a und b zurück."""
return a # ← gibt IMMER den kleinsten Wert zurück!
def shuffle(lst: list) -> None:
"""Mischt eine Liste in-place."""
pass # ← tut gar nichts!
def sample(population: list, k: int) -> list:
"""Gibt k zufällige Elemente aus population zurück."""
return list(population)[:k] # ← gibt IMMER die ersten k Elemente zurück!
def seed(a=None):
"""Setzt den Zufallsgenerator-Seed."""
pass # ← keine Wirkung

View File

@ -1,51 +0,0 @@
# Lotterie-Simulation: Zieht 6 aus 45 Zahlen, dazu eine Zusatzzahl.
#
# Wiederholt die Ziehung 5× und gibt die Ergebnisse aus.
# Anschließend wird die Gewinnerliste zufällig gemischt.
#
# Erwartetes Verhalten:
# • Jede Ziehung liefert 6 VERSCHIEDENE, ZUFÄLLIGE Zahlen aus 145
# • Wiederholte Aufrufe liefern UNTERSCHIEDLICHE Ergebnisse
# • Die Teilnehmerliste ist nach dem Mischen in ZUFÄLLIGER Reihenfolge
#
# ----------------------------------------------------------------------
# AUFGABE: Das Programm läuft ohne Fehlermeldung, aber die Ergebnisse
# sind offensichtlich nicht zufällig. Finde die Ursache und
# behebe den Fehler ohne ziehung.py oder statistik.py zu ändern!
#
# REMARK: Möglicherweise ist das Problem sogar abhängig von der verwendeten
# IDE... Lasst das main.py mal aus eurer IDE laufen sowie aus dem
# Terminal. Sind die Resultate gleich?
# ----------------------------------------------------------------------
from ziehung import ziehe_zahlen, ziehe_zusatzzahl
from statistik import simuliere_ziehungen, mische_teilnehmer
TEILNEHMER = ["Alice", "Bob", "Carol", "Dave", "Eve"]
def main():
print("=" * 45)
print(" LOTTO-SIMULATION (6 aus 45)")
print("=" * 45)
print("\n--- 5 unabhängige Ziehungen ---")
for i, ziehung in enumerate(simuliere_ziehungen(5), 1):
print(f" Ziehung {i}: {ziehung}")
print("\n--- Einzelziehung mit Zusatzzahl ---")
haupt = ziehe_zahlen()
zusatz = ziehe_zusatzzahl(haupt)
print(f" Hauptzahlen : {haupt}")
print(f" Zusatzzahl : {zusatz}")
print("\n--- Gewinnerliste (zufällig gemischt) ---")
gemischt = mische_teilnehmer(TEILNEHMER)
for rang, name in enumerate(gemischt, 1):
print(f" Rang {rang}: {name}")
print("=" * 45)
if __name__ == "__main__":
main()

View File

@ -1,20 +0,0 @@
# Wertet mehrere Lottoziehungen statistisch aus
import custom_random
from ziehung import ziehe_zahlen
def simuliere_ziehungen(anzahl: int = 10) -> list[list[int]]:
"""Führt 'anzahl' unabhängige Lottoziehungen durch."""
ergebnisse = []
for _ in range(anzahl):
zahlen = ziehe_zahlen()
ergebnisse.append(zahlen)
return ergebnisse
def mische_teilnehmer(teilnehmer: list[str]) -> list[str]:
"""Mischt die Teilnehmerliste zufällig (z. B. für Gewinner-Reihenfolge)."""
kopie = teilnehmer[:]
custom_random.shuffle(kopie)
return kopie

View File

@ -1,19 +0,0 @@
# lotterie/ziehung.py
# Modul für die eigentliche Lottoziehung
import custom_random
ZAHLEN_POOL = list(range(1, 46)) # Zahlen 145
def ziehe_zahlen(anzahl: int = 6) -> list[int]:
"""Zieht 'anzahl' verschiedene Lottozahlen aus dem Pool."""
gezogen = custom_random.sample(ZAHLEN_POOL, anzahl)
gezogen.sort()
return gezogen
def ziehe_zusatzzahl(ausgeschlossen: list[int]) -> int:
"""Zieht eine Zusatzzahl, die nicht in der Hauptziehung vorkommt."""
pool = [z for z in ZAHLEN_POOL if z not in ausgeschlossen]
return custom_random.randint(1, len(pool) - 1) # ← Index in pool, dann Lookup

View File

@ -1,53 +0,0 @@
# Schulverwaltungs-Simulation:
# • Drei Schüler werden angelegt (mit Noten in zwei Kursen)
# • Zwei Kurse werden erstellt, Schüler werden eingeschrieben
# • Notenspiegel je Schüler und Kursbericht je Kurs wird ausgegeben
#
# ----------------------------------------------------------------------
# AUFGABE: Das Programm lässt sich gar nicht erst starten es gibt
# sofort einen ImportError.
#
# 1. Lies die Fehlermeldung sorgfältig und zeichne den
# Importgraphen auf: Welches Modul importiert welches?
#
# 2. Versuche durch eine geeignete Gegenmassnahme den Fehler zu
# beheben.
# ----------------------------------------------------------------------
from src.kurs import Kurs
from src.schueler import Schueler
def main():
# --- Schüler anlegen ---
anna = Schueler("Anna Meier", {"Mathematik": 2.5, "Deutsch": 3.0})
ben = Schueler("Ben Keller", {"Mathematik": 5.0, "Deutsch": 2.0})
clara = Schueler("Clara Huber", {"Mathematik": 3.5, "Deutsch": 4.5})
# --- Kurse anlegen und Schüler einschreiben ---
mathe = Kurs("Mathematik")
deutsch = Kurs("Deutsch")
for s in [anna, ben, clara]:
mathe.einschreiben(s)
deutsch.einschreiben(s)
# --- Ausgabe Notenspiegel ---
print("=" * 45)
print(" NOTENSPIEGEL")
print("=" * 45)
for s in [anna, ben, clara]:
print(s.notenspiegel())
print()
# --- Ausgabe Kursberichte ---
print("=" * 45)
print(" KURSBERICHTE")
print("=" * 45)
print(mathe.kursbericht())
print(deutsch.kursbericht())
print("=" * 45)
if __name__ == "__main__":
main()

View File

@ -1,16 +0,0 @@
# ---------------------------------------------------------------
# Gemeinsam genutzte Hilfsfunktion wird auch in schueler.py
# über den Import von oben verwendet.
# ---------------------------------------------------------------
def ist_bestanden(note: float) -> bool:
"""Gibt True zurück, wenn die Note ≤ 4.0 (bestanden) ist."""
return note <= 4.0
def berechne_klassendurchschnitt(noten: list[float]) -> float:
"""Berechnet den Notendurchschnitt einer Gruppe."""
if not noten:
return 0.0
return round(sum(noten) / len(noten), 2)

View File

@ -1,35 +0,0 @@
# schulverwaltung/kurs.py
#
# Verwaltet Kurse und eingeschriebene Schüler.
# Benötigt die Schueler-Klasse aus schueler.py für die Einschreibung
# und Notenauswertung.
from notenrechner import ist_bestanden, berechne_klassendurchschnitt
from .schueler import Schueler
class Kurs:
def __init__(self, name: str):
self.name = name
self.schueler: list[Schueler] = []
def einschreiben(self, schueler: Schueler) -> None:
"""Schreibt einen Schüler in diesen Kurs ein."""
self.schueler.append(schueler)
def kursbericht(self) -> str:
"""Gibt eine Übersicht über alle Schüler und den Kursdurchschnitt aus."""
zeilen = [f"\n Kurs: {self.name} ({len(self.schueler)} Schüler)"]
noten_im_kurs = []
for s in self.schueler:
note = s.noten.get(self.name)
if note is not None:
status = "" if ist_bestanden(note) else ""
zeilen.append(f" {status} {s.name:<20} {note:.1f}")
noten_im_kurs.append(note)
if noten_im_kurs:
schnitt = berechne_klassendurchschnitt(noten_im_kurs)
zeilen.append(f" → Klassendurchschnitt: {schnitt:.2f}")
return "\n".join(zeilen)

View File

@ -1,28 +0,0 @@
# Verwaltet Schüler-Daten und prüft den Abschlussstatus.
# Benötigt ist_bestanden() aus kurs.py, um festzustellen ob
# ein Schüler alle Kurse bestanden hat.
from notenrechner import ist_bestanden
class Schueler:
def __init__(self, name: str, noten: dict[str, float]):
"""
name : Vollständiger Name des Schülers
noten : {Kursname: Note} z. B. {'Mathematik': 4.5, 'Deutsch': 3.0}
"""
self.name = name
self.noten = noten
def kann_abschliessen(self) -> bool:
"""Gibt True zurück, wenn alle Kursnoten bestanden sind (Note ≤ 4.0)."""
return all(ist_bestanden(note) for note in self.noten.values())
def notenspiegel(self) -> str:
"""Gibt eine formatierte Übersicht aller Noten zurück."""
zeilen = [f" Schüler: {self.name}"]
for kurs, note in self.noten.items():
status = "" if ist_bestanden(note) else ""
zeilen.append(f" {status} {kurs:<18} Note: {note:.1f}")
zeilen.append(f" → Abschluss möglich: {self.kann_abschliessen()}")
return "\n".join(zeilen)

View File

@ -1,6 +1,2 @@
def discount_price(price: float, percent: float) -> float:
if price < 0:
raise ValueError("Price must be >= 0")
if not 0 <= percent <= 100:
raise ValueError("Percent must be between 0 and 100")
return price - price * percent / 100

View File

@ -1,17 +0,0 @@
from src.codewars.kata_building_blocks import Block
def test_block():
b1 = Block([2, 2, 2])
b2 = Block([41, 87, 67])
assert b1.get_width() == 2
assert b1.get_length() == 2
assert b1.get_height() == 2
assert b1.get_volume() == 8
assert b1.get_surface_area() == 24
assert b2.get_width() == 41
assert b2.get_length() == 87
assert b2.get_height() == 67
assert b2.get_volume() == 238989
assert b2.get_surface_area() == 24286

View File

@ -1,8 +0,0 @@
from src.codewars.kata_ceasar_cipher_helper import CaesarCipher
def test_cipher_helper():
c = CaesarCipher(5)
assert c.encode("Codewars") == "HTIJBFWX"
assert c.decode("HTIJBFWX") == "CODEWARS"

View File

@ -1,15 +0,0 @@
from src.codewars.kata_chinese_numeral_encoder import to_chinese_numeral
def test_to_chinese():
assert to_chinese_numeral(9) == ""
assert to_chinese_numeral(-5) == "负五"
assert to_chinese_numeral(0.5) == "零点五"
assert to_chinese_numeral(10) == ""
assert to_chinese_numeral(110) == "一百一十"
assert to_chinese_numeral(111) == "一百一十一"
assert to_chinese_numeral(1000) == "一千"
assert to_chinese_numeral(10000) == "一万"
assert to_chinese_numeral(10006) == "一万零六"
assert to_chinese_numeral(10306.005) == "一万零三百零六点零零五"
assert to_chinese_numeral(-10.000001) == "负十点零零零零零一"

View File

@ -1,7 +0,0 @@
from src.codewars.kata_force_of_gravity import solution
def test_a():
assert solution([1000, 1000, 100], ["g", "kg", "m"]) == 6.67e-12
assert solution([1000, 1000, 100], ["kg", "kg", "m"]) == 6.6699999999999995e-09
assert solution([1000, 1000, 100], ["kg", "kg", "cm"]) == 0.0000667

View File

@ -1,15 +0,0 @@
from src.codewars.kata_next_bigger_number_same_digits import next_bigger
def test_next_bigger():
assert next_bigger(12) == 21
assert next_bigger(21) == -1
assert next_bigger(513) == 531
assert next_bigger(2017) == 2071
assert next_bigger(414) == 441
assert next_bigger(144) == 414
assert next_bigger(1234567890) == 1234567908
assert next_bigger(59884848459853) == 59884848483559
assert next_bigger(7600201336) == 7600201363
assert next_bigger(5113455566888) == 5113455568688
assert next_bigger(4769560370633) == 4769560373036

View File

@ -1,15 +0,0 @@
from src.codewars.kata_object_oriented_piracy import Ship
def test_piracy():
empty_ship = Ship(0, 0)
assert not empty_ship.is_worth_it()
boat = Ship(15, 20)
assert not boat.is_worth_it()
worthy_ship = Ship(100, 20)
assert worthy_ship.is_worth_it()
big_boat = Ship(35, 20)
assert not big_boat.is_worth_it()

View File

@ -1,27 +0,0 @@
from src.codewars.kata_pagination_helper import PaginationHelper
def test_pagination():
collection = ["a", "b", "c", "d", "e", "f"]
helper = PaginationHelper(collection, 4)
assert helper.page_count() == 2
assert helper.item_count() == 6
assert helper.page_item_count(0) == 4
assert helper.page_item_count(1) == 2
assert helper.page_item_count(2) == -1
assert helper.page_index(5) == 1
assert helper.page_index(2) == 0
assert helper.page_index(20) == -1
assert helper.page_index(-10) == -1
empty = PaginationHelper([], 10)
assert empty.item_count() == 0
assert empty.page_count() == 0
assert empty.page_index(0) == -1
assert empty.page_index(1) == -1
assert empty.page_index(-1) == -1
assert empty.page_item_count(0) == -1
assert empty.page_item_count(1) == -1
assert empty.page_item_count(-1) == -1

View File

@ -1,10 +0,0 @@
from src.codewars.kata_snail import snail
def test_snail():
array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
assert snail(array) == [1, 2, 3, 6, 9, 8, 7, 4, 5]
array = [[1, 2, 3], [8, 9, 4], [7, 6, 5]]
assert snail(array) == [1, 2, 3, 4, 5, 6, 7, 8, 9]

View File

@ -1,13 +0,0 @@
from src.codewars.kata_the_lamp import Lamp
def test_lamp():
my_lamp = Lamp("Blue")
assert my_lamp.color == "Blue"
assert not my_lamp.on
assert my_lamp.state() == "The lamp is off."
my_lamp.toggle_switch()
assert my_lamp.state() == "The lamp is on."
my_lamp.toggle_switch()
assert my_lamp.state() == "The lamp is off."

View File

@ -1,15 +0,0 @@
from src.codewars.kata_thinkful_quarks import Quark
def test_quark():
q1 = Quark("red", "up")
q2 = Quark("blue", "strange")
assert q1.color == "red"
assert q2.flavor == "strange"
assert q2.baryon_number == 1 / 3
q1.interact(q2)
assert q1.color == "blue"
assert q2.color == "red"

View File

@ -1,13 +0,0 @@
from src.codewars.kata_thinkful_vectors import Vector
def test_vector():
v1 = Vector(3, 4)
v2 = Vector(1, 2)
assert v1.x == 3
assert v2.y == 2
assert hasattr(v1, "add")
assert isinstance(v1.add(v2), Vector)
assert (v1.add(v2)).x == 4
assert (v1.add(v2)).y == 6

View File

@ -1,7 +0,0 @@
from src.codewars.kata_version_mamanger import VersionManager
def test_lamp():
v = VersionManager("1.1.1")
assert v.release() == "1.1.1"

View File

@ -1,16 +0,0 @@
from src.codewars.kata_vigenere_cipher_helper import VigenereCipher
def test_cipher_helper():
abc = "abcdefghijklmnopqrstuvwxyz"
key = "password"
c = VigenereCipher(key, abc)
assert c.encode("codewars") == "rovwsoiv"
assert c.decode("rovwsoiv") == "codewars"
assert c.encode("waffles") == "laxxhsj"
assert c.decode("laxxhsj") == "waffles"
assert c.encode("CODEWARS") == "CODEWARS"
assert c.decode("CODEWARS") == "CODEWARS"

View File

@ -1,15 +0,0 @@
from src.codewars.kata_who_the_most_money import Student, most_money
def test_most_money():
phil = Student("Phil", 2, 2, 1)
cam = Student("Cameron", 2, 2, 0)
geoff = Student("Geoff", 0, 3, 0)
assert most_money([cam, geoff, phil]) == "Phil"
phil = Student("Phil", 2, 2, 2)
cam = Student("Cameron", 2, 2, 2)
geoff = Student("Geoff", 2, 2, 2)
assert most_money([cam, geoff, phil]) == "all"

View File

@ -1,21 +1,6 @@
from src.tutorial.testing.shop.pricing import discount_price
import pytest
def test_discount_price_reduces_price():
result = discount_price(100.0, 20.0)
assert result == 80
assert discount_price(100.0, 50.0) == 50
assert discount_price(100.0, 0) == 100
assert discount_price(100.0, 100) == 0
def test_negative_orice_raises_value_error() -> None:
with pytest.raises(ValueError):
discount_price(-100.0, 50.0) is ValueError
def test_percent_above_hundered_message() -> None:
with pytest.raises(ValueError, match="between 0 and 100"):
discount_price(100.0, 150.0)