From e1954bbc4479de99e999b616e750c69bbb2393bc Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 3 Apr 2024 17:27:29 -0400 Subject: [PATCH] Implement generic `linked.List` (#2908) Co-authored-by: dhrubabasu <7675102+dhrubabasu@users.noreply.github.com> --- utils/linked/list.go | 217 ++++++++++++++++++++++++++++++++++++++ utils/linked/list_test.go | 168 +++++++++++++++++++++++++++++ 2 files changed, 385 insertions(+) create mode 100644 utils/linked/list.go create mode 100644 utils/linked/list_test.go diff --git a/utils/linked/list.go b/utils/linked/list.go new file mode 100644 index 000000000000..4a7f3eb0a421 --- /dev/null +++ b/utils/linked/list.go @@ -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, + ) +} diff --git a/utils/linked/list_test.go b/utils/linked/list_test.go new file mode 100644 index 000000000000..9618ccb379d7 --- /dev/null +++ b/utils/linked/list_test.go @@ -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()) +}