File: ./doc.go
   1 /*
   2 # builtinplus
   3 
   4 This package has extras which don't require functionality from any package.
   5 
   6 Types
   7 =====
   8 
   9     AutoIndexer     an automatic assigner of non-negative integers to any
  10                     string given to it, and which remembers previous values
  11 
  12     Object          an ordered map, also usable as an unordered map
  13 
  14     RingBuffer      a circular/ring-buffer, which remembers up to the latest
  15                     n values put into it
  16 
  17     RingMap         a ring-buffer-like map, circularly reusing its values
  18                     when indexing by new keys
  19 
  20 Functions
  21 =========
  22 
  23     Index           allows backward or circular indexing of slices
  24 
  25     IsApp64Bit      checks if the app (target architecture) is 64-bit
  26 
  27     Max             like math.Max, but for all rankable/sortable types
  28 
  29     Min             like math.Min, but for all rankable/sortable types
  30 
  31     Slice           allows a safer and more flexible use of slicing indices
  32 */
  33 package builtinplus

     File: ./functions.go
   1 package builtinplus
   2 
   3 // CompactSlice shrinks a slice in-place, meaning later items can be copied
   4 // over what (formerly) previous items were along a slice, using a boolean
   5 // callback indicating whether each item is being `kept`, so to speak.
   6 //
   7 // The result's length is at most the original slice's length, and since the
   8 // original slice is updated in-place, this func never allocates.
   9 func CompactSlice[V any](x []V, fn func(v V) bool) []V {
  10     // start is the index of the first item failing the boolean test;
  11     // the name refers to where the loop at the end of this func starts
  12     start := -1
  13 
  14     // test leading items, until test fails for the 1st time: if many
  15     // leading items, let alone all items, pass the test, value-copies
  16     // are minimized
  17     for i, v := range x {
  18         if !fn(v) {
  19             start = i
  20             break
  21         }
  22     }
  23 
  24     // if all items are being kept, just return the full original slice
  25     if start < 0 {
  26         return x
  27     }
  28 
  29     // copy leading streak of passing values
  30     compact := x[:start]
  31     copy(compact, x[:start])
  32 
  33     // copy all later values passing the test
  34     for _, v := range x[start:] {
  35         if fn(v) {
  36             compact = append(compact, v)
  37         }
  38     }
  39     return compact
  40 }
  41 
  42 // IfElse almost implements the C-style ternary/conditional operator.
  43 //
  44 // The only difference is that this involves a func call, which means both
  45 // results are always evaluated: this isn't a problem if both alternatives
  46 // are constant values or very simple expressions, but you may want to use
  47 // an explicit if-else otherwise.
  48 //
  49 // The use-case for this function is a 1-line variable initialization which
  50 // uses either of 2 constants. This func is especially handy when there are
  51 // several such declarations, making such code way more compact.
  52 //
  53 // # Examples
  54 //
  55 // x := IfElse(len(v) == 0, "empty", "not empty")
  56 //
  57 // bool2str := IfElse(v, "true", "false")
  58 //
  59 // max = IfElse(max >= v, max, v)
  60 func IfElse[V any](cond bool, yes, no V) V {
  61     if cond {
  62         return yes
  63     }
  64     return no
  65 }
  66 
  67 // Min is a generic shortcut which, when used with judgement, lets you write
  68 // more readable code, by avoiding the extra clutter of finding the minimum
  69 // of 2 integers directly. It's best used where your code is already nested.
  70 func Min[V Ordered](x, y V) V {
  71     return IfElse(x <= y, x, y)
  72 }
  73 
  74 // Max is a generic shortcut which, when used with judgement, lets you write
  75 // more readable code, by avoiding the extra clutter of finding the maximum
  76 // of 2 integers directly. It's best used where your code is already nested.
  77 func Max[V Ordered](x, y V) V {
  78     return IfElse(x >= y, x, y)
  79 }
  80 
  81 // ResolveAlias does what it says, returning either the original value given,
  82 // or its substituted/dealiased equivalent; the returned boolean is whether
  83 // a substitution happened.
  84 //
  85 // The point of using this func, instead of just doing it explicitly, is to
  86 // simplify/flatten control-flow in already-nested code.
  87 //
  88 // This func is also handy when multiple dealiasing steps are needed, as it
  89 // makes intentions much clearer than using multiple if-statements directly.
  90 func ResolveAlias[V comparable](k V, aliases map[V]V) (v V, ok bool) {
  91     if v, ok := aliases[k]; ok {
  92         return v, true
  93     }
  94     return k, false
  95 }
  96 
  97 // ExactFloat64 tries to convert an int64 into a float64 which represents
  98 // the original value exactly.
  99 func ExactFloat64(n int64) (f float64, ok bool) {
 100     const limit = 1 << 53
 101     if -limit <= n && n <= +limit {
 102         return float64(n), true
 103     }
 104     return 0, false
 105 }
 106 
 107 // Is64Bit checks if your app is a 64-bit one: if so, it means pointers
 108 // take 8 bytes, an int is effectively the same as an int64, and so on.
 109 //
 110 // This func doesn't use build directives, being `multi-platform` as is.
 111 func Is64Bit() bool {
 112     v := 1 << 31
 113     return 2*v > 0
 114 }

     File: ./functions_test.go
   1 package builtinplus
   2 
   3 import (
   4     "strconv"
   5     "testing"
   6 )
   7 
   8 func TestCompactSlice(t *testing.T) {
   9     // reusable boolean funcs to keep test-case definitions compact
  10     allGood := func(n int) bool { return true }
  11     atLeast10 := func(n int) bool { return n >= 10 }
  12     isEven := func(n int) bool { return n%2 == 0 }
  13 
  14     var tests = []struct {
  15         input    []int
  16         expected []int
  17         test     func(n int) bool
  18     }{
  19         // empty input slice
  20         {[]int{}, []int{}, allGood},
  21         // non-empty input, all items kept
  22         {[]int{1, 2, 3}, []int{1, 2, 3}, allGood},
  23         // non-empty mix of callback results
  24         {[]int{1, 2, 3}, []int{2}, isEven},
  25         // non-empty input, no items kept
  26         {[]int{1, 2, 3}, []int{}, atLeast10},
  27         // leading ok-streak, no later items kept
  28         {[]int{10, 20, 30, -1, 11, 61}, []int{10, 20, 30}, isEven},
  29         // leading ok-streak, some later items kept
  30         {[]int{10, 20, 30, -1, 11, 6}, []int{10, 20, 30, 6}, isEven},
  31         // trailing ok-streak, no earlier items kept
  32         {[]int{1, 3, 10, 20, 30}, []int{10, 20, 30}, isEven},
  33         // trailing ok-streak, some earlier items kept
  34         {[]int{100, 3, 10, 20, 30}, []int{100, 10, 20, 30}, isEven},
  35     }
  36 
  37     for i, tc := range tests {
  38         t.Run(strconv.Itoa(i), func(t *testing.T) {
  39             src := append([]int{}, tc.input...)
  40             got := CompactSlice(src, tc.test)
  41             if !checkSlice(got, tc.expected) {
  42                 t.Fatalf(`expected %#v, instead of %#v`, tc.expected, got)
  43             }
  44         })
  45     }
  46 }
  47 
  48 func Test64BitCheck(t *testing.T) {
  49     t.Run(`32-bit`, func(t *testing.T) {
  50         v := 1 << 31
  51         k := 2 * int32(v)
  52         if k > 0 {
  53             t.Fatalf(`%d isn't positive`, k)
  54         }
  55     })
  56 
  57     t.Run(`64-bit`, func(t *testing.T) {
  58         v := 1 << 31
  59         k := 2 * int64(v)
  60         if !(k > 0) {
  61             t.Fatalf(`%d isn't negative`, k)
  62         }
  63     })
  64 }
  65 
  66 // checkSlice tests if 2 slices have the same length and items
  67 func checkSlice[V comparable](x, y []V) bool {
  68     if len(x) != len(y) {
  69         return false
  70     }
  71 
  72     for i := range x {
  73         if x[i] != y[i] {
  74             return false
  75         }
  76     }
  77     return true
  78 }

     File: ./go.mod
   1 module builtinplus
   2 
   3 go 1.18

     File: ./indices.go
   1 package builtinplus
   2 
   3 // AutoIndexer automatically assigns an integer to each new distinct value met,
   4 // starting from 0, and incrementing by 1 for each new item.
   5 type AutoIndexer struct {
   6     index map[string]int
   7     count int
   8 }
   9 
  10 // Get assigns an index for the string given: if value wasn't indexed before,
  11 // a distinct number is assigned to it on the spot.
  12 func (a *AutoIndexer) Get(s string) int {
  13     if a.index == nil {
  14         a.index = make(map[string]int)
  15     }
  16 
  17     if i, ok := a.index[s]; ok {
  18         return i
  19     }
  20 
  21     i := a.count
  22     a.index[s] = i
  23     a.count++
  24     return i
  25 }
  26 
  27 // Add is like method Get, but has no return value, making your intention to
  28 // populate the index more obvious.
  29 func (a *AutoIndexer) Add(s string) {
  30     a.Get(s)
  31 }
  32 
  33 // Has checks whether a string is already indexed, without changing anything.
  34 func (a AutoIndexer) Has(s string) bool {
  35     if a.index == nil {
  36         return false
  37     }
  38     _, ok := a.index[s]
  39     return ok
  40 }
  41 
  42 // Len gives you the index length back, which isn't necessarily the same as
  43 // counting unique indices.
  44 // func (a *AutoIndexer) Len() int {
  45 //  return len(a.index)
  46 // }
  47 
  48 // Reset resets an auto-indexer to the initial state given, which can safely be
  49 // nil. Note that the initial state can introduce different values leading to
  50 // the same index, so if your map makes that happen, make sure it's on purpose.
  51 //
  52 // The initial state is copied, so the auto-indexer will never change it.
  53 //
  54 // You can use this map before you start indexing your actual data using method
  55 // Get. For example, you can set the empty string to -1, an index commonly used
  56 // to mean `not found`, and which is distinct from the non-negative indices all
  57 // other strings would give you back.
  58 //
  59 // # Examples
  60 //
  61 // var categ AutoIndexer
  62 // categ.Reset(0, map[string]int{"": -1}) // negative index means `not found`
  63 //
  64 //  for _, v := range data {
  65 //      categ.Set(v) // index each new unique value, starting from 0
  66 //  }
  67 //
  68 // ...
  69 // someFunc(categ.Get(s))
  70 //
  71 // var worldRegions AutoIndexer
  72 //
  73 //  worldRegions.Reset(8, map[string]int{
  74 //      "":              0,
  75 //      "Antarctica":    1,
  76 //      "Africa":        2,
  77 //      "Asia":          3,
  78 //      "Europe":        4,
  79 //      "North America": 5,
  80 //      "South America": 6,
  81 //      "Oceania":       7,
  82 //  })
  83 //
  84 // // dynamically expand index with other geographic names
  85 //
  86 //  for _, v := range data {
  87 //      worldRegions.Set(v.Region)
  88 //  }
  89 func (a *AutoIndexer) Reset(start int, defaults map[string]int) {
  90     a.index = make(map[string]int)
  91     a.count = start
  92     for k, v := range defaults {
  93         a.index[k] = v
  94     }
  95 }
  96 
  97 // Loop lets you access the indexed items via a callback. Notice how the callback
  98 // lets you quit the loop via its boolean return, should you want to.
  99 func (a *AutoIndexer) Loop(f func(k string, v int) (keepLooping bool)) {
 100     for k, v := range a.index {
 101         if !f(k, v) {
 102             return
 103         }
 104     }
 105 }
 106 
 107 // Index gets a slice's item at the index given, allowing backwards-indexing
 108 // when using negative values, and circular-buffer indexing otherwise.
 109 //
 110 // # Examples
 111 //
 112 // v := Index(data, i)
 113 // first := Index(data, 0)
 114 // last := Index(data, -1)
 115 // two := Index([]int{1, 2, 3}, 4)
 116 func Index[V any](arr []V, i int) V {
 117     if len(arr) == 0 || i < -len(arr) {
 118         var zero V
 119         return zero
 120     }
 121 
 122     // circular indexing
 123     if i >= 0 {
 124         return arr[i%len(arr)]
 125     }
 126 
 127     // backward indexing
 128     return arr[i+len(arr)]
 129 }
 130 
 131 // Slice sub-slices a slice using some flexibility over the indices used, as
 132 // well as safety.
 133 func Slice[V any](arr []V, i, j int) []V {
 134     if len(arr) == 0 {
 135         return nil
 136     }
 137 
 138     if i >= len(arr) && j >= len(arr) {
 139         // both indices are both beyond the last valid index
 140         return nil
 141     }
 142     if i < -len(arr) && j < -len(arr) {
 143         // both indices are both beyond the least valid negative index
 144         return nil
 145     }
 146 
 147     // backward indexing
 148     if i < 0 {
 149         i += len(arr)
 150     }
 151     if j < 0 {
 152         j += len(arr)
 153     }
 154 
 155     if i < j {
 156         return arr[i:j]
 157     }
 158 
 159     // indices aren't ordered correctly
 160     return nil
 161 }

     File: ./indices_test.go
   1 package builtinplus
   2 
   3 import (
   4     "testing"
   5 )
   6 
   7 func TestEmptyAutoIndexer(t *testing.T) {
   8     var tests = []struct {
   9         Key      string
  10         Expected int
  11     }{
  12         {``, 0},
  13         {`abc`, 1},
  14         {`123`, 2},
  15         {`abc`, 1},
  16     }
  17 
  18     var ai AutoIndexer
  19     for _, tc := range tests {
  20         t.Run(tc.Key, func(t *testing.T) {
  21             if got := ai.Get(tc.Key); got != tc.Expected {
  22                 t.Fatalf(`expected %d, but got %d instead`, tc.Expected, got)
  23             }
  24         })
  25     }
  26 }
  27 
  28 func TestAutoIndexerCategoriesExample(t *testing.T) {
  29     var tests = []struct {
  30         Key      string
  31         Expected int
  32     }{
  33         {`xyz`, 0},
  34         {``, -1},
  35         {`abc`, 1},
  36         {`.`, 2},
  37         {`abc`, 1},
  38     }
  39 
  40     var categories AutoIndexer
  41 
  42     // negative indices mean `not found`
  43     categories.Reset(0, map[string]int{``: -1})
  44 
  45     for _, v := range tests {
  46         // index each new unique value, starting from 0
  47         categories.Add(v.Key)
  48     }
  49 
  50     for _, tc := range tests {
  51         t.Run(tc.Key, func(t *testing.T) {
  52             if got := categories.Get(tc.Key); got != tc.Expected {
  53                 t.Fatalf(`expected %d, but got %d instead`, tc.Expected, got)
  54             }
  55         })
  56     }
  57 }
  58 
  59 func TestAutoIndexerRegionsExample(t *testing.T) {
  60     var worldRegions AutoIndexer
  61     worldRegions.Reset(8, map[string]int{
  62         ``:              0,
  63         `Antarctica`:    1,
  64         `Africa`:        2,
  65         `Asia`:          3,
  66         `Europe`:        4,
  67         `North America`: 5,
  68         `South America`: 6,
  69         `Oceania`:       7,
  70     })
  71 
  72     // dynamically expand index with other geographic names
  73     for _, s := range []string{`Central America`} {
  74         worldRegions.Add(s)
  75     }
  76 
  77     var tests = []struct {
  78         Key      string
  79         Expected int
  80     }{
  81         {``, 0},
  82         {`Antarctica`, 1},
  83         {`Africa`, 2},
  84         {`Asia`, 3},
  85         {`Europe`, 4},
  86         {`North America`, 5},
  87         {`South America`, 6},
  88         {`Oceania`, 7},
  89         {`Central America`, 8},
  90     }
  91 
  92     for _, tc := range tests {
  93         t.Run(tc.Key, func(t *testing.T) {
  94             if got := worldRegions.Get(tc.Key); got != tc.Expected {
  95                 t.Fatalf(`expected %d, but got %d instead`, tc.Expected, got)
  96             }
  97         })
  98     }
  99 }

     File: ./maps.go
   1 package builtinplus
   2 
   3 // Assign works like JavaScript's Object.assign, allowing you to update the
   4 // 1st map given using any number of extra maps.
   5 //
   6 // The result is always the 1st argument, which was changed in place, or a
   7 // newly-created map if the 1st argument was nil: this means this func never
   8 // gives nil values back, making its results safe to use.
   9 //
  10 // # Examples
  11 //
  12 // // it doesn't even matter if defaultConfig was nil before this
  13 // defaultConfig = Assign(defaultConfig, customSettings)
  14 //
  15 // // shallow-copying is a side-effect of Assign on an empty/nil map
  16 // kvcopy := Assign(nil, kv)
  17 //
  18 // // most generally, Assign can also merge multiple maps into one
  19 // merged := Assign(defaultConfig, kv1, kv2)
  20 //
  21 // // assume this is a general default config: if go allowed this
  22 // // to be a constant, it would have been declared so; if go allowed
  23 // // constant fields, they would've all been declared so as well
  24 //
  25 //  var defaults = map[string]any{
  26 //      ...
  27 //  }
  28 //
  29 //  func doSomething(..., custom map[string]string) {
  30 //      // 1st arg being nil prevents changing the global default config
  31 //      cfg := Assign(nil, defaults, custom)
  32 //      ...
  33 //  }
  34 func Assign[K comparable, V any](dest map[K]V, src ...map[K]V) map[K]V {
  35     if dest == nil {
  36         // allocate for (at least) the exactly-needed capacity
  37         n := 0
  38         for _, v := range src {
  39             n += len(v)
  40         }
  41         dest = make(map[K]V, n)
  42     }
  43 
  44     for _, kv := range src {
  45         for k, v := range kv {
  46             dest[k] = v
  47         }
  48     }
  49     return dest
  50 }
  51 
  52 // Mappend is for maps what append is for slices: the func creates/grows its
  53 // 1st argument and is safe to use with nil values. Since maps are pointers
  54 // behind the scenes, the 1st arg is updated in place. This func never gives
  55 // nil values back, making its results safe to use.
  56 //
  57 // This makes returning the 1st arg back redundant, but allows identical use
  58 // patterns as append with slices. It also gives you a convenient to way to
  59 // ensure maps aren't nil.
  60 //
  61 // # Examples
  62 //
  63 // // override default config with custom map, which needs not be complete
  64 // options = Mappend(options, overrides)
  65 //
  66 // // shallow-copying is a side-effect of Mappend on an empty/nil map
  67 // kvcopy := Mappend(nil, kv)
  68 //
  69 // // data starts as a nil map, but it's safe to Mappend on it in a loop,
  70 // // just as you'd do with append and a slice
  71 // var data map[...]...
  72 //
  73 //  for _, kv := range src {
  74 //      data = Mappend(data, kv)
  75 //  }
  76 func Mappend[K comparable, V any](dest map[K]V, src map[K]V) map[K]V {
  77     if dest == nil {
  78         dest = make(map[K]V, len(src))
  79     }
  80 
  81     for k, v := range src {
  82         dest[k] = v
  83     }
  84     return dest
  85 }
  86 
  87 // LookupMaps tries to lookup a key using several maps in the order given.
  88 func LookupMaps[K comparable, V any](k K, maps ...map[K]V) (v V, found bool) {
  89     for _, kv := range maps {
  90         if v, ok := kv[k]; ok {
  91             return v, true
  92         }
  93     }
  94 
  95     var zero V // zero value for the parametric type used
  96     return zero, false
  97 }
  98 
  99 // SetValue updates a map, using slices/arrays when existing keys are being
 100 // reused: this means some values which weren't slices can become ones, the
 101 // second time their key is being assigned to.
 102 func SetValue[K comparable](kv map[K]any, k K, v any) {
 103     if prev, ok := kv[k]; ok {
 104         // if already a slice, append value to it
 105         if slice, ok := prev.([]any); ok {
 106             kv[k] = append(slice, v)
 107             return
 108         }
 109 
 110         // if not a slice, turn it into a 2-item slice
 111         kv[k] = []any{prev, v}
 112         return
 113     }
 114 
 115     // value is brand new for the key given
 116     kv[k] = v
 117 }

     File: ./maps.txt
   1 package builtinplus
   2 
   3 // RingMap is an associative array with ring-buffer-like behavior, as it
   4 // cyclically reuses the items it's given initially.
   5 type RingMap[K comparable, V any] struct {
   6     // values is the source of all possible values keys can associate to
   7     values []V
   8 
   9     // kv has all key-value associations, the keys always coming from
  10     // outside this type, the values always drawn from `inside`
  11     kv map[K]int
  12 }
  13 
  14 // NewRingMap creates a new RingMap. If given an empty slice of values, the
  15 // RingMap will always associate the zero value to any key, and will never
  16 // grow internally.
  17 func NewRingMap[K comparable, V any](values []V) RingMap[K, V] {
  18     return NewRingMapSize[K](values, -1)
  19 }
  20 
  21 // NewRingMapSize makes a new RingMap, allocating using the capacity given:
  22 // invalid capacities are ignored safely. If given an empty slice of values,
  23 // the RingMap will always associate the zero value to any key, and never
  24 // grow internally.
  25 func NewRingMapSize[K comparable, V any](values []V, cap int) RingMap[K, V] {
  26     if len(values) == 0 {
  27         return RingMap[K, V]{}
  28     }
  29 
  30     // this check also prevents negative capacities
  31     if cap < len(values) {
  32         cap = len(values)
  33     }
  34 
  35     return RingMap[K, V]{
  36         values: values,
  37         kv:     make(map[K]int, cap),
  38     }
  39 }
  40 
  41 // Get returns the associated value for the key given: since the association
  42 // expands when a new key is met, this lookup always succeeds. If the RingMap
  43 // was created with an empty slice of values, the result here is always the
  44 // zero value.
  45 func (rm RingMap[K, V]) Get(k K) V {
  46     if len(rm.values) == 0 {
  47         var zero V
  48         return zero
  49     }
  50 
  51     if i, ok := rm.kv[k]; ok {
  52         return rm.values[i]
  53     }
  54 
  55     // divisor was zero-checked at func start
  56     i := len(rm.kv) % len(rm.values)
  57     rm.kv[k] = i
  58     return rm.values[i]
  59 }
  60 
  61 // Len returns the number of keys (length) of the map: this is in contrast to
  62 // func Period, which returns the number of values keys can associate to.
  63 func (rm RingMap[K, V]) Len() int {
  64     return len(rm.kv)
  65 }
  66 
  67 // Period returns how many values are available: this is in contrast to func
  68 // Len, which returns how many keys are currently associated to those values,
  69 // where multiple different keys can lead to the same value.
  70 func (rm RingMap[K, V]) Period() int {
  71     return len(rm.values)
  72 }

     File: ./maps_test.go
   1 package builtinplus
   2 
   3 import "testing"
   4 
   5 func TestAssign(t *testing.T) {
   6     var tests = []struct {
   7         Args     []map[string]int
   8         Expected map[string]int
   9     }{
  10         {
  11             []map[string]int{},
  12             make(map[string]int),
  13         },
  14         {
  15             []map[string]int{{`aa`: -2, `x`: 0}, {`x`: 3}, {`aa`: -42}},
  16             map[string]int{`x`: 3, `aa`: -42},
  17         },
  18         {
  19             []map[string]int{{`c`: 3}, {`b`: 2}, {`a`: 1}},
  20             map[string]int{`a`: 1, `b`: 2, `c`: 3},
  21         },
  22     }
  23 
  24     for i, tc := range tests {
  25         res := Assign(nil, tc.Args...)
  26         if !checkMap(res, tc.Expected) {
  27             t.Fatalf(`test at index %d failed`, i)
  28             return
  29         }
  30     }
  31 }
  32 
  33 // checkMap checks if 2 maps of comparable types have the same key-value pairs
  34 func checkMap[K comparable, V comparable](x, y map[K]V) bool {
  35     if len(x) != len(y) {
  36         return false
  37     }
  38 
  39     for k, v := range x {
  40         if o, ok := y[k]; !ok || v != o {
  41             return false
  42         }
  43     }
  44     return true
  45 }

     File: ./maps_test.txt
   1 package builtinplus
   2 
   3 import "testing"
   4 
   5 func TestRingMap(t *testing.T) {
   6     var tests = []struct {
   7         Categories []string
   8         Inputs     []string
   9         Expected   map[string]string
  10     }{
  11         {
  12             []string{`a`, `b`, `c`, `d`},
  13             []string{},
  14             map[string]string{},
  15         },
  16         {
  17             []string{`a`, `b`, `c`, `d`},
  18             []string{`first`, `second`},
  19             map[string]string{`first`: `a`, `second`: `b`},
  20         },
  21         {
  22             []string{`a`, `b`, `c`, `d`},
  23             []string{`first`, `second`, `first`},
  24             map[string]string{`first`: `a`, `second`: `b`},
  25         },
  26         {
  27             []string{`a`, `b`, `c`, `d`},
  28             []string{`1`, `2`, `3`, `4`},
  29             map[string]string{`1`: `a`, `2`: `b`, `3`: `c`, `4`: `d`},
  30         },
  31         {
  32             []string{`a`, `b`, `c`, `d`},
  33             []string{`1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `10`},
  34             map[string]string{
  35                 `1`: `a`, `2`: `b`, `3`: `c`, `4`: `d`, `5`: `a`,
  36                 `6`: `b`, `7`: `c`, `8`: `d`, `9`: `a`, `10`: `b`,
  37             },
  38         },
  39     }
  40 
  41     for i, tc := range tests {
  42         rm := NewRingMap[string](tc.Categories)
  43         res := make(map[string]string)
  44         for _, v := range tc.Inputs {
  45             res[v] = rm.Get(v)
  46         }
  47 
  48         if !checkMap(res, tc.Expected) {
  49             t.Fatalf(`%d: expected %#v instead of %#v`, i, tc.Expected, res)
  50             return
  51         }
  52     }
  53 }

     File: ./objects.go
   1 package builtinplus
   2 
   3 // Object is an ordered map, which is also still usable as an unordered map.
   4 // To access values in order, just loop over the Keys slice.
   5 //
   6 // An Object's explicit representation allows you to make shallow-copy subsets,
   7 // by simply using slices of the Keys, while using the same underlying Map.
   8 type Object[K comparable, V any] struct {
   9     // Keys has all the object's official keys, in first-insertion order.
  10     Keys []K
  11 
  12     // Map has all key-value associations, and may include unofficial
  13     // ones whose keys aren't in Keys, allowing for hidden aliases
  14     // to values which aren't repeated when looping over Keys.
  15     Map map[K]V
  16 }
  17 
  18 // NewObject is a constructor for type Object: this func returns objects
  19 // which are safe to change, since they allocate the Map field.
  20 func NewObject[K comparable, V any]() Object[K, V] {
  21     return NewObjectSize[K, V](-1)
  22 }
  23 
  24 // NewObjectSize is another constructor for type Object: it's like func
  25 // NewObject, but also takes a capacity, which can safely be negative,
  26 // meaning it should be ignored, and thus act exactly like func NewObject.
  27 func NewObjectSize[K comparable, V any](cap int) Object[K, V] {
  28     if cap < 0 {
  29         return Object[K, V]{Map: make(map[K]V)}
  30     }
  31 
  32     return Object[K, V]{
  33         Keys: make([]K, 0, cap),
  34         Map:  make(map[K]V, cap),
  35     }
  36 }
  37 
  38 // Assign is similar to func Assign, but updates an Object in place, instead
  39 // of a plain map.
  40 func (o *Object[K, V]) Assign(src ...Object[K, V]) {
  41     // allow one of the same use-patterns available to func Assign,
  42     // where the destination starts unallocated
  43     // if o.Map == nil {
  44     //  o.Map = make(map[K]V)
  45     // }
  46 
  47     for _, kv := range src {
  48         for _, k := range kv.Keys {
  49             o.Set(k, kv.Map[k])
  50         }
  51     }
  52 }
  53 
  54 // Grow (re)allocates internal fields to accomodate the number of items
  55 // given; nothing happens if capacity of existing fields is already enough.
  56 func (o *Object[K, V]) Grow(n int) {
  57     if n <= len(o.Map) {
  58         return
  59     }
  60 
  61     prevKeys := o.Keys
  62     o.Keys = make([]K, len(prevKeys), n)
  63     copy(o.Keys, prevKeys)
  64 
  65     prevMap := o.Map
  66     o.Map = make(map[K]V, n)
  67     for k, v := range prevMap {
  68         o.Map[k] = v
  69     }
  70 }
  71 
  72 // Set correctly adds/changes the key-value pair given, ensuring field Keys
  73 // has non-repeating items kept in first-insertion order.
  74 func (o *Object[K, V]) Set(k K, v V) {
  75     if _, ok := o.Map[k]; ok {
  76         o.Map[k] = v
  77     } else {
  78         o.Keys = append(o.Keys, k)
  79         o.Map[k] = v
  80     }
  81 }
  82 
  83 // Len returns the number of official keys.
  84 func (o Object[K, V]) Len() int {
  85     return len(o.Keys)
  86 }
  87 
  88 // Loop is a convenience func to loop over all official key-value pairs in
  89 // an object, in order of appearance from the object's Keys slice. Callbacks
  90 // can quit the loop early by returning false.
  91 func (o Object[K, V]) Loop(fn func(k K, v V) bool) {
  92     for _, k := range o.Keys {
  93         if !fn(k, o.Map[k]) {
  94             return
  95         }
  96     }
  97 }
  98 
  99 // LoopError is a convenience func similar to func Loop, except it uses
 100 // non-nil errors to quit early, instead of false booleans.
 101 func (o Object[K, V]) LoopError(fn func(k K, v V) error) error {
 102     for _, k := range o.Keys {
 103         if err := fn(k, o.Map[k]); err != nil {
 104             return err
 105         }
 106     }
 107     return nil
 108 }

     File: ./rings.go
   1 package builtinplus
   2 
   3 // RingBuffer is a circular/ring-buffer, a slice which keeps up to the latest
   4 // n items given to it.
   5 type RingBuffer[V any] struct {
   6     // Max is the maximum number of items the buffer can hold at any time.
   7     Max int
   8 
   9     // items has all items as 2 contiguous sub-slices in a single slice
  10     items []V
  11 
  12     // pos is the index marking the end/start of the 2 sub-slices, and
  13     // which runs around all valid indices of the items slice
  14     pos int
  15 }
  16 
  17 // Grow ensures internal buffer-capacity is allocated/available for the number
  18 // of values given, updating field Max (if needed) in the process.
  19 func (rb *RingBuffer[V]) Grow(cap int) {
  20     if cap < 1 {
  21         // ensure full-cap even when given a non-sense value
  22         rb.fullCap()
  23         return
  24     }
  25 
  26     if rb.Max < cap {
  27         rb.Max = cap
  28     }
  29 
  30     // ensure full-cap, whether Max was updated or not
  31     rb.fullCap()
  32 }
  33 
  34 // Insert sets/adds the item given to the buffer, possibly forgetting an
  35 // existing one, when the buffer is already full.
  36 func (rb *RingBuffer[V]) Insert(v V) {
  37     if rb.Max < 1 {
  38         return
  39     }
  40 
  41     if len(rb.items) < rb.Max {
  42         rb.items = append(rb.items, v)
  43         rb.pos = len(rb.items) - 1
  44     } else {
  45         rb.pos = rb.nextPos()
  46         rb.items[rb.pos] = v
  47     }
  48 }
  49 
  50 // Len returns the current number of items in the buffer.
  51 func (rb *RingBuffer[V]) Len() int {
  52     return len(rb.items)
  53 }
  54 
  55 // Parts returns 2 slices, which together give all buffer-items in order of
  56 // insertion.
  57 func (rb RingBuffer[V]) Parts() (first []V, second []V) {
  58     if len(rb.items) == 0 {
  59         return nil, nil
  60     }
  61 
  62     pos := rb.nextPos()
  63     return rb.items[pos:], rb.items[:pos]
  64 }
  65 
  66 // nextPos returns the next position, and relies on the buffer not being
  67 // currently empty, to avoid a modulus-by-0
  68 func (rb RingBuffer[V]) nextPos() int {
  69     return (rb.pos + 1) % len(rb.items)
  70 }
  71 
  72 // ensure buffer is allocated to full capacity
  73 func (rb *RingBuffer[V]) fullCap() {
  74     // no capacity to allocate/expand the buffer to
  75     if rb.Max < 1 {
  76         return
  77     }
  78 
  79     // capacity is already enough
  80     if cap(rb.items) >= rb.Max {
  81         return
  82     }
  83 
  84     // reallocate/expand existing buffer
  85     items := make([]V, len(rb.items), rb.Max)
  86     first, second := rb.Parts()
  87     copy(items, first)
  88     copy(items[len(first):], second)
  89     rb.items = items
  90     rb.pos = len(rb.items) - 1
  91 }

     File: ./rings_test.go
   1 package builtinplus
   2 
   3 import (
   4     "fmt"
   5     "strconv"
   6     "testing"
   7 )
   8 
   9 // ringBufferIntTests is a table of test-cases shared by various funcs
  10 var ringBufferIntTests = []struct {
  11     max      int
  12     values   []int
  13     expected []int
  14 }{
  15     {-5, nil, nil},
  16     {0, nil, nil},
  17     {10, nil, nil},
  18     {1, []int{1, 2, 3}, []int{3}},
  19     {2, nil, nil},
  20     {2, []int{-10}, []int{-10}},
  21     {2, []int{1, 2, 3}, []int{2, 3}},
  22     {2, []int{1, 2, 3, 4, 5}, []int{4, 5}},
  23 }
  24 
  25 func TestRingBufferInsert(t *testing.T) {
  26     for i, tc := range ringBufferIntTests {
  27         name := fmt.Sprintf(`max=%d%#v`, tc.max, tc.values)
  28 
  29         t.Run(name, func(t *testing.T) {
  30             var buf RingBuffer[int]
  31             buf.Max = tc.max
  32 
  33             for _, v := range tc.values {
  34                 buf.Insert(v)
  35             }
  36 
  37             if !checkRingBuffer(buf, tc.expected) {
  38                 var got []int
  39                 first, second := buf.Parts()
  40                 got = append(got, first...)
  41                 got = append(got, second...)
  42                 const fs = `%d: expected %#v instead of %#v`
  43                 t.Fatalf(fs, i, tc.expected, got)
  44                 return
  45             }
  46         })
  47     }
  48 }
  49 
  50 func TestRingBufferGrow(t *testing.T) {
  51     const startExtra = 100
  52     extra := startExtra
  53 
  54     for i, tc := range ringBufferIntTests {
  55         t.Run(strconv.Itoa(i), func(t *testing.T) {
  56             var buf RingBuffer[int]
  57             buf.Max = tc.max
  58 
  59             for _, v := range tc.values {
  60                 buf.Insert(v)
  61             }
  62 
  63             buf.Grow(buf.Len() + extra)
  64 
  65             extra--
  66             if extra < 0 {
  67                 extra = startExtra
  68             }
  69 
  70             if !checkRingBuffer(buf, tc.expected) {
  71                 var got []int
  72                 first, second := buf.Parts()
  73                 got = append(got, first...)
  74                 got = append(got, second...)
  75                 const fs = `%d: expected %#v instead of %#v`
  76                 t.Fatalf(fs, i, tc.expected, got)
  77                 return
  78             }
  79         })
  80     }
  81 }
  82 
  83 // checkRingBuffer tests if a buffer has the items in the same order as those
  84 // from the slice given
  85 func checkRingBuffer[V comparable](buf RingBuffer[V], expected []V) bool {
  86     if len(expected) != buf.Len() {
  87         return false
  88     }
  89 
  90     var items []V
  91     first, second := buf.Parts()
  92     items = append(items, first...)
  93     items = append(items, second...)
  94 
  95     for j := range items {
  96         if expected[j] != items[j] {
  97             return false
  98         }
  99     }
 100     return true
 101 }

     File: ./sets.txt
   1 package builtinplus
   2 
   3 // Set is a keys-only map: given its trivial definition, its main point is
   4 // the self-documenting type name. Its values are empty structs to minimize
   5 // memory use.
   6 type Set[K comparable] map[K]struct{}
   7 
   8 // NewSet is the main constructor for type Set.
   9 func NewSet[K comparable]() Set[K] {
  10     return NewSetSize[K](-1)
  11 }
  12 
  13 // NewSetSize makes a new Set, allocating using the capacity given: invalid
  14 // capacities are ignored safely.
  15 func NewSetSize[K comparable](cap int) Set[K] {
  16     if cap < 1 {
  17         return make(Set[K])
  18     }
  19     return make(Set[K], cap)
  20 }
  21 
  22 // Has checks if a value is in a set.
  23 func (s Set[K]) Has(v K) bool {
  24     _, ok := s[v]
  25     return ok
  26 }
  27 
  28 // Insert adds a value to a set, unless that value is already in it.
  29 func (s Set[K]) Insert(v K) {
  30     s[v] = struct{}{}
  31 }
  32 
  33 // Len returns how many items the set has.
  34 func (s Set[K]) Len(v K) int {
  35     return len(s)
  36 }
  37 
  38 // MultiSet is a Set whose items have an associated count, so inserting the
  39 // same items more than once does have an effect.
  40 type MultiSet[K comparable] struct {
  41     // counts keeps track of all multi-set values and their counts
  42     counts map[K]int
  43 
  44     // total is the sum of all counts
  45     total int
  46 }
  47 
  48 // NewMultiSet is the main constructor for type MultiSet.
  49 func NewMultiSet[K comparable]() MultiSet[K] {
  50     return NewMultiSetSize[K](-1)
  51 }
  52 
  53 // NewMultiSetSize makes a new MultiSet, allocating using the capacity given:
  54 // invalid capacities are ignored safely.
  55 func NewMultiSetSize[K comparable](cap int) MultiSet[K] {
  56     if cap < 1 {
  57         return MultiSet[K]{counts: make(map[K]int)}
  58     }
  59     return MultiSet[K]{counts: make(map[K]int, cap)}
  60 }
  61 
  62 // Count returns the count for the value given, which can be zero if it's
  63 // not in the multi-set.
  64 func (s MultiSet[K]) Count(v K) int {
  65     return s.counts[v]
  66 }
  67 
  68 // Has checks if a value is in a multi-set.
  69 func (s MultiSet[K]) Has(v K) bool {
  70     _, ok := s.counts[v]
  71     return ok
  72 }
  73 
  74 // Insert adds a value to a multi-set.
  75 func (s *MultiSet[K]) Insert(v K) {
  76     s.counts[v]++
  77     s.total++
  78 }
  79 
  80 // Loop loops over all official values in an arbitrary order you can't rely
  81 // on: to quit the loop early, just return false in the callback.
  82 func (s MultiSet[K]) Loop(fn func(k K, v int) bool) {
  83     for k, v := range s.counts {
  84         if !fn(k, v) {
  85             return
  86         }
  87     }
  88 }
  89 
  90 // LoopError is similar to func Loop, except it uses non-nil errors to quit
  91 // early, instead of false booleans. Remember that loop-order is arbitrary,
  92 // so you can't rely on it.
  93 func (s MultiSet[K]) LoopError(fn func(k K, v int) error) error {
  94     for k, v := range s.counts {
  95         if err := fn(k, v); err != nil {
  96             return err
  97         }
  98     }
  99     return nil
 100 }
 101 
 102 // Total returns the same of all counts in the multi-set.
 103 func (s MultiSet[K]) Total(v K) int {
 104     return s.total
 105 }
 106 
 107 // Unique returns how many different items the multi-set has.
 108 func (s MultiSet[K]) Unique(v K) int {
 109     return len(s.counts)
 110 }

     File: ./types.go
   1 package builtinplus
   2 
   3 const (
   4     // MinInt8 is the lowest value an int8 can have.
   5     MinInt8 int8 = -(1 << 7)
   6 
   7     // MinInt16 is the lowest value an int16 can have.
   8     MinInt16 int16 = -(1 << 15)
   9 
  10     // MinInt32 is the lowest value an int32 can have.
  11     MinInt32 int32 = -(1 << 31)
  12 
  13     // MinInt64 is the lowest value an int64 can have.
  14     MinInt64 int64 = -(1 << 63)
  15 
  16     // MaxInt8 is the highest value an int8 can have.
  17     MaxInt8 int8 = 1<<7 - 1
  18 
  19     // MaxInt16 is the highest value an int16 can have.
  20     MaxInt16 int16 = 1<<15 - 1
  21 
  22     // MaxInt32 is the highest value an int32 can have.
  23     MaxInt32 int32 = 1<<31 - 1
  24 
  25     // MaxInt64 is the highest value an int64 can have.
  26     MaxInt64 int64 = 1<<63 - 1
  27 
  28     // MaxUint8 is the highest value a uint8 can have.
  29     MaxUint8 uint8 = 1<<8 - 1
  30 
  31     // MaxUint16 is the highest value a uint16 can have.
  32     MaxUint16 uint16 = 1<<16 - 1
  33 
  34     // MaxUint32 is the highest value a uint32 can have.
  35     MaxUint32 uint32 = 1<<32 - 1
  36 
  37     // MaxUint64 is the highest value a uint64 can have.
  38     MaxUint64 uint64 = 1<<64 - 1
  39 
  40     // KB is a binary kilo.
  41     KB = 1024
  42 
  43     // MB is a binary mega.
  44     MB = 1024 * KB
  45 
  46     // GB is a binary giga.
  47     GB = 1024 * MB
  48 
  49     // TB is a binary tera.
  50     TB = 1024 * GB
  51 
  52     // PB is a binary peta.
  53     PB = 1024 * TB
  54 
  55     // Kilo is a decimal kilo.
  56     Kilo = 1000
  57 
  58     // Mega is a decimal mega.
  59     Mega = 1000 * Kilo
  60 
  61     // Giga is a decimal giga.
  62     Giga = 1000 * Mega
  63 
  64     // Tera is a decimal tera.
  65     Tera = 1000 * Giga
  66 
  67     // Peta is a decimal peta.
  68     Peta = 1000 * Tera
  69 )
  70 
  71 // Ordered is any type supported by the <, <=, >, and >= operators: numeric
  72 // types complex64 and complex128 aren't clearly `ordered` due to the more
  73 // general nature of `complex` numbers vs. `real` ones.
  74 type Ordered interface {
  75     anyInt | anyUint | float32 | float64 | string
  76 }
  77 
  78 // anyInt is any signed-integer type
  79 type anyInt interface {
  80     int | int8 | int16 | int32 | int64
  81 }
  82 
  83 // anyUint is any unsigned-integer type
  84 type anyUint interface {
  85     uint | uint8 | uint16 | uint32 | uint64
  86 }