zig/std/linked_list.zig

328 lines
10 KiB
Zig

const std = @import("index.zig");
const debug = std.debug;
const assert = debug.assert;
const mem = std.mem;
const Allocator = mem.Allocator;
/// Generic non-intrusive doubly linked list.
pub fn LinkedList(comptime T: type) type {
return BaseLinkedList(T, void, "");
}
/// Generic intrusive doubly linked list.
pub fn IntrusiveLinkedList(comptime ParentType: type, comptime field_name: []const u8) type {
return BaseLinkedList(void, ParentType, field_name);
}
/// Generic doubly linked list.
fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_name: []const u8) type {
return struct {
const Self = this;
/// Node inside the linked list wrapping the actual data.
pub const Node = struct {
prev: ?&Node,
next: ?&Node,
data: T,
pub fn init(value: &const T) Node {
return Node {
.prev = null,
.next = null,
.data = *value,
};
}
pub fn initIntrusive() Node {
// TODO: when #678 is solved this can become `init`.
return Node.init({});
}
pub fn toData(node: &Node) &ParentType {
comptime assert(isIntrusive());
return @fieldParentPtr(ParentType, field_name, node);
}
};
first: ?&Node,
last: ?&Node,
len: usize,
/// Initialize a linked list.
///
/// Returns:
/// An empty linked list.
pub fn init() Self {
return Self {
.first = null,
.last = null,
.len = 0,
};
}
fn isIntrusive() bool {
return ParentType != void or field_name.len != 0;
}
/// 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: &Node, new_node: &Node) 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;
list.len += 1;
}
/// 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: &Node, new_node: &Node) 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;
list.len += 1;
}
/// 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: &Node) 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: &Node) 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;
list.len = 1;
}
}
/// Remove a node from the list.
///
/// Arguments:
/// node: Pointer to the node to be removed.
pub fn remove(list: &Self, node: &Node) 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;
}
list.len -= 1;
assert(list.len == 0 or (list.first != null and list.last != null));
}
/// Remove and return the last node in the list.
///
/// Returns:
/// A pointer to the last node in the list.
pub fn pop(list: &Self) ?&Node {
const last = list.last ?? 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) ?&Node {
const first = list.first ?? return null;
list.remove(first);
return first;
}
/// Allocate a new node.
///
/// Arguments:
/// allocator: Dynamic memory allocator.
///
/// Returns:
/// A pointer to the new node.
pub fn allocateNode(list: &Self, allocator: &Allocator) !&Node {
comptime assert(!isIntrusive());
return allocator.create(Node);
}
/// Deallocate a node.
///
/// Arguments:
/// node: Pointer to the node to deallocate.
/// allocator: Dynamic memory allocator.
pub fn destroyNode(list: &Self, node: &Node, allocator: &Allocator) void {
comptime assert(!isIntrusive());
allocator.destroy(node);
}
/// Allocate and initialize a node and its data.
///
/// Arguments:
/// data: The data to put inside the node.
/// allocator: Dynamic memory allocator.
///
/// Returns:
/// A pointer to the new node.
pub fn createNode(list: &Self, data: &const T, allocator: &Allocator) !&Node {
comptime assert(!isIntrusive());
var node = try list.allocateNode(allocator);
*node = Node.init(data);
return node;
}
};
}
test "basic linked list test" {
const allocator = debug.global_allocator;
var list = LinkedList(u32).init();
var one = try list.createNode(1, allocator);
var two = try list.createNode(2, allocator);
var three = try list.createNode(3, allocator);
var four = try list.createNode(4, allocator);
var five = try list.createNode(5, allocator);
defer {
list.destroyNode(one, allocator);
list.destroyNode(two, allocator);
list.destroyNode(three, allocator);
list.destroyNode(four, allocator);
list.destroyNode(five, allocator);
}
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) {
assert(node.data == index);
index += 1;
}
}
// Traverse backwards.
{
var it = list.last;
var index: u32 = 1;
while (it) |node| : (it = node.prev) {
assert(node.data == (6 - index));
index += 1;
}
}
var first = list.popFirst(); // {2, 3, 4, 5}
var last = list.pop(); // {2, 3, 4}
list.remove(three); // {2, 4}
assert ((??list.first).data == 2);
assert ((??list.last ).data == 4);
assert (list.len == 2);
}
const ElementList = IntrusiveLinkedList(Element, "link");
const Element = struct {
value: u32,
link: IntrusiveLinkedList(Element, "link").Node,
};
test "basic intrusive linked list test" {
const allocator = debug.global_allocator;
var list = ElementList.init();
var one = Element { .value = 1, .link = ElementList.Node.initIntrusive() };
var two = Element { .value = 2, .link = ElementList.Node.initIntrusive() };
var three = Element { .value = 3, .link = ElementList.Node.initIntrusive() };
var four = Element { .value = 4, .link = ElementList.Node.initIntrusive() };
var five = Element { .value = 5, .link = ElementList.Node.initIntrusive() };
list.append(&two.link); // {2}
list.append(&five.link); // {2, 5}
list.prepend(&one.link); // {1, 2, 5}
list.insertBefore(&five.link, &four.link); // {1, 2, 4, 5}
list.insertAfter(&two.link, &three.link); // {1, 2, 3, 4, 5}
// Traverse forwards.
{
var it = list.first;
var index: u32 = 1;
while (it) |node| : (it = node.next) {
assert(node.toData().value == index);
index += 1;
}
}
// Traverse backwards.
{
var it = list.last;
var index: u32 = 1;
while (it) |node| : (it = node.prev) {
assert(node.toData().value == (6 - index));
index += 1;
}
}
var first = list.popFirst(); // {2, 3, 4, 5}
var last = list.pop(); // {2, 3, 4}
list.remove(&three.link); // {2, 4}
assert ((??list.first).toData().value == 2);
assert ((??list.last ).toData().value == 4);
assert (list.len == 2);
}