A collection of nice to have generic function and algorithms for slices, maps and channels
This project came about in the experimentation with go1.18 and generics with helper functions for slices. It now also includes algorithms and constructs for dealing with channels and maps as well
It is expected to that there might be a lot of these types of libraries floating around after the release of go1.18. The go team did not include any of these fairly common constructs in this release but instead put some of them into the exp package. There for the might be quite a bit of overlap in the coming releases with this and other packages.
Some other work with similar concepts
- https://github.com/golang/exp/tree/master/slices
- https://github.com/golang/exp/tree/master/maps
- https://github.com/samber/lo
go get github.com/modfin/henry/...
Import the part of henry you are interested of using
import (
"github.com/modfin/henry/chanz"
"github.com/modfin/henry/mapz"
"github.com/modfin/henry/slicez"
)
Then use the functions in the libraries such as
upperPets := slicez.Map([]string{"Dog", "Lizard", "Cat"}, strings.ToUpper))
// []string{"DOG", "LIZARD", "CAT"}
In go errors is made visible and is a core construct for sound code, so we can't simply ignore them. One way of dealing with them is to wrap the result in a result type. This does have some implication in that early returns might not be possible and might introduce some extra looping the check the result.
Example
package main
import (
"fmt"
"github.com/modfin/henry/exp/result"
"github.com/modfin/henry/slicez"
"net/url"
)
func parsUrls(stringUrls []string) ([]*url.URL, error) {
urls := slicez.Map(stringUrls, func(u string) result.Result[*url.URL] {
url, err := url.Parse(u)
return result.From(url, err)
})
return result.Unwrap(urls)
}
func main() {
stringUrls := []string{
"https://example.com",
"https://github.com",
"bad\n url",
}
urls, err := parsUrls(stringUrls)
fmt.Println("URLs", urls)
// URLs [https://example.com https://github.com]
fmt.Println("Error", err)
// Error parse "bad\n url": net/url: invalid control character in URL
}
Henry contain tree main packages. slicez
, chanz
and mapz
Functions in slicez
- Clone
- Compact
- CompactFunc
- Compare
- CompareFunc
- Complement
- ComplementBy
- Concat
- Contains
- ContainsFunc
- Cut
- CutFunc
- Difference
- DifferenceBy
- Drop
- DropRight
- DropRightWhile
- DropWhile
- Each
- Equal
- EqualFunc
- Every
- EveryFunc
- Filter
- Find
- FindLast
- FlatMap
- Flatten
- Fold
- FoldRight
- GroupBy
- Head
- Index
- IndexFunc
- Intersection
- IntersectionBy
- Join
- KeyBy
- Last
- LastIndex
- LastIndexFunc
- Map
- Max
- Min
- None
- NoneFunc
- Nth
- Partition
- Reject
- Reverse
- Sample
- Search
- Shuffle
- Some
- SomeFunc
- Sort
- SortFunc
- Tail
- Take
- TakeRight
- TakeRightWhile
- TakeWhile
- Union
- UnionBy
- Uniq
- UniqBy
- Unzip
- Unzip2
- Zip
- Zip2
Functions in mapz
- Clear
- Clone
- Copy
- DeleteFunc
- DeleteValue
- Equal
- EqualFunc
- Keys
- Merge
- Remap
- Values
Functions in chanz
- Collect
- CollectUntil
- Compact
- Compact1
- CompactN
- CompactUntil
- Concat
- Concat1
- ConcatN
- ConcatUntil
- Drop
- Drop1
- DropAll
- DropN
- DropUntil
- DropWhile
- DropWhile1
- DropWhileN
- DropWhileUntil
- EveryDone
- FanOut
- FanOut1
- FanOutN
- FanOutUntil
- Filter
- Filter1
- FilterN
- FilterUntil
- Flatten
- Flatten1
- FlattenN
- FlattenUntil
- Generate
- Generate1
- GenerateN
- GenerateUntil
- Map
- Map1
- MapN
- MapUntil
- Merge
- Merge1
- MergeN
- MergeUntil
- Partition
- Partition1
- PartitionN
- PartitionUntil
- Peek
- Peek1
- PeekN
- PeekUntil
- Readers
- SomeDone
- Take
- Take1
- TakeN
- TakeUntil
- TakeWhile
- TakeWhile1
- TakeWhileN
- TakeWhileUntil
- Unzip
- Unzip1
- UnzipN
- UnzipUntil
- Writers
- Zip
- Zip1
- ZipN
- ZipUntil
The slicez
package contains generic utility functions and algorithms for slices
Produces a copy of a given slice
s := []int{1,2,3}
clone := slicez.Clone[int](s)
// []int{1,2,3}
Removes consecutive duplicates from a slice
s := []int{1, 1, 2, 3, 3}
slicez.Compact[int](s)
// []int{1,2,3}
Removes consecutive duplicates from a slice using a function for determine equality
s := []rune("Alot of white spaces")
slicez.CompactFunc[rune](s, func(a, b rune) {
return a == ' ' && a == b
})
// "Alot of white spaces"
Compares two slices for equality
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
slicez.Compare[int](s1, s2)
// 0
Compares two slices for equality with supplied func
s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6}
slicez.CompareFunc[int](s1, s2, func (a, b int) int{
return a%4 - b%4
})
// 0
Returns the complement of two slices
a := []int{1, 2, 3}
b := []int{3, 2, 5, 5, 6, 1}
slicez.Complement[int](a, b)
// []int{5, 6}
Concatenates slices into a new slice
a := []int{1, 2, 3}
b := []int{4, 5, 6}
b := []int{7, 8, 9}
slicez.Concat[int](a, b, c)
// []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
Return true if an element is present in the slice
slicez.Contains[int]([]int{1, 2, 3, 4, 5}, 3)
// true
Returns true if the function returns true.
slicez.ContainsFunc[int]([]int{4, 5, 6, 7}, func(i int){
return i % 4 == 3
})
// true
Cuts a slice into two parts
slicez.Cut[int]([]int{1,2,3,4,5}, 3)
// []int{1,2}, []int{4,5}, true
Cuts a slice into two parts
slicez.CutFunc[int]([]int{1,2,3,4,5}, func(i int){ return i == 3})
// []int{1,2}, []int{4,5}, true
Returns the difference between slices
a := []int{1,2,3}
b := []int{2,3,4}
c := []int{2,3,5}
slicez.Difference[int](a, b, c)
// []int{1,4,5}
Drops the first N elements
slicez.Drop[int]([]int{1, 2, 3, 4, 5}, 2)
// []int{3,4,5}
Drops the last N elements
slicez.DropRight[int]([]int{1, 2, 3, 4, 5}, 2)
// []int{1,2,3}
Drops elements from the left until function returns false
slicez.DropWhile[int]([]int{1, 2, 3, 4, 5}, func (i int) {return i < 3})
// []int{3,4,5}
Drops elements from the right until function returns false
slicez.DropRightWhile[int]([]int{1, 2, 3, 4, 5}, func (i int) {return i > 3})
// []int{1,2,3}
Applies a function to each element of a slice
slicez.Each[int]([]int{1, 2, 3}, func (i int) { fmt.Print(i) })
// 123
Returns true if two slices are identical
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
slicez.Equal[int](s1, s2)
// true
Returns true if two slices are identical, given an equality function
s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6}
slicez.EqualFunc[int, int](s1, s2, func (a, b int) int{
return a%4 - b%4
})
// true
Returns true if every element matches the given value
s1 := []int{1, 1, 1}
slicez.Every[int](s1, 1)
// true
Returns true if every element matches the given value using the equality function
s1 := []int{0, 3, 6}
slicez.EveryFunc[int](s1, func (i int) { return i % 3 == 0})
// true
Filters a slice to contain things we are looking for
s1 := []int{1, 2, 3, 4}
slicez.Filter[int](s1, func (i int) { return i % 2 == 0})
// []int{2,4}
Find returns the first instance of an object where the function returns true
s1 := []int{1, 2, 3, 4, 5,6, 7}
slicez.Find[int](s1, func (i int) { return i % 3 == 0})
// 3
FindLast returns the last instance of an object where the function returns true
s1 := []int{1, 2, 3, 4, 5,6, 7}
slicez.FindLast[int](s1, func (i int) { return i % 3 == 0})
// 6
Takes a slice, expands every element into a slice and flattens it to a single slice
s := []string{"a b c", "d e f"}
slicez.FlatMap(s, func(e string) []string {
return strings.Split(e, " ")
})
// []string{"a","b","c","d","e","f"}
Flattens a nested slice
s := [][]string{{"a", "b"}, {"c","d"}}
slicez.Flatten(s)
// []string{"a","b","c","d"}
Folds a slice into a value from the left (aka reduce)
s := []string{"a", "b", "c","d"}
slicez.Fold[string, string](s, func(acc string, str string) string { return acc + str}, ">")
// ">abcd"
Folds a slice into a value from the right (aka reduce)
s := []string{"a", "b", "c","d"}
slicez.FoldRight[string, string](s, func(acc string, str string) string { return acc + str}, ">")
// ">dcba"
Groups elements in a slice into a map
s = []int{0,1,2,3}
slicez.GroupBy(s, func(e int) int {return i % 2})
// map[int][]int{0: [0,2], 1:[1,2]}
Returns the first element of a slice if present
slicez.Head([]int{1,2,3})
// 1, nil
Returns the index of the first occurrence of an element
slicez.Index([]int{1,2,3}, 2)
// 1
Returns the index of the first occurrence of an element using a function
slicez.IndexFunc([]int{1,2,3}, func(i int) bool {i % 2 == 1})
// 0
Returns the intersection of slices
a := []int{1,2,3,4}
b := []int{3,4,5,6}
c := []int{3,4,8,9}
slicez.Intersection(a,b,c)
// []int{3,4}
Returns the intersection of slices
a := []int{0,1}
b := []int{4,2}
c := []int{8,3}
slicez.IntersectionBy(func(i int) int {
return i % 4
} a,b,c)
// []int{0,4,8}
Joining a 2d slice into 1d slice using a glue
s = [][]string{{"hello", " ", "world"}, {"or", " ", "something"}}
slicez.Join(s, []string{" "})
// []string{"hello", " ", "world", " ", "or", s" ", "something"}
Returns a map with the slice elements in it, using the by function to determine key
s = []int{1,2,3,4}
slicez.KeyBy(s, func(i int) int { return i % 3 })
// map[int]int{0: 3, 1: 1, 2: 2}
Returns the last element in a slice, or an error if len(s) == 0
slicez.Last([]int{1,2,3})
// 3, nil
Finds the last index of a needle, or -1 if not present
slicez.LastIndex([]int{1,1,2,1,3}, 1)
// 3
Finds the last index of a func needle, or -1 if not present
slicez.LastIndex([]int{1,2,3,4,5}, func(i int) bool { return i % 3 == 1})
// 2
Map values in a slice producing a new one
s := []int{1,2,3}
slicez.Map(s, func(i int) string { return fmt.Sprint(i)})
// []string{"1","2","3"}
Returns the maximum value of a slice
s := []int{1,2,5,4}
slicez.Max(s...)
// 5
Returns the minimum value of a slice
s := []int{1,2,5,0, 4}
slicez.Min(s...)
// 0
Returns true if no element match the needle
s := []int{1,2,3}
slicez.None(s, 0)
// true
Returns true if no element returns true from the function
s := []int{1,2,3}
slicez.NoneFunc(s, func(i int) bool { return i < 1 })
// true
Returns the N:th element in a slice, zero value if empty and regards the slice as a modulo group
s := []int{1,2,3}
slicez.Nth(s, 1)
// 2
slicez.Nth(s, 3)
// 1
slicez.Nth(s, -1)
// 3
Returns two slices which represents the partitions
s := []int{1,2,3,4}
slicez.Partition(s, func(i int) bool { return i % 2 == 0})
// []int{2,4}, []int{1,3}
Reject is the complement to Filter and excludes items
s := []int{1,2,3,4}
slicez.Reject(s, func(i int) bool { return i % 2 == 0})
// []int{1,3}
Reverses a slice
s := []int{1,2,3}
slicez.Reverse(s)
//[]int{3,2,1}
Returns a random sample of size N from the slice
s := []int{1,2,3,4,5,6,7,8}
slicez.Sample(s, 2)
//[]int{8,3}
Returns a shuffled version of the slice
s := []int{1,2,3,4,5}
slicez.Shuffle(s)
//[]int{3,1,4,5,3}
Returns true there exist an element in the slice that is equal to the needle, an alias for Contains
s := []int{1,2,3,4,5}
slicez.Some(s, 4)
//true
Returns true if there is an element in the slice for which the predicate function returns true
s := []int{1,2,3,4,5}
slicez.SomeFunc(s, func(i int) bool { return i > 4})
//true
Sorts a slice
s := []int{3,2,1}
slicez.Sort(s)
//[]int{1,2,3}
Sorts a slice with a comparator
s := []int{1,2,3}
slicez.SortFunc(s, func(a, b int) bool { return b < a })
//[]int{3,2,1}
Returns the tail of a slice
s := []int{1,2,3}
slicez.Tail(s)
// []int{2,3}
Returns the N first element of a slice
s := []int{1,2,3,4}
slicez.Take(s, 2)
// []int{1,2}
Returns the N last element of a slice
s := []int{1,2,3,4}
slicez.TakeRight(s, 2)
// []int{3, 4}
Returns the first element of a slice that as long as function returns t
s := []int{1,2,3,4}
slicez.TakeRight(s, func(i int) bool { return i < 3})
// []int{1, 2}
Returns the last element of a slice that as long as function returns t
s := []int{1,2,3,4}
slicez.TakeWhileRight(s, func(i int) bool { return i > 2})
// []int{3, 4}
Returs the union of a slices
a := []int{1,2,3}
b := []int{3,4,5}
slicez.Union(a, b)
// []int{1,2,3,4,5}
Returs the union of a slices using a function for equality
a := []int{1,5}
b := []int{2,4}
slicez.UnionBy(func(i int) bool { return i % 2 == 0 } a, b)
// []int{1,2}
Returns a slice of uniq elements
a := []int{1,2,3,1,3,4}
slicez.Uniq(a)
// []int{1,2,3,4}
Returns a slice of uniq elements, where equality is determined through the function
a := []int{1,2,3,1,3,4}
slicez.UniqBy(a, func(i int) bool { return i % 2 == 0 })
// []int{1,2}
Takes a slice and unzips it into two slices
s := []int{-1,2}
slicez.Unzip(s, func(i int) (bool, int){
return i > 0, int(Math.Abs(i))
})
// []bool{false, true}, []int{1,2}
Takes a slice and unzips it into three slices
s := []int{-2,-1,2}
slicez.Unzip(s, func(i int) (bool, bool, int){
return i > 0, i % 2 == 0, int(Math.Abs(i))
})
// []bool{false, false, true}, []bool{true, false, true}, []int{2, 1,2}
Takes 2 slices and zips them into one slice
a := []int{1,2,3}
b := []string{"a","b","c"}
slicez.Zip(a,b, func(i int, s string) string {
return fmt.Sprint(i, s)
})
// []string{"1a", "2b", "3c"}
Takes 3 slices and zips them into one slice
a := []int{1,2,3}
b := []string{"a","b","c"}
b := []bool{true, false, true}
slicez.Zip(a, b, c, func(i int, s string, b bool) string {
return fmt.Sprint(b, i, s)
})
// []string{"true1a", "false2b", "true3c"}
The mapz
package contains generic utility functions and algorithms for maps
Deletes every entry in a map
m := map[int]int{1:1, 2:2}
mapz.Clear(m)
// map[int]int{}
Creates a clone of a map
m := map[int]int{1:1, 2:2}
mapz.Clone(m)
// map[int]int{1:1, 2:2}
Copies one map into another
src := map[int]int{1:1, 2:2}
dst := map[int]int{1:0, 3:3}
mapz.Copy(dst, src)
// map[int]int{1:1, 2:2, 3:3}
Will remove all entries from a map where the del function returns true
m := map[int]int{1:1, 2:2, 3:3}
mapz.DeleteFunc(m, func(k, v int) bool { return k == 2 })
// map[int]int{1:1, 3:3}
Deletes a value and the associated key from a map
m := map[int]int{1:1, 2:800, 3:3}
mapz.DeleteValue(m, 800)
// map[int]int{1:1, 3:3}
Returns true if a map i equal
m1 := map[int]int{1:1, 3:3}
m2 := map[int]int{1:1, 3:3}
mapz.Equal(m1, m2)
// true
Returns true if a map i equal using the equality function to test it
m1 := map[int]int{1:1, 3:3}
m2 := map[int]int{1:1, 3:6}
mapz.EqualFunc(m1, m2, func(a, b int) bool {return a % 3 == b % 3})
// true
Returns a slice of all keys in the map
m := map[int]int{1:1, 3:3}
mapz.Keys(m)
// []int{1,3}
Merges multiple maps into one map
m1 := map[int]int{1:1, 3:3, 4:800}
m2 := map[int]int{2:2, 4:4}
mapz.Merge(m1, m2)
// map[int]int{1:1, 2:2, 3:3, 4:4}
Remaps a map in terms of its keys and values
m := map[int]int{2:2, 4:4}
mapz.Remap(m, func(k, v int) (k2, v2 string){
return fmt.Sprint(k), fmt.Sprint(v*2)
})
// map[string]string{"2":"4", "4":"8"}
Returns a slice of all values in the map
m := map[int]int{2:6, 4:12}
mapz.Values(m)
// []int{6,12}
The chanz
package contains generic utility functions and algorithms for channels
Takes N channels as input and returns one channel. If any of the input channels is closed, the output channel is closed. This is used for control structure.
done1 := make(chan, interface{})
done2 := make(chan, interface{})
done := chanz.SomeDone(done1, done2)
go func(){
time.Sleep(time.Second)
close(done1)
time.Sleep(time.Second)
close(done2)
}
<- done // will read in 1 secound
Takes N channels as input and returns one channel. When all input channels is closed, the output channel will be closed. This is used for control structure.
done1 := make(chan, interface{})
done2 := make(chan, interface{})
done := chanz.SomeDone(done1, done2)
go func(){
time.Sleep(time.Second)
close(done1)
time.Sleep(time.Second)
close(done2)
}
<- done // will read in 2 seconds
Will collect all read items into a slice and return it
in := chanz.Generate(1,2,3,4,5)
chanz.Collect(in)
// []int{1,2,3,4,5}
Will remove consecutive duplicates from the channel
in := chanz.Generate(1,1,3,2,2,5,1)
w := chanz.Compact(in)
chanz.Collect(w)
// []int{1,3,2,5,1}
Will concatenate channels
in1 := chanz.Generate(1,2,3)
in2 := chanz.Generate(4,5,6)
w := chanz.Concat(in1, in2)
chanz.Collect(w)
// []int{1,2,3,4,5,6}
Drops the first N entries of the channel
in := chanz.Generate(1,2,3,4,5,6)
w := chanz.Drop(in, 2)
chanz.Collect(w)
// []int{3, 4, 5, 6}
Drops the first entries of the channel until function returns true
in := chanz.Generate(1,2,3,4,5,6,1)
w := chanz.DropWhile(in, func(i int) bool { return i < 3})
chanz.Collect(w)
// []int{3, 4, 5, 6, 1}
Drops all elements until closed
in := chanz.Generate(1,2,3,4,5,6)
w := chanz.DropAll(in, false)
chanz.Collect(w)
// []int{}
Drops all elements that is buffered in the chan
in := chanz.GenerateWith[int](chanz.Buffer(2))(1,2,3,4,5,6)
chanz.DropBuffer(in, false)
chanz.Collect(in)
// []int{3,4,5,6}
Take all elements that is buffered in the chan
in := chanz.GenerateWith[int](chanz.Buffer(2))(1,2,3,4,5,6)
r := chanz.TakeBuffer(in)
// []int{1,2}
c := chanz.Collect(w)
// []int{3,4,5,6}
Takes an input channel and fans it out to multiple output channels
in := chanz.Generate(1,2,3,4,5,6)
chans := chanz.FanOut(in, 2)
go chanz.Collect(chans[0])
chanz.Collect(chans[1])
// []int{1,2,3,4,5,6}
// []int{1,2,3,4,5,6}
Filters the items read onto the output chan
in := chanz.Generate(1,2,3,4,5,6)
even := chanz.Filter(in, func(i int) bool { return i % 2 == 0})
chanz.Collect(even)
// []int{2,4,6}
Flattens a channel that produces slices
in := chanz.Generate([]int{1,2,3}, []int{4,5,6})
w := chanz.Flatten(in)
chanz.Collect(w)
// []int{1,2,3,4,5,6}
Takes elements, creates a channel and writes the elements to it
w := chanz.Generate(1,2,3,4)
chanz.Collect(w)
// []int{1,2,3,4}
Maps element from one channel to another
in := chanz.Generate(1,2,3,4)
w := chanz.Map(in, func(i int) string { return fmt.Sprint(i) })
chanz.Collect(w)
// []string{"1","2","3","4"}
Merge will take N chans and merge them onto one channel (in a non-particular order)
in1 := chanz.Generate(1,2,3)
in2 := chanz.Generate(4,5,6)
w := chanz.Merge(in1, in2)
chanz.Collect(w)
// []int{4,1,5,6,2,3}
Partition a channel into to two channels
in := chanz.Generate(1,2,3,4,5,6)
even, odd := chanz.Partition(in, func(i int) bool { return i % 2 == 0})
go chanz.Collect(even)
chanz.Collect(odd)
// []int{2,4,6}
// []int{1,3,5}
Will produce a channel that runs a function for each item
in := chanz.Generate(1,2,3,4,5,6)
in := chanz.Peek(in, func(i int){ fmt.Print(i)})
chanz.Collect(in)
// 123456
// []int{1,2,3,4,5,6}
Will take the first N items from the channel
in := chanz.Generate(1,2,3,4,5,6)
w := chanz.Take(in, 2)
chanz.Collect(w)
// []int{1,2}
Will take the first items from the channel until the predicate function returns false
in := chanz.Generate(1,2,3,4,5,6)
w := chanz.TakeWhile(in, func(i int) bool{ return i < 3})
chanz.Collect(w)
// []int{1,2}
Takes one chan and unzips it into two
in := chanz.Generate(-2, -1, 1, 2)
possitive, value := chanz.Unzip(in, func(i int) (bool, int) {
return i > 0, Math.Abs(i)
})
go chanz.Collect(possitive)
chanz.Collect(value)
// []bool{false, false, true, true}
// []int{2,1,1,2}
Takes two channels and zips them into one channel
in1 := chanz.Generate(1,2,3)
in2 := chanz.Generate("a","b","c")
x := chanz.Unzip(in1, in2, func(i int, s string) string {
return fmt.Sprint(i,s)
})
chanz.Collect(w)
// []string{"1a", "2b", "3c"}
Takes a slice of channels and returns a slice casted to read channels
Takes a slice of channels and returns a slice casted to write channels