github.com/mitranim/gg@v0.1.17/coll.go (about) 1 package gg 2 3 import "encoding/json" 4 5 /* 6 Short for "valid primary key". Returns the primary key generated by the given 7 input, asserts that the key is non-zero, and returns the resulting key. 8 Used internally by `Coll`. 9 */ 10 func ValidPk[Key comparable, Val Pker[Key]](val Val) Key { 11 key := val.Pk() 12 if IsZero(key) { 13 panic(Errf(`unexpected empty key %v in %v`, Type[Key](), Type[Val]())) 14 } 15 return key 16 } 17 18 /* 19 Syntactic shortcut for making a `Coll` of the given arguments. Reuses the given 20 slice as-is with no reallocation. 21 */ 22 func CollOf[Key comparable, Val Pker[Key]](src ...Val) Coll[Key, Val] { 23 var tar Coll[Key, Val] 24 tar.Reset(src...) 25 return tar 26 } 27 28 /* 29 Syntactic shortcut for making a `Coll` from any number of source slices. When 30 called with exactly one argument, this reuses the given slice as-is with no 31 reallocation. 32 */ 33 func CollFrom[Key comparable, Val Pker[Key], Slice ~[]Val](src ...Slice) Coll[Key, Val] { 34 var tar Coll[Key, Val] 35 36 switch len(src) { 37 case 1: 38 tar.Reset(src[0]...) 39 default: 40 for _, src := range src { 41 tar.Add(src...) 42 } 43 } 44 45 return tar 46 } 47 48 /* 49 Short for "collection". Represents an ordered map where keys are automatically 50 derived from values. Keys must be non-zero. Similarly to a map, this ensures 51 value uniqueness by primary key, and allows efficient access by key. Unlike a 52 map, values in this type are ordered and can be iterated cheaply, because they 53 are stored in a publicly-accessible slice. However, as a tradeoff, this type 54 does not support deletion. 55 */ 56 type Coll[ 57 Key comparable, 58 Val Pker[Key], 59 ] struct { 60 Slice []Val `role:"ref"` 61 Index map[Key]int 62 } 63 64 // Same as `len(self.Slice)`. 65 func (self Coll[_, _]) Len() int { return len(self.Slice) } 66 67 // True if `.Len` <= 0. Inverse of `.IsNotEmpty`. 68 func (self Coll[_, _]) IsEmpty() bool { return self.Len() <= 0 } 69 70 // True if `.Len` > 0. Inverse of `.IsEmpty`. 71 func (self Coll[_, _]) IsNotEmpty() bool { return self.Len() > 0 } 72 73 // True if the index has the given key. 74 func (self Coll[Key, _]) Has(key Key) bool { 75 return MapHas(self.Index, key) 76 } 77 78 // Returns the value indexed on the given key, or the zero value of that type. 79 func (self Coll[Key, Val]) Get(key Key) Val { 80 return PtrGet(self.Ptr(key)) 81 } 82 83 /* 84 Short for "get required". Returns the value indexed on the given key. Panics if 85 the value is missing. 86 */ 87 func (self Coll[Key, Val]) GetReq(key Key) Val { 88 ptr := self.Ptr(key) 89 if ptr != nil { 90 return *ptr 91 } 92 panic(errCollMissing[Val](key)) 93 } 94 95 /* 96 Returns the value indexed on the given key and a boolean indicating if the value 97 was actually present. 98 */ 99 func (self Coll[Key, Val]) Got(key Key) (Val, bool) { 100 // Note: we must check `ok` because if the entry is missing, `ind` is `0`, 101 // which is invalid. 102 ind, ok := self.Index[key] 103 if ok { 104 return Got(self.Slice, ind) 105 } 106 return Zero[Val](), false 107 } 108 109 /* 110 Short for "pointer". Returns a pointer to the value indexed on the given key, or 111 nil if the value is missing. 112 */ 113 func (self Coll[Key, Val]) Ptr(key Key) *Val { 114 // Note: we must check `ok` because if the entry is missing, `ind` is `0`, 115 // which is invalid. 116 ind, ok := self.Index[key] 117 if ok { 118 return GetPtr(self.Slice, ind) 119 } 120 return nil 121 } 122 123 /* 124 Short for "pointer required". Returns a non-nil pointer to the value indexed 125 on the given key, or panics if the value is missing. 126 */ 127 func (self Coll[Key, Val]) PtrReq(key Key) *Val { 128 ptr := self.Ptr(key) 129 if ptr != nil { 130 return ptr 131 } 132 panic(errCollMissing[Val](key)) 133 } 134 135 /* 136 Idempotently adds each given value to both the inner slice and the inner index. 137 Every value whose key already exists in the index is replaced at the existing 138 position in the slice. 139 */ 140 func (self *Coll[Key, Val]) Add(src ...Val) *Coll[Key, Val] { 141 index := MapInit(&self.Index) 142 143 for _, val := range src { 144 key := ValidPk[Key](val) 145 ind, ok := index[key] 146 if ok { 147 self.Slice[ind] = val 148 continue 149 } 150 index[key] = AppendIndex(&self.Slice, val) 151 } 152 153 return self 154 } 155 156 /* 157 Same as `Coll.Add`, but panics if any inputs are redundant, as in, their primary 158 keys are already present in the index. 159 */ 160 func (self *Coll[Key, Val]) AddUniq(src ...Val) *Coll[Key, Val] { 161 index := MapInit(&self.Index) 162 163 for _, val := range src { 164 key := ValidPk[Key](val) 165 if MapHas(index, key) { 166 panic(Errf( 167 `unexpected redundant %v with key %v`, 168 Type[Val](), key, 169 )) 170 } 171 index[key] = AppendIndex(&self.Slice, val) 172 } 173 174 return self 175 } 176 177 /* 178 Replaces `.Slice` with the given slice and rebuilds `.Index`. Uses the slice 179 as-is with no reallocation. Callers must be careful to avoid modifying the 180 source data, which may invalidate the collection's index. 181 */ 182 func (self *Coll[Key, Val]) Reset(src ...Val) *Coll[Key, Val] { 183 self.Slice = src 184 self.Reindex() 185 return self 186 } 187 188 // Nullifies both the slice and the index. Does not preserve their capacity. 189 func (self *Coll[Key, Val]) Clear() *Coll[Key, Val] { 190 if self != nil { 191 self.Slice = nil 192 self.Index = nil 193 } 194 return self 195 } 196 197 /* 198 Rebuilds the inner index from the inner slice, without checking the validity of 199 the existing index. Can be useful for external code that directly modifies the 200 inner `.Slice`, for example by sorting it. This is NOT used when adding items 201 via `.Add`, which modifies the index incrementally rather than all-at-once. 202 */ 203 func (self *Coll[Key, Val]) Reindex() *Coll[Key, Val] { 204 slice := self.Slice 205 if len(slice) <= 0 { 206 self.Index = nil 207 return self 208 } 209 210 index := make(map[Key]int, len(slice)) 211 for ind, val := range slice { 212 index[ValidPk[Key](val)] = ind 213 } 214 self.Index = index 215 216 return self 217 } 218 219 /* 220 Swaps two elements both in `.Slice` and in `.Index`. Useful for sorting. 221 `.Index` may be nil, in which case it's unaffected. Slice indices must be 222 either equal or valid. 223 */ 224 func (self Coll[Key, _]) Swap(ind0, ind1 int) { 225 if ind0 == ind1 { 226 return 227 } 228 229 slice := self.Slice 230 val0, val1 := slice[ind0], slice[ind1] 231 slice[ind0], slice[ind1] = val1, val0 232 233 index := self.Index 234 if index != nil { 235 index[ValidPk[Key](val0)], index[ValidPk[Key](val1)] = ind1, ind0 236 } 237 } 238 239 // Implement `json.Marshaler`. Encodes the inner slice, ignoring the index. 240 func (self Coll[_, _]) MarshalJSON() ([]byte, error) { 241 return json.Marshal(self.Slice) 242 } 243 244 /* 245 Implement `json.Unmarshaler`. Decodes the input into the inner slice and 246 rebuilds the index. 247 */ 248 func (self *Coll[_, _]) UnmarshalJSON(src []byte) error { 249 err := json.Unmarshal(src, &self.Slice) 250 self.Reindex() 251 return err 252 } 253 254 // Free cast to equivalent `LazyColl`. 255 func (self *Coll[Key, Val]) LazyColl() *LazyColl[Key, Val] { 256 return (*LazyColl[Key, Val])(self) 257 }