Skip to content

Commit e68f833

Browse files
Introducing a full implementation of a doubly linked list (#16)
* Introducing binary search tree implementation * Changed container name to match previous work * Introducing a complete doubly linked list implementation * Remove previous implemented stuff
1 parent 9351c5d commit e68f833

File tree

1 file changed

+226
-0
lines changed

1 file changed

+226
-0
lines changed
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
const std = @import("std");
2+
const print = std.debug.print;
3+
const testing = std.testing;
4+
5+
// Returns a doubly linked list instance.
6+
// Arguments:
7+
// T: the type of the info(i.e. i32, i16, u32, etc...)
8+
// Allocator: This is needed for the struct instance. In most cases, feel free
9+
// to use std.heap.GeneralPurposeAllocator
10+
pub fn DoublyLinkedList(comptime T: type) type {
11+
return struct {
12+
const Self = @This();
13+
14+
// This is the node struct. It holds:
15+
// info: T
16+
// next: A pointer to the next element
17+
// prev: A pointer to the previous element
18+
pub const node = struct {
19+
info: T,
20+
next: ?*node = null,
21+
prev: ?*node = null,
22+
};
23+
24+
allocator: *std.mem.Allocator,
25+
root: ?*node = null,
26+
tail: ?*node = null,
27+
size: usize = 0,
28+
29+
// Function that inserts elements to the tail of the list
30+
// Runs in O(1)
31+
// Arguments:
32+
// key: T - the key to be inserted to the list
33+
pub fn push_back(self: *Self, key: T) !void {
34+
const nn = try self.allocator.create(node);
35+
nn.* = node{ .info = key, .next = null, .prev = self.tail };
36+
37+
if (self.tail != null) {
38+
self.tail.?.next = nn;
39+
}
40+
if (self.root == null) {
41+
self.root = nn;
42+
}
43+
44+
self.tail = nn;
45+
self.size += 1;
46+
}
47+
48+
// Function that inserts elements to the front of the list
49+
// Runs in O(1)
50+
// Arguments:
51+
// key: T - the key to be inserted to the list
52+
pub fn push_front(self: *Self, key: T) !void {
53+
const nn = try self.allocator.create(node);
54+
nn.* = node{ .info = key, .next = self.root, .prev = null };
55+
56+
if (self.root != null) {
57+
self.root.?.prev = nn;
58+
}
59+
if (self.tail == null) {
60+
self.tail = nn;
61+
}
62+
63+
self.root = nn;
64+
self.size += 1;
65+
}
66+
67+
// Function that removes the front of the list
68+
// Runs in O(1)
69+
pub fn pop_front(self: *Self) void {
70+
if (self.root == null) {
71+
return;
72+
}
73+
74+
const temp: *node = self.root.?;
75+
defer self.allocator.destroy(temp);
76+
77+
self.root = self.root.?.next;
78+
self.size -= 1;
79+
}
80+
81+
// Function that removes the back of the list
82+
// Runs in O(1)
83+
pub fn pop_back(self: *Self) void {
84+
if (self.root == null) {
85+
return;
86+
}
87+
88+
const temp: *node = self.tail.?;
89+
defer self.allocator.destroy(temp);
90+
91+
self.tail = self.tail.?.prev;
92+
93+
if (self.tail != null) {
94+
self.tail.?.next = null;
95+
} else {
96+
self.root = null;
97+
}
98+
99+
self.size -= 1;
100+
}
101+
102+
// Function that returns true if the list is empty
103+
pub fn empty(self: *Self) bool {
104+
return (self.size == 0);
105+
}
106+
107+
// Function to search if a key exists in the list
108+
// Runs in O(n)
109+
// Arguments:
110+
// key: T - the key that will be searched
111+
pub fn search(self: *Self, key: T) bool {
112+
if (self.root == null) {
113+
return false;
114+
}
115+
116+
var head: ?*node = self.root;
117+
while (head) |curr| {
118+
if (curr.info == key) {
119+
return true;
120+
}
121+
122+
head = curr.next;
123+
}
124+
125+
return false;
126+
}
127+
128+
// Function that removes elements from the list
129+
// Runs in O(n)
130+
// Arguments:
131+
// key: T - the key to be removed from the list(if it exists)
132+
pub fn remove(self: *Self, key: T) void {
133+
if (self.root == null) {
134+
return;
135+
}
136+
137+
var head: ?*node = self.root;
138+
var prev: ?*node = null;
139+
while (head) |curr| {
140+
if (curr.info == key) {
141+
const temp: *node = curr;
142+
if (prev == null) {
143+
self.root = self.root.?.next;
144+
} else {
145+
prev.?.next = curr.next;
146+
}
147+
148+
self.allocator.destroy(temp);
149+
self.size -= 1;
150+
return;
151+
}
152+
prev = curr;
153+
head = curr.next.?;
154+
}
155+
}
156+
157+
// Function that prints the list
158+
pub fn printList(self: *Self) void {
159+
if (self.root == null) {
160+
return;
161+
}
162+
163+
var head: ?*node = self.root;
164+
while (head) |curr| {
165+
print("{} -> ", .{curr.info});
166+
head = curr.next;
167+
}
168+
print("\n", .{});
169+
}
170+
171+
// Function that destroys the allocated memory of the whole list
172+
pub fn destroy(self: *Self) void {
173+
var head: ?*node = self.root;
174+
175+
while (head) |curr| {
176+
const next = curr.next;
177+
self.allocator.destroy(curr);
178+
head = next;
179+
}
180+
181+
self.root = null;
182+
self.tail = null;
183+
self.size = 0;
184+
}
185+
};
186+
}
187+
188+
test "Testing Doubly Linked List" {
189+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
190+
defer _ = gpa.deinit();
191+
var allocator = gpa.allocator();
192+
193+
var list = DoublyLinkedList(i32){ .allocator = &allocator };
194+
defer list.destroy();
195+
196+
try list.push_front(10);
197+
try list.push_front(20);
198+
try list.push_front(30);
199+
200+
try testing.expect(list.search(10) == true);
201+
try testing.expect(list.search(30) == true);
202+
203+
list.remove(20);
204+
try testing.expect(list.search(20) == false);
205+
206+
var list2 = DoublyLinkedList(i32){ .allocator = &allocator };
207+
defer list2.destroy();
208+
209+
inline for (0..4) |el| {
210+
try list2.push_back(el);
211+
}
212+
213+
inline for (0..4) |el| {
214+
try testing.expect(list2.search(el) == true);
215+
}
216+
217+
try testing.expect(list2.size == 4);
218+
219+
list2.pop_front();
220+
try testing.expect(list2.search(0) == false);
221+
222+
list2.pop_back();
223+
224+
try testing.expect(list2.size == 2);
225+
try testing.expect(list2.search(3) == false);
226+
}

0 commit comments

Comments
 (0)