183 lines
4.5 KiB
Markdown
183 lines
4.5 KiB
Markdown
# 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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
// 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
|
|
|
|
```zig
|
|
// 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
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
const removed = dequeue.popIndex(index); // asserts in bounds; heap index is not priority rank
|
|
```
|
|
|
|
## Iteration
|
|
|
|
```zig
|
|
// Iterate (order is NOT priority order)
|
|
var it = dequeue.iterator();
|
|
while (it.next()) |elem| {
|
|
// process elem
|
|
}
|
|
it.reset();
|
|
```
|
|
|
|
## Capacity Management
|
|
|
|
```zig
|
|
try dequeue.ensureTotalCapacity(allocator, 100);
|
|
try dequeue.ensureUnusedCapacity(allocator, 10);
|
|
dequeue.shrinkAndFree(allocator, new_capacity);
|
|
```
|
|
|
|
## Context-Based Comparator
|
|
|
|
```zig
|
|
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
|
|
|
|
```zig
|
|
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
|