zig-skills/references/std-priority-dequeue.md

4.5 KiB

std.PriorityDequeue (Zig 0.16.0)

Primary release-note source: https://ziglang.org/download/0.16.0/release-notes.html

Zig 0.16 changed priority dequeues to align with unmanaged containers:

  • Initialize with .empty.
  • add -> push.
  • addSlice -> pushSlice.
  • The old unchecked insertion helper has no public pushUnchecked replacement.
  • removeMin / removeMinOrNull -> popMin.
  • removeMax / removeMaxOrNull -> popMax.
  • removeIndex -> popIndex.

All examples below use the Zig 0.16 unmanaged API. .empty leaves context undefined; use initContext whenever the comparator reads it.

A min-max heap that efficiently supports both min and max extraction. Unlike PriorityQueue, you can pop from either end.

When to Use

  • Need both min and max extraction
  • Double-ended priority queue
  • Sliding window min/max
  • Median maintenance (with two heaps)

Initialization

const std = @import("std");

fn compare(context: void, a: u32, b: u32) std.math.Order {
    _ = context;
    return std.math.order(a, b);
}

const PDQ = std.PriorityDequeue(u32, void, compare);

var dequeue = PDQ.initContext({});
defer dequeue.deinit(allocator);

Basic Operations

// Add elements
try dequeue.push(allocator, 54);
try dequeue.push(allocator, 12);
try dequeue.push(allocator, 7);

// Add multiple
try dequeue.pushSlice(allocator, &[_]u32{ 1, 2, 3 });

// Peek at min/max (doesn't remove)
if (dequeue.peekMin()) |min| {
    std.debug.print("min: {}\n", .{min});
}
if (dequeue.peekMax()) |max| {
    std.debug.print("max: {}\n", .{max});
}

// Remove min/max
const maybe_min = dequeue.popMin(); // ?T; null if empty
const maybe_max = dequeue.popMax(); // ?T; null if empty

// Size
const n = dequeue.count();
const cap = dequeue.capacity();

From Existing Slice

// Take ownership of slice, heapify in place
var items = try allocator.dupe(u32, &[_]u32{ 5, 3, 8, 1, 2 });
var dequeue = PDQ.fromOwnedSlice(items, {});
defer dequeue.deinit(allocator);

Update Priority

try dequeue.update(old_value, new_value);
// Lookup uses comparator equality. Equal-priority duplicates are ambiguous;
// the API does not identify a particular equal element. Errors if none exists.

Remove by Index

const removed = dequeue.popIndex(index); // asserts in bounds; heap index is not priority rank

Iteration

// Iterate (order is NOT priority order)
var it = dequeue.iterator();
while (it.next()) |elem| {
    // process elem
}
it.reset();

Capacity Management

try dequeue.ensureTotalCapacity(allocator, 100);
try dequeue.ensureUnusedCapacity(allocator, 10);
dequeue.shrinkAndFree(allocator, new_capacity);

Context-Based Comparator

fn compareByScore(scores: []const u32, a: usize, b: usize) std.math.Order {
    return std.math.order(scores[a], scores[b]);
}

const IndexPDQ = std.PriorityDequeue(usize, []const u32, compareByScore);

const scores = [_]u32{ 50, 30, 80, 20 };
var dequeue = IndexPDQ.initContext(scores[0..]);
defer dequeue.deinit(allocator);

Complete Example: Bounded Range Tracker

const std = @import("std");

fn order(_: void, a: i32, b: i32) std.math.Order {
    return std.math.order(a, b);
}

const RangePDQ = std.PriorityDequeue(i32, void, order);

pub fn main() !void {
    var gpa: std.heap.DebugAllocator(.{}) = .init;
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();
    var tracker = RangePDQ.initContext({});
    defer tracker.deinit(allocator);

    // Add values
    try tracker.push(allocator, 10);
    try tracker.push(allocator, 5);
    try tracker.push(allocator, 20);
    try tracker.push(allocator, 3);
    try tracker.push(allocator, 15);

    // Get range without removing
    const min = tracker.peekMin().?;  // 3
    const max = tracker.peekMax().?;  // 20
    const range = max - min;           // 17

    std.debug.print("Range: {} to {} = {}\n", .{ min, max, range });

    // Pop from both ends
    _ = tracker.popMin();  // removes 3
    _ = tracker.popMax();  // removes 20

    // New range is 5 to 15
}

Difference from PriorityQueue

Feature PriorityQueue PriorityDequeue
Pop min Yes Yes
Pop max No (unless you reverse comparator) Yes
Peek min Yes Yes
Peek max No Yes
Structure Binary heap Min-max heap

Notes

  • Both popMin() and popMax() are O(log n)
  • Both nullable peeks are O(1), including empty and one-element deques
  • Iterator order is heap array order, not priority order
  • Use when you need efficient access to both extremes