Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: speed up LinkedHashMap Remove() function from O(n) to O(1) #264

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, " ") + "]"

}