henry
henry copied to clipboard
Henry is a go library with generic helper functions in dealing with slices, maps and channels
Henry
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
Install
go get github.com/modfin/henry/...
Usage
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"}
Error handling
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
}
Content
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
Slicez
The slicez
package contains generic utility functions and algorithms for slices
Clone
Produces a copy of a given slice
s := []int{1,2,3}
clone := slicez.Clone[int](s)
// []int{1,2,3}
Compact
Removes consecutive duplicates from a slice
s := []int{1, 1, 2, 3, 3}
slicez.Compact[int](s)
// []int{1,2,3}
CompactFunc
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"
Compare
Compares two slices for equality
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
slicez.Compare[int](s1, s2)
// 0
CompareFunc
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
Complement
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}
ComplementBy
Concat
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}
Contains
Return true if an element is present in the slice
slicez.Contains[int]([]int{1, 2, 3, 4, 5}, 3)
// true
ContainsFunc
Returns true if the function returns true.
slicez.ContainsFunc[int]([]int{4, 5, 6, 7}, func(i int){
return i % 4 == 3
})
// true
Cut
Cuts a slice into two parts
slicez.Cut[int]([]int{1,2,3,4,5}, 3)
// []int{1,2}, []int{4,5}, true
CutFunc
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
Difference
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}
DifferenceBy
Drop
Drops the first N elements
slicez.Drop[int]([]int{1, 2, 3, 4, 5}, 2)
// []int{3,4,5}
DropRight
Drops the last N elements
slicez.DropRight[int]([]int{1, 2, 3, 4, 5}, 2)
// []int{1,2,3}
DropWhile
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}
DropRightWhile
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}
Each
Applies a function to each element of a slice
slicez.Each[int]([]int{1, 2, 3}, func (i int) { fmt.Print(i) })
// 123
Equal
Returns true if two slices are identical
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
slicez.Equal[int](s1, s2)
// true
EqualFunc
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
Every
Returns true if every element matches the given value
s1 := []int{1, 1, 1}
slicez.Every[int](s1, 1)
// true
EveryFunc
Every
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
Filter
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
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
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
FlatMap
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"}
Flatten
Flattens a nested slice
s := [][]string{{"a", "b"}, {"c","d"}}
slicez.Flatten(s)
// []string{"a","b","c","d"}
Fold
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"
FoldRight
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"
GroupBy
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]}
Head
Returns the first element of a slice if present
slicez.Head([]int{1,2,3})
// 1, nil
Index
Returns the index of the first occurrence of an element
slicez.Index([]int{1,2,3}, 2)
// 1
IndexFunc
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
Intersection
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}
IntersectionBy
Intersection
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}
Join
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"}
KeyBy
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}
Last
Returns the last element in a slice, or an error if len(s) == 0
slicez.Last([]int{1,2,3})
// 3, nil
LastIndex
Finds the last index of a needle, or -1 if not present
slicez.LastIndex([]int{1,1,2,1,3}, 1)
// 3
LastIndexFunc
LastIndex
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
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"}
Max
Returns the maximum value of a slice
s := []int{1,2,5,4}
slicez.Max(s...)
// 5
Min
Returns the minimum value of a slice
s := []int{1,2,5,0, 4}
slicez.Min(s...)
// 0
None
Returns true if no element match the needle
s := []int{1,2,3}
slicez.None(s, 0)
// true
NoneFunc
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
Nth
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
Partition
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
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}
Reverse
Reverses a slice
s := []int{1,2,3}
slicez.Reverse(s)
//[]int{3,2,1}
Sample
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}
Search
Shuffle
Returns a shuffled version of the slice
s := []int{1,2,3,4,5}
slicez.Shuffle(s)
//[]int{3,1,4,5,3}
Some
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
SomeFunc
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
Sort
Sorts a slice
s := []int{3,2,1}
slicez.Sort(s)
//[]int{1,2,3}
SortFunc
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}
Tail
Returns the tail of a slice
s := []int{1,2,3}
slicez.Tail(s)
// []int{2,3}
Take
Returns the N first element of a slice
s := []int{1,2,3,4}
slicez.Take(s, 2)
// []int{1,2}
TakeRight
Returns the N last element of a slice
s := []int{1,2,3,4}
slicez.TakeRight(s, 2)
// []int{3, 4}
TakeWhile
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}
TakeRightWhile
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}
Union
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}
UnionBy
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}
Uniq
Returns a slice of uniq elements
a := []int{1,2,3,1,3,4}
slicez.Uniq(a)
// []int{1,2,3,4}
UniqBy
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}
Unzip
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}
Unzip2
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}
Zip
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"}
Zip2
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"}
Mapz
The mapz
package contains generic utility functions and algorithms for maps
Clear
Deletes every entry in a map
m := map[int]int{1:1, 2:2}
mapz.Clear(m)
// map[int]int{}
Clone
Creates a clone of a map
m := map[int]int{1:1, 2:2}
mapz.Clone(m)
// map[int]int{1:1, 2:2}
Copy
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}
DeleteFunc
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}
DeleteValue
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}
Equal
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
EqualFunc
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
Keys
Returns a slice of all keys in the map
m := map[int]int{1:1, 3:3}
mapz.Keys(m)
// []int{1,3}
Merge
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}
Remap
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"}
Values
Returns a slice of all values in the map
m := map[int]int{2:6, 4:12}
mapz.Values(m)
// []int{6,12}
Chanz
The chanz
package contains generic utility functions and algorithms for channels
SomeDone
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
EveryDone
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
Collect, CollectUntil
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}
Compact, Compact1, CompactN, CompactUntil
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}
Concat, Concat1, ConcatN, ConcatUntil
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}
Drop, Drop1, DropN, DropUntil
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}
DropWhile, DropWhile1, DropWhileN, DropWhileUntil
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}
DropAll
Drops all elements until closed
in := chanz.Generate(1,2,3,4,5,6)
w := chanz.DropAll(in, false)
chanz.Collect(w)
// []int{}
DropBuffer
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}
TakeBuffer
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}
FanOut
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}
Filter
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}
Flatten
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}
Generate
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}
Map
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
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
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}
Peek
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}
Take
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}
TakeWhile
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}
Unzip
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}
Zip
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"}
Readers
Takes a slice of channels and returns a slice casted to read channels
Writers
Takes a slice of channels and returns a slice casted to write channels