From 6fcb23fa685eec6445287c8db19a3edbf89db2c8 Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Fri, 19 Dec 2025 16:00:09 +0100 Subject: [PATCH 01/14] add Problem "linkedAfter" --- linkedAfter/.cs50.yml | 10 ++ linkedAfter/checks/__init__.py | 0 linkedAfter/checks/main.py | 147 +++++++++++++++++++++ linkedAfter/html/linkedAfter.html | 204 ++++++++++++++++++++++++++++++ linkedAfter/html/params.json | 9 ++ linkedAfter/html/template.html | 112 ++++++++++++++++ 6 files changed, 482 insertions(+) create mode 100644 linkedAfter/.cs50.yml create mode 100644 linkedAfter/checks/__init__.py create mode 100644 linkedAfter/checks/main.py create mode 100644 linkedAfter/html/linkedAfter.html create mode 100644 linkedAfter/html/params.json create mode 100644 linkedAfter/html/template.html diff --git a/linkedAfter/.cs50.yml b/linkedAfter/.cs50.yml new file mode 100644 index 0000000..39c54b8 --- /dev/null +++ b/linkedAfter/.cs50.yml @@ -0,0 +1,10 @@ +submit50: + files: &submit50_files + - !exclude "*" + - !include "*.py" + - !require linkedAfter.py + +check50: + files: *submit50_files + checks: checks/main.py + \ No newline at end of file diff --git a/linkedAfter/checks/__init__.py b/linkedAfter/checks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/linkedAfter/checks/main.py b/linkedAfter/checks/main.py new file mode 100644 index 0000000..f686cc7 --- /dev/null +++ b/linkedAfter/checks/main.py @@ -0,0 +1,147 @@ +import check50 +import check50.py + +FILE_NAME = "linkedAfter.py" + + +def to_list(ll): + """LinkedList -> Python-Liste (mit Zyklus-Schutz).""" + out = [] + cur = ll.head + seen = 0 + while cur is not None: + out.append(cur.value) + cur = cur.next + seen += 1 + if seen > 10_000: + msg = f"Linked list appears to have a cycle (more than {seen} nodes)" + raise check50.Failure(msg) + return out + + +def build_ll(module, values): + """Erzeugt eine LinkedList über FromPythonlist (wie in der Aufgabe vorgegeben).""" + ll = module.LinkedList() + ll.FromPythonlist(values) + return ll + + +@check50.check() +def exists(): + """linkedAfter.py exists""" + check50.exists(FILE_NAME) + + +@check50.check(exists) +def compiles(): + """linkedAfter.py compiles""" + check50.py.compile(FILE_NAME) + + +@check50.check(compiles) +def has_classes_and_method(): + """ListNode, LinkedList and insertAfterNode exist""" + module = check50.py.import_(FILE_NAME) + + if not hasattr(module, "ListNode"): + msg = f"Class `ListNode` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module, "LinkedList"): + msg = f"Class `LinkedList` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module.LinkedList(), "insertAfterNode"): + msg = "Method `insertAfterNode` not found in Class `LinkedList`" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_method) +def test_frompythonlist_order_is_head_insertion(): + """FromPythonlist: inserts after head (result is reversed input order)""" + module = check50.py.import_(FILE_NAME) + + ll = build_ll(module, [1, 2, 8, 4]) + expected = [4, 8, 2, 1] # weil insertAfterHead bei Vorwärtsiteration umdreht + actual = to_list(ll) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_insert_after_middle(): + """insertAfterNode inserts after a middle node (in reversed-built list)""" + module = check50.py.import_(FILE_NAME) + + ll = build_ll(module, [1, 2, 8, 4]) # Liste: 4 -> 8 -> 2 -> 1 + node = ll.searchValue(8) + if node is None: + msg = "searchValue(8) returned None, but 8 should be in the list" + raise check50.Failure(msg) + + ll.insertAfterNode(node, module.ListNode(3)) + + expected = [4, 8, 3, 2, 1] + actual = to_list(ll) + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_insert_after_head(): + """insertAfterNode inserts after head""" + module = check50.py.import_(FILE_NAME) + + ll = build_ll(module, [10, 20, 30]) # Liste: 30 -> 20 -> 10 + head = ll.head + if head is None: + msg = "Linked list head is None after FromPythonlist" + raise check50.Failure(msg) + + ll.insertAfterNode(head, module.ListNode(25)) + + expected = [30, 25, 20, 10] + actual = to_list(ll) + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_insert_after_tail(): + """insertAfterNode inserts after last node (tail)""" + module = check50.py.import_(FILE_NAME) + + ll = build_ll(module, [10, 20, 30]) # Liste: 30 -> 20 -> 10 + tail = ll.searchValue(10) # 10 ist Tail + if tail is None: + msg = "searchValue(10) returned None, but 10 should be in the list" + raise check50.Failure(msg) + + ll.insertAfterNode(tail, module.ListNode(5)) + + expected = [30, 20, 10, 5] + actual = to_list(ll) + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_insert_after_none_prevnode(): + """insertAfterNode with prevNode=None: + We expect an exception (ValueError) rather than silently doing nothing.""" + module = check50.py.import_(FILE_NAME) + + ll = build_ll(module, [1, 2, 3]) + prev = ll.searchValue(999) # not in list -> None + new_node = module.ListNode(4) + + threw = False + try: + ll.insertAfterNode(prev, new_node) + except ValueError: + threw = True + + if not threw: + msg = "insertAfterNode should raise an ValueError when prevNode is None" + raise check50.Failure(msg) diff --git a/linkedAfter/html/linkedAfter.html b/linkedAfter/html/linkedAfter.html new file mode 100644 index 0000000..d26a140 --- /dev/null +++ b/linkedAfter/html/linkedAfter.html @@ -0,0 +1,204 @@ +

Linked List: Insert After Node

+ +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden zur Erzeugung einer verkettenten Liste aus eine Python Liste FromPythonlist(inputList) + und der Methode zur Ausgabe einer verketteten Liste printList(). Eine weitere Methode ist + searchValue(value) mit deren Hilfe Sie eine Listenelement mit einem bestimmten Wert + suchen können. Ihre Aufgabe ist es, eine Methode insertAfterNode(prevNode, newNode) + zu implementieren, die ein neues Listenelement newNode direkt nach dem gegebenen + Element prevNode einfügt. +

+ + +

Aufgabe

+ +

+ Implementieren Sie die Funktion insertAfterNode in Python. + Die Funktion bekommt das Element prevNode und das neue Element newNode + übergeben und soll das neue Element in die Liste einfügen. Ist prevNode None, + soll eine ValueError Exception ausgelöst werden. +

+ + +

+ Erstellen Sie eine Datei mit dem Namen + linkedAfter.py und + Implementieren Sie die Funktion insertAfterNode in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei linkedAfter.py, + und ergänzen Sie die Methode insertAfterNode: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def searchValue(self, value: int) -> ListNode:
+        current = self.head
+        while current is not None:
+            if current.value == value:
+                return current
+            current = current.next
+        return None
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+
+ + + + +

Bevor Sie beginnen

+

Melden Sie sich bei cs50.dev an, klicken Sie + auf Ihr Terminal und führen Sie cd ohne Parameter aus. Sie + sollten feststellen, dass der Prompt Ihres Terminals wie unten aussieht:

+
$
+

Als nächstes führen Sie

+
mkdir + linkedAfter
+

aus, um einen Ordner namens linkedAfter in Ihrem Codespace + zu erstellen.

+

Führen Sie anschließend

+
cd + linkedAfter
+

aus, um in dieses Verzeichnis zu wechseln. Der Prompt Ihres Terminals sollte + nun linkedAfter/$ anzeigen. + Jetzt können Sie

+
code + linkedAfter.py
+

ausführen, um eine Datei namens linkedAfter.py zu erstellen, in der Sie Ihr + Programm schreiben. +

+ +

So testen Sie Ihr Programm

+

So können Sie Ihr Programm manuell testen:

+ +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, fügen Sie Elemente hinzu und rufen Sie dann Ihre Methode insertAfterNode auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat, indem Sie printList() verwenden. +

+ +
+

+  # Erstellen einer verketteten Liste aus einer Python-Liste
+  inputList = [4, 8, 2, 1]
+  ll = LinkedList()
+  ll.FromPythonlist(inputList)
+
+  print("Liste vorher:")
+  ll.printList()
+
+  node = ll.searchValue(8)
+  newNode = ListNode(3)
+  # Einfügen von 3 nach 8
+  ll.insertAfterNode(node, newNode)
+
+  print("Liste nachher:")
+  ll.printList()
+  # Erwartete Ausgabe: 1 -> 2 -> 8 -> 3 -> 4 -> None
+
+
+ + + +

Sie können das folgende Kommando ausführen, um Ihren Code mit check50 zu überprüfen, einem Programm, das bei + der Abgabe verwendet wird um Ihren Code zu testen. Testen Sie Ihr Programm + aber auch selbst!

+
+ check50 HSDDigitalLabor/problems/adg2025/linkedAfter +
+

