Skip to content

Commit 8314d95

Browse files
committed
Fix a few bugs
1 parent bd04a6c commit 8314d95

2 files changed

Lines changed: 131 additions & 24 deletions

File tree

src/ordered/cartesian_tree_map.zig

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,21 @@ pub fn CartesianTreeMap(
110110
/// Inserts a key-value pair with a random priority.
111111
///
112112
/// Uses per-instance PRNG priorities to ensure expected O(log n) performance.
113-
/// If the key already exists, updates its value and priority.
113+
/// If the key already exists, updates its value in place; the node keeps
114+
/// its current priority, so the tree shape (and balance) is unchanged.
114115
///
115116
/// Time complexity: O(log n) expected
116117
///
117118
/// ## Errors
118119
/// Returns `error.OutOfMemory` if node allocation fails.
119120
pub fn put(self: *Self, key: K, value: V) !void {
120-
const priority = self.prng.random().int(u32);
121-
try self.putWithPriority(key, value, priority);
121+
// A value-only update keeps the existing priority: re-randomising it
122+
// on every update would churn the tree structure for no benefit.
123+
if (self.findNode(key)) |existing| {
124+
existing.value = value;
125+
return;
126+
}
127+
try self.insertNew(key, value, self.prng.random().int(u32));
122128
}
123129

124130
/// Inserts a key-value pair with an explicit priority.
@@ -134,17 +140,20 @@ pub fn CartesianTreeMap(
134140
/// ## Errors
135141
/// Returns `error.OutOfMemory` if node allocation fails.
136142
pub fn putWithPriority(self: *Self, key: K, value: V, priority: u32) !void {
137-
// Update in place if the key already exists. Resolving duplicates up
138-
// front is required for correctness: `insertNode` only detects an
139-
// equal key at the node it is currently visiting, so once the new
140-
// priority exceeds an ancestor's priority it would split and insert
141-
// a second node with the same key.
142-
if (self.findNode(key)) |existing| {
143-
existing.value = value;
144-
existing.priority = priority;
145-
return;
143+
// To honor the explicit priority on an update, remove the existing
144+
// node and reinsert it. Reinsertion positions the node by its new
145+
// priority and preserves the max-heap invariant, whereas writing the
146+
// priority in place would leave a node out of heap order relative to
147+
// its parent or children.
148+
if (self.contains(key)) {
149+
_ = self.remove(key);
146150
}
151+
try self.insertNew(key, value, priority);
152+
}
147153

154+
/// Inserts a key known not to be present. Callers must ensure the key is
155+
/// absent so the insertion never produces a duplicate.
156+
fn insertNew(self: *Self, key: K, value: V, priority: u32) !void {
148157
const new_node = try self.allocator.create(Node);
149158
new_node.* = Node.init(key, value, priority);
150159

@@ -403,9 +412,28 @@ fn i32Compare(lhs: i32, rhs: i32) std.math.Order {
403412

404413
const MapOracle = @import("oracle.zig").MapOracle;
405414

415+
const TreapI32 = CartesianTreeMap(i32, i32, i32Compare);
416+
417+
/// Recursively asserts the two invariants every treap node must satisfy: BST
418+
/// ordering on keys and the max-heap property on priorities (a parent's
419+
/// priority is greater than or equal to each child's).
420+
fn expectTreapInvariants(node: ?*const TreapI32.Node) !void {
421+
const n = node orelse return;
422+
if (n.left) |l| {
423+
try testing.expect(i32Compare(l.key, n.key) == .lt);
424+
try testing.expect(l.priority <= n.priority);
425+
try expectTreapInvariants(l);
426+
}
427+
if (n.right) |r| {
428+
try testing.expect(i32Compare(r.key, n.key) == .gt);
429+
try testing.expect(r.priority <= n.priority);
430+
try expectTreapInvariants(r);
431+
}
432+
}
433+
406434
test "CartesianTreeMap: differential test against sorted-array oracle" {
407435
const allocator = testing.allocator;
408-
var tree = CartesianTreeMap(i32, i32, i32Compare).init(allocator);
436+
var tree = TreapI32.init(allocator);
409437
defer tree.deinit();
410438

411439
var oracle: MapOracle(i32, i32, i32Compare) = .{};
@@ -456,10 +484,53 @@ test "CartesianTreeMap: differential test against sorted-array oracle" {
456484
while (k < @as(i32, @intCast(key_space))) : (k += 1) {
457485
try testing.expectEqual(oracle.contains(k), tree.contains(k));
458486
}
487+
// Every put, remove, and value-update must leave the treap a valid
488+
// max-heap on priorities and a valid BST on keys.
489+
try expectTreapInvariants(tree.root);
459490
}
460491
}
461492
}
462493

494+
test "CartesianTreeMap: putWithPriority update preserves heap invariant" {
495+
var tree = TreapI32.init(testing.allocator);
496+
defer tree.deinit();
497+
498+
try tree.putWithPriority(5, 50, 10);
499+
try tree.putWithPriority(3, 30, 20);
500+
try tree.putWithPriority(8, 80, 5);
501+
try tree.putWithPriority(1, 10, 15);
502+
try tree.putWithPriority(7, 70, 25);
503+
try expectTreapInvariants(tree.root);
504+
505+
// Raising key 5's priority above all others must lift it to the root while
506+
// keeping the structure a valid treap.
507+
try tree.putWithPriority(5, 55, 100);
508+
try testing.expectEqual(@as(usize, 5), tree.count());
509+
try testing.expectEqual(@as(i32, 55), tree.get(5).?);
510+
try testing.expectEqual(@as(i32, 5), tree.root.?.key);
511+
try testing.expectEqual(@as(u32, 100), tree.root.?.priority);
512+
try expectTreapInvariants(tree.root);
513+
514+
// Lowering it again must re-sink it without breaking the invariants, and the
515+
// value update must persist.
516+
try tree.putWithPriority(5, 555, 1);
517+
try testing.expectEqual(@as(usize, 5), tree.count());
518+
try testing.expectEqual(@as(i32, 555), tree.get(5).?);
519+
try expectTreapInvariants(tree.root);
520+
}
521+
522+
test "CartesianTreeMap: put updates value without changing priority" {
523+
var tree = TreapI32.init(testing.allocator);
524+
defer tree.deinit();
525+
526+
try tree.putWithPriority(10, 100, 42);
527+
// A value-only `put` must leave the node's priority untouched.
528+
try tree.put(10, 200);
529+
try testing.expectEqual(@as(usize, 1), tree.count());
530+
try testing.expectEqual(@as(i32, 200), tree.get(10).?);
531+
try testing.expectEqual(@as(u32, 42), tree.root.?.priority);
532+
}
533+
463534
test "CartesianTreeMap basic operations" {
464535
var tree = CartesianTreeMap(i32, []const u8, i32Compare).init(testing.allocator);
465536
defer tree.deinit();

src/ordered/trie_map.zig

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ pub fn TrieMap(comptime V: type) type {
267267
errdefer stack.deinit(allocator);
268268
try stack.append(allocator, PrefixIteratorFrame{
269269
.node = prefix_node.?,
270-
.child_iter = prefix_node.?.children.iterator(),
270+
.next_char = 0,
271271
.visited_self = false,
272272
});
273273

@@ -286,7 +286,9 @@ pub fn TrieMap(comptime V: type) type {
286286

287287
pub const PrefixIteratorFrame = struct {
288288
node: *const TrieNode,
289-
child_iter: std.HashMap(u8, *TrieNode, std.hash_map.AutoContext(u8), std.hash_map.default_max_load_percentage).Iterator,
289+
// Next child byte to examine, scanned over 0..256 so children are
290+
// visited in ascending byte order, yielding keys in sorted order.
291+
next_char: u16,
290292
visited_self: bool,
291293
};
292294

@@ -303,22 +305,34 @@ pub fn TrieMap(comptime V: type) type {
303305

304306
pub fn next(self: *PrefixIterator) !?[]const u8 {
305307
while (self.stack.items.len > 0) {
306-
var frame = &self.stack.items[self.stack.items.len - 1];
308+
const top = self.stack.items.len - 1;
307309

308-
if (!frame.visited_self and frame.node.is_end) {
309-
frame.visited_self = true;
310+
if (!self.stack.items[top].visited_self and self.stack.items[top].node.is_end) {
311+
self.stack.items[top].visited_self = true;
310312
return self.current_key.items;
311313
}
312314

313-
if (frame.child_iter.next()) |entry| {
314-
const char = entry.key_ptr.*;
315-
const child = entry.value_ptr.*;
316-
317-
try self.current_key.append(self.allocator, char);
315+
// Scan ascending byte values for the next existing child.
316+
const node = self.stack.items[top].node;
317+
var next_child: ?*TrieNode = null;
318+
var next_byte: u8 = 0;
319+
while (self.stack.items[top].next_char < 256) {
320+
const char: u8 = @intCast(self.stack.items[top].next_char);
321+
self.stack.items[top].next_char += 1;
322+
if (node.children.get(char)) |child| {
323+
next_child = child;
324+
next_byte = char;
325+
break;
326+
}
327+
}
318328

329+
if (next_child) |child| {
330+
try self.current_key.append(self.allocator, next_byte);
331+
// This append may reallocate `stack`, so `top` is
332+
// recomputed on the next loop iteration rather than reused.
319333
try self.stack.append(self.allocator, PrefixIteratorFrame{
320334
.node = child,
321-
.child_iter = child.children.iterator(),
335+
.next_char = 0,
322336
.visited_self = false,
323337
});
324338
} else {
@@ -693,3 +707,25 @@ test "TrieMap: special characters" {
693707
try std.testing.expectEqual(@as(i32, 2), trie.get("test_case").?.*);
694708
try std.testing.expectEqual(@as(i32, 3), trie.get("foo.bar").?.*);
695709
}
710+
711+
test "TrieMap: keysWithPrefix yields keys in sorted order" {
712+
const allocator = std.testing.allocator;
713+
var trie = try TrieMap(i32).init(allocator);
714+
defer trie.deinit();
715+
716+
// Insert in an order that is neither sorted nor hash order.
717+
const keys = [_][]const u8{ "bandit", "ban", "bandana", "band", "banana", "bee", "apex" };
718+
for (keys, 0..) |k, i| try trie.put(k, @intCast(i));
719+
720+
var iter = try trie.keysWithPrefix(allocator, "ban");
721+
defer iter.deinit();
722+
723+
// Keys under the "ban" prefix must come out in lexicographic order.
724+
const expected = [_][]const u8{ "ban", "banana", "band", "bandana", "bandit" };
725+
var idx: usize = 0;
726+
while (try iter.next()) |k| : (idx += 1) {
727+
try std.testing.expect(idx < expected.len);
728+
try std.testing.expectEqualStrings(expected[idx], k);
729+
}
730+
try std.testing.expectEqual(expected.len, idx);
731+
}

0 commit comments

Comments
 (0)