Commit 6f66691214

Andrea Orru <andreaorru1991@gmail.com>
2017-05-03 20:28:06
Generic doubly linked list. (#361)
Standard linked list
1 parent cceaa73
std/index.zig
@@ -8,6 +8,7 @@ pub const empty_import = @import("empty.zig");
 pub const fmt = @import("fmt.zig");
 pub const hash_map = @import("hash_map.zig");
 pub const io = @import("io.zig");
+pub const linked_list = @import("linked_list.zig");
 pub const list = @import("list.zig");
 pub const math = @import("math.zig");
 pub const mem = @import("mem.zig");
@@ -28,6 +29,7 @@ test "std" {
     _ = @import("fmt.zig");
     _ = @import("hash_map.zig");
     _ = @import("io.zig");
+    _ = @import("linked_list.zig");
     _ = @import("list.zig");
     _ = @import("math.zig");
     _ = @import("mem.zig");
std/linked_list.zig
@@ -0,0 +1,271 @@
+const debug = @import("debug.zig");
+const assert = debug.assert;
+const mem = @import("mem.zig");
+const Allocator = mem.Allocator;
+
+/// Generic doubly linked list.
+pub fn LinkedList(comptime T: type) -> type {
+    struct {
+        const List = this;
+
+        /// Node inside the linked list wrapping the actual data.
+        pub const Node = struct {
+            prev: ?&Node,
+            next: ?&Node,
+            data: T,
+        };
+
+        first: ?&Node,
+        last:  ?&Node,
+        len:   usize,
+        allocator: &Allocator,
+
+        /// Initialize a linked list.
+        ///
+        /// Arguments:
+        ///     allocator: Dynamic memory allocator.
+        ///
+        /// Returns:
+        ///     An empty linked list.
+        pub fn init(allocator: &Allocator) -> List {
+            List {
+                .first = null,
+                .last  = null,
+                .len   = 0,
+                .allocator = allocator,
+            }
+        }
+
+        /// Insert a new node after an existing one.
+        ///
+        /// Arguments:
+        ///     node: Pointer to a node in the list.
+        ///     new_node: Pointer to the new node to insert.
+        pub fn insertAfter(list: &List, node: &Node, new_node: &Node) {
+            new_node.prev = node;
+            test (node.next) |next_node| {
+                // Intermediate node.
+                new_node.next = next_node;
+                next_node.prev = new_node;
+            } else {
+                // Last element of the list.
+                new_node.next = null;
+                list.last = new_node;
+            }
+            node.next = new_node;
+
+            list.len += 1;
+        }
+
+        /// Insert a new node before an existing one.
+        ///
+        /// Arguments:
+        ///     node: Pointer to a node in the list.
+        ///     new_node: Pointer to the new node to insert.
+        pub fn insertBefore(list: &List, node: &Node, new_node: &Node) {
+            new_node.next = node;
+            test (node.prev) |prev_node| {
+                // Intermediate node.
+                new_node.prev = prev_node;
+                prev_node.next = new_node;
+            } else {
+                // First element of the list.
+                new_node.prev = null;
+                list.first = new_node;
+            }
+            node.prev = new_node;
+
+            list.len += 1;
+        }
+
+        /// Insert a new node at the end of the list.
+        ///
+        /// Arguments:
+        ///     new_node: Pointer to the new node to insert.
+        pub fn append(list: &List, new_node: &Node) {
+            test (list.last) |last| {
+                // Insert after last.
+                list.insertAfter(last, new_node);
+            } else {
+                // Empty list.
+                list.prepend(new_node);
+            }
+        }
+
+        /// Insert a new node at the beginning of the list.
+        ///
+        /// Arguments:
+        ///     new_node: Pointer to the new node to insert.
+        pub fn prepend(list: &List, new_node: &Node) {
+            test (list.first) |first| {
+                // Insert before first.
+                list.insertBefore(first, new_node);
+            } else {
+                // Empty list.
+                list.first = new_node;
+                list.last  = new_node;
+                new_node.prev = null;
+                new_node.next = null;
+
+                list.len = 1;
+            }
+        }
+
+        /// Remove a node from the list.
+        ///
+        /// Arguments:
+        ///     node: Pointer to the node to be removed.
+        pub fn remove(list: &List, node: &Node) {
+            test (node.prev) |prev_node| {
+                // Intermediate node.
+                prev_node.next = node.next;
+            } else {
+                // First element of the list.
+                list.first = node.next;
+            }
+
+            test (node.next) |next_node| {
+                // Intermediate node.
+                next_node.prev = node.prev;
+            } else {
+                // Last element of the list.
+                list.last = node.prev;
+            }
+
+            list.len -= 1;
+        }
+
+        /// Remove and return the last node in the list.
+        ///
+        /// Returns:
+        ///     A pointer to the last node in the list.
+        pub fn pop(list: &List) -> ?&Node {
+            const last = list.last ?? return null;
+            list.remove(last);
+            return last;
+        }
+
+        /// Remove and return the first node in the list.
+        ///
+        /// Returns:
+        ///     A pointer to the first node in the list.
+        pub fn popFirst(list: &List) -> ?&Node {
+            const first = list.first ?? return null;
+            list.remove(first);
+            return first;
+        }
+
+        /// Allocate a new node.
+        ///
+        /// Returns:
+        ///     A pointer to the new node.
+        pub fn allocateNode(list: &List) -> %&Node {
+            list.allocator.create(Node)
+        }
+
+        /// Deallocate a node.
+        ///
+        /// Arguments:
+        ///     node: Pointer to the node to deallocate.
+        pub fn destroyNode(list: &List, node: &Node) {
+            list.allocator.destroy(node);
+        }
+
+        /// Allocate and initialize a node and its data.
+        ///
+        /// Arguments:
+        ///     data: The data to put inside the node.
+        ///
+        /// Returns:
+        ///     A pointer to the new node.
+        pub fn createNode(list: &List, data: &const T) -> %&Node {
+            var node = %return list.allocateNode();
+            *node = Node {
+                .prev = null,
+                .next = null,
+                .data = *data,
+            };
+            return node;
+        }
+
+        /// Iterate through the elements of the list.
+        ///
+        /// Returns:
+        ///     A list iterator with a next() method.
+        pub fn iterate(list: &List) -> List.Iterator(false) {
+            List.Iterator(false) {
+                .node = list.first,
+            }
+        }
+
+        /// Iterate through the elements of the list backwards.
+        ///
+        /// Returns:
+        ///     A list iterator with a next() method.
+        pub fn iterateBackwards(list: &List) -> List.Iterator(true) {
+            List.Iterator(true) {
+                .node = list.last,
+            }
+        }
+
+        /// Abstract iteration over a linked list.
+        pub fn Iterator(comptime backwards: bool) -> type {
+            struct {
+                const It = this;
+
+                node: ?&Node,
+
+                /// Return the next element of the list, until the end.
+                /// When no more elements are available, return null.
+                pub fn next(it: &It) -> ?&Node {
+                    const current = it.node ?? return null;
+                    it.node = if (backwards) current.prev else current.next;
+                    return current;
+                }
+            }
+        }
+    }
+}
+
+test "basic linked list test" {
+    var list = LinkedList(u32).init(&debug.global_allocator);
+
+    var one   = %%list.createNode(1);
+    var two   = %%list.createNode(2);
+    var three = %%list.createNode(3);
+    var four  = %%list.createNode(4);
+    var five  = %%list.createNode(5);
+    defer {
+        list.destroyNode(one);
+        list.destroyNode(two);
+        list.destroyNode(three);
+        list.destroyNode(four);
+        list.destroyNode(five);
+    }
+
+    list.append(two);               // {2}
+    list.append(five);              // {2, 5}
+    list.prepend(one);              // {1, 2, 5}
+    list.insertBefore(five, four);  // {1, 2, 4, 5}
+    list.insertAfter(two, three);   // {1, 2, 3, 4, 5}
+
+    // Traverse the list forwards and backwards.
+    var it = list.iterate();
+    var it_reverse = list.iterateBackwards();
+    var index: u32 = 1;
+    while (true) {
+        const node = it.next() ?? break;
+        const node_reverse = it_reverse.next() ?? break;
+        assert (node.data == index);
+        assert (node_reverse.data == (6 - index));
+        index += 1;
+    }
+
+    var first = list.popFirst();    // {2, 3, 4, 5}
+    var last  = list.pop();         // {2, 3, 4}
+    list.remove(three);             // {2, 4}
+
+    assert ((??list.first).data == 2);
+    assert ((??list.last ).data == 4);
+    assert (list.len == 2);
+}
CMakeLists.txt
@@ -215,6 +215,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/fmt.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/hash_map.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/index.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/io.zig" DESTINATION "${ZIG_STD_DEST}")
+install(FILES "${CMAKE_SOURCE_DIR}/std/linked_list.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/list.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/math.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/mem.zig" DESTINATION "${ZIG_STD_DEST}")