From f55bc8353b1a171a364be1eacc39011262a853ed Mon Sep 17 00:00:00 2001
From: Hong-Phuc Bui <hong-phuc.bui@htwsaar.de>
Date: Mon, 06 Jul 2026 07:55:39 +0200
Subject: [PATCH] Bezier
---
06-bezier-kurve/requirements.txt | 1
kalendar/test_chronos.py | 13 +
06-bezier-kurve/README.md | 29 +++
uebungsblatt-3/.gitignore | 3
.gitignore | 1
06-bezier-kurve/main-nicegui.py | 28 +++
04-module-kalender-bsp/pycalendar.py | 39 ++++
04-module-kalender-bsp/chronos.py | 55 ++++++
06-bezier-kurve/main-canvas.py | 36 ++++
06-bezier-kurve/bezier.py | 43 +++++
kalendar/chronos.py | 29 +++
04-module-kalender-bsp/chronos_test.py | 29 +++
kalendar/main.py | 5
05-oop-polynome/test_polynomial.py | 72 +++++++++
gezeiten/gezeiten.py | 26 +++
05-oop-polynome/.coverage | 0
05-oop-polynome/polynomial.py | 39 ++++
gezeiten/test_gezeiten.py | 9 +
05-oop-polynome/requirements.txt | 7
19 files changed, 464 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
index d9cf955..1d09bb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.pytest_cache
+.venv
__pycache__/
diff --git a/04-module-kalender-bsp/chronos.py b/04-module-kalender-bsp/chronos.py
new file mode 100644
index 0000000..ed82c04
--- /dev/null
+++ b/04-module-kalender-bsp/chronos.py
@@ -0,0 +1,55 @@
+# Module zur Berechnung von Kalendar Datum
+
+def wochentag(d:int, m:int, y:int) -> int:
+ """
+ bereche den Wochentag eines Datum
+
+ Die Wochentage werden wie folgt codiert:
+ So | Mo | Di | Mi | Do | Fr | Sa
+ 0 | 1 | 2 | 3 | 4 | 5 | 6
+
+ @param d: Der Tag im Monat fängt mit 0 an
+ @param m: Der Monat 1 = Januar, 2 = Februa, ... 12 = Dezember
+ @param y: Die Jahresnummer in 4 Ziffer
+
+ @return den Wochentag als eine ganze Zahl
+ """
+ y0 = y - (14 - m) // 12
+ x = y0 + y0//4 - y0//100 + y0//400
+ m0 = m + 12*( (14-m)//12 ) - 2
+ return (d + x + (31*m0)//12) % 7
+
+
+def berechne_ueberhang_tage(wochentag:int) -> int:
+ """Nach Gepflogenheit in deutches Kalender-System fängt eine Woche mit Montag an.
+
+ Mo -> Keinen Überhang;
+ Di -> 1 Überhang;
+ Mi -> 2 Überhange;
+ Do -> 3 Überhange;
+ Fr -> 4 Überhange;
+ Sa -> 5 Überhange;
+ So -> 6 Überhange
+ """
+ return (wochentag - 1) % 7
+
+
+def ist_schaltjahr(y:int) -> bool:
+ """Ein Jahr ist ein Schaltjahr, wenn es
+ * durch 4 teilbar ist,
+ * und nicht durch 100 teilbar,
+ * aber durch 400 teilbar ist.
+ """
+ return (y % 4 == 0) and ( (y%100 != 0) or (y%400 == 0) )
+
+
+def letzter_tag(m: int, y:int) -> int:
+ """Berechne den letztenn Tag eines Monat in einem Jahr
+ """
+ tage = [None, 31, 28, 31, 30,
+ 31, 30, 31, 31,
+ 30, 31, 30, 31]
+ if m != 2: return tage[m]
+ return 29 if ist_schaltjahr(y) else 28
+
+
diff --git a/04-module-kalender-bsp/chronos_test.py b/04-module-kalender-bsp/chronos_test.py
new file mode 100644
index 0000000..859bf70
--- /dev/null
+++ b/04-module-kalender-bsp/chronos_test.py
@@ -0,0 +1,29 @@
+import pytest
+
+from chronos import *
+
+
+def test_ist_schaltjar_einfache():
+ """DRY"""
+ assert ist_schaltjahr(2000) == True
+
+
+@pytest.mark.parametrize("jahr,expected", [
+ (2000, True),
+ (2004, True),
+ (2100, False),
+ (2400, True)
+])
+def test_schaltjahr(jahr, expected):
+ assert ist_schaltjahr(jahr) == expected
+
+
+@pytest.mark.parametrize("d,m,y,expected_d", [
+ (24, 12, 2026, 4), # 24.12.2026 fällt auf einem Donnerstag
+ ( 3, 10, 2026, 6)
+])
+def test_wochentag(d, m, y, expected_d):
+ t = wochentag(d, m, y)
+ assert t == expected_d
+
+
diff --git a/04-module-kalender-bsp/pycalendar.py b/04-module-kalender-bsp/pycalendar.py
new file mode 100644
index 0000000..4942765
--- /dev/null
+++ b/04-module-kalender-bsp/pycalendar.py
@@ -0,0 +1,39 @@
+import sys
+
+from chronos import *
+
+def print_kalender(m: int, y: int):
+ monaten = [None,
+ "Januar", "Februar", "März", "April",
+ "Mai", "Juni", "July", "August",
+ "September", "Oktober", "November", "Dezember"]
+ # Ausgabe des Monat und Jahr
+ print(f"{monaten[m]} {y}")
+ wochentage = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
+ print(" ".join(wochentage))
+
+ # Ausgabe der Überhang von letzen Monat
+ erster_wochentag = wochentag(1, m, y)
+ ueberhang = berechne_ueberhang_tage(erster_wochentag)
+ spalten_breite = len(wochentage[0]) + 1
+ ueberhang_zeichen = " "*(spalten_breite * ueberhang)
+ print(ueberhang_zeichen, end="")
+
+ # Ausgabe des Kalenders
+ spalten_idx = ueberhang
+ tag = 1
+ letzter_tag_im_monat = letzter_tag(m, y)
+ while tag <= letzter_tag_im_monat:
+ print(f"{tag:2d}", end=" ")
+ tag += 1
+ spalten_idx += 1
+ if spalten_idx % 7 == 0: print()
+
+
+if __name__ == "__main__":
+ try:
+ m, y = int(sys.argv[1]), int(sys.argv[2])
+ print_kalender(m, y)
+ print()
+ except:
+ print(f"CLI-Syntax: `python {sys.argv[0]} <monat:int> <jahr:int>`")
diff --git a/05-oop-polynome/.coverage b/05-oop-polynome/.coverage
new file mode 100644
index 0000000..3738c0c
--- /dev/null
+++ b/05-oop-polynome/.coverage
Binary files differ
diff --git a/05-oop-polynome/polynomial.py b/05-oop-polynome/polynomial.py
new file mode 100644
index 0000000..c78bcd5
--- /dev/null
+++ b/05-oop-polynome/polynomial.py
@@ -0,0 +1,39 @@
+import numbers
+
+class Polynomial:
+ def __init__(self, coe):
+ if coe[-1] == 0.0:
+ raise RuntimeError("Coefficient invalid")
+ self._coe = tuple(coe)
+
+ def getExponent(self, n):
+ """
+ :param: n der n-Expoment des Polynom
+ """
+ return self._coe[n]
+
+ def getExponents(self):
+ return tuple(self._coe)
+
+ def eval(self, x:float) -> float:
+ """
+ Den Wert des Polynoms an der Stelle x berechnen
+ """
+ wert = 0
+ for c in self._coe[-1:0:-1]:
+ wert = (wert + c)*x
+ print(c, wert)
+ wert += self._coe[0]
+ return wert
+
+ def __call__(self, value:float) -> float:
+ if isinstance(value, numbers.Number):
+ return self.eval(value)
+ else:
+ raise TypeError("Bad argument, expected a number")
+
+ def __getitem__(self, idx:int) -> float:
+ if idx >= 0 and idx < len(self._coe):
+ return self._coe[idx]
+ else:
+ raise IndexError("idx invalid")
diff --git a/05-oop-polynome/requirements.txt b/05-oop-polynome/requirements.txt
new file mode 100644
index 0000000..33bd0c2
--- /dev/null
+++ b/05-oop-polynome/requirements.txt
@@ -0,0 +1,7 @@
+coverage==7.14.3
+iniconfig==2.3.0
+packaging==26.2
+pluggy==1.6.0
+Pygments==2.20.0
+pytest==9.1.1
+pytest-cov==7.1.0
diff --git a/05-oop-polynome/test_polynomial.py b/05-oop-polynome/test_polynomial.py
new file mode 100644
index 0000000..1302663
--- /dev/null
+++ b/05-oop-polynome/test_polynomial.py
@@ -0,0 +1,72 @@
+from pytest import approx, fail
+
+from polynomial import Polynomial
+
+def test_construct_polynomial_error():
+ coe = [3.5, -4.6, 0] # 3.5 - 4.6x + 0x^2
+ try:
+ p = Polynomial(coe) #
+ except RuntimeError as ex:
+ print(ex)
+ pass
+
+def test_construct_quadratic_polynomial():
+ coe = [3, -4.5, 1.5] # 3 - 4.5x + 1.5x^2
+ p = Polynomial(coe)
+ assert p.getExponent(0) == 3
+ #e = p[0]
+
+def test_get_coefficient():
+ coe = [3, -4.5, 1.5] # 3 - 4.5x + 1.5x^2
+ p = Polynomial(coe)
+ for idx in range(0, len(coe)):
+ assert p[idx] == coe[idx]
+
+def test_get_coefficient_bad_argument():
+ coe = [3, -4.5, 1.5] # 3 - 4.5x + 1.5x^2
+ p = Polynomial(coe)
+ try:
+ p[-1]
+ fail("Expected an exception")
+ except IndexError as ex:
+ pass
+
+
+def test_eval():
+ coe = [3, 2, 1] # 3 + 2x + x^2
+ p = Polynomial(coe)
+ w = p.eval(0) #
+ assert w == 3
+ w = p.eval(1)
+ assert w == 6
+ w = p.eval(-1)
+
+def test_eval_per_operator():
+ coe = [3, 2, 1] # 3 + 2x + x^2
+ p = Polynomial(coe)
+ w = p(-4.5)
+ assert w == approx(14.25)
+
+def test_eval_per_operator_with_str_argument():
+ coe = [3, 2, 1] # 3 + 2x + x^2
+ p = Polynomial(coe)
+ try:
+ w = p("hello world")
+ fail("Bad argument")
+ except TypeError as ex:
+ #assert ex.message == "Bad argument"
+ print(ex)
+
+
+
+def test_approximate_eval():
+ coe = [3, 2, 1] # 3 + 2x + x^2
+ p = Polynomial(coe)
+ w = p.eval(1.25)
+ assert w == approx(7.0625)
+ w = p.eval(1/3)
+ assert w == approx(3.77777777)
+
+
+
+
diff --git a/06-bezier-kurve/README.md b/06-bezier-kurve/README.md
new file mode 100644
index 0000000..067ea78
--- /dev/null
+++ b/06-bezier-kurve/README.md
@@ -0,0 +1,29 @@
+# Demo NiceGUI vs Tk
+
+Eine kleine Demonstration von Plot-Möglichkeiten in NiceGUI via HTML-Canvas und Tk Canvas
+
+## Install
+
+```bash
+python3.13 -m venv .venv
+source .venv/bin/activate
+pip install -r requirements.txt
+```
+
+
+## Run
+
+In beiden Programme kann man Punkten einer (kubischen) Bezier-Kurve mit Mouse-Klicks bestimmen,
+danach wird eine Kurve angezeigt.
+
+### Tk
+
+```bash
+python ./main-canvas.py
+```
+
+### NiceGUI
+
+```bash
+python ./main-nicegui.py
+```
diff --git a/06-bezier-kurve/bezier.py b/06-bezier-kurve/bezier.py
new file mode 100644
index 0000000..26f8eed
--- /dev/null
+++ b/06-bezier-kurve/bezier.py
@@ -0,0 +1,43 @@
+""""
+Module related to bezier curve
+"""
+def make_cubic_bezier(control_points: [(float, float)], n: int = 10) -> [(float, float)]:
+ r""""
+ Interpolate a Bezier-curve by split it into segments of lines (=polyline).
+ A Bezier-Curve is defined by
+
+ \[
+ B(P_0, P_1, P_2, P_3:t) = (1-t)^3*P_0 + 3(1-t)^2*t*P_1 + 3(1-t)*t^2*P_2 + t^3*P_3
+ \]
+
+ for $t \in \[0, 1\]$
+
+ :param control_points
+ :param n number of segments
+ :return a list of points on the Bezier-Curve.
+ """
+
+ p0x = control_points[0][0]
+ p0y = control_points[0][1]
+ p1x = control_points[1][0]
+ p1y = control_points[1][1]
+ p2x = control_points[2][0]
+ p2y = control_points[2][1]
+ p3x = control_points[3][0]
+ p3y = control_points[3][1]
+ points = [(p0x, p0y)]
+ for i in range(1, n):
+ t = i / n
+ u = 1 - t
+ x = p0x * u**3 + 3 * p1x * t* u**2 + 3 * p2x * t**2 * u + p3x * t**3
+ y = p0y * u**3 + 3 * p1y * t* u**2 + 3 * p2y * t**2 * u + p3y * t**3
+ points.append( (x,y) )
+ points.append((p3x, p3y))
+ return points
+
+
+def make_svg_polyline(points) -> str:
+ line = map(lambda p: f"{p[0]},{p[1]}", points)
+ svg = f'<polyline points="{" ".join(line)}" fill="none" stroke="gray" stroke-width="3" />'
+ return svg
+
diff --git a/06-bezier-kurve/main-canvas.py b/06-bezier-kurve/main-canvas.py
new file mode 100644
index 0000000..756cfa8
--- /dev/null
+++ b/06-bezier-kurve/main-canvas.py
@@ -0,0 +1,36 @@
+from tkinter import *
+
+from bezier import make_cubic_bezier
+
+def draw_bezier(canvas, control_points):
+ points = make_cubic_bezier(control_points, n= 400)
+ for i in range(1,len(points)):
+ x1 = points[i-1][0]
+ y1 = points[i-1][1]
+ x2 = points[i][0]
+ y2 = points[i][1]
+ canvas.create_line(x1,y1, x2,y2)
+
+
+def get_point(event):
+ global control_points
+ point = x, y = (event.x, event.y)
+ control_points.append(point)
+ canvas.create_oval(x, y, x+3, y+3)
+ if len(control_points) == 4:
+ draw_bezier(canvas, control_points)
+ control_points = []
+
+
+if __name__ == '__main__':
+
+ control_points = []
+
+ root = Tk()
+
+ canvas = Canvas(root, width=800, height=800)
+ canvas.pack()
+
+ canvas.bind('<Button-1>', get_point)
+
+ root.mainloop()
\ No newline at end of file
diff --git a/06-bezier-kurve/main-nicegui.py b/06-bezier-kurve/main-nicegui.py
new file mode 100644
index 0000000..4a055ea
--- /dev/null
+++ b/06-bezier-kurve/main-nicegui.py
@@ -0,0 +1,28 @@
+from nicegui import ui
+
+from bezier import make_cubic_bezier, make_svg_polyline
+
+control_points = []
+svg = ""
+
+def get_point(event):
+ global control_points
+ global svg
+ x = event.image_x
+ y = event.image_y
+ control_points.append((x,y))
+ svg += f'<circle cx="{x}" cy="{y}" r="1" fill="orange" />'
+ ui.notification(f"(x,y) = ({x},{y})")
+ if len(control_points) == 4:
+ bezier_points = make_cubic_bezier(control_points, n = 50)
+ svg += make_svg_polyline(bezier_points)
+ control_points.clear()
+ event.sender.set_content(svg)
+
+ui.interactive_image(
+ size=(900, 400), cross=True,
+ on_mouse=get_point,
+).classes('w-full bg-blue-50')
+
+ui.run()
+
diff --git a/06-bezier-kurve/requirements.txt b/06-bezier-kurve/requirements.txt
new file mode 100644
index 0000000..e57426a
--- /dev/null
+++ b/06-bezier-kurve/requirements.txt
@@ -0,0 +1 @@
+nicegui==2.18.0
diff --git a/gezeiten/gezeiten.py b/gezeiten/gezeiten.py
new file mode 100644
index 0000000..f661274
--- /dev/null
+++ b/gezeiten/gezeiten.py
@@ -0,0 +1,26 @@
+def parse_dataline(data:str):
+ fields = data.split('#')
+ fields[5] = fields[5].replace(' ', '')
+ return tuple(fields)
+
+def parse_data_file(filename: str):
+ wasser_pegel = []
+ in_data = False
+ with open(filename) as data:
+ line = data.readline()
+ while line is not None:
+ line = line.strip()
+ if (line == "LLL#"):
+ none_data_line = 3
+ while none_data_line > 0:
+ none_data_line -= 1
+ data.readline()
+ in_data = True
+ if in_data:
+ wasser_pegel.append(parse_dataline(line))
+ return wasser_pegel
+
+
+if __name__ == "__main__":
+ wasser_pegel = parse_data_file(sys.argv[1])
+
diff --git a/gezeiten/test_gezeiten.py b/gezeiten/test_gezeiten.py
new file mode 100644
index 0000000..bea0828
--- /dev/null
+++ b/gezeiten/test_gezeiten.py
@@ -0,0 +1,9 @@
+from gezeiten import parse_dataline
+
+def test_parse_dataline():
+ line = "VB2#DE__508P# #H#Di# 1. 1.2019# 0:00# 6.87 # # 1#+ 1:00# 24348# 1#2458484.458495#"
+ data = parse_dataline(line)
+ assert data[0] == "VB2"
+ assert data[1] == "DE__508P"
+ assert data[5] == "1.1.2019"
+
diff --git a/kalendar/chronos.py b/kalendar/chronos.py
new file mode 100644
index 0000000..952362c
--- /dev/null
+++ b/kalendar/chronos.py
@@ -0,0 +1,29 @@
+
+def ist_schaltjahr(y:int) -> bool:
+ """Ein Jahr ist ein Schaltjahr, wenn es
+ * durch 4 teilbar ist,
+ * und nicht durch 100 teilbar,
+ * aber durch 400 teilbar ist.
+ """
+ return (y % 4 == 0) and ( (y%100 != 0) or (y%400 == 0) )
+
+
+def berechne_ueberhang_tage(wochentag:int) -> int:
+ """Nach Gepflogenheit in deutches Kalender-System fängt eine Woche mit Montag an.
+
+ Mo -> Keinen Überhang;
+ Di -> 1 Überhang;
+ Mi -> 2 Überhange;
+ Do -> 3 Überhange;
+ Fr -> 4 Überhange;
+ Sa -> 5 Überhange;
+ So -> 6 Überhange
+ """
+ #return (wochentag - 1) % 7
+ return (wochentag - 2) % 7
+
+
+
+
+
+
diff --git a/kalendar/main.py b/kalendar/main.py
new file mode 100644
index 0000000..895fdde
--- /dev/null
+++ b/kalendar/main.py
@@ -0,0 +1,5 @@
+import chronos
+
+jahr = 2024
+schaltjahr = chronos.ist_schaltjahr(jahr)
+print(schaltjahr)
\ No newline at end of file
diff --git a/kalendar/test_chronos.py b/kalendar/test_chronos.py
new file mode 100644
index 0000000..c9f52a4
--- /dev/null
+++ b/kalendar/test_chronos.py
@@ -0,0 +1,13 @@
+from chronos import ist_schaltjahr, berechne_ueberhang_tage
+
+def test_ist_schaltjahr():
+ assert ist_schaltjahr(2000) , "2000 ist schaltjahr"
+ assert not ist_schaltjahr(2025) , "2025 ist kein schaltjahr"
+
+
+def test_berechne_ueberhang_tage():
+ assert berechne_ueberhang_tage(1) == 0, "Keinen Überhang Platz bei Montag"
+ assert berechne_ueberhang_tage(0) == 6, "6 Überhang Plätze bei Sonntag"
+
+
+
diff --git a/uebungsblatt-3/.gitignore b/uebungsblatt-3/.gitignore
new file mode 100644
index 0000000..788c8a1
--- /dev/null
+++ b/uebungsblatt-3/.gitignore
@@ -0,0 +1,3 @@
+gezeiten-bsh.py
+
+
--
Gitblit v1.10.0