Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement generic linked.List #2908

Merged
merged 5 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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())
}
Loading