Skip to content

Commit 2a703b0

Browse files
committed
add generic list type
1 parent 2249c63 commit 2a703b0

2 files changed

Lines changed: 204 additions & 0 deletions

File tree

utils/list/list.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package list
2+
3+
type Hook[T any] struct {
4+
next *T
5+
prev *T
6+
}
7+
8+
func (h *Hook[T]) getListHook() *Hook[T] {
9+
return h
10+
}
11+
12+
func (h *Hook[T]) Next() *T {
13+
if h == nil {
14+
return nil
15+
}
16+
return h.next
17+
}
18+
19+
func (h *Hook[T]) Prev() *T {
20+
if h == nil {
21+
return nil
22+
}
23+
return h.prev
24+
}
25+
26+
type hookAccessor[T any] interface {
27+
getListHook() *Hook[T]
28+
}
29+
30+
type Hooked[T any] interface {
31+
*T
32+
hookAccessor[T]
33+
}
34+
35+
type List[T any, P Hooked[T]] struct {
36+
head P
37+
tail P
38+
}
39+
40+
func (l *List[T, P]) Empty() bool {
41+
return l.head == nil
42+
}
43+
44+
func (l *List[T, P]) Front() P {
45+
return l.head
46+
}
47+
48+
func (l *List[T, P]) Back() P {
49+
return l.tail
50+
}
51+
52+
func (l *List[T, P]) PushFront(it P) {
53+
l.insert(it, nil, l.head)
54+
}
55+
56+
func (l *List[T, P]) PushBack(it P) {
57+
l.insert(it, l.tail, nil)
58+
}
59+
60+
func (l *List[T, P]) InsertBefore(it, mark P) {
61+
l.insert(it, mark.getListHook().prev, mark)
62+
}
63+
64+
func (l *List[T, P]) InsertAfter(it, mark P) {
65+
l.insert(it, mark, mark.getListHook().next)
66+
}
67+
68+
func (l *List[T, P]) MoveToFront(it P) {
69+
if l.head == it {
70+
return
71+
}
72+
h := it.getListHook()
73+
l.unlink(h)
74+
l.link(it, h, nil, l.head)
75+
}
76+
77+
func (l *List[T, P]) MoveToBack(it P) {
78+
if l.tail == it {
79+
return
80+
}
81+
h := it.getListHook()
82+
l.unlink(h)
83+
l.link(it, h, l.tail, nil)
84+
}
85+
86+
func (l *List[T, P]) Remove(it P) {
87+
h := it.getListHook()
88+
l.unlink(h)
89+
h.next = nil
90+
h.prev = nil
91+
}
92+
93+
func (l *List[T, P]) insert(it, prev, next P) {
94+
l.link(it, it.getListHook(), prev, next)
95+
}
96+
97+
func (l *List[T, P]) link(it P, h *Hook[T], prev, next P) {
98+
h.prev = prev
99+
h.next = next
100+
101+
if prev != nil {
102+
prev.getListHook().next = it
103+
} else {
104+
l.head = it
105+
}
106+
if next != nil {
107+
next.getListHook().prev = it
108+
} else {
109+
l.tail = it
110+
}
111+
}
112+
113+
func (l *List[T, P]) unlink(h *Hook[T]) {
114+
if h.prev != nil {
115+
P(h.prev).getListHook().next = h.next
116+
} else {
117+
l.head = h.next
118+
}
119+
if h.next != nil {
120+
P(h.next).getListHook().prev = h.prev
121+
} else {
122+
l.tail = h.prev
123+
}
124+
}

utils/list/list_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package list
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
type testItem struct {
10+
Hook[testItem]
11+
value string
12+
}
13+
14+
type testList = List[testItem, *testItem]
15+
16+
func collectValues(l *testList) []string {
17+
var out []string
18+
for it := l.Front(); it != nil; it = it.Next() {
19+
out = append(out, it.value)
20+
}
21+
return out
22+
}
23+
24+
func TestTLZeroValue(t *testing.T) {
25+
var l testList
26+
require.True(t, l.Empty())
27+
require.Nil(t, l.Front())
28+
require.Nil(t, l.Back())
29+
}
30+
31+
func TestTLPushFrontBackAndRemove(t *testing.T) {
32+
var l testList
33+
a := &testItem{value: "a"}
34+
b := &testItem{value: "b"}
35+
c := &testItem{value: "c"}
36+
37+
l.PushFront(b)
38+
l.PushFront(a)
39+
l.PushBack(c)
40+
41+
require.Equal(t, []string{"a", "b", "c"}, collectValues(&l))
42+
require.Same(t, a, l.Front())
43+
require.Same(t, c, l.Back())
44+
require.Same(t, b, a.Next())
45+
require.Same(t, b, c.Prev())
46+
47+
l.Remove(b)
48+
require.Equal(t, []string{"a", "c"}, collectValues(&l))
49+
require.Nil(t, b.Next())
50+
require.Nil(t, b.Prev())
51+
52+
l.Remove(a)
53+
l.Remove(c)
54+
require.True(t, l.Empty())
55+
require.Nil(t, l.Front())
56+
require.Nil(t, l.Back())
57+
}
58+
59+
func TestTLInsertAndMove(t *testing.T) {
60+
var l testList
61+
a := &testItem{value: "a"}
62+
b := &testItem{value: "b"}
63+
c := &testItem{value: "c"}
64+
d := &testItem{value: "d"}
65+
66+
l.PushBack(a)
67+
l.PushBack(c)
68+
l.InsertAfter(b, a)
69+
l.InsertBefore(d, a)
70+
71+
require.Equal(t, []string{"d", "a", "b", "c"}, collectValues(&l))
72+
73+
l.MoveToFront(c)
74+
require.Equal(t, []string{"c", "d", "a", "b"}, collectValues(&l))
75+
76+
l.MoveToBack(d)
77+
require.Equal(t, []string{"c", "a", "b", "d"}, collectValues(&l))
78+
require.Same(t, c, l.Front())
79+
require.Same(t, d, l.Back())
80+
}

0 commit comments

Comments
 (0)