diff --git a/README.md b/README.md index bbd91d8..95c19df 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# GoUtils - -A collection of useful functions and structs that I use for my projects. +# GoUtils + +A collection of useful functions and structs that I use for my projects. diff --git a/pkg/containers/generics.go b/pkg/containers/generics.go index a06ba02..17c1c4d 100644 --- a/pkg/containers/generics.go +++ b/pkg/containers/generics.go @@ -1,34 +1,34 @@ -package containers - -type ChainElem[T any] struct { - Elem *T - Next *ChainElem[T] -} - -// reachable checks if you can reach elem l when starting from elem f. -// It detects loops and returns false if it runs into one. -func reachable[T any](f, l *ChainElem[T]) bool { - // Map to keep track of nodes already visited - checks := make(map[*ChainElem[T]]bool) - for w := f; w != l; w = w.Next { - if w == nil { - return false - } - // Shortcut: Maps where the value is bool have a default value of false - // If a key doesn't exist yet the result will thus be false - if checks[w] { - return false - } - // Set the current elem to true to mark it as visited - checks[w] = true - } - return true -} - -// emptyElem creates a new ChainElem[T] with empty values. -func emptyElem[T any]() *ChainElem[T] { - return &ChainElem[T]{ - Elem: nil, - Next: nil, - } -} +package containers + +type ChainElem[T any] struct { + Elem *T + Next *ChainElem[T] +} + +// reachable checks if you can reach elem l when starting from elem f. +// It detects loops and returns false if it runs into one. +func reachable[T any](f, l *ChainElem[T]) bool { + // Map to keep track of nodes already visited + checks := make(map[*ChainElem[T]]bool) + for w := f; w != l; w = w.Next { + if w == nil { + return false + } + // Shortcut: Maps where the value is bool have a default value of false + // If a key doesn't exist yet the result will thus be false + if checks[w] { + return false + } + // Set the current elem to true to mark it as visited + checks[w] = true + } + return true +} + +// emptyElem creates a new ChainElem[T] with empty values. +func emptyElem[T any]() *ChainElem[T] { + return &ChainElem[T]{ + Elem: nil, + Next: nil, + } +} diff --git a/pkg/containers/queues.go b/pkg/containers/queues.go index 4138320..b528692 100644 --- a/pkg/containers/queues.go +++ b/pkg/containers/queues.go @@ -1,95 +1,95 @@ -package containers - -import ( - "encoding/json" - "errors" -) - -type Queue[T any] struct { - head *ChainElem[T] - tail *ChainElem[T] -} - -// isValid checks if the queue is still valid. -func (q *Queue[T]) isValid() bool { - return q.head != nil && - q.tail != nil && - reachable(q.head, q.tail) -} - -// IsEmpty checks if the queue is empty. -func (q *Queue[T]) IsEmpty() bool { - return q.head == q.tail -} - -// Push adds a new element to the end of the queue. -func (q *Queue[T]) Push(elem *T) error { - if !q.isValid() { - return errors.New("invalid queue") - } - - e := emptyElem[T]() - q.tail.Elem = elem - q.tail.Next = e - q.tail = e - - return nil -} - -// Pop removes the first element of the queue. -// It errors out if there is no element or the queue is invalid. -func (q *Queue[T]) Pop() (*T, error) { - if !q.isValid() { - return nil, errors.New("invalid queue") - } - if q.IsEmpty() { - return nil, errors.New("empty queue") - } - Elem := q.head.Elem - q.head = q.head.Next - return Elem, nil -} - -// Top returns the first element of the queue without removing it. -// It errors out if there is no element or the queue is invalid. -func (q *Queue[T]) Top() (*T, error) { - if !q.isValid() { - return nil, errors.New("queue invalid") - } - if q.IsEmpty() { - return nil, errors.New("queue empty") - } - return q.head.Elem, nil -} - -// HeadElem returns the first ChainElem of the queue without removing it. -// It errors out if there is no element or the queue is invalid. -func (q *Queue[T]) HeadElem() (*ChainElem[T], error) { - if !q.isValid() { - return nil, errors.New("queue invalid") - } - if q.IsEmpty() { - return nil, errors.New("queue empty") - } - return q.head, nil -} - -// MarshalJSON is used for generating json data when using json.Marshal. -func (q *Queue[T]) MarshalJSON() ([]byte, error) { - if !q.isValid() { - return nil, errors.New("queue invalid") - } - if q.IsEmpty() { - return nil, errors.New("queue empty") - } - - return json.Marshal(q.head) -} - -func BuildQueue[T any]() *Queue[T] { - empty := emptyElem[T]() - return &Queue[T]{ - head: empty, - tail: empty, - } -} +package containers + +import ( + "encoding/json" + "errors" +) + +type Queue[T any] struct { + head *ChainElem[T] + tail *ChainElem[T] +} + +// isValid checks if the queue is still valid. +func (q *Queue[T]) isValid() bool { + return q.head != nil && + q.tail != nil && + reachable(q.head, q.tail) +} + +// IsEmpty checks if the queue is empty. +func (q *Queue[T]) IsEmpty() bool { + return q.head == q.tail +} + +// Push adds a new element to the end of the queue. +func (q *Queue[T]) Push(elem *T) error { + if !q.isValid() { + return errors.New("invalid queue") + } + + e := emptyElem[T]() + q.tail.Elem = elem + q.tail.Next = e + q.tail = e + + return nil +} + +// Pop removes the first element of the queue. +// It errors out if there is no element or the queue is invalid. +func (q *Queue[T]) Pop() (*T, error) { + if !q.isValid() { + return nil, errors.New("invalid queue") + } + if q.IsEmpty() { + return nil, errors.New("empty queue") + } + Elem := q.head.Elem + q.head = q.head.Next + return Elem, nil +} + +// Top returns the first element of the queue without removing it. +// It errors out if there is no element or the queue is invalid. +func (q *Queue[T]) Top() (*T, error) { + if !q.isValid() { + return nil, errors.New("queue invalid") + } + if q.IsEmpty() { + return nil, errors.New("queue empty") + } + return q.head.Elem, nil +} + +// HeadElem returns the first ChainElem of the queue without removing it. +// It errors out if there is no element or the queue is invalid. +func (q *Queue[T]) HeadElem() (*ChainElem[T], error) { + if !q.isValid() { + return nil, errors.New("queue invalid") + } + if q.IsEmpty() { + return nil, errors.New("queue empty") + } + return q.head, nil +} + +// MarshalJSON is used for generating json data when using json.Marshal. +func (q *Queue[T]) MarshalJSON() ([]byte, error) { + if !q.isValid() { + return nil, errors.New("queue invalid") + } + if q.IsEmpty() { + return nil, errors.New("queue empty") + } + + return json.Marshal(q.head) +} + +func BuildQueue[T any]() *Queue[T] { + empty := emptyElem[T]() + return &Queue[T]{ + head: empty, + tail: empty, + } +} diff --git a/pkg/containers/stacks.go b/pkg/containers/stacks.go index 4bb70cd..57a4b55 100644 --- a/pkg/containers/stacks.go +++ b/pkg/containers/stacks.go @@ -1,81 +1,81 @@ -package containers - -import ( - "encoding/json" - "errors" -) - -type Stack[T any] struct { - top *ChainElem[T] - bottom *ChainElem[T] -} - -// isValid checks if the stack is valid. -func (s *Stack[T]) isValid() bool { - return s.top != nil && - s.bottom != nil && - reachable(s.top, s.bottom) -} - -// IsEmpty checks if the stack is currently empty. -func (s *Stack[T]) IsEmpty() bool { - return s.top == s.bottom -} - -// Push adds a new element to the top of the stack. -// It errors out if the stack is invalid. -func (s *Stack[T]) Push(e *T) error { - if !s.isValid() { - return errors.New("stack invalid") - } - n := emptyElem[T]() - n.Elem = e - n.Next = s.top - s.top = n - return nil -} - -// Pop removes the first element at the top of the stack and returns it. -// It errors out if the stack is invalid or empty. -func (s *Stack[T]) Pop() (*T, error) { - if !s.isValid() { - return nil, errors.New("stack invalid") - } - if s.IsEmpty() { - return nil, errors.New("stack empty") - } - e := s.top.Elem - s.top = s.top.Next - return e, nil -} - -// Top returns the first element at the top of the stack without removing it. -// It errors out if the stack is empty or invalid. -func (s *Stack[T]) Top() (*T, error) { - if !s.isValid() { - return nil, errors.New("stack invalid") - } - if s.IsEmpty() { - return nil, errors.New("stack empty") - } - return s.top.Elem, nil -} - -// MarshalJSON is used by json.Marshal to create a json representation. -func (s *Stack[T]) MarshalJSON() ([]byte, error) { - if !s.isValid() { - return nil, errors.New("queue invalid") - } - if s.IsEmpty() { - return nil, errors.New("queue empty") - } - - return json.Marshal(s.top) -} -func BuildStack[T any]() *Stack[T] { - empty := emptyElem[T]() - return &Stack[T]{ - top: empty, - bottom: empty, - } -} +package containers + +import ( + "encoding/json" + "errors" +) + +type Stack[T any] struct { + top *ChainElem[T] + bottom *ChainElem[T] +} + +// isValid checks if the stack is valid. +func (s *Stack[T]) isValid() bool { + return s.top != nil && + s.bottom != nil && + reachable(s.top, s.bottom) +} + +// IsEmpty checks if the stack is currently empty. +func (s *Stack[T]) IsEmpty() bool { + return s.top == s.bottom +} + +// Push adds a new element to the top of the stack. +// It errors out if the stack is invalid. +func (s *Stack[T]) Push(e *T) error { + if !s.isValid() { + return errors.New("stack invalid") + } + n := emptyElem[T]() + n.Elem = e + n.Next = s.top + s.top = n + return nil +} + +// Pop removes the first element at the top of the stack and returns it. +// It errors out if the stack is invalid or empty. +func (s *Stack[T]) Pop() (*T, error) { + if !s.isValid() { + return nil, errors.New("stack invalid") + } + if s.IsEmpty() { + return nil, errors.New("stack empty") + } + e := s.top.Elem + s.top = s.top.Next + return e, nil +} + +// Top returns the first element at the top of the stack without removing it. +// It errors out if the stack is empty or invalid. +func (s *Stack[T]) Top() (*T, error) { + if !s.isValid() { + return nil, errors.New("stack invalid") + } + if s.IsEmpty() { + return nil, errors.New("stack empty") + } + return s.top.Elem, nil +} + +// MarshalJSON is used by json.Marshal to create a json representation. +func (s *Stack[T]) MarshalJSON() ([]byte, error) { + if !s.isValid() { + return nil, errors.New("queue invalid") + } + if s.IsEmpty() { + return nil, errors.New("queue empty") + } + + return json.Marshal(s.top) +} +func BuildStack[T any]() *Stack[T] { + empty := emptyElem[T]() + return &Stack[T]{ + top: empty, + bottom: empty, + } +} diff --git a/pkg/maputils/mapUtils.go b/pkg/maputils/mapUtils.go index 8feef02..8689ad4 100644 --- a/pkg/maputils/mapUtils.go +++ b/pkg/maputils/mapUtils.go @@ -1,57 +1,57 @@ -package maps - -import "gitlab.com/beckersam/goutils/pkg/sliceutils" - -// MapMap applies a given function to every key-value pair of a map. -// The returned map's value type may be different from the type of the inital map's value. -func MapMap[K comparable, V any, R any](dic map[K]V, apply func(K, V) R) map[K]R { - n := make(map[K]R, len(dic)) - for key, val := range dic { - n[key] = apply(key, val) - } - return n -} - -// FilterMap filters a map using a given function. -// If the filter function returns true, the key-value pair stays, otherwise it gets removed. -func FilterMap[K comparable, V any](dic map[K]V, filter func(K, V) bool) map[K]V { - n := make(map[K]V, 0) - for key, val := range dic { - if filter(key, val) { - n[key] = val - } - } - return n -} - -// KeysFromMap creates a slice of keys that match the keys in a given map. -func KeysFromMap[K comparable, V any](m map[K]V) []K { - keys := make([]K, len(m)) - - i := 0 - for k := range m { - keys[i] = k - i++ - } - return keys -} - -// CompareMap compares two maps for key-val equality (If both maps have the same key-value pairs). -func CompareMap[K, V comparable](a, b map[K]V) bool { - if len(a) != len(b) { - return false - } - // Check if both maps have the same keys - if !sliceutils.CompareUnorderedSlice(KeysFromMap(a), KeysFromMap(b)) { - return false - } - - // Then compare key-value pairs - for k, v := range a { - val, ok := b[k] - if !(ok && val == v) { - return false - } - } - return true -} +package maps + +import "gitlab.com/beckersam/goutils/pkg/sliceutils" + +// MapMap applies a given function to every key-value pair of a map. +// The returned map's value type may be different from the type of the inital map's value. +func MapMap[K comparable, V any, R any](dic map[K]V, apply func(K, V) R) map[K]R { + n := make(map[K]R, len(dic)) + for key, val := range dic { + n[key] = apply(key, val) + } + return n +} + +// FilterMap filters a map using a given function. +// If the filter function returns true, the key-value pair stays, otherwise it gets removed. +func FilterMap[K comparable, V any](dic map[K]V, filter func(K, V) bool) map[K]V { + n := make(map[K]V, 0) + for key, val := range dic { + if filter(key, val) { + n[key] = val + } + } + return n +} + +// KeysFromMap creates a slice of keys that match the keys in a given map. +func KeysFromMap[K comparable, V any](m map[K]V) []K { + keys := make([]K, len(m)) + + i := 0 + for k := range m { + keys[i] = k + i++ + } + return keys +} + +// CompareMap compares two maps for key-val equality (If both maps have the same key-value pairs). +func CompareMap[K, V comparable](a, b map[K]V) bool { + if len(a) != len(b) { + return false + } + // Check if both maps have the same keys + if !sliceutils.CompareUnorderedSlice(KeysFromMap(a), KeysFromMap(b)) { + return false + } + + // Then compare key-value pairs + for k, v := range a { + val, ok := b[k] + if !(ok && val == v) { + return false + } + } + return true +} diff --git a/pkg/sliceutils/sliceUtils.go b/pkg/sliceutils/sliceUtils.go index b4ccec3..40070a2 100644 --- a/pkg/sliceutils/sliceUtils.go +++ b/pkg/sliceutils/sliceUtils.go @@ -1,73 +1,73 @@ -package sliceutils - -// MapSlice applies a given function to every element of a slice. -// The return type may be different from the initial type of the slice. -func MapSlice[T any, M any](arr []T, apply func(T) M) []M { - n := make([]M, len(arr)) - for i, e := range arr { - n[i] = apply(e) - } - return n -} - -// FilterSlice filters a slice using a given function. -// If the filter function returns true, the element stays, otherwise it gets removed. -func FilterSlice[T any](arr []T, filter func(T) bool) []T { - n := make([]T, 0) - for _, e := range arr { - if filter(e) { - n = append(n, e) - } - } - return n -} - -// RemoveDuplicateSlice removes all duplicates inside a slice. -func RemoveDuplicateSlice[T comparable](sliceList []T) []T { - allKeys := make(map[T]bool) - list := []T{} - for _, item := range sliceList { - if _, value := allKeys[item]; !value { - allKeys[item] = true - list = append(list, item) - } - } - return list -} - -// ReverseSlice reverses a given slice. -func ReverseSlice[S ~[]E, E any](s S) { - for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { - s[i], s[j] = s[j], s[i] - } -} - -// CompareOrderedSlice compares two slices for both element equality and element order. -func CompareOrderedSlice[T comparable](a, b []T) bool { - if len(a) != len(b) { - return false - } - for i, v := range a { - if b[i] != v { - return false - } - } - return true -} - -// CompareUnorderedSlice compares two slices for element equality. -// The order of those elements does not matter. -func CompareUnorderedSlice[T comparable](a, b []T) bool { - if len(a) != len(b) { - return false - } - hits := 0 - for _, v := range a { - for _, o := range b { - if o == v { - hits += 1 - } - } - } - return hits == len(a) -} +package sliceutils + +// MapSlice applies a given function to every element of a slice. +// The return type may be different from the initial type of the slice. +func MapSlice[T any, M any](arr []T, apply func(T) M) []M { + n := make([]M, len(arr)) + for i, e := range arr { + n[i] = apply(e) + } + return n +} + +// FilterSlice filters a slice using a given function. +// If the filter function returns true, the element stays, otherwise it gets removed. +func FilterSlice[T any](arr []T, filter func(T) bool) []T { + n := make([]T, 0) + for _, e := range arr { + if filter(e) { + n = append(n, e) + } + } + return n +} + +// RemoveDuplicateSlice removes all duplicates inside a slice. +func RemoveDuplicateSlice[T comparable](sliceList []T) []T { + allKeys := make(map[T]bool) + list := []T{} + for _, item := range sliceList { + if _, value := allKeys[item]; !value { + allKeys[item] = true + list = append(list, item) + } + } + return list +} + +// ReverseSlice reverses a given slice. +func ReverseSlice[S ~[]E, E any](s S) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} + +// CompareOrderedSlice compares two slices for both element equality and element order. +func CompareOrderedSlice[T comparable](a, b []T) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true +} + +// CompareUnorderedSlice compares two slices for element equality. +// The order of those elements does not matter. +func CompareUnorderedSlice[T comparable](a, b []T) bool { + if len(a) != len(b) { + return false + } + hits := 0 + for _, v := range a { + for _, o := range b { + if o == v { + hits += 1 + } + } + } + return hits == len(a) +}