Skip to content
Snippets Groups Projects
Commit 07488f12 authored by Hans Moog's avatar Hans Moog
Browse files

Feat: added lru cache and doublylinkedlist

parent df301366
No related branches found
No related tags found
No related merge requests found
package datastructure
import (
"github.com/iotaledger/goshimmer/packages/errors"
"sync"
)
type DoublyLinkedList struct {
head *DoublyLinkedListEntry
tail *DoublyLinkedListEntry
count int
mutex sync.RWMutex
}
// region public methods with locking //////////////////////////////////////////////////////////////////////////////////
// Appends the specified value to the end of this list.
func (list *DoublyLinkedList) Add(value interface{}) *DoublyLinkedListEntry {
return list.AddLast(value)
}
// Appends the specified element to the end of this list.
func (list *DoublyLinkedList) AddEntry(entry *DoublyLinkedListEntry) {
list.AddLastEntry(entry)
}
func (list *DoublyLinkedList) AddLast(value interface{}) *DoublyLinkedListEntry {
newEntry := &DoublyLinkedListEntry{
value: value,
}
list.AddLastEntry(newEntry)
return newEntry
}
func (list *DoublyLinkedList) AddLastEntry(entry *DoublyLinkedListEntry) {
list.mutex.Lock()
defer list.mutex.Unlock()
list.addLastEntry(entry)
}
func (list *DoublyLinkedList) AddFirst(value interface{}) *DoublyLinkedListEntry {
newEntry := &DoublyLinkedListEntry{
value: value,
}
list.AddFirstEntry(newEntry)
return newEntry
}
func (list *DoublyLinkedList) AddFirstEntry(entry *DoublyLinkedListEntry) {
list.mutex.Lock()
defer list.mutex.Unlock()
list.addFirstEntry(entry)
}
func (list *DoublyLinkedList) Clear() {
list.mutex.Lock()
defer list.mutex.Unlock()
list.clear()
}
func (list *DoublyLinkedList) GetFirst() (interface{}, errors.IdentifiableError) {
if firstEntry, err := list.GetFirstEntry(); err != nil {
return nil, err
} else {
return firstEntry.GetValue(), nil
}
}
func (list *DoublyLinkedList) GetFirstEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) {
list.mutex.RLock()
defer list.mutex.RUnlock()
return list.getFirstEntry()
}
func (list *DoublyLinkedList) GetLast() (interface{}, errors.IdentifiableError) {
if lastEntry, err := list.GetLastEntry(); err != nil {
return nil, err
} else {
return lastEntry.GetValue(), nil
}
}
func (list *DoublyLinkedList) GetLastEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) {
list.mutex.RLock()
defer list.mutex.RUnlock()
return list.getLastEntry()
}
func (list *DoublyLinkedList) RemoveFirst() (interface{}, errors.IdentifiableError) {
if firstEntry, err := list.RemoveFirstEntry(); err != nil {
return nil, err
} else {
return firstEntry.GetValue(), nil
}
}
func (list *DoublyLinkedList) RemoveFirstEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) {
list.mutex.Lock()
defer list.mutex.Unlock()
return list.removeFirstEntry()
}
func (list *DoublyLinkedList) RemoveLast() (interface{}, errors.IdentifiableError) {
if lastEntry, err := list.RemoveLastEntry(); err != nil {
return nil, err
} else {
return lastEntry.GetValue(), nil
}
}
func (list *DoublyLinkedList) RemoveLastEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) {
list.mutex.Lock()
defer list.mutex.Unlock()
return list.removeLastEntry()
}
func (list *DoublyLinkedList) Remove(value interface{}) errors.IdentifiableError {
list.mutex.RLock()
currentEntry := list.head
for currentEntry != nil {
if currentEntry.GetValue() == value {
list.mutex.RUnlock()
if err := list.RemoveEntry(currentEntry); err != nil {
return err
}
return nil
}
currentEntry = currentEntry.GetNext()
}
list.mutex.RUnlock()
return ErrNoSuchElement.Derive("the entry is not part of the list")
}
func (list *DoublyLinkedList) RemoveEntry(entry *DoublyLinkedListEntry) errors.IdentifiableError {
list.mutex.Lock()
defer list.mutex.Unlock()
return list.removeEntry(entry)
}
func (list *DoublyLinkedList) GetSize() int {
list.mutex.RLock()
defer list.mutex.RUnlock()
return list.count
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// region private methods without locking //////////////////////////////////////////////////////////////////////////////
func (list *DoublyLinkedList) addLastEntry(entry *DoublyLinkedListEntry) {
if list.head == nil {
list.head = entry
} else {
list.tail.SetNext(entry)
entry.SetPrev(list.tail)
}
list.tail = entry
list.count++
}
func (list *DoublyLinkedList) addFirstEntry(entry *DoublyLinkedListEntry) {
if list.tail == nil {
list.tail = entry
} else {
list.head.SetPrev(entry)
entry.SetNext(list.head)
}
list.head = entry
list.count++
}
func (list *DoublyLinkedList) clear() {
list.head = nil
list.tail = nil
list.count = 0
}
func (list *DoublyLinkedList) getFirstEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) {
if list.head == nil {
return nil, ErrNoSuchElement.Derive("the list is empty")
}
return list.head, nil
}
func (list *DoublyLinkedList) getLastEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) {
if list.tail == nil {
return nil, ErrNoSuchElement.Derive("the list is empty")
}
return list.tail, nil
}
func (list *DoublyLinkedList) removeFirstEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) {
entryToRemove := list.head
if err := list.removeEntry(entryToRemove); err != nil {
return nil, err
}
return entryToRemove, nil
}
func (list *DoublyLinkedList) removeLastEntry() (*DoublyLinkedListEntry, errors.IdentifiableError) {
entryToRemove := list.tail
if err := list.removeEntry(entryToRemove); err != nil {
return nil, err
}
return entryToRemove, nil
}
func (list *DoublyLinkedList) removeEntry(entry *DoublyLinkedListEntry) errors.IdentifiableError {
if entry == nil {
return ErrInvalidArgument.Derive("the entry must not be nil")
}
if list.head == nil {
return ErrNoSuchElement.Derive("the entry is not part of the list")
}
nextEntry := entry.GetNext()
if nextEntry != nil {
nextEntry.SetPrev(entry.GetPrev())
}
if list.head == entry {
list.head = nextEntry
}
prevEntry := entry.GetPrev()
if prevEntry != nil {
prevEntry.SetNext(entry.GetNext())
}
if list.tail == entry {
list.tail = prevEntry
}
list.count--
return nil
}
// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
package datastructure
import (
"sync"
)
type DoublyLinkedListEntry struct {
value interface{}
prev *DoublyLinkedListEntry
next *DoublyLinkedListEntry
mutex sync.RWMutex
}
func (entry *DoublyLinkedListEntry) GetNext() *DoublyLinkedListEntry {
entry.mutex.RLock()
defer entry.mutex.RUnlock()
return entry.next
}
func (entry *DoublyLinkedListEntry) SetNext(next *DoublyLinkedListEntry) {
entry.mutex.Lock()
defer entry.mutex.Unlock()
entry.next = next
}
func (entry *DoublyLinkedListEntry) GetPrev() *DoublyLinkedListEntry {
entry.mutex.RLock()
defer entry.mutex.RUnlock()
return entry.prev
}
func (entry *DoublyLinkedListEntry) SetPrev(prev *DoublyLinkedListEntry) {
entry.mutex.Lock()
defer entry.mutex.Unlock()
entry.prev = prev
}
func (entry *DoublyLinkedListEntry) GetValue() interface{} {
entry.mutex.RLock()
defer entry.mutex.RUnlock()
return entry.value
}
func (entry *DoublyLinkedListEntry) SetValue(value interface{}) {
entry.mutex.Lock()
defer entry.mutex.Unlock()
entry.value = value
}
package datastructure
import (
"testing"
)
func TestAdd(t *testing.T) {
doublyLinkedList := &DoublyLinkedList{}
doublyLinkedList.Add(12)
doublyLinkedList.Add(12)
doublyLinkedList.Add(15)
doublyLinkedList.Add(99)
if doublyLinkedList.GetSize() != 4 {
t.Error("the size of the list is wrong")
}
}
func TestDelete(t *testing.T) {
doublyLinkedList := &DoublyLinkedList{}
doublyLinkedList.Add(12)
doublyLinkedList.Add(13)
doublyLinkedList.Add(15)
doublyLinkedList.Add(99)
if _, err := doublyLinkedList.RemoveFirst(); err != nil {
t.Error(err)
}
firstEntry, err := doublyLinkedList.GetFirst()
if err != nil {
t.Error(err)
}
if firstEntry != 13 {
t.Error("first entry should be 13 after delete")
}
if _, err := doublyLinkedList.RemoveLast(); err != nil {
t.Error(err)
}
lastEntry, err := doublyLinkedList.GetLast()
if err != nil {
t.Error(err)
}
if lastEntry != 15 {
t.Error("last entry should be 15 after delete")
}
if doublyLinkedList.GetSize() != 2 {
t.Error("the size of the list should be 2 after delete")
}
}
package datastructure
import (
"github.com/iotaledger/goshimmer/packages/errors"
)
var (
ErrNoSuchElement = errors.New("element does not exist")
ErrInvalidArgument = errors.New("invalid argument")
)
package datastructure
import (
"sync"
)
type lruCacheElement struct {
key interface{}
value interface{}
}
type LRUCache struct {
directory map[interface{}]*DoublyLinkedListEntry
doublyLinkedList *DoublyLinkedList
capacity int
size int
processingCallback bool
mutex sync.RWMutex
}
func NewLRUCache(capacity int) *LRUCache {
return &LRUCache{
directory: make(map[interface{}]*DoublyLinkedListEntry),
doublyLinkedList: &DoublyLinkedList{},
capacity: capacity,
}
}
func (cache *LRUCache) Set(key interface{}, value interface{}) {
if !cache.processingCallback {
cache.mutex.Lock()
defer cache.mutex.Unlock()
}
element, exists := cache.directory[key]
if exists {
if !cache.processingCallback {
element.GetValue().(*lruCacheElement).value = value
cache.doublyLinkedList.mutex.Lock()
defer cache.doublyLinkedList.mutex.Unlock()
} else {
element.value.(*lruCacheElement).value = value
}
cache.promoteElement(element)
} else {
cache.directory[key] = cache.doublyLinkedList.AddFirst(&lruCacheElement{key: key, value: value})
if cache.size == cache.capacity {
if element, err := cache.doublyLinkedList.RemoveLast(); err != nil {
panic(err)
} else {
delete(cache.directory, element.(*lruCacheElement).key)
}
} else {
cache.size++
}
}
}
func (cache *LRUCache) Contains(key interface{}, optionalCallback ... func(interface{}, bool)) bool {
var callback func(interface{}, bool)
if len(optionalCallback) >= 1 {
if !cache.processingCallback {
cache.mutex.Lock()
defer cache.mutex.Unlock()
}
callback = optionalCallback[0]
} else {
if !cache.processingCallback {
cache.mutex.RLock()
defer cache.mutex.RUnlock()
}
}
var elementValue interface{}
element, exists := cache.directory[key]
if exists {
cache.doublyLinkedList.mutex.Lock()
defer cache.doublyLinkedList.mutex.Unlock()
cache.promoteElement(element)
elementValue = element.GetValue().(*lruCacheElement).value
}
if callback != nil {
cache.processingCallback = true
callback(elementValue, exists)
cache.processingCallback = false
}
return exists
}
func (cache *LRUCache) GetCapacity() int {
cache.mutex.RLock()
defer cache.mutex.RUnlock()
return cache.capacity
}
func (cache *LRUCache) GetSize() int {
cache.mutex.RLock()
defer cache.mutex.RUnlock()
return cache.size
}
func (cache *LRUCache) Delete(key interface{}) bool {
if !cache.processingCallback {
cache.mutex.RLock()
}
entry, exists := cache.directory[key]
if exists {
if !cache.processingCallback {
cache.mutex.RUnlock()
cache.mutex.Lock()
defer cache.mutex.Unlock()
cache.doublyLinkedList.mutex.Lock()
defer cache.doublyLinkedList.mutex.Unlock()
}
if err := cache.doublyLinkedList.removeEntry(entry); err != nil {
panic(err)
}
delete(cache.directory, key)
cache.size--
return true
}
if !cache.processingCallback {
cache.mutex.RUnlock()
}
return false
}
func (cache *LRUCache) promoteElement(element *DoublyLinkedListEntry) {
if err := cache.doublyLinkedList.removeEntry(element); err != nil {
panic(err)
}
cache.doublyLinkedList.addFirstEntry(element)
}
package datastructure
import (
"testing"
)
func TestLRUCache(t *testing.T) {
cache := NewLRUCache(5)
cache.Contains("test", func(elem interface{}, contains bool) {
if !contains {
cache.Set("test", 12)
}
})
if !cache.Contains("test", func(elem interface{}, contains bool) {
if !contains || elem != 12 {
t.Error("the cache contains the wrong element")
}
}) {
t.Error("the cache does not contain the added elements")
}
if cache.GetSize() != 1 {
t.Error("the size should be 1")
}
if cache.GetCapacity() != 5 {
t.Error("the capacity should be 5")
}
cache.Set("a", 3)
cache.Set("b", 4)
cache.Set("c", 5)
cache.Set("d", 6)
if cache.GetSize() != 5 {
t.Error("the size should be 5")
}
cache.Set("e", 7)
if cache.GetSize() != 5 {
t.Error("the size should be 5")
}
if cache.Contains("test") {
t.Error("'test' should have been dropped")
}
cache.Set("a", 6)
cache.Set("f", 8)
if cache.GetSize() != 5 {
t.Error("the size should be 5")
}
if !cache.Contains("a") {
t.Error("'a' should not have been dropped")
}
if cache.Contains("b") {
t.Error("'b' should have been dropped")
}
cache.Contains("tust", func(elem interface{}, contains bool) {
if !contains {
cache.Set("tust", 1337)
}
})
if cache.GetSize() != 5 {
t.Error("the size should be 5")
}
cache.Contains("a", func(value interface{}, exists bool) {
if exists {
cache.Delete("a")
}
})
if cache.GetSize() != 4 {
t.Error("the size should be 4")
}
cache.Delete("f")
if cache.GetSize() != 3 {
t.Error("the size should be 3")
}
}
func BenchmarkLRUCache(b *testing.B) {
cache := NewLRUCache(10000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
cache.Contains(i, func(val interface{}, exists bool) {
if !exists {
cache.Set(i, i)
}
})
}
}
......@@ -20,7 +20,7 @@ func NewApprovers(hash ternary.Trinary) *Approvers {
}
}
// region public method with locking ///////////////////////////////////////////////////////////////////////////////////
// region public methods with locking //////////////////////////////////////////////////////////////////////////////////
func (approvers *Approvers) Add(transactionHash ternary.Trinary) {
approvers.hashesMutex.Lock()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment