Skip to content

Commit

Permalink
#3 added support for fixed order map which maintains the insertion or…
Browse files Browse the repository at this point in the history
…der of keys- an immutable order that is threadsafe
  • Loading branch information
Krishnakant C authored and Krishnakant C committed Sep 12, 2024
1 parent c1f6118 commit 4e0a3b6
Show file tree
Hide file tree
Showing 2 changed files with 239 additions and 0 deletions.
146 changes: 146 additions & 0 deletions pkg/utils/fixed_order_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2024 Atomstate Technologies Private Limited
//
// 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 utils

import (
"errors"
"sync"
)

// Custom errors for FixedOrderMap operations.
var (
// ErrKeyExists is returned when attempting to add a key that already exists.
ErrKeyExists = errors.New("key already exists")

// ErrRemovalNotAllowed is returned when attempting to remove an entry.
ErrRemovalNotAllowed = errors.New("removal of entries is not allowed")
)

// FixedOrderMap is a map that maintains the insertion order of keys.
// Once keys are added, they cannot be removed or modified.
// It provides thread-safe operations for setting and getting values,
// while ensuring that the order of insertion is preserved.
type FixedOrderMap struct {
mu sync.RWMutex
data map[interface{}]interface{}
order []interface{}
}

// NewFixedOrderMap creates and returns a new instance of FixedOrderMap.
// Example usage:
//
// m := NewFixedOrderMap()
// err := m.Set("key1", "value1")
func NewFixedOrderMap() *FixedOrderMap {
return &FixedOrderMap{
data: make(map[interface{}]interface{}),
order: []interface{}{},
}
}

// Set adds a new key-value pair to the map.
// It returns an error if the key already exists in the map.
// Example usage:
//
// err := m.Set("key1", "value1")
// if err != nil {
// log.Fatalf("error setting value: %v", err)
// }
func (m *FixedOrderMap) Set(key, value interface{}) error {
m.mu.Lock()
defer m.mu.Unlock()

if _, exists := m.data[key]; exists {
return ErrKeyExists
}

m.data[key] = value
m.order = append(m.order, key)
return nil
}

// Get retrieves the value associated with the given key.
// It returns the value and a boolean indicating whether the key exists in the map.
// Example usage:
//
// value, exists := m.Get("key1")
// if exists {
// fmt.Println("Value:", value)
// }
func (m *FixedOrderMap) Get(key interface{}) (interface{}, bool) {
m.mu.RLock()
defer m.mu.RUnlock()

value, exists := m.data[key]
return value, exists
}

// Order returns a slice of keys in the order they were added.
// The slice is a copy, ensuring immutability of the original order slice.
// Example usage:
//
// order := m.Order()
// fmt.Println("Keys in order:", order)
func (m *FixedOrderMap) Order() []interface{} {
m.mu.RLock()
defer m.mu.RUnlock()

// Create a copy of the order slice for immutability
orderCopy := make([]interface{}, len(m.order))
copy(orderCopy, m.order)
return orderCopy
}

// Remove does nothing and always returns an error indicating removal is not allowed.
// Example usage:
//
// err := m.Remove("key1")
// if err != nil {
// fmt.Println("Error:", err)
// }
func (m *FixedOrderMap) Remove(key interface{}) error {
return ErrRemovalNotAllowed
}

// RemoveEntry does nothing and always returns an error indicating removal is not allowed.
// Example usage:
//
// err := m.RemoveEntry("key1", "value1")
// if err != nil {
// fmt.Println("Error:", err)
// }
func (m *FixedOrderMap) RemoveEntry(key, value interface{}) error {
return ErrRemovalNotAllowed
}

// Clone creates and returns a new FixedOrderMap with the same key-value pairs and insertion order.
// The cloned map is independent of the original map.
// Example usage:
//
// clone := m.Clone()
// fmt.Println("Cloned map order:", clone.Order())
func (m *FixedOrderMap) Clone() *FixedOrderMap {
m.mu.RLock()
defer m.mu.RUnlock()

clone := NewFixedOrderMap()
for _, key := range m.order {
clone.data[key] = m.data[key]
}
// Copy the order slice
clone.order = make([]interface{}, len(m.order))
copy(clone.order, m.order)
return clone
}
93 changes: 93 additions & 0 deletions pkg/utils/fixed_order_map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2024 Atomstate Technologies Private Limited
//
// 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 utils

import (
"testing"
)

func TestFixedOrderMap(t *testing.T) {
// Create a new FixedOrderMap
m := NewFixedOrderMap()

// Test Set and Get
if err := m.Set("key1", "value1"); err != nil {
t.Fatalf("expected no error, got %v", err)
}

value, exists := m.Get("key1")
if !exists {
t.Fatal("expected key1 to exist")
}
if value != "value1" {
t.Fatalf("expected value1, got %v", value)
}

// Test duplicate key
if err := m.Set("key1", "newValue"); err != ErrKeyExists {
t.Fatalf("expected ErrKeyExists, got %v", err)
}

// Test Order
if order := m.Order(); len(order) != 1 || order[0] != "key1" {
t.Fatalf("expected order [key1], got %v", order)
}

// Test Clone
clone := m.Clone()
if clone == nil {
t.Fatal("expected a valid clone")
}
if value, exists := clone.Get("key1"); !exists || value != "value1" {
t.Fatalf("expected clone to have key1 with value1, got %v", value)
}

// Ensure clone is independent
if err := clone.Set("key2", "value2"); err != nil {
t.Fatalf("expected no error when setting key2 in clone, got %v", err)
}
if _, exists := m.Get("key2"); exists {
t.Fatal("original map should not have key2")
}
if order := clone.Order(); len(order) != 2 || order[1] != "key2" {
t.Fatalf("expected clone order [key1 key2], got %v", order)
}

// Test Remove operations
if err := m.Remove("key1"); err != ErrRemovalNotAllowed {
t.Fatalf("expected ErrRemovalNotAllowed, got %v", err)
}
if err := m.RemoveEntry("key1", "value1"); err != ErrRemovalNotAllowed {
t.Fatalf("expected ErrRemovalNotAllowed, got %v", err)
}
}

func BenchmarkFixedOrderMap(b *testing.B) {
m := NewFixedOrderMap()

b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := m.Set(i, i); err != nil {
b.Fatalf("unexpected error: %v", err)
}
}

for i := 0; i < b.N; i++ {
_, exists := m.Get(i)
if !exists {
b.Fatalf("key %d should exist", i)
}
}
}

0 comments on commit 4e0a3b6

Please sign in to comment.