+ Grüne Smileys :) + bedeuten, dass Ihr Programm einen Test bestanden hat! Rote + traurige Smileys :( + zeigen an, dass Ihr Programm etwas Unerwartetes ausgegeben + hat. Besuchen Sie die URL, die check50 + ausgibt, um zu sehen, welche Eingabe check50 + an Ihr Programm übergeben hat, welche Ausgabe erwartet wurde und welche + Ausgabe Ihr Programm tatsächlich geliefert hat. +

+ +

Abgabe

+

Führen Sie im Terminal den folgenden Befehl aus, um Ihre Arbeit einzureichen. +

+
+ submit50 HSDDigitalLabor/problems/adg2025/linkedAfter +
+

+ Führen Sie diesen Befehl vor dem Fälligkeitsdatum aus. Sie können Ihre Lösung + nach dem ersten Einsenden noch ändern und erneut einreichen. Bewertet wird + die zuletzt eingereichte Version vor dem Fälligkeitsdatum. Nach Ablauf der + Frist können Sie technisch zwar noch Abgaben tätigen, Ihre Lösung wird jedoch + nicht mehr gewertet. Das Ergebnis kann ausschließlich mit obigem Befehl abgegeben + werden, eine Abgabe in Moodle ist nicht möglich. +

+ +

Markierung der Aufgabe als erledigt

+

Nach dem Einreichen der Lösung mit submit50, + nehmen Sie eine Pseudolösung in Moodle vor, indem Sie auf den Button + + weiter unten auf dieser Seite betätigen und in das sich öffnende Feld unter + "Texteingabe online" den folgenden Text ein: +

+
+ Mit submit50 abgegeben. +
+

Schließen Sie die Eingabe durch Klick auf den Button + unterhalb des Textfeldes ab. +

+

+ Zur besseren Übersicht markieren Sie das Problem linkedAfter.py in Moodle als erledigt, + indem Sie ganz oben auf dieser Seite den Button +

+

+ + klicken. Daraufhin erscheint der Button + +

+ +

generated 2025-12-19 19:13:17

\ No newline at end of file diff --git a/linkedAfter/html/params.json b/linkedAfter/html/params.json new file mode 100644 index 0000000..579ab35 --- /dev/null +++ b/linkedAfter/html/params.json @@ -0,0 +1,9 @@ +{ + "id": "linkedAfter", + "title": "Linked List: Insert After Node", + "foldername": "linkedAfter", + "filename": "linkedAfter.py", + "asciicast_id": "", + "check50_path": "HSDDigitalLabor/problems/adg2025/linkedAfter", + "submit50_path": "HSDDigitalLabor/problems/adg2025/linkedAfter" +} \ No newline at end of file diff --git a/linkedAfter/html/template.html b/linkedAfter/html/template.html new file mode 100644 index 0000000..ff90e7a --- /dev/null +++ b/linkedAfter/html/template.html @@ -0,0 +1,112 @@ +{% include "header.html" %} + +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden zur Erzeugung einer verkettenten Liste aus eine Python Liste FromPythonlist(inputList) + und der Methode zur Ausgabe einer verketteten Liste printList(). Eine weitere Methode ist + searchValue(value) mit deren Hilfe Sie eine Listenelement mit einem bestimmten Wert + suchen können. Ihre Aufgabe ist es, eine Methode insertAfterNode(prevNode, newNode) + zu implementieren, die ein neues Listenelement newNode direkt nach dem gegebenen + Element prevNode einfügt. +

+ + +

Aufgabe

+ +

+ Implementieren Sie die Funktion insertAfterNode in Python. + Die Funktion bekommt das Element prevNode und das neue Element newNode + übergeben und soll das neue Element in die Liste einfügen. Ist prevNode None, + soll eine ValueError Exception ausgelöst werden. +

+ + +

+ {% include "file.html" %} + Implementieren Sie die Funktion insertAfterNode in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei {{filename}}, + und ergänzen Sie die Methode insertAfterNode: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def searchValue(self, value: int) -> ListNode:
+        current = self.head
+        while current is not None:
+            if current.value == value:
+                return current
+            current = current.next
+        return None
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+
+ + + + +{% include "before_you_begin.html" %} + +{% include "how_to_test_head.html" %} + +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, fügen Sie Elemente hinzu und rufen Sie dann Ihre Methode insertAfterNode auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat, indem Sie printList() verwenden. +

+ +
+

+  # Erstellen einer verketteten Liste aus einer Python-Liste
+  inputList = [4, 8, 2, 1]
+  ll = LinkedList()
+  ll.FromPythonlist(inputList)
+
+  print("Liste vorher:")
+  ll.printList()
+
+  node = ll.searchValue(8)
+  newNode = ListNode(3)
+  # Einfügen von 3 nach 8
+  ll.insertAfterNode(node, newNode)
+
+  print("Liste nachher:")
+  ll.printList()
+  # Erwartete Ausgabe: 1 -> 2 -> 8 -> 3 -> 4 -> None
+
+
+ + + +{% include "how_to_test_auto.html" %} + +{% include "how_to_submit.html" %} + +{% include "how_to_mark_in_moodle.html" %} + +{% include "footer.html" %} \ No newline at end of file From aea32c18414ada050a28b163be4a9fff6059cbb6 Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 11:03:46 +0100 Subject: [PATCH 02/14] add linkedInsert --- linkedInsert/.cs50.yml | 10 ++ linkedInsert/checks/__init__.py | 0 linkedInsert/checks/main.py | 106 +++++++++++++++ linkedInsert/html/linkedInsert.html | 204 ++++++++++++++++++++++++++++ linkedInsert/html/params.json | 9 ++ linkedInsert/html/template.html | 112 +++++++++++++++ 6 files changed, 441 insertions(+) create mode 100644 linkedInsert/.cs50.yml create mode 100644 linkedInsert/checks/__init__.py create mode 100644 linkedInsert/checks/main.py create mode 100644 linkedInsert/html/linkedInsert.html create mode 100644 linkedInsert/html/params.json create mode 100644 linkedInsert/html/template.html diff --git a/linkedInsert/.cs50.yml b/linkedInsert/.cs50.yml new file mode 100644 index 0000000..4cfe528 --- /dev/null +++ b/linkedInsert/.cs50.yml @@ -0,0 +1,10 @@ +submit50: + files: &submit50_files + - !exclude "*" + - !include "*.py" + - !require linkedInsert.py + +check50: + files: *submit50_files + checks: checks/main.py + \ No newline at end of file diff --git a/linkedInsert/checks/__init__.py b/linkedInsert/checks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/linkedInsert/checks/main.py b/linkedInsert/checks/main.py new file mode 100644 index 0000000..e4e4cc6 --- /dev/null +++ b/linkedInsert/checks/main.py @@ -0,0 +1,106 @@ +import check50 +import check50.py + +FILE_NAME = "linkedInsert.py" + + +def to_list(ll): + """LinkedList -> Python-Liste (mit Zyklus-Schutz).""" + out = [] + cur = ll.head + seen = 0 + while cur is not None: + out.append(cur.value) + cur = cur.next + seen += 1 + if seen > 10_000: + msg = f"Linked list appears to have a cycle (more than {seen} nodes)" + raise check50.Failure(msg) + return out + + +def build_ll(module, values): + """Erzeugt eine LinkedList über FromPythonlist (wie in der Aufgabe vorgegeben).""" + ll = module.LinkedList() + ll.FromPythonlist(values) + return ll + + +@check50.check() +def exists(): + """linkedInsert.py exists""" + check50.exists(FILE_NAME) + + +@check50.check(exists) +def compiles(): + """linkedInsert.py compiles""" + check50.py.compile(FILE_NAME) + + +@check50.check(compiles) +def has_classes_and_method(): + """ListNode, LinkedList and insertAfterLast exist""" + module = check50.py.import_(FILE_NAME) + + if not hasattr(module, "ListNode"): + msg = f"Class `ListNode` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module, "LinkedList"): + msg = f"Class `LinkedList` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module.LinkedList(), "insertAfterLast"): + msg = "Method `insertAfterLast` not found in Class `LinkedList`" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_method) +def test_insert_empty_list(): + """insertAfterLast inserts into empty list (becomes head)""" + module = check50.py.import_(FILE_NAME) + ll = module.LinkedList() + + new_node = module.ListNode(10) + ll.insertAfterLast(new_node) + + expected = [10] + actual = to_list(ll) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_insert_non_empty_list(): + """insertAfterLast appends to non-empty list""" + module = check50.py.import_(FILE_NAME) + # List: 30 -> 20 -> 10 + ll = build_ll(module, [10, 20, 30]) + + new_node = module.ListNode(5) + ll.insertAfterLast(new_node) + + expected = [30, 20, 10, 5] + actual = to_list(ll) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_insert_multiple(): + """insertAfterLast appends multiple elements correctly""" + module = check50.py.import_(FILE_NAME) + ll = module.LinkedList() + + ll.insertAfterLast(module.ListNode(1)) + ll.insertAfterLast(module.ListNode(2)) + ll.insertAfterLast(module.ListNode(3)) + + expected = [1, 2, 3] + actual = to_list(ll) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) diff --git a/linkedInsert/html/linkedInsert.html b/linkedInsert/html/linkedInsert.html new file mode 100644 index 0000000..b5b4110 --- /dev/null +++ b/linkedInsert/html/linkedInsert.html @@ -0,0 +1,204 @@ +

Linked List: Insert After Last Node

+ +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. + Ihre Aufgabe ist es, die Methode insertAfterLast(newNode) + zu implementieren, die ein neues Listenelement newNode an das Ende der Liste einfügt. +

+ + +

Aufgabe

+ +

+ Implementieren Sie die Methode insertAfterLast(self, newNode) in der Klasse LinkedList. + Die Methode erhält das neue Element newNode und soll dieses am Ende der Liste einfügen. + Wenn die Liste leer ist, wird das neue Element zum Kopf (Head) der Liste. +

+ + +

+ Erstellen Sie eine Datei mit dem Namen + linkedInsert.py und + Implementieren Sie die Methode insertAfterLast in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei linkedInsert.py, + und ergänzen Sie die Methode insertAfterLast: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def searchValue(self, value: int) -> ListNode | None:
+        # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode
+        return None
+
+    def insertAfterNode(self, prevNode: ListNode | None, newNode: ListNode):
+        # Siehe Problem 'linkedAfter.py' für die Implementierung dieser Methode
+        return
+
+    def insertAfterLast(self, newNode: ListNode):
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+
+ + + + +

Bevor Sie beginnen

+

Melden Sie sich bei cs50.dev an, klicken Sie + auf Ihr Terminal und führen Sie cd ohne Parameter aus. Sie + sollten feststellen, dass der Prompt Ihres Terminals wie unten aussieht:

+
$
+

Als nächstes führen Sie

+
mkdir + linkedInsert
+

aus, um einen Ordner namens linkedInsert in Ihrem Codespace + zu erstellen.

+

Führen Sie anschließend

+
cd + linkedInsert
+

aus, um in dieses Verzeichnis zu wechseln. Der Prompt Ihres Terminals sollte + nun linkedInsert/$ anzeigen. + Jetzt können Sie

+
code + linkedInsert.py
+

ausführen, um eine Datei namens linkedInsert.py zu erstellen, in der Sie Ihr + Programm schreiben. +

+ +

So testen Sie Ihr Programm

+

So können Sie Ihr Programm manuell testen:

+ +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, fügen Sie Elemente hinzu und rufen Sie dann Ihre Methode insertAfterLast auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat, indem Sie printList() verwenden. +

+ +
+

+  # Erstellen einer verketteten Liste aus einer Python-Liste
+  inputList = [4, 8, 2, 1]
+  ll = LinkedList()
+  ll.FromPythonlist(inputList)
+
+  print("Liste vorher:")
+  ll.printList()
+
+  newNode = ListNode(99)
+  # Einfügen von 99 am Ende
+  ll.insertAfterLast(newNode)
+
+  print("Liste nachher:")
+  ll.printList()
+  # Erwartete Ausgabe: 1 -> 2 -> 8 -> 4 -> 99 -> None
+
+
+ + + +

Sie können das folgende Kommando ausführen, um Ihren Code mit check50 zu überprüfen, einem Programm, das bei + der Abgabe verwendet wird um Ihren Code zu testen. Testen Sie Ihr Programm + aber auch selbst!

+
+ check50 HSDDigitalLabor/problems/adg2025/linkedInsert +
+

+ Grüne Smileys :) + bedeuten, dass Ihr Programm einen Test bestanden hat! Rote + traurige Smileys :( + zeigen an, dass Ihr Programm etwas Unerwartetes ausgegeben + hat. Besuchen Sie die URL, die check50 + ausgibt, um zu sehen, welche Eingabe check50 + an Ihr Programm übergeben hat, welche Ausgabe erwartet wurde und welche + Ausgabe Ihr Programm tatsächlich geliefert hat. +

+ +

Abgabe

+

Führen Sie im Terminal den folgenden Befehl aus, um Ihre Arbeit einzureichen. +

+
+ submit50 HSDDigitalLabor/problems/adg2025/linkedInsert +
+

+ Führen Sie diesen Befehl vor dem Fälligkeitsdatum aus. Sie können Ihre Lösung + nach dem ersten Einsenden noch ändern und erneut einreichen. Bewertet wird + die zuletzt eingereichte Version vor dem Fälligkeitsdatum. Nach Ablauf der + Frist können Sie technisch zwar noch Abgaben tätigen, Ihre Lösung wird jedoch + nicht mehr gewertet. Das Ergebnis kann ausschließlich mit obigem Befehl abgegeben + werden, eine Abgabe in Moodle ist nicht möglich. +

+ +

Markierung der Aufgabe als erledigt

+

Nach dem Einreichen der Lösung mit submit50, + nehmen Sie eine Pseudolösung in Moodle vor, indem Sie auf den Button + + weiter unten auf dieser Seite betätigen und in das sich öffnende Feld unter + "Texteingabe online" den folgenden Text ein: +

+
+ Mit submit50 abgegeben. +
+

Schließen Sie die Eingabe durch Klick auf den Button + unterhalb des Textfeldes ab. +

+

+ Zur besseren Übersicht markieren Sie das Problem linkedInsert.py in Moodle als erledigt, + indem Sie ganz oben auf dieser Seite den Button +

+

+ + klicken. Daraufhin erscheint der Button + +

+ +

generated 2025-12-22 10:30:01

\ No newline at end of file diff --git a/linkedInsert/html/params.json b/linkedInsert/html/params.json new file mode 100644 index 0000000..8feb51e --- /dev/null +++ b/linkedInsert/html/params.json @@ -0,0 +1,9 @@ +{ + "id": "linkedInsert", + "title": "Linked List: Insert After Last Node", + "foldername": "linkedInsert", + "filename": "linkedInsert.py", + "asciicast_id": "", + "check50_path": "HSDDigitalLabor/problems/adg2025/linkedInsert", + "submit50_path": "HSDDigitalLabor/problems/adg2025/linkedInsert" +} \ No newline at end of file diff --git a/linkedInsert/html/template.html b/linkedInsert/html/template.html new file mode 100644 index 0000000..9005e14 --- /dev/null +++ b/linkedInsert/html/template.html @@ -0,0 +1,112 @@ +{% include "header.html" %} + +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. + Ihre Aufgabe ist es, die Methode insertAfterLast(newNode) + zu implementieren, die ein neues Listenelement newNode an das Ende der Liste einfügt. +

+ + +

Aufgabe

+ +

+ Implementieren Sie die Methode insertAfterLast(self, newNode) in der Klasse LinkedList. + Die Methode erhält das neue Element newNode und soll dieses am Ende der Liste einfügen. + Wenn die Liste leer ist, wird das neue Element zum Kopf (Head) der Liste. +

+ + +

+ {% include "file.html" %} + Implementieren Sie die Methode insertAfterLast in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei {{filename}}, + und ergänzen Sie die Methode insertAfterLast: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def searchValue(self, value: int) -> ListNode | None:
+        # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode
+        return None
+
+    def insertAfterNode(self, prevNode: ListNode | None, newNode: ListNode):
+        # Siehe Problem 'linkedAfter.py' für die Implementierung dieser Methode
+        return
+
+    def insertAfterLast(self, newNode: ListNode):
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+
+ + + + +{% include "before_you_begin.html" %} + +{% include "how_to_test_head.html" %} + +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, fügen Sie Elemente hinzu und rufen Sie dann Ihre Methode insertAfterLast auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat, indem Sie printList() verwenden. +

+ +
+

+  # Erstellen einer verketteten Liste aus einer Python-Liste
+  inputList = [4, 8, 2, 1]
+  ll = LinkedList()
+  ll.FromPythonlist(inputList)
+
+  print("Liste vorher:")
+  ll.printList()
+
+  newNode = ListNode(99)
+  # Einfügen von 99 am Ende
+  ll.insertAfterLast(newNode)
+
+  print("Liste nachher:")
+  ll.printList()
+  # Erwartete Ausgabe: 1 -> 2 -> 8 -> 4 -> 99 -> None
+
+
+ + + +{% include "how_to_test_auto.html" %} + +{% include "how_to_submit.html" %} + +{% include "how_to_mark_in_moodle.html" %} + +{% include "footer.html" %} \ No newline at end of file From fb98a5cb860f0f3f1a47b0e5a36c6f5c8fd85dc4 Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 11:04:10 +0100 Subject: [PATCH 03/14] add linkedFind --- linkedFind/.cs50.yml | 10 ++ linkedFind/checks/__init__.py | 0 linkedFind/checks/main.py | 132 +++++++++++++++++++++ linkedFind/html/linkedFind.html | 200 ++++++++++++++++++++++++++++++++ linkedFind/html/params.json | 9 ++ linkedFind/html/template.html | 108 +++++++++++++++++ 6 files changed, 459 insertions(+) create mode 100644 linkedFind/.cs50.yml create mode 100644 linkedFind/checks/__init__.py create mode 100644 linkedFind/checks/main.py create mode 100644 linkedFind/html/linkedFind.html create mode 100644 linkedFind/html/params.json create mode 100644 linkedFind/html/template.html diff --git a/linkedFind/.cs50.yml b/linkedFind/.cs50.yml new file mode 100644 index 0000000..82f0073 --- /dev/null +++ b/linkedFind/.cs50.yml @@ -0,0 +1,10 @@ +submit50: + files: &submit50_files + - !exclude "*" + - !include "*.py" + - !require linkedFind.py + +check50: + files: *submit50_files + checks: checks/main.py + \ No newline at end of file diff --git a/linkedFind/checks/__init__.py b/linkedFind/checks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/linkedFind/checks/main.py b/linkedFind/checks/main.py new file mode 100644 index 0000000..a9b77b4 --- /dev/null +++ b/linkedFind/checks/main.py @@ -0,0 +1,132 @@ +import check50 +import check50.py + +FILE_NAME = "linkedFind.py" + + +def build_ll(module, values): + """Erzeugt eine LinkedList über FromPythonlist (wie in der Aufgabe vorgegeben).""" + ll = module.LinkedList() + ll.FromPythonlist(values) + return ll + + +@check50.check() +def exists(): + """linkedFind.py exists""" + check50.exists(FILE_NAME) + + +@check50.check(exists) +def compiles(): + """linkedFind.py compiles""" + check50.py.compile(FILE_NAME) + + +@check50.check(compiles) +def has_classes_and_method(): + """ListNode, LinkedList and searchValue exist""" + module = check50.py.import_(FILE_NAME) + + if not hasattr(module, "ListNode"): + msg = f"Class `ListNode` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module, "LinkedList"): + msg = f"Class `LinkedList` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module.LinkedList(), "searchValue"): + msg = "Method `searchValue` not found in Class `LinkedList`" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_method) +def test_search_found_head(): + """searchValue finds the head element""" + module = check50.py.import_(FILE_NAME) + # List: 30 -> 20 -> 10 (FromPythonlist inserts at head) + ll = build_ll(module, [10, 20, 30]) + + found = ll.searchValue(30) + + if found is None: + msg = "searchValue returned None for an existing value (head)" + raise check50.Failure(msg) + + if not isinstance(found, module.ListNode): + msg = "searchValue must return a ListNode object" + raise check50.Failure(msg) + + if found.value != 30: + msg = f"Node with value 30, Node with value {found.value}" + raise check50.Mismatch(msg) + + +@check50.check(has_classes_and_method) +def test_search_found_middle(): + """searchValue finds a middle element""" + module = check50.py.import_(FILE_NAME) + # List: 30 -> 20 -> 10 + ll = build_ll(module, [10, 20, 30]) + + found = ll.searchValue(20) + + if found is None: + msg = "searchValue returned None for an existing value (middle)" + raise check50.Failure(msg) + + if found.value != 20: + msg = "Node with value 20", f"Node with value {found.value}" + raise check50.Mismatch(msg) + + +@check50.check(has_classes_and_method) +def test_search_found_tail(): + """searchValue finds the tail element""" + module = check50.py.import_(FILE_NAME) + # List: 30 -> 20 -> 10 + ll = build_ll(module, [10, 20, 30]) + + found = ll.searchValue(10) + + if found is None: + msg = "searchValue returned None for an existing value (tail)" + raise check50.Failure(msg) + + if found.value != 10: + msg = "Node with value 10", f"Node with value {found.value}" + raise check50.Mismatch(msg) + + +@check50.check(has_classes_and_method) +def test_search_not_found(): + """searchValue returns None for non-existing value""" + module = check50.py.import_(FILE_NAME) + # List: 30 -> 20 -> 10 + ll = build_ll(module, [10, 20, 30]) + + found = ll.searchValue(99) + + if found is not None: + msg = ( + f"searchValue should return None for non-existing value," + f"but returned Node with value {found.value}" + ) + raise check50.Failure(msg) + + +@check50.check(has_classes_and_method) +def test_search_empty_list(): + """searchValue returns None on empty list""" + module = check50.py.import_(FILE_NAME) + ll = module.LinkedList() + + found = ll.searchValue(10) + + if found is not None: + msg = ( + f"searchValue should return None on empty list, " + f"but returned Node with value {found.value}" + ) + raise check50.Failure(msg) diff --git a/linkedFind/html/linkedFind.html b/linkedFind/html/linkedFind.html new file mode 100644 index 0000000..ed877bc --- /dev/null +++ b/linkedFind/html/linkedFind.html @@ -0,0 +1,200 @@ +

