Skip to content

Commit e1954bb

Browse files
Implement generic linked.List (#2908)
Co-authored-by: dhrubabasu <7675102+dhrubabasu@users.noreply.github.com>
1 parent f786a24 commit e1954bb

File tree

2 files changed

+385
-0
lines changed

2 files changed

+385
-0
lines changed

utils/linked/list.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package linked
5+
6+
// ListElement is an element of a linked list.
7+
type ListElement[T any] struct {
8+
next, prev *ListElement[T]
9+
list *List[T]
10+
Value T
11+
}
12+
13+
// Next returns the next element or nil.
14+
func (e *ListElement[T]) Next() *ListElement[T] {
15+
if p := e.next; e.list != nil && p != &e.list.sentinel {
16+
return p
17+
}
18+
return nil
19+
}
20+
21+
// Prev returns the previous element or nil.
22+
func (e *ListElement[T]) Prev() *ListElement[T] {
23+
if p := e.prev; e.list != nil && p != &e.list.sentinel {
24+
return p
25+
}
26+
return nil
27+
}
28+
29+
// List implements a doubly linked list with a sentinel node.
30+
//
31+
// See: https://en.wikipedia.org/wiki/Doubly_linked_list
32+
//
33+
// This datastructure is designed to be an almost complete drop-in replacement
34+
// for the standard library's "container/list".
35+
//
36+
// The primary design change is to remove all memory allocations from the list
37+
// definition. This allows these lists to be used in performance critical paths.
38+
// Additionally the zero value is not useful. Lists must be created with the
39+
// NewList method.
40+
type List[T any] struct {
41+
// sentinel is only used as a placeholder to avoid complex nil checks.
42+
// sentinel.Value is never used.
43+
sentinel ListElement[T]
44+
length int
45+
}
46+
47+
// NewList creates a new doubly linked list.
48+
func NewList[T any]() *List[T] {
49+
l := &List[T]{}
50+
l.sentinel.next = &l.sentinel
51+
l.sentinel.prev = &l.sentinel
52+
l.sentinel.list = l
53+
return l
54+
}
55+
56+
// Len returns the number of elements in l.
57+
func (l *List[_]) Len() int {
58+
return l.length
59+
}
60+
61+
// Front returns the element at the front of l.
62+
// If l is empty, nil is returned.
63+
func (l *List[T]) Front() *ListElement[T] {
64+
if l.length == 0 {
65+
return nil
66+
}
67+
return l.sentinel.next
68+
}
69+
70+
// Back returns the element at the back of l.
71+
// If l is empty, nil is returned.
72+
func (l *List[T]) Back() *ListElement[T] {
73+
if l.length == 0 {
74+
return nil
75+
}
76+
return l.sentinel.prev
77+
}
78+
79+
// Remove removes e from l if e is in l.
80+
func (l *List[T]) Remove(e *ListElement[T]) {
81+
if e.list != l {
82+
return
83+
}
84+
85+
e.prev.next = e.next
86+
e.next.prev = e.prev
87+
e.next = nil
88+
e.prev = nil
89+
e.list = nil
90+
l.length--
91+
}
92+
93+
// PushFront inserts e at the front of l.
94+
// If e is already in a list, l is not modified.
95+
func (l *List[T]) PushFront(e *ListElement[T]) {
96+
l.insertAfter(e, &l.sentinel)
97+
}
98+
99+
// PushBack inserts e at the back of l.
100+
// If e is already in a list, l is not modified.
101+
func (l *List[T]) PushBack(e *ListElement[T]) {
102+
l.insertAfter(e, l.sentinel.prev)
103+
}
104+
105+
// InsertBefore inserts e immediately before location.
106+
// If e is already in a list, l is not modified.
107+
// If location is not in l, l is not modified.
108+
func (l *List[T]) InsertBefore(e *ListElement[T], location *ListElement[T]) {
109+
if location.list == l {
110+
l.insertAfter(e, location.prev)
111+
}
112+
}
113+
114+
// InsertAfter inserts e immediately after location.
115+
// If e is already in a list, l is not modified.
116+
// If location is not in l, l is not modified.
117+
func (l *List[T]) InsertAfter(e *ListElement[T], location *ListElement[T]) {
118+
if location.list == l {
119+
l.insertAfter(e, location)
120+
}
121+
}
122+
123+
// MoveToFront moves e to the front of l.
124+
// If e is not in l, l is not modified.
125+
func (l *List[T]) MoveToFront(e *ListElement[T]) {
126+
// If e is already at the front of l, there is nothing to do.
127+
if e != l.sentinel.next {
128+
l.moveAfter(e, &l.sentinel)
129+
}
130+
}
131+
132+
// MoveToBack moves e to the back of l.
133+
// If e is not in l, l is not modified.
134+
func (l *List[T]) MoveToBack(e *ListElement[T]) {
135+
l.moveAfter(e, l.sentinel.prev)
136+
}
137+
138+
// MoveBefore moves e immediately before location.
139+
// If the elements are equal or not in l, the list is not modified.
140+
func (l *List[T]) MoveBefore(e, location *ListElement[T]) {
141+
// Don't introduce a cycle by moving an element before itself.
142+
if e != location {
143+
l.moveAfter(e, location.prev)
144+
}
145+
}
146+
147+
// MoveAfter moves e immediately after location.
148+
// If the elements are equal or not in l, the list is not modified.
149+
func (l *List[T]) MoveAfter(e, location *ListElement[T]) {
150+
l.moveAfter(e, location)
151+
}
152+
153+
func (l *List[T]) insertAfter(e, location *ListElement[T]) {
154+
if e.list != nil {
155+
// Don't insert an element that is already in a list
156+
return
157+
}
158+
159+
e.prev = location
160+
e.next = location.next
161+
e.prev.next = e
162+
e.next.prev = e
163+
e.list = l
164+
l.length++
165+
}
166+
167+
func (l *List[T]) moveAfter(e, location *ListElement[T]) {
168+
if e.list != l || location.list != l || e == location {
169+
// Don't modify an element that is in a different list.
170+
// Don't introduce a cycle by moving an element after itself.
171+
return
172+
}
173+
174+
e.prev.next = e.next
175+
e.next.prev = e.prev
176+
177+
e.prev = location
178+
e.next = location.next
179+
e.prev.next = e
180+
e.next.prev = e
181+
}
182+
183+
// PushFront inserts v into a new element at the front of l.
184+
func PushFront[T any](l *List[T], v T) {
185+
l.PushFront(&ListElement[T]{
186+
Value: v,
187+
})
188+
}
189+
190+
// PushBack inserts v into a new element at the back of l.
191+
func PushBack[T any](l *List[T], v T) {
192+
l.PushBack(&ListElement[T]{
193+
Value: v,
194+
})
195+
}
196+
197+
// InsertBefore inserts v into a new element immediately before location.
198+
// If location is not in l, l is not modified.
199+
func InsertBefore[T any](l *List[T], v T, location *ListElement[T]) {
200+
l.InsertBefore(
201+
&ListElement[T]{
202+
Value: v,
203+
},
204+
location,
205+
)
206+
}
207+
208+
// InsertAfter inserts v into a new element immediately after location.
209+
// If location is not in l, l is not modified.
210+
func InsertAfter[T any](l *List[T], v T, location *ListElement[T]) {
211+
l.InsertAfter(
212+
&ListElement[T]{
213+
Value: v,
214+
},
215+
location,
216+
)
217+
}

utils/linked/list_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package linked
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func flattenForwards[T any](l *List[T]) []T {
13+
var s []T
14+
for e := l.Front(); e != nil; e = e.Next() {
15+
s = append(s, e.Value)
16+
}
17+
return s
18+
}
19+
20+
func flattenBackwards[T any](l *List[T]) []T {
21+
var s []T
22+
for e := l.Back(); e != nil; e = e.Prev() {
23+
s = append(s, e.Value)
24+
}
25+
return s
26+
}
27+
28+
func TestList_Empty(t *testing.T) {
29+
require := require.New(t)
30+
31+
l := NewList[int]()
32+
33+
require.Empty(flattenForwards(l))
34+
require.Empty(flattenBackwards(l))
35+
require.Zero(l.Len())
36+
}
37+
38+
func TestList_PushBack(t *testing.T) {
39+
require := require.New(t)
40+
41+
l := NewList[int]()
42+
43+
for i := 0; i < 5; i++ {
44+
l.PushBack(&ListElement[int]{
45+
Value: i,
46+
})
47+
}
48+
49+
require.Equal([]int{0, 1, 2, 3, 4}, flattenForwards(l))
50+
require.Equal([]int{4, 3, 2, 1, 0}, flattenBackwards(l))
51+
require.Equal(5, l.Len())
52+
}
53+
54+
func TestList_PushBack_Duplicate(t *testing.T) {
55+
require := require.New(t)
56+
57+
l := NewList[int]()
58+
59+
e := &ListElement[int]{
60+
Value: 0,
61+
}
62+
l.PushBack(e)
63+
l.PushBack(e)
64+
65+
require.Equal([]int{0}, flattenForwards(l))
66+
require.Equal([]int{0}, flattenBackwards(l))
67+
require.Equal(1, l.Len())
68+
}
69+
70+
func TestList_PushFront(t *testing.T) {
71+
require := require.New(t)
72+
73+
l := NewList[int]()
74+
75+
for i := 0; i < 5; i++ {
76+
l.PushFront(&ListElement[int]{
77+
Value: i,
78+
})
79+
}
80+
81+
require.Equal([]int{4, 3, 2, 1, 0}, flattenForwards(l))
82+
require.Equal([]int{0, 1, 2, 3, 4}, flattenBackwards(l))
83+
require.Equal(5, l.Len())
84+
}
85+
86+
func TestList_PushFront_Duplicate(t *testing.T) {
87+
require := require.New(t)
88+
89+
l := NewList[int]()
90+
91+
e := &ListElement[int]{
92+
Value: 0,
93+
}
94+
l.PushFront(e)
95+
l.PushFront(e)
96+
97+
require.Equal([]int{0}, flattenForwards(l))
98+
require.Equal([]int{0}, flattenBackwards(l))
99+
require.Equal(1, l.Len())
100+
}
101+
102+
func TestList_Remove(t *testing.T) {
103+
require := require.New(t)
104+
105+
l := NewList[int]()
106+
107+
e0 := &ListElement[int]{
108+
Value: 0,
109+
}
110+
e1 := &ListElement[int]{
111+
Value: 1,
112+
}
113+
e2 := &ListElement[int]{
114+
Value: 2,
115+
}
116+
l.PushBack(e0)
117+
l.PushBack(e1)
118+
l.PushBack(e2)
119+
120+
l.Remove(e1)
121+
122+
require.Equal([]int{0, 2}, flattenForwards(l))
123+
require.Equal([]int{2, 0}, flattenBackwards(l))
124+
require.Equal(2, l.Len())
125+
require.Nil(e1.next)
126+
require.Nil(e1.prev)
127+
require.Nil(e1.list)
128+
}
129+
130+
func TestList_MoveToFront(t *testing.T) {
131+
require := require.New(t)
132+
133+
l := NewList[int]()
134+
135+
e0 := &ListElement[int]{
136+
Value: 0,
137+
}
138+
e1 := &ListElement[int]{
139+
Value: 1,
140+
}
141+
l.PushFront(e0)
142+
l.PushFront(e1)
143+
l.MoveToFront(e0)
144+
145+
require.Equal([]int{0, 1}, flattenForwards(l))
146+
require.Equal([]int{1, 0}, flattenBackwards(l))
147+
require.Equal(2, l.Len())
148+
}
149+
150+
func TestList_MoveToBack(t *testing.T) {
151+
require := require.New(t)
152+
153+
l := NewList[int]()
154+
155+
e0 := &ListElement[int]{
156+
Value: 0,
157+
}
158+
e1 := &ListElement[int]{
159+
Value: 1,
160+
}
161+
l.PushFront(e0)
162+
l.PushFront(e1)
163+
l.MoveToBack(e1)
164+
165+
require.Equal([]int{0, 1}, flattenForwards(l))
166+
require.Equal([]int{1, 0}, flattenBackwards(l))
167+
require.Equal(2, l.Len())
168+
}

0 commit comments

Comments
 (0)