Skip to content

Commit

Permalink
Implement generic linked.List (#2908)
Browse files Browse the repository at this point in the history
Co-authored-by: dhrubabasu <[email protected]>
  • Loading branch information
StephenButtolph and dhrubabasu authored Apr 3, 2024
1 parent f786a24 commit e1954bb
Show file tree
Hide file tree
Showing 2 changed files with 385 additions and 0 deletions.
217 changes: 217 additions & 0 deletions utils/linked/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package linked

// ListElement is an element of a linked list.
type ListElement[T any] struct {
next, prev *ListElement[T]
list *List[T]
Value T
}

// Next returns the next element or nil.
func (e *ListElement[T]) Next() *ListElement[T] {
if p := e.next; e.list != nil && p != &e.list.sentinel {
return p
}
return nil
}

// Prev returns the previous element or nil.
func (e *ListElement[T]) Prev() *ListElement[T] {
if p := e.prev; e.list != nil && p != &e.list.sentinel {
return p
}
return nil
}

// List implements a doubly linked list with a sentinel node.
//
// See: https://en.wikipedia.org/wiki/Doubly_linked_list
//
// This datastructure is designed to be an almost complete drop-in replacement
// for the standard library's "container/list".
//
// The primary design change is to remove all memory allocations from the list
// definition. This allows these lists to be used in performance critical paths.
// Additionally the zero value is not useful. Lists must be created with the
// NewList method.
type List[T any] struct {
// sentinel is only used as a placeholder to avoid complex nil checks.
// sentinel.Value is never used.
sentinel ListElement[T]
length int
}

// NewList creates a new doubly linked list.
func NewList[T any]() *List[T] {
l := &List[T]{}
l.sentinel.next = &l.sentinel
l.sentinel.prev = &l.sentinel
l.sentinel.list = l
return l
}

// Len returns the number of elements in l.
func (l *List[_]) Len() int {
return l.length
}

// Front returns the element at the front of l.
// If l is empty, nil is returned.
func (l *List[T]) Front() *ListElement[T] {
if l.length == 0 {
return nil
}
return l.sentinel.next
}

// Back returns the element at the back of l.
// If l is empty, nil is returned.
func (l *List[T]) Back() *ListElement[T] {
if l.length == 0 {
return nil
}
return l.sentinel.prev
}

// Remove removes e from l if e is in l.
func (l *List[T]) Remove(e *ListElement[T]) {
if e.list != l {
return
}

e.prev.next = e.next
e.next.prev = e.prev
e.next = nil
e.prev = nil
e.list = nil
l.length--
}

// PushFront inserts e at the front of l.
// If e is already in a list, l is not modified.
func (l *List[T]) PushFront(e *ListElement[T]) {
l.insertAfter(e, &l.sentinel)
}

// PushBack inserts e at the back of l.
// If e is already in a list, l is not modified.
func (l *List[T]) PushBack(e *ListElement[T]) {
l.insertAfter(e, l.sentinel.prev)
}

// InsertBefore inserts e immediately before location.
// If e is already in a list, l is not modified.
// If location is not in l, l is not modified.
func (l *List[T]) InsertBefore(e *ListElement[T], location *ListElement[T]) {
if location.list == l {
l.insertAfter(e, location.prev)
}
}

// InsertAfter inserts e immediately after location.
// If e is already in a list, l is not modified.
// If location is not in l, l is not modified.
func (l *List[T]) InsertAfter(e *ListElement[T], location *ListElement[T]) {
if location.list == l {
l.insertAfter(e, location)
}
}

// MoveToFront moves e to the front of l.
// If e is not in l, l is not modified.
func (l *List[T]) MoveToFront(e *ListElement[T]) {
// If e is already at the front of l, there is nothing to do.
if e != l.sentinel.next {
l.moveAfter(e, &l.sentinel)
}
}

// MoveToBack moves e to the back of l.
// If e is not in l, l is not modified.
func (l *List[T]) MoveToBack(e *ListElement[T]) {
l.moveAfter(e, l.sentinel.prev)
}

// MoveBefore moves e immediately before location.
// If the elements are equal or not in l, the list is not modified.
func (l *List[T]) MoveBefore(e, location *ListElement[T]) {
// Don't introduce a cycle by moving an element before itself.
if e != location {
l.moveAfter(e, location.prev)
}
}

// MoveAfter moves e immediately after location.
// If the elements are equal or not in l, the list is not modified.
func (l *List[T]) MoveAfter(e, location *ListElement[T]) {
l.moveAfter(e, location)
}

func (l *List[T]) insertAfter(e, location *ListElement[T]) {
if e.list != nil {
// Don't insert an element that is already in a list
return
}

e.prev = location
e.next = location.next
e.prev.next = e
e.next.prev = e
e.list = l
l.length++
}

func (l *List[T]) moveAfter(e, location *ListElement[T]) {
if e.list != l || location.list != l || e == location {
// Don't modify an element that is in a different list.
// Don't introduce a cycle by moving an element after itself.
return
}

e.prev.next = e.next
e.next.prev = e.prev

e.prev = location
e.next = location.next
e.prev.next = e
e.next.prev = e
}

// PushFront inserts v into a new element at the front of l.
func PushFront[T any](l *List[T], v T) {
l.PushFront(&ListElement[T]{
Value: v,
})
}

// PushBack inserts v into a new element at the back of l.
func PushBack[T any](l *List[T], v T) {
l.PushBack(&ListElement[T]{
Value: v,
})
}

// InsertBefore inserts v into a new element immediately before location.
// If location is not in l, l is not modified.
func InsertBefore[T any](l *List[T], v T, location *ListElement[T]) {
l.InsertBefore(
&ListElement[T]{
Value: v,
},
location,
)
}

// InsertAfter inserts v into a new element immediately after location.
// If location is not in l, l is not modified.
func InsertAfter[T any](l *List[T], v T, location *ListElement[T]) {
l.InsertAfter(
&ListElement[T]{
Value: v,
},
location,
)
}
168 changes: 168 additions & 0 deletions utils/linked/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package linked

import (
"testing"

"github.com/stretchr/testify/require"
)

func flattenForwards[T any](l *List[T]) []T {
var s []T
for e := l.Front(); e != nil; e = e.Next() {
s = append(s, e.Value)
}
return s
}

func flattenBackwards[T any](l *List[T]) []T {
var s []T
for e := l.Back(); e != nil; e = e.Prev() {
s = append(s, e.Value)
}
return s
}

func TestList_Empty(t *testing.T) {
require := require.New(t)

l := NewList[int]()

require.Empty(flattenForwards(l))
require.Empty(flattenBackwards(l))
require.Zero(l.Len())
}

func TestList_PushBack(t *testing.T) {
require := require.New(t)

l := NewList[int]()

for i := 0; i < 5; i++ {
l.PushBack(&ListElement[int]{
Value: i,
})
}

require.Equal([]int{0, 1, 2, 3, 4}, flattenForwards(l))
require.Equal([]int{4, 3, 2, 1, 0}, flattenBackwards(l))
require.Equal(5, l.Len())
}

func TestList_PushBack_Duplicate(t *testing.T) {
require := require.New(t)

l := NewList[int]()

e := &ListElement[int]{
Value: 0,
}
l.PushBack(e)
l.PushBack(e)

require.Equal([]int{0}, flattenForwards(l))
require.Equal([]int{0}, flattenBackwards(l))
require.Equal(1, l.Len())
}

func TestList_PushFront(t *testing.T) {
require := require.New(t)

l := NewList[int]()

for i := 0; i < 5; i++ {
l.PushFront(&ListElement[int]{
Value: i,
})
}

require.Equal([]int{4, 3, 2, 1, 0}, flattenForwards(l))
require.Equal([]int{0, 1, 2, 3, 4}, flattenBackwards(l))
require.Equal(5, l.Len())
}

func TestList_PushFront_Duplicate(t *testing.T) {
require := require.New(t)

l := NewList[int]()

e := &ListElement[int]{
Value: 0,
}
l.PushFront(e)
l.PushFront(e)

require.Equal([]int{0}, flattenForwards(l))
require.Equal([]int{0}, flattenBackwards(l))
require.Equal(1, l.Len())
}

func TestList_Remove(t *testing.T) {
require := require.New(t)

l := NewList[int]()

e0 := &ListElement[int]{
Value: 0,
}
e1 := &ListElement[int]{
Value: 1,
}
e2 := &ListElement[int]{
Value: 2,
}
l.PushBack(e0)
l.PushBack(e1)
l.PushBack(e2)

l.Remove(e1)

require.Equal([]int{0, 2}, flattenForwards(l))
require.Equal([]int{2, 0}, flattenBackwards(l))
require.Equal(2, l.Len())
require.Nil(e1.next)
require.Nil(e1.prev)
require.Nil(e1.list)
}

func TestList_MoveToFront(t *testing.T) {
require := require.New(t)

l := NewList[int]()

e0 := &ListElement[int]{
Value: 0,
}
e1 := &ListElement[int]{
Value: 1,
}
l.PushFront(e0)
l.PushFront(e1)
l.MoveToFront(e0)

require.Equal([]int{0, 1}, flattenForwards(l))
require.Equal([]int{1, 0}, flattenBackwards(l))
require.Equal(2, l.Len())
}

func TestList_MoveToBack(t *testing.T) {
require := require.New(t)

l := NewList[int]()

e0 := &ListElement[int]{
Value: 0,
}
e1 := &ListElement[int]{
Value: 1,
}
l.PushFront(e0)
l.PushFront(e1)
l.MoveToBack(e1)

require.Equal([]int{0, 1}, flattenForwards(l))
require.Equal([]int{1, 0}, flattenBackwards(l))
require.Equal(2, l.Len())
}

0 comments on commit e1954bb

Please sign in to comment.