Linked List: Ist der Wert in der Liste

+ +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. + Ihre Aufgabe ist es, die Methode searchValue(value) + zu implementieren, mit deren Hilfe Sie ein Listenelement mit einem bestimmten Wert + suchen können. +

+ + +

Aufgabe

+ +

+ Implementieren Sie die Methode searchValue(self, value) in der Klasse LinkedList. + Die Methode erhält den Wert value, nach dem gesucht werden soll, und + soll das erste ListNode-Objekt zurückgeben, das diesen Wert enthält. Wird kein Element mit dem + gesuchten Wert gefunden, soll None zurückgegeben werden. +

+ + +

+ Erstellen Sie eine Datei mit dem Namen + linkedFind.py und + Implementieren Sie die Funktion searchValue(self, value) in Ihrer Datei. + Kopieren Sie dazu den folgenden Codeblock in die Datei + linkedFind.py, + und ergänzen Sie die Methode searchValue(self, value): +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def searchValue(self, value: int) -> ListNode | None:
+        # TODO implementieren Sie hier die Suche nach dem Wert
+        return None
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+
+ + + + +

Bevor Sie beginnen

+

Melden Sie sich bei cs50.dev an, klicken Sie + auf Ihr Terminal und führen Sie cd ohne Parameter aus. Sie + sollten feststellen, dass der Prompt Ihres Terminals wie unten aussieht:

+
$
+

Als nächstes führen Sie

+
mkdir + linkedFind
+

aus, um einen Ordner namens linkedFind in Ihrem Codespace + zu erstellen.

+

Führen Sie anschließend

+
cd + linkedFind
+

aus, um in dieses Verzeichnis zu wechseln. Der Prompt Ihres Terminals sollte + nun linkedFind/$ anzeigen. + Jetzt können Sie

+
code + linkedFind.py
+

ausführen, um eine Datei namens linkedFind.py zu erstellen, in der Sie Ihr + Programm schreiben. +

+ +

So testen Sie Ihr Programm

+

So können Sie Ihr Programm manuell testen:

+ +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, + fügen Sie Elemente hinzu und rufen Sie dann Ihre Methode searchValue auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat, + indem Sie printList() verwenden. +

+ +
+

+  # Erstellen einer verketteten Liste aus einer Python-Liste
+  inputList = [1, 2, 8, 4]
+  ll = LinkedList()
+  ll.FromPythonlist(inputList)
+
+  print("Liste:")
+  ll.printList()
+
+  node = ll.searchValue(8)
+  if node is not None:
+      print(f"Gefundenes Element: {node.value}")
+  else:
+      print("Element nicht gefunden.")
+
+
+
+ + + +

Sie können das folgende Kommando ausführen, um Ihren Code mit check50 zu überprüfen, einem Programm, das bei + der Abgabe verwendet wird um Ihren Code zu testen. Testen Sie Ihr Programm + aber auch selbst!

+
+ check50 HSDDigitalLabor/problems/adg2025/linkedFind +
+

+ Grüne Smileys :) + bedeuten, dass Ihr Programm einen Test bestanden hat! Rote + traurige Smileys :( + zeigen an, dass Ihr Programm etwas Unerwartetes ausgegeben + hat. Besuchen Sie die URL, die check50 + ausgibt, um zu sehen, welche Eingabe check50 + an Ihr Programm übergeben hat, welche Ausgabe erwartet wurde und welche + Ausgabe Ihr Programm tatsächlich geliefert hat. +

+ +

Abgabe

+

Führen Sie im Terminal den folgenden Befehl aus, um Ihre Arbeit einzureichen. +

+
+ submit50 HSDDigitalLabor/problems/adg2025/linkedFind +
+

+ Führen Sie diesen Befehl vor dem Fälligkeitsdatum aus. Sie können Ihre Lösung + nach dem ersten Einsenden noch ändern und erneut einreichen. Bewertet wird + die zuletzt eingereichte Version vor dem Fälligkeitsdatum. Nach Ablauf der + Frist können Sie technisch zwar noch Abgaben tätigen, Ihre Lösung wird jedoch + nicht mehr gewertet. Das Ergebnis kann ausschließlich mit obigem Befehl abgegeben + werden, eine Abgabe in Moodle ist nicht möglich. +

+ +

Markierung der Aufgabe als erledigt

+

Nach dem Einreichen der Lösung mit submit50, + nehmen Sie eine Pseudolösung in Moodle vor, indem Sie auf den Button + + weiter unten auf dieser Seite betätigen und in das sich öffnende Feld unter + "Texteingabe online" den folgenden Text ein: +

+
+ Mit submit50 abgegeben. +
+

Schließen Sie die Eingabe durch Klick auf den Button + unterhalb des Textfeldes ab. +

+

+ Zur besseren Übersicht markieren Sie das Problem linkedFind.py in Moodle als erledigt, + indem Sie ganz oben auf dieser Seite den Button +

+

+ + klicken. Daraufhin erscheint der Button + +

+ +

generated 2025-12-22 10:29:53

\ No newline at end of file diff --git a/linkedFind/html/params.json b/linkedFind/html/params.json new file mode 100644 index 0000000..198cb71 --- /dev/null +++ b/linkedFind/html/params.json @@ -0,0 +1,9 @@ +{ + "id": "linkedFind", + "title": "Linked List: Ist der Wert in der Liste", + "foldername": "linkedFind", + "filename": "linkedFind.py", + "asciicast_id": "", + "check50_path": "HSDDigitalLabor/problems/adg2025/linkedFind", + "submit50_path": "HSDDigitalLabor/problems/adg2025/linkedFind" +} \ No newline at end of file diff --git a/linkedFind/html/template.html b/linkedFind/html/template.html new file mode 100644 index 0000000..77d73cb --- /dev/null +++ b/linkedFind/html/template.html @@ -0,0 +1,108 @@ +{% include "header.html" %} + +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. + Ihre Aufgabe ist es, die Methode searchValue(value) + zu implementieren, mit deren Hilfe Sie ein Listenelement mit einem bestimmten Wert + suchen können. +

+ + +

Aufgabe

+ +

+ Implementieren Sie die Methode searchValue(self, value) in der Klasse LinkedList. + Die Methode erhält den Wert value, nach dem gesucht werden soll, und + soll das erste ListNode-Objekt zurückgeben, das diesen Wert enthält. Wird kein Element mit dem + gesuchten Wert gefunden, soll None zurückgegeben werden. +

+ + +

+ {% include "file.html" %} + Implementieren Sie die Funktion searchValue(self, value) in Ihrer Datei. + Kopieren Sie dazu den folgenden Codeblock in die Datei + {{filename}}, + und ergänzen Sie die Methode searchValue(self, value): +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def searchValue(self, value: int) -> ListNode | None:
+        # TODO implementieren Sie hier die Suche nach dem Wert
+        return None
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+
+ + + + +{% include "before_you_begin.html" %} + +{% include "how_to_test_head.html" %} + +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, + fügen Sie Elemente hinzu und rufen Sie dann Ihre Methode searchValue auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat, + indem Sie printList() verwenden. +

+ +
+

+  # Erstellen einer verketteten Liste aus einer Python-Liste
+  inputList = [1, 2, 8, 4]
+  ll = LinkedList()
+  ll.FromPythonlist(inputList)
+
+  print("Liste:")
+  ll.printList()
+
+  node = ll.searchValue(8)
+  if node is not None:
+      print(f"Gefundenes Element: {node.value}")
+  else:
+      print("Element nicht gefunden.")
+
+
+
+ + + +{% include "how_to_test_auto.html" %} + +{% include "how_to_submit.html" %} + +{% include "how_to_mark_in_moodle.html" %} + +{% include "footer.html" %} \ No newline at end of file From 3ff0095c06ae196ab31c9da9e095c6896d4e5f1d Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 11:04:31 +0100 Subject: [PATCH 04/14] add linkedRemove --- linkedRemove/.cs50.yml | 10 ++ linkedRemove/checks/__init__.py | 0 linkedRemove/checks/main.py | 149 ++++++++++++++++++++ linkedRemove/html/linkedRemove.html | 210 ++++++++++++++++++++++++++++ linkedRemove/html/params.json | 9 ++ linkedRemove/html/template.html | 118 ++++++++++++++++ 6 files changed, 496 insertions(+) create mode 100644 linkedRemove/.cs50.yml create mode 100644 linkedRemove/checks/__init__.py create mode 100644 linkedRemove/checks/main.py create mode 100644 linkedRemove/html/linkedRemove.html create mode 100644 linkedRemove/html/params.json create mode 100644 linkedRemove/html/template.html diff --git a/linkedRemove/.cs50.yml b/linkedRemove/.cs50.yml new file mode 100644 index 0000000..eb529e8 --- /dev/null +++ b/linkedRemove/.cs50.yml @@ -0,0 +1,10 @@ +submit50: + files: &submit50_files + - !exclude "*" + - !include "*.py" + - !require linkedRemove.py + +check50: + files: *submit50_files + checks: checks/main.py + \ No newline at end of file diff --git a/linkedRemove/checks/__init__.py b/linkedRemove/checks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/linkedRemove/checks/main.py b/linkedRemove/checks/main.py new file mode 100644 index 0000000..d6c5b8e --- /dev/null +++ b/linkedRemove/checks/main.py @@ -0,0 +1,149 @@ +import check50 +import check50.py + +FILE_NAME = "linkedRemove.py" + + +def to_list(ll): + """LinkedList -> Python-Liste (mit Zyklus-Schutz).""" + out = [] + cur = ll.head + seen = 0 + while cur is not None: + out.append(cur.value) + cur = cur.next + seen += 1 + if seen > 10_000: + msg = f"Linked list appears to have a cycle (more than {seen} nodes)" + raise check50.Failure(msg) + return out + + +def build_ll(module, values): + """Erzeugt eine LinkedList über FromPythonlist (wie in der Aufgabe vorgegeben).""" + ll = module.LinkedList() + ll.FromPythonlist(values) + return ll + + +@check50.check() +def exists(): + """linkedRemove.py exists""" + check50.exists(FILE_NAME) + + +@check50.check(exists) +def compiles(): + """linkedRemove.py compiles""" + check50.py.compile(FILE_NAME) + + +@check50.check(compiles) +def has_classes_and_method(): + """ListNode, LinkedList and removeElement exist""" + module = check50.py.import_(FILE_NAME) + + if not hasattr(module, "ListNode"): + msg = f"Class `ListNode` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module, "LinkedList"): + msg = f"Class `LinkedList` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module.LinkedList(), "removeElement"): + msg = "Method `removeElement` not found in Class `LinkedList`" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_method) +def test_remove_middle(): + """removeElement removes a middle node""" + module = check50.py.import_(FILE_NAME) + # List: 30 -> 20 -> 10 + ll = build_ll(module, [10, 20, 30]) + + # Find node with value 20 + node_to_remove = ll.searchValue(20) + if node_to_remove is None: + msg = ("searchValue(20) returned None, but 20 should be in the list. " + "Cannot test removeElement.") + raise check50.Failure(msg) + + ll.removeElement(node_to_remove) + + expected = [30, 10] + actual = to_list(ll) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_remove_head(): + """removeElement removes the head node""" + module = check50.py.import_(FILE_NAME) + # List: 30 -> 20 -> 10 + ll = build_ll(module, [10, 20, 30]) + + # Find node with value 30 (head) + node_to_remove = ll.searchValue(30) + if node_to_remove is None: + msg = ("searchValue(30) returned None, but 30 should be in the list. " + "Cannot test removeElement.") + raise check50.Failure(msg) + + ll.removeElement(node_to_remove) + + expected = [20, 10] + actual = to_list(ll) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_remove_tail(): + """removeElement removes the tail node""" + module = check50.py.import_(FILE_NAME) + # List: 30 -> 20 -> 10 + ll = build_ll(module, [10, 20, 30]) + + # Find node with value 10 (tail) + node_to_remove = ll.searchValue(10) + if node_to_remove is None: + msg = ("searchValue(10) returned None, but 10 should be in the list. " + "Cannot test removeElement.") + raise check50.Failure(msg) + + ll.removeElement(node_to_remove) + + expected = [30, 20] + actual = to_list(ll) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_method) +def test_remove_single_element(): + """removeElement removes the only element in the list""" + module = check50.py.import_(FILE_NAME) + # List: 10 + ll = build_ll(module, [10]) + + node_to_remove = ll.searchValue(10) + if node_to_remove is None: + msg = ( + "searchValue(10) returned None, but 10 should be in the list. " + "Cannot test removeElement." + ) + raise check50.Failure(msg) + + ll.removeElement(node_to_remove) + + expected = [] + actual = to_list(ll) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) diff --git a/linkedRemove/html/linkedRemove.html b/linkedRemove/html/linkedRemove.html new file mode 100644 index 0000000..a99b7e0 --- /dev/null +++ b/linkedRemove/html/linkedRemove.html @@ -0,0 +1,210 @@ +

