Skip to content

Commit

Permalink
feat(swissmap): add WithSortMapKeys option
Browse files Browse the repository at this point in the history
Signed-off-by: Dwi Siswanto <[email protected]>
  • Loading branch information
dwisiswant0 committed Feb 5, 2025
1 parent 8b86421 commit 72ef9f3
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 10 deletions.
11 changes: 11 additions & 0 deletions swissmap/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ func WithThreadSafety[K, V comparable]() Option[K, V] {
m.threadSafe = true
}
}

// WithSortMapKeys enables sorting of map keys
func WithSortMapKeys[K, V comparable]() Option[K, V] {
cfg := getDefaultSonicConfig()
cfg.SortMapKeys = true

return func(m *Map[K, V]) {
m.sorted = true
m.api = cfg.Froze()
}
}
33 changes: 24 additions & 9 deletions swissmap/swissmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ import (
"github.com/cockroachdb/swiss"
)

// Map is a generic map implementation using swiss.Map with optional thread-safety
// Map is a generic map implementation using swiss.Map with additional [Option]s
type Map[K, V comparable] struct {
api sonic.API
data *swiss.Map[K, V]
mutex sync.RWMutex
threadSafe bool
data *swiss.Map[K, V]
sorted bool
}

// New creates a new Map with the given options
func New[K, V comparable](options ...Option[K, V]) *Map[K, V] {
m := &Map[K, V]{data: swiss.New[K, V](0)}
m := &Map[K, V]{
data: swiss.New[K, V](0),
api: getDefaultSonicConfig().Froze(),
}

for _, opt := range options {
opt(m)
Expand Down Expand Up @@ -108,6 +113,17 @@ func (m *Map[K, V]) GetOrDefault(key K, defaultValue V) V {
return defaultValue
}

// GetByIndex retrieves a value by its index
//
// The index is 0-based and must be less than the number of elements in the map
func (m *Map[K, V]) GetByIndex(idx int) (V, bool) {
// TODO(dwisiswant0): Implement this method

var value V

return value, false
}

// Has checks if a key exists in the map
func (m *Map[K, V]) Has(key K) bool {
m.rLock()
Expand All @@ -128,11 +144,8 @@ func (m *Map[K, V]) IsEmpty() bool {

// Merge adds all key/value pairs from the input map
func (m *Map[K, V]) Merge(n map[K]V) {
m.lock()
defer m.unlock()

for k, v := range n {
m.data.Put(k, v)
m.Set(k, v)
}
}

Expand All @@ -157,17 +170,19 @@ func (m *Map[K, V]) MarshalJSON() ([]byte, error) {
return true
})

return sonic.Marshal(target)
return m.api.Marshal(target)
}

// UnmarshalJSON unmarshals the map from JSON
//
// The map is merged with the input data.
func (m *Map[K, V]) UnmarshalJSON(buf []byte) error {
m.lock()
defer m.unlock()

target := make(map[K]V)

if err := sonic.Unmarshal(buf, &target); err != nil {
if err := m.api.Unmarshal(buf, &target); err != nil {
return err
}

Expand Down
42 changes: 41 additions & 1 deletion swissmap/swissmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,46 @@ func TestMapJSON(t *testing.T) {
}
})

t.Run("WithSortMapKeys", func(t *testing.T) {
t.SkipNow()

m := New(WithSortMapKeys[string, int]())

// Insert items in random order
m.Set("zebra", 1)
m.Set("alpha", 2)
m.Set("beta", 3)

data, err := m.MarshalJSON()
if err != nil {
t.Fatalf("MarshalJSON failed with sorted keys: %v", err)
}
t.Logf("marshaled data with sorted keys: %s", data)

// Test getting by index with sorted keys
if v, ok := m.GetByIndex(0); !ok || v != 2 {
t.Errorf("GetByIndex(0) = (%v, %v), want (2, true)", v, ok)
}

if v, ok := m.GetByIndex(1); !ok || v != 3 {
t.Errorf("GetByIndex(1) = (%v, %v), want (3, true)", v, ok)
}

if v, ok := m.GetByIndex(2); !ok || v != 1 {
t.Errorf("GetByIndex(2) = (%v, %v), want (1, true)", v, ok)
}

// Test out of bounds index
if _, ok := m.GetByIndex(3); ok {
t.Error("GetByIndex(3) should return false for out of bounds index")
}

// Test negative index
if _, ok := m.GetByIndex(-1); ok {
t.Error("GetByIndex(-1) should return false for negative index")
}
})

t.Run("Empty map", func(t *testing.T) {
m := New[string, string]()

Expand All @@ -181,8 +221,8 @@ func TestMapJSON(t *testing.T) {

t.Run("Complex types", func(t *testing.T) {
type Complex struct {
ID int
Name string
ID int
}
m := New[string, Complex]()
m.Set("item1", Complex{ID: 1, Name: "test1"})
Expand Down
18 changes: 18 additions & 0 deletions swissmap/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package swissmap

import "github.com/bytedance/sonic"

// getDefaultSonicConfig provides the default configuration for the Sonic
// library.
//
// This function returns a [sonic.Config] instance with standard `encoding/json`
// settings but with unsorted map keys. You may want to use the [WithSortMapKeys]
// option to enable sorting of map keys.
func getDefaultSonicConfig() sonic.Config {
return sonic.Config{
EscapeHTML: true,
CompactMarshaler: true,
CopyString: true,
ValidateString: true,
}
}

0 comments on commit 72ef9f3

Please sign in to comment.