Skip to content

Commit

Permalink
Starting doubly-linked list implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
eliben committed Aug 24, 2024
1 parent bed5279 commit 6ce6c2e
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 0 deletions.
100 changes: 100 additions & 0 deletions list/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Package list implements a doubly-linked list.
package list

import (
"fmt"
"iter"
)

type Node[T any] struct {
next, prev *Node[T]
Value T
}

type List[T any] struct {
front *Node[T]
back *Node[T]
length int
}

func New[T any]() *List[T] {
// A List always has two allocated sentinel nodes: front points to the
// first, and back points at the last. The length of the list remains
// 0 as long as there are only sentinel nodes in it.
lst := &List[T]{
front: &Node[T]{},
back: &Node[T]{},
length: 0,
}
lst.front.next = lst.back
lst.back.prev = lst.front
return lst
}

func (lst *List[T]) Len() int {
return lst.length
}

func (lst *List[T]) Front() *Node[T] {
if lst.length == 0 {
return nil
}
return lst.front.next
}

func (lst *List[T]) Back() *Node[T] {
if lst.length == 0 {
return nil
}
return lst.back.prev
}

// InsertFront inserts a new node with the given value at the front of the list.
func (lst *List[T]) InsertFront(val T) {
oldFirst := lst.front.next
lst.front.next = &Node[T]{
next: oldFirst,
prev: lst.front,
Value: val,
}
oldFirst.prev = lst.front.next
lst.length += 1
}

// InsertBack inserts a new node with the given value at the back of the list.
func (lst *List[T]) InsertBack(val T) {
oldLast := lst.back.prev
lst.back.prev = &Node[T]{
next: lst.back,
prev: oldLast,
Value: val,
}
oldLast.next = lst.back.prev
lst.length += 1
}

// All returns an iterator over all the values in the list.
func (lst *List[T]) All() iter.Seq[T] {
return func(yield func(T) bool) {
for node := lst.front.next; node != lst.back; node = node.next {
if !yield(node.Value) {
return
}
}
}
}

func (lst *List[T]) debugPrint() {
fmt.Println("-----------------------")
for n := lst.front; n != nil; n = n.next {
var specialName string
if n == lst.front {
specialName = " [FRONT]"
} else if n == lst.back {
specialName = " [BACK]"
}
fmt.Printf("| Node addr=%p%v:\n", n, specialName)
fmt.Printf("| value=%v next=%p prev=%p\n", n.Value, n.next, n.prev)
}
fmt.Println("-----------------------")
}
38 changes: 38 additions & 0 deletions list/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package list

import (
"slices"
"testing"
)

func checkList[T comparable](t *testing.T, lst *List[T], want []T) {
t.Helper()
if lst.Len() != len(want) {
t.Errorf("got len=%v, want %v", lst.Len(), len(want))
}

got := slices.Collect(lst.All())
if !slices.Equal(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}

func TestBasicInsertFront(t *testing.T) {
nl := New[int]()
nl.InsertFront(20)
checkList(t, nl, []int{20})
nl.InsertFront(30)
checkList(t, nl, []int{30, 20})
nl.InsertFront(10)
checkList(t, nl, []int{10, 30, 20})
}

func TestBasicInsertBack(t *testing.T) {
nl := New[int]()
nl.InsertBack(20)
checkList(t, nl, []int{20})
nl.InsertBack(30)
checkList(t, nl, []int{20, 30})
nl.InsertBack(10)
checkList(t, nl, []int{20, 30, 10})
}

0 comments on commit 6ce6c2e

Please sign in to comment.