From 9c7892c27c21877c502c7ccd964f089a9dcba7c5 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Thu, 2 Jan 2025 22:23:40 +0200 Subject: [PATCH 01/10] Introducing binary search tree implementation --- dataStructures/binarySearchTree.zig | 241 ++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 dataStructures/binarySearchTree.zig diff --git a/dataStructures/binarySearchTree.zig b/dataStructures/binarySearchTree.zig new file mode 100644 index 0000000..be7fd66 --- /dev/null +++ b/dataStructures/binarySearchTree.zig @@ -0,0 +1,241 @@ +const std = @import("std"); +const print = std.debug.print; +const ArrayList = std.ArrayList; +const testing = std.testing; + +// Returns a binary search tree instance. +// Arguments: +// T: the type of the info(i.e. i32, i16, u32, etc...) +// Allocator: This is needed for the struct instance. In most cases, feel free +// to use std.heap.GeneralPurposeAllocator. +pub fn bst(comptime T: type) type { + return struct { + const Self = @This(); + + // This is the node struct. It holds: + // info: T + // right: A pointer to the right child + // left: A pointer to the left child + pub const node = struct { + info: T, + right: ?*node = null, + left: ?*node = null, + }; + + allocator: *std.mem.Allocator, + root: ?*node = null, + size: usize = 0, + + // Function to insert elements into the tree + // Runs in θ(logn)/O(n), uses the helper _insert private function + // Arguments: + // key: T - the key to be inserted into the tree + pub fn insert(self: *Self, key: T) !void { + self.root = try self._insert(self.root, key); + self.size += 1; + } + + // Function to remove elements from the tree + // Runs in θ(logn)/O(n), uses the helper _remove private function + // Arguments: + // key: T - the key to be removed from the tree + pub fn remove(self: *Self, key: T) !void { + if (self.root == null) { return; } + self.root = try self._remove(self.root, key); + self.size -= 1; + } + + // Function to search if a key exists in the tree + // Runs in θ(logn)/O(n), uses the helper _search private function + // Arguments: + // key: T - the key that will be searched + pub fn search(self: *Self, key: T) bool { + return _search(self.root, key); + } + + // Function that performs inorder traversal of the tree + pub fn inorder(self: *Self, path: *ArrayList(T)) !void { + if (self.root == null) { return; } + try self._inorder(self.root, path); + } + + // Function that performs preorder traversal of the tree + pub fn preorder(self: *Self, path: *ArrayList(T)) !void { + if (self.root == null) { return; } + try self._preorder(self.root, path); + } + + // Function that performs postorder traversal of the tree + pub fn postorder(self: *Self, path: *ArrayList(T)) !void { + if (self.root == null) { return; } + try self._postorder(self.root, path); + } + + // Function that destroys the allocated memory of the whole tree + // Uses the _destroy helper private function + pub fn destroy(self: *Self) void { + if (self.root == null) { return; } + self._destroy(self.root); + self.size = 0; + } + + // Function that generates a new node + // Arguments: + // key: T - The info of the node + fn new_node(self: *Self, key: T) !?*node { + const nn = try self.allocator.create(node); + nn.* = node { .info = key, .right = null, .left = null }; + return nn; + } + + fn _insert(self: *Self, root: ?*node, key: T) !?*node { + if(root == null) { + return try self.new_node(key); + } + else { + if (root.?.info < key) { + root.?.right = try self._insert(root.?.right, key); + } + else { + root.?.left = try self._insert(root.?.left, key); + } + } + + return root; + } + + fn _remove(self: *Self, root: ?*node, key: T) !?*node { + if (root == null) { + return root; + } + + if (root.?.info < key) { + root.?.right = try self._remove(root.?.right, key); + } + else if (root.?.info > key) { + root.?.left = try self._remove(root.?.left, key); + } + else { + if (root.?.left == null and root.?.right == null) { + self.allocator.destroy(root.?); + return null; + } + else if (root.?.left == null) { + const temp = root.?.right; + self.allocator.destroy(root.?); + return temp; + } + else if (root.?.right == null) { + const temp = root.?.left; + self.allocator.destroy(root.?); + return temp; + } + else { + var curr: ?*node = root.?.right; + while (curr.?.left != null) : (curr = curr.?.left) { } + root.?.info = curr.?.info; + root.?.right = try self._remove(root.?.right, curr.?.info); + } + } + + return root; + } + + fn _search(root: ?*node, key: T) bool { + var head: ?*node = root; + while (head) |curr| { + if (curr.info < key) { + head = curr.right; + } + else if (curr.info > key) { + head = curr.left; + } + else { + return true; + } + } + + return false; + } + + fn _inorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { + if (root != null) { + try self._inorder(root.?.left, path); + try path.append(root.?.info); + try self._inorder(root.?.right, path); + } + } + + fn _preorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { + if (root != null) { + try path.append(root.?.info); + try self._preorder(root.?.left, path); + try self._preorder(root.?.right, path); + } + } + + fn _postorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { + if (root != null) { + try self._postorder(root.?.left, path); + try self._postorder(root.?.right, path); + try path.append(root.?.info); + } + } + + fn _destroy(self: *Self, root: ?*node) void { + if(root != null) { + self._destroy(root.?.left); + self._destroy(root.?.right); + self.allocator.destroy(root.?); + } + } + }; +} + +test "Testing Binary Search Tree" { + var gpa = std.heap.GeneralPurposeAllocator(.{}) {}; + defer _ = gpa.deinit(); + var allocator = gpa.allocator(); + + var t = bst(i32) { .allocator = &allocator }; + defer t.destroy(); + + try t.insert(10); + try t.insert(5); + try t.insert(25); + try t.insert(3); + try t.insert(12); + try testing.expect(t.size == 5); + try testing.expect(t.search(10) == true); + try testing.expect(t.search(15) == false); + try t.insert(15); + try testing.expect(t.size == 6); + try testing.expect(t.search(15) == true); + try t.remove(10); + try testing.expect(t.size == 5); + try testing.expect(t.search(10) == false); + + var ino = ArrayList(i32).init(allocator); + defer ino.deinit(); + + const check_ino = [_]i32{3, 5, 12, 15, 25}; + try t.inorder(&ino); + try testing.expect(std.mem.eql(i32, ino.items, &check_ino)); + + var pre = ArrayList(i32).init(allocator); + defer pre.deinit(); + + const check_pre = [_]i32{12, 5, 3, 25, 15}; + try t.preorder(&pre); + + try testing.expect(std.mem.eql(i32, pre.items, &check_pre)); + + var post = ArrayList(i32).init(allocator); + defer post.deinit(); + + const check_post = [_]i32{3, 5, 15, 25, 12}; + try t.postorder(&post); + + try testing.expect(std.mem.eql(i32, post.items, &check_post)); +} + From 7b9c618f0eb5add71e0fb39e4ece1c2959fa2da4 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Thu, 2 Jan 2025 22:46:52 +0200 Subject: [PATCH 02/10] Changed container name to match previous work --- dataStructures/binarySearchTree.zig | 74 ++++++++++++++--------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/dataStructures/binarySearchTree.zig b/dataStructures/binarySearchTree.zig index be7fd66..9c9683c 100644 --- a/dataStructures/binarySearchTree.zig +++ b/dataStructures/binarySearchTree.zig @@ -8,7 +8,7 @@ const testing = std.testing; // T: the type of the info(i.e. i32, i16, u32, etc...) // Allocator: This is needed for the struct instance. In most cases, feel free // to use std.heap.GeneralPurposeAllocator. -pub fn bst(comptime T: type) type { +pub fn BinarySearchTree(comptime T: type) type { return struct { const Self = @This(); @@ -40,7 +40,9 @@ pub fn bst(comptime T: type) type { // Arguments: // key: T - the key to be removed from the tree pub fn remove(self: *Self, key: T) !void { - if (self.root == null) { return; } + if (self.root == null) { + return; + } self.root = try self._remove(self.root, key); self.size -= 1; } @@ -55,26 +57,34 @@ pub fn bst(comptime T: type) type { // Function that performs inorder traversal of the tree pub fn inorder(self: *Self, path: *ArrayList(T)) !void { - if (self.root == null) { return; } + if (self.root == null) { + return; + } try self._inorder(self.root, path); } // Function that performs preorder traversal of the tree pub fn preorder(self: *Self, path: *ArrayList(T)) !void { - if (self.root == null) { return; } + if (self.root == null) { + return; + } try self._preorder(self.root, path); } // Function that performs postorder traversal of the tree pub fn postorder(self: *Self, path: *ArrayList(T)) !void { - if (self.root == null) { return; } + if (self.root == null) { + return; + } try self._postorder(self.root, path); } // Function that destroys the allocated memory of the whole tree // Uses the _destroy helper private function pub fn destroy(self: *Self) void { - if (self.root == null) { return; } + if (self.root == null) { + return; + } self._destroy(self.root); self.size = 0; } @@ -84,19 +94,17 @@ pub fn bst(comptime T: type) type { // key: T - The info of the node fn new_node(self: *Self, key: T) !?*node { const nn = try self.allocator.create(node); - nn.* = node { .info = key, .right = null, .left = null }; + nn.* = node{ .info = key, .right = null, .left = null }; return nn; } fn _insert(self: *Self, root: ?*node, key: T) !?*node { - if(root == null) { + if (root == null) { return try self.new_node(key); - } - else { + } else { if (root.?.info < key) { root.?.right = try self._insert(root.?.right, key); - } - else { + } else { root.?.left = try self._insert(root.?.left, key); } } @@ -111,28 +119,23 @@ pub fn bst(comptime T: type) type { if (root.?.info < key) { root.?.right = try self._remove(root.?.right, key); - } - else if (root.?.info > key) { + } else if (root.?.info > key) { root.?.left = try self._remove(root.?.left, key); - } - else { + } else { if (root.?.left == null and root.?.right == null) { self.allocator.destroy(root.?); return null; - } - else if (root.?.left == null) { + } else if (root.?.left == null) { const temp = root.?.right; self.allocator.destroy(root.?); return temp; - } - else if (root.?.right == null) { + } else if (root.?.right == null) { const temp = root.?.left; self.allocator.destroy(root.?); return temp; - } - else { + } else { var curr: ?*node = root.?.right; - while (curr.?.left != null) : (curr = curr.?.left) { } + while (curr.?.left != null) : (curr = curr.?.left) {} root.?.info = curr.?.info; root.?.right = try self._remove(root.?.right, curr.?.info); } @@ -146,11 +149,9 @@ pub fn bst(comptime T: type) type { while (head) |curr| { if (curr.info < key) { head = curr.right; - } - else if (curr.info > key) { + } else if (curr.info > key) { head = curr.left; - } - else { + } else { return true; } } @@ -168,9 +169,9 @@ pub fn bst(comptime T: type) type { fn _preorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { if (root != null) { - try path.append(root.?.info); - try self._preorder(root.?.left, path); - try self._preorder(root.?.right, path); + try path.append(root.?.info); + try self._preorder(root.?.left, path); + try self._preorder(root.?.right, path); } } @@ -183,7 +184,7 @@ pub fn bst(comptime T: type) type { } fn _destroy(self: *Self, root: ?*node) void { - if(root != null) { + if (root != null) { self._destroy(root.?.left); self._destroy(root.?.right); self.allocator.destroy(root.?); @@ -193,11 +194,11 @@ pub fn bst(comptime T: type) type { } test "Testing Binary Search Tree" { - var gpa = std.heap.GeneralPurposeAllocator(.{}) {}; + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); var allocator = gpa.allocator(); - var t = bst(i32) { .allocator = &allocator }; + var t = BinarySearchTree(i32){ .allocator = &allocator }; defer t.destroy(); try t.insert(10); @@ -218,14 +219,14 @@ test "Testing Binary Search Tree" { var ino = ArrayList(i32).init(allocator); defer ino.deinit(); - const check_ino = [_]i32{3, 5, 12, 15, 25}; + const check_ino = [_]i32{ 3, 5, 12, 15, 25 }; try t.inorder(&ino); try testing.expect(std.mem.eql(i32, ino.items, &check_ino)); var pre = ArrayList(i32).init(allocator); defer pre.deinit(); - const check_pre = [_]i32{12, 5, 3, 25, 15}; + const check_pre = [_]i32{ 12, 5, 3, 25, 15 }; try t.preorder(&pre); try testing.expect(std.mem.eql(i32, pre.items, &check_pre)); @@ -233,9 +234,8 @@ test "Testing Binary Search Tree" { var post = ArrayList(i32).init(allocator); defer post.deinit(); - const check_post = [_]i32{3, 5, 15, 25, 12}; + const check_post = [_]i32{ 3, 5, 15, 25, 12 }; try t.postorder(&post); try testing.expect(std.mem.eql(i32, post.items, &check_post)); } - From 562951411ac79b60f4596685c27a05f25a1d8c12 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 3 Jan 2025 11:37:02 +0200 Subject: [PATCH 03/10] Updated Binary Search Tree data structure Now the new BST function has more methods and in general feels more like my previous DoublyLinkedList implementation. --- dataStructures/binarySearchTree.zig | 241 ------------------------ search/binarySearchTree.zig | 279 ++++++++++++++++++++++------ 2 files changed, 219 insertions(+), 301 deletions(-) delete mode 100644 dataStructures/binarySearchTree.zig diff --git a/dataStructures/binarySearchTree.zig b/dataStructures/binarySearchTree.zig deleted file mode 100644 index 9c9683c..0000000 --- a/dataStructures/binarySearchTree.zig +++ /dev/null @@ -1,241 +0,0 @@ -const std = @import("std"); -const print = std.debug.print; -const ArrayList = std.ArrayList; -const testing = std.testing; - -// Returns a binary search tree instance. -// Arguments: -// T: the type of the info(i.e. i32, i16, u32, etc...) -// Allocator: This is needed for the struct instance. In most cases, feel free -// to use std.heap.GeneralPurposeAllocator. -pub fn BinarySearchTree(comptime T: type) type { - return struct { - const Self = @This(); - - // This is the node struct. It holds: - // info: T - // right: A pointer to the right child - // left: A pointer to the left child - pub const node = struct { - info: T, - right: ?*node = null, - left: ?*node = null, - }; - - allocator: *std.mem.Allocator, - root: ?*node = null, - size: usize = 0, - - // Function to insert elements into the tree - // Runs in θ(logn)/O(n), uses the helper _insert private function - // Arguments: - // key: T - the key to be inserted into the tree - pub fn insert(self: *Self, key: T) !void { - self.root = try self._insert(self.root, key); - self.size += 1; - } - - // Function to remove elements from the tree - // Runs in θ(logn)/O(n), uses the helper _remove private function - // Arguments: - // key: T - the key to be removed from the tree - pub fn remove(self: *Self, key: T) !void { - if (self.root == null) { - return; - } - self.root = try self._remove(self.root, key); - self.size -= 1; - } - - // Function to search if a key exists in the tree - // Runs in θ(logn)/O(n), uses the helper _search private function - // Arguments: - // key: T - the key that will be searched - pub fn search(self: *Self, key: T) bool { - return _search(self.root, key); - } - - // Function that performs inorder traversal of the tree - pub fn inorder(self: *Self, path: *ArrayList(T)) !void { - if (self.root == null) { - return; - } - try self._inorder(self.root, path); - } - - // Function that performs preorder traversal of the tree - pub fn preorder(self: *Self, path: *ArrayList(T)) !void { - if (self.root == null) { - return; - } - try self._preorder(self.root, path); - } - - // Function that performs postorder traversal of the tree - pub fn postorder(self: *Self, path: *ArrayList(T)) !void { - if (self.root == null) { - return; - } - try self._postorder(self.root, path); - } - - // Function that destroys the allocated memory of the whole tree - // Uses the _destroy helper private function - pub fn destroy(self: *Self) void { - if (self.root == null) { - return; - } - self._destroy(self.root); - self.size = 0; - } - - // Function that generates a new node - // Arguments: - // key: T - The info of the node - fn new_node(self: *Self, key: T) !?*node { - const nn = try self.allocator.create(node); - nn.* = node{ .info = key, .right = null, .left = null }; - return nn; - } - - fn _insert(self: *Self, root: ?*node, key: T) !?*node { - if (root == null) { - return try self.new_node(key); - } else { - if (root.?.info < key) { - root.?.right = try self._insert(root.?.right, key); - } else { - root.?.left = try self._insert(root.?.left, key); - } - } - - return root; - } - - fn _remove(self: *Self, root: ?*node, key: T) !?*node { - if (root == null) { - return root; - } - - if (root.?.info < key) { - root.?.right = try self._remove(root.?.right, key); - } else if (root.?.info > key) { - root.?.left = try self._remove(root.?.left, key); - } else { - if (root.?.left == null and root.?.right == null) { - self.allocator.destroy(root.?); - return null; - } else if (root.?.left == null) { - const temp = root.?.right; - self.allocator.destroy(root.?); - return temp; - } else if (root.?.right == null) { - const temp = root.?.left; - self.allocator.destroy(root.?); - return temp; - } else { - var curr: ?*node = root.?.right; - while (curr.?.left != null) : (curr = curr.?.left) {} - root.?.info = curr.?.info; - root.?.right = try self._remove(root.?.right, curr.?.info); - } - } - - return root; - } - - fn _search(root: ?*node, key: T) bool { - var head: ?*node = root; - while (head) |curr| { - if (curr.info < key) { - head = curr.right; - } else if (curr.info > key) { - head = curr.left; - } else { - return true; - } - } - - return false; - } - - fn _inorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { - if (root != null) { - try self._inorder(root.?.left, path); - try path.append(root.?.info); - try self._inorder(root.?.right, path); - } - } - - fn _preorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { - if (root != null) { - try path.append(root.?.info); - try self._preorder(root.?.left, path); - try self._preorder(root.?.right, path); - } - } - - fn _postorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { - if (root != null) { - try self._postorder(root.?.left, path); - try self._postorder(root.?.right, path); - try path.append(root.?.info); - } - } - - fn _destroy(self: *Self, root: ?*node) void { - if (root != null) { - self._destroy(root.?.left); - self._destroy(root.?.right); - self.allocator.destroy(root.?); - } - } - }; -} - -test "Testing Binary Search Tree" { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - var allocator = gpa.allocator(); - - var t = BinarySearchTree(i32){ .allocator = &allocator }; - defer t.destroy(); - - try t.insert(10); - try t.insert(5); - try t.insert(25); - try t.insert(3); - try t.insert(12); - try testing.expect(t.size == 5); - try testing.expect(t.search(10) == true); - try testing.expect(t.search(15) == false); - try t.insert(15); - try testing.expect(t.size == 6); - try testing.expect(t.search(15) == true); - try t.remove(10); - try testing.expect(t.size == 5); - try testing.expect(t.search(10) == false); - - var ino = ArrayList(i32).init(allocator); - defer ino.deinit(); - - const check_ino = [_]i32{ 3, 5, 12, 15, 25 }; - try t.inorder(&ino); - try testing.expect(std.mem.eql(i32, ino.items, &check_ino)); - - var pre = ArrayList(i32).init(allocator); - defer pre.deinit(); - - const check_pre = [_]i32{ 12, 5, 3, 25, 15 }; - try t.preorder(&pre); - - try testing.expect(std.mem.eql(i32, pre.items, &check_pre)); - - var post = ArrayList(i32).init(allocator); - defer post.deinit(); - - const check_post = [_]i32{ 3, 5, 15, 25, 12 }; - try t.postorder(&post); - - try testing.expect(std.mem.eql(i32, post.items, &check_post)); -} diff --git a/search/binarySearchTree.zig b/search/binarySearchTree.zig index c78f598..9c9683c 100644 --- a/search/binarySearchTree.zig +++ b/search/binarySearchTree.zig @@ -1,82 +1,241 @@ const std = @import("std"); -const expect = std.testing.expect; +const print = std.debug.print; +const ArrayList = std.ArrayList; +const testing = std.testing; -fn Node(comptime T: type) type { +// Returns a binary search tree instance. +// Arguments: +// T: the type of the info(i.e. i32, i16, u32, etc...) +// Allocator: This is needed for the struct instance. In most cases, feel free +// to use std.heap.GeneralPurposeAllocator. +pub fn BinarySearchTree(comptime T: type) type { return struct { - value: T, - parent: ?*Node(T) = null, - left: ?*Node(T) = null, - right: ?*Node(T) = null, - }; -} + const Self = @This(); -fn Tree(comptime T: type) type { - return struct { - root: ?*Node(T) = null, + // This is the node struct. It holds: + // info: T + // right: A pointer to the right child + // left: A pointer to the left child + pub const node = struct { + info: T, + right: ?*node = null, + left: ?*node = null, + }; + + allocator: *std.mem.Allocator, + root: ?*node = null, + size: usize = 0, - pub fn search(node: ?*Node(T), value: T) ?*Node(T) { - if (node == null or node.?.value == value) { - return node; + // Function to insert elements into the tree + // Runs in θ(logn)/O(n), uses the helper _insert private function + // Arguments: + // key: T - the key to be inserted into the tree + pub fn insert(self: *Self, key: T) !void { + self.root = try self._insert(self.root, key); + self.size += 1; + } + + // Function to remove elements from the tree + // Runs in θ(logn)/O(n), uses the helper _remove private function + // Arguments: + // key: T - the key to be removed from the tree + pub fn remove(self: *Self, key: T) !void { + if (self.root == null) { + return; } - if (value < node.?.value) { - return search(node.?.left, value); - } else { - return search(node.?.right, value); + self.root = try self._remove(self.root, key); + self.size -= 1; + } + + // Function to search if a key exists in the tree + // Runs in θ(logn)/O(n), uses the helper _search private function + // Arguments: + // key: T - the key that will be searched + pub fn search(self: *Self, key: T) bool { + return _search(self.root, key); + } + + // Function that performs inorder traversal of the tree + pub fn inorder(self: *Self, path: *ArrayList(T)) !void { + if (self.root == null) { + return; + } + try self._inorder(self.root, path); + } + + // Function that performs preorder traversal of the tree + pub fn preorder(self: *Self, path: *ArrayList(T)) !void { + if (self.root == null) { + return; + } + try self._preorder(self.root, path); + } + + // Function that performs postorder traversal of the tree + pub fn postorder(self: *Self, path: *ArrayList(T)) !void { + if (self.root == null) { + return; + } + try self._postorder(self.root, path); + } + + // Function that destroys the allocated memory of the whole tree + // Uses the _destroy helper private function + pub fn destroy(self: *Self) void { + if (self.root == null) { + return; } + self._destroy(self.root); + self.size = 0; + } + + // Function that generates a new node + // Arguments: + // key: T - The info of the node + fn new_node(self: *Self, key: T) !?*node { + const nn = try self.allocator.create(node); + nn.* = node{ .info = key, .right = null, .left = null }; + return nn; } - pub fn insert(self: *Tree(T), z: *Node(T)) void { - var y: ?*Node(T) = null; - var x = self.root; - while (x) |node| { - y = node; - if (z.value < node.value) { - x = node.left; + fn _insert(self: *Self, root: ?*node, key: T) !?*node { + if (root == null) { + return try self.new_node(key); + } else { + if (root.?.info < key) { + root.?.right = try self._insert(root.?.right, key); } else { - x = node.right; + root.?.left = try self._insert(root.?.left, key); } } - z.parent = y; - if (y == null) { - self.root = z; - } else if (z.value < y.?.value) { - y.?.left = z; + + return root; + } + + fn _remove(self: *Self, root: ?*node, key: T) !?*node { + if (root == null) { + return root; + } + + if (root.?.info < key) { + root.?.right = try self._remove(root.?.right, key); + } else if (root.?.info > key) { + root.?.left = try self._remove(root.?.left, key); } else { - y.?.right = z; + if (root.?.left == null and root.?.right == null) { + self.allocator.destroy(root.?); + return null; + } else if (root.?.left == null) { + const temp = root.?.right; + self.allocator.destroy(root.?); + return temp; + } else if (root.?.right == null) { + const temp = root.?.left; + self.allocator.destroy(root.?); + return temp; + } else { + var curr: ?*node = root.?.right; + while (curr.?.left != null) : (curr = curr.?.left) {} + root.?.info = curr.?.info; + root.?.right = try self._remove(root.?.right, curr.?.info); + } + } + + return root; + } + + fn _search(root: ?*node, key: T) bool { + var head: ?*node = root; + while (head) |curr| { + if (curr.info < key) { + head = curr.right; + } else if (curr.info > key) { + head = curr.left; + } else { + return true; + } + } + + return false; + } + + fn _inorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { + if (root != null) { + try self._inorder(root.?.left, path); + try path.append(root.?.info); + try self._inorder(root.?.right, path); + } + } + + fn _preorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { + if (root != null) { + try path.append(root.?.info); + try self._preorder(root.?.left, path); + try self._preorder(root.?.right, path); + } + } + + fn _postorder(self: *Self, root: ?*node, path: *ArrayList(T)) !void { + if (root != null) { + try self._postorder(root.?.left, path); + try self._postorder(root.?.right, path); + try path.append(root.?.info); + } + } + + fn _destroy(self: *Self, root: ?*node) void { + if (root != null) { + self._destroy(root.?.left); + self._destroy(root.?.right); + self.allocator.destroy(root.?); } } }; } -test "search empty tree" { - const tree = Tree(i32){}; - const result = Tree(i32).search(tree.root, 3); - try expect(result == null); -} +test "Testing Binary Search Tree" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + var allocator = gpa.allocator(); -test "search an existing element" { - var tree = Tree(i32){}; - var node = Node(i32){ .value = 3 }; - tree.insert(&node); - const result = Tree(i32).search(tree.root, 3); - try expect(result.? == &node); -} + var t = BinarySearchTree(i32){ .allocator = &allocator }; + defer t.destroy(); -test "search non-existent element" { - var tree = Tree(i32){}; - var node = Node(i32){ .value = 3 }; - tree.insert(&node); - const result = Tree(i32).search(tree.root, 4); - try expect(result == null); -} + try t.insert(10); + try t.insert(5); + try t.insert(25); + try t.insert(3); + try t.insert(12); + try testing.expect(t.size == 5); + try testing.expect(t.search(10) == true); + try testing.expect(t.search(15) == false); + try t.insert(15); + try testing.expect(t.size == 6); + try testing.expect(t.search(15) == true); + try t.remove(10); + try testing.expect(t.size == 5); + try testing.expect(t.search(10) == false); + + var ino = ArrayList(i32).init(allocator); + defer ino.deinit(); + + const check_ino = [_]i32{ 3, 5, 12, 15, 25 }; + try t.inorder(&ino); + try testing.expect(std.mem.eql(i32, ino.items, &check_ino)); + + var pre = ArrayList(i32).init(allocator); + defer pre.deinit(); + + const check_pre = [_]i32{ 12, 5, 3, 25, 15 }; + try t.preorder(&pre); + + try testing.expect(std.mem.eql(i32, pre.items, &check_pre)); + + var post = ArrayList(i32).init(allocator); + defer post.deinit(); + + const check_post = [_]i32{ 3, 5, 15, 25, 12 }; + try t.postorder(&post); -test "search for an element with multiple nodes" { - var tree = Tree(i32){}; - const values = [_]i32{ 15, 18, 17, 6, 7, 20, 3, 13, 2, 4, 9 }; - for (values) |v| { - var node = Node(i32){ .value = v }; - tree.insert(&node); - } - const result = Tree(i32).search(tree.root, 9); - try expect(result.?.value == 9); + try testing.expect(std.mem.eql(i32, post.items, &check_post)); } From 0cc4afa6649960aab279c3856acbca8e7d087c32 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 3 Jan 2025 16:08:02 +0200 Subject: [PATCH 04/10] Added some more unit tests --- search/binarySearchTree.zig | 53 +++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/search/binarySearchTree.zig b/search/binarySearchTree.zig index 9c9683c..3f8fbb1 100644 --- a/search/binarySearchTree.zig +++ b/search/binarySearchTree.zig @@ -193,7 +193,7 @@ pub fn BinarySearchTree(comptime T: type) type { }; } -test "Testing Binary Search Tree" { +test "Testing insertion" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); var allocator = gpa.allocator(); @@ -209,12 +209,40 @@ test "Testing Binary Search Tree" { try testing.expect(t.size == 5); try testing.expect(t.search(10) == true); try testing.expect(t.search(15) == false); +} + +test "Testing bst removal" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + var allocator = gpa.allocator(); + + var t = BinarySearchTree(i32){ .allocator = &allocator }; + defer t.destroy(); + + try t.insert(10); + try t.insert(5); + try t.insert(3); try t.insert(15); - try testing.expect(t.size == 6); + try testing.expect(t.size == 4); try testing.expect(t.search(15) == true); try t.remove(10); - try testing.expect(t.size == 5); + try testing.expect(t.size == 3); try testing.expect(t.search(10) == false); +} + +test "Testing traversal methods" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + var allocator = gpa.allocator(); + + var t = BinarySearchTree(i32){ .allocator = &allocator }; + defer t.destroy(); + + try t.insert(5); + try t.insert(25); + try t.insert(3); + try t.insert(12); + try t.insert(15); var ino = ArrayList(i32).init(allocator); defer ino.deinit(); @@ -226,7 +254,7 @@ test "Testing Binary Search Tree" { var pre = ArrayList(i32).init(allocator); defer pre.deinit(); - const check_pre = [_]i32{ 12, 5, 3, 25, 15 }; + const check_pre = [_]i32{ 5, 3, 25, 12, 15 }; try t.preorder(&pre); try testing.expect(std.mem.eql(i32, pre.items, &check_pre)); @@ -234,8 +262,23 @@ test "Testing Binary Search Tree" { var post = ArrayList(i32).init(allocator); defer post.deinit(); - const check_post = [_]i32{ 3, 5, 15, 25, 12 }; + const check_post = [_]i32{ 3, 15, 12, 25, 5 }; try t.postorder(&post); try testing.expect(std.mem.eql(i32, post.items, &check_post)); } + + +test "Testing operations on empty trees" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + var allocator = gpa.allocator(); + + var t = BinarySearchTree(i32){ .allocator = &allocator }; + defer t.destroy(); + + try testing.expect(t.size == 0); + try testing.expect(t.search(10) == false); + try t.remove(10); + try testing.expect(t.search(10) == false); +} From 601db3174241c1548d06a5463437ebb6cdb0ff65 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 3 Jan 2025 16:09:02 +0200 Subject: [PATCH 05/10] Reformatting code --- search/binarySearchTree.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/search/binarySearchTree.zig b/search/binarySearchTree.zig index 3f8fbb1..6ab93e8 100644 --- a/search/binarySearchTree.zig +++ b/search/binarySearchTree.zig @@ -268,7 +268,6 @@ test "Testing traversal methods" { try testing.expect(std.mem.eql(i32, post.items, &check_post)); } - test "Testing operations on empty trees" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); From cba6568acd91f13fa99924d9aa31b3287ece9311 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 3 Jan 2025 19:19:34 +0200 Subject: [PATCH 06/10] Added longest increasing subsequence dp algorithm --- build.zig | 7 ++ .../longestIncreasingSubsequence.zig | 69 +++++++++++++++++++ runall.cmd | 1 + runall.sh | 1 + 4 files changed, 78 insertions(+) create mode 100644 dynamicProgramming/longestIncreasingSubsequence.zig diff --git a/build.zig b/build.zig index aca1ad9..4636075 100644 --- a/build.zig +++ b/build.zig @@ -97,6 +97,13 @@ pub fn build(b: *std.Build) void { .name = "knapsack.zig", .category = "dynamicProgramming" }); + if (std.mem.eql(u8, op, "dp/longestIncreasingSubsequence")) + build_algorithm(b, .{ + .optimize = optimize, + .target = target, + .name = "longestIncreasingSubsequence.zig", + .category = "dynamicProgramming" + }); // Math algorithms if (std.mem.eql(u8, op, "math/ceil")) diff --git a/dynamicProgramming/longestIncreasingSubsequence.zig b/dynamicProgramming/longestIncreasingSubsequence.zig new file mode 100644 index 0000000..678fc7c --- /dev/null +++ b/dynamicProgramming/longestIncreasingSubsequence.zig @@ -0,0 +1,69 @@ +const std = @import("std"); +const print = std.debug.print; +const testing = std.testing; +const ArrayList = std.ArrayList; + +// Function that returns the lower bound in O(logn) +pub fn lower_bound(arr: []const i32, key: i32) usize { + var lo: usize = 0; + var hi: usize = arr.len; + + while(lo < hi) { + const mid: usize = lo + (hi - lo) / 2; + if (key <= arr[mid]) { + hi = mid; + } + else { + lo = mid + 1; + } + } + + if (lo < arr.len and arr[lo] < key) { + lo += 1; + } + + return lo; +} + +// Function that returns the length of the longest increasing subsequence of an array +// Runs in O(nlogn) using the lower bound function +pub fn lis(arr: []const i32) usize { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + const allocator = gpa.allocator(); + + var v = ArrayList(i32).init(allocator); + defer v.deinit(); + + const n = arr.len; + + for (0..n) |i| { + const it = lower_bound(v.items, arr[i]); + if (it == v.items.len) { + _ = v.append(arr[i]) catch return 0; + } + else { + v.items[it] = arr[i]; + } + } + + return v.items.len; +} + +test "testing longest increasing subsequence function" { + const v = [4]i32 { 1, 5, 6, 7 }; + try testing.expect(lis(&v) == 4); + + const v2 = [5]i32 { 1, -1, 5, 6, 7 }; + try testing.expect(lis(&v2) == 4); + + const v3 = [5]i32 { 1, 2, -1, 0, 1 }; + try testing.expect(lis(&v3) == 3); + + const v4 = [0]i32 {}; + try testing.expect(lis(&v4) == 0); + + const v5 = [5]i32 { 0, 0, 0, 0, 0 }; + try testing.expect(lis(&v5) == 1); +} diff --git a/runall.cmd b/runall.cmd index a971e85..3848b0f 100644 --- a/runall.cmd +++ b/runall.cmd @@ -25,6 +25,7 @@ rem Data Structures rem Dynamic Programming %ZIG_TEST% -Dalgorithm=dp/coinChange %Args% %ZIG_TEST% -Dalgorithm=dp/knapsack %Args% +%ZIG_TEST% -Dalgorithm=dp/longestIncreasingSubsequence %Args% rem Sort %ZIG_TEST% -Dalgorithm=sort/quicksort %Args% diff --git a/runall.sh b/runall.sh index 9e394f7..cdd3947 100755 --- a/runall.sh +++ b/runall.sh @@ -25,6 +25,7 @@ $ZIG_TEST -Dalgorithm=ds/lrucache $Args # Dynamic Programming $ZIG_TEST -Dalgorithm=dp/coinChange $Args $ZIG_TEST -Dalgorithm=dp/knapsack $Args +$ZIG_TEST -Dalgorithm=dp/longestIncreasingSubsequence $Args ## Sort $ZIG_TEST -Dalgorithm=sort/quicksort $Args From a4014b225124e97802fdf46aaefa3c61faea609e Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 3 Jan 2025 19:22:01 +0200 Subject: [PATCH 07/10] Reformatting code --- .../longestIncreasingSubsequence.zig | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/dynamicProgramming/longestIncreasingSubsequence.zig b/dynamicProgramming/longestIncreasingSubsequence.zig index 678fc7c..243c1e3 100644 --- a/dynamicProgramming/longestIncreasingSubsequence.zig +++ b/dynamicProgramming/longestIncreasingSubsequence.zig @@ -8,12 +8,11 @@ pub fn lower_bound(arr: []const i32, key: i32) usize { var lo: usize = 0; var hi: usize = arr.len; - while(lo < hi) { + while (lo < hi) { const mid: usize = lo + (hi - lo) / 2; if (key <= arr[mid]) { hi = mid; - } - else { + } else { lo = mid + 1; } } @@ -42,8 +41,7 @@ pub fn lis(arr: []const i32) usize { const it = lower_bound(v.items, arr[i]); if (it == v.items.len) { _ = v.append(arr[i]) catch return 0; - } - else { + } else { v.items[it] = arr[i]; } } @@ -52,18 +50,18 @@ pub fn lis(arr: []const i32) usize { } test "testing longest increasing subsequence function" { - const v = [4]i32 { 1, 5, 6, 7 }; + const v = [4]i32{ 1, 5, 6, 7 }; try testing.expect(lis(&v) == 4); - const v2 = [5]i32 { 1, -1, 5, 6, 7 }; + const v2 = [5]i32{ 1, -1, 5, 6, 7 }; try testing.expect(lis(&v2) == 4); - const v3 = [5]i32 { 1, 2, -1, 0, 1 }; + const v3 = [5]i32{ 1, 2, -1, 0, 1 }; try testing.expect(lis(&v3) == 3); - const v4 = [0]i32 {}; + const v4 = [0]i32{}; try testing.expect(lis(&v4) == 0); - const v5 = [5]i32 { 0, 0, 0, 0, 0 }; + const v5 = [5]i32{ 0, 0, 0, 0, 0 }; try testing.expect(lis(&v5) == 1); } From 47f1ce5d55813e5dd544ba1e51f477a77bcfc697 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Fri, 3 Jan 2025 20:33:45 +0200 Subject: [PATCH 08/10] Added allocator support in function --- .../longestIncreasingSubsequence.zig | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/dynamicProgramming/longestIncreasingSubsequence.zig b/dynamicProgramming/longestIncreasingSubsequence.zig index 243c1e3..bc78012 100644 --- a/dynamicProgramming/longestIncreasingSubsequence.zig +++ b/dynamicProgramming/longestIncreasingSubsequence.zig @@ -26,12 +26,10 @@ pub fn lower_bound(arr: []const i32, key: i32) usize { // Function that returns the length of the longest increasing subsequence of an array // Runs in O(nlogn) using the lower bound function -pub fn lis(arr: []const i32) usize { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer _ = gpa.deinit(); - - const allocator = gpa.allocator(); - +// Arguments: +// arr: the passed array +// allocator: Any std.heap type allocator +pub fn lis(arr: []const i32, allocator: anytype) usize { var v = ArrayList(i32).init(allocator); defer v.deinit(); @@ -50,18 +48,21 @@ pub fn lis(arr: []const i32) usize { } test "testing longest increasing subsequence function" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const v = [4]i32{ 1, 5, 6, 7 }; - try testing.expect(lis(&v) == 4); + try testing.expect(lis(&v, gpa.allocator()) == 4); const v2 = [5]i32{ 1, -1, 5, 6, 7 }; - try testing.expect(lis(&v2) == 4); + try testing.expect(lis(&v2, gpa.allocator()) == 4); const v3 = [5]i32{ 1, 2, -1, 0, 1 }; - try testing.expect(lis(&v3) == 3); + try testing.expect(lis(&v3, gpa.allocator()) == 3); const v4 = [0]i32{}; - try testing.expect(lis(&v4) == 0); + try testing.expect(lis(&v4, gpa.allocator()) == 0); const v5 = [5]i32{ 0, 0, 0, 0, 0 }; - try testing.expect(lis(&v5) == 1); + try testing.expect(lis(&v5, gpa.allocator()) == 1); } From 61a6952657ecb6e3f7f6d3f4a69d03a982661c95 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Sat, 4 Jan 2025 14:19:53 +0200 Subject: [PATCH 09/10] Implemented edit distance dp function --- build.zig | 7 +++ dynamicProgramming/editDistance.zig | 71 +++++++++++++++++++++++++++++ runall.cmd | 1 + runall.sh | 1 + 4 files changed, 80 insertions(+) create mode 100644 dynamicProgramming/editDistance.zig diff --git a/build.zig b/build.zig index 4636075..70d44f9 100644 --- a/build.zig +++ b/build.zig @@ -104,6 +104,13 @@ pub fn build(b: *std.Build) void { .name = "longestIncreasingSubsequence.zig", .category = "dynamicProgramming" }); + if (std.mem.eql(u8, op, "dp/editDistance")) + build_algorithm(b, .{ + .optimize = optimize, + .target = target, + .name = "editDistance.zig", + .category = "dynamicProgramming" + }); // Math algorithms if (std.mem.eql(u8, op, "math/ceil")) diff --git a/dynamicProgramming/editDistance.zig b/dynamicProgramming/editDistance.zig new file mode 100644 index 0000000..fc20375 --- /dev/null +++ b/dynamicProgramming/editDistance.zig @@ -0,0 +1,71 @@ +const std = @import("std"); +const testing = std.testing; + +// Function that computes the minimum distance(or operations) to make 2 strings equal +// Well known as the edit distance dp function. +// Arguments: +// word1: The first passed string +// word2: The second passed string +// Returns u32: The minimum operations to make the 2 strings equal +pub fn minDist(comptime word1: []const u8, comptime word2: []const u8) u32 { + if (word1.len == 0 and word2.len == 0) { return 0; } + if (word1.len == 0 and word2.len != 0) { return @as(u32, @intCast(word2.len)); } + if (word1.len != 0 and word2.len == 0) { return @as(u32, @intCast(word1.len)); } + + const n: usize = word1.len; + const w: usize = word2.len; + + var dp: [n + 1][w + 1]u32 = undefined; + for (0..(n + 1)) |i| { + dp[i][0] = @as(u32, @intCast(i)); + } + + for (0..(w + 1)) |i| { + dp[0][i] = @as(u32, @intCast(i)); + } + + for (1..(n + 1)) |i| { + for (1..(w + 1)) |j| { + if (word1[i - 1] == word2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } + else { + dp[i][j] = @min(dp[i - 1][j - 1], @min(dp[i - 1][j], dp[i][j - 1])) + 1; + } + } + } + + return dp[n][w]; +} + +test "Testing edit distance function" { + const word1 = "hello"; + const word2 = "world"; + + try testing.expect(minDist(word1, word2) == 4); + + const word3 = "Hell0There"; + const word4 = "hellothere"; + + try testing.expect(minDist(word3, word4) == 3); + + const word5 = "abcdefg"; + const word6 = "abcdefg"; + + try testing.expect(minDist(word5, word6) == 0); + + const word7 = ""; + const word8 = "abasda"; + + try testing.expect(minDist(word7, word8) == 6); + + const word9 = "abcsa"; + const word10 = ""; + + try testing.expect(minDist(word9, word10) == 5); + + const word11 = "sdasdafda"; + const word12 = "sdasdbbba"; + + try testing.expect(minDist(word11, word12) == 3); +} diff --git a/runall.cmd b/runall.cmd index 3848b0f..960f9db 100644 --- a/runall.cmd +++ b/runall.cmd @@ -26,6 +26,7 @@ rem Dynamic Programming %ZIG_TEST% -Dalgorithm=dp/coinChange %Args% %ZIG_TEST% -Dalgorithm=dp/knapsack %Args% %ZIG_TEST% -Dalgorithm=dp/longestIncreasingSubsequence %Args% +%ZIG_TEST% -Dalgorithm=dp/editDistance %Args% rem Sort %ZIG_TEST% -Dalgorithm=sort/quicksort %Args% diff --git a/runall.sh b/runall.sh index cdd3947..7d2c4a2 100755 --- a/runall.sh +++ b/runall.sh @@ -26,6 +26,7 @@ $ZIG_TEST -Dalgorithm=ds/lrucache $Args $ZIG_TEST -Dalgorithm=dp/coinChange $Args $ZIG_TEST -Dalgorithm=dp/knapsack $Args $ZIG_TEST -Dalgorithm=dp/longestIncreasingSubsequence $Args +$ZIG_TEST -Dalgorithm=dp/editDistance $Args ## Sort $ZIG_TEST -Dalgorithm=sort/quicksort $Args From 489810a30d9314b87544a566aa6be30f3257d084 Mon Sep 17 00:00:00 2001 From: Spiros Maggioros Date: Sat, 4 Jan 2025 14:24:56 +0200 Subject: [PATCH 10/10] Reformatting code --- dynamicProgramming/editDistance.zig | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dynamicProgramming/editDistance.zig b/dynamicProgramming/editDistance.zig index fc20375..cac86b9 100644 --- a/dynamicProgramming/editDistance.zig +++ b/dynamicProgramming/editDistance.zig @@ -8,9 +8,15 @@ const testing = std.testing; // word2: The second passed string // Returns u32: The minimum operations to make the 2 strings equal pub fn minDist(comptime word1: []const u8, comptime word2: []const u8) u32 { - if (word1.len == 0 and word2.len == 0) { return 0; } - if (word1.len == 0 and word2.len != 0) { return @as(u32, @intCast(word2.len)); } - if (word1.len != 0 and word2.len == 0) { return @as(u32, @intCast(word1.len)); } + if (word1.len == 0 and word2.len == 0) { + return 0; + } + if (word1.len == 0 and word2.len != 0) { + return @as(u32, @intCast(word2.len)); + } + if (word1.len != 0 and word2.len == 0) { + return @as(u32, @intCast(word1.len)); + } const n: usize = word1.len; const w: usize = word2.len; @@ -28,8 +34,7 @@ pub fn minDist(comptime word1: []const u8, comptime word2: []const u8) u32 { for (1..(w + 1)) |j| { if (word1[i - 1] == word2[j - 1]) { dp[i][j] = dp[i - 1][j - 1]; - } - else { + } else { dp[i][j] = @min(dp[i - 1][j - 1], @min(dp[i - 1][j], dp[i][j - 1])) + 1; } }