diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..81d7bcf --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitlab.com/beckersamuel9/utils + +go 1.19 diff --git a/pkg/datastructs/generics.go b/pkg/datastructs/generics.go new file mode 100644 index 0000000..7b42398 --- /dev/null +++ b/pkg/datastructs/generics.go @@ -0,0 +1,34 @@ +package datastructs + +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/datastructs/queues.go b/pkg/datastructs/queues.go new file mode 100644 index 0000000..b953c4d --- /dev/null +++ b/pkg/datastructs/queues.go @@ -0,0 +1,95 @@ +package datastructs + +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/datastructs/stacks.go b/pkg/datastructs/stacks.go new file mode 100644 index 0000000..4d2dc90 --- /dev/null +++ b/pkg/datastructs/stacks.go @@ -0,0 +1,81 @@ +package DataStructs + +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 new file mode 100644 index 0000000..3c572f0 --- /dev/null +++ b/pkg/maputils/mapUtils.go @@ -0,0 +1,55 @@ +package maps + +// 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 new file mode 100644 index 0000000..b4ccec3 --- /dev/null +++ b/pkg/sliceutils/sliceUtils.go @@ -0,0 +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) +}