From 20634d10907f48010dc0897d59db6480679ff634 Mon Sep 17 00:00:00 2001 From: Magnus Kokk Date: Sun, 30 Apr 2023 15:23:14 +0300 Subject: [PATCH] Implement all functionality for ringlist --- ringlist/ringlist.go | 100 +++++++++++++++++- ringlist/ringlist_test.go | 213 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 ringlist/ringlist_test.go diff --git a/ringlist/ringlist.go b/ringlist/ringlist.go index 5739b6b..f9d78ad 100644 --- a/ringlist/ringlist.go +++ b/ringlist/ringlist.go @@ -42,7 +42,7 @@ func (l *List[T, E]) Back() E { return l.tail } -// PushBack inserts a new element at the back of the list. +// PushBack inserts a new element at the back of list l. func (l *List[T, E]) PushBack(e E) { if l.tail != nil { l.tail.Link(e) @@ -51,6 +51,104 @@ func (l *List[T, E]) PushBack(e E) { l.len++ } +// PushFront inserts a new element at the front of list l. +func (l *List[T, E]) PushFront(e E) { + if l.tail != nil { + l.tail.Link(e) + } else { + l.tail = e + } + l.len++ +} + +// Do calls function f on each element of the list, in forward order. +// If f returns false, Do stops the iteration. +// f must not change l. +func (l *List[T, E]) Do(f func(e E) bool) { + e := l.Front() + if e == nil { + return + } + + if !f(e) { + return + } + + for p := e.Next(); p != e; p = p.Next() { + if !f(p) { + return + } + } +} + +// MoveAfter moves an element to its new position after mark. +func (l *List[T, E]) MoveAfter(e, mark E) { + l.Remove(e) + + mark.Link(e) + l.len++ + + if mark == l.tail { + l.tail = e + } +} + +// MoveBefore moves an element to its new position before mark. +func (l *List[T, E]) MoveBefore(e, mark E) { + l.Remove(e) + + mark.Prev().Link(e) + + l.len++ +} + +// MoveToFront moves the element to the front of list l. +func (l *List[T, E]) MoveToFront(e E) { + l.MoveBefore(e, l.Front()) +} + +// MoveToBack moves the element to the back of list l. +func (l *List[T, E]) MoveToBack(e E) { + l.MoveAfter(e, l.Back()) +} + +// Move moves element e forward or backwards by at most delta positions +// or until the element becomes the front or back element in the list. +func (l *List[T, E]) Move(e E, delta int) { + if l.tail == nil { + panic("ringlist: invalid element") + } + + if l.len == 1 && e != l.tail { + panic("ringlist: invalid element") + } + + mark := e + + switch { + case delta == 0: + return + + case delta > 0: + for i := 0; i < delta; i++ { + if mark = mark.Next(); mark == l.tail { + break + } + } + + l.MoveAfter(e, mark) + + case delta < 0: + for i := 0; i > delta; i-- { + if mark = mark.Prev(); mark == l.tail.Next() { + break + } + } + + l.MoveBefore(e, mark) + } +} + // Remove an element from the list. func (l *List[T, E]) Remove(e E) { if e == l.tail { diff --git a/ringlist/ringlist_test.go b/ringlist/ringlist_test.go new file mode 100644 index 0000000..d95a6ad --- /dev/null +++ b/ringlist/ringlist_test.go @@ -0,0 +1,213 @@ +package ringlist_test + +import ( + "testing" + + "github.com/mgnsk/evcache/v3/ringlist" + . "github.com/onsi/gomega" +) + +func TestPushFront(t *testing.T) { + var list ringlist.ElementList[int] + + g := NewWithT(t) + + list.PushFront(ringlist.NewElement(0)) + g.Expect(list.Len()).To(Equal(1)) + + list.PushFront(ringlist.NewElement(1)) + g.Expect(list.Len()).To(Equal(2)) + + expectValidRing(g, &list) +} + +func TestPushBack(t *testing.T) { + var list ringlist.ElementList[int] + + g := NewWithT(t) + + list.PushFront(ringlist.NewElement(0)) + g.Expect(list.Len()).To(Equal(1)) + + list.PushFront(ringlist.NewElement(1)) + g.Expect(list.Len()).To(Equal(2)) + + expectValidRing(g, &list) +} + +func TestMoveToFront(t *testing.T) { + t.Run("moving the back element", func(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.MoveToFront(list.Back()) + + expectValidRing(g, &list) + g.Expect(list.Front().Value).To(Equal("two")) + g.Expect(list.Back().Value).To(Equal("one")) + }) + + t.Run("moving the middle element", func(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.PushBack(ringlist.NewElement("three")) + list.MoveToFront(list.Front().Next()) + + expectValidRing(g, &list) + g.Expect(list.Front().Value).To(Equal("two")) + g.Expect(list.Back().Value).To(Equal("three")) + }) +} + +func TestMoveToBack(t *testing.T) { + t.Run("moving the front element", func(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.MoveToBack(list.Front()) + + expectValidRing(g, &list) + g.Expect(list.Front().Value).To(Equal("two")) + g.Expect(list.Back().Value).To(Equal("one")) + }) + + t.Run("moving the middle element", func(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.PushBack(ringlist.NewElement("three")) + list.MoveToBack(list.Front().Next()) + + expectValidRing(g, &list) + g.Expect(list.Front().Value).To(Equal("one")) + g.Expect(list.Back().Value).To(Equal("two")) + }) +} + +func TestMoveForward(t *testing.T) { + t.Run("overflow", func(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.Move(list.Front(), 3) + + expectValidRing(g, &list) + g.Expect(list.Front().Value).To(Equal("two")) + g.Expect(list.Back().Value).To(Equal("one")) + }) + + t.Run("not overflow", func(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.PushBack(ringlist.NewElement("three")) + list.Move(list.Front(), 1) + + expectValidRing(g, &list) + g.Expect(list.Front().Value).To(Equal("two")) + g.Expect(list.Front().Next().Value).To(Equal("one")) + g.Expect(list.Back().Value).To(Equal("three")) + }) +} + +func TestMoveBackwards(t *testing.T) { + t.Run("overflow", func(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.Move(list.Back(), -3) + + expectValidRing(g, &list) + g.Expect(list.Front().Value).To(Equal("two")) + g.Expect(list.Back().Value).To(Equal("one")) + }) + + t.Run("not overflow", func(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.PushBack(ringlist.NewElement("three")) + list.Move(list.Back(), -1) + + expectValidRing(g, &list) + g.Expect(list.Front().Value).To(Equal("one")) + g.Expect(list.Front().Next().Value).To(Equal("three")) + g.Expect(list.Back().Value).To(Equal("two")) + }) +} + +func TestDo(t *testing.T) { + var list ringlist.ElementList[string] + + g := NewWithT(t) + + list.PushBack(ringlist.NewElement("one")) + list.PushBack(ringlist.NewElement("two")) + list.PushBack(ringlist.NewElement("three")) + + g.Expect(list.Len()).To(Equal(3)) + expectValidRing(g, &list) + + var elems []string + list.Do(func(e *ringlist.Element[string]) bool { + elems = append(elems, e.Value) + return true + }) + + g.Expect(elems).To(Equal([]string{"one", "two", "three"})) +} + +func expectValidRing[T any](g *WithT, list *ringlist.ElementList[T]) { + g.Expect(list.Len()).To(BeNumerically(">", 0)) + g.Expect(list.Front()).To(Equal(list.Back().Next())) + g.Expect(list.Back()).To(Equal(list.Front().Prev())) + + { + expectedFront := list.Front() + + front := list.Front() + + for i := 0; i < list.Len(); i++ { + front = front.Next() + } + + g.Expect(front).To(Equal(expectedFront)) + } + + { + expectedBack := list.Back() + + back := list.Back() + + for i := 0; i < list.Len(); i++ { + back = back.Prev() + } + + g.Expect(back).To(Equal(expectedBack)) + } +}