Skip to content

Commit

Permalink
chore: refactor linked queues
Browse files Browse the repository at this point in the history
  • Loading branch information
maypok86 committed Sep 19, 2024
1 parent 9d4b3a4 commit 542e8af
Show file tree
Hide file tree
Showing 15 changed files with 343 additions and 491 deletions.
2 changes: 1 addition & 1 deletion cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import (
"time"

"github.com/maypok86/otter/v2/internal/clock"
"github.com/maypok86/otter/v2/internal/deque/queue"
"github.com/maypok86/otter/v2/internal/eviction"
"github.com/maypok86/otter/v2/internal/eviction/s3fifo"
"github.com/maypok86/otter/v2/internal/expiry"
"github.com/maypok86/otter/v2/internal/generated/node"
"github.com/maypok86/otter/v2/internal/hashmap"
"github.com/maypok86/otter/v2/internal/lossy"
"github.com/maypok86/otter/v2/internal/queue"
"github.com/maypok86/otter/v2/internal/xmath"
"github.com/maypok86/otter/v2/internal/xruntime"
)
Expand Down
157 changes: 157 additions & 0 deletions internal/deque/linked.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Copyright (c) 2024 Alexey Mayshev. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package deque

import (
"github.com/maypok86/otter/v2/internal/generated/node"
)

type Linked[K comparable, V any] struct {
head node.Node[K, V]
tail node.Node[K, V]
len int
isExp bool
}

func NewLinked[K comparable, V any](isExp bool) *Linked[K, V] {
return &Linked[K, V]{
isExp: isExp,
}
}

func (d *Linked[K, V]) PushBack(n node.Node[K, V]) {
if d.IsEmpty() {
d.head = n
d.tail = n
} else {
d.setPrev(n, d.tail)
d.setNext(d.tail, n)
d.tail = n
}

d.len++
}

func (d *Linked[K, V]) PushFront(n node.Node[K, V]) {
if d.IsEmpty() {
d.head = n
d.tail = n
} else {
d.setNext(n, d.head)
d.setPrev(d.head, n)
d.head = n
}

d.len++
}

func (d *Linked[K, V]) PopFront() node.Node[K, V] {
if d.IsEmpty() {
return nil
}

result := d.head
d.Delete(result)
return result
}

func (d *Linked[K, V]) PopBack() node.Node[K, V] {
if d.IsEmpty() {
return nil
}

result := d.tail
d.Delete(result)
return result
}

func (d *Linked[K, V]) Delete(n node.Node[K, V]) {
next := d.getNext(n)
prev := d.getPrev(n)

if node.Equals(prev, nil) {
if node.Equals(next, nil) && !node.Equals(d.head, n) {
return
}

d.head = next
} else {
d.setNext(prev, next)
d.setPrev(n, nil)
}

if node.Equals(next, nil) {
d.tail = prev
} else {
d.setPrev(next, prev)
d.setNext(n, nil)
}

d.len--
}

func (d *Linked[K, V]) Clear() {
for !d.IsEmpty() {
d.PopFront()
}
}

func (d *Linked[K, V]) Len() int {
return d.len
}

func (d *Linked[K, V]) IsEmpty() bool {
return d.Len() == 0
}

func (d *Linked[K, V]) Head() node.Node[K, V] {
return d.head
}

func (d *Linked[K, V]) Tail() node.Node[K, V] {
return d.tail
}

func (d *Linked[K, V]) setPrev(to, n node.Node[K, V]) {
if d.isExp {
to.SetPrevExp(n)
} else {
to.SetPrev(n)
}
}

func (d *Linked[K, V]) setNext(to, n node.Node[K, V]) {
if d.isExp {
to.SetNextExp(n)
} else {
to.SetNext(n)
}
}

func (d *Linked[K, V]) getNext(n node.Node[K, V]) node.Node[K, V] {
if d.isExp {
return n.NextExp()
} else {
return n.Next()
}
}

func (d *Linked[K, V]) getPrev(n node.Node[K, V]) node.Node[K, V] {
if d.isExp {
return n.PrevExp()
} else {
return n.Prev()
}
}
150 changes: 150 additions & 0 deletions internal/deque/linked_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (c) 2024 Alexey Mayshev. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
//
// Copyright notice. Initial version of the following tests was based on
// the following file from the Go Programming Language core repo:
// https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/container/list/list_test.go
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// That can be found at https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:LICENSE

package deque