Linked List: Entferne ein Element aus einer Liste

+ +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. + Ihre Aufgabe ist es, die Methode removeElement(node) + zu implementieren, die das übergebene Listenelement node aus der Liste entfernt. +

+ + +

Aufgabe

+ +

+ Implementieren Sie die Methode removeElement(self, node) in der Klasse LinkedList. + Die Methode erhält das Element node, das entfernt werden soll. Sie soll dieses Element aus der Liste entfernen. + Achten Sie darauf, den Vorgänger des Elements korrekt mit dem Nachfolger zu verbinden. + Wenn das Element der Kopf (Head) der Liste ist, muss der Kopf aktualisiert werden. +

+ + +

+ Erstellen Sie eine Datei mit dem Namen + linkedRemove.py und + Implementieren Sie die Methode removeElement in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei linkedRemove.py, + und ergänzen Sie die Methode removeElement: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def searchValue(self, value: int) -> ListNode | None:
+        # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode
+        return None
+
+    def insertAfterNode(self, prevNode: ListNode | None, newNode: ListNode):
+        # Siehe Problem 'linkedAfter.py' für die Implementierung dieser Methode
+        return
+
+    def insertAfterLast(self, newNode: ListNode):
+        # Siehe Problem 'linkedInsert.py' für die Implementierung dieser Methode
+        pass
+
+    def removeElement(self, node: ListNode):
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+
+ + + + +

Bevor Sie beginnen

+

Melden Sie sich bei cs50.dev an, klicken Sie + auf Ihr Terminal und führen Sie cd ohne Parameter aus. Sie + sollten feststellen, dass der Prompt Ihres Terminals wie unten aussieht:

+
$
+

Als nächstes führen Sie

+
mkdir + linkedRemove
+

aus, um einen Ordner namens linkedRemove in Ihrem Codespace + zu erstellen.

+

Führen Sie anschließend

+
cd + linkedRemove
+

aus, um in dieses Verzeichnis zu wechseln. Der Prompt Ihres Terminals sollte + nun linkedRemove/$ anzeigen. + Jetzt können Sie

+
code + linkedRemove.py
+

ausführen, um eine Datei namens linkedRemove.py zu erstellen, in der Sie Ihr + Programm schreiben. +

+ +

So testen Sie Ihr Programm

+

So können Sie Ihr Programm manuell testen:

+ +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, fügen Sie Elemente hinzu und rufen Sie dann Ihre Methode removeElement auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat, indem Sie printList() verwenden. +

+ +
+

+  # Erstellen einer verketteten Liste aus einer Python-Liste
+  inputList = [4, 8, 2, 1]
+  ll = LinkedList()
+  ll.FromPythonlist(inputList)
+
+  print("Liste vorher:")
+  ll.printList()
+
+  # Suchen und Entfernen von 8
+  node = ll.searchValue(8)
+  if node:
+      ll.removeElement(node)
+
+  print("Liste nachher:")
+  ll.printList()
+  # Erwartete Ausgabe: 1 -> 2 -> 4 -> None
+
+
+ + + +

Sie können das folgende Kommando ausführen, um Ihren Code mit check50 zu überprüfen, einem Programm, das bei + der Abgabe verwendet wird um Ihren Code zu testen. Testen Sie Ihr Programm + aber auch selbst!

+
+ check50 HSDDigitalLabor/problems/adg2025/linkedRemove +
+

