1- // Copyright (c) 2019 srfrog - https://srfrog.me
1+ // Copyright (c) 2025 srfrog - https://srfrog.dev
22// Use of this source code is governed by the license in the LICENSE file.
33
44package dict
55
66import (
77 "fmt"
8+ "reflect"
89 "strings"
10+ "sync"
11+ "sync/atomic"
912)
1013
1114// Dict is a type that uses a hash mapping index, also known as a dictionary.
1215type Dict struct {
13- size , version int
16+ size , version int64
1417 keys []* Key
1518 values map [uint64 ]interface {}
19+ mu sync.RWMutex
1620}
1721
1822// Version returns the version of the dictionary. The version is increased after every
1923// change to dict items.
2024// Returns version, which is zero (0) initially.
21- func (d * Dict ) Version () int { return d .version }
25+ func (d * Dict ) Version () int {
26+ return int (atomic .LoadInt64 (& d .version ))
27+ }
2228
2329// Len returns the size of a Dict.
24- func (d * Dict ) Len () int { return d .size }
30+ func (d * Dict ) Len () int {
31+ return int (atomic .LoadInt64 (& d .size ))
32+ }
2533
2634// New returns a new Dict object.
2735// vargs can be any Go basic type, slices, and maps. The keys in a map are
@@ -45,14 +53,23 @@ func (d *Dict) Set(key, value interface{}) *Dict {
4553 return d
4654 }
4755
48- if _ , ok := d .values [k .ID ]; ok {
56+ d .mu .Lock ()
57+ defer d .mu .Unlock ()
58+
59+ if curr , ok := d .values [k .ID ]; ok {
4960 d .values [k .ID ] = value
61+
62+ // Value changed, update version.
63+ if ! reflect .DeepEqual (value , curr ) {
64+ atomic .AddInt64 (& d .version , 1 )
65+ }
66+
5067 return d
5168 }
5269 d .keys = append (d .keys , k )
5370 d .values [k .ID ] = value
54- d .size ++
55- d .version ++
71+ atomic . AddInt64 ( & d .size , 1 )
72+ atomic . AddInt64 ( & d .version , 1 )
5673
5774 return d
5875}
@@ -64,8 +81,11 @@ func (d *Dict) Get(key interface{}, alt ...interface{}) interface{} {
6481 if d .IsEmpty () {
6582 return nil
6683 }
84+
6785 h , ok := d .GetKeyID (key )
6886 if ok {
87+ d .mu .RLock ()
88+ defer d .mu .RUnlock ()
6989 return d .values [h ]
7090 }
7191 if alt != nil {
@@ -80,16 +100,21 @@ func (d *Dict) GetKeyID(key interface{}) (uint64, bool) {
80100 if d .IsEmpty () {
81101 return 0 , false
82102 }
103+
83104 k := MakeKey (key )
84105 if k == nil {
85106 return 0 , false
86107 }
108+
109+ d .mu .RLock ()
87110 _ , ok := d .values [k .ID ]
111+ d .mu .RUnlock ()
112+
88113 return k .ID , ok
89114}
90115
91116func (d * Dict ) deleteItem (idx int ) {
92- if d .IsEmpty () || idx >= d .size {
117+ if d .IsEmpty () || idx >= d .Len () {
93118 return
94119 }
95120
@@ -98,8 +123,8 @@ func (d *Dict) deleteItem(idx int) {
98123 l := len (d .keys )
99124 d .keys [l - 1 ] = nil
100125 d .keys = d .keys [:l - 1 ]
101- d .size = l
102- d .version ++
126+ atomic . StoreInt64 ( & d .size , int64 ( l - 1 ))
127+ atomic . AddInt64 ( & d .version , 1 )
103128}
104129
105130// Del removes an item from dict by key name.
@@ -110,14 +135,18 @@ func (d *Dict) Del(key interface{}) bool {
110135 return false
111136 }
112137
138+ d .mu .Lock ()
139+ defer d .mu .Unlock ()
140+
113141 var idx int
114142 for i := range d .keys {
115143 if d .keys [i ].ID == id {
116144 idx = i
117145 break
118146 }
119147 }
120- if idx > d .size || d .keys [idx ].ID != id {
148+
149+ if idx >= len (d .keys ) || d .keys [idx ].ID != id {
121150 return false
122151 }
123152
@@ -143,9 +172,17 @@ func (d *Dict) PopItem() *Item {
143172 return nil
144173 }
145174
146- key := d .keys [d .size - 1 ]
175+ d .mu .Lock ()
176+ defer d .mu .Unlock ()
177+
178+ size := len (d .keys )
179+ if size == 0 {
180+ return nil
181+ }
182+
183+ key := d .keys [size - 1 ]
147184 value := d .values [key .ID ]
148- d .deleteItem (d . size - 1 )
185+ d .deleteItem (size - 1 )
149186
150187 return & Item {
151188 Key : key .Name ,
@@ -161,7 +198,7 @@ func (d *Dict) Key(key interface{}) bool {
161198
162199// IsEmpty returns true if the dict is empty, false otherwise.
163200func (d * Dict ) IsEmpty () bool {
164- return d == nil || d .size == 0
201+ return d == nil || d .Len () == 0
165202}
166203
167204// Clear empties a Dict d.
@@ -170,8 +207,13 @@ func (d *Dict) Clear() bool {
170207 if d .IsEmpty () {
171208 return false
172209 }
173- d .size = 0
174- d .version ++ // not a new dict
210+
211+ d .mu .Lock ()
212+ defer d .mu .Unlock ()
213+
214+ atomic .StoreInt64 (& d .size , 0 )
215+ atomic .AddInt64 (& d .version , 1 )
216+
175217 d .keys = []* Key {}
176218 d .values = make (map [uint64 ]interface {})
177219 return true
@@ -182,7 +224,11 @@ func (d *Dict) Keys() []string {
182224 if d .IsEmpty () {
183225 return nil
184226 }
185- keys := make ([]string , d .size )
227+
228+ d .mu .RLock ()
229+ defer d .mu .RUnlock ()
230+
231+ keys := make ([]string , d .Len ())
186232 for i := range d .keys {
187233 keys [i ] = d .keys [i ].Name
188234 }
@@ -194,7 +240,11 @@ func (d *Dict) Values() []interface{} {
194240 if d .IsEmpty () {
195241 return nil
196242 }
197- values := make ([]interface {}, d .size )
243+
244+ d .mu .RLock ()
245+ defer d .mu .RUnlock ()
246+
247+ values := make ([]interface {}, d .Len ())
198248 for i , key := range d .keys {
199249 values [i ] = d .values [key .ID ]
200250 }
@@ -204,17 +254,29 @@ func (d *Dict) Values() []interface{} {
204254// Items returns a channel of key-value items, or nil if the dict is empty.
205255func (d * Dict ) Items () <- chan Item {
206256 ci := make (chan Item )
257+ if d .IsEmpty () {
258+ close (ci )
259+ return ci
260+ }
261+
262+ // Avoid lock contention
263+ d .mu .RLock ()
264+ items := make ([]Item , len (d .keys ))
265+ for i := range d .keys {
266+ items [i ] = Item {
267+ Key : d .keys [i ].Name ,
268+ Value : d .values [d .keys [i ].ID ],
269+ }
270+ }
271+ d .mu .RUnlock ()
207272
208273 go func () {
209274 defer close (ci )
210- if d . IsEmpty () {
275+ if len ( items ) == 0 {
211276 return
212277 }
213- for i := range d .keys {
214- ci <- Item {
215- Key : d .keys [i ].Name ,
216- Value : d .values [d .keys [i ].ID ],
217- }
278+ for _ , item := range items {
279+ ci <- item
218280 }
219281 }()
220282
@@ -241,14 +303,16 @@ func (d *Dict) Update(vargs ...interface{}) bool {
241303 // iterables and scalars
242304 for item := range toIterable (vargs [i ]) {
243305 if item .Key == nil {
244- item .Key = d .size
306+ item .Key = d .Len ()
245307 }
246308 d .Set (item .Key , item .Value )
247309 }
248310 }
249311 return ver != d .Version ()
250312}
251313
314+ // String implements the fmt.Stringer interface to print d similar to a Python dict.
315+ // Returns a formatted string with the keys and values of the dict.
252316func (d * Dict ) String () string {
253317 items := make ([]string , 0 , d .Len ())
254318 for item := range d .Items () {
0 commit comments