Skip to content

Commit

Permalink
Implement all functionality for ringlist
Browse files Browse the repository at this point in the history
  • Loading branch information
mgnsk committed Apr 30, 2023
1 parent 680ba5c commit 20634d1
Show file tree
Hide file tree
Showing 2 changed files with 312 additions and 1 deletion.
100 changes: 99 additions & 1 deletion ringlist/ringlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
213 changes: 213 additions & 0 deletions ringlist/ringlist_test.go
Original file line number Diff line number Diff line change
@@ -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))
}
}

0 comments on commit 20634d1

Please sign in to comment.