diff --git a/lib/std/deque.zig b/lib/std/deque.zig index 267b8a0afe..2d4cfc8600 100644 --- a/lib/std/deque.zig +++ b/lib/std/deque.zig @@ -174,18 +174,115 @@ pub fn Deque(comptime T: type) type { deque.len += 1; } + /// Add `items` to the front of the deque. + /// This is equivalent to iterating `items` in reverse and calling + /// `pushFront` on every single entry. + /// + /// Invalidates element pointers if additional memory is needed. + pub fn pushFrontSlice(deque: *Self, gpa: Allocator, items: []const T) error{OutOfMemory}!void { + try deque.ensureUnusedCapacity(gpa, items.len); + return deque.pushFrontSliceAssumeCapacity(items); + } + + /// Add `items` to the front of the deque. + /// This is equivalent to iterating `items` in reverse and calling + /// `pushFront` on every single entry. + /// + /// Never invalidates element pointers. + /// + /// If the deque lacks unused capacity for the additional items, returns + /// `error.OutOfMemory`. + pub fn pushFrontSliceBounded(deque: *Self, items: []const T) error{OutOfMemory}!void { + if (deque.buffer.len - deque.len < items.len) return error.OutOfMemory; + return deque.pushFrontSliceAssumeCapacity(items); + } + + /// Add `items` to the front of the deque. + /// This is equivalent to iterating `items` in reverse and calling + /// `pushFront` on every single entry. + /// + /// Never invalidates element pointers. + /// + /// Asserts that the deque can hold the additional items. + pub fn pushFrontSliceAssumeCapacity(deque: *Self, items: []const T) void { + assert(deque.buffer.len - deque.len >= items.len); + if (deque.head < items.len) { + @memcpy(deque.buffer[0..deque.head], items[items.len - deque.head ..]); + deque.head = deque.buffer.len - items.len + deque.head; + @memcpy(deque.buffer[deque.head..], items.ptr); + } else { + deque.head -= items.len; + @memcpy(deque.buffer[deque.head..][0..items.len], items); + } + deque.len += items.len; + } + + /// Add `items` to the back of the deque. + /// This is equivalent to iterating `items` in order and calling + /// `pushBack` on every single entry. + /// + /// Invalidates element pointers if additional memory is needed. + pub fn pushBackSlice(deque: *Self, gpa: Allocator, items: []const T) error{OutOfMemory}!void { + try deque.ensureUnusedCapacity(gpa, items.len); + return deque.pushBackSliceAssumeCapacity(items); + } + + /// Add `items` to the back of the deque. + /// This is equivalent to iterating `items` in order and calling + /// `pushBack` on every single entry. + /// + /// Never invalidates element pointers. + /// + /// If the deque lacks unused capacity for the additional items, returns + /// `error.OutOfMemory`. + pub fn pushBackSliceBounded(deque: *Self, items: []const T) error{OutOfMemory}!void { + if (deque.buffer.len - deque.len < items.len) return error.OutOfMemory; + return deque.pushBackSliceAssumeCapacity(items); + } + + /// Add `items` to the back of the deque. + /// This is equivalent to iterating `items` in order and calling + /// `pushBack` on every single entry. + /// + /// Never invalidates element pointers. + /// + /// Asserts that the deque can hold the additional items. + pub fn pushBackSliceAssumeCapacity(deque: *Self, items: []const T) void { + assert(deque.buffer.len - deque.len >= items.len); + const trailing_buffer = deque.buffer[deque.bufferIndex(deque.len)..]; + if (trailing_buffer.len < items.len) { + @memcpy(trailing_buffer, items[0..trailing_buffer.len]); + @memcpy(deque.buffer.ptr, items[trailing_buffer.len..]); + } else { + @memcpy(trailing_buffer[0..items.len], items); + } + deque.len += items.len; + } + /// Return the first item in the deque or null if empty. pub fn front(deque: *const Self) ?T { if (deque.len == 0) return null; return deque.buffer[deque.head]; } + /// Return pointer to the first item in the deque or null if empty. + pub fn frontPtr(deque: *const Self) ?*T { + if (deque.len == 0) return null; + return &deque.buffer[deque.head]; + } + /// Return the last item in the deque or null if empty. pub fn back(deque: *const Self) ?T { if (deque.len == 0) return null; return deque.buffer[deque.bufferIndex(deque.len - 1)]; } + /// Return the last item in the deque or null if empty. + pub fn backPtr(deque: *const Self) ?*T { + if (deque.len == 0) return null; + return &deque.buffer[deque.bufferIndex(deque.len - 1)]; + } + /// Return the item at the given index in the deque. /// /// The first item in the queue is at index 0. @@ -196,6 +293,16 @@ pub fn Deque(comptime T: type) type { return deque.buffer[deque.bufferIndex(index)]; } + /// Return pointer to the item at the given index in the deque. + /// + /// The first item in the queue is at index 0. + /// + /// Asserts that the index is in-bounds. + pub fn atPtr(deque: *const Self, index: usize) *T { + assert(index < deque.len); + return &deque.buffer[deque.bufferIndex(index)]; + } + /// Remove and return the first item in the deque or null if empty. pub fn popFront(deque: *Self) ?T { if (deque.len == 0) return null; @@ -216,13 +323,24 @@ pub fn Deque(comptime T: type) type { deque: *const Self, index: usize, + pub fn peek(it: Iterator) ?T { + if (it.index >= it.deque.len) return null; + return it.deque.at(it.index); + } pub fn next(it: *Iterator) ?T { - if (it.index < it.deque.len) { - defer it.index += 1; - return it.deque.at(it.index); - } else { - return null; - } + const item = it.peek() orelse return null; + it.index += 1; + return item; + } + + pub fn peekPtr(it: Iterator) ?*T { + if (it.index >= it.deque.len) return null; + return it.deque.atPtr(it.index); + } + pub fn nextPtr(it: *Iterator) ?*T { + const item_ptr = it.peekPtr() orelse return null; + it.index += 1; + return item_ptr; } }; @@ -328,6 +446,74 @@ test "slow growth" { try testing.expectEqual(null, q.popBack()); } +test "slice" { + const testing = std.testing; + const gpa = testing.allocator; + + var q: Deque(i32) = .empty; + defer q.deinit(gpa); + + try q.pushBackSlice(gpa, &.{ 3, 4, 5 }); + try q.pushBackSlice(gpa, &.{ 6, 7 }); + try q.pushFrontSlice(gpa, &.{2}); + try q.pushBackSlice(gpa, &.{}); + try q.pushFrontSlice(gpa, &.{ 0, 1 }); + try q.pushFrontSlice(gpa, &.{}); + + try testing.expectEqual(0, q.popFront()); + try testing.expectEqual(1, q.popFront()); + try testing.expectEqual(7, q.popBack()); + try testing.expectEqual(6, q.popBack()); + + try q.pushFrontSlice(gpa, &.{ 0, 1 }); + try q.pushBackSlice(gpa, &.{ 6, 7 }); + + try testing.expectEqual(0, q.popFront()); + try testing.expectEqual(1, q.popFront()); + try testing.expectEqual(2, q.popFront()); + try testing.expectEqual(7, q.popBack()); + try testing.expectEqual(6, q.popBack()); + try testing.expectEqual(3, q.popFront()); + try testing.expectEqual(4, q.popFront()); + try testing.expectEqual(5, q.popBack()); + try testing.expectEqual(null, q.popFront()); + try testing.expectEqual(null, q.popBack()); +} + +test "iterator" { + const testing = std.testing; + const gpa = testing.allocator; + + var q: Deque(i32) = .empty; + defer q.deinit(gpa); + + const items: []const i32 = &.{ 0, 1, 2, 3, 4, 5 }; + try q.pushFrontSlice(gpa, items); + + { + var it = q.iterator(); + for (items) |item| { + try testing.expectEqual(item, it.peek()); + try testing.expectEqual(item, it.next()); + } + try testing.expectEqual(null, it.peek()); + try testing.expectEqual(null, it.next()); + } + { + var it = q.iterator(); + for (items) |item| { + if (it.peekPtr()) |ptr| { + try testing.expectEqual(item, ptr.*); + } else return error.TestExpectedNonNull; + if (it.nextPtr()) |ptr| { + try testing.expectEqual(item, ptr.*); + } else return error.TestExpectedNonNull; + } + try testing.expectEqual(null, it.peekPtr()); + try testing.expectEqual(null, it.nextPtr()); + } +} + test "fuzz against ArrayList oracle" { try std.testing.fuzz({}, fuzzAgainstArrayList, .{}); } @@ -362,6 +548,8 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void { const Action = enum { push_back, push_front, + push_back_slice, + push_front_slice, pop_back, pop_front, grow, @@ -384,6 +572,28 @@ fn fuzzAgainstArrayList(_: void, input: []const u8) anyerror!void { q.pushFrontBounded(item), ); }, + .push_back_slice => { + var buffer: [std.math.maxInt(u3)]u32 = undefined; + const items = buffer[0..random.int(u3)]; + for (items) |*item| { + item.* = random.int(u8); + } + try testing.expectEqual( + l.appendSliceBounded(items), + q.pushBackSliceBounded(items), + ); + }, + .push_front_slice => { + var buffer: [std.math.maxInt(u3)]u32 = undefined; + const items = buffer[0..random.int(u3)]; + for (items) |*item| { + item.* = random.int(u8); + } + try testing.expectEqual( + l.insertSliceBounded(0, items), + q.pushFrontSliceBounded(items), + ); + }, .pop_back => { try testing.expectEqual(l.pop(), q.popBack()); },