Commit 19343db593

Andrea Orru <andrea@orru.io>
2018-01-10 06:33:07
Intrusive linked lists
1 parent 3c09411
Changed files (1)
std/linked_list.zig
@@ -4,8 +4,18 @@ const assert = debug.assert;
 const mem = std.mem;
 const Allocator = mem.Allocator;
 
-/// Generic doubly linked list.
+/// Generic non-intrusive doubly linked list.
 pub fn LinkedList(comptime T: type) -> type {
+    return BaseLinkedList(T, void, "");
+}
+
+/// Generic intrusive doubly linked list.
+pub fn IntrusiveLinkedList(comptime ParentType: type, comptime field_name: []const u8) -> type {
+    return BaseLinkedList(void, ParentType, field_name);
+}
+
+/// Generic doubly linked list.
+fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_name: []const u8) -> type {
     return struct {
         const Self = this;
 
@@ -15,13 +25,23 @@ pub fn LinkedList(comptime T: type) -> type {
             next: ?&Node,
             data: T,
 
-            pub fn init(data: &const T) -> Node {
+            pub fn init(value: &const T) -> Node {
                 return Node {
                     .prev = null,
                     .next = null,
-                    .data = *data,
+                    .data = *value,
                 };
             }
+
+            pub fn initIntrusive() -> Node {
+                // TODO: when #678 is solved this can become `init`.
+                return Node.init({});
+            }
+
+            pub fn toData(node: &Node) -> &ParentType {
+                comptime assert(isIntrusive());
+                return @fieldParentPtr(ParentType, field_name, node);
+            }
         };
 
         first: ?&Node,
@@ -40,6 +60,10 @@ pub fn LinkedList(comptime T: type) -> type {
             };
         }
 
+        fn isIntrusive() -> bool {
+            return ParentType != void or field_name.len != 0;
+        }
+
         /// Insert a new node after an existing one.
         ///
         /// Arguments:
@@ -167,6 +191,7 @@ pub fn LinkedList(comptime T: type) -> type {
         /// Returns:
         ///     A pointer to the new node.
         pub fn allocateNode(list: &Self, allocator: &Allocator) -> %&Node {
+            comptime assert(!isIntrusive());
             return allocator.create(Node);
         }
 
@@ -176,6 +201,7 @@ pub fn LinkedList(comptime T: type) -> type {
         ///     node: Pointer to the node to deallocate.
         ///     allocator: Dynamic memory allocator.
         pub fn destroyNode(list: &Self, node: &Node, allocator: &Allocator) {
+            comptime assert(!isIntrusive());
             allocator.destroy(node);
         }
 
@@ -188,6 +214,7 @@ pub fn LinkedList(comptime T: type) -> type {
         /// Returns:
         ///     A pointer to the new node.
         pub fn createNode(list: &Self, data: &const T, allocator: &Allocator) -> %&Node {
+            comptime assert(!isIntrusive());
             var node = try list.allocateNode(allocator);
             *node = Node.init(data);
             return node;
@@ -199,11 +226,11 @@ test "basic linked list test" {
     const allocator = debug.global_allocator;
     var list = LinkedList(u32).init();
 
-    var one   = list.createNode(1, allocator) catch unreachable;
-    var two   = list.createNode(2, allocator) catch unreachable;
-    var three = list.createNode(3, allocator) catch unreachable;
-    var four  = list.createNode(4, allocator) catch unreachable;
-    var five  = list.createNode(5, allocator) catch unreachable;
+    var one   = try list.createNode(1, allocator);
+    var two   = try list.createNode(2, allocator);
+    var three = try list.createNode(3, allocator);
+    var four  = try list.createNode(4, allocator);
+    var five  = try list.createNode(5, allocator);
     defer {
         list.destroyNode(one, allocator);
         list.destroyNode(two, allocator);
@@ -246,3 +273,55 @@ test "basic linked list test" {
     assert ((??list.last ).data == 4);
     assert (list.len == 2);
 }
+
+const link = "link";
+const ElementList = IntrusiveLinkedList(Element, link);
+const Element = struct {
+    value: u32,
+    link: IntrusiveLinkedList(Element, link).Node,
+};
+
+test "basic intrusive linked list test" {
+    const allocator = debug.global_allocator;
+    var list = ElementList.init();
+
+    var one   = Element { .value = 1, .link = ElementList.Node.initIntrusive() };
+    var two   = Element { .value = 2, .link = ElementList.Node.initIntrusive() };
+    var three = Element { .value = 3, .link = ElementList.Node.initIntrusive() };
+    var four  = Element { .value = 4, .link = ElementList.Node.initIntrusive() };
+    var five  = Element { .value = 5, .link = ElementList.Node.initIntrusive() };
+
+    list.append(&two.link);                     // {2}
+    list.append(&five.link);                    // {2, 5}
+    list.prepend(&one.link);                    // {1, 2, 5}
+    list.insertBefore(&five.link, &four.link);  // {1, 2, 4, 5}
+    list.insertAfter(&two.link, &three.link);   // {1, 2, 3, 4, 5}
+
+    // Traverse forwards.
+    {
+        var it = list.first;
+        var index: u32 = 1;
+        while (it) |node| : (it = node.next) {
+            assert(node.toData().value == index);
+            index += 1;
+        }
+    }
+
+    // Traverse backwards.
+    {
+        var it = list.last;
+        var index: u32 = 1;
+        while (it) |node| : (it = node.prev) {
+            assert(node.toData().value == (6 - index));
+            index += 1;
+        }
+    }
+
+    var first = list.popFirst();  // {2, 3, 4, 5}
+    var last  = list.pop();       // {2, 3, 4}
+    list.remove(&three.link);     // {2, 4}
+
+    assert ((??list.first).toData().value == 2);
+    assert ((??list.last ).toData().value == 4);
+    assert (list.len == 2);
+}