const std = @import("index.zig"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; // Imagine that `fn at(self: &Self, index: usize) &T` is a customer asking for a box // from a warehouse, based on a flat array, boxes ordered from 0 to N - 1. // But the warehouse actually stores boxes in shelves of increasing powers of 2 sizes. // So when the customer requests a box index, we have to translate it to shelf index // and box index within that shelf. Illustration: // // customer indexes: // shelf 0: 0 // shelf 1: 1 2 // shelf 2: 3 4 5 6 // shelf 3: 7 8 9 10 11 12 13 14 // shelf 4: 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // shelf 5: 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 // ... // // warehouse indexes: // shelf 0: 0 // shelf 1: 0 1 // shelf 2: 0 1 2 3 // shelf 3: 0 1 2 3 4 5 6 7 // shelf 4: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // shelf 5: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // ... // // With this arrangement, here are the equations to get the shelf index and // box index based on customer box index: // // shelf_index = floor(log2(customer_index + 1)) // shelf_count = ceil(log2(box_count + 1)) // box_index = customer_index + 1 - 2 ** shelf // shelf_size = 2 ** shelf_index // // Now we complicate it a little bit further by adding a preallocated shelf, which must be // a power of 2: // prealloc=4 // // customer indexes: // prealloc: 0 1 2 3 // shelf 0: 4 5 6 7 8 9 10 11 // shelf 1: 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // shelf 2: 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // ... // // warehouse indexes: // prealloc: 0 1 2 3 // shelf 0: 0 1 2 3 4 5 6 7 // shelf 1: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // shelf 2: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // ... // // Now the equations are: // // shelf_index = floor(log2(customer_index + prealloc)) - log2(prealloc) - 1 // shelf_count = ceil(log2(box_count + prealloc)) - log2(prealloc) - 1 // box_index = customer_index + prealloc - 2 ** (log2(prealloc) + 1 + shelf) // shelf_size = prealloc * 2 ** (shelf_index + 1) /// This is a stack data structure where pointers to indexes have the same lifetime as the data structure /// itself, unlike ArrayList where push() invalidates all existing element pointers. /// The tradeoff is that elements are not guaranteed to be contiguous. For that, use ArrayList. /// Note however that most elements are contiguous, making this data structure cache-friendly. /// /// Because it never has to copy elements from an old location to a new location, it does not require /// its elements to be copyable, and it avoids wasting memory when backed by an ArenaAllocator. /// Note that the push() and pop() convenience methods perform a copy, but you can instead use /// addOne(), at(), setCapacity(), and shrinkCapacity() to avoid copying items. /// /// This data structure has O(1) push and O(1) pop. /// /// It supports preallocated elements, making it especially well suited when the expected maximum /// size is small. `prealloc_item_count` must be 0, or a power of 2. pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type { return struct { const Self = this; const prealloc_exp = blk: { // we don't use the prealloc_exp constant when prealloc_item_count is 0. assert(prealloc_item_count != 0); const value = std.math.log2_int(usize, prealloc_item_count); assert((1 << value) == prealloc_item_count); // prealloc_item_count must be a power of 2 break :blk @typeOf(1)(value); }; const ShelfIndex = std.math.Log2Int(usize); allocator: &Allocator, len: usize, prealloc_segment: [prealloc_item_count]T, dynamic_segments: []&T, /// Deinitialize with `deinit` pub fn init(allocator: &Allocator) Self { return Self { .allocator = allocator, .len = 0, .prealloc_segment = undefined, .dynamic_segments = []&T{}, }; } pub fn deinit(self: &Self) void { self.freeShelves(ShelfIndex(self.dynamic_segments.len), 0); self.allocator.free(self.dynamic_segments); *self = undefined; } pub fn at(self: &Self, i: usize) &T { assert(i < self.len); return self.uncheckedAt(i); } pub fn count(self: &const Self) usize { return self.len; } pub fn push(self: &Self, item: &const T) !void { const new_item_ptr = try self.addOne(); *new_item_ptr = *item; } pub fn pushMany(self: &Self, items: []const T) !void { for (items) |item| { try self.push(item); } } pub fn pop(self: &Self) ?T { if (self.len == 0) return null; const index = self.len - 1; const result = *self.uncheckedAt(index); self.len = index; return result; } pub fn addOne(self: &Self) !&T { const new_length = self.len + 1; try self.setCapacity(new_length); const result = self.uncheckedAt(self.len); self.len = new_length; return result; } pub fn setCapacity(self: &Self, new_capacity: usize) !void { if (new_capacity <= prealloc_item_count) { const len = ShelfIndex(self.dynamic_segments.len); if (len == 0) return; self.freeShelves(len, 0); self.allocator.free(self.dynamic_segments); self.dynamic_segments = []&T{}; return; } const new_cap_shelf_count = shelfCount(new_capacity); const old_shelf_count = ShelfIndex(self.dynamic_segments.len); if (new_cap_shelf_count > old_shelf_count) { self.dynamic_segments = try self.allocator.realloc(&T, self.dynamic_segments, new_cap_shelf_count); var i = old_shelf_count; errdefer { self.freeShelves(i, old_shelf_count); self.dynamic_segments = self.allocator.shrink(&T, self.dynamic_segments, old_shelf_count); } while (i < new_cap_shelf_count) : (i += 1) { self.dynamic_segments[i] = (try self.allocator.alloc(T, shelfSize(i))).ptr; } return; } if (new_cap_shelf_count == old_shelf_count) { return; } self.freeShelves(old_shelf_count, new_cap_shelf_count); self.dynamic_segments = self.allocator.shrink(&T, self.dynamic_segments, new_cap_shelf_count); } pub fn shrinkCapacity(self: &Self, new_capacity: usize) void { assert(new_capacity <= prealloc_item_count or shelfCount(new_capacity) <= self.dynamic_segments.len); self.setCapacity(new_capacity) catch unreachable; } pub fn uncheckedAt(self: &Self, index: usize) &T { if (index < prealloc_item_count) { return &self.prealloc_segment[index]; } const shelf_index = shelfIndex(index); const box_index = boxIndex(index, shelf_index); return &self.dynamic_segments[shelf_index][box_index]; } fn shelfCount(box_count: usize) ShelfIndex { if (prealloc_item_count == 0) { return std.math.log2_int_ceil(usize, box_count + 1); } return std.math.log2_int_ceil(usize, box_count + prealloc_item_count) - prealloc_exp - 1; } fn shelfSize(shelf_index: ShelfIndex) usize { if (prealloc_item_count == 0) { return usize(1) << shelf_index; } return usize(1) << (shelf_index + (prealloc_exp + 1)); } fn shelfIndex(list_index: usize) ShelfIndex { if (prealloc_item_count == 0) { return std.math.log2_int(usize, list_index + 1); } return std.math.log2_int(usize, list_index + prealloc_item_count) - prealloc_exp - 1; } fn boxIndex(list_index: usize, shelf_index: ShelfIndex) usize { if (prealloc_item_count == 0) { return (list_index + 1) - (usize(1) << shelf_index); } return list_index + prealloc_item_count - (usize(1) << ((prealloc_exp + 1) + shelf_index)); } fn freeShelves(self: &Self, from_count: ShelfIndex, to_count: ShelfIndex) void { var i = from_count; while (i != to_count) { i -= 1; self.allocator.free(self.dynamic_segments[i][0..shelfSize(i)]); } } }; } test "std.SegmentedList" { var da = std.heap.DirectAllocator.init(); defer da.deinit(); var a = &da.allocator; try testSegmentedList(0, a); try testSegmentedList(1, a); try testSegmentedList(2, a); try testSegmentedList(4, a); try testSegmentedList(8, a); try testSegmentedList(16, a); } fn testSegmentedList(comptime prealloc: usize, allocator: &Allocator) !void { var list = SegmentedList(i32, prealloc).init(allocator); defer list.deinit(); {var i: usize = 0; while (i < 100) : (i += 1) { try list.push(i32(i + 1)); assert(list.len == i + 1); }} {var i: usize = 0; while (i < 100) : (i += 1) { assert(*list.at(i) == i32(i + 1)); }} assert(??list.pop() == 100); assert(list.len == 99); try list.pushMany([]i32 { 1, 2, 3 }); assert(list.len == 102); assert(??list.pop() == 3); assert(??list.pop() == 2); assert(??list.pop() == 1); assert(list.len == 99); try list.pushMany([]const i32 {}); assert(list.len == 99); var i: i32 = 99; while (list.pop()) |item| : (i -= 1) { assert(item == i); list.shrinkCapacity(list.len); } }