Skip to content

Commit

Permalink
Add doubly linked list
Browse files Browse the repository at this point in the history
  • Loading branch information
Ismail Gjevori committed Apr 2, 2022
1 parent 55b23b1 commit c8eb8d3
Show file tree
Hide file tree
Showing 2 changed files with 311 additions and 0 deletions.
152 changes: 152 additions & 0 deletions linked_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package collection

// DLList is a doubly linked list. It is based on the linked list implementation.
// It can be used as a stack and/or a queue.
// All the operations are O(1), even when the size of the list is large.
//
// The zero value for DLList is an empty list ready to use.
// var queue collection.DLList[int]
// queue.PushBack(1)
// queue.PushBack(2)
// queue.PushBack(3)
// fmt.Println(queue.PopFront()) // 1, true
// fmt.Println(queue.PopFront()) // 2, true
// fmt.Println(queue.PopFront()) // 3, true
// fmt.Println(queue.PopFront()) // 0, false
type DLList[T any] struct {
head *node[T]
tail *node[T]
size int
}

// Back returns the last element of the list.
// If the list is empty, the zero value is returned and false.
func (ll *DLList[T]) Back() (T, bool) {
if ll.size == 0 {
return *new(T), false
}
return ll.tail.value, true
}

// Clear removes all elements from the list.
func (ll *DLList[T]) Clear() {
ll.head = nil
ll.tail = nil
ll.size = 0
}

// Front returns the first element of the list.
// If the list is empty, the zero value is returned and false.
func (ll *DLList[T]) Front() (T, bool) {
if ll.size == 0 {
return *new(T), false
}
return ll.head.value, true
}

// IsEmpty returns true if the list is empty.
func (ll *DLList[T]) IsEmpty() bool {
return ll.size == 0
}

// Iter returns a new iterator for the list, iterating the values from front to back.
func (ll *DLList[T]) Iter() Iterator[T] {
cur_node := ll.head
return func() (T, bool) {
if cur_node == nil {
return *new(T), false
}
v := cur_node.value
cur_node = cur_node.next
return v, true
}
}

// PopBack removes the last element from the list.
// If the second return value is false, the list is empty and the zero value is returned.
func (ll *DLList[T]) PopBack() (T, bool) {
if ll.size == 0 {
return *new(T), false
}
n := ll.tail
if ll.size == 1 {
ll.head = nil
ll.tail = nil
} else {
ll.tail = n.prev
ll.tail.next = nil
}
ll.size--
return n.value, true
}

// PopFront removes the first element from the list.
// If the second return value is false, the list is empty and the zero value is returned.
func (ll *DLList[T]) PopFront() (T, bool) {
if ll.size == 0 {
return *new(T), false
}
n := ll.head
if ll.size == 1 {
ll.head = nil
ll.tail = nil
} else {
ll.head = n.next
ll.head.prev = nil
}
ll.size--
return n.value, true
}

// PushBack adds a new element at the back of the list.
func (ll *DLList[T]) PushBack(v T) {
n := &node[T]{value: v}
if ll.size == 0 {
ll.head = n
ll.tail = n
} else {
ll.tail.next = n
n.prev = ll.tail
ll.tail = n
}
ll.size++
}

// PushFront adds a new element at the front of the list.
func (ll *DLList[T]) PushFront(v T) {
n := &node[T]{value: v}
if ll.size == 0 {
ll.head = n
ll.tail = n
} else {
ll.head.prev = n
n.next = ll.head
ll.head = n
}
ll.size++
}

// ReverseIter returns a new iterator for the list, iterating the values from back to front.
func (ll *DLList[T]) ReverseIter() Iterator[T] {
cur_node := ll.tail
return func() (T, bool) {
if cur_node == nil {
return *new(T), false
}
v := cur_node.value
cur_node = cur_node.prev
return v, true
}
}

// Size returns the number of elements in the list.
func (ll *DLList[T]) Size() int {
return ll.size
}

// node is a helper struct that holds the value and the links to the next and previous nodes.
type node[T any] struct {
value T
prev *node[T]
next *node[T]
}
159 changes: 159 additions & 0 deletions linked_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package collection

import "testing"