+ Grüne Smileys :) + bedeuten, dass Ihr Programm einen Test bestanden hat! Rote + traurige Smileys :( + zeigen an, dass Ihr Programm etwas Unerwartetes ausgegeben + hat. Besuchen Sie die URL, die check50 + ausgibt, um zu sehen, welche Eingabe check50 + an Ihr Programm übergeben hat, welche Ausgabe erwartet wurde und welche + Ausgabe Ihr Programm tatsächlich geliefert hat. +

+ +

Abgabe

+

Führen Sie im Terminal den folgenden Befehl aus, um Ihre Arbeit einzureichen. +

+
+ submit50 HSDDigitalLabor/problems/adg2025/linkedRemove +
+

+ Führen Sie diesen Befehl vor dem Fälligkeitsdatum aus. Sie können Ihre Lösung + nach dem ersten Einsenden noch ändern und erneut einreichen. Bewertet wird + die zuletzt eingereichte Version vor dem Fälligkeitsdatum. Nach Ablauf der + Frist können Sie technisch zwar noch Abgaben tätigen, Ihre Lösung wird jedoch + nicht mehr gewertet. Das Ergebnis kann ausschließlich mit obigem Befehl abgegeben + werden, eine Abgabe in Moodle ist nicht möglich. +

+ +

Markierung der Aufgabe als erledigt

+

Nach dem Einreichen der Lösung mit submit50, + nehmen Sie eine Pseudolösung in Moodle vor, indem Sie auf den Button + + weiter unten auf dieser Seite betätigen und in das sich öffnende Feld unter + "Texteingabe online" den folgenden Text ein: +

+
+ Mit submit50 abgegeben. +
+

Schließen Sie die Eingabe durch Klick auf den Button + unterhalb des Textfeldes ab. +

+

+ Zur besseren Übersicht markieren Sie das Problem linkedRemove.py in Moodle als erledigt, + indem Sie ganz oben auf dieser Seite den Button +

+

+ + klicken. Daraufhin erscheint der Button + +

+ +

generated 2025-12-22 10:45:25

\ No newline at end of file diff --git a/linkedRemove/html/params.json b/linkedRemove/html/params.json new file mode 100644 index 0000000..49a5feb --- /dev/null +++ b/linkedRemove/html/params.json @@ -0,0 +1,9 @@ +{ + "id": "linkedRemove", + "title": "Linked List: Entferne ein Element aus einer Liste", + "foldername": "linkedRemove", + "filename": "linkedRemove.py", + "asciicast_id": "", + "check50_path": "HSDDigitalLabor/problems/adg2025/linkedRemove", + "submit50_path": "HSDDigitalLabor/problems/adg2025/linkedRemove" +} \ No newline at end of file diff --git a/linkedRemove/html/template.html b/linkedRemove/html/template.html new file mode 100644 index 0000000..4c0be5a --- /dev/null +++ b/linkedRemove/html/template.html @@ -0,0 +1,118 @@ +{% include "header.html" %} + +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. + Ihre Aufgabe ist es, die Methode removeElement(node) + zu implementieren, die das übergebene Listenelement node aus der Liste entfernt. +

+ + +

Aufgabe

+ +

+ Implementieren Sie die Methode removeElement(self, node) in der Klasse LinkedList. + Die Methode erhält das Element node, das entfernt werden soll. Sie soll dieses Element aus der Liste entfernen. + Achten Sie darauf, den Vorgänger des Elements korrekt mit dem Nachfolger zu verbinden. + Wenn das Element der Kopf (Head) der Liste ist, muss der Kopf aktualisiert werden. +

+ + +

+ {% include "file.html" %} + Implementieren Sie die Methode removeElement in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei {{filename}}, + und ergänzen Sie die Methode removeElement: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def searchValue(self, value: int) -> ListNode | None:
+        # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode
+        return None
+
+    def insertAfterNode(self, prevNode: ListNode | None, newNode: ListNode):
+        # Siehe Problem 'linkedAfter.py' für die Implementierung dieser Methode
+        return
+
+    def insertAfterLast(self, newNode: ListNode):
+        # Siehe Problem 'linkedInsert.py' für die Implementierung dieser Methode
+        pass
+
+    def removeElement(self, node: ListNode):
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+
+ + + + +{% include "before_you_begin.html" %} + +{% include "how_to_test_head.html" %} + +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, fügen Sie Elemente hinzu und rufen Sie dann Ihre Methode removeElement auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat, indem Sie printList() verwenden. +

+ +
+

+  # Erstellen einer verketteten Liste aus einer Python-Liste
+  inputList = [4, 8, 2, 1]
+  ll = LinkedList()
+  ll.FromPythonlist(inputList)
+
+  print("Liste vorher:")
+  ll.printList()
+
+  # Suchen und Entfernen von 8
+  node = ll.searchValue(8)
+  if node:
+      ll.removeElement(node)
+
+  print("Liste nachher:")
+  ll.printList()
+  # Erwartete Ausgabe: 1 -> 2 -> 4 -> None
+
+
+ + + +{% include "how_to_test_auto.html" %} + +{% include "how_to_submit.html" %} + +{% include "how_to_mark_in_moodle.html" %} + +{% include "footer.html" %} \ No newline at end of file From 3277d1b4be2f0b046440f3e448d7f9614d389184 Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 11:04:49 +0100 Subject: [PATCH 05/14] update LinkedAfter --- linkedAfter/checks/main.py | 13 +++++++++++++ linkedAfter/html/linkedAfter.html | 32 +++++++++++++++---------------- linkedAfter/html/template.html | 30 ++++++++++++++--------------- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/linkedAfter/checks/main.py b/linkedAfter/checks/main.py index f686cc7..296cb8c 100644 --- a/linkedAfter/checks/main.py +++ b/linkedAfter/checks/main.py @@ -26,6 +26,15 @@ def build_ll(module, values): return ll +def a(self, b): + c = self.head + while c is not None: + if c.value == b: + return c + c = c.next + return None + + @check50.check() def exists(): """linkedAfter.py exists""" @@ -75,6 +84,7 @@ def test_insert_after_middle(): module = check50.py.import_(FILE_NAME) ll = build_ll(module, [1, 2, 8, 4]) # Liste: 4 -> 8 -> 2 -> 1 + module.LinkedList.searchValue = a node = ll.searchValue(8) if node is None: msg = "searchValue(8) returned None, but 8 should be in the list" @@ -94,6 +104,7 @@ def test_insert_after_head(): module = check50.py.import_(FILE_NAME) ll = build_ll(module, [10, 20, 30]) # Liste: 30 -> 20 -> 10 + module.LinkedList.searchValue = a head = ll.head if head is None: msg = "Linked list head is None after FromPythonlist" @@ -113,6 +124,7 @@ def test_insert_after_tail(): module = check50.py.import_(FILE_NAME) ll = build_ll(module, [10, 20, 30]) # Liste: 30 -> 20 -> 10 + module.LinkedList.searchValue = a tail = ll.searchValue(10) # 10 ist Tail if tail is None: msg = "searchValue(10) returned None, but 10 should be in the list" @@ -133,6 +145,7 @@ def test_insert_after_none_prevnode(): module = check50.py.import_(FILE_NAME) ll = build_ll(module, [1, 2, 3]) + module.LinkedList.searchValue = a prev = ll.searchValue(999) # not in list -> None new_node = module.ListNode(4) diff --git a/linkedAfter/html/linkedAfter.html b/linkedAfter/html/linkedAfter.html index d26a140..c778136 100644 --- a/linkedAfter/html/linkedAfter.html +++ b/linkedAfter/html/linkedAfter.html @@ -3,10 +3,10 @@

Linked List: Insert After Node

In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige - Hilfsmethoden zur Erzeugung einer verkettenten Liste aus eine Python Liste FromPythonlist(inputList) - und der Methode zur Ausgabe einer verketteten Liste printList(). Eine weitere Methode ist - searchValue(value) mit deren Hilfe Sie eine Listenelement mit einem bestimmten Wert - suchen können. Ihre Aufgabe ist es, eine Methode insertAfterNode(prevNode, newNode) + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. Ebenfalls gegeben ist die Methode + searchValue(value), mit deren Hilfe Sie ein Listenelement mit einem bestimmten Wert + suchen können. Ihre Aufgabe ist es, die Methode insertAfterNode(prevNode, newNode) zu implementieren, die ein neues Listenelement newNode direkt nach dem gegebenen Element prevNode einfügt.

@@ -15,17 +15,17 @@

Linked List: Insert After Node

Aufgabe

- Implementieren Sie die Funktion insertAfterNode in Python. - Die Funktion bekommt das Element prevNode und das neue Element newNode - übergeben und soll das neue Element in die Liste einfügen. Ist prevNode None, - soll eine ValueError Exception ausgelöst werden. + Implementieren Sie die Methode insertAfterNode(self, prevNode, newNode) in der Klasse LinkedList. + Die Methode erhält das Element prevNode und das neue Element newNode + und soll das neue Element direkt nach prevNode in die Liste einfügen. Ist prevNode None, + soll eine ValueError-Exception ausgelöst werden.

Erstellen Sie eine Datei mit dem Namen linkedAfter.py und - Implementieren Sie die Funktion insertAfterNode in Ihrer Datei. Kopieren Sie dazu den + Implementieren Sie die Methode insertAfterNode in Ihrer Datei. Kopieren Sie dazu den folgenden Codeblock in die Datei linkedAfter.py, und ergänzen Sie die Methode insertAfterNode:

@@ -48,12 +48,12 @@

Aufgabe

self.insertAfterHead(newNode) def searchValue(self, value: int) -> ListNode: - current = self.head - while current is not None: - if current.value == value: - return current - current = current.next - return None + # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode + return + + def insertAfterNode(self, prevNode: ListNode | None, newNode: ListNode): + # TODO: Implementieren Sie diese Methode + return def printList(self): current = self.head @@ -201,4 +201,4 @@

Markierung der Aufgabe als erledigt

-

generated 2025-12-19 19:13:17

\ No newline at end of file +

generated 2025-12-22 10:29:48

\ No newline at end of file diff --git a/linkedAfter/html/template.html b/linkedAfter/html/template.html index ff90e7a..44a8460 100644 --- a/linkedAfter/html/template.html +++ b/linkedAfter/html/template.html @@ -3,10 +3,10 @@

In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige - Hilfsmethoden zur Erzeugung einer verkettenten Liste aus eine Python Liste FromPythonlist(inputList) - und der Methode zur Ausgabe einer verketteten Liste printList(). Eine weitere Methode ist - searchValue(value) mit deren Hilfe Sie eine Listenelement mit einem bestimmten Wert - suchen können. Ihre Aufgabe ist es, eine Methode insertAfterNode(prevNode, newNode) + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. Ebenfalls gegeben ist die Methode + searchValue(value), mit deren Hilfe Sie ein Listenelement mit einem bestimmten Wert + suchen können. Ihre Aufgabe ist es, die Methode insertAfterNode(prevNode, newNode) zu implementieren, die ein neues Listenelement newNode direkt nach dem gegebenen Element prevNode einfügt.

@@ -15,16 +15,16 @@

Aufgabe

- Implementieren Sie die Funktion insertAfterNode in Python. - Die Funktion bekommt das Element prevNode und das neue Element newNode - übergeben und soll das neue Element in die Liste einfügen. Ist prevNode None, - soll eine ValueError Exception ausgelöst werden. + Implementieren Sie die Methode insertAfterNode(self, prevNode, newNode) in der Klasse LinkedList. + Die Methode erhält das Element prevNode und das neue Element newNode + und soll das neue Element direkt nach prevNode in die Liste einfügen. Ist prevNode None, + soll eine ValueError-Exception ausgelöst werden.

{% include "file.html" %} - Implementieren Sie die Funktion insertAfterNode in Ihrer Datei. Kopieren Sie dazu den + Implementieren Sie die Methode insertAfterNode in Ihrer Datei. Kopieren Sie dazu den folgenden Codeblock in die Datei {{filename}}, und ergänzen Sie die Methode insertAfterNode:

@@ -47,12 +47,12 @@

Aufgabe

self.insertAfterHead(newNode) def searchValue(self, value: int) -> ListNode: - current = self.head - while current is not None: - if current.value == value: - return current - current = current.next - return None + # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode + return + + def insertAfterNode(self, prevNode: ListNode | None, newNode: ListNode): + # TODO: Implementieren Sie diese Methode + return def printList(self): current = self.head From 236fa3334a9be1b815974132c3bfa783e2090522 Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 12:14:53 +0100 Subject: [PATCH 06/14] add linkedStack --- linkedStack/.cs50.yml | 10 ++ linkedStack/checks/__init__.py | 0 linkedStack/checks/main.py | 185 ++++++++++++++++++++++++ linkedStack/html/generate_png.py | 62 ++++++++ linkedStack/html/linkedStack.html | 221 +++++++++++++++++++++++++++++ linkedStack/html/params.json | 9 ++ linkedStack/html/stack_graphic.png | Bin 0 -> 11572 bytes linkedStack/html/template.html | 129 +++++++++++++++++ 8 files changed, 616 insertions(+) create mode 100644 linkedStack/.cs50.yml create mode 100644 linkedStack/checks/__init__.py create mode 100644 linkedStack/checks/main.py create mode 100644 linkedStack/html/generate_png.py create mode 100644 linkedStack/html/linkedStack.html create mode 100644 linkedStack/html/params.json create mode 100644 linkedStack/html/stack_graphic.png create mode 100644 linkedStack/html/template.html diff --git a/linkedStack/.cs50.yml b/linkedStack/.cs50.yml new file mode 100644 index 0000000..60f5b04 --- /dev/null +++ b/linkedStack/.cs50.yml @@ -0,0 +1,10 @@ +submit50: + files: &submit50_files + - !exclude "*" + - !include "*.py" + - !require linkedStack.py + +check50: + files: *submit50_files + checks: checks/main.py + \ No newline at end of file diff --git a/linkedStack/checks/__init__.py b/linkedStack/checks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/linkedStack/checks/main.py b/linkedStack/checks/main.py new file mode 100644 index 0000000..628a8c9 --- /dev/null +++ b/linkedStack/checks/main.py @@ -0,0 +1,185 @@ +import check50 +import check50.py + +FILE_NAME = "linkedStack.py" + + +@check50.check() +def exists(): + """linkedStack.py exists""" + check50.exists(FILE_NAME) + + +@check50.check(exists) +def compiles(): + """linkedStack.py compiles""" + check50.py.compile(FILE_NAME) + + +@check50.check(compiles) +def has_classes_and_methods(): + """Classes ListNode, LinkedList, Stack and methods push, pop, peek exist""" + module = check50.py.import_(FILE_NAME) + + if not hasattr(module, "ListNode"): + msg = f"Class `ListNode` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module, "LinkedList"): + msg = f"Class `LinkedList` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module, "Stack"): + msg = f"Class `Stack` not found in {FILE_NAME}" + raise check50.Failure(msg) + + stack_instance = module.Stack() + + if not hasattr(stack_instance, "push"): + msg = "Method `push` not found in Class `Stack`" + raise check50.Failure(msg) + if not hasattr(stack_instance, "pop"): + msg = "Method `pop` not found in Class `Stack`" + raise check50.Failure(msg) + if not hasattr(stack_instance, "peek"): + msg = "Method `peek` not found in Class `Stack`" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_methods) +def test_push_peek(): + """push adds element and peek returns it""" + module = check50.py.import_(FILE_NAME) + stack = module.Stack() + + stack.push(10) + top = stack.peek() + + if top != 10: + raise check50.Mismatch( + "10", str(top), help="peek should return the last pushed value" + ) + + +@check50.check(has_classes_and_methods) +def test_push_pop(): + """pop removes and returns the top element""" + module = check50.py.import_(FILE_NAME) + stack = module.Stack() + + stack.push(20) + popped = stack.pop() + + if popped != 20: + raise check50.Mismatch( + "20", str(popped), help="pop should return the last pushed value" + ) + + if stack.peek() is not None: + msg = "Stack should be empty after popping the only element" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_methods) +def test_lifo_order(): + """Stack follows LIFO (Last-In, First-Out) order""" + module = check50.py.import_(FILE_NAME) + stack = module.Stack() + + stack.push(1) + stack.push(2) + stack.push(3) + + if stack.pop() != 3: + msg = "First pop should return 3" + raise check50.Failure(msg) + if stack.pop() != 2: + msg = "Second pop should return 2" + raise check50.Failure(msg) + if stack.pop() != 1: + msg = "Third pop should return 1" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_methods) +def test_empty_stack(): + """pop and peek return None on empty stack""" + module = check50.py.import_(FILE_NAME) + stack = module.Stack() + + if stack.peek() is not None: + msg = "peek on empty stack should return None" + raise check50.Failure(msg) + + if stack.pop() is not None: + msg = "pop on empty stack should return None" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_methods) +def test_mixed_operations(): + """Stack handles mixed push and pop operations correctly""" + module = check50.py.import_(FILE_NAME) + stack = module.Stack() + + stack.push(10) + stack.push(20) + if stack.pop() != 20: + msg = "Pop should return 20" + raise check50.Failure(msg) + + stack.push(30) + if stack.pop() != 30: + msg = "Pop should return 30" + raise check50.Failure(msg) + + if stack.pop() != 10: + msg = "Pop should return 10" + raise check50.Failure(msg) + + if stack.pop() is not None: + msg = "Pop on empty stack should return None" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_methods) +def test_peek_idempotent(): + """peek returns the same value multiple times without removing it""" + module = check50.py.import_(FILE_NAME) + stack = module.Stack() + + stack.push(42) + + val1 = stack.peek() + val2 = stack.peek() + + if val1 != 42 or val2 != 42: + msg = "peek should return 42 consistently" + raise check50.Failure(msg) + + if stack.pop() != 42: + msg = "Item should still be on stack after peek" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_methods) +def test_independent_stacks(): + """Multiple Stack instances are independent""" + module = check50.py.import_(FILE_NAME) + stack1 = module.Stack() + stack2 = module.Stack() + + stack1.push(1) + stack2.push(2) + + if stack1.peek() != 1: + msg = "stack1 should have 1 at top" + raise check50.Failure(msg) + if stack2.peek() != 2: + msg = "stack2 should have 2 at top" + raise check50.Failure(msg) + + stack1.pop() + if stack2.peek() != 2: + msg = "stack2 should still have 2 after popping stack1" + raise check50.Failure(msg) diff --git a/linkedStack/html/generate_png.py b/linkedStack/html/generate_png.py new file mode 100644 index 0000000..50c1eb9 --- /dev/null +++ b/linkedStack/html/generate_png.py @@ -0,0 +1,62 @@ +import os +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle, FancyArrowPatch + +def draw_stack_colored(items, title="Stack - (Last In, First Out)"): + # smaller figure + smaller cells + w, h = 2.1, 0.6 + x0, y0 = 0.9, 0.6 + + fig, ax = plt.subplots(figsize=(4.0, 2.5)) + + # pastel-ish colors cycling + colors = ["#CFE8FF", "#DFF7D9", "#FFF2CC", "#F8D7DA", "#E7D7FF", "#D1F2EB"] + + # Draw stack cells (bottom to top) + for i, item in enumerate(items): + y = y0 + i * h + face = colors[i % len(colors)] + rect = Rectangle((x0, y), w, h, linewidth=1.8, edgecolor="black", facecolor=face) + ax.add_patch(rect) + ax.text(x0 + w/2, y + h/2, str(item), ha="center", va="center", fontsize=12, weight="bold") + + # Frame around stack + frame = Rectangle((x0, y0), w, max(len(items), 1)*h, fill=False, linewidth=1.8) + ax.add_patch(frame) + + # Top pointer + top_y = y0 + (len(items)-1)*h + h/2 if items else y0 + h/2 + arrow = FancyArrowPatch((x0 + w + 0.9, top_y), (x0 + w + 0.03, top_y), + arrowstyle='-|>', mutation_scale=14, linewidth=1.8, color="black") + ax.add_patch(arrow) + ax.text(x0 + w + 0.95, top_y, "top", ha="left", va="center", fontsize=10) + + # Optional push/pop mini labels + ax.text( + x0 + w + 0.85, + top_y + 0.35, + r"$\uparrow$ Push()", + ha="center", + va="center", + fontsize=9, + ) + ax.text( + x0 + w + 0.85, + top_y - 0.35, + r"$\downarrow$ Pop()", + ha="center", + va="center", + fontsize=9, + ) + + + # Styling + ax.set_xlim(0, x0 + w + 1.6) + ax.set_ylim(0, y0 + max(len(items), 1)*h + 0.9) + ax.axis("off") + + script_dir = os.path.dirname(os.path.abspath(__file__)) + plt.savefig(os.path.join(script_dir, "stack_graphic.png"), dpi=200, bbox_inches="tight") + + +draw_stack_colored(["14", "67", "43", "8"]) diff --git a/linkedStack/html/linkedStack.html b/linkedStack/html/linkedStack.html new file mode 100644 index 0000000..b8b4c8e --- /dev/null +++ b/linkedStack/html/linkedStack.html @@ -0,0 +1,221 @@ +

Stack mit verketteter Liste implementieren

+ +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) verwenden, um einen Stack (Stapel) zu implementieren. + Gegeben sind bereits die Klassen ListNode und LinkedList. + Ihre Aufgabe ist es, die Klasse Stack zu implementieren, die intern eine LinkedList verwendet, um die Stack-Operationen durchzuführen. +

+ +

Stack +

+ +

Aufgabe

+ +

+ Implementieren Sie die Klasse Stack mit den folgenden Methoden: +

    +
  • push(value): Legt einen Wert auf den Stack (fügt ihn am Anfang der Liste ein).
  • +
  • pop(): Entfernt das oberste Element vom Stack und gibt dessen Wert zurück. Gibt None zurück, wenn der Stack leer ist.
  • +
  • peek(): Gibt den Wert des obersten Elements zurück, ohne es zu entfernen. Gibt None zurück, wenn der Stack leer ist.
  • +
+ Nutzen Sie für die Implementierung die Methoden der Klasse LinkedList. +

+ + +

+ Erstellen Sie eine Datei mit dem Namen + linkedStack.py und + Implementieren Sie die Klasse Stack in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei linkedStack.py, + und ergänzen Sie die fehlenden Methoden: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+    def removeAfterHead(self):
+        # TODO: Eine hilfreiche Methode zum Erstellen eines Stacks
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+
+class Stack:
+    def __init__(self):
+        self.ll = LinkedList()
+
+    def push(self, value: int):
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+    def pop(self) -> int | None:
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+    def peek(self) -> int | None:
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+
+ + + + +

Bevor Sie beginnen

+

Melden Sie sich bei cs50.dev an, klicken Sie + auf Ihr Terminal und führen Sie cd ohne Parameter aus. Sie + sollten feststellen, dass der Prompt Ihres Terminals wie unten aussieht:

+
$
+

Als nächstes führen Sie

+
mkdir + linkedStack
+

aus, um einen Ordner namens linkedStack in Ihrem Codespace + zu erstellen.

+

Führen Sie anschließend

+
cd + linkedStack
+

aus, um in dieses Verzeichnis zu wechseln. Der Prompt Ihres Terminals sollte + nun linkedStack/$ anzeigen. + Jetzt können Sie

+
code + linkedStack.py
+

ausführen, um eine Datei namens linkedStack.py zu erstellen, in der Sie Ihr + Programm schreiben. +

+ +

So testen Sie Ihr Programm

+

So können Sie Ihr Programm manuell testen:

+ +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der Klasse Stack, führen Sie einige Operationen durch und überprüfen Sie die Ausgaben. +

+ +
+

+  stack = Stack()
+  
+  print("Push 10, 20, 30")
+  stack.push(10)
+  stack.push(20)
+  stack.push(30)
+  
+  print("Stack Inhalt (via LinkedList):")
+  stack.ll.printList()
+  # Erwartet: 30 -> 20 -> 10 -> None
+  
+  print(f"Peek: {stack.peek()}")
+  # Erwartet: 30
+  
+  print(f"Pop: {stack.pop()}")
+  # Erwartet: 30
+  
+  print("Stack Inhalt nach Pop:")
+  stack.ll.printList()
+  # Erwartet: 20 -> 10 -> None
+  
+  print(f"Pop: {stack.pop()}")
+  print(f"Pop: {stack.pop()}")
+  print(f"Pop (leer): {stack.pop()}")
+  # Erwartet: 20, 10, None
+
+
+ + + +

Sie können das folgende Kommando ausführen, um Ihren Code mit check50 zu überprüfen, einem Programm, das bei + der Abgabe verwendet wird um Ihren Code zu testen. Testen Sie Ihr Programm + aber auch selbst!

+
+ check50 HSDDigitalLabor/problems/adg2025/linkedStack +
+

+ Grüne Smileys :) + bedeuten, dass Ihr Programm einen Test bestanden hat! Rote + traurige Smileys :( + zeigen an, dass Ihr Programm etwas Unerwartetes ausgegeben + hat. Besuchen Sie die URL, die check50 + ausgibt, um zu sehen, welche Eingabe check50 + an Ihr Programm übergeben hat, welche Ausgabe erwartet wurde und welche + Ausgabe Ihr Programm tatsächlich geliefert hat. +

+ +

Abgabe

+

Führen Sie im Terminal den folgenden Befehl aus, um Ihre Arbeit einzureichen. +

+
+ submit50 HSDDigitalLabor/problems/adg2025/linkedStack +
+

+ Führen Sie diesen Befehl vor dem Fälligkeitsdatum aus. Sie können Ihre Lösung + nach dem ersten Einsenden noch ändern und erneut einreichen. Bewertet wird + die zuletzt eingereichte Version vor dem Fälligkeitsdatum. Nach Ablauf der + Frist können Sie technisch zwar noch Abgaben tätigen, Ihre Lösung wird jedoch + nicht mehr gewertet. Das Ergebnis kann ausschließlich mit obigem Befehl abgegeben + werden, eine Abgabe in Moodle ist nicht möglich. +

+ +

Markierung der Aufgabe als erledigt

+

Nach dem Einreichen der Lösung mit submit50, + nehmen Sie eine Pseudolösung in Moodle vor, indem Sie auf den Button + + weiter unten auf dieser Seite betätigen und in das sich öffnende Feld unter + "Texteingabe online" den folgenden Text ein: +

+
+ Mit submit50 abgegeben. +
+

Schließen Sie die Eingabe durch Klick auf den Button + unterhalb des Textfeldes ab. +

+

+ Zur besseren Übersicht markieren Sie das Problem linkedStack.py in Moodle als erledigt, + indem Sie ganz oben auf dieser Seite den Button +

+

+ + klicken. Daraufhin erscheint der Button + +

+ +

generated 2025-12-22 12:14:08

\ No newline at end of file diff --git a/linkedStack/html/params.json b/linkedStack/html/params.json new file mode 100644 index 0000000..d899a42 --- /dev/null +++ b/linkedStack/html/params.json @@ -0,0 +1,9 @@ +{ + "id": "linkedStack", + "title": "Stack mit verketteter Liste implementieren", + "foldername": "linkedStack", + "filename": "linkedStack.py", + "asciicast_id": "", + "check50_path": "HSDDigitalLabor/problems/adg2025/linkedStack", + "submit50_path": "HSDDigitalLabor/problems/adg2025/linkedStack" +} \ No newline at end of file diff --git a/linkedStack/html/stack_graphic.png b/linkedStack/html/stack_graphic.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6bc2a4780451bbdf18bea59db93e85ef6a0d9e GIT binary patch literal 11572 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 + In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) verwenden, um einen Stack (Stapel) zu implementieren. + Gegeben sind bereits die Klassen ListNode und LinkedList. + Ihre Aufgabe ist es, die Klasse Stack zu implementieren, die intern eine LinkedList verwendet, um die Stack-Operationen durchzuführen. +

+ +

Stack +

+ +

Aufgabe

+ +

+ Implementieren Sie die Klasse Stack mit den folgenden Methoden: +

    +
  • push(value): Legt einen Wert auf den Stack (fügt ihn am Anfang der Liste ein).
  • +
  • pop(): Entfernt das oberste Element vom Stack und gibt dessen Wert zurück. Gibt None zurück, wenn der Stack leer ist.
  • +
  • peek(): Gibt den Wert des obersten Elements zurück, ohne es zu entfernen. Gibt None zurück, wenn der Stack leer ist.
  • +
+ Nutzen Sie für die Implementierung die Methoden der Klasse LinkedList. +

+ + +

+ {% include "file.html" %} + Implementieren Sie die Klasse Stack in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei {{filename}}, + und ergänzen Sie die fehlenden Methoden: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+    def removeAfterHead(self):
+        # TODO: Eine hilfreiche Methode zum Erstellen eines Stacks
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+
+class Stack:
+    def __init__(self):
+        self.ll = LinkedList()
+
+    def push(self, value: int):
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+    def pop(self) -> int | None:
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+    def peek(self) -> int | None:
+        # TODO: Implementieren Sie diese Methode
+        pass
+
+
+ + + + +{% include "before_you_begin.html" %} + +{% include "how_to_test_head.html" %} + +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der Klasse Stack, führen Sie einige Operationen durch und überprüfen Sie die Ausgaben. +

+ +
+

