Data Structures in Go: Linked List

Posted on December 22, 2019 | 4 minute read

Definition

A linked list is a linear collection of data elements, whose order is not given by their physical placement in memory. Instead, each element points to the next. It is a data structure consisting of a collection of nodes which together represent a sequence. (https://en.wikipedia.org/wiki/Linked_list)

A little intro

This is a first post from a series on Data Structure implementations in Go. I'm starting with what I consider one of the simplest data structures (I might be proven wrong later) and will continue with some of the other popular ones.

The full code will be available on GitHub and I will break it down here in as small chunks as I see necessary and explain what was done.

https://github.com/dorin131/go-data-structures/

This is not meant to be production code, so there is no error handling, it is for educational purposes only.

With all this said, let's get into it! :)

Implementation

To start with, we are going to create a struct which will contain a field Head with a reference to the first node of the list and another field Tail pointing to the last node. Also, one more struct will be created which represents the node itself. The node will be holding a reference to the following node and its own data. Notice how the type for Data is interface{}, which means that it can be of any type.

// LinkedList : Data structure
type LinkedList struct {
	Head *Node
	Tail *Node
}

// Node : A Linked List node
type Node struct {
	Next *Node
	Data interface{}
}

Following this, we want to create a “constructor” function which is just going to return a pointer to a new instance of a Linked List, with an empty node as Head and the exactly same node as Tail. You will see later that we are going to check whether our node is empty or not when performing some operations.

// New : Create a new Linked List
func New() *LinkedList {
	emptyNode := &Node{
		Next: nil,
		Data: nil,
	}
	return &LinkedList{
		Head: emptyNode,
		Tail: emptyNode,
	}
}

The first method we are going to add is Append. It has a pointer receiver which will let us modify the instance. In our case, we want to add a value to the last nodes Next field, which will place a new node at the end of our Linked List.

At the top we create a new node that doesn't have a Next value because it will be the last one and set the data argument to the Data field.

Notice how we check whether our first node is empty, and in that case our new node will be set as the Head, other wise it will be set as the next node of the Tail.

// Append : Appending a new node to the end of the Linked List
func (ll *LinkedList) Append(d interface{}) *LinkedList {
	nextNode := &Node{
		Next: nil,
		Data: d,
	}
	if ll.Head.Data == nil {
		ll.Head = nextNode
	} else {
		ll.Tail.Next = nextNode
	}
	ll.Tail = nextNode
	return ll
}

Another method I wanted to add was one which would let us delete a node which holds a specific value (only the first instance in this case). I called this method DeleteWithValue.

First we check whether the first element matches the value and then in a loop we check every Next's node value. We look one node ahead so that we can modify previous node's Next reference to point to the node after next, basically skipping it.

// DeleteWithValue : Deletes node which has a specific value
func (ll *LinkedList) DeleteWithValue(v interface{}) *LinkedList {
	var node = ll.Head
	if node.Data == v {
		ll.Head = ll.Head.Next
		return ll
	}
	for {
		if v == node.Next.Data {
			if node.Next.Next != nil {
				node.Next = node.Next.Next
				return ll
			}
			node.Next = nil
			return ll
		}
		node = node.Next
	}
	return ll
}

Finally, I added a PrintAll method which prints all the nodes, starting with the first one, to the standard output.

// PrintAll : Prints all elements of the Linked List
func (ll *LinkedList) PrintAll() {
	var node = ll.Head
	for {
		fmt.Println(node.Data)
		if node.Next == nil {
			return
		}
		node = node.Next
	}
}

If you want to test the this package, then you can clone the GitHub repo linked at the beginning of this post and run go test ./linkedlist from the root directory. There are a few “Example” tests which illustrate the usage and test it at the same time.

Conclusion

The Linked List was a fun data structure to implement but it's not the most practical or efficient out there. The main disadvantages would be slow indexing and a larger memory footprint. By having a pointer to the last element in the list (like we had) and a pointer on every node to the previous element (not implemented) we could speed up some operations but it would still lag behind.

tags:go data structure linked list
comments powered by Disqus