-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#3 added support for fixed order map which maintains the insertion or…
…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
Showing
2 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |