From 485fca69ec56b34b4ace0785c21e33b66da6d9f3 Mon Sep 17 00:00:00 2001 From: Benjamin VAUDOUR Date: Sat, 23 Sep 2023 15:39:05 +0200 Subject: [PATCH] Module collection (1) --- collection/chain.go | 54 ++++++++++++ collection/list.go | 125 +++++++++++++++++++++++++++ collection/queue.go | 95 ++++++++++++++++++++ collection/set.go | 78 +++++++++++++++++ collection/slice.go | 205 ++++++++++++++++++++++++++++++++++++++++++++ collection/stack.go | 72 ++++++++++++++++ 6 files changed, 629 insertions(+) create mode 100644 collection/chain.go create mode 100644 collection/list.go create mode 100644 collection/queue.go create mode 100644 collection/set.go create mode 100644 collection/slice.go create mode 100644 collection/stack.go diff --git a/collection/chain.go b/collection/chain.go new file mode 100644 index 0000000..fe357e7 --- /dev/null +++ b/collection/chain.go @@ -0,0 +1,54 @@ +package collection + +import ( + . "gitea.zaclys.com/bvaudour/gob/option" +) + +type chain[T any] struct { + value T + next *chain[T] +} + +func prevChain[T any](value T, next *chain[T]) *chain[T] { + return &chain[T]{ + value: value, + next: next, + } +} + +func nextChain[T any](value T, prev Option[*chain[T]]) *chain[T] { + c := &chain[T]{ + value: value, + } + + if p, ok := prev.Get(); ok { + p.next = c + } + + return c +} + +type dualChain[T any] struct { + value T + prev *dualChain[T] + next *dualChain[T] +} + +func newDual[T any](e T) *dualChain[T] { + return &dualChain[T]{ + value: e, + } +} + +func link[T any](o1, o2 Option[*dualChain[T]]) { + if c1, ok := o1.Get(); ok { + if c2, ok := o2.Get(); ok { + c1.next = c2 + c2.prev = c1 + } else { + c1.next = nil + } + } else if c2, ok := o2.Get(); ok { + c2.prev = nil + } +} diff --git a/collection/list.go b/collection/list.go new file mode 100644 index 0000000..f20a615 --- /dev/null +++ b/collection/list.go @@ -0,0 +1,125 @@ +package collection + +import ( + . "gitea.zaclys.com/bvaudour/gob/option" +) + +// List représente une collection d’élément qui peut être modifiée (ajout/suppression) par chaque bout. +// Elle peut donc se comporter à la fois comme une pile et une file. +type List[T any] struct { + first *dualChain[T] + last *dualChain[T] + size uint +} + +// Len retourne le nombre d’éléments de la liste. +func (l *List[T]) Len() uint { + return l.size +} + +func (l *List[T]) push_front(e T) { + var ( + c = newDual(e) + oldFirst Option[*dualChain[T]] + ) + + if l.size == 0 { + l.last = c + } else { + oldFirst = Some(l.first) + } + + link(Some(c), oldFirst) + l.first = c + l.size++ +} + +func (l *List[T]) push_back(e T) { + var ( + c = newDual(e) + oldLast Option[*dualChain[T]] + ) + + if l.size == 0 { + l.first = c + } else { + oldLast = Some(l.last) + } + link(oldLast, Some(c)) + + l.last = c + l.size++ +} + +// PushFront ajoute tous les éléments d’entrée en début de liste. +func (l *List[T]) PushFront(elems ...T) { + for _, e := range elems { + l.push_front(e) + } +} + +// PushBack ajoute tous les éléments d’entrée en fin de liste. +func (l *List[T]) PushBack(elems ...T) { + for _, e := range elems { + l.push_back(e) + } +} + +// PopFront supprime le premier élément de la liste. +func (l *List[T]) PopFront() (e Option[T]) { + if l.size > 0 { + e = Some(l.first.value) + l.first = l.first.next + l.size-- + link(None[*dualChain[T]](), Some(l.first)) + if l.size == 0 { + l.last = l.first + } + } + + return +} + +// PopLast supprime le dernier élément de la liste. +func (l *List[T]) PopLast() (e Option[T]) { + if l.size > 0 { + e = Some(l.last.value) + l.last = l.last.prev + l.size-- + link(Some(l.last), None[*dualChain[T]]()) + if l.size == 0 { + l.first = l.last + } + } + + return +} + +// Concatenate concatène deux listes. +func (q1 *List[T]) Concatenate(l2 *List[T]) { + c, l := l2.first, l2.size + for i := uint(0); i < l; i++ { + q1.push_back(c.value) + c = c.next + } +} + +// ToSlice convertit une liste en slice. +func (l *List[T]) ToSlice() (sl []T) { + sl = make([]T, l.size) + c := l.first + for i := uint(0); i < l.size; i++ { + sl[i] = c.value + c = c.next + } + + return +} + +// NewList retourne une liste initialisée à partir des données d’entrées. +func NewList[T any](elems ...T) (l *List[T]) { + l = new(List[T]) + l.PushBack(elems...) + + return +} diff --git a/collection/queue.go b/collection/queue.go new file mode 100644 index 0000000..4970456 --- /dev/null +++ b/collection/queue.go @@ -0,0 +1,95 @@ +package collection + +import ( + . "gitea.zaclys.com/bvaudour/gob/option" +) + +// Queue représente une file d’élément. +// Une file est une collection où les éléments suivant la logique “premier arrivé, premier servi“. +type Queue[T any] struct { + first *chain[T] + last *chain[T] + size uint +} + +// Len retourne le nombre d’éléments de la file. +func (q Queue[T]) Len() uint { + return q.size +} + +func (q *Queue[T]) push(e T) { + var ( + isEmpty = q.size == 0 + oldLast Option[*chain[T]] + ) + + if !isEmpty { + oldLast = Some(q.last) + } + q.last = nextChain(e, oldLast) + + if isEmpty { + q.first = q.last + } + q.size++ +} + +// Push ajoute tous les éléments d’entrée dans la file. +func (q *Queue[T]) Push(elems ...T) { + for _, e := range elems { + q.push(e) + } +} + +// Concatenate concatène deux files. +func (q1 *Queue[T]) Concatenate(q2 *Queue[T]) { + c, l := q2.first, q2.size + for i := uint(0); i < l; i++ { + q1.push(c.value) + c = c.next + } +} + +// Pop dépile le premier élément de la file et le retourne. +func (q *Queue[T]) Pop() (e Option[T]) { + if q.size > 0 { + e = Some(q.first.value) + q.first = q.first.next + q.size-- + if q.size == 0 { + q.last = q.first + } + } + + return +} + +// ToSlice convertit une file en slice. +func (q *Queue[T]) ToSlice() (sl []T) { + sl = make([]T, q.size) + c := q.first + for i := uint(0); i < q.size; i++ { + sl[i] = c.value + c = c.next + } + + return +} + +// ToStack convertit une file en pile. +func (q *Queue[T]) ToStack() (s *Stack[T]) { + s = &Stack[T]{ + first: q.first, + size: q.size, + } + + return +} + +// NewQueue retourne une file initialisée à partir des données d’entrées. +func NewQueue[T any](elems ...T) (q *Stack[T]) { + q = new(Stack[T]) + q.Push(elems...) + + return +} diff --git a/collection/set.go b/collection/set.go new file mode 100644 index 0000000..7b603ac --- /dev/null +++ b/collection/set.go @@ -0,0 +1,78 @@ +package collection + +var empty struct{} + +// Set est une collection de valeurs uniques. +type Set[T comparable] map[T]struct{} + +// Add ajoute les valeurs en entrée dans le set et retourne le set lui-même. +func (s Set[T]) Add(in ...T) Set[T] { + for _, e := range in { + s[e] = empty + } + + return s +} + +// Del supprime les valeurs données en entrées du set et retourne le set lui-même. +func (s Set[T]) Del(in ...T) Set[T] { + for _, e := range in { + delete(s, e) + } + + return s +} + +// Contains retourne vrai si le set contient la valeur d'entrée. +func (s Set[T]) Contains(in T) (out bool) { + _, out = s[in] + + return +} + +// ContainsOneOf retourne vrai si le set contient au moins une des valeurs d’entrée. +func (s Set[T]) ContainsOneOf(in ...T) bool { + for _, e := range in { + if s.Contains(e) { + return true + } + } + + return false +} + +// ContainsAll retourne vrai si le set contient toutes les valeurs d’entrée. +func (s Set[T]) ContainsAll(in ...T) bool { + for _, e := range in { + if !s.Contains(e) { + return false + } + } + + return true +} + +// Len retourne le nombre d’éléments du set. +func (s Set[T]) Len() int { + return len(s) +} + +// ToSlice convertit un set en slice d'éléments de même type. +func (s Set[T]) ToSlice() (out []T) { + out = make([]T, len(s)) + i := 0 + for e := range s { + out[i] = e + i++ + } + + return +} + +// NewSet retourne un set initialisé avec les valeurs d'entrée. +func NewSet[T comparable](in ...T) (out Set[T]) { + out = make(Set[T]) + out.Add(in...) + + return +} diff --git a/collection/slice.go b/collection/slice.go new file mode 100644 index 0000000..2e8f168 --- /dev/null +++ b/collection/slice.go @@ -0,0 +1,205 @@ +package collection + +// Slice crée un slice générique contenant les valeurs d’entrée. +func Slice[T any](in ...T) (out []any) { + out = make([]any, len(in)) + + for i, e := range in { + out[i] = e + } + + return +} + +// Add ajoute les valeurs d’entrée en fin du slice. +func Add[T any](s *([]T), in ...T) { + *s = append(*s, in...) +} + +// Insert ajoute les valeurs d’entrée en début du slice. +func Insert[T any](s *([]T), in ...T) { + *s = append(in, (*s)...) +} + +// InsertAt ajoute les valeurs d’entrée à l’index i du slice. +// - Si l’index est inférieur à 0, agit comme Insert. +// - Si l’index est supérieur ou égal à la taille du slice, agit comme Add. +func InsertAt[T any](s *([]T), i int, in ...T) { + if i <= 0 { + Insert(s, in...) + return + } else if i >= len(*s) { + Add(s, in...) + return + } + + l1, l2 := len(*s), len(in) + result := make([]T, l1+l2) + copy(result[:i], (*s)[:i]) + copy(result[i:i+l2], in) + copy(result[i+l2:], (*s)[i:]) + + *s = result +} + +// Diff retourne un slice contenant les entrées de s1 qui ne sont pas dans s2. +func Diff[T comparable](s1, s2 []T) (out []T) { + ss := NewSet(s2...) + + for _, e := range s1 { + if !ss.Contains(e) { + Add(&out, e) + } + } + + return +} + +// Intersect retourne un slice contenant les entrées contenues à la fois dans s1 et s2. +func Intersect[T comparable](s1, s2 []T) (out []T) { + ss := NewSet(s2...) + + for _, e := range s1 { + if ss.Contains(e) { + Add(&out, e) + } + } + + return +} + +// Uniq supprime les valeurs redondantes du slice. +func Uniq[T comparable](s []T) (out []T) { + ss := NewSet[T]() + + for _, e := range s { + if !ss.Contains(e) { + ss.Add(e) + Add(&out, e) + } + } + + return +} + +// AddUniq ajoute les entrées en fin de slice si elles n’existent pas dans le slice. +func AddUniq[T comparable](s *([]T), in ...T) { + in2 := Uniq(Diff(in, *s)) + Add(s, in2...) +} + +// InsertUniq ajoute les entrées en début de slice si elles n’existent pas dans le slice. +func InsertUniq[T comparable](s *([]T), in ...T) { + in2 := Uniq(Diff(in, *s)) + Insert(s, in2...) +} + +// InsertAtUniq ajoute les entrées dans le slice à la position donnée si elles n’existent pas dans le slice. +func InsertAtUniq[T comparable](s *([]T), i int, in ...T) { + in2 := Uniq(Diff(in, *s)) + InsertAt(s, i, in2...) +} + +// Contains retourne vrai si le slice contient la valeur d’entrée. +func Contains[T comparable](s []T, in T) bool { + for _, e := range s { + if e == in { + return true + } + } + + return false +} + +// ContainsOneOf retourne vrai si le slice contient une des valeurs d’entrée. +func ContainsOneOf[T comparable](s []T, in ...T) bool { + return NewSet(s...).ContainsOneOf(in...) +} + +// ContainsAll retourne vrai si le slice contient toutes les valeurs d’entrée. +func ContainsAll[T comparable](s []T, in ...T) bool { + return NewSet(s...).ContainsAll(in...) +} + +// Maps retourne un slice à partir d’un autre slice en appliquant une fonction de transformation pour chaque valeur. +func Maps[T1, T2 any](s []T1, f func(T1) T2) (out []T2) { + out = make([]T2, len(s)) + + for i, e := range s { + out[i] = f(e) + } + + return +} + +// Filter retourne un nouveau slice dont les valeurs passent la fonction. +func Filter[T any](s []T, f func(T) bool) (out []T) { + for _, e := range s { + if f(e) { + Add(&out, e) + } + } + + return +} + +// Reduce applique une fonction à tous les éléments dont le résultat sert à la prochaine itération. +func Reduce[T any](s []T, f func(T, T) T, initial ...T) (out T) { + if len(initial) > 0 { + out = initial[0] + } + + for _, e := range s { + out = f(out, e) + } + + return +} + +// Reverse inverse l’ordre du slice. +func Reverse[T any](s []T) (out []T) { + l := len(s) + out = make([]T, l) + for i, e := range s { + out[l-1-i] = e + } + + return +} + +// Zip retourne une map dont les clés sont les valeurs du premier slice et les valeurs, les valeurs du second. +func Zip[K comparable, V any](s1 []K, s2 []V) (out map[K]V) { + out = make(map[K]V) + l := min(len(s1), len(s2)) + + for i, k := range s1[:l] { + out[k] = s2[i] + } + + return +} + +// Unzip sépare les clés et les valeurs de la map en 2 slices distincts. +func Unzip[K comparable, V any](m map[K]V) (keys []K, values []V) { + for k, v := range m { + Add(&keys, k) + Add(&values, v) + } + + return +} + +// Equal retourne vrai si les slices sont identiques. +func Equal[T comparable](sl1, sl2 []T) bool { + if len(sl1) != len(sl2) { + return false + } + + for i, e1 := range sl1 { + if e1 != sl2[i] { + return false + } + } + + return true +} diff --git a/collection/stack.go b/collection/stack.go new file mode 100644 index 0000000..97a689b --- /dev/null +++ b/collection/stack.go @@ -0,0 +1,72 @@ +package collection + +import ( + . "gitea.zaclys.com/bvaudour/gob/option" +) + +// Stack représente une pile d’élément. +// Une pile est une collection où les éléments suivant la logique “dernier arrivé, premier servi“. +type Stack[T any] struct { + first *chain[T] + size uint +} + +// Len retourne le nombre d’éléments de la pile. +func (s Stack[T]) Len() uint { + return s.size +} + +// Push ajoute tous les éléments d’entrée dans la pile. +func (s *Stack[T]) Push(elems ...T) { + for _, e := range elems { + s.first = prevChain(e, s.first) + s.size++ + } +} + +// Pop dépile le premier élément de la pile et le retourne. +func (s *Stack[T]) Pop() (e Option[T]) { + if s.size > 0 { + e = Some(s.first.value) + s.first = s.first.next + s.size-- + } + + return +} + +// ToSlice convertit une pile en slice. +func (s *Stack[T]) ToSlice() (sl []T) { + sl = make([]T, s.size) + c := s.first + for i := uint(0); i < s.size; i++ { + sl[i] = c.value + c = c.next + } + + return +} + +// ToQueue convertit une pile en file. +func (s *Stack[T]) ToQueue() (q *Queue[T]) { + q = &Queue[T]{ + first: s.first, + size: s.size, + } + + var c = s.first + for i := uint(1); i < s.size; i++ { + c = c.next + } + q.last = c + + return +} + +// NewStack retourne une pile initialisée à partir des données d’entrées. +func NewStack[T any](elems ...T) (s *Stack[T]) { + s = new(Stack[T]) + s.Push(elems...) + + return +}