diff --git a/lib/std/SinglyLinkedList.zig b/lib/std/SinglyLinkedList.zig index d118ce0395a5..61c01573f50e 100644 --- a/lib/std/SinglyLinkedList.zig +++ b/lib/std/SinglyLinkedList.zig @@ -164,3 +164,130 @@ test "basics" { try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?.next.?)).data == 2); try testing.expect(list.first.?.next.?.next == null); } + +/// implements a "simple" intrusive singly linked list with a "data" field alongside +/// "node" field. This hides @fieldParentPtr complexity and adds type safety for simple +/// cases. +/// +/// note that the signatures on the member functions of the generated datastructure take +/// pointers to the payload, not the node. +pub fn Simple(T: type) type { + return struct { + const SimpleLinkedList = @This(); + wrapped: SinglyLinkedList = .{}, + + pub const Payload = struct { + data: T, + node: Node = .{}, + + pub fn next(payload: *Payload) ?*Payload { + return @fieldParentPtr("node", payload.node.next orelse return null); + } + + pub fn insertAfter(payload: *Payload, new_payload: *Payload) void { + payload.node.insertAfter(&new_payload.node); + } + }; + + pub fn prepend(list: *SimpleLinkedList, new_payload: *Payload) void { + list.wrapped.prepend(&new_payload.node); + } + + pub fn remove(list: *SimpleLinkedList, payload: *Payload) void { + list.wrapped.remove(&payload.node); + } + + /// Remove and return the first node in the list. + pub fn popFirst(list: *SimpleLinkedList) ?*Payload { + const poppednode = (list.wrapped.popFirst()) orelse return null; + return @fieldParentPtr("node", poppednode); + } + + /// Given a Simple list, returns the payload at position . + /// If the list does not have that many elements, returns `null`. + /// + /// This is a linear search through the list, consider avoiding this + /// operation, except for index == 0 + pub fn at(list: *SimpleLinkedList, index: usize) ?*Payload { + var thisnode = list.wrapped.first orelse return null; + var ctr: usize = index; + while (ctr > 0) : (ctr -= 1) { + thisnode = thisnode.next orelse return null; + } + return @fieldParentPtr("node", thisnode); + } + + // Iterate over all nodes, returning the count. + /// + /// This operation is O(N). Consider tracking the length separately rather than + /// computing it. + pub fn len(list: SimpleLinkedList) usize { + return list.wrapped.len(); + } + }; +} + +test "Simple singly linked list" { + const SimpleList = Simple(u32); + const L = SimpleList.Payload; + + var list: SimpleList = .{}; + + try testing.expect(list.len() == 0); + + var one: L = .{ .data = 1 }; + var two: L = .{ .data = 2 }; + var three: L = .{ .data = 3 }; + var four: L = .{ .data = 4 }; + var five: L = .{ .data = 5 }; + + try testing.expect(list.at(0) == null); + + list.prepend(&two); // {2} + two.node.insertAfter(&five.node); // {2, 5} + list.prepend(&one); // {1, 2, 5} + two.insertAfter(&three); // {1, 2, 3, 5} + three.node.insertAfter(&four.node); // {1, 2, 3, 4, 5} + + try testing.expect(list.len() == 5); + + try testing.expect(list.at(0).?.data == 1); + try testing.expect(list.at(3).?.data == 4); + try testing.expect(list.at(7) == null); + + try testing.expect(one.next().?.data == 2); + try testing.expect(two.next().?.data == 3); + try testing.expect(three.next().?.data == 4); + try testing.expect(four.next().?.data == 5); + try testing.expect(five.next() == null); + + // Traverse forwards. + { + var it = list.at(0); + var index: u32 = 1; + while (it) |payload| : (it = payload.next()) { + try testing.expect(payload.data == index); + index += 1; + } + } + + _ = list.popFirst(); // {2, 3, 4, 5} + _ = list.remove(&five); // {2, 3, 4} + _ = two.node.removeNext(); // {2, 4} + + try testing.expect(@as(*L, @fieldParentPtr("node", list.wrapped.first.?)).data == 2); + try testing.expect(list.at(0).?.data == 2); + try testing.expect(@as(*L, @fieldParentPtr("node", list.wrapped.first.?.next.?)).data == 4); + try testing.expect(list.at(1).?.data == 4); + + try testing.expect(list.wrapped.first.?.next.?.next == null); + + SinglyLinkedList.Node.reverse(&list.wrapped.first); + + try testing.expect(@as(*L, @fieldParentPtr("node", list.wrapped.first.?)).data == 4); + try testing.expect(list.at(0).?.data == 4); + try testing.expect(@as(*L, @fieldParentPtr("node", list.wrapped.first.?.next.?)).data == 2); + try testing.expect(list.at(1).?.data == 2); + try testing.expect(list.wrapped.first.?.next.?.next == null); + try testing.expect(list.at(2) == null); +} diff --git a/lib/std/std.zig b/lib/std/std.zig index 94851657a680..31e52ab84f49 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -34,6 +34,7 @@ pub const RingBuffer = @import("RingBuffer.zig"); pub const SegmentedList = @import("segmented_list.zig").SegmentedList; pub const SemanticVersion = @import("SemanticVersion.zig"); pub const SinglyLinkedList = @import("SinglyLinkedList.zig"); +pub const SimpleSinglyLinkedList = SinglyLinkedList.Simple; pub const StaticBitSet = bit_set.StaticBitSet; pub const StringHashMap = hash_map.StringHashMap; pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged;