From d69d2c600b4fd7aea09828cfe0f2b90c4708e5a5 Mon Sep 17 00:00:00 2001 From: Miccah Date: Fri, 24 Dec 2021 03:19:40 -0600 Subject: [PATCH] adt: implement a doubly linked list (#12950) --- vlib/adt/doubly_linked_list.v | 284 +++++++++++++++++++++++++++++ vlib/adt/doubly_linked_list_test.v | 160 ++++++++++++++++ 2 files changed, 444 insertions(+) create mode 100644 vlib/adt/doubly_linked_list.v create mode 100644 vlib/adt/doubly_linked_list_test.v diff --git a/vlib/adt/doubly_linked_list.v b/vlib/adt/doubly_linked_list.v new file mode 100644 index 0000000000..e02920e8b9 --- /dev/null +++ b/vlib/adt/doubly_linked_list.v @@ -0,0 +1,284 @@ +module adt + +struct DoublyListNode { +mut: + data T + next &DoublyListNode = 0 + prev &DoublyListNode = 0 +} + +pub struct DoublyLinkedList { +mut: + head &DoublyListNode = 0 + tail &DoublyListNode = 0 + // Internal iter pointer for allowing safe modification + // of the list while iterating. TODO: use an option + // instead of a pointer to determine it is initialized. + iter &DoublyListIter = 0 + len int +} + +// is_empty checks if the linked list is empty +pub fn (list DoublyLinkedList) is_empty() bool { + return list.len == 0 +} + +// len returns the length of the linked list +pub fn (list DoublyLinkedList) len() int { + return list.len +} + +// first returns the first element of the linked list +pub fn (list DoublyLinkedList) first() ?T { + if list.is_empty() { + return error('Linked list is empty') + } + return list.head.data +} + +// last returns the last element of the linked list +pub fn (list DoublyLinkedList) last() ?T { + if list.is_empty() { + return error('Linked list is empty') + } + return list.tail.data +} + +// push_back adds an element to the end of the linked list +pub fn (mut list DoublyLinkedList) push_back(item T) { + mut new_node := &DoublyListNode{ + data: item + } + if list.is_empty() { + // first node case + list.head = new_node + list.tail = new_node + } else { + list.tail.next = new_node + new_node.prev = list.tail + list.tail = new_node + } + list.len += 1 +} + +// push_front adds an element to the beginning of the linked list +pub fn (mut list DoublyLinkedList) push_front(item T) { + mut new_node := &DoublyListNode{ + data: item + } + if list.is_empty() { + // first node case + list.head = new_node + list.tail = new_node + } else { + list.head.prev = new_node + new_node.next = list.head + list.head = new_node + } + list.len += 1 +} + +// pop_back removes the last element of the linked list +pub fn (mut list DoublyLinkedList) pop_back() ?T { + if list.is_empty() { + return error('Linked list is empty') + } + defer { + list.len -= 1 + } + if list.len == 1 { + // head == tail + value := list.tail.data + list.head = voidptr(0) + list.tail = voidptr(0) + return value + } + value := list.tail.data + list.tail.prev.next = voidptr(0) // unlink tail + list.tail = list.tail.prev + return value +} + +// pop_front removes the last element of the linked list +pub fn (mut list DoublyLinkedList) pop_front() ?T { + if list.is_empty() { + return error('Linked list is empty') + } + defer { + list.len -= 1 + } + if list.len == 1 { + // head == tail + value := list.head.data + list.head = voidptr(0) + list.tail = voidptr(0) + return value + } + value := list.head.data + list.head.next.prev = voidptr(0) // unlink head + list.head = list.head.next + return value +} + +// insert adds an element to the linked list at the given index +pub fn (mut list DoublyLinkedList) insert(idx int, item T) ? { + if idx < 0 || idx > list.len { + return error('Index out of bounds') + } else if idx == 0 { + // new head + list.push_front(item) + } else if idx == list.len { + // new tail + list.push_back(item) + } else if idx <= list.len / 2 { + list.insert_front(idx, item) + } else { + list.insert_back(idx, item) + } +} + +// insert_back walks from the tail and inserts a new item at index idx +// (determined from the forward index). This function should be called +// when idx > list.len/2. This helper function assumes idx bounds have +// already been checked and idx is not at the edges. +fn (mut list DoublyLinkedList) insert_back(idx int, item T) { + mut node := list.node(idx + 1) + mut prev := node.prev + // prev node + // ------ ------ + // |next|---->|next| + // |prev|<----|prev| + // ------ ------ + new := &DoublyListNode{ + data: item + next: node + prev: prev + } + // prev new node + // ------ ------ ------ + // |next|---->|next|---->|next| + // |prev|<----|prev|<----|prev| + // ------ ------ ------ + node.prev = new + prev.next = new + list.len += 1 +} + +// insert_front walks from the head and inserts a new item at index idx +// (determined from the forward index). This function should be called +// when idx <= list.len/2. This helper function assumes idx bounds have +// already been checked and idx is not at the edges. +fn (mut list DoublyLinkedList) insert_front(idx int, item T) { + mut node := list.node(idx - 1) + mut next := node.next + // node next + // ------ ------ + // |next|---->|next| + // |prev|<----|prev| + // ------ ------ + new := &DoublyListNode{ + data: item + next: next + prev: node + } + // node new next + // ------ ------ ------ + // |next|---->|next|---->|next| + // |prev|<----|prev|<----|prev| + // ------ ------ ------ + node.next = new + next.prev = new + list.len += 1 +} + +// node walks from the head or tail and finds the node at index idx. +// This helper function assumes the list is not empty and idx is in +// bounds. +fn (list &DoublyLinkedList) node(idx int) &DoublyListNode { + if idx <= list.len / 2 { + mut node := list.head + for h := 0; h < idx; h += 1 { + node = node.next + } + return node + } + mut node := list.tail + for t := list.len - 1; t >= idx; t -= 1 { + node = node.prev + } + return node +} + +// index searches the linked list for item and returns the forward index +// or none if not found. +pub fn (list &DoublyLinkedList) index(item T) ?int { + mut hn := list.head + mut tn := list.tail + for h, t := 0, list.len - 1; h <= t; { + if hn.data == item { + return h + } else if tn.data == item { + return t + } + h += 1 + hn = hn.next + t -= 1 + tn = tn.prev + } + return none +} + +// delete removes index idx from the linked list and is safe to call +// for any idx. +pub fn (mut list DoublyLinkedList) delete(idx int) { + if idx < 0 || idx >= list.len { + return + } else if idx == 0 { + list.pop_front() or {} + return + } else if idx == list.len - 1 { + list.pop_back() or {} + return + } + // node should be somewhere in the middle + mut node := list.node(idx) + node.prev.next = node.next + node.next.prev = node.prev + list.len -= 1 +} + +// str returns a string representation of the linked list +pub fn (list DoublyLinkedList) str() string { + mut result_array := []T{} + mut node := list.head + for node != 0 { + result_array << node.data + node = node.next + } + return result_array.str() +} + +// next implements the iter interface to use DoublyLinkedList with +// V's for loop syntax. +pub fn (mut list DoublyLinkedList) next() ?T { + if list.iter == voidptr(0) { + // initialize new iter object + list.iter = &DoublyListIter{ + node: list.head + } + return list.next() + } + if list.iter.node == voidptr(0) { + list.iter = voidptr(0) + return none + } + defer { + list.iter.node = list.iter.node.next + } + return list.iter.node.data +} + +struct DoublyListIter { +mut: + node &DoublyListNode = 0 +} diff --git a/vlib/adt/doubly_linked_list_test.v b/vlib/adt/doubly_linked_list_test.v new file mode 100644 index 0000000000..5668aeda3c --- /dev/null +++ b/vlib/adt/doubly_linked_list_test.v @@ -0,0 +1,160 @@ +module adt + +fn test_is_empty() { + mut list := DoublyLinkedList{} + assert list.is_empty() == true + list.push_back(1) + assert list.is_empty() == false +} + +fn test_len() ? { + mut list := DoublyLinkedList{} + assert list.len() == 0 + list.push_back(1) + assert list.len() == 1 + list.pop_back() ? + assert list.len() == 0 +} + +fn test_first() ? { + mut list := DoublyLinkedList{} + list.push_back(1) + assert list.first() ? == 1 + list.push_back(2) + assert list.first() ? == 1 + list = DoublyLinkedList{} + list.first() or { return } + assert false +} + +fn test_last() ? { + mut list := DoublyLinkedList{} + list.push_back(1) + assert list.last() ? == 1 + list.push_back(2) + assert list.last() ? == 2 + list = DoublyLinkedList{} + list.last() or { return } + assert false +} + +fn test_push() ? { + mut list := DoublyLinkedList{} + list.push_back(1) + assert list.last() ? == 1 + list.push_back(2) + assert list.last() ? == 2 + list.push_back(3) + assert list.last() ? == 3 +} + +fn test_pop() ? { + mut list := DoublyLinkedList{} + list.push_back(1) + list.push_back(2) + list.push_back(3) + assert list.pop_back() ? == 3 + list.push_back(4) + assert list.pop_back() ? == 4 + assert list.pop_back() ? == 2 + list = DoublyLinkedList{} + list.pop_back() or { return } + assert false +} + +fn test_pop_front() ? { + mut list := DoublyLinkedList{} + list.push_back(1) + list.push_back(2) + list.push_back(3) + assert list.pop_front() ? == 1 + list.push_back(4) + assert list.pop_front() ? == 2 + assert list.pop_front() ? == 3 + list = DoublyLinkedList{} + list.pop_front() or { return } + assert false +} + +fn test_insert() ? { + mut list := DoublyLinkedList{} + list.push_back(1) + list.push_back(2) + list.push_back(3) + // [1, 2, 3] + list.insert(1, 111) ? + // [1, 111, 2, 3] + list.insert(3, 222) ? + // [1, 111, 2, 222, 3] + assert list.pop_back() ? == 3 + assert list.pop_back() ? == 222 + assert list.pop_front() ? == 1 + assert list.pop_front() ? == 111 +} + +fn test_push_front() ? { + mut list := DoublyLinkedList{} + list.push_back(1) + list.push_back(2) + list.push_back(3) + list.push_front(111) + assert list.first() ? == 111 +} + +fn test_delete() ? { + mut list := DoublyLinkedList{} + list.push_back(0) + list.push_back(1) + list.push_back(2) + list.delete(1) + assert list.first() ? == 0 + assert list.last() ? == 2 + assert list.len() == 2 + list.delete(1) + assert list.first() ? == 0 + assert list.last() ? == 0 + assert list.len() == 1 + list.delete(0) + assert list.len() == 0 +} + +fn test_iter() ? { + mut list := DoublyLinkedList{} + for i := 0; i < 10; i++ { + list.push_back(i * 10) + } + + mut count := 0 + for i, v in list { + count += 1 + assert int(i * 10) == v + } + assert count == 10 + + // test it gets reset + count = 0 + for i, v in list { + count += 1 + assert int(i * 10) == v + } + assert count == 10 +} + +fn test_index() ? { + mut list := DoublyLinkedList{} + for i := 0; i < 10; i++ { + list.push_back(i * 10) + } + + for i := 0; i < 10; i++ { + assert list.index(i * 10) ? == i + } +} + +fn test_str() ? { + mut list := DoublyLinkedList{} + list.push_back(1) + list.push_back(2) + list.push_back(3) + assert list.str() == '[1, 2, 3]' +}