Skip to content

Commit

Permalink
perf: speed-up linkedhashmap
Browse files Browse the repository at this point in the history
  • Loading branch information
ErenDursun committed Aug 18, 2024
1 parent 8323d02 commit 2f00422
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 32 deletions.
64 changes: 50 additions & 14 deletions maps/linkedhashmap/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ package linkedhashmap

import (
"github.com/emirpasic/gods/v2/containers"
"github.com/emirpasic/gods/v2/lists/doublylinkedlist"
)

// Assert Iterator implementation
var _ containers.ReverseIteratorWithKey[string, int] = (*Iterator[string, int])(nil)

// Iterator holding the iterator's state
type Iterator[K comparable, V any] struct {
iterator doublylinkedlist.Iterator[K]
table map[K]V
m *Map[K, V]
index int
element *element[K, V]
}

// Iterator returns a stateful iterator whose elements are key/value pairs.
func (m *Map[K, V]) Iterator() *Iterator[K, V] {
return &Iterator[K, V]{
iterator: m.ordering.Iterator(),
table: m.table,
m: m,
index: -1,
}
}

Expand All @@ -31,53 +31,84 @@ func (m *Map[K, V]) Iterator() *Iterator[K, V] {
// If Next() was called for the first time, then it will point the iterator to the first element if it exists.
// Modifies the state of the iterator.
func (iterator *Iterator[K, V]) Next() bool {
return iterator.iterator.Next()
if iterator.index < iterator.m.Size() {
iterator.index++
}
if !iterator.m.withinRange(iterator.index) {
iterator.element = nil
return false
}
if iterator.index != 0 {
iterator.element = iterator.element.next
} else {
iterator.element = iterator.m.first
}
return true
}

// Prev moves the iterator to the previous element and returns true if there was a previous element in the container.
// If Prev() returns true, then previous element's key and value can be retrieved by Key() and Value().
// Modifies the state of the iterator.
func (iterator *Iterator[K, V]) Prev() bool {
return iterator.iterator.Prev()
if iterator.index >= 0 {
iterator.index--
}
if !iterator.m.withinRange(iterator.index) {
iterator.element = nil
return false
}
if iterator.index == iterator.m.Size()-1 {
iterator.element = iterator.m.last
} else {
iterator.element = iterator.element.prev
}
return iterator.m.withinRange(iterator.index)
}

// Value returns the current element's value.
// Does not modify the state of the iterator.
func (iterator *Iterator[K, V]) Value() V {
key := iterator.iterator.Value()
return iterator.table[key]
if iterator.element != nil {
return iterator.element.value
}
var v V
return v
}

// Key returns the current element's key.
// Does not modify the state of the iterator.
func (iterator *Iterator[K, V]) Key() K {
return iterator.iterator.Value()
return iterator.element.key
}

// Begin resets the iterator to its initial state (one-before-first)
// Call Next() to fetch the first element if any.
func (iterator *Iterator[K, V]) Begin() {
iterator.iterator.Begin()
iterator.index = -1
iterator.element = nil
}

// End moves the iterator past the last element (one-past-the-end).
// Call Prev() to fetch the last element if any.
func (iterator *Iterator[K, V]) End() {
iterator.iterator.End()
iterator.index = iterator.m.Size()
iterator.element = nil
}

// First moves the iterator to the first element and returns true if there was a first element in the container.
// If First() returns true, then first element's key and value can be retrieved by Key() and Value().
// Modifies the state of the iterator
func (iterator *Iterator[K, V]) First() bool {
return iterator.iterator.First()
iterator.Begin()
return iterator.Next()
}

// Last moves the iterator to the last element and returns true if there was a last element in the container.
// If Last() returns true, then last element's key and value can be retrieved by Key() and Value().
// Modifies the state of the iterator.
func (iterator *Iterator[K, V]) Last() bool {
return iterator.iterator.Last()
iterator.End()
return iterator.Prev()
}

// NextTo moves the iterator to the next element from current position that satisfies the condition given by the
Expand Down Expand Up @@ -107,3 +138,8 @@ func (iterator *Iterator[K, V]) PrevTo(f func(key K, value V) bool) bool {
}
return false
}

// Check that the index is within bounds of the list
func (m *Map[K, V]) withinRange(index int) bool {
return index >= 0 && index < m.Size()
}
78 changes: 60 additions & 18 deletions maps/linkedhashmap/linkedhashmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"fmt"
"strings"

"github.com/emirpasic/gods/v2/lists/doublylinkedlist"
"github.com/emirpasic/gods/v2/maps"
)

Expand All @@ -24,42 +23,78 @@ var _ maps.Map[string, int] = (*Map[string, int])(nil)

// Map holds the elements in a regular hash table, and uses doubly-linked list to store key ordering.
type Map[K comparable, V any] struct {
table map[K]V
ordering *doublylinkedlist.List[K]
table map[K]*element[K, V]
first *element[K, V]
last *element[K, V]
}

type element[K comparable, V any] struct {
key K
value V
prev *element[K, V]
next *element[K, V]
}

// New instantiates a linked-hash-map.
func New[K comparable, V any]() *Map[K, V] {
return &Map[K, V]{
table: make(map[K]V),
ordering: doublylinkedlist.New[K](),
table: make(map[K]*element[K, V]),
}
}

// Put inserts key-value pair into the map.
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (m *Map[K, V]) Put(key K, value V) {
if _, contains := m.table[key]; !contains {
m.ordering.Append(key)
if el, contains := m.table[key]; contains {
el.value = value
} else {
e := &element[K, V]{
key: key,
value: value,
prev: m.last,
}

if m.Size() == 0 {
m.first = e
m.last = e
} else {
m.last.next = e
m.last = e
}
m.table[key] = e
}
m.table[key] = value
}

// Get searches the element in the map by key and returns its value or nil if key is not found in tree.
// Second return parameter is true if key was found, otherwise false.
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (m *Map[K, V]) Get(key K) (value V, found bool) {
value, found = m.table[key]
return value, found
element := m.table[key]
if element != nil {
found = true
value = element.value
}
return
}

// Remove removes the element from the map by key.
// Key should adhere to the comparator's type assertion, otherwise method panics.
func (m *Map[K, V]) Remove(key K) {
if _, contains := m.table[key]; contains {
if element, contains := m.table[key]; contains {
if element == m.first {
m.first = element.next
}
if element == m.last {
m.last = element.prev
}
if element.prev != nil {
element.prev.next = element.next
}
if element.next != nil {
element.next.prev = element.prev
}
element = nil
delete(m.table, key)
index := m.ordering.IndexOf(key)
m.ordering.Remove(index)
}
}

Expand All @@ -70,12 +105,19 @@ func (m *Map[K, V]) Empty() bool {

// Size returns number of elements in the map.
func (m *Map[K, V]) Size() int {
return m.ordering.Size()
return len(m.table)
}

// Keys returns all keys in-order
func (m *Map[K, V]) Keys() []K {
return m.ordering.Values()
keys := make([]K, m.Size())
count := 0
it := m.Iterator()
for it.Next() {
keys[count] = it.Key()
count++
}
return keys
}

// Values returns all values in-order based on the key.
Expand All @@ -92,8 +134,9 @@ func (m *Map[K, V]) Values() []V {

// Clear removes all elements from the map.
func (m *Map[K, V]) Clear() {
clear(m.table)
m.ordering.Clear()
m.table = make(map[K]*element[K, V])
m.first = nil
m.last = nil
}

// String returns a string representation of container
Expand All @@ -104,5 +147,4 @@ func (m *Map[K, V]) String() string {
str += fmt.Sprintf("%v:%v ", it.Key(), it.Value())
}
return strings.TrimRight(str, " ") + "]"

}

0 comments on commit 2f00422

Please sign in to comment.