mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 06:23:45 +00:00
add a pairing heap implementation
This commit is contained in:
180
src/heap.zig
Normal file
180
src/heap.zig
Normal file
@@ -0,0 +1,180 @@
|
||||
//! https://github.com/mitchellh/libxev/blob/main/src/heap.zig
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
/// An intrusive heap implementation backed by a pairing heap[1] implementation.
|
||||
///
|
||||
/// Why? Intrusive data structures require the element type to hold the metadata
|
||||
/// required for the structure, rather than an additional container structure.
|
||||
/// There are numerous pros/cons that are documented well by Boost[2]. For Zig,
|
||||
/// I think the primary benefits are making data structures allocation free
|
||||
/// (rather, shifting allocation up to the consumer which can choose how they
|
||||
/// want the memory to be available). There are various costs to this such as
|
||||
/// the costs of pointer chasing, larger memory overhead, requiring the element
|
||||
/// type to be aware of its container, etc. But for certain use cases an intrusive
|
||||
/// data structure can yield much better performance.
|
||||
///
|
||||
/// Usage notes:
|
||||
/// - The element T is expected to have a field "heap" of type InstrusiveHeapField.
|
||||
/// See the tests for a full example of how to set this.
|
||||
/// - You can easily make this a min or max heap by inverting the result of
|
||||
/// "less" below.
|
||||
///
|
||||
/// [1]: https://en.wikipedia.org/wiki/Pairing_heap
|
||||
/// [2]: https://www.boost.org/doc/libs/1_64_0/doc/html/intrusive/intrusive_vs_nontrusive.html
|
||||
pub fn Intrusive(
|
||||
comptime T: type,
|
||||
comptime Context: type,
|
||||
comptime less: *const fn (ctx: Context, a: *T, b: *T) bool,
|
||||
) type {
|
||||
lp.assert(@FieldType(T, "heap") == IntrusiveField(T), "heap: unexpected type", .{ .type = @typeName(T) });
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
root: ?*T = null,
|
||||
context: Context,
|
||||
|
||||
/// Insert a new element v into the heap. An element v can only
|
||||
/// be a member of a single heap at any given time. When compiled
|
||||
/// with runtime-safety, assertions will help verify this property.
|
||||
pub fn insert(self: *Self, v: *T) void {
|
||||
self.root = if (self.root) |root| self.meld(v, root) else v;
|
||||
}
|
||||
|
||||
/// Look at the next minimum value but do not remove it.
|
||||
pub fn peek(self: *Self) ?*T {
|
||||
return self.root;
|
||||
}
|
||||
|
||||
/// Delete the minimum value from the heap and return it.
|
||||
pub fn deleteMin(self: *Self) ?*T {
|
||||
const root = self.root orelse return null;
|
||||
self.root = if (root.heap.child) |child|
|
||||
self.combine_siblings(child)
|
||||
else
|
||||
null;
|
||||
|
||||
// Clear pointers with runtime safety so we can verify on
|
||||
// insert that values aren't incorrectly being set multiple times.
|
||||
root.heap = .{};
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/// Remove the value v from the heap.
|
||||
pub fn remove(self: *Self, v: *T) void {
|
||||
// If v doesn't have a previous value, this must be the root
|
||||
// element. If it is NOT the root element, v can't be in this
|
||||
// heap and we trigger an assertion failure.
|
||||
const prev = v.heap.prev orelse {
|
||||
lp.assert(self.root.? == v, "heap: remove", .{});
|
||||
_ = self.deleteMin();
|
||||
return;
|
||||
};
|
||||
|
||||
// Detach "v" from the tree and clean up any links so it
|
||||
// is as if this node never nexisted. The previous value
|
||||
// must point to the proper next value and the pointers
|
||||
// must all be cleaned up.
|
||||
if (v.heap.next) |next| next.heap.prev = prev;
|
||||
if (prev.heap.child == v)
|
||||
prev.heap.child = v.heap.next
|
||||
else
|
||||
prev.heap.next = v.heap.next;
|
||||
v.heap.prev = null;
|
||||
v.heap.next = null;
|
||||
|
||||
// If we have children, then we need to merge them back in.
|
||||
const child = v.heap.child orelse return;
|
||||
v.heap.child = null;
|
||||
const x = self.combine_siblings(child);
|
||||
self.root = self.meld(x, self.root.?);
|
||||
}
|
||||
|
||||
/// Meld (union) two heaps together. This isn't a generalized
|
||||
/// union. It assumes that a.heap.next is null so this is only
|
||||
/// meant in specific scenarios in the pairing heap where meld
|
||||
/// is expected.
|
||||
///
|
||||
/// For example, when melding a new value "v" with an existing
|
||||
/// root "root", "v" must always be the first param.
|
||||
fn meld(self: *Self, a: *T, b: *T) *T {
|
||||
lp.assert(a.heap.next == null, "heap: meld", .{});
|
||||
|
||||
if (less(self.context, a, b)) {
|
||||
// B points back to A
|
||||
b.heap.prev = a;
|
||||
|
||||
// If B has siblings, then A inherits B's siblings
|
||||
// and B's immediate sibling must point back to A to
|
||||
// maintain the doubly linked list.
|
||||
if (b.heap.next) |b_next| {
|
||||
a.heap.next = b_next;
|
||||
b_next.heap.prev = a;
|
||||
b.heap.next = null;
|
||||
}
|
||||
|
||||
// If A has a child, then B becomes the leftmost sibling
|
||||
// of that child.
|
||||
if (a.heap.child) |a_child| {
|
||||
b.heap.next = a_child;
|
||||
a_child.heap.prev = b;
|
||||
}
|
||||
|
||||
// B becomes the leftmost child of A
|
||||
a.heap.child = b;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
// Replace A with B in the tree. Any of B's children
|
||||
// become siblings of A. A becomes the leftmost child of B.
|
||||
// A points back to B
|
||||
b.heap.prev = a.heap.prev;
|
||||
a.heap.prev = b;
|
||||
if (b.heap.child) |b_child| {
|
||||
a.heap.next = b_child;
|
||||
b_child.heap.prev = a;
|
||||
}
|
||||
b.heap.child = a;
|
||||
return b;
|
||||
}
|
||||
|
||||
/// Combine the siblings of the leftmost value "left" into a single
|
||||
/// new rooted with the minimum value.
|
||||
fn combine_siblings(self: *Self, left: *T) *T {
|
||||
left.heap.prev = null;
|
||||
|
||||
// Merge pairs right
|
||||
var root: *T = root: {
|
||||
var a: *T = left;
|
||||
while (true) {
|
||||
var b = a.heap.next orelse break :root a;
|
||||
a.heap.next = null;
|
||||
b = self.meld(a, b);
|
||||
a = b.heap.next orelse break :root b;
|
||||
}
|
||||
};
|
||||
|
||||
// Merge pairs left
|
||||
while (true) {
|
||||
var b = root.heap.prev orelse return root;
|
||||
b.heap.next = null;
|
||||
root = self.meld(b, root);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// The state that is required for IntrusiveHeap element types. This
|
||||
/// should be set as the "heap" field in the type T.
|
||||
pub fn IntrusiveField(comptime T: type) type {
|
||||
return struct {
|
||||
child: ?*T = null,
|
||||
prev: ?*T = null,
|
||||
next: ?*T = null,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user