diff --git a/utils/orderedmap.go b/utils/orderedmap.go
new file mode 100644
index 000000000..a6155af91
--- /dev/null
+++ b/utils/orderedmap.go
@@ -0,0 +1,82 @@
+/*
+Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
+Copyright (C) ITsysCOM GmbH
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+
+package utils
+
+// OrderedMap is a map that maintains the order of its key-value pairs.
+type OrderedMap[K comparable, V any] struct {
+ keys []K // keys holds the keys in order of their insertion.
+ values map[K]V // values is a map of key-value pairs.
+}
+
+// NewOrderedMap creates a new ordered map and returns a pointer to it.
+func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] {
+ return &OrderedMap[K, V]{
+ keys: make([]K, 0), // Initialize an empty slice for keys.
+ values: make(map[K]V), // Initialize an empty map for key-value pairs.
+ }
+}
+
+// Set adds a new key-value pair to the ordered map. If the key already exists, it updates the value.
+func (om *OrderedMap[K, V]) Set(key K, value V) {
+ // If the key does not exist in the map, append it to the keys slice.
+ if _, exists := om.values[key]; !exists {
+ om.keys = append(om.keys, key)
+ }
+ // Add or update the value for the key in the map.
+ om.values[key] = value
+}
+
+// Get retrieves the value associated with the given key from the ordered map.
+// It returns the value and a boolean indicating whether the key exists in the map.
+func (om *OrderedMap[K, V]) Get(key K) (V, bool) {
+ // Retrieve the value for the key from the map.
+ val, ok := om.values[key]
+ return val, ok
+}
+
+// Delete removes the key-value pair associated with the given key from the ordered map.
+func (om *OrderedMap[K, V]) Delete(key K) {
+ // Iterate over the keys slice to find the key to delete.
+ for i, k := range om.keys {
+ // When the key is found, remove it from the slice.
+ if k == key {
+ om.keys = append(om.keys[:i], om.keys[i+1:]...)
+ break
+ }
+ }
+ // Remove the key-value pair from the map.
+ delete(om.values, key)
+}
+
+// Keys returns all keys of the ordered map in order of their insertion.
+func (om *OrderedMap[K, V]) Keys() []K {
+ return om.keys
+}
+
+// Values returns all values of the ordered map in the order of their corresponding keys' insertion.
+func (om *OrderedMap[K, V]) Values() []V {
+ // Initialize an empty slice to hold the values.
+ vals := make([]V, 0, len(om.values))
+
+ // Iterate over the keys in order and append the corresponding value to the values slice.
+ for _, key := range om.keys {
+ vals = append(vals, om.values[key])
+ }
+ return vals
+}
diff --git a/utils/orderedmap_test.go b/utils/orderedmap_test.go
new file mode 100644
index 000000000..02d3b5bb1
--- /dev/null
+++ b/utils/orderedmap_test.go
@@ -0,0 +1,169 @@
+/*
+Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
+Copyright (C) ITsysCOM GmbH
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+*/
+package utils
+
+import (
+ "crypto/rand"
+ "reflect"
+ "testing"
+)
+
+func TestOrderedMapSetGetDelete(t *testing.T) {
+ testCases := []struct {
+ key string
+ value string
+ }{
+ {"shortKey", "shortValue"},
+ {"longKeylongKeylongKeylongKeylongKeylongKey", "longValuelongValuelongValuelongValuelongValue"},
+ {"keyWithSpecialCharacters!@#$%^&*()", "valueWithSpecialCharacters!@#$%^&*()"},
+ {"", ""},
+ }
+
+ for _, tc := range testCases {
+ // Initialize a new OrderedMap for each test case
+ om := NewOrderedMap[string, string]()
+
+ // Perform a set and a get for the initial key-value pair
+ om.Set(tc.key, tc.value)
+ val, ok := om.Get(tc.key)
+ if !ok || val != tc.value {
+ t.Errorf("Set key-value pair did not match Get result: expected %s, got %s", tc.value, val)
+ }
+
+ // Update the value
+ newValue := tc.value + "updated"
+ om.Set(tc.key, newValue)
+ val, ok = om.Get(tc.key)
+ if !ok || val != newValue {
+ t.Errorf("Updated key-value pair did not match Get result: expected %s, got %s", newValue, val)
+ }
+
+ // Delete the key
+ om.Delete(tc.key)
+ _, ok = om.Get(tc.key)
+ if ok {
+ t.Errorf("Deleted key was still found in map")
+ }
+
+ // Try to get a non-existent key
+ _, ok = om.Get("non-existent key")
+ if ok {
+ t.Errorf("Non-existent key was found in map")
+ }
+ }
+}
+
+func TestOrderedMapKeysValues(t *testing.T) {
+ // Initialize a new OrderedMap
+ om := NewOrderedMap[string, string]()
+
+ // Set multiple key-value pairs
+ om.Set("key1", "value1")
+ om.Set("key2", "value2")
+ om.Set("key3", "value3")
+
+ // Get the keys and values
+ keys := om.Keys()
+ values := om.Values()
+
+ // Check the keys
+ expectedKeys := []string{"key1", "key2", "key3"}
+ if !reflect.DeepEqual(keys, expectedKeys) {
+ t.Errorf("Keys do not match expected keys: expected %v, received %v", expectedKeys, keys)
+ }
+
+ // Check the values
+ expectedValues := []string{"value1", "value2", "value3"}
+ if !reflect.DeepEqual(values, expectedValues) {
+ t.Errorf("Values do not match expected values: expected %v, received %v", expectedValues, values)
+ }
+}
+
+// Benchmark that emphasizes the difference in performance for simple values between
+// OrderedNavigableMap and the generic OrderedMap.
+// Sample usage: go test -bench=. -run=Benchmark_NavigableMaps -benchtime=5s -count 3 -benchmem
+func BenchmarkOrderedMaps(b *testing.B) {
+ letterBytes := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
+
+ // Function to generate a random string of `strLen` length
+ randomString := func(strLen int) string {
+ byt := make([]byte, strLen)
+ rand.Read(byt)
+
+ for i := range byt {
+ byt[i] = letterBytes[byt[i]%byte(len(letterBytes))]
+ }
+ return string(byt)
+ }
+
+ // Function to generate a slice of n random strings
+ generateStringSlice := func(n int) []string {
+ randStrings := make([]string, n)
+ for i := range randStrings {
+ randStrings[i] = randomString(10)
+ }
+ return randStrings
+ }
+
+ keys := generateStringSlice(100000)
+ values := generateStringSlice(100000)
+
+ b.Run("Generic ordered map - Set", func(b *testing.B) {
+ genericOm := NewOrderedMap[string, string]()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ genericOm.Set(keys[i%len(keys)], values[i%len(values)])
+ }
+ })
+ b.Run("Generic ordered map - Get", func(b *testing.B) {
+ genericOm := NewOrderedMap[string, string]()
+ for i := 0; i < len(keys); i++ {
+ genericOm.Set(keys[i], values[i])
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, _ = genericOm.Get(keys[i%len(keys)])
+ }
+ })
+
+ b.Run("OrderedNavigableMap - Set", func(b *testing.B) {
+ onm := NewOrderedNavigableMap()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ onm.Set(&FullPath{
+ Path: keys[i%len(keys)],
+ PathSlice: []string{keys[i%len(keys)]},
+ }, NewLeafNode(values[i%len(values)]))
+ }
+ })
+ b.Run("OrderedNavigableMap - Field+String", func(b *testing.B) {
+ onm := NewOrderedNavigableMap()
+ for i := 0; i < len(keys); i++ {
+ onm.Set(&FullPath{
+ Path: keys[i],
+ PathSlice: []string{keys[i]},
+ }, NewLeafNode(values[i%len(values)]))
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ val, _ := onm.Field([]string{keys[i%len(keys)]})
+ _ = val.String()
+ }
+ })
+
+}