adt: implement a doubly linked list (#12950)
							parent
							
								
									c0dcd1a9a5
								
							
						
					
					
						commit
						d69d2c600b
					
				|  | @ -0,0 +1,284 @@ | ||||||
|  | module adt | ||||||
|  | 
 | ||||||
|  | struct DoublyListNode<T> { | ||||||
|  | mut: | ||||||
|  | 	data T | ||||||
|  | 	next &DoublyListNode<T> = 0 | ||||||
|  | 	prev &DoublyListNode<T> = 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct DoublyLinkedList<T> { | ||||||
|  | mut: | ||||||
|  | 	head &DoublyListNode<T> = 0 | ||||||
|  | 	tail &DoublyListNode<T> = 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<T> = 0 | ||||||
|  | 	len  int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // is_empty checks if the linked list is empty
 | ||||||
|  | pub fn (list DoublyLinkedList<T>) is_empty() bool { | ||||||
|  | 	return list.len == 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // len returns the length of the linked list
 | ||||||
|  | pub fn (list DoublyLinkedList<T>) len() int { | ||||||
|  | 	return list.len | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // first returns the first element of the linked list
 | ||||||
|  | pub fn (list DoublyLinkedList<T>) 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<T>) 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<T>) 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<T>) 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<T>) 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<T>) 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<T>) 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<T>) 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<T>) 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<T>) node(idx int) &DoublyListNode<T> { | ||||||
|  | 	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<T>) 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<T>) 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<T>) 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<T>) next() ?T { | ||||||
|  | 	if list.iter == voidptr(0) { | ||||||
|  | 		// initialize new iter object
 | ||||||
|  | 		list.iter = &DoublyListIter<T>{ | ||||||
|  | 			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<T> { | ||||||
|  | mut: | ||||||
|  | 	node &DoublyListNode<T> = 0 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,160 @@ | ||||||
|  | module adt | ||||||
|  | 
 | ||||||
|  | fn test_is_empty() { | ||||||
|  | 	mut list := DoublyLinkedList<int>{} | ||||||
|  | 	assert list.is_empty() == true | ||||||
|  | 	list.push_back(1) | ||||||
|  | 	assert list.is_empty() == false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn test_len() ? { | ||||||
|  | 	mut list := DoublyLinkedList<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	list.push_back(1) | ||||||
|  | 	assert list.first() ? == 1 | ||||||
|  | 	list.push_back(2) | ||||||
|  | 	assert list.first() ? == 1 | ||||||
|  | 	list = DoublyLinkedList<int>{} | ||||||
|  | 	list.first() or { return } | ||||||
|  | 	assert false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn test_last() ? { | ||||||
|  | 	mut list := DoublyLinkedList<int>{} | ||||||
|  | 	list.push_back(1) | ||||||
|  | 	assert list.last() ? == 1 | ||||||
|  | 	list.push_back(2) | ||||||
|  | 	assert list.last() ? == 2 | ||||||
|  | 	list = DoublyLinkedList<int>{} | ||||||
|  | 	list.last() or { return } | ||||||
|  | 	assert false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn test_push() ? { | ||||||
|  | 	mut list := DoublyLinkedList<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	list.pop_back() or { return } | ||||||
|  | 	assert false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn test_pop_front() ? { | ||||||
|  | 	mut list := DoublyLinkedList<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	list.pop_front() or { return } | ||||||
|  | 	assert false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn test_insert() ? { | ||||||
|  | 	mut list := DoublyLinkedList<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	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<int>{} | ||||||
|  | 	list.push_back(1) | ||||||
|  | 	list.push_back(2) | ||||||
|  | 	list.push_back(3) | ||||||
|  | 	assert list.str() == '[1, 2, 3]' | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue