From e5fab1bec57911797f02b1ee9982d8fbdf64565d Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sat, 23 May 2026 22:06:24 +0200 Subject: [PATCH] uebung_03: implement Task 1 (IntLinkedList) and Task 2 (generic LinkedList) Doubly linked list with head/tail references for int values (Task 1) and a generic LinkedList/ListNode version (Task 2). Both implement add, get, remove, getSize with O(n/2) walks via the closer-end heuristic. Includes IntLinkedListTest covering empty, single-element, head/tail/middle removal, and out-of-bounds cases. Co-Authored-By: Claude Opus 4.7 (1M context) --- uebung_03/src/IntLinkedList.java | 110 ++++++++++++++++++ uebung_03/src/IntLinkedListTest.java | 166 +++++++++++++++++++++++++++ uebung_03/src/IntListNode.java | 11 ++ uebung_03/src/LinkedList.java | 100 ++++++++++++++++ uebung_03/src/ListNode.java | 11 ++ 5 files changed, 398 insertions(+) create mode 100644 uebung_03/src/IntLinkedList.java create mode 100644 uebung_03/src/IntLinkedListTest.java create mode 100644 uebung_03/src/IntListNode.java create mode 100644 uebung_03/src/LinkedList.java create mode 100644 uebung_03/src/ListNode.java diff --git a/uebung_03/src/IntLinkedList.java b/uebung_03/src/IntLinkedList.java new file mode 100644 index 0000000..a860083 --- /dev/null +++ b/uebung_03/src/IntLinkedList.java @@ -0,0 +1,110 @@ +public class IntLinkedList { + private IntListNode headNode; + private IntListNode tailNode; + private int size; + public IntLinkedList() { + headNode = null; + tailNode = null; + size = 0; + } + public void add(int value) { + if (headNode == null) { + headNode = new IntListNode(value, null, null); + tailNode = headNode; + + } + else { + + IntListNode newNode = new IntListNode(value, null, null); + newNode.prevNode = tailNode; + tailNode.nextNode = newNode; + tailNode = newNode; + } + size++; + } + public int getSize() { + return size; + } + public int get(int index){ + if (index < 0 || index > (size - 1)){ + throw new IllegalArgumentException("Index Out of Bounds"); + } + if (index < size/2){ + int pos = 0; + IntListNode curNode = headNode; + while (pos < index){ + curNode = curNode.nextNode; + pos++; + } + return curNode.value; + } + else { + int pos = size - 1; + IntListNode curNode = tailNode; + while (pos > index){ + curNode = curNode.prevNode; + pos --; + } + return curNode.value; + } + } + + public void remove(int index){ + if (index < 0 || index > (size - 1)){ + throw new IllegalArgumentException("Index Out of Bounds"); + } + if(index == 0){ + if(size == 1){ + headNode = null; + tailNode = null; + } + else { + headNode = headNode.nextNode; + headNode.prevNode = null; + } + size --; + return; + } + if (index == (size - 1)){ + + tailNode = tailNode.prevNode; + tailNode.nextNode = null; + size --; + return; + } + + if (index < size/2){ + int pos = 0; + IntListNode curNode = headNode; + while (pos < index){ + curNode = curNode.nextNode; + pos++; + } + // Set the prev of the following node so pos +1 + curNode.nextNode.prevNode = curNode.prevNode; + + // Then set the next of the previous node; + curNode.prevNode.nextNode = curNode.nextNode; + + size--; + + } + else { + int pos = size - 1; + IntListNode curNode = tailNode; + while (pos > index){ + curNode = curNode.prevNode; + pos --; + } + // Set the prev of the following node so pos +1 + curNode.nextNode.prevNode = curNode.prevNode; + + // Then set the next of the previous node; + curNode.prevNode.nextNode = curNode.nextNode; + + size--; + } + } + + +} \ No newline at end of file diff --git a/uebung_03/src/IntLinkedListTest.java b/uebung_03/src/IntLinkedListTest.java new file mode 100644 index 0000000..bd975f8 --- /dev/null +++ b/uebung_03/src/IntLinkedListTest.java @@ -0,0 +1,166 @@ +public class IntLinkedListTest { + + static int passed = 0; + static int failed = 0; + + public static void main(String[] args) { + testEmpty(); + testAddOne(); + testAddMany(); + testGetAllPositions(); + testGetFromBothSides(); + testGetOutOfBounds(); + + testRemoveOnlyElement(); + testRemoveHead(); + testRemoveTail(); + testRemoveMiddleHeadWalk(); + testRemoveMiddleTailWalk(); + testRemoveOutOfBounds(); + testRemoveThenAdd(); + testRemoveAllSequentially(); + + System.out.println(); + System.out.println("Passed: " + passed + " / " + (passed + failed)); + } + + static void testEmpty() { + IntLinkedList l = new IntLinkedList(); + check("empty size", l.getSize(), 0); + } + + static void testAddOne() { + IntLinkedList l = new IntLinkedList(); + l.add(42); + check("size after 1 add", l.getSize(), 1); + check("get(0) after 1 add", l.get(0), 42); + } + + static void testAddMany() { + IntLinkedList l = new IntLinkedList(); + for (int i = 0; i < 5; i++) l.add(i * 10); + check("size after 5 adds", l.getSize(), 5); + } + + static void testGetAllPositions() { + IntLinkedList l = new IntLinkedList(); + int[] vals = {10, 20, 30, 40, 50, 60}; + for (int v : vals) l.add(v); + for (int i = 0; i < vals.length; i++) { + check("get(" + i + ")", l.get(i), vals[i]); + } + } + + static void testGetFromBothSides() { + // size=4 -> indices 0,1 walk from head; 2,3 walk from tail + IntLinkedList l = new IntLinkedList(); + l.add(1); l.add(2); l.add(3); l.add(4); + check("get(0) head-walk", l.get(0), 1); + check("get(1) head-walk", l.get(1), 2); + check("get(2) tail-walk", l.get(2), 3); + check("get(3) tail-walk", l.get(3), 4); + } + + static void testGetOutOfBounds() { + IntLinkedList l = new IntLinkedList(); + l.add(1); l.add(2); + try { l.get(-1); System.out.println("FAIL get(-1) should throw"); failed++; } + catch (IllegalArgumentException e) { System.out.println("PASS get(-1) throws"); passed++; } + try { l.get(2); System.out.println("FAIL get(size) should throw"); failed++; } + catch (IllegalArgumentException e) { System.out.println("PASS get(size) throws"); passed++; } + } + + static void testRemoveOnlyElement() { + IntLinkedList l = new IntLinkedList(); + l.add(99); + l.remove(0); + check("remove only: size", l.getSize(), 0); + // Re-add to make sure head/tail were properly reset + l.add(5); + check("remove only then add: size", l.getSize(), 1); + check("remove only then add: get(0)", l.get(0), 5); + } + + static void testRemoveHead() { + IntLinkedList l = new IntLinkedList(); + l.add(1); l.add(2); l.add(3); + l.remove(0); + check("remove head: size", l.getSize(), 2); + check("remove head: get(0)", l.get(0), 2); + check("remove head: get(1)", l.get(1), 3); // uses tail-walk -> tests prev chain + } + + static void testRemoveTail() { + IntLinkedList l = new IntLinkedList(); + l.add(1); l.add(2); l.add(3); + l.remove(2); + check("remove tail: size", l.getSize(), 2); + check("remove tail: get(0)", l.get(0), 1); + check("remove tail: get(1)", l.get(1), 2); // tail-walk -> new tail must be correct + } + + static void testRemoveMiddleHeadWalk() { + // size=4, remove index 1 (< size/2 -> head-walk branch) + IntLinkedList l = new IntLinkedList(); + l.add(1); l.add(2); l.add(3); l.add(4); + l.remove(1); + check("remove mid (head-walk): size", l.getSize(), 3); + check("remove mid (head-walk): get(0)", l.get(0), 1); + check("remove mid (head-walk): get(1)", l.get(1), 3); + check("remove mid (head-walk): get(2)", l.get(2), 4); + } + + static void testRemoveMiddleTailWalk() { + // size=6, remove index 4 (>= size/2 -> tail-walk branch) + IntLinkedList l = new IntLinkedList(); + l.add(1); l.add(2); l.add(3); l.add(4); l.add(5); l.add(6); + l.remove(4); + check("remove mid (tail-walk): size", l.getSize(), 5); + check("remove mid (tail-walk): get(0)", l.get(0), 1); + check("remove mid (tail-walk): get(3)", l.get(3), 4); + check("remove mid (tail-walk): get(4)", l.get(4), 6); + } + + static void testRemoveOutOfBounds() { + IntLinkedList l = new IntLinkedList(); + l.add(1); l.add(2); + try { l.remove(-1); System.out.println("FAIL remove(-1) should throw"); failed++; } + catch (IllegalArgumentException e) { System.out.println("PASS remove(-1) throws"); passed++; } + try { l.remove(2); System.out.println("FAIL remove(size) should throw"); failed++; } + catch (IllegalArgumentException e) { System.out.println("PASS remove(size) throws"); passed++; } + check("oob did not mutate: size", l.getSize(), 2); + } + + static void testRemoveThenAdd() { + IntLinkedList l = new IntLinkedList(); + l.add(1); l.add(2); l.add(3); + l.remove(2); // remove tail + l.add(99); // new tail + check("remove-then-add: size", l.getSize(), 3); + check("remove-then-add: get(0)", l.get(0), 1); + check("remove-then-add: get(1)", l.get(1), 2); + check("remove-then-add: get(2)", l.get(2), 99); + } + + static void testRemoveAllSequentially() { + // Remove from head repeatedly until empty, then re-fill. + IntLinkedList l = new IntLinkedList(); + for (int i = 1; i <= 5; i++) l.add(i); + while (l.getSize() > 0) l.remove(0); + check("remove all: size", l.getSize(), 0); + l.add(7); l.add(8); + check("refill after remove-all: size", l.getSize(), 2); + check("refill after remove-all: get(0)", l.get(0), 7); + check("refill after remove-all: get(1)", l.get(1), 8); + } + + static void check(String label, int actual, int expected) { + if (actual == expected) { + System.out.println("PASS " + label + " = " + actual); + passed++; + } else { + System.out.println("FAIL " + label + " expected=" + expected + " actual=" + actual); + failed++; + } + } +} diff --git a/uebung_03/src/IntListNode.java b/uebung_03/src/IntListNode.java new file mode 100644 index 0000000..dba202a --- /dev/null +++ b/uebung_03/src/IntListNode.java @@ -0,0 +1,11 @@ +public class IntListNode { + int value; + IntListNode nextNode; + IntListNode prevNode; + + public IntListNode(int value, IntListNode nextNode, IntListNode prevNode) { + this.value = value; + this.nextNode = nextNode; + this.prevNode = prevNode; + } +} \ No newline at end of file diff --git a/uebung_03/src/LinkedList.java b/uebung_03/src/LinkedList.java new file mode 100644 index 0000000..74654e5 --- /dev/null +++ b/uebung_03/src/LinkedList.java @@ -0,0 +1,100 @@ +public class LinkedList { + private ListNode headNode; + private ListNode tailNode; + private int size; + + public LinkedList() { + headNode = null; + tailNode = null; + size = 0; + } + + public void add(T value) { + if (headNode == null) { + headNode = new ListNode<>(value, null, null); + tailNode = headNode; + } + else { + ListNode newNode = new ListNode<>(value, null, null); + newNode.prevNode = tailNode; + tailNode.nextNode = newNode; + tailNode = newNode; + } + size++; + } + + public int getSize() { + return size; + } + + public T get(int index) { + if (index < 0 || index > (size - 1)) { + throw new IllegalArgumentException("Index Out of Bounds"); + } + if (index < size / 2) { + int pos = 0; + ListNode curNode = headNode; + while (pos < index) { + curNode = curNode.nextNode; + pos++; + } + return curNode.value; + } + else { + int pos = size - 1; + ListNode curNode = tailNode; + while (pos > index) { + curNode = curNode.prevNode; + pos--; + } + return curNode.value; + } + } + + public void remove(int index) { + if (index < 0 || index > (size - 1)) { + throw new IllegalArgumentException("Index Out of Bounds"); + } + if (index == 0) { + if (size == 1) { + headNode = null; + tailNode = null; + } + else { + headNode = headNode.nextNode; + headNode.prevNode = null; + } + size--; + return; + } + if (index == (size - 1)) { + tailNode = tailNode.prevNode; + tailNode.nextNode = null; + size--; + return; + } + + if (index < size / 2) { + int pos = 0; + ListNode curNode = headNode; + while (pos < index) { + curNode = curNode.nextNode; + pos++; + } + curNode.nextNode.prevNode = curNode.prevNode; + curNode.prevNode.nextNode = curNode.nextNode; + size--; + } + else { + int pos = size - 1; + ListNode curNode = tailNode; + while (pos > index) { + curNode = curNode.prevNode; + pos--; + } + curNode.nextNode.prevNode = curNode.prevNode; + curNode.prevNode.nextNode = curNode.nextNode; + size--; + } + } +} diff --git a/uebung_03/src/ListNode.java b/uebung_03/src/ListNode.java new file mode 100644 index 0000000..4b9326b --- /dev/null +++ b/uebung_03/src/ListNode.java @@ -0,0 +1,11 @@ +public class ListNode { + T value; + ListNode nextNode; + ListNode prevNode; + + public ListNode(T value, ListNode nextNode, ListNode prevNode) { + this.value = value; + this.nextNode = nextNode; + this.prevNode = prevNode; + } +}