├── go.mod ├── go_chainable ├── Makefile ├── .gitignore ├── LICENSE ├── maps ├── maps.go └── maps_test.go ├── lists ├── lists.go └── lists_test.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/neurocollective/go_chainable 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go_chainable: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neurocollective/go_chainable/HEAD/go_chainable -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | # using go8 alias to target go 1.18 beta binary 3 | go1.18beta1 test ./lists/ ./maps -coverprofile=coverage.out 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 neurocollective 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /maps/maps.go: -------------------------------------------------------------------------------- 1 | package maps 2 | 3 | import ( 4 | "fmt" 5 | "github.com/neurocollective/go_chainable/lists" 6 | ) 7 | 8 | // ordered HashMap type, wrapper around native map 9 | // because of key array, removes are slow 10 | // K 11 | type Map[K comparable, V comparable, R any] struct { 12 | NativeMap *map[K]V 13 | KeysList *lists.List[K, R] 14 | } 15 | 16 | func New[K comparable, V comparable, R any](nativeMap map[K]V) *Map[K, V, R] { 17 | 18 | keyList := lists.NewEmpty[K, R]() 19 | for key, _ := range nativeMap { 20 | keyList.Add(key) 21 | } 22 | newMap := Map[K, V, R]{ &nativeMap, keyList } 23 | return &newMap 24 | } 25 | 26 | func NewEmpty[K comparable, V comparable, R any]() *Map[K, V, R] { 27 | newMap := map[K]V{} 28 | newList := lists.NewEmpty[K, R]() 29 | return &Map[K, V, R]{ &newMap, newList } 30 | } 31 | 32 | // TODO -> loop through key array to create ordered string output 33 | func (h *Map[K, V, R]) String() string { 34 | return fmt.Sprint(*h.NativeMap) 35 | } 36 | 37 | func (h *Map[K, V, R]) Set(key K, value V) *Map[K, V, R] { 38 | theMap := *h.NativeMap 39 | _, exists := theMap[key] 40 | theMap[key] = value 41 | if exists { 42 | return h 43 | } 44 | h.KeysList.Add(key) 45 | return h 46 | } 47 | 48 | func (h *Map[K, V, R]) Get(key K) (V, bool) { 49 | val, found := (*h.NativeMap)[key] 50 | return val, found 51 | } 52 | 53 | // TODO - not yet implemented 54 | // func (h *Map[K, V, R]) Remove(key K) *Map[K, V, R] { 55 | // // remove from h.NativeMap, 56 | // // use h.KeysList.Remove(key) 57 | // return h 58 | // } 59 | 60 | // returns keys in order of being added 61 | // removed keys are gone and no longer part of the order 62 | func (h *Map[K, V, R]) Keys() *lists.List[K, R] { 63 | return h.KeysList 64 | } 65 | 66 | func (h *Map[K, V, R]) Values() *lists.List[V, R] { 67 | size := len(*h.KeysList.Array) 68 | values := make([]V, size) 69 | 70 | for index, key := range *h.KeysList.Array { 71 | values[index] = (*h.NativeMap)[key] 72 | } 73 | var cypher R 74 | return &lists.List[V, R]{ &values, cypher } 75 | } 76 | 77 | func (theMap *Map[K, V, R]) Map(mapper func(value V, key K, index int) R) *lists.List[R, R] { 78 | 79 | nativeMap := theMap.NativeMap 80 | keysArray := theMap.Keys().Raw() 81 | keysCount := len(keysArray) 82 | 83 | newArray := make([]R, keysCount) 84 | 85 | for index, key := range keysArray { 86 | value := (*nativeMap)[key] 87 | newArray[index] = mapper(value, key, index) 88 | } 89 | return lists.New[R, R](newArray) 90 | } 91 | 92 | func (theMap *Map[K, V, R]) Reduce( 93 | reducer func(accumulator R, value V, key K, index int) R, 94 | initial R, 95 | ) R { 96 | 97 | nativeMap := theMap.NativeMap 98 | keysArray := theMap.Keys().Raw() 99 | 100 | accumulator := initial 101 | for index, key := range keysArray { 102 | value := (*nativeMap)[key] 103 | accumulator = reducer(accumulator, value, key, index) 104 | } 105 | return accumulator 106 | } 107 | 108 | /* standalone functions */ 109 | 110 | func ResultTypeSwap[K comparable, V comparable, OldR any, NewR any] ( 111 | oldMap *Map[K, V, OldR], 112 | ) *Map[K, V, NewR] { 113 | newMap := New[K, V, NewR](*oldMap.NativeMap) 114 | return newMap 115 | } 116 | -------------------------------------------------------------------------------- /maps/maps_test.go: -------------------------------------------------------------------------------- 1 | package maps 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMapDotMap(t *testing.T) { 8 | theMap := NewEmpty[string, string, string]() 9 | 10 | theMap.Set("hey", "dude") 11 | theMap.Set("sup", "brah") 12 | 13 | mappedList := theMap.Map(func(value string, key string, i int) string { 14 | return key + "_" + value 15 | }) 16 | if error, value := mappedList.Get(0); error != nil || value != "hey_dude" { 17 | t.Error("unexpcted value from mappedList.Get(0) in TestMapDotMap, got " + value) 18 | } 19 | } 20 | 21 | func TestMapDotNew(t *testing.T) { 22 | nativeMap := make(map[string]string) 23 | nativeMap["sup"] = "brah" 24 | theMap := New[string, string, string](nativeMap) 25 | 26 | stringy := theMap.String() 27 | if stringy != "map[sup:brah]" { 28 | t.Error("unexpcted value in TestMapDotNew, got: " + stringy) 29 | } 30 | error, key := theMap.Keys().First() 31 | if error != nil { 32 | t.Error("error in TestMapDotNew, got: " + error.Error()) 33 | } 34 | if key != "sup" { 35 | t.Error("unexpcted key in TestMapDotNew, got: " + key) 36 | } 37 | } 38 | 39 | func TestMapDotValues(t *testing.T) { 40 | theMap := NewEmpty[string, string, any]() 41 | theMap.Set("sup", "brah") 42 | stringy := theMap.Values().String() 43 | if stringy != "[brah]" { 44 | t.Error("unexpcted value in TestMapDotNew, got: " + stringy) 45 | } 46 | } 47 | 48 | func TestMapDotNewEmpty(t *testing.T) { 49 | theMap := NewEmpty[string, string, string]() 50 | stringy := theMap.String() 51 | if stringy != "map[]" { 52 | t.Error("unexpected value in TestMapDotNewEmpty, got: " + stringy) 53 | } 54 | } 55 | 56 | func TestMapDotReduce(t *testing.T) { 57 | theMap := NewEmpty[string, string, string]() 58 | 59 | theMap.Set("hey", "dude") 60 | theMap.Set("sup", "brah") 61 | 62 | initial := "When I meet someone new, I always say: " 63 | message := theMap.Reduce(func(accumulator string, value string, key string, i int) string { 64 | return accumulator + key + " " + value + " " 65 | }, initial) 66 | expected := initial + "hey dude sup brah " 67 | if message != expected { 68 | t.Error("oh noes, got" + message + "in TestMapDotReduce") 69 | } 70 | } 71 | 72 | func TestMapDotString(t *testing.T) { 73 | theMap := NewEmpty[string, string, string]() 74 | 75 | theMap.Set("hey", "dude") 76 | theMap.Set("sup", "brah") 77 | 78 | stringy := theMap.String() 79 | 80 | if stringy != "map[hey:dude sup:brah]" { 81 | t.Error("oh noes, got" + stringy + "in TestMapDotString") 82 | } 83 | } 84 | 85 | func TestResultTypeSwap(t *testing.T) { 86 | mappy := NewEmpty[string, string, any]() 87 | mappy.Set("bruh", "braaah") 88 | 89 | valueOne, found := mappy.Get("bruh") 90 | if !found { 91 | t.Error("oh noes, could not find key \"bruh\" in mappy as expected in TestResultTypeSwap") 92 | } 93 | 94 | newMappy := ResultTypeSwap[string, string, any, string](mappy) 95 | valueTwo, foundTwo := newMappy.Get("bruh") 96 | 97 | if !foundTwo { 98 | t.Error("oh noes, could not find key \"bruh\" in NewMappy as expected in TestResultTypeSwap") 99 | } 100 | 101 | if valueOne != valueTwo { 102 | t.Error("oh noes, got" + valueTwo + " after swap, in TestResultTypeSwap") 103 | } 104 | } -------------------------------------------------------------------------------- /lists/lists.go: -------------------------------------------------------------------------------- 1 | package lists 2 | 3 | import ( 4 | "fmt" 5 | "errors" 6 | ) 7 | 8 | /* 9 | List 10 | immutable by default. 11 | Any operations that involve change create a new List and return a pointer to the new List 12 | */ 13 | 14 | 15 | // TODO - for immutability, does the pointer to `List` need to be new each time, or just the underlying `Array`? 16 | type List[T any, R any] struct { 17 | Array *[]T 18 | Value R 19 | } 20 | 21 | // return underlying array 22 | func (list *List[T, R]) Raw() []T { 23 | return *list.Array 24 | } 25 | 26 | // returns pointer to the underlying array 27 | func (list *List[T, R]) RawPointer() *[]T { 28 | return list.Array 29 | } 30 | 31 | func New[T any, R any](array []T) *List[T, R] { 32 | var val R 33 | return &List[T,R]{ &array, val } 34 | } 35 | 36 | func NewEmpty[T any, R any]() *List[T, R] { 37 | var val R 38 | var array []T 39 | return &List[T,R]{ &array, val } 40 | } 41 | 42 | /* Chainable methods */ 43 | // perform a mapping operation over each element in List.Array, return pointer to new List 44 | func (list *List[T, R]) Map(mapper func(value T, index int) T) *List[T, R] { 45 | 46 | oldArray := *list.Array 47 | oldArraySize := len(oldArray) 48 | newArray := make([]T, oldArraySize) // create new array of same size 49 | 50 | for index := 0; index < oldArraySize; index++ { 51 | value := T(oldArray[index]) 52 | 53 | newArray[index] = mapper(value, index) 54 | } 55 | return &List[T, R]{ &newArray, list.Value } 56 | } 57 | 58 | // same as Map, but function gets access to array itself 59 | func (list *List[T, R]) MapFull(mapper func(value T, index int, array *[]T) T) *List[T, R] { 60 | 61 | oldArray := *list.Array 62 | oldArraySize := len(oldArray) 63 | newArray := make([]T, oldArraySize) // create new array of same size 64 | 65 | for index := 0; index < oldArraySize; index++ { 66 | value := T(oldArray[index]) 67 | 68 | newArray[index] = mapper(value, index, list.Array) 69 | } 70 | return &List[T, R]{ &newArray, list.Value } 71 | } 72 | 73 | func (list *List[T, R]) Reduce( 74 | reducer func(accumulator R, value T, index int) R, 75 | initial R, 76 | ) R { 77 | array := *list.Array 78 | 79 | accumulator := initial 80 | for index, value := range array { 81 | accumulator = reducer(accumulator, value, index) 82 | } 83 | return accumulator 84 | } 85 | 86 | func (list *List[T, R]) ReduceFull( 87 | reducer func(accumulator R, value T, index int, array *[]T) R, 88 | initial R, 89 | ) R { 90 | array := *list.Array 91 | 92 | accumulator := initial 93 | for index, value := range array { 94 | accumulator = reducer(accumulator, value, index, list.Array) 95 | } 96 | return accumulator 97 | } 98 | 99 | // does not return a new List pointer, merely passes each element to `operation` function 100 | func (list *List[T, R]) ForEach(operation func(element T, index int)) *List[T, R] { 101 | for index, value := range *list.Array { 102 | operation(value, index) 103 | } 104 | return list 105 | } 106 | 107 | func (list *List[T, R]) ForEachFull(operation func(element T, index int, array *[]T)) *List[T, R] { 108 | for index, value := range *list.Array { 109 | operation(value, index, list.Array) 110 | } 111 | return list 112 | } 113 | 114 | func (list *List[T, R]) Filter(filterFunc func(element T, index int) bool) *List[T, R] { 115 | oldArray := *list.Array 116 | newArray := make([]T, len(oldArray), len(oldArray)) 117 | 118 | counter := 0 119 | for index, value := range *list.Array { 120 | ok := filterFunc(value, index) 121 | if ok { 122 | newArray[counter] = value 123 | counter++ 124 | } 125 | } 126 | slicedDown := newArray[:counter] 127 | return &List[T, R]{ &slicedDown, list.Value } 128 | } 129 | 130 | func (list *List[T, R]) FilterFull(filterFunc func(element T, index int, array *[]T) bool) *List[T, R] { 131 | oldArray := *list.Array 132 | newArray := make([]T, len(oldArray), len(oldArray)) 133 | 134 | counter := 0 135 | for index, value := range *list.Array { 136 | ok := filterFunc(value, index, list.Array) 137 | if ok { 138 | newArray[counter] = value 139 | counter++ 140 | } 141 | } 142 | slicedDown := newArray[:counter] 143 | return &List[T, R]{ &slicedDown, list.Value } 144 | } 145 | 146 | // TODO - implement sort method 147 | // func (list *List[T, R]) Sort(compare func(element T, index int) *List[T, R]*List[T, R] { 148 | // } 149 | 150 | // func (list *List[T, R]) SortFull(compare func(element T, index int, array *[]T) bool) *List[T, R] { 151 | // } 152 | 153 | func (list *List[T, R]) Append(addition *[]T) *List[T, R] { 154 | oldArray := list.Array 155 | newArray := append(*oldArray, *addition...) 156 | list.Array = &newArray 157 | return list 158 | } 159 | 160 | func (list *List[T, R]) Add(value T) *List[T, R] { 161 | oldArray := list.Array 162 | newArray := append(*oldArray, value) 163 | list.Array = &newArray 164 | return list 165 | } 166 | 167 | // may `panic` if capacity is less than current length of underlying array 168 | func (list *List[T, R]) SetCap(capacity int) *List[T, R] { 169 | theArray := *list.Array 170 | if (list.Array == nil) { 171 | newSlice := make([]T, 0, capacity) 172 | list.Array = &newSlice 173 | return list 174 | } 175 | size := len(theArray) 176 | newSlice := append(make([]T, size, capacity), theArray...) 177 | list.Array = &newSlice 178 | return list 179 | } 180 | 181 | func (list *List[T, R]) IncrementCap(capacity int) *List[T, R] { 182 | theArray := *list.Array 183 | if (list.Array == nil) { 184 | return list.SetCap(capacity) 185 | } 186 | size := len(theArray) 187 | oldCap := cap(theArray) 188 | newSlice := append(make([]T, size, oldCap + capacity), theArray...) 189 | list.Array = &newSlice 190 | return list 191 | } 192 | 193 | /* End List's Chainable methods */ 194 | 195 | func (list *List[T, R]) Find(finder func(element T, index int) bool) (error, *T) { 196 | for index, value := range *list.Array { 197 | match := finder(value, index) 198 | if match { 199 | return nil, &value 200 | } 201 | } 202 | return errors.New("Not Found"), nil 203 | } 204 | 205 | func (list *List[T, R]) String() string { 206 | return fmt.Sprint((*list.Array)) 207 | } 208 | 209 | // returns first match from matcher function 210 | func (list *List[T, R]) IndexOf(matcher func(element T) bool) (error, int) { 211 | for index, value := range *list.Array { 212 | match := matcher(value) 213 | if match { 214 | return nil, index 215 | } 216 | } 217 | return errors.New("Not Found"), -1 218 | } 219 | 220 | // returns `size`, Will return error if underlying array pointer is `nil` 221 | func (list *List[T, R]) Size() (error, int) { 222 | arrayPtr := list.Array 223 | if arrayPtr == nil { 224 | return errors.New("method called when List.Array == nil"), -1 225 | } 226 | theArray := *arrayPtr 227 | return nil, len(theArray) 228 | } 229 | 230 | func (list *List[T, R]) Cap() (error, int) { 231 | arrayPtr := list.Array 232 | if arrayPtr == nil { 233 | return errors.New("method called when List.Array == nil"), -1 234 | } 235 | theArray := *arrayPtr 236 | return nil, cap(theArray) 237 | } 238 | 239 | // returns `true` if list has a `len() > 0`. Will return error if underlying array pointer is `nil` 240 | func (list *List[T, R]) IsEmpty() (error, bool) { 241 | theError, size := list.Size() 242 | if theError != nil { 243 | return theError, true 244 | } 245 | return nil, size == 0 246 | } 247 | 248 | func listValidation[T any, R any](list *List[T, R]) (error, []T) { 249 | arrayPtr := list.Array 250 | theArray := *arrayPtr 251 | if arrayPtr == nil { 252 | var nada []T 253 | return errors.New(".First() called on List where `List.Array === nil`"), nada 254 | } 255 | errur, isEmpty := list.IsEmpty() 256 | if errur != nil { 257 | var nada []T 258 | return errur, nada 259 | } 260 | if isEmpty { 261 | var nada []T 262 | return errors.New("Empty"), nada 263 | } 264 | return nil, theArray 265 | } 266 | 267 | func (list *List[T, R]) Get(index int) (error, T) { 268 | error, array := listValidation[T, R](list) 269 | if error != nil { 270 | var nada T 271 | return error, nada 272 | } 273 | size := len(array) 274 | if index < 0 || index >= size { 275 | var nada T 276 | return errors.New(".Get() seeking an index out of bound"), nada 277 | } 278 | return nil, array[index] 279 | } 280 | 281 | func (list *List[T, R]) Last() (error, T) { 282 | error, array := listValidation[T, R](list) 283 | if error != nil { 284 | var nada T 285 | return error, nada 286 | } 287 | return nil, array[len(array)- 1] 288 | } 289 | 290 | func (list *List[T, R]) First() (error, T) { 291 | return list.Get(0) 292 | } 293 | 294 | /* Standalone Functions */ 295 | 296 | func ResultTypeSwap[T any, OldR any, NewR any] (list *List[T, OldR]) *List[T, NewR] { 297 | var value NewR 298 | newList := List[T, NewR]{ list.Array, value } 299 | return &newList 300 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go_chainable 2 | 3 | `NOTE: Requires go1.18` 4 | 5 | Go-Chainable is a library using generics to mimic the functional `.map(x -> y).reduce(x -> y, z)` patterns of javascript. `List` is also inspired by Java's `ArrayList`. 6 | 7 | ## List 8 | 9 | ### Construct a List 10 | 11 | ``` 12 | array := []string { "hello", "world" } 13 | list := lists.New[string, any](array) 14 | ``` 15 | 16 | or get an empty list 17 | 18 | ``` 19 | list := lists.NewEmpty[string, any]() 20 | ``` 21 | 22 | #### First type parameter 23 | 24 | First type parameter is the type of the List's values. 25 | 26 | #### "Result" type parameter 27 | 28 | The second type parameter for a list is needed only for when calling `list.Reduce()`. If you won't need to call `.Reduce()` then set the second type argument as `any` and forget it. 29 | 30 | ### Map Over A List 31 | 32 | ``` 33 | import "github.com/neurocollective/go_chainable/lists" 34 | 35 | array := []int{1, 2} 36 | list := lists.New[int, any](array) 37 | doubled := list.Map(func(val int, index int) int { 38 | return val * 2 39 | }) 40 | fmt.Println(doubled.String()) // -> [2 4] 41 | ``` 42 | 43 | ### Filter A List 44 | 45 | ``` 46 | import "github.com/neurocollective/go_chainable/lists" 47 | array := []int { 1, 2 } 48 | list := lists.New[int, any](array) 49 | newList := list.Filter(func(val int, index int) bool { 50 | return val < 2 51 | }) 52 | error, size := newList.Size() 53 | fmt.Println(size) // -> 1 54 | ``` 55 | 56 | ### Reduce A List 57 | 58 | ``` 59 | import "github.com/neurocollective/go_chainable/lists" 60 | 61 | array := []int { 1, 2 } 62 | list := lists.New[int, int](array) // second type passed to New is the "result" type used by reducer as return type 63 | added := list.Reduce(func(accumulator int, val int, index int) int { 64 | return accumulator + val 65 | }, 0) 66 | fmt.Println(added) // -> 3 67 | ``` 68 | 69 | ### Chain Operations Over A List 70 | 71 | ``` 72 | array := []int { 1, 2, 3 } 73 | list := lists.New[int, int](array) 74 | added := list.Map(func(val int, index int) int { 75 | return val + 1 76 | }).Filter(func(val int, index int) bool { 77 | return val < 4 78 | }).Reduce(func(accumulator int, val int, index int) int { 79 | return accumulator + val 80 | }, 0) 81 | fmt.Println(added) // -> 5 82 | ``` 83 | 84 | ## Map 85 | 86 | ### `.Map` over key/value pairs 87 | 88 | ``` 89 | import "github.com/neurocollective/go_chainable/maps" 90 | 91 | theMap := maps.NewEmpty[string, string, string]() 92 | 93 | theMap.Set("hey", "dude") 94 | theMap.Set("sup", "brah") 95 | 96 | mappedList := theMap.Map(func(value string, key string, i int) string { 97 | return key + "_" + value 98 | }) 99 | 100 | fmt.Println(mappedList.String()) // -> [hey_dude sup_brah] 101 | ``` 102 | 103 | ## `.Reduce` over key/value pairs 104 | 105 | ``` 106 | import "github.com/neurocollective/go_chainable/maps" 107 | theMap := maps.NewEmpty[string, string, string]() 108 | 109 | theMap.Set("hey", "dude") 110 | theMap.Set("sup", "brah") 111 | 112 | initial := "When I meet someone new, I always say: " 113 | message := theMap.Reduce(func(accumulator string, value string, key string, i int) string { 114 | return accumulator + key + " " + value + " " 115 | }, initial) 116 | fmt.Println(message) // -> "When I meet someone new, I always say: hey dude sup brah" 117 | ``` 118 | 119 | ## Current Test Coverage 120 | 121 | ``` 122 | ok github.com/neurocollective/go_chainable/lists 0.001s coverage: 80.4% of statements 123 | ok github.com/neurocollective/go_chainable/maps 0.001s coverage: 97.4% of statements 124 | ``` 125 | 126 | ## Methods 127 | 128 | ### List Methods 129 | 130 | `.Raw()` 131 | 132 | Returns a raw underlying `[]T` 133 | 134 | `.RawPointer()` 135 | 136 | Returns the raw Pointer to the underlying `[]T` 137 | 138 | `.Map(mapper func(value T, index int) T) *List[T, R]` 139 | 140 | Calls the `mapper` function for each element, passing in `value` and `int`. The underlying slice is changed to a new slice, with each element being the returned value from `mapper`. 141 | 142 | `.MapFull(mapper func(value T, index int, array *[]T) T) *List[T, R]` 143 | 144 | Same as `.Map` but the `mapper` function takes a pointer to the underlying slice as an additional argument. 145 | 146 | `.Reduce(reducer func(accumulator R, value T, index int) R, initial R) R)` 147 | 148 | Calls the `reducer` function for each element, passing in `accumulator` `value` and `int`. On the first call to `reducer` the `accumulator` value is the `initial` value. But each subsequent call receives an `accumulator` that is the returned `R` value from the previous call to `reducer`. 149 | 150 | `.ReduceFull(reducer func(accumulator R, value T, index int, array *[]T) R, initial R) R` 151 | 152 | Same as `.Reduce` but the `reducer` function takes a pointer to the underlying slice as an additional argument. 153 | 154 | `.ForEach(operation func(element T, index int) T) *List[T, R]` 155 | 156 | Calls the `operation` on each element in the slice, returning nothing from each invocation. The unmodified `*List[T, R]` is returned. 157 | 158 | `.ForEachFull(operation func(element T, index int, array *[]T)) *List[T, R]` 159 | 160 | Same as `.ForEach` but the `operation` function takes a pointer to the underlying slice as an additional argument. 161 | 162 | `Filter(filterFunc func(element T, index int) bool) *List[T, R]` 163 | 164 | Calls `filterFunc` for each element in the slice. This populates a new slice, which only receives the `T` at that index if `filterFunc` return `true`. The `List[T, R]` then points to the new, filtered array. 165 | 166 | `.FilterFull(filterFunc func(element T, index int, array *[]T) bool) *List[T, R]` 167 | 168 | Same as `.ForEach` but the `filterFunc` function takes a pointer to the underlying slice as an additional argument. 169 | 170 | `.Append(addition *[]T) *List[T, R]` 171 | 172 | Adds the values from `[]T` to the `List[T, R]`. 173 | 174 | `.Add(value T) *List[T, R]` 175 | 176 | Adds the value `T` to the `List[T, R]`. 177 | 178 | `.Get(index int) (error, T)` 179 | 180 | Returns the value `T` of the element at `index`. Returns an error if the underlying slice is a `nil` pointer or requested `index` would be out of bounds. 181 | 182 | `.SetCap(capacity int) *List[T, R]` 183 | 184 | Set the capacity of the underlying slice. 185 | 186 | `.IncrementCap(capacity int) *List[T, R]` 187 | 188 | Increase the capacity of the underlying slice by the `int` value `capacity`. 189 | 190 | `.Find(finder func(element T, index int) bool) (error, *T)` 191 | 192 | Calls `finder` on every element in the slice. As soon as `finder` returns `true`, the `T` value at that index is retured. If `finder`never returns `true` after traversing all values, an `error` is returned. 193 | 194 | `.String() string` 195 | 196 | Returns a `string` representation of the underlying slice. 197 | 198 | `.IndexOf(matcher func(element T) bool) (error, int)` 199 | 200 | Calls `matcher` on each element in the array. For the first element from which `matcher` returns `true`, the `int` index of that element will be returned. 201 | 202 | `.Size() (error, int)` 203 | 204 | Returns the `int` length of the underlying slice. 205 | 206 | `.Cap() (error, int)` 207 | 208 | Returns the `int` capacity of the underlying slice. 209 | 210 | `.IsEmpty() (error, bool)` 211 | 212 | Returns `true` if the underlying slice has a length of 0. Returns an error if it is a `nil` pointer. 213 | 214 | `.Last() (error, T)` 215 | 216 | Returns the value `T` of the element at the last index. Returns an error if the underlying slice is a `nil` pointer or list is empty. 217 | 218 | `.First() (error, T)` 219 | 220 | Returns the value `T` of the element at index 0. Returns an error if the underlying slice is a `nil` pointer or list is empty. 221 | 222 | ### List Functions 223 | 224 | `lists.New[T any, R any](array []T) *List[T, R]` 225 | 226 | Build a `*List[T, R]` from an array or slice. 227 | 228 | `lists.NewEmpty[T any, R any]() *List[T, R]` 229 | 230 | Build an empty `*List[T, R]` from an array or slice. 231 | 232 | `ResultTypeSwap[T any, OldR any, NewR any] (list *List[T, OldR]) *List[T, NewR]` 233 | 234 | Get a new `List[T, NewR]`, in the case where result type was incorrect or a new result type if needed. 235 | 236 | ### Map Methods 237 | 238 | `.Map(mapper func(value V, key K, index int) R) *lists.List[R, R]` 239 | 240 | Call `mapper` for each key value pair, returning an `R` value which is appended into the returned `*lists.List[R, R]`. 241 | 242 | `.Reduce(reducer func(accumulator R, value V, key K, index int) R, initial R) R` 243 | 244 | Calls the `reducer` function for each key-value pair, passing in `accumulator` `value` `key` and `index` (`index` corresonds to the order added to the `Map`). On the first call to `reducer` the `accumulator` value is the `initial` value. But each subsequent call receives an `accumulator` that is the returned `R` value from the previous call to `reducer`. 245 | 246 | `.Set(key K, value V) *Map[K, V, R]` 247 | 248 | Add a key-value pair to the `Map`. Key will be stored by order added. 249 | 250 | `.Get(key K) (V, bool)` 251 | 252 | Returns the `V` at `key`. Second value is `false` if the key was not found, and a zero-value was returned. 253 | 254 | `.Keys() *lists.List[K, R]` 255 | 256 | Returns a `*lists.List[K, R]` of all the keys `K` added to the `Map`, in order of addition. 257 | 258 | `.Values() *lists.List[V, R]` 259 | 260 | Returns a `*lists.List[K, R]` of all the values `V` added to the `Map`, in order of addition. 261 | 262 | `.String() string` 263 | 264 | Returns a string representation of the map - order not yet guaranteed. 265 | 266 | ### Map Functions 267 | 268 | `maps.New[K comparable, V comparable, R any](nativeMap map[K]V) *Map[K, V, R]` 269 | 270 | Get a new `*Map[K, V, R]` from a `map[K]V` 271 | 272 | `maps.NewEmpty[K comparable, V comparable, R any]() *Map[K, V, R]` 273 | 274 | Get a new empty `*Map[K, V, R]` 275 | -------------------------------------------------------------------------------- /lists/lists_test.go: -------------------------------------------------------------------------------- 1 | package lists 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRaw(t *testing.T) { 8 | aList := NewEmpty[string, any]() 9 | 10 | array := aList.Raw() 11 | 12 | if len(array) != 0 { 13 | t.Error("list.Raw() returned unexpected result in TestRaw") 14 | } 15 | } 16 | 17 | func TestRawPointer(t *testing.T) { 18 | list := NewEmpty[string, any]() 19 | 20 | arrayPtr := list.RawPointer() 21 | 22 | if arrayPtr != list.Array { 23 | t.Error("list.RawPointer() returned unexpected result in TestRawPointer") 24 | } 25 | } 26 | 27 | func TestNewEmptyConstructor(t *testing.T) { 28 | aList := NewEmpty[string, any]() 29 | 30 | error, isEmpty := aList.IsEmpty() 31 | 32 | if error != nil { 33 | t.Error("list.IsEmpty() got an error") 34 | t.Error(error) 35 | } 36 | if !isEmpty { 37 | t.Error("list.IsEmpty() returned false") 38 | } 39 | } 40 | 41 | func TestConstructor(t *testing.T) { 42 | array := []string{ "dude" } 43 | aList := New[string, any](array) 44 | 45 | error, isEmpty := aList.IsEmpty() 46 | 47 | if error != nil { 48 | t.Error("TestConstructor got an error") 49 | t.Error(error) 50 | } 51 | if isEmpty { 52 | t.Error("list.IsEmpty() in TestConstructor returned false") 53 | } 54 | } 55 | 56 | func TestListDotMap(t *testing.T) { 57 | arr := []int { 0, 1, 2, 3, 4 } 58 | list := New[int, any](arr) 59 | 60 | mapper := func (value int, index int) int { 61 | return value + 1 62 | } 63 | 64 | mapped := list.Map(mapper) 65 | if (*mapped.Array)[4] != 5 { 66 | t.Error("unexpected value in `mapped[4]` in TestListDotMap") 67 | } 68 | } 69 | 70 | func TestListDotMapFull(t *testing.T) { 71 | arr := []int { 0, 1, 2, 3, 4 } 72 | list := New[int, any](arr) 73 | 74 | mapper := func (value int, index int, array *[]int) int { 75 | return value + 1 76 | } 77 | 78 | mapped := list.MapFull(mapper) 79 | if (*mapped.Array)[4] != 5 { 80 | t.Error("unexpected value in `mapped[4]` in TestListDotMapFull") 81 | } 82 | } 83 | 84 | func TestListDotGet(t *testing.T) { 85 | arr := []int { 0, 1, 2, 3, 4 } 86 | list := New[int, any](arr) 87 | 88 | error, valueAtFour := list.Get(4) 89 | if error != nil { 90 | t.Error("error during TestListDotGet") 91 | t.Error(error) 92 | } 93 | if valueAtFour != 4 { 94 | t.Error("unexpected value in `mapped[4]` in TestListDotGet") 95 | } 96 | } 97 | 98 | func TestReduce(t *testing.T) { 99 | array := []int { 1, 2 } 100 | list := New[int, int](array) 101 | added := list.Reduce(func(accumulator int, val int, index int) int { 102 | return accumulator + val 103 | }, 0) 104 | if added != 3 { 105 | t.Error(".Reduce() in TestReduce returned unexpected value") 106 | } 107 | } 108 | 109 | func TestReduceFull(t *testing.T) { 110 | array := []int { 1, 2 } 111 | list := New[int, int](array) 112 | added := list.ReduceFull(func(accumulator int, val int, index int, array *[]int) int { 113 | return accumulator + val 114 | }, 0) 115 | if added != 3 { 116 | t.Error(".Reduce() in TestReduceFull returned unexpected value") 117 | } 118 | } 119 | 120 | func TestFilter(t *testing.T) { 121 | array := []int { 1, 2 } 122 | list := New[int, any](array) 123 | newList := list.Filter(func(val int, index int) bool { 124 | return val < 2 125 | }) 126 | error, value := newList.Size() 127 | if error != nil { 128 | t.Error(".Filter() in TestFilter returned an error") 129 | } 130 | if value != 1 { 131 | t.Error(".Filter() in TestFilter returned unexpected value") 132 | } 133 | } 134 | 135 | func TestFilterFull(t *testing.T) { 136 | array := []int { 1, 2 } 137 | list := New[int, any](array) 138 | newList := list.FilterFull(func(val int, index int, array *[]int) bool { 139 | return val < 2 140 | }) 141 | error, value := newList.Get(0) 142 | if error != nil { 143 | t.Error(".Filter() in TestFilterFull returned an error") 144 | } 145 | if value != 1 { 146 | t.Error(".Filter() in TestFilterFull returned unexpected value") 147 | } 148 | } 149 | 150 | func TestChain(t *testing.T) { 151 | array := []int { 1, 2 } 152 | list := New[int, int](array) 153 | added := list.Map(func(val int, index int) int { 154 | return val + 1 155 | }).Reduce(func(accumulator int, val int, index int) int { 156 | return accumulator + val 157 | }, 0) 158 | if added != 5 { 159 | t.Error(".Map().Reduce() in TestChain returned unexpected value") 160 | } 161 | } 162 | 163 | func TestChainTwo(t *testing.T) { 164 | array := []int { 1, 2, 3 } 165 | list := New[int, int](array) 166 | added := list.Map(func(val int, index int) int { 167 | return val + 1 168 | }).Filter(func(val int, index int) bool { 169 | return val < 4 170 | }).Reduce(func(accumulator int, val int, index int) int { 171 | return accumulator + val 172 | }, 0) 173 | if added != 5 { 174 | t.Error(".Map().Filter().Reduce() in TestChainTwo returned unexpected value") 175 | } 176 | } 177 | 178 | func TestReduceWithDifferingResultType(t *testing.T) { 179 | type Number struct { 180 | Value int 181 | } 182 | 183 | array := []Number { Number{1}, Number{2} } 184 | list := New[Number, int](array) 185 | added := list.Reduce(func(accumulator int, val Number, index int) int { 186 | return accumulator + val.Value 187 | }, 0) 188 | if added != 3 { 189 | t.Error(".Reduce() in TestReduceWithDifferingResultType returned unexpected value") 190 | } 191 | } 192 | 193 | func TestAppend(t *testing.T) { 194 | list := NewEmpty[int, any]() 195 | 196 | list.Append(&[]int { 1, 2 }) 197 | errorZero, indexZero := list.Get(0) 198 | errorOne, indexOne := list.Get(1) 199 | 200 | if errorZero != nil || errorOne != nil { 201 | t.Error("TestAppend returned an error") 202 | t.Error(errorZero.Error()) 203 | t.Error(errorOne.Error()) 204 | } 205 | if indexZero != 1 { 206 | t.Error("indexZero in TestAppend returned unexpected value") 207 | } 208 | if indexOne != 2 { 209 | t.Error("indexOne in TestAppend returned unexpected value") 210 | } 211 | } 212 | 213 | func TestAdd(t *testing.T) { 214 | list := NewEmpty[int, any]() 215 | 216 | list.Add(1) 217 | errorZero, indexZero := list.Get(0) 218 | 219 | if errorZero != nil { 220 | t.Error("TestAdd returned an error") 221 | t.Error(errorZero.Error()) 222 | } 223 | if indexZero != 1 { 224 | t.Error("indexZero in TestAdd returned unexpected value") 225 | } 226 | } 227 | 228 | func TestSetCap(t *testing.T) { 229 | list := NewEmpty[int, any]() 230 | 231 | list.SetCap(100) 232 | 233 | error, capacity := list.Cap() 234 | 235 | if error != nil { 236 | t.Error("TestSetCap returned an error") 237 | t.Error(error.Error()) 238 | } 239 | if capacity != 100 { 240 | t.Error("indexZero in TestSetCap returned unexpected value") 241 | } 242 | } 243 | 244 | func TestIncrementCap(t *testing.T) { 245 | list := NewEmpty[int, any]() 246 | 247 | list.SetCap(100) 248 | 249 | list.IncrementCap(100) 250 | 251 | error, capacity := list.Cap() 252 | 253 | if error != nil { 254 | t.Error("TestIncrementCap returned an error") 255 | t.Error(error.Error()) 256 | } 257 | if capacity != 200 { 258 | t.Error("indexZero in TestIncrementCap returned unexpected value") 259 | } 260 | } 261 | 262 | func TestFind(t *testing.T) { 263 | list := New[int, any]([]int{ 8, 6, 7, 5, 3, 0, 9 }) 264 | 265 | error, target := list.Find(func(element int, index int) bool { 266 | if element == 0 { 267 | return true 268 | } 269 | return false 270 | }) 271 | if error != nil { 272 | t.Error("TestFind returned an error") 273 | } 274 | if *target != 0 { 275 | t.Error("TestFind did not return the expected value") 276 | } 277 | } 278 | 279 | func TestString(t *testing.T) { 280 | list := New[int, any]([]int{ 1, 2 }) 281 | 282 | str := list.String() 283 | 284 | if str != "[1 2]" { 285 | t.Error("TestString did not return the expected value") 286 | } 287 | } 288 | 289 | func TestIndexOf(t *testing.T) { 290 | list := New[int, any]([]int{ 1, 2 }) 291 | 292 | error, theIndex := list.IndexOf(func(element int) bool { 293 | if element == 2 { 294 | return true 295 | } 296 | return false 297 | }) 298 | if error != nil { 299 | t.Error("TestIndexOf returned an error") 300 | } 301 | if theIndex != 1 { 302 | t.Error("TestIndexOf did not return the expected value") 303 | } 304 | } 305 | 306 | func TestSize(t *testing.T) { 307 | list := New[int, any]([]int{ 1, 2 }) 308 | 309 | error, size := list.Size() 310 | if error != nil { 311 | t.Error("TestSize returned an error") 312 | } 313 | if size != 2 { 314 | t.Error("TestSize did not return the expected value") 315 | } 316 | } 317 | 318 | func TestCap(t *testing.T) { 319 | list := New[int, any]([]int{ 1, 2 }) 320 | 321 | error, cap := list.Cap() 322 | if error != nil { 323 | t.Error("TestCap returned an error") 324 | } 325 | if cap != 2 { 326 | t.Error("TestCap did not return the expected value") 327 | } 328 | } 329 | 330 | func TestIsEmpty(t *testing.T) { 331 | list := NewEmpty[int, any]() 332 | 333 | error, empty := list.IsEmpty() 334 | if error != nil { 335 | t.Error("TestIsEmpty returned an error") 336 | } 337 | if !empty { 338 | t.Error("TestIsEmpty did not return the expected empty value") 339 | } 340 | } 341 | 342 | func TestGet(t *testing.T) { 343 | list := New[int, any]([]int{ 1, 2 }) 344 | 345 | error, value := list.Get(1) 346 | if error != nil { 347 | t.Error("TestGet returned an error") 348 | } 349 | if value != 2 { 350 | t.Error("TestGet did not return the expected value") 351 | } 352 | } 353 | 354 | func TestLast(t *testing.T) { 355 | list := New[int, any]([]int{ 1, 2 }) 356 | 357 | error, value := list.Last() 358 | if error != nil { 359 | t.Error("TestLast returned an error") 360 | } 361 | if value != 2 { 362 | t.Error("TestLast did not return the expected value") 363 | } 364 | } 365 | 366 | func TestFirst(t *testing.T) { 367 | list := New[int, any]([]int{ 1, 2 }) 368 | 369 | error, value := list.First() 370 | if error != nil { 371 | t.Error("TestGet returned an error") 372 | } 373 | if value != 1 { 374 | t.Error("TestFirst did not return the expected value") 375 | } 376 | } 377 | 378 | func TestResultTypeSwap(t *testing.T) { 379 | list := New[int, any]([]int{ 1, 2 }) 380 | 381 | type Number struct { 382 | Value int 383 | } 384 | 385 | newList := ResultTypeSwap[int, any, Number](list) 386 | 387 | reduced := newList.Reduce(func (accumulator Number, value int, index int) Number { 388 | return Number{ accumulator.Value + value } 389 | }, Number{ 22 }) 390 | 391 | if reduced.Value != 25 { 392 | t.Error("TestResultTypeSwap did not return the expected empty value") 393 | } 394 | } 395 | --------------------------------------------------------------------------------