156 lines
3.6 KiB
Markdown
156 lines
3.6 KiB
Markdown
# std.DoublyLinkedList / std.SinglyLinkedList
|
|
|
|
Intrusive linked lists for O(1) insertion/removal. Nodes are embedded in user structs via `@fieldParentPtr`.
|
|
|
|
## When to Use
|
|
|
|
- O(1) insertion/removal anywhere in list
|
|
- Elements that need to be in multiple lists
|
|
- Preallocated/arena-allocated nodes
|
|
- No allocation on insert (nodes already exist)
|
|
|
|
## DoublyLinkedList
|
|
|
|
Bidirectional traversal, O(1) removal of any node.
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
|
|
const Item = struct {
|
|
data: u32,
|
|
node: std.DoublyLinkedList.Node = .{}, // embed node
|
|
};
|
|
|
|
var list: std.DoublyLinkedList = .{};
|
|
|
|
// Create items (you manage memory)
|
|
var a: Item = .{ .data = 1 };
|
|
var b: Item = .{ .data = 2 };
|
|
var c: Item = .{ .data = 3 };
|
|
|
|
// Insert
|
|
list.append(&a.node); // add to end
|
|
list.prepend(&b.node); // add to start
|
|
list.insertAfter(&a.node, &c.node); // insert c after a
|
|
list.insertBefore(&a.node, &c.node); // insert c before a
|
|
|
|
// Remove
|
|
list.remove(&a.node); // O(1) remove specific node
|
|
const last = list.pop(); // remove and return last
|
|
const first = list.popFirst(); // remove and return first
|
|
|
|
// Get data from node
|
|
if (list.first) |node| {
|
|
const item: *Item = @fieldParentPtr("node", node);
|
|
std.debug.print("data: {}\n", .{item.data});
|
|
}
|
|
|
|
// Traverse forward
|
|
var it = list.first;
|
|
while (it) |node| : (it = node.next) {
|
|
const item: *Item = @fieldParentPtr("node", node);
|
|
// use item.data
|
|
}
|
|
|
|
// Traverse backward
|
|
var it = list.last;
|
|
while (it) |node| : (it = node.prev) {
|
|
const item: *Item = @fieldParentPtr("node", node);
|
|
// use item.data
|
|
}
|
|
|
|
// Concatenate (moves all from list2 to end of list1)
|
|
list1.concatByMoving(&list2);
|
|
|
|
// Length (O(n) - consider tracking separately)
|
|
const n = list.len();
|
|
```
|
|
|
|
## SinglyLinkedList
|
|
|
|
Forward-only, minimal memory (one pointer per node).
|
|
|
|
```zig
|
|
const Item = struct {
|
|
data: u32,
|
|
node: std.SinglyLinkedList.Node = .{},
|
|
};
|
|
|
|
var list: std.SinglyLinkedList = .{};
|
|
|
|
var a: Item = .{ .data = 1 };
|
|
var b: Item = .{ .data = 2 };
|
|
|
|
// Insert (only at front or after existing node)
|
|
list.prepend(&a.node); // add to front
|
|
a.node.insertAfter(&b.node); // insert b after a
|
|
|
|
// Remove
|
|
const first = list.popFirst(); // remove and return first
|
|
_ = a.node.removeNext(); // remove node after a
|
|
list.remove(&b.node); // O(n) - must find predecessor
|
|
|
|
// Traverse (forward only)
|
|
var it = list.first;
|
|
while (it) |node| : (it = node.next) {
|
|
const item: *Item = @fieldParentPtr("node", node);
|
|
// use item.data
|
|
}
|
|
|
|
// Find last (O(n))
|
|
if (list.first) |first| {
|
|
const last = first.findLast();
|
|
}
|
|
|
|
// Reverse in place
|
|
std.SinglyLinkedList.Node.reverse(&list.first);
|
|
|
|
// Length (O(n))
|
|
const n = list.len();
|
|
```
|
|
|
|
## Node Methods
|
|
|
|
```zig
|
|
// DoublyLinkedList.Node
|
|
node.prev // ?*Node
|
|
node.next // ?*Node
|
|
|
|
// SinglyLinkedList.Node
|
|
node.next // ?*Node
|
|
node.insertAfter(new_node)
|
|
node.removeNext() // ?*Node - removes and returns next
|
|
node.findLast() // *Node
|
|
node.countChildren() // usize
|
|
node.reverse(&optional_ptr)
|
|
```
|
|
|
|
## Common Pattern: LRU Cache
|
|
|
|
```zig
|
|
const Entry = struct {
|
|
key: []const u8,
|
|
value: Value,
|
|
node: std.DoublyLinkedList.Node = .{},
|
|
};
|
|
|
|
var lru_list: std.DoublyLinkedList = .{};
|
|
var entries: std.StringHashMap(*Entry) = .init(allocator);
|
|
|
|
fn access(key: []const u8) ?*Entry {
|
|
const entry = entries.get(key) orelse return null;
|
|
// Move to front (most recently used)
|
|
lru_list.remove(&entry.node);
|
|
lru_list.prepend(&entry.node);
|
|
return entry;
|
|
}
|
|
|
|
fn evictOldest() void {
|
|
if (lru_list.pop()) |node| {
|
|
const entry: *Entry = @fieldParentPtr("node", node);
|
|
_ = entries.remove(entry.key);
|
|
// free entry
|
|
}
|
|
}
|
|
```
|