From 2b032cfddd06c791839fb46cd2db95e08e02595e Mon Sep 17 00:00:00 2001 From: Isaac Yonemoto Date: Wed, 16 Apr 2025 13:50:24 -0500 Subject: [PATCH 1/5] adds a generic singly linked list --- lib/std/SinglyLinkedList.zig | 119 +++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/lib/std/SinglyLinkedList.zig b/lib/std/SinglyLinkedList.zig index d118ce0395a5..6713c6f459ad 100644 --- a/lib/std/SinglyLinkedList.zig +++ b/lib/std/SinglyLinkedList.zig @@ -164,3 +164,122 @@ 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{ + first: ?*Node = null, + + pub const Payload = struct { + data: T, + node: Node = .{}, + + pub fn next(payload: *@This()) ?*Payload { + return @fieldParentPtr("node", payload.node.next orelse return null); + } + }; + + pub fn prepend(list: *@This(), new_payload: *Payload) void { + new_payload.node.next = list.first; + list.first = new_payload.node; + } + + pub fn remove(list: *SinglyLinkedList, payload: *Payload) void { + if (list.first == payload.node) { + list.first = payload.node.next; + } else { + var current_elm = list.first.?; + while (current_elm.next != payload.node) { + current_elm = current_elm.next.?; + } + current_elm.next = payload.node.next; + } + } + + /// Remove and return the first node in the list. + pub fn popFirst(list: *SinglyLinkedList) ?*Payload { + const first = list.first orelse return null; + list.first = first.next; + return @fieldParentPtr("node", first); + } + + /// 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: *@This(), index: usize) ?*Payload { + var thisnode = list.first orelse return null; + while (index > 0) { + thisnode = thisnode.?.next orelse return null; + } + return @fieldParentPtr("node", thisnode); + } + }; +} + +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.node.insertAfter(&three.node); // {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.first; + var index: u32 = 1; + while (it) |node| : (it = node.next) { + const l: *L = @fieldParentPtr("node", node); + try testing.expect(l.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.first.?)).data == 2); + try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?.next.?)).data == 4); + try testing.expect(list.first.?.next.?.next == null); + + SinglyLinkedList.Node.reverse(&list.first); + + try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?)).data == 4); + try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?.next.?)).data == 2); + try testing.expect(list.first.?.next.?.next == null); +} From 5a250ff256199982c8e302b5aca8241368a30ca6 Mon Sep 17 00:00:00 2001 From: Isaac Yonemoto Date: Wed, 16 Apr 2025 20:45:44 -0500 Subject: [PATCH 2/5] tested for correctness --- lib/std/SinglyLinkedList.zig | 48 ++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/lib/std/SinglyLinkedList.zig b/lib/std/SinglyLinkedList.zig index 6713c6f459ad..86806c10d043 100644 --- a/lib/std/SinglyLinkedList.zig +++ b/lib/std/SinglyLinkedList.zig @@ -165,15 +165,14 @@ test "basics" { try testing.expect(list.first.?.next.?.next == null); } - -/// implements a "simple" intrusive singly linked list with a "data" field alongside +/// 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{ + return struct { first: ?*Node = null, pub const Payload = struct { @@ -183,19 +182,23 @@ pub fn Simple(T: type) type { pub fn next(payload: *@This()) ?*Payload { return @fieldParentPtr("node", payload.node.next orelse return null); } + + pub fn insertAfter(payload: *@This(), new_payload: *Payload) void { + payload.node.insertAfter(&new_payload.node); + } }; pub fn prepend(list: *@This(), new_payload: *Payload) void { new_payload.node.next = list.first; - list.first = new_payload.node; + list.first = &new_payload.node; } - pub fn remove(list: *SinglyLinkedList, payload: *Payload) void { - if (list.first == payload.node) { + pub fn remove(list: *@This(), payload: *Payload) void { + if (list.first == &payload.node) { list.first = payload.node.next; } else { var current_elm = list.first.?; - while (current_elm.next != payload.node) { + while (current_elm.next != &payload.node) { current_elm = current_elm.next.?; } current_elm.next = payload.node.next; @@ -203,7 +206,7 @@ pub fn Simple(T: type) type { } /// Remove and return the first node in the list. - pub fn popFirst(list: *SinglyLinkedList) ?*Payload { + pub fn popFirst(list: *@This()) ?*Payload { const first = list.first orelse return null; list.first = first.next; return @fieldParentPtr("node", first); @@ -211,22 +214,35 @@ pub fn Simple(T: type) type { /// 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 + /// + /// This is a linear search through the list, consider avoiding this /// operation, except for index == 0 pub fn at(list: *@This(), index: usize) ?*Payload { var thisnode = list.first orelse return null; - while (index > 0) { - thisnode = thisnode.?.next 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: @This()) usize { + if (list.first) |n| { + return 1 + n.countChildren(); + } else { + return 0; } - return @fieldParentPtr("node", thisnode); } }; } test "Simple singly linked list" { const SimpleList = Simple(u32); - const L = SimpleList.Payload(); + const L = SimpleList.Payload; var list: SimpleList = .{}; @@ -243,7 +259,7 @@ test "Simple singly linked list" { list.prepend(&two); // {2} two.node.insertAfter(&five.node); // {2, 5} list.prepend(&one); // {1, 2, 5} - two.node.insertAfter(&three.node); // {1, 2, 3, 5} + two.insertAfter(&three); // {1, 2, 3, 5} three.node.insertAfter(&four.node); // {1, 2, 3, 4, 5} try testing.expect(list.len() == 5); From 8f2df69fe97a04b10f0c25acdeb137c4d808686d Mon Sep 17 00:00:00 2001 From: Isaac Yonemoto Date: Wed, 16 Apr 2025 20:48:28 -0500 Subject: [PATCH 3/5] adds legibility --- lib/std/SinglyLinkedList.zig | 15 ++++++++------- lib/std/std.zig | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/std/SinglyLinkedList.zig b/lib/std/SinglyLinkedList.zig index 86806c10d043..bfd21273c35d 100644 --- a/lib/std/SinglyLinkedList.zig +++ b/lib/std/SinglyLinkedList.zig @@ -172,6 +172,7 @@ test "basics" { /// 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 { + const SimpleLinkedList = @This(); return struct { first: ?*Node = null, @@ -179,21 +180,21 @@ pub fn Simple(T: type) type { data: T, node: Node = .{}, - pub fn next(payload: *@This()) ?*Payload { + pub fn next(payload: *SimpleLinkedList) ?*Payload { return @fieldParentPtr("node", payload.node.next orelse return null); } - pub fn insertAfter(payload: *@This(), new_payload: *Payload) void { + pub fn insertAfter(payload: *SimpleLinkedList, new_payload: *Payload) void { payload.node.insertAfter(&new_payload.node); } }; - pub fn prepend(list: *@This(), new_payload: *Payload) void { + pub fn prepend(list: *SimpleLinkedList, new_payload: *Payload) void { new_payload.node.next = list.first; list.first = &new_payload.node; } - pub fn remove(list: *@This(), payload: *Payload) void { + pub fn remove(list: *SimpleLinkedList, payload: *Payload) void { if (list.first == &payload.node) { list.first = payload.node.next; } else { @@ -206,7 +207,7 @@ pub fn Simple(T: type) type { } /// Remove and return the first node in the list. - pub fn popFirst(list: *@This()) ?*Payload { + pub fn popFirst(list: *SimpleLinkedList) ?*Payload { const first = list.first orelse return null; list.first = first.next; return @fieldParentPtr("node", first); @@ -217,7 +218,7 @@ pub fn Simple(T: type) type { /// /// This is a linear search through the list, consider avoiding this /// operation, except for index == 0 - pub fn at(list: *@This(), index: usize) ?*Payload { + pub fn at(list: *SimpleLinkedList, index: usize) ?*Payload { var thisnode = list.first orelse return null; var ctr: usize = index; while (ctr > 0) : (ctr -= 1) { @@ -230,7 +231,7 @@ pub fn Simple(T: type) type { /// /// This operation is O(N). Consider tracking the length separately rather than /// computing it. - pub fn len(list: @This()) usize { + pub fn len(list: SimpleLinkedList) usize { if (list.first) |n| { return 1 + n.countChildren(); } else { 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; From 3413b1a2c4b0cd9f6effe2043de608d87aca844b Mon Sep 17 00:00:00 2001 From: Isaac Yonemoto Date: Wed, 16 Apr 2025 20:50:19 -0500 Subject: [PATCH 4/5] adds legibility changes --- lib/std/SinglyLinkedList.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/SinglyLinkedList.zig b/lib/std/SinglyLinkedList.zig index bfd21273c35d..f0655ea11572 100644 --- a/lib/std/SinglyLinkedList.zig +++ b/lib/std/SinglyLinkedList.zig @@ -172,19 +172,19 @@ test "basics" { /// 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 { - const SimpleLinkedList = @This(); return struct { + const SimpleLinkedList = @This(); first: ?*Node = null, pub const Payload = struct { data: T, node: Node = .{}, - pub fn next(payload: *SimpleLinkedList) ?*Payload { + pub fn next(payload: *Payload) ?*Payload { return @fieldParentPtr("node", payload.node.next orelse return null); } - pub fn insertAfter(payload: *SimpleLinkedList, new_payload: *Payload) void { + pub fn insertAfter(payload: *Payload, new_payload: *Payload) void { payload.node.insertAfter(&new_payload.node); } }; From 79f7da3e1daa2708649d2f9f703014d0bb30a3cd Mon Sep 17 00:00:00 2001 From: Isaac Yonemoto Date: Wed, 30 Apr 2025 08:41:44 -0400 Subject: [PATCH 5/5] fixes to wrap instead of reimplement --- lib/std/SinglyLinkedList.zig | 55 +++++++++++++++--------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/lib/std/SinglyLinkedList.zig b/lib/std/SinglyLinkedList.zig index f0655ea11572..61c01573f50e 100644 --- a/lib/std/SinglyLinkedList.zig +++ b/lib/std/SinglyLinkedList.zig @@ -174,7 +174,7 @@ test "basics" { pub fn Simple(T: type) type { return struct { const SimpleLinkedList = @This(); - first: ?*Node = null, + wrapped: SinglyLinkedList = .{}, pub const Payload = struct { data: T, @@ -190,27 +190,17 @@ pub fn Simple(T: type) type { }; pub fn prepend(list: *SimpleLinkedList, new_payload: *Payload) void { - new_payload.node.next = list.first; - list.first = &new_payload.node; + list.wrapped.prepend(&new_payload.node); } pub fn remove(list: *SimpleLinkedList, payload: *Payload) void { - if (list.first == &payload.node) { - list.first = payload.node.next; - } else { - var current_elm = list.first.?; - while (current_elm.next != &payload.node) { - current_elm = current_elm.next.?; - } - current_elm.next = payload.node.next; - } + list.wrapped.remove(&payload.node); } /// Remove and return the first node in the list. pub fn popFirst(list: *SimpleLinkedList) ?*Payload { - const first = list.first orelse return null; - list.first = first.next; - return @fieldParentPtr("node", first); + const poppednode = (list.wrapped.popFirst()) orelse return null; + return @fieldParentPtr("node", poppednode); } /// Given a Simple list, returns the payload at position . @@ -219,7 +209,7 @@ pub fn Simple(T: type) type { /// 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.first orelse return null; + var thisnode = list.wrapped.first orelse return null; var ctr: usize = index; while (ctr > 0) : (ctr -= 1) { thisnode = thisnode.next orelse return null; @@ -232,11 +222,7 @@ pub fn Simple(T: type) type { /// This operation is O(N). Consider tracking the length separately rather than /// computing it. pub fn len(list: SimpleLinkedList) usize { - if (list.first) |n| { - return 1 + n.countChildren(); - } else { - return 0; - } + return list.wrapped.len(); } }; } @@ -277,11 +263,10 @@ test "Simple singly linked list" { // Traverse forwards. { - var it = list.first; + var it = list.at(0); var index: u32 = 1; - while (it) |node| : (it = node.next) { - const l: *L = @fieldParentPtr("node", node); - try testing.expect(l.data == index); + while (it) |payload| : (it = payload.next()) { + try testing.expect(payload.data == index); index += 1; } } @@ -290,13 +275,19 @@ test "Simple singly linked list" { _ = list.remove(&five); // {2, 3, 4} _ = two.node.removeNext(); // {2, 4} - try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?)).data == 2); - try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?.next.?)).data == 4); - try testing.expect(list.first.?.next.?.next == null); + 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); - SinglyLinkedList.Node.reverse(&list.first); + try testing.expect(list.wrapped.first.?.next.?.next == null); - try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?)).data == 4); - try testing.expect(@as(*L, @fieldParentPtr("node", list.first.?.next.?)).data == 2); - try testing.expect(list.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); }