diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5987222..132b3aa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,7 +5,7 @@ on: pull_request: jobs: - unit-tests: + unit-tests-v2: strategy: matrix: go: @@ -19,3 +19,12 @@ jobs: go-version: ${{ matrix.go }} - run: go test -v ./... - run: cd v2 && go test -v ./... + + unit-tests-v3: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: '^1.23' + - run: cd v3 && go test -v ./... diff --git a/README.md b/README.md index f6a2a0b..708dde5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🔃 github.com/elliotchance/orderedmap/v2 [![GoDoc](https://godoc.org/github.com/elliotchance/orderedmap/v2?status.svg)](https://godoc.org/github.com/elliotchance/orderedmap/v2) +# 🔃 github.com/elliotchance/orderedmap/v3 [![GoDoc](https://godoc.org/github.com/elliotchance/orderedmap/v3?status.svg)](https://godoc.org/github.com/elliotchance/orderedmap/v3) ## Basic Usage @@ -6,7 +6,7 @@ An `*OrderedMap` is a high performance ordered map that maintains amortized O(1) for `Set`, `Get`, `Delete` and `Len`: ```go -import "github.com/elliotchance/orderedmap/v2" +import "github.com/elliotchance/orderedmap/v3" func main() { m := orderedmap.NewOrderedMap[string, any]() @@ -19,8 +19,10 @@ func main() { } ``` -*Note: v2 requires Go v1.18 for generics.* If you need to support Go 1.17 or -below, you can use v1. +> [!NOTE] +> +> - _v3 requires Go v1.23_ - If you need to support Go 1.18-1.22, you can use v2. +> - _v2 requires Go v1.18 for generics_ - If you need to support Go 1.17 or below, you can use v1. Internally an `*OrderedMap` uses the composite type [map](https://go.dev/blog/maps) combined with a @@ -28,17 +30,44 @@ trimmed down linked list to maintain the order. ## Iterating -Be careful using `Keys()` as it will create a copy of all of the keys so it's -only suitable for a small number of items: +The following methods all return +[iterators](https://go.dev/doc/go1.23#iterators) that can be used to loop over +elements in an ordered map: + +- `AllFromFront()` +- `AllFromBack()` +- `Keys()` +- `Values()` ```go -for _, key := range m.Keys() { - value, _:= m.Get(key) +// Iterate through all elements from oldest to newest: +for key, value := range m.AllFromFront() { fmt.Println(key, value) } ``` -For larger maps you should use `Front()` or `Back()` to iterate per element: +Iterators are safe to use bidirectionally, and will return `nil` once it goes +beyond the first or last item. If the map is changing while the iteration is +in-flight it may produce unexpected behavior. + +If you want to get a slice of the map keys or values, you can use the standard +`slices.Collect` method with the iterator returned from `Keys()` or `Values()`: + +```go +fmt.Println(slices.Collect(m.Keys()) +// [A B C] +``` + +Likewise, calling `maps.Collect` on the iterator returned from `AllFromFront()` +will create a regular unordered map from the ordered one: + +```go +fmt.Println(maps.Collect(m.AllFromFront()) +// [A:1 B:2 C:3] +``` + +If you don't want to use iterators, you can also manually loop over the elements +using `Front()` or `Back()` with `Next()`: ```go // Iterate through all elements from oldest to newest: @@ -51,23 +80,3 @@ for el := m.Back(); el != nil; el = el.Prev() { fmt.Println(el.Key, el.Value) } ``` - -In case you're using Go 1.23, you can also [iterate with -`range`](https://go.dev/doc/go1.23#iterators) by using `Iterator()` or -`ReverseIterator()` methods: - -```go -for key, value := range m.Iterator() { - fmt.Println(key, value) -} - -for key, value := range m.ReverseIterator() { - fmt.Println(key, value) -} -``` - -The iterator is safe to use bidirectionally, and will return `nil` once it goes -beyond the first or last item. - -If the map is changing while the iteration is in-flight it may produce -unexpected behavior. diff --git a/v3/go.mod b/v3/go.mod new file mode 100644 index 0000000..41fdae7 --- /dev/null +++ b/v3/go.mod @@ -0,0 +1,11 @@ +module github.com/elliotchance/orderedmap/v3 + +go 1.23.0 + +require github.com/stretchr/testify v1.7.1 + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/v3/go.sum b/v3/go.sum new file mode 100644 index 0000000..2dca7c9 --- /dev/null +++ b/v3/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/list.go b/v3/list.go new file mode 100644 index 0000000..c2a571d --- /dev/null +++ b/v3/list.go @@ -0,0 +1,95 @@ +package orderedmap + +// Element is an element of a null terminated (non circular) intrusive doubly linked list that contains the key of the correspondent element in the ordered map too. +type Element[K comparable, V any] struct { + // Next and previous pointers in the doubly-linked list of elements. + // To simplify the implementation, internally a list l is implemented + // as a ring, such that &l.root is both the next element of the last + // list element (l.Back()) and the previous element of the first list + // element (l.Front()). + next, prev *Element[K, V] + + // The key that corresponds to this element in the ordered map. + Key K + + // The value stored with this element. + Value V +} + +// Next returns the next list element or nil. +func (e *Element[K, V]) Next() *Element[K, V] { + return e.next +} + +// Prev returns the previous list element or nil. +func (e *Element[K, V]) Prev() *Element[K, V] { + return e.prev +} + +// list represents a null terminated (non circular) intrusive doubly linked list. +// The list is immediately usable after instantiation without the need of a dedicated initialization. +type list[K comparable, V any] struct { + root Element[K, V] // list head and tail +} + +func (l *list[K, V]) IsEmpty() bool { + return l.root.next == nil +} + +// Front returns the first element of list l or nil if the list is empty. +func (l *list[K, V]) Front() *Element[K, V] { + return l.root.next +} + +// Back returns the last element of list l or nil if the list is empty. +func (l *list[K, V]) Back() *Element[K, V] { + return l.root.prev +} + +// Remove removes e from its list +func (l *list[K, V]) Remove(e *Element[K, V]) { + if e.prev == nil { + l.root.next = e.next + } else { + e.prev.next = e.next + } + if e.next == nil { + l.root.prev = e.prev + } else { + e.next.prev = e.prev + } + e.next = nil // avoid memory leaks + e.prev = nil // avoid memory leaks +} + +// PushFront inserts a new element e with value v at the front of list l and returns e. +func (l *list[K, V]) PushFront(key K, value V) *Element[K, V] { + e := &Element[K, V]{Key: key, Value: value} + if l.root.next == nil { + // It's the first element + l.root.next = e + l.root.prev = e + return e + } + + e.next = l.root.next + l.root.next.prev = e + l.root.next = e + return e +} + +// PushBack inserts a new element e with value v at the back of list l and returns e. +func (l *list[K, V]) PushBack(key K, value V) *Element[K, V] { + e := &Element[K, V]{Key: key, Value: value} + if l.root.prev == nil { + // It's the first element + l.root.next = e + l.root.prev = e + return e + } + + e.prev = l.root.prev + l.root.prev.next = e + l.root.prev = e + return e +} diff --git a/v3/orderedmap.go b/v3/orderedmap.go new file mode 100644 index 0000000..7dbfbae --- /dev/null +++ b/v3/orderedmap.go @@ -0,0 +1,183 @@ +package orderedmap + +import "iter" + +type OrderedMap[K comparable, V any] struct { + kv map[K]*Element[K, V] + ll list[K, V] +} + +func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] { + return &OrderedMap[K, V]{ + kv: make(map[K]*Element[K, V]), + } +} + +// NewOrderedMapWithCapacity creates a map with enough pre-allocated space to +// hold the specified number of elements. +func NewOrderedMapWithCapacity[K comparable, V any](capacity int) *OrderedMap[K, V] { + return &OrderedMap[K, V]{ + kv: make(map[K]*Element[K, V], capacity), + } +} + +func NewOrderedMapWithElements[K comparable, V any](els ...*Element[K, V]) *OrderedMap[K, V] { + om := &OrderedMap[K, V]{ + kv: make(map[K]*Element[K, V], len(els)), + } + for _, el := range els { + om.Set(el.Key, el.Value) + } + return om +} + +// Get returns the value for a key. If the key does not exist, the second return +// parameter will be false and the value will be nil. +func (m *OrderedMap[K, V]) Get(key K) (value V, ok bool) { + v, ok := m.kv[key] + if ok { + value = v.Value + } + + return +} + +// Set will set (or replace) a value for a key. If the key was new, then true +// will be returned. The returned value will be false if the value was replaced +// (even if the value was the same). +func (m *OrderedMap[K, V]) Set(key K, value V) bool { + _, alreadyExist := m.kv[key] + if alreadyExist { + m.kv[key].Value = value + return false + } + + element := m.ll.PushBack(key, value) + m.kv[key] = element + return true +} + +// ReplaceKey replaces an existing key with a new key while preserving order of +// the value. This function will return true if the operation was successful, or +// false if 'originalKey' is not found OR 'newKey' already exists (which would be an overwrite). +func (m *OrderedMap[K, V]) ReplaceKey(originalKey, newKey K) bool { + element, originalExists := m.kv[originalKey] + _, newKeyExists := m.kv[newKey] + if originalExists && !newKeyExists { + delete(m.kv, originalKey) + m.kv[newKey] = element + element.Key = newKey + return true + } + return false +} + +// GetOrDefault returns the value for a key. If the key does not exist, returns +// the default value instead. +func (m *OrderedMap[K, V]) GetOrDefault(key K, defaultValue V) V { + if value, ok := m.kv[key]; ok { + return value.Value + } + + return defaultValue +} + +// GetElement returns the element for a key. If the key does not exist, the +// pointer will be nil. +func (m *OrderedMap[K, V]) GetElement(key K) *Element[K, V] { + element, ok := m.kv[key] + if ok { + return element + } + + return nil +} + +// Len returns the number of elements in the map. +func (m *OrderedMap[K, V]) Len() int { + return len(m.kv) +} + +// AllFromFront returns an iterator that yields all elements in the map starting +// at the front (oldest Set element). +func (m *OrderedMap[K, V]) AllFromFront() iter.Seq2[K, V] { + return func(yield func(key K, value V) bool) { + for el := m.Front(); el != nil; el = el.Next() { + if !yield(el.Key, el.Value) { + return + } + } + } +} + +// AllFromBack returns an iterator that yields all elements in the map starting +// at the back (most recent Set element). +func (m *OrderedMap[K, V]) AllFromBack() iter.Seq2[K, V] { + return func(yield func(key K, value V) bool) { + for el := m.Back(); el != nil; el = el.Prev() { + if !yield(el.Key, el.Value) { + return + } + } + } +} + +// Keys returns an iterator that yields all the keys in the map starting at the +// front (oldest Set element). To create a slice containing all the map keys, +// use the slices.Collect function on the returned iterator. +func (m *OrderedMap[K, V]) Keys() iter.Seq[K] { + return func(yield func(key K) bool) { + for el := m.Front(); el != nil; el = el.Next() { + if !yield(el.Key) { + return + } + } + } +} + +// Values returns an iterator that yields all the values in the map starting at +// the front (oldest Set element). To create a slice containing all the map +// values, use the slices.Collect function on the returned iterator. +func (m *OrderedMap[K, V]) Values() iter.Seq[V] { + return func(yield func(value V) bool) { + for el := m.Front(); el != nil; el = el.Next() { + if !yield(el.Value) { + return + } + } + } +} + +// Delete will remove a key from the map. It will return true if the key was +// removed (the key did exist). +func (m *OrderedMap[K, V]) Delete(key K) (didDelete bool) { + element, ok := m.kv[key] + if ok { + m.ll.Remove(element) + delete(m.kv, key) + } + + return ok +} + +// Front will return the element that is the first (oldest Set element). If +// there are no elements this will return nil. +func (m *OrderedMap[K, V]) Front() *Element[K, V] { + return m.ll.Front() +} + +// Back will return the element that is the last (most recent Set element). If +// there are no elements this will return nil. +func (m *OrderedMap[K, V]) Back() *Element[K, V] { + return m.ll.Back() +} + +// Copy returns a new OrderedMap with the same elements. +// Using Copy while there are concurrent writes may mangle the result. +func (m *OrderedMap[K, V]) Copy() *OrderedMap[K, V] { + m2 := NewOrderedMap[K, V]() + for el := m.Front(); el != nil; el = el.Next() { + m2.Set(el.Key, el.Value) + } + return m2 +} diff --git a/v3/orderedmap_test.go b/v3/orderedmap_test.go new file mode 100644 index 0000000..f6220f0 --- /dev/null +++ b/v3/orderedmap_test.go @@ -0,0 +1,1031 @@ +package orderedmap_test + +import ( + "slices" + "strconv" + "testing" + + "github.com/elliotchance/orderedmap/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewOrderedMap(t *testing.T) { + m := orderedmap.NewOrderedMap[int, string]() + assert.IsType(t, &orderedmap.OrderedMap[int, string]{}, m) +} + +func TestGet(t *testing.T) { + t.Run("ReturnsNotOKIfStringKeyDoesntExist", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + _, ok := m.Get("foo") + assert.False(t, ok) + }) + + t.Run("ReturnsNotOKIfNonStringKeyDoesntExist", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, string]() + _, ok := m.Get(123) + assert.False(t, ok) + }) + + t.Run("ReturnsOKIfKeyExists", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "bar") + _, ok := m.Get("foo") + assert.True(t, ok) + }) + + t.Run("ReturnsValueForKey", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "bar") + value, _ := m.Get("foo") + assert.Equal(t, "bar", value) + }) + + t.Run("ReturnsDynamicValueForKey", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "baz") + value, _ := m.Get("foo") + assert.Equal(t, "baz", value) + }) + + t.Run("KeyDoesntExistOnNonEmptyMap", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "baz") + _, ok := m.Get("bar") + assert.False(t, ok) + }) + + t.Run("ValueForKeyDoesntExistOnNonEmptyMap", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "baz") + value, _ := m.Get("bar") + assert.Empty(t, value) + }) +} + +func TestSet(t *testing.T) { + t.Run("ReturnsTrueIfStringKeyIsNew", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + ok := m.Set("foo", "bar") + assert.True(t, ok) + }) + + t.Run("ReturnsTrueIfNonStringKeyIsNew", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, string]() + ok := m.Set(123, "bar") + assert.True(t, ok) + }) + + t.Run("ValueCanBeNonString", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + ok := m.Set(123, true) + assert.True(t, ok) + }) + + t.Run("ReturnsFalseIfKeyIsNotNew", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "bar") + ok := m.Set("foo", "bar") + assert.False(t, ok) + }) + + t.Run("SetThreeDifferentKeys", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "bar") + m.Set("baz", "qux") + ok := m.Set("quux", "corge") + assert.True(t, ok) + }) +} + +func TestReplaceKey(t *testing.T) { + t.Run("ReturnsFalseIfOriginalKeyDoesntExist", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + assert.False(t, m.ReplaceKey("foo", "bar")) + }) + + t.Run("ReturnsFalseIfNewKeyAlreadyExists", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "bar") + m.Set("baz", "qux") + assert.False(t, m.ReplaceKey("foo", "baz")) + assert.Equal(t, []string{"foo", "baz"}, slices.Collect(m.Keys())) + }) + + t.Run("ReturnsTrueIfOnlyOriginalKeyExists", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "bar") + assert.True(t, m.ReplaceKey("foo", "baz")) + + // Now validate the "replacement" was a success. + el := m.GetElement("baz") + require.NotNil(t, el) + assert.Equal(t, "bar", el.Value) + assert.Equal(t, "baz", el.Key) + + v, ok := m.Get("baz") + assert.True(t, ok) + assert.Equal(t, "bar", v) + assert.Equal(t, []string{"baz"}, slices.Collect(m.Keys())) + assert.Equal(t, 1, m.Len()) + + _, ok = m.Get("foo") // original key + assert.False(t, ok) + }) + + t.Run("KeyMaintainsOrderWhenReplaced", func(t *testing.T) { + count := 100 + // Build a larger map to help validate that the order is not coincidental. + m := orderedmap.NewOrderedMap[int, int]() + for i := 0; i < count; i++ { + m.Set(i, i) + } + // Rename the middle 50-60 elements to 100+ current + for i := 50; i < 60; i++ { + assert.True(t, m.ReplaceKey(i, i+100)) + } + + // ensure length is maintained + assert.Equal(t, count, m.Len()) + + // Validate the order is maintained. + for i, key := range slices.Collect(m.Keys()) { + if i >= 50 && i < 60 { + assert.Equal(t, i+100, key) + } else { + assert.Equal(t, i, key) + } + } + }) +} + +func TestLen(t *testing.T) { + t.Run("EmptyMapIsZeroLen", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + assert.Equal(t, 0, m.Len()) + }) + + t.Run("SingleElementIsLenOne", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + m.Set(123, true) + assert.Equal(t, 1, m.Len()) + }) + + t.Run("ThreeElements", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + m.Set(1, true) + m.Set(2, true) + m.Set(3, true) + assert.Equal(t, 3, m.Len()) + }) +} + +func TestKeys(t *testing.T) { + t.Run("EmptyMap", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + assert.Empty(t, slices.Collect(m.Keys())) + }) + + t.Run("OneElement", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + m.Set(1, true) + assert.Equal(t, []int{1}, slices.Collect(m.Keys())) + }) + + t.Run("RetainsOrder", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 1; i < 10; i++ { + m.Set(i, true) + } + assert.Equal(t, + []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, + slices.Collect(m.Keys())) + }) + + t.Run("ReplacingKeyDoesntChangeOrder", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, bool]() + m.Set("foo", true) + m.Set("bar", true) + m.Set("foo", false) + assert.Equal(t, + []string{"foo", "bar"}, + slices.Collect(m.Keys())) + }) + + t.Run("KeysAfterDelete", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, bool]() + m.Set("foo", true) + m.Set("bar", true) + m.Delete("foo") + assert.Equal(t, []string{"bar"}, slices.Collect(m.Keys())) + }) +} + +func TestDelete(t *testing.T) { + t.Run("KeyDoesntExistReturnsFalse", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + assert.False(t, m.Delete("foo")) + }) + + t.Run("KeyDoesExist", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, any]() + m.Set("foo", nil) + assert.True(t, m.Delete("foo")) + }) + + t.Run("KeyNoLongerExists", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, any]() + m.Set("foo", nil) + m.Delete("foo") + _, exists := m.Get("foo") + assert.False(t, exists) + }) + + t.Run("KeyDeleteIsIsolated", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, any]() + m.Set("foo", nil) + m.Set("bar", nil) + m.Delete("foo") + _, exists := m.Get("bar") + assert.True(t, exists) + }) +} + +func TestOrderedMap_Front(t *testing.T) { + t.Run("NilOnEmptyMap", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + assert.Nil(t, m.Front()) + }) + + t.Run("NilOnEmptyMap", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + m.Set(1, true) + assert.NotNil(t, m.Front()) + }) +} + +func TestOrderedMap_Back(t *testing.T) { + t.Run("NilOnEmptyMap", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + assert.Nil(t, m.Back()) + }) + + t.Run("NilOnEmptyMap", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + m.Set(1, true) + assert.NotNil(t, m.Back()) + }) +} + +func TestOrderedMap_Copy(t *testing.T) { + t.Run("ReturnsEqualButNotSame", func(t *testing.T) { + key, value := 1, "a value" + m := orderedmap.NewOrderedMap[int, string]() + m.Set(key, value) + + m2 := m.Copy() + m2.Set(key, "a different value") + + assert.Equal(t, m.Len(), m2.Len(), "not all elements are copied") + assert.Equal(t, value, m.GetElement(key).Value) + }) +} + +func TestGetElement(t *testing.T) { + t.Run("ReturnsElementForKey", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "bar") + + var results []any + element := m.GetElement("foo") + if element != nil { + results = append(results, element.Key, element.Value) + } + + assert.Equal(t, []any{"foo", "bar"}, results) + }) + + t.Run("ElementForKeyDoesntExistOnNonEmptyMap", func(t *testing.T) { + m := orderedmap.NewOrderedMap[string, string]() + m.Set("foo", "baz") + element := m.GetElement("bar") + assert.Nil(t, element) + }) +} + +func TestSetAndGet(t *testing.T) { + t.Run("FourBoolElements", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + expected := map[int]bool{1: true, 3: false, 5: false, 4: true} + for k, v := range expected { + m.Set(k, v) + } + for k, v := range expected { + w, ok := m.Get(k) + assert.True(t, ok) + assert.Equal(t, v, w) + } + }) +} + +func TestIterations(t *testing.T) { + type Element struct { + Key int + Value bool + } + t.Run("FourBoolElements", func(t *testing.T) { + m := orderedmap.NewOrderedMap[int, bool]() + expected := []Element{{5, true}, {3, false}, {1, false}, {4, true}} + for _, v := range expected { + m.Set(v.Key, v.Value) + } + element := m.Front() + for i := 0; i < len(expected); i++ { + assert.NotNil(t, element) + assert.Equal(t, expected[i].Key, element.Key) + assert.Equal(t, expected[i].Value, element.Value) + element = element.Next() + } + assert.Nil(t, element) + }) +} + +func TestIterators(t *testing.T) { + type Element struct { + Key int + Value bool + } + m := orderedmap.NewOrderedMap[int, bool]() + expected := []Element{{5, true}, {3, false}, {1, false}, {4, true}} + for _, v := range expected { + m.Set(v.Key, v.Value) + } + + t.Run("Iterator", func(t *testing.T) { + i := 0 + for key, value := range m.AllFromFront() { + assert.Equal(t, expected[i].Key, key) + assert.Equal(t, expected[i].Value, value) + i++ + } + }) + + t.Run("ReverseIterator", func(t *testing.T) { + i := len(expected) - 1 + for key, value := range m.AllFromBack() { + assert.Equal(t, expected[i].Key, key) + assert.Equal(t, expected[i].Value, value) + i-- + } + }) +} + +func benchmarkMap_Set(multiplier int) func(b *testing.B) { + return func(b *testing.B) { + m := make(map[int]bool) + for i := 0; i < b.N*multiplier; i++ { + m[i] = true + } + } +} + +func BenchmarkMap_Set(b *testing.B) { + benchmarkMap_Set(1)(b) +} + +func benchmarkOrderedMap_Set(multiplier int) func(b *testing.B) { + return func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < b.N*multiplier; i++ { + m.Set(i, true) + } + } +} + +func BenchmarkOrderedMap_Set(b *testing.B) { + benchmarkOrderedMap_Set(1)(b) +} + +func benchmarkMap_Get(multiplier int) func(b *testing.B) { + m := make(map[int]bool) + for i := 0; i < 1000*multiplier; i++ { + m[i] = true + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = m[i%1000*multiplier] + } + } +} + +func BenchmarkMap_Get(b *testing.B) { + benchmarkMap_Get(1)(b) +} + +func benchmarkOrderedMap_Get(multiplier int) func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < 1000*multiplier; i++ { + m.Set(i, true) + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + m.Get(i % 1000 * multiplier) + } + } +} + +func BenchmarkOrderedMap_Get(b *testing.B) { + benchmarkOrderedMap_Get(1)(b) +} + +func benchmarkOrderedMap_GetElement(multiplier int) func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < 1000*multiplier; i++ { + m.Set(i, true) + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + m.GetElement(i % 1000 * multiplier) + } + } +} + +func BenchmarkOrderedMap_GetElement(b *testing.B) { + benchmarkOrderedMap_GetElement(1)(b) +} + +var tempInt int + +func benchmarkOrderedMap_Len(multiplier int) func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < 1000*multiplier; i++ { + m.Set(i, true) + } + + return func(b *testing.B) { + var temp int + for i := 0; i < b.N; i++ { + temp = m.Len() + } + + // prevent compiler from optimising Len away. + tempInt = temp + } +} + +func BenchmarkOrderedMap_Len(b *testing.B) { + benchmarkOrderedMap_Len(1)(b) +} + +func benchmarkMap_Delete(multiplier int) func(b *testing.B) { + return func(b *testing.B) { + m := make(map[int]bool) + for i := 0; i < b.N*multiplier; i++ { + m[i] = true + } + + for i := 0; i < b.N; i++ { + delete(m, i) + } + } +} + +func BenchmarkMap_Delete(b *testing.B) { + benchmarkMap_Delete(1)(b) +} + +func benchmarkOrderedMap_Delete(multiplier int) func(b *testing.B) { + return func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < b.N*multiplier; i++ { + m.Set(i, true) + } + + for i := 0; i < b.N; i++ { + m.Delete(i) + } + } +} + +func BenchmarkOrderedMap_Delete(b *testing.B) { + benchmarkOrderedMap_Delete(1)(b) +} + +func benchmarkMap_Iterate(multiplier int) func(b *testing.B) { + m := make(map[int]bool) + for i := 0; i < 1000*multiplier; i++ { + m[i] = true + } + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, v := range m { + nothing(v) + } + } + } +} + +func BenchmarkMap_Iterate(b *testing.B) { + benchmarkMap_Iterate(1)(b) +} + +func benchmarkOrderedMap_Iterate(multiplier int) func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < 1000*multiplier; i++ { + m.Set(i, true) + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, key := range slices.Collect(m.Keys()) { + _, v := m.Get(key) + nothing(v) + } + } + } +} + +func BenchmarkOrderedMap_Iterate(b *testing.B) { + benchmarkOrderedMap_Iterate(1)(b) +} + +func benchmarkMapString_Set(multiplier int) func(b *testing.B) { + return func(b *testing.B) { + m := make(map[string]bool) + a := "12345678" + for i := 0; i < b.N*multiplier; i++ { + m[a+strconv.Itoa(i)] = true + } + } +} + +func BenchmarkMapString_Set(b *testing.B) { + benchmarkMapString_Set(1)(b) +} + +func benchmarkOrderedMapString_Set(multiplier int) func(b *testing.B) { + return func(b *testing.B) { + m := orderedmap.NewOrderedMap[string, bool]() + a := "12345678" + for i := 0; i < b.N*multiplier; i++ { + m.Set(a+strconv.Itoa(i), true) + } + } +} + +func BenchmarkOrderedMapString_Set(b *testing.B) { + benchmarkOrderedMapString_Set(1)(b) +} + +func benchmarkMapString_Get(multiplier int) func(b *testing.B) { + m := make(map[string]bool) + a := "12345678" + for i := 0; i < 1000*multiplier; i++ { + m[a+strconv.Itoa(i)] = true + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = m[a+strconv.Itoa(i%1000*multiplier)] + } + } +} + +func BenchmarkMapString_Get(b *testing.B) { + benchmarkMapString_Get(1)(b) +} + +func benchmarkOrderedMapString_Get(multiplier int) func(b *testing.B) { + m := orderedmap.NewOrderedMap[string, bool]() + a := "12345678" + for i := 0; i < 1000*multiplier; i++ { + m.Set(a+strconv.Itoa(i), true) + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + m.Get(a + strconv.Itoa(i%1000*multiplier)) + } + } +} + +func BenchmarkOrderedMapString_Get(b *testing.B) { + benchmarkOrderedMapString_Get(1)(b) +} + +func benchmarkOrderedMapString_GetElement(multiplier int) func(b *testing.B) { + m := orderedmap.NewOrderedMap[string, bool]() + a := "12345678" + for i := 0; i < 1000*multiplier; i++ { + m.Set(a+strconv.Itoa(i), true) + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + m.GetElement(a + strconv.Itoa(i%1000*multiplier)) + } + } +} + +func BenchmarkOrderedMapString_GetElement(b *testing.B) { + benchmarkOrderedMapString_GetElement(1)(b) +} + +func benchmarkMapString_Delete(multiplier int) func(b *testing.B) { + return func(b *testing.B) { + m := make(map[string]bool) + a := "12345678" + for i := 0; i < b.N*multiplier; i++ { + m[a+strconv.Itoa(i)] = true + } + + for i := 0; i < b.N; i++ { + delete(m, a+strconv.Itoa(i)) + } + } +} + +func BenchmarkMapString_Delete(b *testing.B) { + benchmarkMapString_Delete(1)(b) +} + +func benchmarkOrderedMapString_Delete(multiplier int) func(b *testing.B) { + return func(b *testing.B) { + m := orderedmap.NewOrderedMap[string, bool]() + a := "12345678" + for i := 0; i < b.N*multiplier; i++ { + m.Set(a+strconv.Itoa(i), true) + } + + for i := 0; i < b.N; i++ { + m.Delete(a + strconv.Itoa(i)) + } + } +} + +func BenchmarkOrderedMapString_Delete(b *testing.B) { + benchmarkOrderedMapString_Delete(1)(b) +} + +func benchmarkMapString_Iterate(multiplier int) func(b *testing.B) { + m := make(map[string]bool) + a := "12345678" + for i := 0; i < 1000*multiplier; i++ { + m[a+strconv.Itoa(i)] = true + } + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, v := range m { + nothing(v) + } + } + } +} + +func BenchmarkMapString_Iterate(b *testing.B) { + benchmarkMapString_Iterate(1)(b) +} + +func benchmarkOrderedMapString_Iterate(multiplier int) func(b *testing.B) { + m := orderedmap.NewOrderedMap[string, bool]() + a := "12345678" + for i := 0; i < 1000*multiplier; i++ { + m.Set(a+strconv.Itoa(i), true) + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, key := range slices.Collect(m.Keys()) { + _, v := m.Get(key) + nothing(v) + } + } + } +} + +func BenchmarkOrderedMapString_Iterate(b *testing.B) { + benchmarkOrderedMapString_Iterate(1)(b) +} + +func nothing(v interface{}) { + v = false +} + +func benchmarkBigMap_Set() func(b *testing.B) { + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + m := make(map[int]bool) + for i := 0; i < 10000000; i++ { + m[i] = true + } + } + } +} + +func BenchmarkBigMap_Set(b *testing.B) { + benchmarkBigMap_Set()(b) +} + +func benchmarkBigOrderedMap_Set() func(b *testing.B) { + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < 10000000; i++ { + m.Set(i, true) + } + } + } +} + +func BenchmarkBigOrderedMap_Set(b *testing.B) { + benchmarkBigOrderedMap_Set()(b) +} + +func benchmarkBigMapWithCapacity_Set() func(b *testing.B) { + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + m := orderedmap.NewOrderedMapWithCapacity[int, bool](10000000) + for i := 0; i < 10000000; i++ { + m.Set(i, true) + } + } + } +} + +func BenchmarkBigMapWithCapacity_Set(b *testing.B) { + benchmarkBigMapWithCapacity_Set()(b) +} + +func benchmarkBigMap_Get() func(b *testing.B) { + m := make(map[int]bool) + for i := 0; i < 10000000; i++ { + m[i] = true + } + + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + for i := 0; i < 10000000; i++ { + _ = m[i] + } + } + } +} + +func BenchmarkBigMap_Get(b *testing.B) { + benchmarkBigMap_Get()(b) +} + +func benchmarkBigOrderedMap_Get() func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < 10000000; i++ { + m.Set(i, true) + } + + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + for i := 0; i < 10000000; i++ { + m.Get(i) + } + } + } +} + +func BenchmarkBigOrderedMap_Get(b *testing.B) { + benchmarkBigOrderedMap_Get()(b) +} + +func benchmarkBigOrderedMap_GetElement() func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < 10000000; i++ { + m.Set(i, true) + } + + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + for i := 0; i < 10000000; i++ { + m.GetElement(i) + } + } + } +} + +func BenchmarkBigOrderedMap_GetElement(b *testing.B) { + benchmarkBigOrderedMap_GetElement()(b) +} + +func benchmarkBigMap_Iterate() func(b *testing.B) { + m := make(map[int]bool) + for i := 0; i < 10000000; i++ { + m[i] = true + } + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, v := range m { + nothing(v) + } + } + } +} + +func BenchmarkBigMap_Iterate(b *testing.B) { + benchmarkBigMap_Iterate()(b) +} + +func benchmarkBigOrderedMap_Iterate() func(b *testing.B) { + m := orderedmap.NewOrderedMap[int, bool]() + for i := 0; i < 10000000; i++ { + m.Set(i, true) + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, key := range slices.Collect(m.Keys()) { + _, v := m.Get(key) + nothing(v) + } + } + } +} + +func BenchmarkBigOrderedMap_Iterate(b *testing.B) { + benchmarkBigOrderedMap_Iterate()(b) +} + +func benchmarkBigMapString_Set() func(b *testing.B) { + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + m := make(map[string]bool) + a := "1234567" + for i := 0; i < 10000000; i++ { + m[a+strconv.Itoa(i)] = true + } + } + } +} + +func BenchmarkBigMapString_Set(b *testing.B) { + benchmarkBigMapString_Set()(b) +} + +func benchmarkBigOrderedMapString_Set() func(b *testing.B) { + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + m := orderedmap.NewOrderedMap[string, bool]() + a := "1234567" + for i := 0; i < 10000000; i++ { + m.Set(a+strconv.Itoa(i), true) + } + } + } +} + +func BenchmarkBigOrderedMapString_Set(b *testing.B) { + benchmarkBigOrderedMapString_Set()(b) +} + +func benchmarkBigMapString_Get() func(b *testing.B) { + m := make(map[string]bool) + a := "1234567" + for i := 0; i < 10000000; i++ { + m[a+strconv.Itoa(i)] = true + } + + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + for i := 0; i < 10000000; i++ { + _ = m[a+strconv.Itoa(i)] + } + } + } +} + +func BenchmarkBigMapString_Get(b *testing.B) { + benchmarkBigMapString_Get()(b) +} + +func benchmarkBigOrderedMapString_Get() func(b *testing.B) { + m := orderedmap.NewOrderedMap[string, bool]() + a := "1234567" + for i := 0; i < 10000000; i++ { + m.Set(a+strconv.Itoa(i), true) + } + + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + for i := 0; i < 10000000; i++ { + m.Get(a + strconv.Itoa(i)) + } + } + } +} + +func BenchmarkBigOrderedMapString_Get(b *testing.B) { + benchmarkBigOrderedMapString_Get()(b) +} + +func benchmarkBigOrderedMapString_GetElement() func(b *testing.B) { + m := orderedmap.NewOrderedMap[string, bool]() + a := "1234567" + for i := 0; i < 10000000; i++ { + m.Set(a+strconv.Itoa(i), true) + } + + return func(b *testing.B) { + for j := 0; j < b.N; j++ { + for i := 0; i < 10000000; i++ { + m.GetElement(a + strconv.Itoa(i)) + } + } + } +} + +func BenchmarkBigOrderedMapString_GetElement(b *testing.B) { + benchmarkBigOrderedMapString_GetElement()(b) +} + +func benchmarkBigMapString_Iterate() func(b *testing.B) { + m := make(map[string]bool) + a := "12345678" + for i := 0; i < 10000000; i++ { + m[a+strconv.Itoa(i)] = true + } + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, v := range m { + nothing(v) + } + } + } +} + +func BenchmarkBigMapString_Iterate(b *testing.B) { + benchmarkBigMapString_Iterate()(b) +} + +func benchmarkBigOrderedMapString_Iterate() func(b *testing.B) { + m := orderedmap.NewOrderedMap[string, bool]() + a := "12345678" + for i := 0; i < 10000000; i++ { + m.Set(a+strconv.Itoa(i), true) + } + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, key := range slices.Collect(m.Keys()) { + _, v := m.Get(key) + nothing(v) + } + } + } +} + +func BenchmarkBigOrderedMapString_Iterate(b *testing.B) { + benchmarkBigOrderedMapString_Iterate()(b) +} + +func BenchmarkAll(b *testing.B) { + b.Run("BenchmarkOrderedMap_Set", BenchmarkOrderedMap_Set) + b.Run("BenchmarkMap_Set", BenchmarkMap_Set) + b.Run("BenchmarkOrderedMap_Get", BenchmarkOrderedMap_Get) + b.Run("BenchmarkMap_Get", BenchmarkMap_Get) + b.Run("BenchmarkOrderedMap_GetElement", BenchmarkOrderedMap_GetElement) + b.Run("BenchmarkOrderedMap_Delete", BenchmarkOrderedMap_Delete) + b.Run("BenchmarkMap_Delete", BenchmarkMap_Delete) + b.Run("BenchmarkOrderedMap_Iterate", BenchmarkOrderedMap_Iterate) + b.Run("BenchmarkMap_Iterate", BenchmarkMap_Iterate) + + b.Run("BenchmarkBigMap_Set", BenchmarkBigMap_Set) + b.Run("BenchmarkBigOrderedMap_Set", BenchmarkBigOrderedMap_Set) + b.Run("BenchmarkBigMap_Get", BenchmarkBigMap_Get) + b.Run("BenchmarkBigOrderedMap_Get", BenchmarkBigOrderedMap_Get) + b.Run("BenchmarkBigOrderedMap_GetElement", + BenchmarkBigOrderedMap_GetElement) + b.Run("BenchmarkBigOrderedMap_Iterate", BenchmarkBigOrderedMap_Iterate) + b.Run("BenchmarkBigMap_Iterate", BenchmarkBigMap_Iterate) + + b.Run("BenchmarkOrderedMapString_Set", BenchmarkOrderedMapString_Set) + b.Run("BenchmarkMapString_Set", BenchmarkMapString_Set) + b.Run("BenchmarkOrderedMapString_Get", BenchmarkOrderedMapString_Get) + b.Run("BenchmarkMapString_Get", BenchmarkMapString_Get) + b.Run("BenchmarkOrderedMapString_GetElement", + BenchmarkOrderedMapString_GetElement) + b.Run("BenchmarkOrderedMapString_Delete", BenchmarkOrderedMapString_Delete) + b.Run("BenchmarkMapString_Delete", BenchmarkMapString_Delete) + b.Run("BenchmarkOrderedMapString_Iterate", + BenchmarkOrderedMapString_Iterate) + b.Run("BenchmarkMapString_Iterate", BenchmarkMapString_Iterate) + + b.Run("BenchmarkBigMapString_Set", BenchmarkBigMapString_Set) + b.Run("BenchmarkBigOrderedMapString_Set", BenchmarkBigOrderedMapString_Set) + b.Run("BenchmarkBigMapString_Get", BenchmarkBigMapString_Get) + b.Run("BenchmarkBigOrderedMapString_Get", BenchmarkBigOrderedMapString_Get) + b.Run("BenchmarkBigOrderedMapString_GetElement", + BenchmarkBigOrderedMapString_GetElement) + b.Run("BenchmarkBigOrderedMapString_Iterate", + BenchmarkBigOrderedMapString_Iterate) + b.Run("BenchmarkBigMapString_Iterate", BenchmarkBigMapString_Iterate) +}