func TestBack(t *testing.T) {
var queue DLList[int]
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
s := queue.Size()
if v, ok := queue.Back(); !ok || v != 3 {
t.Errorf("queue.Back() = %v, %v, want %v, %v", v, ok, 3, true)
}
if s != queue.Size() {
t.Errorf("queue.size cahnged was = %d, now = %d", s, queue.Size())
}
}

func TestClear(t *testing.T) {
var queue DLList[int]
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
queue.Clear()
if !queue.IsEmpty() {
t.Errorf("queue.IsEmpty() = %v, want %v", queue.IsEmpty(), true)
}
if queue.head != nil {
t.Errorf("queue.head = %v, want %v", queue.head, nil)
}
if queue.tail != nil {
t.Errorf("queue.tail = %v, want %v", queue.tail, nil)
}
}

func TestFront(t *testing.T) {
var queue DLList[int]
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
s := queue.Size()
if v, ok := queue.Front(); !ok || v != 1 {
t.Errorf("queue.Front() = %v, %v, want %v, %v", v, ok, 1, true)
}
if s != queue.Size() {
t.Errorf("queue.size cahnged was = %d, now = %d", s, queue.Size())
}
}

func TestIsEmpty(t *testing.T) {
var queue DLList[int]
if !queue.IsEmpty() {
t.Errorf("queue.IsEmpty() = %v, want %v", queue.IsEmpty(), true)
}
queue.PushBack(1)
if queue.IsEmpty() {
t.Errorf("queue.IsEmpty() = %v, want %v", queue.IsEmpty(), false)
}
}

func TestDLLIter(t *testing.T) {
var queue DLList[int]
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
var i int
queue.Iter().ForEach(func(v int) {
if v != i+1 {
t.Errorf("queue.Iter().ForEach() = %v, want %v", v, i+1)
}
i++
})
if i != 3 {
t.Errorf("queue.Iter().ForEach() = %v, want %v", i, 3)
}
i = 3
queue.ReverseIter().ForEach(func(v int) {
if v != i {
t.Errorf("queue.ReverseIter().ForEach() = %v, want %v", v, i)
}
i--
})
if i != 0 {
t.Errorf("queue.ReverseIter().ForEach() = %v, want %v", i, 0)
}
}

func TestPopBack(t *testing.T) {
var queue DLList[int]
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
s := queue.Size()
if v, ok := queue.PopBack(); !ok || v != 3 {
t.Errorf("queue.PopBack() = %v, %v, want %v, %v", v, ok, 3, true)
}
if s != queue.Size()+1 {
t.Errorf("queue.size did not change, before = %d, now = %d", s, queue.Size())
}
}

func TestPopFront(t *testing.T) {
var queue DLList[int]
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
s := queue.Size()
if v, ok := queue.PopFront(); !ok || v != 1 {
t.Errorf("queue.PopFront() = %v, %v, want %v, %v", v, ok, 1, true)
}
if s != queue.Size()+1 {
t.Errorf("queue.size did not change, before = %d, now = %d", s, queue.Size())
}
}

func TestPushBack(t *testing.T) {
var queue DLList[int]
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
s := queue.Size()
if s != 3 {
t.Errorf("queue.size = %d, want %d", s, 3)
}
if queue.head.value != 1 {
t.Errorf("queue.head.value = %d, want %d", queue.head.value, 1)
}
if queue.tail.value != 3 {
t.Errorf("queue.tail.value = %d, want %d", queue.tail.value, 3)
}
}

func TestPushFront(t *testing.T) {
var queue DLList[int]
queue.PushFront(1)
queue.PushFront(2)
queue.PushFront(3)
s := queue.Size()
if s != 3 {
t.Errorf("queue.size = %d, want %d", s, 3)
}
if queue.head.value != 3 {
t.Errorf("queue.head.value = %d, want %d", queue.head.value, 3)
}
if queue.tail.value != 1 {
t.Errorf("queue.tail.value = %d, want %d", queue.tail.value, 1)
}
}

func TestSize(t *testing.T) {
var queue DLList[int]
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
s := queue.Size()
if s != 3 {
t.Errorf("queue.size = %d, want %d", s, 3)
}
}

0 comments on commit c8eb8d3

Please sign in to comment.