From 3aff43b2e88e0f6cab702de7873e787f06685933 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 7 Nov 2024 14:57:46 -0800 Subject: [PATCH] datastruct: add intrusive doubly linked list --- src/datastruct/intrusive_linked_list.zig | 177 +++++++++++++++++++++++ src/datastruct/main.zig | 2 + 2 files changed, 179 insertions(+) create mode 100644 src/datastruct/intrusive_linked_list.zig diff --git a/src/datastruct/intrusive_linked_list.zig b/src/datastruct/intrusive_linked_list.zig new file mode 100644 index 000000000..02279457f --- /dev/null +++ b/src/datastruct/intrusive_linked_list.zig @@ -0,0 +1,177 @@ +const std = @import("std"); +const testing = std.testing; + +/// An intrusive doubly-linked list. The type T must have a "next" and "prev" +/// field pointing to itself. +/// +/// This is an adaptation of the DoublyLinkedList from the Zig standard +/// library, which is MIT licensed. I've removed functionality that I don't +/// need. +pub fn DoublyLinkedList(comptime T: type) type { + return struct { + const Self = @This(); + + first: ?*T = null, + last: ?*T = null, + + /// Insert a new node after an existing one. + /// + /// Arguments: + /// node: Pointer to a node in the list. + /// new_node: Pointer to the new node to insert. + pub fn insertAfter(list: *Self, node: *T, new_node: *T) void { + new_node.prev = node; + if (node.next) |next_node| { + // Intermediate node. + new_node.next = next_node; + next_node.prev = new_node; + } else { + // Last element of the list. + new_node.next = null; + list.last = new_node; + } + node.next = new_node; + } + + /// Insert a new node before an existing one. + /// + /// Arguments: + /// node: Pointer to a node in the list. + /// new_node: Pointer to the new node to insert. + pub fn insertBefore(list: *Self, node: *T, new_node: *T) void { + new_node.next = node; + if (node.prev) |prev_node| { + // Intermediate node. + new_node.prev = prev_node; + prev_node.next = new_node; + } else { + // First element of the list. + new_node.prev = null; + list.first = new_node; + } + node.prev = new_node; + } + + /// Insert a new node at the end of the list. + /// + /// Arguments: + /// new_node: Pointer to the new node to insert. + pub fn append(list: *Self, new_node: *T) void { + if (list.last) |last| { + // Insert after last. + list.insertAfter(last, new_node); + } else { + // Empty list. + list.prepend(new_node); + } + } + + /// Insert a new node at the beginning of the list. + /// + /// Arguments: + /// new_node: Pointer to the new node to insert. + pub fn prepend(list: *Self, new_node: *T) void { + if (list.first) |first| { + // Insert before first. + list.insertBefore(first, new_node); + } else { + // Empty list. + list.first = new_node; + list.last = new_node; + new_node.prev = null; + new_node.next = null; + } + } + + /// Remove a node from the list. + /// + /// Arguments: + /// node: Pointer to the node to be removed. + pub fn remove(list: *Self, node: *T) void { + if (node.prev) |prev_node| { + // Intermediate node. + prev_node.next = node.next; + } else { + // First element of the list. + list.first = node.next; + } + + if (node.next) |next_node| { + // Intermediate node. + next_node.prev = node.prev; + } else { + // Last element of the list. + list.last = node.prev; + } + } + + /// Remove and return the last node in the list. + /// + /// Returns: + /// A pointer to the last node in the list. + pub fn pop(list: *Self) ?*T { + const last = list.last orelse return null; + list.remove(last); + return last; + } + + /// Remove and return the first node in the list. + /// + /// Returns: + /// A pointer to the first node in the list. + pub fn popFirst(list: *Self) ?*T { + const first = list.first orelse return null; + list.remove(first); + return first; + } + }; +} + +test "basic DoublyLinkedList test" { + const Node = struct { + data: u32, + prev: ?*@This() = null, + next: ?*@This() = null, + }; + const L = DoublyLinkedList(Node); + var list: L = .{}; + + var one: Node = .{ .data = 1 }; + var two: Node = .{ .data = 2 }; + var three: Node = .{ .data = 3 }; + var four: Node = .{ .data = 4 }; + var five: Node = .{ .data = 5 }; + + list.append(&two); // {2} + list.append(&five); // {2, 5} + list.prepend(&one); // {1, 2, 5} + list.insertBefore(&five, &four); // {1, 2, 4, 5} + list.insertAfter(&two, &three); // {1, 2, 3, 4, 5} + + // Traverse forwards. + { + var it = list.first; + var index: u32 = 1; + while (it) |node| : (it = node.next) { + try testing.expect(node.data == index); + index += 1; + } + } + + // Traverse backwards. + { + var it = list.last; + var index: u32 = 1; + while (it) |node| : (it = node.prev) { + try testing.expect(node.data == (6 - index)); + index += 1; + } + } + + _ = list.popFirst(); // {2, 3, 4, 5} + _ = list.pop(); // {2, 3, 4} + list.remove(&three); // {2, 4} + + try testing.expect(list.first.?.data == 2); + try testing.expect(list.last.?.data == 4); +} diff --git a/src/datastruct/main.zig b/src/datastruct/main.zig index 70a7ce97f..4f45f9483 100644 --- a/src/datastruct/main.zig +++ b/src/datastruct/main.zig @@ -4,12 +4,14 @@ const blocking_queue = @import("blocking_queue.zig"); const cache_table = @import("cache_table.zig"); const circ_buf = @import("circ_buf.zig"); +const intrusive_linked_list = @import("intrusive_linked_list.zig"); const segmented_pool = @import("segmented_pool.zig"); pub const lru = @import("lru.zig"); pub const BlockingQueue = blocking_queue.BlockingQueue; pub const CacheTable = cache_table.CacheTable; pub const CircBuf = circ_buf.CircBuf; +pub const IntrusiveDoublyLinkedList = intrusive_linked_list.DoublyLinkedList; pub const SegmentedPool = segmented_pool.SegmentedPool; test {