You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rewrite with doubly-linked list for O(1) operations
Architecture changes:
- Replace slice-based implementation with map + doubly-linked list
- All operations now O(1) (except At and Range which are O(n))
- Delete: O(n) → O(1)
- PopFront: O(n) → O(1)
- MoveToEnd: O(n) → O(1)
Testing improvements:
- Add comprehensive race tests for GetOrSet with long mk()
- Add fuzz tests for random operation sequences
- Add property tests for order preservation and list integrity
- Add reentrancy tests for Range vs RangeLocked
- Add big-N tests (10k entries) with allocation tracking
- Add benchmarks for all operations with concurrency patterns
- Maintain 100% test coverage
Documentation:
- Rewrite README from scratch for v1.0
- Document O(1) complexity for all operations
- Add clear warnings for RangeLocked deadlock potential
- Add clear warnings for GetOrSet holding write lock
- Add LRU cache example showcasing O(1) MoveToEnd
CI fixes:
- Update Go versions to 1.22, 1.23, 1.24
- Update golangci-lint to v2 (from v1)
- Update golangci-lint-action to v8 (from v4)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
@@ -10,7 +10,7 @@ A thread-safe, generic ordered map for Go that maintains insertion order while p
10
10
## Features
11
11
12
12
-**Insertion order preservation** - Iterates in the order items were added (unlike Go's built-in maps)
13
-
-**O(1) lookups** - Fast key-based access using internal index
13
+
-**O(1) operations** - Fast lookups, deletes, and moves using map + doubly-linked list
14
14
-**Thread-safe** - All operations use internal locking (RWMutex)
15
15
-**Zero-value usable** - No constructor required: `var om OrderedMap[K,V]` just works
16
16
-**Generic** - Works with any comparable key type and any value type
@@ -68,6 +68,16 @@ Go's built-in `map[K]V` has **random iteration order** by design. This causes pr
68
68
-**FIFO/insertion-order semantics** - Process items in the order they were added
69
69
-**Deterministic testing** - Avoid flaky tests from random map iteration
70
70
71
+
## Implementation
72
+
73
+
OrderedMap uses a **map + doubly-linked list** hybrid approach:
74
+
75
+
-**map[K]*node** - O(1) lookups by key
76
+
-**Doubly-linked list** - O(1) deletes and moves, preserves insertion order
77
+
-**Cached length** - O(1) len() operation
78
+
79
+
This combination provides optimal performance for all operations except indexed access (At), which is O(n).
80
+
71
81
## API
72
82
73
83
### Creating
@@ -92,7 +102,7 @@ val, ok := om.Get("key")
92
102
// Has - Check existence (O(1))
93
103
if om.Has("key") { ... }
94
104
95
-
// Delete - Remove entry (O(n) - see notes)
105
+
// Delete - Remove entry (O(1))
96
106
deleted:= om.Delete("key")
97
107
98
108
// Len - Get size (O(1))
@@ -116,16 +126,54 @@ om.RangeBreak(func(key string, value int) bool {
116
126
117
127
**Important:** Range and RangeBreak take a **snapshot** before iterating, so the callback can safely modify the map without causing deadlocks.
118
128
129
+
### Zero-Allocation Iteration
130
+
131
+
```go
132
+
// RangeLocked - Iterate without allocating a snapshot
133
+
om.RangeLocked(func(key string, value int) {
134
+
// Process items
135
+
})
136
+
```
137
+
138
+
**⚠️ WARNING:** The callback in `RangeLocked`**MUST NOT** call any OrderedMap methods or deadlock will occur. Use `Range()` if you need to modify the map during iteration.
139
+
119
140
### Access First/Last
120
141
121
142
```go
122
-
// Front - Get first entry
143
+
// Front - Get first entry (O(1))
123
144
key, val, ok:= om.Front()
124
145
125
-
// Back - Get last entry
146
+
// Back - Get last entry (O(1))
126
147
key, val, ok:= om.Back()
148
+
149
+
// At - Get entry at index (O(n))
150
+
key, val, ok:= om.At(5)
127
151
```
128
152
153
+
### Queue Operations
154
+
155
+
```go
156
+
// PopFront - Remove and return first entry (O(1))
157
+
key, val, ok:= om.PopFront()
158
+
159
+
// PopBack - Remove and return last entry (O(1))
160
+
key, val, ok:= om.PopBack()
161
+
162
+
// MoveToEnd - Move key to end of order (O(1))
163
+
moved:= om.MoveToEnd("key")
164
+
```
165
+
166
+
### Cache Patterns
167
+
168
+
```go
169
+
// GetOrSet - Atomic get-or-create (O(1))
170
+
val, existed:= om.GetOrSet("key", func() int {
171
+
returnexpensiveComputation()
172
+
})
173
+
```
174
+
175
+
**⚠️ WARNING:** The `mk` function in `GetOrSet` is called **while holding the write lock**. Keep it fast and simple to avoid blocking other operations.
176
+
129
177
### Bulk Operations
130
178
131
179
```go
@@ -146,23 +194,26 @@ om.Reset()
146
194
147
195
| Operation | Complexity | Notes |
148
196
|-----------|-----------|-------|
149
-
| Set | O(1) | Amortized due to slice growth |
150
-
| Get | O(1) | Hash map lookup |
151
-
| Has | O(1) | Hash map lookup |
152
-
| Delete |**O(n)**| Must shift elements and reindex |
153
-
| Len | O(1) | Cached length |
154
-
| Range | O(n) | Snapshot + iteration |
155
-
| Keys/Values | O(n) | Returns defensive copy |
156
-
| Front/Back | O(1) | Direct slice access |
157
-
158
-
**Delete is O(n)** because it preserves insertion order by shifting elements. If you need frequent deletes with high performance, consider using tombstones + periodic compaction instead.
197
+
| Set |**O(1)**| Amortized due to map growth |
198
+
| Get |**O(1)**| Hash map lookup |
199
+
| Has |**O(1)**| Hash map lookup |
200
+
| Delete |**O(1)**| Doubly-linked list removal |
201
+
| PopFront |**O(1)**| Remove head of list |
202
+
| PopBack |**O(1)**| Remove tail of list |
203
+
| MoveToEnd |**O(1)**| Relink nodes |
204
+
| Len |**O(1)**| Cached length |
205
+
| Range |**O(n)**| Snapshot + iteration |
206
+
| RangeLocked |**O(n)**| No allocation, holds lock |
207
+
| Keys/Values |**O(n)**| Returns defensive copy |
208
+
| Front/Back |**O(1)**| Direct pointer access |
209
+
| At |**O(n)**| Must traverse list |
159
210
160
211
## Thread Safety
161
212
162
213
All operations are thread-safe via internal `sync.RWMutex`:
@@ -353,4 +384,4 @@ DocSpring, Inc. ([@DocSpring](https://github.com/DocSpring))
353
384
354
385
## Acknowledgments
355
386
356
-
Inspired by the need for stable UI rendering in terminal applications and the lack of a simple, thread-safe ordered map in Go's standard library.
387
+
Inspired by the need for stable UI rendering in terminal applications and the lack of a simple, thread-safe ordered map with O(1) operations in Go's standard library.
0 commit comments