authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-03 15:35:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-03 15:35:21-07:00
log3b77a845f9a5409d19a9e330b82eece8af4ac18f
treef19a742742aabef64c6ce3842b3a12b97de52161
parent1639fcea43549853f1fded32aa1d711d21771e1c

de-genericify DoublyLinkedList

by making it always intrusive, we make it more broadly useful API, and avoid binary bloat.

3 files changed, 275 insertions(+), 288 deletions(-)

lib/std/DoublyLinkedList.zig created+274
......@@ -0,0 +1,274 @@
1//! A doubly-linked list has a pair of pointers to both the head and
2//! tail of the list. List elements have pointers to both the previous
3//! and next elements in the sequence. The list can be traversed both
4//! forward and backward. Some operations that take linear O(n) time
5//! with a singly-linked list can be done without traversal in constant
6//! O(1) time with a doubly-linked list:
7//!
8//! * Removing an element.
9//! * Inserting a new element before an existing element.
10//! * Pushing or popping an element from the end of the list.
11
12const std = @import("std.zig");
13const debug = std.debug;
14const assert = debug.assert;
15const testing = std.testing;
16const DoublyLinkedList = @This();
17
18first: ?*Node = null,
19last: ?*Node = null,
20len: usize = 0,
21
22/// This struct contains only the prev and next pointers and not any data
23/// payload. The intended usage is to embed it intrusively into another data
24/// structure and access the data with `@fieldParentPtr`.
25pub const Node = struct {
26 prev: ?*Node = null,
27 next: ?*Node = null,
28};
29
30pub fn insertAfter(list: *DoublyLinkedList, existing_node: *Node, new_node: *Node) void {
31 new_node.prev = existing_node;
32 if (existing_node.next) |next_node| {
33 // Intermediate node.
34 new_node.next = next_node;
35 next_node.prev = new_node;
36 } else {
37 // Last element of the list.
38 new_node.next = null;
39 list.last = new_node;
40 }
41 existing_node.next = new_node;
42
43 list.len += 1;
44}
45
46pub fn insertBefore(list: *DoublyLinkedList, existing_node: *Node, new_node: *Node) void {
47 new_node.next = existing_node;
48 if (existing_node.prev) |prev_node| {
49 // Intermediate node.
50 new_node.prev = prev_node;
51 prev_node.next = new_node;
52 } else {
53 // First element of the list.
54 new_node.prev = null;
55 list.first = new_node;
56 }
57 existing_node.prev = new_node;
58
59 list.len += 1;
60}
61
62/// Concatenate list2 onto the end of list1, removing all entries from the former.
63///
64/// Arguments:
65/// list1: the list to concatenate onto
66/// list2: the list to be concatenated
67pub fn concatByMoving(list1: *DoublyLinkedList, list2: *DoublyLinkedList) void {
68 const l2_first = list2.first orelse return;
69 if (list1.last) |l1_last| {
70 l1_last.next = list2.first;
71 l2_first.prev = list1.last;
72 list1.len += list2.len;
73 } else {
74 // list1 was empty
75 list1.first = list2.first;
76 list1.len = list2.len;
77 }
78 list1.last = list2.last;
79 list2.first = null;
80 list2.last = null;
81 list2.len = 0;
82}
83
84/// Insert a new node at the end of the list.
85///
86/// Arguments:
87/// new_node: Pointer to the new node to insert.
88pub fn append(list: *DoublyLinkedList, new_node: *Node) void {
89 if (list.last) |last| {
90 // Insert after last.
91 list.insertAfter(last, new_node);
92 } else {
93 // Empty list.
94 list.prepend(new_node);
95 }
96}
97
98/// Insert a new node at the beginning of the list.
99///
100/// Arguments:
101/// new_node: Pointer to the new node to insert.
102pub fn prepend(list: *DoublyLinkedList, new_node: *Node) void {
103 if (list.first) |first| {
104 // Insert before first.
105 list.insertBefore(first, new_node);
106 } else {
107 // Empty list.
108 list.first = new_node;
109 list.last = new_node;
110 new_node.prev = null;
111 new_node.next = null;
112
113 list.len = 1;
114 }
115}
116
117/// Remove a node from the list.
118///
119/// Arguments:
120/// node: Pointer to the node to be removed.
121pub fn remove(list: *DoublyLinkedList, node: *Node) void {
122 if (node.prev) |prev_node| {
123 // Intermediate node.
124 prev_node.next = node.next;
125 } else {
126 // First element of the list.
127 list.first = node.next;
128 }
129
130 if (node.next) |next_node| {
131 // Intermediate node.
132 next_node.prev = node.prev;
133 } else {
134 // Last element of the list.
135 list.last = node.prev;
136 }
137
138 list.len -= 1;
139 assert(list.len == 0 or (list.first != null and list.last != null));
140}
141
142/// Remove and return the last node in the list.
143///
144/// Returns:
145/// A pointer to the last node in the list.
146pub fn pop(list: *DoublyLinkedList) ?*Node {
147 const last = list.last orelse return null;
148 list.remove(last);
149 return last;
150}
151
152/// Remove and return the first node in the list.
153///
154/// Returns:
155/// A pointer to the first node in the list.
156pub fn popFirst(list: *DoublyLinkedList) ?*Node {
157 const first = list.first orelse return null;
158 list.remove(first);
159 return first;
160}
161
162test "basic DoublyLinkedList test" {
163 const L = DoublyLinkedList(u32);
164 var list = L{};
165
166 var one = L.Node{ .data = 1 };
167 var two = L.Node{ .data = 2 };
168 var three = L.Node{ .data = 3 };
169 var four = L.Node{ .data = 4 };
170 var five = L.Node{ .data = 5 };
171
172 list.append(&two); // {2}
173 list.append(&five); // {2, 5}
174 list.prepend(&one); // {1, 2, 5}
175 list.insertBefore(&five, &four); // {1, 2, 4, 5}
176 list.insertAfter(&two, &three); // {1, 2, 3, 4, 5}
177
178 // Traverse forwards.
179 {
180 var it = list.first;
181 var index: u32 = 1;
182 while (it) |node| : (it = node.next) {
183 try testing.expect(node.data == index);
184 index += 1;
185 }
186 }
187
188 // Traverse backwards.
189 {
190 var it = list.last;
191 var index: u32 = 1;
192 while (it) |node| : (it = node.prev) {
193 try testing.expect(node.data == (6 - index));
194 index += 1;
195 }
196 }
197
198 _ = list.popFirst(); // {2, 3, 4, 5}
199 _ = list.pop(); // {2, 3, 4}
200 list.remove(&three); // {2, 4}
201
202 try testing.expect(list.first.?.data == 2);
203 try testing.expect(list.last.?.data == 4);
204 try testing.expect(list.len == 2);
205}
206
207test "DoublyLinkedList concatenation" {
208 const L = DoublyLinkedList(u32);
209 var list1 = L{};
210 var list2 = L{};
211
212 var one = L.Node{ .data = 1 };
213 var two = L.Node{ .data = 2 };
214 var three = L.Node{ .data = 3 };
215 var four = L.Node{ .data = 4 };
216 var five = L.Node{ .data = 5 };
217
218 list1.append(&one);
219 list1.append(&two);
220 list2.append(&three);
221 list2.append(&four);
222 list2.append(&five);
223
224 list1.concatByMoving(&list2);
225
226 try testing.expect(list1.last == &five);
227 try testing.expect(list1.len == 5);
228 try testing.expect(list2.first == null);
229 try testing.expect(list2.last == null);
230 try testing.expect(list2.len == 0);
231
232 // Traverse forwards.
233 {
234 var it = list1.first;
235 var index: u32 = 1;
236 while (it) |node| : (it = node.next) {
237 try testing.expect(node.data == index);
238 index += 1;
239 }
240 }
241
242 // Traverse backwards.
243 {
244 var it = list1.last;
245 var index: u32 = 1;
246 while (it) |node| : (it = node.prev) {
247 try testing.expect(node.data == (6 - index));
248 index += 1;
249 }
250 }
251
252 // Swap them back, this verifies that concatenating to an empty list works.
253 list2.concatByMoving(&list1);
254
255 // Traverse forwards.
256 {
257 var it = list2.first;
258 var index: u32 = 1;
259 while (it) |node| : (it = node.next) {
260 try testing.expect(node.data == index);
261 index += 1;
262 }
263 }
264
265 // Traverse backwards.
266 {
267 var it = list2.last;
268 var index: u32 = 1;
269 while (it) |node| : (it = node.prev) {
270 try testing.expect(node.data == (6 - index));
271 index += 1;
272 }
273 }
274}
lib/std/linked_list.zig deleted-287
......@@ -1,287 +0,0 @@
1const std = @import("std.zig");
2const debug = std.debug;
3const assert = debug.assert;
4const testing = std.testing;
5
6/// A doubly-linked list has a pair of pointers to both the head and
7/// tail of the list. List elements have pointers to both the previous
8/// and next elements in the sequence. The list can be traversed both
9/// forward and backward. Some operations that take linear O(n) time
10/// with a singly-linked list can be done without traversal in constant
11/// O(1) time with a doubly-linked list:
12///
13/// - Removing an element.
14/// - Inserting a new element before an existing element.
15/// - Pushing or popping an element from the end of the list.
16pub fn DoublyLinkedList(comptime T: type) type {
17 return struct {
18 const Self = @This();
19
20 /// Node inside the linked list wrapping the actual data.
21 pub const Node = struct {
22 prev: ?*Node = null,
23 next: ?*Node = null,
24 data: T,
25 };
26
27 first: ?*Node = null,
28 last: ?*Node = null,
29 len: usize = 0,
30
31 /// Insert a new node after an existing one.
32 ///
33 /// Arguments:
34 /// node: Pointer to a node in the list.
35 /// new_node: Pointer to the new node to insert.
36 pub fn insertAfter(list: *Self, node: *Node, new_node: *Node) void {
37 new_node.prev = node;
38 if (node.next) |next_node| {
39 // Intermediate node.
40 new_node.next = next_node;
41 next_node.prev = new_node;
42 } else {
43 // Last element of the list.
44 new_node.next = null;
45 list.last = new_node;
46 }
47 node.next = new_node;
48
49 list.len += 1;
50 }
51
52 /// Insert a new node before an existing one.
53 ///
54 /// Arguments:
55 /// node: Pointer to a node in the list.
56 /// new_node: Pointer to the new node to insert.
57 pub fn insertBefore(list: *Self, node: *Node, new_node: *Node) void {
58 new_node.next = node;
59 if (node.prev) |prev_node| {
60 // Intermediate node.
61 new_node.prev = prev_node;
62 prev_node.next = new_node;
63 } else {
64 // First element of the list.
65 new_node.prev = null;
66 list.first = new_node;
67 }
68 node.prev = new_node;
69
70 list.len += 1;
71 }
72
73 /// Concatenate list2 onto the end of list1, removing all entries from the former.
74 ///
75 /// Arguments:
76 /// list1: the list to concatenate onto
77 /// list2: the list to be concatenated
78 pub fn concatByMoving(list1: *Self, list2: *Self) void {
79 const l2_first = list2.first orelse return;
80 if (list1.last) |l1_last| {
81 l1_last.next = list2.first;
82 l2_first.prev = list1.last;
83 list1.len += list2.len;
84 } else {
85 // list1 was empty
86 list1.first = list2.first;
87 list1.len = list2.len;
88 }
89 list1.last = list2.last;
90 list2.first = null;
91 list2.last = null;
92 list2.len = 0;
93 }
94
95 /// Insert a new node at the end of the list.
96 ///
97 /// Arguments:
98 /// new_node: Pointer to the new node to insert.
99 pub fn append(list: *Self, new_node: *Node) void {
100 if (list.last) |last| {
101 // Insert after last.
102 list.insertAfter(last, new_node);
103 } else {
104 // Empty list.
105 list.prepend(new_node);
106 }
107 }
108
109 /// Insert a new node at the beginning of the list.
110 ///
111 /// Arguments:
112 /// new_node: Pointer to the new node to insert.
113 pub fn prepend(list: *Self, new_node: *Node) void {
114 if (list.first) |first| {
115 // Insert before first.
116 list.insertBefore(first, new_node);
117 } else {
118 // Empty list.
119 list.first = new_node;
120 list.last = new_node;
121 new_node.prev = null;
122 new_node.next = null;
123
124 list.len = 1;
125 }
126 }
127
128 /// Remove a node from the list.
129 ///
130 /// Arguments:
131 /// node: Pointer to the node to be removed.
132 pub fn remove(list: *Self, node: *Node) void {
133 if (node.prev) |prev_node| {
134 // Intermediate node.
135 prev_node.next = node.next;
136 } else {
137 // First element of the list.
138 list.first = node.next;
139 }
140
141 if (node.next) |next_node| {
142 // Intermediate node.
143 next_node.prev = node.prev;
144 } else {
145 // Last element of the list.
146 list.last = node.prev;
147 }
148
149 list.len -= 1;
150 assert(list.len == 0 or (list.first != null and list.last != null));
151 }
152
153 /// Remove and return the last node in the list.
154 ///
155 /// Returns:
156 /// A pointer to the last node in the list.
157 pub fn pop(list: *Self) ?*Node {
158 const last = list.last orelse return null;
159 list.remove(last);
160 return last;
161 }
162
163 /// Remove and return the first node in the list.
164 ///
165 /// Returns:
166 /// A pointer to the first node in the list.
167 pub fn popFirst(list: *Self) ?*Node {
168 const first = list.first orelse return null;
169 list.remove(first);
170 return first;
171 }
172 };
173}
174
175test "basic DoublyLinkedList test" {
176 const L = DoublyLinkedList(u32);
177 var list = L{};
178
179 var one = L.Node{ .data = 1 };
180 var two = L.Node{ .data = 2 };
181 var three = L.Node{ .data = 3 };
182 var four = L.Node{ .data = 4 };
183 var five = L.Node{ .data = 5 };
184
185 list.append(&two); // {2}
186 list.append(&five); // {2, 5}
187 list.prepend(&one); // {1, 2, 5}
188 list.insertBefore(&five, &four); // {1, 2, 4, 5}
189 list.insertAfter(&two, &three); // {1, 2, 3, 4, 5}
190
191 // Traverse forwards.
192 {
193 var it = list.first;
194 var index: u32 = 1;
195 while (it) |node| : (it = node.next) {
196 try testing.expect(node.data == index);
197 index += 1;
198 }
199 }
200
201 // Traverse backwards.
202 {
203 var it = list.last;
204 var index: u32 = 1;
205 while (it) |node| : (it = node.prev) {
206 try testing.expect(node.data == (6 - index));
207 index += 1;
208 }
209 }
210
211 _ = list.popFirst(); // {2, 3, 4, 5}
212 _ = list.pop(); // {2, 3, 4}
213 list.remove(&three); // {2, 4}
214
215 try testing.expect(list.first.?.data == 2);
216 try testing.expect(list.last.?.data == 4);
217 try testing.expect(list.len == 2);
218}
219
220test "DoublyLinkedList concatenation" {
221 const L = DoublyLinkedList(u32);
222 var list1 = L{};
223 var list2 = L{};
224
225 var one = L.Node{ .data = 1 };
226 var two = L.Node{ .data = 2 };
227 var three = L.Node{ .data = 3 };
228 var four = L.Node{ .data = 4 };
229 var five = L.Node{ .data = 5 };
230
231 list1.append(&one);
232 list1.append(&two);
233 list2.append(&three);
234 list2.append(&four);
235 list2.append(&five);
236
237 list1.concatByMoving(&list2);
238
239 try testing.expect(list1.last == &five);
240 try testing.expect(list1.len == 5);
241 try testing.expect(list2.first == null);
242 try testing.expect(list2.last == null);
243 try testing.expect(list2.len == 0);
244
245 // Traverse forwards.
246 {
247 var it = list1.first;
248 var index: u32 = 1;
249 while (it) |node| : (it = node.next) {
250 try testing.expect(node.data == index);
251 index += 1;
252 }
253 }
254
255 // Traverse backwards.
256 {
257 var it = list1.last;
258 var index: u32 = 1;
259 while (it) |node| : (it = node.prev) {
260 try testing.expect(node.data == (6 - index));
261 index += 1;
262 }
263 }
264
265 // Swap them back, this verifies that concatenating to an empty list works.
266 list2.concatByMoving(&list1);
267
268 // Traverse forwards.
269 {
270 var it = list2.first;
271 var index: u32 = 1;
272 while (it) |node| : (it = node.next) {
273 try testing.expect(node.data == index);
274 index += 1;
275 }
276 }
277
278 // Traverse backwards.
279 {
280 var it = list2.last;
281 var index: u32 = 1;
282 while (it) |node| : (it = node.prev) {
283 try testing.expect(node.data == (6 - index));
284 index += 1;
285 }
286 }
287}
lib/std/std.zig+1-1
......@@ -16,7 +16,7 @@ pub const BufMap = @import("buf_map.zig").BufMap;
1616pub const BufSet = @import("buf_set.zig").BufSet;
1717pub const StaticStringMap = static_string_map.StaticStringMap;
1818pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
19pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
19pub const DoublyLinkedList = @import("DoublyLinkedList.zig");
2020pub const DynLib = @import("dynamic_library.zig").DynLib;
2121pub const DynamicBitSet = bit_set.DynamicBitSet;
2222pub const DynamicBitSetUnmanaged = bit_set.DynamicBitSetUnmanaged;