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  }