import (
"strconv"
"testing"

"github.com/maypok86/otter/v2/internal/generated/node"
)

func checkLinkedLen[K comparable, V any](t *testing.T, d *Linked[K, V], length int) bool {
t.Helper()

if n := d.Len(); n != length {
t.Errorf("d.Len() = %d, want %d", n, length)
return false
}
return true
}

func checkLinkedPtrs[K comparable, V any](t *testing.T, d *Linked[K, V], nodes []node.Node[K, V]) {
t.Helper()

if !checkLinkedLen(t, d, len(nodes)) {
return
}

// zero length queues must be the zero value
if len(nodes) == 0 {
if !(node.Equals(d.head, nil) && node.Equals(d.tail, nil)) {
t.Errorf("d.head = %p, d.tail = %p; both should be nil", d.head, d.tail)
}
return
}

// check internal and external prev/next connections
for i, n := range nodes {
var prev node.Node[K, V]
if i > 0 {
prev = nodes[i-1]
}
if p := d.getPrev(n); !node.Equals(p, prev) {
t.Errorf("elt[%d](%p).prev = %p, want %p", i, n, p, prev)
}

var next node.Node[K, V]
if i < len(nodes)-1 {
next = nodes[i+1]
}
if nn := d.getNext(n); !node.Equals(nn, next) {
t.Errorf("nodes[%d](%p).next = %p, want %p", i, n, nn, next)
}
}
}

func newNode[K comparable](e K) node.Node[K, K] {
m := node.NewManager[K, K](node.Config{WithWeight: true, WithExpiration: true})
return m.Create(e, e, 0, 0)
}

func TestLinked(t *testing.T) {
d := NewLinked[string, string](false)
checkLinkedPtrs(t, d, []node.Node[string, string]{})

// Single element Linked
e := newNode("a")
d.PushBack(e)
checkLinkedPtrs(t, d, []node.Node[string, string]{e})
d.Delete(e)
d.PushBack(e)
checkLinkedPtrs(t, d, []node.Node[string, string]{e})
d.Delete(e)
checkLinkedPtrs(t, d, []node.Node[string, string]{})

// Bigger Linked
e2 := newNode("2")
e1 := newNode("1")
e3 := newNode("3")
e4 := newNode("4")
d.PushBack(e1)
d.PushBack(e2)
d.PushBack(e3)
d.PushBack(e4)
checkLinkedPtrs(t, d, []node.Node[string, string]{e1, e2, e3, e4})

d.Delete(e2)
checkLinkedPtrs(t, d, []node.Node[string, string]{e1, e3, e4})

// move from middle
d.Delete(e3)
d.PushBack(e3)
checkLinkedPtrs(t, d, []node.Node[string, string]{e1, e4, e3})

d.Clear()
d.PushBack(e3)
d.PushBack(e1)
d.PushBack(e4)
checkLinkedPtrs(t, d, []node.Node[string, string]{e3, e1, e4})

// should be no-op
d.Delete(e3)
d.PushBack(e3)
checkLinkedPtrs(t, d, []node.Node[string, string]{e1, e4, e3})

// Check standard iteration.
sum := 0
for e := d.head; !node.Equals(e, nil); e = d.getNext(e) {
i, err := strconv.Atoi(e.Value())
if err != nil {
continue
}
sum += i
}
if sum != 8 {
t.Errorf("sum over l = %d, want 8", sum)
}

// Clear all elements by iterating
var next node.Node[string, string]
for e := d.head; !node.Equals(e, nil); e = next {
next = d.getNext(e)
d.Delete(e)
}
checkLinkedPtrs(t, d, []node.Node[string, string]{})
}

func TestLinked_Delete(t *testing.T) {
d := NewLinked[int, int](true)

e1 := newNode(1)
e2 := newNode(2)
d.PushBack(e1)
d.PushBack(e2)
checkLinkedPtrs(t, d, []node.Node[int, int]{e1, e2})
e := d.head
d.Delete(e)
checkLinkedPtrs(t, d, []node.Node[int, int]{e2})
d.Delete(e)
checkLinkedPtrs(t, d, []node.Node[int, int]{e2})
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion internal/eviction/s3fifo/ghost.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (g *ghost[K, V]) insert(n node.Node[K, V]) {
return
}

maxLength := g.small.length() + g.main.length()
maxLength := g.small.len() + g.main.len()
if maxLength == 0 {
return
}
Expand Down
Loading

0 comments on commit 542e8af

Please sign in to comment.