From 9b6528d8003c96c1c7dc7e9864cd78918542c979 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sat, 23 May 2026 23:19:48 +0200 Subject: [PATCH] uebung_03: implement Task 3 (BST) and Task 4 (IntHashSet) Task 3 adds IntBinarySearchTree with iterative add/contains and a test class covering empty trees, duplicates, and degenerate ascending and descending insertion orders. Task 4 adds IntHashSet backed by an IntLinkedList bucket array with a 0.7 load factor, Math.floorMod-based hashing for negative-int safety, doubling resize that rehashes via a private addWithoutResize helper, and a test class covering negatives, Integer.MIN_VALUE, forced collisions on bucket 0, and 100-element inserts spanning three resizes. Co-Authored-By: Claude Opus 4.7 (1M context) --- uebung_03/src/IntBinarySearchTree.java | 54 ++++++++++++ uebung_03/src/IntBinarySearchTreeTest.java | 96 ++++++++++++++++++++++ uebung_03/src/IntHashSet.java | 81 ++++++++++++++++++ uebung_03/src/IntHashSetTest.java | 74 +++++++++++++++++ uebung_03/src/IntTreeNode.java | 12 +++ 5 files changed, 317 insertions(+) create mode 100644 uebung_03/src/IntBinarySearchTree.java create mode 100644 uebung_03/src/IntBinarySearchTreeTest.java create mode 100644 uebung_03/src/IntHashSet.java create mode 100644 uebung_03/src/IntHashSetTest.java create mode 100644 uebung_03/src/IntTreeNode.java diff --git a/uebung_03/src/IntBinarySearchTree.java b/uebung_03/src/IntBinarySearchTree.java new file mode 100644 index 0000000..928ce6f --- /dev/null +++ b/uebung_03/src/IntBinarySearchTree.java @@ -0,0 +1,54 @@ +public class IntBinarySearchTree { + IntTreeNode root; + + public IntBinarySearchTree(){ + } + + public void add(int value){ + if (root == null){ + root = new IntTreeNode(value); + return; + } + + IntTreeNode currentRelativRoot = root; + while (true) { + if(value == currentRelativRoot.value){ + return; + } + if (value < currentRelativRoot.value) { + if (currentRelativRoot.left == null) { + currentRelativRoot.left = new IntTreeNode(value); + return; + } else currentRelativRoot = currentRelativRoot.left; + + } else { + if (currentRelativRoot.right == null) { + currentRelativRoot.right = new IntTreeNode(value); + return; + } else currentRelativRoot = currentRelativRoot.right; + + } + } + } + public boolean contains(int value){ + if (root == null) return false; + IntTreeNode currentRelativRoot = root; + while (true){ + if(value == currentRelativRoot.value){ + return true; + } + if (value < currentRelativRoot.value) { + if (currentRelativRoot.left == null) { + return false; + } else currentRelativRoot = currentRelativRoot.left; + + } else { + if (currentRelativRoot.right == null) { + return false; + } else currentRelativRoot = currentRelativRoot.right; + + } + } + + } +} diff --git a/uebung_03/src/IntBinarySearchTreeTest.java b/uebung_03/src/IntBinarySearchTreeTest.java new file mode 100644 index 0000000..ba1d975 --- /dev/null +++ b/uebung_03/src/IntBinarySearchTreeTest.java @@ -0,0 +1,96 @@ +public class IntBinarySearchTreeTest { + + static int passed = 0; + static int failed = 0; + + public static void main(String[] args) { + testEmptyContains(); + testSingleAdd(); + testStandardTree(); + testDuplicate(); + testLeftAndRightExtremes(); + testStrictlyAscending(); + testStrictlyDescending(); + + System.out.println(); + System.out.println("Passed: " + passed + " / " + (passed + failed)); + } + + static void testEmptyContains() { + IntBinarySearchTree t = new IntBinarySearchTree(); + checkBool("empty contains(5)", t.contains(5), false); + } + + static void testSingleAdd() { + IntBinarySearchTree t = new IntBinarySearchTree(); + t.add(42); + checkBool("single contains(42)", t.contains(42), true); + checkBool("single contains(7)", t.contains(7), false); + } + + static void testStandardTree() { + IntBinarySearchTree t = new IntBinarySearchTree(); + int[] vals = {5, 3, 8, 1, 4, 7, 9, 5}; + for (int v : vals) t.add(v); + checkBool("contains(7)", t.contains(7), true); + checkBool("contains(6)", t.contains(6), false); + checkBool("contains(1)", t.contains(1), true); + checkBool("contains(9)", t.contains(9), true); + checkBool("contains(5)", t.contains(5), true); + checkBool("contains(4)", t.contains(4), true); + checkBool("contains(3)", t.contains(3), true); + checkBool("contains(0)", t.contains(0), false); + checkBool("contains(10)", t.contains(10), false); + } + + static void testDuplicate() { + IntBinarySearchTree t = new IntBinarySearchTree(); + t.add(10); + t.add(10); + t.add(10); + checkBool("duplicates contains(10)", t.contains(10), true); + // Tree should still work after duplicates + t.add(5); + t.add(15); + checkBool("after dup contains(5)", t.contains(5), true); + checkBool("after dup contains(15)", t.contains(15), true); + } + + static void testLeftAndRightExtremes() { + IntBinarySearchTree t = new IntBinarySearchTree(); + int[] vals = {50, 25, 75, 10, 35, 60, 90, 5, 15, 30, 40}; + for (int v : vals) t.add(v); + for (int v : vals) checkBool("extremes contains(" + v + ")", t.contains(v), true); + checkBool("extremes contains(-1)", t.contains(-1), false); + checkBool("extremes contains(100)", t.contains(100), false); + checkBool("extremes contains(45)", t.contains(45), false); + } + + static void testStrictlyAscending() { + // Degenerates into a right-only chain - good edge case + IntBinarySearchTree t = new IntBinarySearchTree(); + for (int i = 1; i <= 10; i++) t.add(i); + for (int i = 1; i <= 10; i++) checkBool("asc contains(" + i + ")", t.contains(i), true); + checkBool("asc contains(0)", t.contains(0), false); + checkBool("asc contains(11)", t.contains(11), false); + } + + static void testStrictlyDescending() { + // Degenerates into a left-only chain + IntBinarySearchTree t = new IntBinarySearchTree(); + for (int i = 10; i >= 1; i--) t.add(i); + for (int i = 1; i <= 10; i++) checkBool("desc contains(" + i + ")", t.contains(i), true); + checkBool("desc contains(0)", t.contains(0), false); + checkBool("desc contains(11)", t.contains(11), false); + } + + static void checkBool(String label, boolean actual, boolean 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/IntHashSet.java b/uebung_03/src/IntHashSet.java new file mode 100644 index 0000000..fa4838a --- /dev/null +++ b/uebung_03/src/IntHashSet.java @@ -0,0 +1,81 @@ +public class IntHashSet { + private IntLinkedList[] buckets; + private int size; + + private int capacity; + + private static final double LOAD_PERCENT = 0.7; + + public IntHashSet(){ + buckets = new IntLinkedList[16]; + capacity = 16; + } + + private int hash(int value){ + return Math.floorMod(value, capacity); + } + private boolean loadReached(){ + return (double) size / capacity >= LOAD_PERCENT; + } + + private void resize(){ + IntLinkedList[] oldBuckets = buckets; + capacity *=2; + buckets = new IntLinkedList[capacity]; + size = 0; + for (IntLinkedList oldBucket : oldBuckets) { + if (oldBucket == null) continue; + for (int j = 0; j < oldBucket.getSize(); j++) { + addWithoutResize(oldBucket.get(j)); + } + } + } + + + public void add(int value){ + int index = hash(value); + if (buckets[index] == null){ + buckets[index] = new IntLinkedList(); + buckets[index].add(value); + size++; + if (loadReached()) resize(); + return; + } + for (int i = 0; i < buckets[index].getSize(); i++){ + if (buckets[index].get(i) == value){ + return; + } + } + buckets[index].add(value); + size++; + if (loadReached()) resize(); + } + private void addWithoutResize(int value){ + int index = hash(value); + if (buckets[index] == null){ + buckets[index] = new IntLinkedList(); + buckets[index].add(value); + size++; + return; + } + for (int i = 0; i < buckets[index].getSize(); i++){ + if (buckets[index].get(i) == value){ + return; + } + } + buckets[index].add(value); + size++; + } + public boolean contains(int value){ + int index = hash(value); + if (buckets[index] == null){ + return false; + } + for (int i = 0; i < buckets[index].getSize(); i++){ + if (buckets[index].get(i) == value){ + return true; + } + } + return false; + } +} diff --git a/uebung_03/src/IntHashSetTest.java b/uebung_03/src/IntHashSetTest.java new file mode 100644 index 0000000..5cb7148 --- /dev/null +++ b/uebung_03/src/IntHashSetTest.java @@ -0,0 +1,74 @@ +public class IntHashSetTest { + static int passed = 0, failed = 0; + + public static void main(String[] args) { + testEmpty(); + testAddContains(); + testDuplicates(); + testNegatives(); + testManyForcingResize(); + testCollisions(); + System.out.println(); + System.out.println("Passed: " + passed + " / " + (passed + failed)); + } + + static void testEmpty() { + IntHashSet s = new IntHashSet(); + checkBool("empty contains(5)", s.contains(5), false); + checkBool("empty contains(-1)", s.contains(-1), false); + checkBool("empty contains(0)", s.contains(0), false); + } + + static void testAddContains() { + IntHashSet s = new IntHashSet(); + s.add(1); s.add(2); s.add(3); + checkBool("contains(1)", s.contains(1), true); + checkBool("contains(2)", s.contains(2), true); + checkBool("contains(3)", s.contains(3), true); + checkBool("contains(4)", s.contains(4), false); + } + + static void testDuplicates() { + IntHashSet s = new IntHashSet(); + s.add(7); s.add(7); s.add(7); s.add(7); + checkBool("dup contains(7)", s.contains(7), true); + // no easy way to inspect size without exposing it; trust it + } + + static void testNegatives() { + IntHashSet s = new IntHashSet(); + s.add(-1); s.add(-100); s.add(Integer.MIN_VALUE); + checkBool("contains(-1)", s.contains(-1), true); + checkBool("contains(-100)", s.contains(-100), true); + checkBool("contains(MIN_VALUE)", s.contains(Integer.MIN_VALUE), true); + checkBool("contains(1) after negs", s.contains(1), false); + } + + static void testManyForcingResize() { + // Insert 100 values - capacity starts at 16, will resize multiple times + IntHashSet s = new IntHashSet(); + for (int i = 0; i < 100; i++) s.add(i); + for (int i = 0; i < 100; i++) { + checkBool("post-resize contains(" + i + ")", s.contains(i), true); + } + checkBool("post-resize contains(100)", s.contains(100), false); + checkBool("post-resize contains(-1)", s.contains(-1), false); + } + + static void testCollisions() { + // Values that all hash to bucket 0 in capacity 16 (multiples of 16) + IntHashSet s = new IntHashSet(); + s.add(0); s.add(16); s.add(32); s.add(48); + checkBool("collision contains(0)", s.contains(0), true); + checkBool("collision contains(16)", s.contains(16), true); + checkBool("collision contains(32)", s.contains(32), true); + checkBool("collision contains(48)", s.contains(48), true); + checkBool("collision contains(8)", s.contains(8), false); + checkBool("collision contains(64)", s.contains(64), false); + } + + static void checkBool(String label, boolean actual, boolean 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/IntTreeNode.java b/uebung_03/src/IntTreeNode.java new file mode 100644 index 0000000..e62dbdc --- /dev/null +++ b/uebung_03/src/IntTreeNode.java @@ -0,0 +1,12 @@ +public class IntTreeNode { + int value; + IntTreeNode left; + + IntTreeNode right; + + public IntTreeNode(int value){ + this.value = value; + } + + +}