Skip to content

Commit 8acedfd

Browse files
authored
Merge pull request #23459 from ziglang/linked-lists
de-genericify linked lists
2 parents 84c9cee + 810f70e commit 8acedfd

File tree

9 files changed

+553
-543
lines changed

9 files changed

+553
-543
lines changed

CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,6 @@ set(ZIG_STAGE2_SOURCES
444444
lib/std/json.zig
445445
lib/std/json/stringify.zig
446446
lib/std/leb128.zig
447-
lib/std/linked_list.zig
448447
lib/std/log.zig
449448
lib/std/macho.zig
450449
lib/std/math.zig

lib/std/DoublyLinkedList.zig

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

0 commit comments

Comments
 (0)