github.com/m4gshm/gollections@v0.0.10/README.md (about) 1 # Gollections 2 3 This is a set of utilities aimed at reducing boilerplate code when using 4 [slices](./slice/api.go), [maps](./map_/api.go) and extending 5 functionality by new collection implementations such as [ordered 6 map](./collection/collection/mutable/omap/api.go) or 7 [set](./collection/collection/mutable/oset/api.go). 8 9 Supports Go version 1.20. 10 11 For example, you want to group some 12 [users](./internal/examples/boilerplate/user_type.go) by their role 13 names converted to lowercase: 14 15 ``` go 16 var users = []User{ 17 {name: "Bob", age: 26, roles: []Role{{"Admin"}, {"manager"}}}, 18 {name: "Alice", age: 35, roles: []Role{{"Manager"}}}, 19 {name: "Tom", age: 18}, 20 } 21 ``` 22 23 You can make clear code, extensive, but without dependencies: 24 25 ``` go 26 var namesByRole = map[string][]string{} 27 add := func(role string, u User) { 28 namesByRole[role] = append(namesByRole[role], u.Name()) 29 } 30 for _, u := range users { 31 roles := u.Roles() 32 if len(roles) == 0 { 33 add("", u) 34 } else { 35 for _, r := range roles { 36 add(strings.ToLower(r.Name()), u) 37 } 38 } 39 } 40 41 assert.Equal(t, namesByRole[""], []string{"Tom"}) 42 assert.Equal(t, namesByRole["manager"], []string{"Bob", "Alice"}) 43 assert.Equal(t, namesByRole["admin"], []string{"Bob"}) 44 ``` 45 46 Or you can write more compact code using the collections API, like so: 47 48 ``` go 49 var namesByRole = group.ByMultipleKeys(users, func(u User) []string { 50 return convert.AndConvert(u.Roles(), Role.Name, strings.ToLower) 51 }, User.Name) 52 53 assert.Equal(t, namesByRole[""], []string{"Tom"}) 54 assert.Equal(t, namesByRole["manager"], []string{"Bob", "Alice"}) 55 assert.Equal(t, namesByRole["admin"], []string{"Bob"}) 56 ``` 57 58 ## Installation 59 60 ``` console 61 go get -u github.com/m4gshm/gollections 62 ``` 63 64 or 65 66 ``` console 67 go get -u github.com/m4gshm/gollections@HEAD 68 ``` 69 70 ## Main packages 71 72 All packages consists of functions placed in the package and subpackages 73 aimed to make short aliases of that functions. For example the function 74 [slice.SortByOrdered](./slice/api.go#L459) has aliases 75 [sort.By](./slice/sort/api.go#L12) and 76 [sort.Of](./slice/sort/api.go#L23). 77 78 ### [slice](./slice/api.go) and [map\_](./map_/api.go) 79 80 Contains utility functions of [converting](./slice/api.go#L156), 81 [filtering](./slice/api.go#L379) (searching), 82 [reducing](./slice/api.go#L464), [cloning](./map_/api.go#L90) elements 83 of embedded slices and maps. The functions compute result in-place. For 84 delayed computations see 85 [loops](#loop-kvloop-and-breakable-versions-breakloop-breakkvloop) or 86 [collection functions](#collection-functions). 87 88 ``` go 89 even := func(i int) bool { return i%2 == 0 } 90 result := slice.Reduce( 91 slice.Convert( 92 slice.Filter(slice.Of(1, 2, 3, 4), even), 93 strconv.Itoa, 94 ), 95 op.Sum[string], 96 ) 97 98 assert.Equal(t, "24", result) 99 ``` 100 101 More examples 102 [here](./internal/examples/sliceexamples/slice_examples_test.go) and 103 [here](./internal/examples/mapexamples/map_examples_test.go). 104 105 ### [mutable](./collection/mutable/api.go) and [immutable](./collection/immutable/api.go) collections 106 107 Provides implelentations of [Vector](./collection/iface.go#L25), 108 [Set](./collection/iface.go#L35) and [Map](./collection/iface.go#L41). 109 110 Mutables support content appending, updating and deleting (the ordered 111 map implementation is not supported delete operations). 112 Immutables are read-only datasets. 113 114 Detailed description of implementations [below](#mutable-collections). 115 116 ### [predicate](./predicate/api.go) and breakable [break/predicate](./predicate/api.go) 117 118 Provides predicate builder api that used for filtering collection 119 elements. 120 121 ``` go 122 bob, _ := slice.First(users, where.Eq(User.Name, "Bob")) 123 124 assert.Equal(t, "Bob", bob.Name()) 125 ``` 126 127 ### [loop](./loop/api.go), [kv/loop](./kv/loop/api.go) and breakable versions [break/loop](./break/loop/api.go), [break/kv/loop](./break/kv/loop/api.go) 128 129 Low level iteration api based on `next` function. 130 131 ``` go 132 type ( 133 next[T any] func() (element T, ok bool) 134 kvNext[K, V any] func() (key K, value V, ok bool) 135 ) 136 ``` 137 138 The function retrieves a next element from a dataset and returns 139 `ok==true` if successful. 140 The API in most cases is similar to the [slice](./slice/api.go) API but 141 with delayed computation which means that the methods don’t compute a 142 result but only return a loop provider. The loop provider is type with a 143 `Next` method that returns a next processed element. 144 145 ``` go 146 even := func(i int) bool { return i%2 == 0 } 147 loopStream := loop.Convert(loop.Filter(loop.Of(1, 2, 3, 4), even).Next, strconv.Itoa) 148 149 assert.Equal(t, []string{"2", "4"}, loop.Slice(loopStream.Next)) 150 ``` 151 152 Breakable loops additionaly have error returned value. 153 154 ``` go 155 type ( 156 next[T any] func() (element T, ok bool, err error) 157 kvNext[K, V any] func() (key K, value V, ok bool, err error) 158 ) 159 ``` 160 161 It is used for computations where an error may occur. 162 163 ``` go 164 iter := loop.Conv(loop.Of("1", "2", "3", "ddd4", "5"), strconv.Atoi) 165 result, err := loop.Slice(iter.Next) 166 167 assert.Equal(t, []int{1, 2, 3}, result) 168 assert.ErrorContains(t, err, "invalid syntax") 169 ``` 170 171 ## Expressions: [use](./expr/use/api.go), [get](./expr/get/api.go), [first](./expr/first/api.go), [last](./expr/last/api.go) 172 173 Aimed to evaluate a value using conditions. May cause to make code 174 shorter by not in all cases. 175 As example: 176 177 ``` go 178 user := User{name: "Bob", surname: "Smith"} 179 180 fullName := use.If(len(user.surname) == 0, user.name).If(len(user.name) == 0, user.surname). 181 ElseGet(func() string { return user.name + " " + user.surname }) 182 183 assert.Equal(t, "Bob Smith", fullName) 184 ``` 185 186 instead of: 187 188 ``` go 189 fullName := "" 190 if len(user.surname) == 0 { 191 fullName = user.name 192 } else if len(user.name) == 0 { 193 fullName = user.surname 194 } else { 195 fullName = user.name + " " + user.surname 196 } 197 198 assert.Equal(t, "Bob Smith", fullName) 199 ``` 200 201 ## Mutable collections 202 203 Supports write operations (append, delete, replace). 204 205 - [Vector](./collection/mutable/vector/api.go) - the simplest based on 206 built-in slice collection. 207 208 ``` go 209 _ *mutable.Vector[int] = vector.Of(1, 2, 3) 210 _ collection.Vector[int] = &mutable.Vector[int]{} 211 ``` 212 213 - [Set](./collection/mutable/set/api.go) - collection of unique items, 214 prevents duplicates. 215 216 ``` go 217 _ *mutable.Set[int] = set.Of(1, 2, 3) 218 _ collection.Set[int] = &mutable.Set[int]{} 219 ``` 220 221 - [Map](./collection/mutable/map_/api.go) - built-in map wrapper that 222 supports [stream functions](#stream-functions). 223 224 ``` go 225 _ *mutable.Map[int, string] = map_.Of(k.V(1, "1"), k.V(2, "2"), k.V(3, "3")) 226 _ collection.Map[int, string] = mutable.NewMapOf(map[int]string{1: "2", 2: "2", 3: "3"}) 227 ``` 228 229 - [OrderedSet](./collection/mutable/oset/api.go) - collection of unique 230 items, prevents duplicates, provides iteration in order of addition. 231 232 ``` go 233 _ *ordered.Set[int] = set.Of(1, 2, 3) 234 _ collection.Set[int] = &ordered.Set[int]{} 235 ``` 236 237 - [OrderedMap](./collection/mutable/omap/api.go) - same as the Map, but 238 supports iteration in the order in which elements are added. 239 240 ``` go 241 _ *ordered.Map[int, string] = map_.Of(k.V(1, "1"), k.V(2, "2"), k.V(3, "3")) 242 _ collection.Map[int, string] = ordered.NewMapOf( 243 /*order */ []int{3, 1, 2}, 244 /*uniques*/ map[int]string{1: "2", 2: "2", 3: "3"}, 245 ) 246 ``` 247 248 ### Immutable containers 249 250 The same underlying interfaces but for read-only use cases. 251 252 ## Collection functions 253 254 There are three groups of operations: 255 256 - Immediate - retrieves the result in place 257 ([Sort](./collection/mutable/vector.go#L322), 258 [Reduce](./collection/immutable/vector.go#L154), 259 [Track](./collection/immutable/vector.go#L111), 260 [TrackEach](./collection/mutable/ordered/map.go#L182), 261 [For](./collection/immutable/vector.go#L122), 262 [ForEach](./collection/immutable/ordered/map.go#L175)) 263 264 - Intermediate - only defines a computation 265 ([Convert](./collection/api.go#L17), 266 [Filter](./collection/immutable/ordered/set.go#L124), 267 [Flatt](./collection/api.go#L36), [Group](./collection/api.go#L69)). 268 269 - Final - applies intermediates and retrieves a result 270 ([First](./collection/api.go#L75), 271 [Slice](./collection/immutable/ordered/set.go#L94), 272 [Reduce](./collection/immutable/ordered/set.go#L146)) 273 274 Intermediates should wrap one by one to make a lazy computation chain 275 that can be applied to the latest final operation. 276 277 ``` go 278 var groupedByLength = group.Of(set.Of( 279 "seventh", "seventh", //duplicate 280 "first", "second", "third", "fourth", 281 "fifth", "sixth", "eighth", 282 "ninth", "tenth", "one", "two", "three", "1", 283 "second", //duplicate 284 ), func(v string) int { return len(v) }, 285 ).FilterKey( 286 more.Than(3), 287 ).ConvertValue( 288 func(v string) string { return v + "_" }, 289 ).Map() 290 291 assert.Equal(t, map[int][]string{ 292 5: {"first_", "third_", "fifth_", "sixth_", "ninth_", "tenth_", "three_"}, 293 6: {"second_", "fourth_", "eighth_"}, 294 7: {"seventh_"}, 295 }, groupedByLength) 296 ```