├── .gitignore ├── .github └── workflows │ └── ci.yml ├── README.md ├── LICENSE └── src └── deque.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: mlugg/setup-zig@v2 17 | with: 18 | version: 0.14.1 19 | - name: fmt 20 | run: zig fmt --check *.zig src/*.zig 21 | - name: test 22 | run: zig build test 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-deque 2 | 3 | Double-ended queue implementation in Zig, ported from [the Rust's standard library](https://doc.rust-lang.org/std/collections/struct.VecDeque.html). 4 | 5 | ## Usage 6 | 7 | ```zig 8 | const std = @import("std"); 9 | 10 | var deque = try Deque(usize).init(std.heap.page_allocator); 11 | defer deque.deinit(); 12 | 13 | // You can efficiently push items to both ends 14 | try deque.pushBack(1); 15 | try deque.pushBack(2); 16 | try deque.pushFront(0); 17 | 18 | // Possible to random-access via `get` method 19 | std.debug.assert(deque.get(0).?.* == @as(usize, 0)); 20 | std.debug.assert(deque.get(1).?.* == @as(usize, 1)); 21 | std.debug.assert(deque.get(2).?.* == @as(usize, 2)); 22 | std.debug.assert(deque.get(3) == null); 23 | 24 | // An iterator is provided 25 | var it = deque.iterator(); 26 | var sum: usize = 0; 27 | while (it.next()) |val| { 28 | sum += val.*; 29 | } 30 | std.debug.assert(sum == 3); 31 | 32 | // Of course, you can pop items from both ends 33 | std.debug.assert(deque.popFront().? == @as(usize, 0)); 34 | std.debug.assert(deque.popBack().? == @as(usize, 2)); 35 | ``` 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Yusuke Tanaka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/deque.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mem = std.mem; 3 | const math = std.math; 4 | const Allocator = mem.Allocator; 5 | const assert = std.debug.assert; 6 | 7 | /// Double-ended queue ported from Rust's standard library, which is provided under MIT License. 8 | /// It can be found at https://github.com/rust-lang/rust/blob/master/LICENSE-MIT 9 | pub fn Deque(comptime T: type) type { 10 | return struct { 11 | /// tail and head are pointers into the buffer. Tail always points 12 | /// to the first element that could be read, Head always points 13 | /// to where data should be written. 14 | /// If tail == head the buffer is empty. The length of the ringbuffer 15 | /// is defined as the distance between the two. 16 | tail: usize, 17 | head: usize, 18 | /// Users should **NOT** use this field directly. 19 | /// In order to access an item with an index, use `get` method. 20 | /// If you want to iterate over the items, call `iterator` method to get an iterator. 21 | buf: []T, 22 | allocator: Allocator, 23 | 24 | const Self = @This(); 25 | const INITIAL_CAPACITY = 7; // 2^3 - 1 26 | const MINIMUM_CAPACITY = 1; // 2 - 1 27 | 28 | /// Creates an empty deque. 29 | /// Deinitialize with `deinit`. 30 | pub fn init(allocator: Allocator) Allocator.Error!Self { 31 | return initCapacity(allocator, INITIAL_CAPACITY); 32 | } 33 | 34 | /// Creates an empty deque with space for at least `capacity` elements. 35 | /// 36 | /// Note that there is no guarantee that the created Deque has the specified capacity. 37 | /// If it is too large, this method gives up meeting the capacity requirement. 38 | /// In that case, it will instead create a Deque with the default capacity anyway. 39 | /// 40 | /// Deinitialize with `deinit`. 41 | pub fn initCapacity(allocator: Allocator, capacity: usize) Allocator.Error!Self { 42 | const effective_cap = 43 | math.ceilPowerOfTwo(usize, @max(capacity +| 1, MINIMUM_CAPACITY + 1)) catch 44 | math.ceilPowerOfTwoAssert(usize, INITIAL_CAPACITY + 1); 45 | const buf = try allocator.alloc(T, effective_cap); 46 | return Self{ 47 | .tail = 0, 48 | .head = 0, 49 | .buf = buf, 50 | .allocator = allocator, 51 | }; 52 | } 53 | 54 | /// Release all allocated memory. 55 | pub fn deinit(self: Self) void { 56 | self.allocator.free(self.buf); 57 | } 58 | 59 | /// Returns the length of the already-allocated buffer. 60 | pub fn cap(self: Self) usize { 61 | return self.buf.len; 62 | } 63 | 64 | /// Returns the number of elements in the deque. 65 | pub fn len(self: Self) usize { 66 | return count(self.tail, self.head, self.cap()); 67 | } 68 | 69 | /// Gets the pointer to the element with the given index, if any. 70 | /// Otherwise it returns `null`. 71 | pub fn get(self: Self, index: usize) ?*T { 72 | if (index >= self.len()) return null; 73 | 74 | const idx = self.wrapAdd(self.tail, index); 75 | return &self.buf[idx]; 76 | } 77 | 78 | /// Gets the pointer to the first element, if any. 79 | pub fn front(self: Self) ?*T { 80 | return self.get(0); 81 | } 82 | 83 | /// Gets the pointer to the last element, if any. 84 | pub fn back(self: Self) ?*T { 85 | const last_idx = math.sub(usize, self.len(), 1) catch return null; 86 | return self.get(last_idx); 87 | } 88 | 89 | /// Adds the given element to the back of the deque. 90 | pub fn pushBack(self: *Self, item: T) Allocator.Error!void { 91 | if (self.isFull()) { 92 | try self.grow(); 93 | } 94 | 95 | const head = self.head; 96 | self.head = self.wrapAdd(self.head, 1); 97 | self.buf[head] = item; 98 | } 99 | 100 | /// Adds the given element to the front of the deque. 101 | pub fn pushFront(self: *Self, item: T) Allocator.Error!void { 102 | if (self.isFull()) { 103 | try self.grow(); 104 | } 105 | 106 | self.tail = self.wrapSub(self.tail, 1); 107 | const tail = self.tail; 108 | self.buf[tail] = item; 109 | } 110 | 111 | /// Pops and returns the last element of the deque. 112 | pub fn popBack(self: *Self) ?T { 113 | if (self.len() == 0) return null; 114 | 115 | self.head = self.wrapSub(self.head, 1); 116 | const head = self.head; 117 | const item = self.buf[head]; 118 | self.buf[head] = undefined; 119 | return item; 120 | } 121 | 122 | /// Pops and returns the first element of the deque. 123 | pub fn popFront(self: *Self) ?T { 124 | if (self.len() == 0) return null; 125 | 126 | const tail = self.tail; 127 | self.tail = self.wrapAdd(self.tail, 1); 128 | const item = self.buf[tail]; 129 | self.buf[tail] = undefined; 130 | return item; 131 | } 132 | 133 | /// Adds all the elements in the given slice to the back of the deque. 134 | pub fn appendSlice(self: *Self, items: []const T) Allocator.Error!void { 135 | for (items) |item| { 136 | try self.pushBack(item); 137 | } 138 | } 139 | 140 | /// Adds all the elements in the given slice to the front of the deque. 141 | pub fn prependSlice(self: *Self, items: []const T) Allocator.Error!void { 142 | if (items.len == 0) return; 143 | 144 | var i: usize = items.len - 1; 145 | 146 | while (true) : (i -= 1) { 147 | const item = items[i]; 148 | try self.pushFront(item); 149 | if (i == 0) break; 150 | } 151 | } 152 | 153 | /// Returns an iterator over the deque. 154 | /// Modifying the deque may invalidate this iterator. 155 | pub fn iterator(self: Self) Iterator { 156 | return .{ 157 | .head = self.head, 158 | .tail = self.tail, 159 | .ring = self.buf, 160 | }; 161 | } 162 | 163 | pub fn format(self: *const Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 164 | try writer.writeAll("Deque("); 165 | try std.fmt.format(writer, "{}", .{T}); 166 | try writer.writeAll(") { .buf = ["); 167 | 168 | var it = self.iterator(); 169 | if (it.next()) |val| try writer.print("{any}", .{val}); 170 | while (it.next()) |val| try writer.print(", {any}", .{val}); 171 | 172 | try writer.writeAll("], .head = "); 173 | try std.fmt.format(writer, "{}", .{self.head}); 174 | try writer.writeAll(", .tail = "); 175 | try std.fmt.format(writer, "{}", .{self.tail}); 176 | try writer.writeAll(", .len = "); 177 | try std.fmt.format(writer, "{}", .{self.len()}); 178 | try writer.writeAll(" }"); 179 | } 180 | 181 | pub const Iterator = struct { 182 | head: usize, 183 | tail: usize, 184 | ring: []T, 185 | 186 | pub fn next(it: *Iterator) ?*T { 187 | if (it.head == it.tail) return null; 188 | 189 | const tail = it.tail; 190 | it.tail = wrapIndex(it.tail +% 1, it.ring.len); 191 | return &it.ring[tail]; 192 | } 193 | 194 | pub fn nextBack(it: *Iterator) ?*T { 195 | if (it.head == it.tail) return null; 196 | 197 | it.head = wrapIndex(it.head -% 1, it.ring.len); 198 | return &it.ring[it.head]; 199 | } 200 | }; 201 | 202 | /// Returns `true` if the buffer is at full capacity. 203 | fn isFull(self: Self) bool { 204 | return self.cap() - self.len() == 1; 205 | } 206 | 207 | fn grow(self: *Self) Allocator.Error!void { 208 | assert(self.isFull()); 209 | const old_cap = self.cap(); 210 | 211 | // Reserve additional space to accomodate more items 212 | self.buf = try self.allocator.realloc(self.buf, old_cap * 2); 213 | 214 | // Update `tail` and `head` pointers accordingly 215 | self.handleCapacityIncrease(old_cap); 216 | 217 | assert(self.cap() >= old_cap * 2); 218 | assert(!self.isFull()); 219 | } 220 | 221 | /// Updates `tail` and `head` values to handle the fact that we just reallocated the internal buffer. 222 | fn handleCapacityIncrease(self: *Self, old_capacity: usize) void { 223 | const new_capacity = self.cap(); 224 | 225 | // Move the shortest contiguous section of the ring buffer. 226 | // There are three cases to consider: 227 | // 228 | // (A) No need to update 229 | // T H 230 | // before: [o o o o o o o . ] 231 | // 232 | // after : [o o o o o o o . . . . . . . . . ] 233 | // T H 234 | // 235 | // 236 | // (B) [..H] needs to be moved 237 | // H T 238 | // before: [o o . o o o o o ] 239 | // --- 240 | // |_______________. 241 | // | 242 | // v 243 | // --- 244 | // after : [. . . o o o o o o o . . . . . . ] 245 | // T H 246 | // 247 | // 248 | // (C) [T..old_capacity] needs to be moved 249 | // H T 250 | // before: [o o o o o . o o ] 251 | // --- 252 | // |_______________. 253 | // | 254 | // v 255 | // --- 256 | // after : [o o o o o . . . . . . . . . o o ] 257 | // H T 258 | 259 | if (self.tail <= self.head) { 260 | // (A), Nop 261 | } else if (self.head < old_capacity - self.tail) { 262 | // (B) 263 | self.copyNonOverlapping(old_capacity, 0, self.head); 264 | self.head += old_capacity; 265 | assert(self.head > self.tail); 266 | } else { 267 | // (C) 268 | const new_tail = new_capacity - (old_capacity - self.tail); 269 | self.copyNonOverlapping(new_tail, self.tail, old_capacity - self.tail); 270 | self.tail = new_tail; 271 | assert(self.head < self.tail); 272 | } 273 | assert(self.head < self.cap()); 274 | assert(self.tail < self.cap()); 275 | } 276 | 277 | fn copyNonOverlapping(self: *Self, dest: usize, src: usize, length: usize) void { 278 | assert(dest + length <= self.cap()); 279 | assert(src + length <= self.cap()); 280 | @memcpy(self.buf[dest .. dest + length], self.buf[src .. src + length]); 281 | } 282 | 283 | fn wrapAdd(self: Self, idx: usize, addend: usize) usize { 284 | return wrapIndex(idx +% addend, self.cap()); 285 | } 286 | 287 | fn wrapSub(self: Self, idx: usize, subtrahend: usize) usize { 288 | return wrapIndex(idx -% subtrahend, self.cap()); 289 | } 290 | }; 291 | } 292 | 293 | fn count(tail: usize, head: usize, size: usize) usize { 294 | assert(math.isPowerOfTwo(size)); 295 | return (head -% tail) & (size - 1); 296 | } 297 | 298 | fn wrapIndex(index: usize, size: usize) usize { 299 | assert(math.isPowerOfTwo(size)); 300 | return index & (size - 1); 301 | } 302 | 303 | test "Deque works" { 304 | const testing = std.testing; 305 | 306 | var deque = try Deque(usize).init(testing.allocator); 307 | defer deque.deinit(); 308 | 309 | // empty deque 310 | try testing.expectEqual(@as(usize, 0), deque.len()); 311 | try testing.expect(deque.get(0) == null); 312 | try testing.expect(deque.front() == null); 313 | try testing.expect(deque.back() == null); 314 | try testing.expect(deque.popBack() == null); 315 | try testing.expect(deque.popFront() == null); 316 | 317 | // pushBack 318 | try deque.pushBack(101); 319 | try testing.expectEqual(@as(usize, 1), deque.len()); 320 | try testing.expectEqual(@as(usize, 101), deque.get(0).?.*); 321 | try testing.expectEqual(@as(usize, 101), deque.front().?.*); 322 | try testing.expectEqual(@as(usize, 101), deque.back().?.*); 323 | 324 | // pushFront 325 | try deque.pushFront(100); 326 | try testing.expectEqual(@as(usize, 2), deque.len()); 327 | try testing.expectEqual(@as(usize, 100), deque.get(0).?.*); 328 | try testing.expectEqual(@as(usize, 100), deque.front().?.*); 329 | try testing.expectEqual(@as(usize, 101), deque.get(1).?.*); 330 | try testing.expectEqual(@as(usize, 101), deque.back().?.*); 331 | 332 | // more items 333 | { 334 | var i: usize = 99; 335 | while (true) : (i -= 1) { 336 | try deque.pushFront(i); 337 | if (i == 0) break; 338 | } 339 | } 340 | { 341 | var i: usize = 102; 342 | while (i < 200) : (i += 1) { 343 | try deque.pushBack(i); 344 | } 345 | } 346 | 347 | try testing.expectEqual(@as(usize, 200), deque.len()); 348 | { 349 | var i: usize = 0; 350 | while (i < deque.len()) : (i += 1) { 351 | try testing.expectEqual(i, deque.get(i).?.*); 352 | } 353 | } 354 | { 355 | var i: usize = 0; 356 | var it = deque.iterator(); 357 | while (it.next()) |val| : (i += 1) { 358 | try testing.expectEqual(i, val.*); 359 | } 360 | try testing.expectEqual(@as(usize, 200), i); 361 | } 362 | } 363 | 364 | test "initCapacity with too large capacity" { 365 | const testing = std.testing; 366 | 367 | var deque = try Deque(i32).initCapacity(testing.allocator, math.maxInt(usize)); 368 | defer deque.deinit(); 369 | 370 | // The specified capacity `math.maxInt(usize)` was too large. 371 | // Internally this is just ignored, and the default capacity is used instead. 372 | try testing.expectEqual(@as(usize, 8), deque.buf.len); 373 | } 374 | 375 | test "appendSlice and prependSlice" { 376 | const testing = std.testing; 377 | 378 | var deque = try Deque(usize).init(testing.allocator); 379 | defer deque.deinit(); 380 | 381 | try deque.prependSlice(&[_]usize{ 1, 2, 3, 4, 5, 6 }); 382 | try deque.appendSlice(&[_]usize{ 7, 8, 9 }); 383 | try deque.prependSlice(&[_]usize{0}); 384 | try deque.appendSlice(&[_]usize{ 10, 11, 12, 13, 14 }); 385 | 386 | { 387 | var i: usize = 0; 388 | while (i <= 14) : (i += 1) { 389 | try testing.expectEqual(i, deque.get(i).?.*); 390 | } 391 | } 392 | } 393 | 394 | test "format" { 395 | const testing = std.testing; 396 | 397 | var deque = try Deque(usize).init(testing.allocator); 398 | defer deque.deinit(); 399 | 400 | try deque.pushBack(69); 401 | try deque.pushBack(420); 402 | 403 | std.debug.print("{}\n", .{deque}); 404 | } 405 | 406 | test "nextBack" { 407 | const testing = std.testing; 408 | 409 | var deque = try Deque(usize).init(testing.allocator); 410 | defer deque.deinit(); 411 | 412 | try deque.appendSlice(&[_]usize{ 5, 4, 3, 2, 1, 0 }); 413 | 414 | { 415 | var i: usize = 0; 416 | var it = deque.iterator(); 417 | while (it.nextBack()) |val| : (i += 1) { 418 | try testing.expectEqual(i, val.*); 419 | } 420 | } 421 | } 422 | 423 | test "code sample in README" { 424 | var deque = try Deque(usize).init(std.testing.allocator); 425 | defer deque.deinit(); 426 | 427 | try deque.pushBack(1); 428 | try deque.pushBack(2); 429 | try deque.pushFront(0); 430 | 431 | std.debug.assert(deque.get(0).?.* == @as(usize, 0)); 432 | std.debug.assert(deque.get(1).?.* == @as(usize, 1)); 433 | std.debug.assert(deque.get(2).?.* == @as(usize, 2)); 434 | std.debug.assert(deque.get(3) == null); 435 | 436 | var it = deque.iterator(); 437 | var sum: usize = 0; 438 | while (it.next()) |val| { 439 | sum += val.*; 440 | } 441 | std.debug.assert(sum == 3); 442 | 443 | std.debug.assert(deque.popFront().? == @as(usize, 0)); 444 | std.debug.assert(deque.popBack().? == @as(usize, 2)); 445 | } 446 | --------------------------------------------------------------------------------