+  stack = Stack()
+  
+  print("Push 10, 20, 30")
+  stack.push(10)
+  stack.push(20)
+  stack.push(30)
+  
+  print("Stack Inhalt (via LinkedList):")
+  stack.ll.printList()
+  # Erwartet: 30 -> 20 -> 10 -> None
+  
+  print(f"Peek: {stack.peek()}")
+  # Erwartet: 30
+  
+  print(f"Pop: {stack.pop()}")
+  # Erwartet: 30
+  
+  print("Stack Inhalt nach Pop:")
+  stack.ll.printList()
+  # Erwartet: 20 -> 10 -> None
+  
+  print(f"Pop: {stack.pop()}")
+  print(f"Pop: {stack.pop()}")
+  print(f"Pop (leer): {stack.pop()}")
+  # Erwartet: 20, 10, None
+
+
+ + + +{% include "how_to_test_auto.html" %} + +{% include "how_to_submit.html" %} + +{% include "how_to_mark_in_moodle.html" %} + +{% include "footer.html" %} \ No newline at end of file From bb0145946555439fec799d4aaf261a467803b6f9 Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 12:15:08 +0100 Subject: [PATCH 07/14] add linkedMerge --- linkedMerge/.cs50.yml | 10 + linkedMerge/checks/__init__.py | 0 linkedMerge/checks/main.py | 132 ++++++++++++++ linkedMerge/html/generate_png.py | 79 ++++++++ linkedMerge/html/linkedMerge.html | 221 +++++++++++++++++++++++ linkedMerge/html/linked_list_graphic.png | Bin 0 -> 44469 bytes linkedMerge/html/params.json | 9 + linkedMerge/html/template.html | 129 +++++++++++++ 8 files changed, 580 insertions(+) create mode 100644 linkedMerge/.cs50.yml create mode 100644 linkedMerge/checks/__init__.py create mode 100644 linkedMerge/checks/main.py create mode 100644 linkedMerge/html/generate_png.py create mode 100644 linkedMerge/html/linkedMerge.html create mode 100644 linkedMerge/html/linked_list_graphic.png create mode 100644 linkedMerge/html/params.json create mode 100644 linkedMerge/html/template.html diff --git a/linkedMerge/.cs50.yml b/linkedMerge/.cs50.yml new file mode 100644 index 0000000..6f1c3c2 --- /dev/null +++ b/linkedMerge/.cs50.yml @@ -0,0 +1,10 @@ +submit50: + files: &submit50_files + - !exclude "*" + - !include "*.py" + - !require linkedMerge.py + +check50: + files: *submit50_files + checks: checks/main.py + \ No newline at end of file diff --git a/linkedMerge/checks/__init__.py b/linkedMerge/checks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/linkedMerge/checks/main.py b/linkedMerge/checks/main.py new file mode 100644 index 0000000..18eab30 --- /dev/null +++ b/linkedMerge/checks/main.py @@ -0,0 +1,132 @@ +import check50 +import check50.py + +FILE_NAME = "linkedMerge.py" + + +def to_list(ll): + """LinkedList -> Python-Liste (mit Zyklus-Schutz).""" + out = [] + if ll is None or ll.head is None: + return out + + cur = ll.head + seen = 0 + while cur is not None: + out.append(cur.value) + cur = cur.next + seen += 1 + if seen > 10_000: + msg = f"Linked list appears to have a cycle (more than {seen} nodes)" + raise check50.Failure(msg) + return out + + +def build_ll(module, values): + """Erzeugt eine LinkedList über FromPythonlist (wie in der Aufgabe vorgegeben).""" + ll = module.LinkedList() + ll.FromPythonlist(values) + return ll + + +@check50.check() +def exists(): + """linkedMerge.py exists""" + check50.exists(FILE_NAME) + + +@check50.check(exists) +def compiles(): + """linkedMerge.py compiles""" + check50.py.compile(FILE_NAME) + + +@check50.check(compiles) +def has_classes_and_function(): + """ListNode, LinkedList and mergeTwoLists exist""" + module = check50.py.import_(FILE_NAME) + + if not hasattr(module, "ListNode"): + msg = f"Class `ListNode` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module, "LinkedList"): + msg = f"Class `LinkedList` not found in {FILE_NAME}" + raise check50.Failure(msg) + + if not hasattr(module, "mergeTwoLists"): + msg = f"Function `mergeTwoLists` not found in {FILE_NAME}" + raise check50.Failure(msg) + + +@check50.check(has_classes_and_function) +def test_merge_two_sorted_lists(): + """mergeTwoLists merges two sorted lists correctly""" + module = check50.py.import_(FILE_NAME) + + # List 1: 1 -> 2 -> 4 (Input [4, 2, 1] because FromPythonlist reverses) + ll1 = build_ll(module, [4, 2, 1]) + # List 2: 1 -> 3 -> 4 (Input [4, 3, 1]) + ll2 = build_ll(module, [4, 3, 1]) + + merged = module.mergeTwoLists(ll1, ll2) + + expected = [1, 1, 2, 3, 4, 4] + actual = to_list(merged) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_function) +def test_merge_empty_lists(): + """mergeTwoLists handles two empty lists""" + module = check50.py.import_(FILE_NAME) + + ll1 = module.LinkedList() + ll2 = module.LinkedList() + + merged = module.mergeTwoLists(ll1, ll2) + + expected = [] + actual = to_list(merged) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_function) +def test_merge_one_empty_list(): + """mergeTwoLists handles one empty list""" + module = check50.py.import_(FILE_NAME) + + ll1 = module.LinkedList() + # List 2: 0 (Input [0]) + ll2 = build_ll(module, [0]) + + merged = module.mergeTwoLists(ll1, ll2) + + expected = [0] + actual = to_list(merged) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) + + +@check50.check(has_classes_and_function) +def test_merge_different_lengths(): + """mergeTwoLists merges lists of different lengths""" + module = check50.py.import_(FILE_NAME) + + # List 1: 2 (Input [2]) + ll1 = build_ll(module, [2]) + # List 2: 1 -> 3 (Input [3, 1]) + ll2 = build_ll(module, [3, 1]) + + merged = module.mergeTwoLists(ll1, ll2) + + expected = [1, 2, 3] + actual = to_list(merged) + + if actual != expected: + raise check50.Mismatch(str(expected), str(actual)) diff --git a/linkedMerge/html/generate_png.py b/linkedMerge/html/generate_png.py new file mode 100644 index 0000000..4c25fc0 --- /dev/null +++ b/linkedMerge/html/generate_png.py @@ -0,0 +1,79 @@ +import matplotlib.pyplot as plt +from matplotlib.patches import Circle, FancyArrowPatch +import numpy as np +import os + + +def draw_node(ax, x, y, text, facecolor=None, radius=0.35): + c = Circle( + (x, y), + radius=radius, + linewidth=2, + edgecolor="black", + facecolor=facecolor if facecolor is not None else "white", + ) + ax.add_patch(c) + ax.text(x, y, str(text), ha="center", va="center", fontsize=14, weight="bold") + return c + + +def draw_arrow(ax, x1, y1, x2, y2, radius=0.35): + # Shorten arrow so it touches the circle boundaries + v = np.array([x2 - x1, y2 - y1], dtype=float) + d = np.linalg.norm(v) + if d == 0: + return + u = v / d + start = np.array([x1, y1]) + u * radius + end = np.array([x2, y2]) - u * radius + arrow = FancyArrowPatch( + start, end, arrowstyle="-|>", mutation_scale=15, linewidth=2, color="black" + ) + ax.add_patch(arrow) + + +# Colors (chosen to match the example) +RED = "#e53935" +PURPLE = "#7e57c2" + +fig, ax = plt.subplots(figsize=(10, 4)) + +# --- Top list: 1 -> 2 -> 4 (red) --- +top_y = 2.3 +top_xs = [1.5, 3.5, 5.5] +top_vals = [1, 2, 4] +for x, v in zip(top_xs, top_vals): + draw_node(ax, x, top_y, v, facecolor=RED) +for x1, x2 in zip(top_xs[:-1], top_xs[1:]): + draw_arrow(ax, x1, top_y, x2, top_y) + +# --- Middle list: 1 -> 3 -> 4 (purple) --- +mid_y = 1.2 +mid_xs = [1.5, 3.5, 5.5] +mid_vals = [1, 3, 4] +for x, v in zip(mid_xs, mid_vals): + draw_node(ax, x, mid_y, v, facecolor=PURPLE) +for x1, x2 in zip(mid_xs[:-1], mid_xs[1:]): + draw_arrow(ax, x1, mid_y, x2, mid_y) + +# Separator line +ax.plot([0.5, 6.5], [0.55, 0.55], linewidth=2, color="black") + +# --- Bottom merged list: 1(p) -> 1(r) -> 2(r) -> 3(p) -> 4(r) -> 4(p) --- +bot_y = -0.2 +bot_xs = [0.8, 1.9, 3.0, 4.1, 5.2, 6.3] +bot_vals = [1, 1, 2, 3, 4, 4] +bot_colors = [PURPLE, RED, RED, PURPLE, RED, PURPLE] +for x, v, col in zip(bot_xs, bot_vals, bot_colors): + draw_node(ax, x, bot_y, v, facecolor=col) +for x1, x2 in zip(bot_xs[:-1], bot_xs[1:]): + draw_arrow(ax, x1, bot_y, x2, bot_y) + +# Layout +ax.set_aspect("equal") +ax.set_xlim(0, 7.1) +ax.set_ylim(-1.0, 3.0) +ax.axis("off") + +script_dir = os.path.dirname(os.path.abspath(__file__)) +plt.savefig(os.path.join(script_dir, "linked_list_graphic.png"), dpi=200, bbox_inches="tight") diff --git a/linkedMerge/html/linkedMerge.html b/linkedMerge/html/linkedMerge.html new file mode 100644 index 0000000..42d40e9 --- /dev/null +++ b/linkedMerge/html/linkedMerge.html @@ -0,0 +1,221 @@ +

Linked List: Füge zwei sortierete verkettete Listen zusammen

+ +

+ In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. + Ihre Aufgabe ist es, die Funktion mergeTwoLists(list1, list2) + zu implementieren, die zwei sortierte verkettete Listen (Objekte vom Typ LinkedList) zu einer einzigen sortierten Liste zusammenfügt. +

+ +

merge +

+ +

Aufgabe

+ +

+ Implementieren Sie die Funktion mergeTwoLists(list1, list2) außerhalb der Klasse LinkedList. + Die Funktion erhält zwei sortierte LinkedList-Objekte, list1 und list2. + Sie soll die beiden Listen so verknüpfen, dass eine einzige, aufsteigend sortierte Liste entsteht. + Geben Sie ein neues LinkedList-Objekt zurück, das die gemergte Liste enthält. +

+ + +

+ Erstellen Sie eine Datei mit dem Namen + linkedMerge.py und + Implementieren Sie die Funktion mergeTwoLists in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei linkedMerge.py, + und ergänzen Sie die Funktion mergeTwoLists: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def searchValue(self, value: int) -> ListNode | None:
+        # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode
+        return None
+
+    def insertAfterNode(self, prevNode: ListNode | None, newNode: ListNode):
+        # Siehe Problem 'linkedAfter.py' für die Implementierung dieser Methode
+        return
+
+    def insertAfterLast(self, newNode: ListNode):
+        # Siehe Problem 'linkedInsert.py' für die Implementierung dieser Methode
+        return
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+def mergeTwoLists(list1: LinkedList, list2: LinkedList) -> LinkedList:
+    mergedList = LinkedList()
+    # TODO: Implementieren Sie diese Funktion
+    return mergedList
+
+
+ + + + +

Bevor Sie beginnen

+

Melden Sie sich bei cs50.dev an, klicken Sie + auf Ihr Terminal und führen Sie cd ohne Parameter aus. Sie + sollten feststellen, dass der Prompt Ihres Terminals wie unten aussieht:

+
$
+

Als nächstes führen Sie

+
mkdir + linkedMerge
+

aus, um einen Ordner namens linkedMerge in Ihrem Codespace + zu erstellen.

+

Führen Sie anschließend

+
cd + linkedMerge
+

aus, um in dieses Verzeichnis zu wechseln. Der Prompt Ihres Terminals sollte + nun linkedMerge/$ anzeigen. + Jetzt können Sie

+
code + linkedMerge.py
+

ausführen, um eine Datei namens linkedMerge.py zu erstellen, in der Sie Ihr + Programm schreiben. +

+ +

So testen Sie Ihr Programm

+

So können Sie Ihr Programm manuell testen:

+ +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, fügen Sie Elemente hinzu und rufen Sie dann Ihre Funktion mergeTwoLists auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat. +

+ +
+

+  # Erstellen zweier sortierter Listen
+  # Hinweis: FromPythonlist fügt vorne ein, daher umgekehrte Reihenfolge für korrekte Sortierung
+  
+  # Liste 1: 1 -> 2 -> 4
+  ll1 = LinkedList()
+  ll1.FromPythonlist([4, 2, 1])
+  
+  # Liste 2: 1 -> 3 -> 4
+  ll2 = LinkedList()
+  ll2.FromPythonlist([4, 3, 1])
+
+  print("Liste 1:")
+  ll1.printList()
+  print("Liste 2:")
+  ll2.printList()
+
+  # Mergen der Listen
+  merged_list = mergeTwoLists(ll1, ll2)
+
+  print("Gemergte Liste:")
+  merged_list.printList()
+  # Erwartete Ausgabe: 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> None
+
+
+ + + +

Sie können das folgende Kommando ausführen, um Ihren Code mit check50 zu überprüfen, einem Programm, das bei + der Abgabe verwendet wird um Ihren Code zu testen. Testen Sie Ihr Programm + aber auch selbst!

+
+ check50 HSDDigitalLabor/problems/adg2025/linkedMerge +
+

+ Grüne Smileys :) + bedeuten, dass Ihr Programm einen Test bestanden hat! Rote + traurige Smileys :( + zeigen an, dass Ihr Programm etwas Unerwartetes ausgegeben + hat. Besuchen Sie die URL, die check50 + ausgibt, um zu sehen, welche Eingabe check50 + an Ihr Programm übergeben hat, welche Ausgabe erwartet wurde und welche + Ausgabe Ihr Programm tatsächlich geliefert hat. +

+ +

Abgabe

+

Führen Sie im Terminal den folgenden Befehl aus, um Ihre Arbeit einzureichen. +

+
+ submit50 HSDDigitalLabor/problems/adg2025/linkedMerge +
+

+ Führen Sie diesen Befehl vor dem Fälligkeitsdatum aus. Sie können Ihre Lösung + nach dem ersten Einsenden noch ändern und erneut einreichen. Bewertet wird + die zuletzt eingereichte Version vor dem Fälligkeitsdatum. Nach Ablauf der + Frist können Sie technisch zwar noch Abgaben tätigen, Ihre Lösung wird jedoch + nicht mehr gewertet. Das Ergebnis kann ausschließlich mit obigem Befehl abgegeben + werden, eine Abgabe in Moodle ist nicht möglich. +

+ +

Markierung der Aufgabe als erledigt

+

Nach dem Einreichen der Lösung mit submit50, + nehmen Sie eine Pseudolösung in Moodle vor, indem Sie auf den Button + + weiter unten auf dieser Seite betätigen und in das sich öffnende Feld unter + "Texteingabe online" den folgenden Text ein: +

+
+ Mit submit50 abgegeben. +
+

Schließen Sie die Eingabe durch Klick auf den Button + unterhalb des Textfeldes ab. +

+

+ Zur besseren Übersicht markieren Sie das Problem linkedMerge.py in Moodle als erledigt, + indem Sie ganz oben auf dieser Seite den Button +

+

+ + klicken. Daraufhin erscheint der Button + +

+ +

generated 2025-12-22 11:31:27

\ No newline at end of file diff --git a/linkedMerge/html/linked_list_graphic.png b/linkedMerge/html/linked_list_graphic.png new file mode 100644 index 0000000000000000000000000000000000000000..ac8e5e813ef8cd819840bb32fb80bd6cccda991e GIT binary patch literal 44469 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

0; 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 + In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. + Gegeben sind bereits die Klassen ListNode und LinkedList, sowie einige + Hilfsmethoden: FromPythonlist(inputList) zur Erzeugung einer verketteten Liste aus einer Python-Liste + und printList() zur Ausgabe der Liste. + Ihre Aufgabe ist es, die Funktion mergeTwoLists(list1, list2) + zu implementieren, die zwei sortierte verkettete Listen (Objekte vom Typ LinkedList) zu einer einzigen sortierten Liste zusammenfügt. +

+ +

merge +

+ +

Aufgabe

+ +

+ Implementieren Sie die Funktion mergeTwoLists(list1, list2) außerhalb der Klasse LinkedList. + Die Funktion erhält zwei sortierte LinkedList-Objekte, list1 und list2. + Sie soll die beiden Listen so verknüpfen, dass eine einzige, aufsteigend sortierte Liste entsteht. + Geben Sie ein neues LinkedList-Objekt zurück, das die gemergte Liste enthält. +

+ + +

+ {% include "file.html" %} + Implementieren Sie die Funktion mergeTwoLists in Ihrer Datei. Kopieren Sie dazu den + folgenden Codeblock in die Datei {{filename}}, + und ergänzen Sie die Funktion mergeTwoLists: +

+ +
+

+class ListNode:
+    def __init__(self, value: int):
+        self.value: int = value
+        self.next: ListNode | None = None
+
+
+class LinkedList:
+    def __init__(self):
+        self.head = None
+
+    def FromPythonlist(self, inputList: list[int]):
+        for val in inputList:
+            newNode = ListNode(value=val)
+            self.insertAfterHead(newNode)
+
+    def printList(self):
+        current = self.head
+        while current is not None:
+            print(current.value, end=" -> ")
+            current = current.next
+        print("None")
+
+    def searchValue(self, value: int) -> ListNode | None:
+        # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode
+        return None
+
+    def insertAfterNode(self, prevNode: ListNode | None, newNode: ListNode):
+        # Siehe Problem 'linkedAfter.py' für die Implementierung dieser Methode
+        return
+
+    def insertAfterLast(self, newNode: ListNode):
+        # Siehe Problem 'linkedInsert.py' für die Implementierung dieser Methode
+        return
+
+    def insertAfterHead(self, newNode: ListNode):
+        newNode.next = self.head
+        self.head = newNode
+
+def mergeTwoLists(list1: LinkedList, list2: LinkedList) -> LinkedList:
+    mergedList = LinkedList()
+    # TODO: Implementieren Sie diese Funktion
+    return mergedList
+
+
+ + + + +{% include "before_you_begin.html" %} + +{% include "how_to_test_head.html" %} + +

+ Testen Sie Ihre Implementierung, indem Sie das Skript ausführen. + Erstellen Sie dazu eine Instanz der LinkedList, fügen Sie Elemente hinzu und rufen Sie dann Ihre Funktion mergeTwoLists auf. + Überprüfen Sie anschließend, ob die Liste die erwartete Struktur hat. +

+ +
+

+  # Erstellen zweier sortierter Listen
+  # Hinweis: FromPythonlist fügt vorne ein, daher umgekehrte Reihenfolge für korrekte Sortierung
+  
+  # Liste 1: 1 -> 2 -> 4
+  ll1 = LinkedList()
+  ll1.FromPythonlist([4, 2, 1])
+  
+  # Liste 2: 1 -> 3 -> 4
+  ll2 = LinkedList()
+  ll2.FromPythonlist([4, 3, 1])
+
+  print("Liste 1:")
+  ll1.printList()
+  print("Liste 2:")
+  ll2.printList()
+
+  # Mergen der Listen
+  merged_list = mergeTwoLists(ll1, ll2)
+
+  print("Gemergte Liste:")
+  merged_list.printList()
+  # Erwartete Ausgabe: 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> None
+
+
+ + + +{% include "how_to_test_auto.html" %} + +{% include "how_to_submit.html" %} + +{% include "how_to_mark_in_moodle.html" %} + +{% include "footer.html" %} \ No newline at end of file From bade0ebf49d62c36430cf66e1341b9a2b3056437 Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 12:15:33 +0100 Subject: [PATCH 08/14] update linkedRemove --- linkedRemove/html/template.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedRemove/html/template.html b/linkedRemove/html/template.html index 4c0be5a..0c93d47 100644 --- a/linkedRemove/html/template.html +++ b/linkedRemove/html/template.html @@ -54,7 +54,7 @@

Aufgabe

def insertAfterLast(self, newNode: ListNode): # Siehe Problem 'linkedInsert.py' für die Implementierung dieser Methode - pass + return def removeElement(self, node: ListNode): # TODO: Implementieren Sie diese Methode From e1706f42c4f72a2b386560e21ce5f8073670c1be Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 12:17:59 +0100 Subject: [PATCH 09/14] format with Ruff --- linkedRemove/checks/main.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/linkedRemove/checks/main.py b/linkedRemove/checks/main.py index d6c5b8e..90d7518 100644 --- a/linkedRemove/checks/main.py +++ b/linkedRemove/checks/main.py @@ -66,8 +66,10 @@ def test_remove_middle(): # Find node with value 20 node_to_remove = ll.searchValue(20) if node_to_remove is None: - msg = ("searchValue(20) returned None, but 20 should be in the list. " - "Cannot test removeElement.") + msg = ( + "searchValue(20) returned None, but 20 should be in the list. " + "Cannot test removeElement." + ) raise check50.Failure(msg) ll.removeElement(node_to_remove) @@ -89,8 +91,10 @@ def test_remove_head(): # Find node with value 30 (head) node_to_remove = ll.searchValue(30) if node_to_remove is None: - msg = ("searchValue(30) returned None, but 30 should be in the list. " - "Cannot test removeElement.") + msg = ( + "searchValue(30) returned None, but 30 should be in the list. " + "Cannot test removeElement." + ) raise check50.Failure(msg) ll.removeElement(node_to_remove) @@ -112,8 +116,10 @@ def test_remove_tail(): # Find node with value 10 (tail) node_to_remove = ll.searchValue(10) if node_to_remove is None: - msg = ("searchValue(10) returned None, but 10 should be in the list. " - "Cannot test removeElement.") + msg = ( + "searchValue(10) returned None, but 10 should be in the list. " + "Cannot test removeElement." + ) raise check50.Failure(msg) ll.removeElement(node_to_remove) From 7e1c5a6e5029e85923ae3bad9012e9f50444fba4 Mon Sep 17 00:00:00 2001 From: Markus Date: Mon, 22 Dec 2025 12:19:16 +0100 Subject: [PATCH 10/14] Update linkedMerge/html/linkedMerge.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- linkedMerge/html/linkedMerge.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedMerge/html/linkedMerge.html b/linkedMerge/html/linkedMerge.html index 42d40e9..f5fd59a 100644 --- a/linkedMerge/html/linkedMerge.html +++ b/linkedMerge/html/linkedMerge.html @@ -1,4 +1,4 @@ -

Linked List: Füge zwei sortierete verkettete Listen zusammen

+

Linked List: Füge zwei sortierte verkettete Listen zusammen

In dieser Übung werden Sie die Datenstruktur Verkettete Liste (Linked List) erweitern. From d652a21e3d83ab993ae7ad4625295989342603a6 Mon Sep 17 00:00:00 2001 From: Markus Date: Mon, 22 Dec 2025 12:20:03 +0100 Subject: [PATCH 11/14] Update linkedMerge/html/params.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- linkedMerge/html/params.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedMerge/html/params.json b/linkedMerge/html/params.json index 6a8723d..6630cee 100644 --- a/linkedMerge/html/params.json +++ b/linkedMerge/html/params.json @@ -1,6 +1,6 @@ { "id": "linkedMerge", - "title": "Linked List: Füge zwei sortierete verkettete Listen zusammen", + "title": "Linked List: Füge zwei sortierte verkettete Listen zusammen", "foldername": "linkedMerge", "filename": "linkedMerge.py", "asciicast_id": "", From 83c18b4845948230794d8cda1f965903aa579e0a Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 12:23:47 +0100 Subject: [PATCH 12/14] format with Ruff --- linkedMerge/html/generate_png.py | 4 +++- linkedStack/html/generate_png.py | 36 ++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/linkedMerge/html/generate_png.py b/linkedMerge/html/generate_png.py index 4c25fc0..feecee9 100644 --- a/linkedMerge/html/generate_png.py +++ b/linkedMerge/html/generate_png.py @@ -76,4 +76,6 @@ def draw_arrow(ax, x1, y1, x2, y2, radius=0.35): ax.axis("off") script_dir = os.path.dirname(os.path.abspath(__file__)) -plt.savefig(os.path.join(script_dir, "linked_list_graphic.png"), dpi=200, bbox_inches="tight") +plt.savefig( + os.path.join(script_dir, "linked_list_graphic.png"), dpi=200, bbox_inches="tight" +) diff --git a/linkedStack/html/generate_png.py b/linkedStack/html/generate_png.py index 50c1eb9..3f5aceb 100644 --- a/linkedStack/html/generate_png.py +++ b/linkedStack/html/generate_png.py @@ -2,6 +2,7 @@ import matplotlib.pyplot as plt from matplotlib.patches import Rectangle, FancyArrowPatch + def draw_stack_colored(items, title="Stack - (Last In, First Out)"): # smaller figure + smaller cells w, h = 2.1, 0.6 @@ -16,18 +17,34 @@ def draw_stack_colored(items, title="Stack - (Last In, First Out)"): for i, item in enumerate(items): y = y0 + i * h face = colors[i % len(colors)] - rect = Rectangle((x0, y), w, h, linewidth=1.8, edgecolor="black", facecolor=face) + rect = Rectangle( + (x0, y), w, h, linewidth=1.8, edgecolor="black", facecolor=face + ) ax.add_patch(rect) - ax.text(x0 + w/2, y + h/2, str(item), ha="center", va="center", fontsize=12, weight="bold") + ax.text( + x0 + w / 2, + y + h / 2, + str(item), + ha="center", + va="center", + fontsize=12, + weight="bold", + ) # Frame around stack - frame = Rectangle((x0, y0), w, max(len(items), 1)*h, fill=False, linewidth=1.8) + frame = Rectangle((x0, y0), w, max(len(items), 1) * h, fill=False, linewidth=1.8) ax.add_patch(frame) # Top pointer - top_y = y0 + (len(items)-1)*h + h/2 if items else y0 + h/2 - arrow = FancyArrowPatch((x0 + w + 0.9, top_y), (x0 + w + 0.03, top_y), - arrowstyle='-|>', mutation_scale=14, linewidth=1.8, color="black") + top_y = y0 + (len(items) - 1) * h + h / 2 if items else y0 + h / 2 + arrow = FancyArrowPatch( + (x0 + w + 0.9, top_y), + (x0 + w + 0.03, top_y), + arrowstyle="-|>", + mutation_scale=14, + linewidth=1.8, + color="black", + ) ax.add_patch(arrow) ax.text(x0 + w + 0.95, top_y, "top", ha="left", va="center", fontsize=10) @@ -49,14 +66,15 @@ def draw_stack_colored(items, title="Stack - (Last In, First Out)"): fontsize=9, ) - # Styling ax.set_xlim(0, x0 + w + 1.6) - ax.set_ylim(0, y0 + max(len(items), 1)*h + 0.9) + ax.set_ylim(0, y0 + max(len(items), 1) * h + 0.9) ax.axis("off") script_dir = os.path.dirname(os.path.abspath(__file__)) - plt.savefig(os.path.join(script_dir, "stack_graphic.png"), dpi=200, bbox_inches="tight") + plt.savefig( + os.path.join(script_dir, "stack_graphic.png"), dpi=200, bbox_inches="tight" + ) draw_stack_colored(["14", "67", "43", "8"]) From 0ba9f90c09855a3c2c5be0fb6cb180b331f30aac Mon Sep 17 00:00:00 2001 From: Markus Date: Mon, 22 Dec 2025 12:27:16 +0100 Subject: [PATCH 13/14] Update linkedAfter/html/template.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- linkedAfter/html/template.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedAfter/html/template.html b/linkedAfter/html/template.html index 44a8460..fe136b9 100644 --- a/linkedAfter/html/template.html +++ b/linkedAfter/html/template.html @@ -46,7 +46,7 @@

Aufgabe

newNode = ListNode(value=val) self.insertAfterHead(newNode) - def searchValue(self, value: int) -> ListNode: + def searchValue(self, value: int) -> ListNode | None: # Siehe Problem 'linkedFind.py' für die Implementierung dieser Methode return From 7e2612e3cee933fccdeb2664cda560fc78038b17 Mon Sep 17 00:00:00 2001 From: Markus Machmerth Date: Mon, 22 Dec 2025 12:28:55 +0100 Subject: [PATCH 14/14] update LinkedAfter html --- linkedAfter/html/linkedAfter.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedAfter/html/linkedAfter.html b/linkedAfter/html/linkedAfter.html index c778136..fb71ea7 100644 --- a/linkedAfter/html/linkedAfter.html +++ b/linkedAfter/html/linkedAfter.html @@ -201,4 +201,4 @@

Markierung der Aufgabe als erledigt

-

generated 2025-12-22 10:29:48

\ No newline at end of file +

generated 2025-12-22 12:27:59

\ No newline at end of file