diff --git a/README.md b/README.md new file mode 100644 index 0000000..f96c165 --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# extypes + +`extypes` is a small Go package with generic collection helpers built on top of native slices and maps. + +It provides: + +- `Slice[T]`: slice helpers such as `Push`, `Pop`, `Remove`, `Filter`, `Map` +- `Tuple[T]`: a read-oriented ordered collection with JSON support +- `Set[T]`: a uniqueness-enforcing collection backed by a slice +- `HashMap[K, V]`: a generic map wrapper with helper methods +- `Queue[T]`: a fixed-size, mutex-protected FIFO queue + +## Requirements + +- Go `1.25+` + +## Installation + +```bash +go get git.nix13.pw/scuroneko/extypes +``` + +## Import + +```go +import "git.nix13.pw/scuroneko/extypes" +``` + +## Design Notes + +- The package stays close to Go built-ins instead of introducing a large abstraction layer. +- `Slice`, `Set`, and `HashMap` methods often return the receiver for chaining. +- Some methods mutate the underlying storage in place: `Slice.Map`, `Slice.Swap`, `Set.Map`, `Set.Swap`, `HashMap.Add`, `HashMap.Remove`, `HashMap.Map`. +- `Tuple` is read-oriented, but `NewTupleFrom` wraps the provided slice without copying it. +- `Set` preserves first-seen order. Its `Equal` method is order-sensitive. +- `Set.Map` does not restore uniqueness if the mapping function produces duplicates. +- `HashMap` iteration order is unspecified, so `Keys`, `Values`, `Items`, `IndexKey`, and `IndexValue` should not be used where stable ordering is required. +- Equality and search for `Slice`, `Tuple`, `Set`, and `HashMap.IndexValue` use `reflect.DeepEqual`, so element types do not need to be `comparable`. + +## Quick Examples + +### Slice + +```go +nums := extypes.NewSliceFrom([]int{1, 2, 3}) + +nums = nums.Push(4) +nums = nums.Remove(2) +nums = nums.Map(func(v int) int { return v * 10 }) + +fmt.Println(nums.ToArray()) // [10 30 40] +``` + +### Tuple + +```go +tuple := extypes.NewTupleFrom([]string{"api", "worker", "cron"}) + +filtered := tuple.Filter(func(v string) bool { + return v != "worker" +}) + +fmt.Println(filtered.Join(", ")) // api, cron +``` + +### Set + +```go +set := extypes.NewSetFrom([]string{"go", "go", "docs", "tests"}) + +set = set.Add("bench") +set = set.Remove("docs") + +fmt.Println(set.ToArray()) // [go tests bench] +``` + +### HashMap + +```go +ports := extypes.NewMapFrom(map[string]int{ + "http": 80, + "https": 443, +}) + +ports.Add("ssh", 22) + +secure := ports.Filter(func(name string, port int) bool { + return port >= 100 || name == "https" +}) + +fmt.Println(secure.Contains("https")) // true +``` + +### Queue + +```go +q := extypes.CreateQueue[string](2) + +_ = q.Enqueue("first") +_ = q.Enqueue("second") + +head, _ := q.Peak() +fmt.Println(head) // first + +item, _ := q.Dequeue() +fmt.Println(item) // first +``` + +## API Overview + +### `Slice[T]` + +Useful methods: + +- `Len`, `Cap`, `Get`, `First`, `Last` +- `Index`, `Equal` +- `Push`, `Pop`, `Remove`, `Swap` +- `Filter`, `ForEach`, `Map`, `Join` +- `ToArray`, `ToAnyArray`, `ToSet`, `ToTuple` + +### `Tuple[T]` + +Useful methods: + +- `Len`, `Cap`, `Get` +- `Equal` +- `Filter`, `ForEach`, `Join` +- `ToArray`, `ToAnyArray`, `ToSet`, `ToSlice` +- `MarshalJSON`, `UnmarshalJSON` + +### `Set[T]` + +Useful methods: + +- `Len`, `Cap`, `Get`, `First`, `Last` +- `Index`, `Equal` +- `Add`, `Pop`, `Remove`, `Swap` +- `Filter`, `ForEach`, `Map`, `Join` +- `ToArray`, `ToAnyArray`, `ToSlice`, `ToTuple` + +### `HashMap[K, V]` + +Useful methods: + +- `Len`, `Get`, `GetOrDefault`, `Contains` +- `Add`, `Remove` +- `Keys`, `Values`, `Items` +- `IndexKey`, `IndexValue` +- `Filter`, `ForEach`, `Map` + +### `Queue[T]` + +Useful methods: + +- `CreateQueue` +- `Len`, `Size`, `IsEmpty`, `IsFull` +- `Enqueue`, `Dequeue`, `Peak` +- `Raw` + +Returned errors: + +- `QueueFullErr` +- `QueueEmptyErr` + +## Running Tests + +```bash +go test ./... +``` + +## Godoc + +The package includes doc comments for the public API, so local documentation can be viewed with standard Go tooling: + +```bash +go doc +go doc git.nix13.pw/scuroneko/extypes +``` diff --git a/README_ru.md b/README_ru.md new file mode 100644 index 0000000..089169b --- /dev/null +++ b/README_ru.md @@ -0,0 +1,178 @@ +# extypes + +`extypes` - это небольшой Go-пакет с generic-коллекциями и вспомогательными методами поверх обычных `slice` и `map`. + +В пакет входят: + +- `Slice[T]`: методы для срезов, например `Push`, `Pop`, `Remove`, `Filter`, `Map` +- `Tuple[T]`: упорядоченная read-oriented коллекция с поддержкой JSON +- `Set[T]`: коллекция с уникальными значениями на базе среза +- `HashMap[K, V]`: generic-обертка над `map` +- `Queue[T]`: ограниченная по размеру FIFO-очередь с mutex-защитой + +## Требования + +- Go `1.25+` + +## Установка + +```bash +go get git.nix13.pw/scuroneko/extypes +``` + +## Импорт + +```go +import "git.nix13.pw/scuroneko/extypes" +``` + +## Особенности дизайна + +- Пакет остается близким к встроенным типам Go и не строит тяжелую абстракцию поверх них. +- Методы `Slice`, `Set` и `HashMap` часто возвращают receiver, чтобы было удобно писать chain-вызовы. +- Часть методов мутирует данные на месте: `Slice.Map`, `Slice.Swap`, `Set.Map`, `Set.Swap`, `HashMap.Add`, `HashMap.Remove`, `HashMap.Map`. +- `Tuple` ориентирован на чтение, но `NewTupleFrom` не копирует входной срез, а оборачивает его. +- `Set` сохраняет порядок первого появления элементов. Метод `Equal` чувствителен к порядку. +- `Set.Map` не восстанавливает уникальность, если функция отображения порождает дубликаты. +- Порядок обхода `HashMap` не определен, поэтому `Keys`, `Values`, `Items`, `IndexKey` и `IndexValue` не подходят там, где нужен стабильный порядок. +- Для поиска и сравнения в `Slice`, `Tuple`, `Set` и `HashMap.IndexValue` используется `reflect.DeepEqual`, поэтому элементы не обязаны быть `comparable`. + +## Быстрые примеры + +### Slice + +```go +nums := extypes.NewSliceFrom([]int{1, 2, 3}) + +nums = nums.Push(4) +nums = nums.Remove(2) +nums = nums.Map(func(v int) int { return v * 10 }) + +fmt.Println(nums.ToArray()) // [10 30 40] +``` + +### Tuple + +```go +tuple := extypes.NewTupleFrom([]string{"api", "worker", "cron"}) + +filtered := tuple.Filter(func(v string) bool { + return v != "worker" +}) + +fmt.Println(filtered.Join(", ")) // api, cron +``` + +### Set + +```go +set := extypes.NewSetFrom([]string{"go", "go", "docs", "tests"}) + +set = set.Add("bench") +set = set.Remove("docs") + +fmt.Println(set.ToArray()) // [go tests bench] +``` + +### HashMap + +```go +ports := extypes.NewMapFrom(map[string]int{ + "http": 80, + "https": 443, +}) + +ports.Add("ssh", 22) + +secure := ports.Filter(func(name string, port int) bool { + return port >= 100 || name == "https" +}) + +fmt.Println(secure.Contains("https")) // true +``` + +### Queue + +```go +q := extypes.CreateQueue[string](2) + +_ = q.Enqueue("first") +_ = q.Enqueue("second") + +head, _ := q.Peak() +fmt.Println(head) // first + +item, _ := q.Dequeue() +fmt.Println(item) // first +``` + +## Краткий обзор API + +### `Slice[T]` + +Полезные методы: + +- `Len`, `Cap`, `Get`, `First`, `Last` +- `Index`, `Equal` +- `Push`, `Pop`, `Remove`, `Swap` +- `Filter`, `ForEach`, `Map`, `Join` +- `ToArray`, `ToAnyArray`, `ToSet`, `ToTuple` + +### `Tuple[T]` + +Полезные методы: + +- `Len`, `Cap`, `Get` +- `Equal` +- `Filter`, `ForEach`, `Join` +- `ToArray`, `ToAnyArray`, `ToSet`, `ToSlice` +- `MarshalJSON`, `UnmarshalJSON` + +### `Set[T]` + +Полезные методы: + +- `Len`, `Cap`, `Get`, `First`, `Last` +- `Index`, `Equal` +- `Add`, `Pop`, `Remove`, `Swap` +- `Filter`, `ForEach`, `Map`, `Join` +- `ToArray`, `ToAnyArray`, `ToSlice`, `ToTuple` + +### `HashMap[K, V]` + +Полезные методы: + +- `Len`, `Get`, `GetOrDefault`, `Contains` +- `Add`, `Remove` +- `Keys`, `Values`, `Items` +- `IndexKey`, `IndexValue` +- `Filter`, `ForEach`, `Map` + +### `Queue[T]` + +Полезные методы: + +- `CreateQueue` +- `Len`, `Size`, `IsEmpty`, `IsFull` +- `Enqueue`, `Dequeue`, `Peak` +- `Raw` + +Возвращаемые ошибки: + +- `QueueFullErr` +- `QueueEmptyErr` + +## Запуск тестов + +```bash +go test ./... +``` + +## Godoc + +Для публичного API добавлены doc-комментарии, поэтому локальную документацию можно смотреть стандартными инструментами Go: + +```bash +go doc +go doc git.nix13.pw/scuroneko/extypes +``` diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..70097f9 --- /dev/null +++ b/doc.go @@ -0,0 +1,6 @@ +// Package extypes provides small generic collection types for Go: +// Slice, Tuple, Set, HashMap, and a fixed-size concurrent Queue. +// +// The package is intentionally lightweight and stays close to the +// semantics of the underlying Go built-in types. +package extypes diff --git a/map.go b/map.go index 6515ac0..c2aeddc 100644 --- a/map.go +++ b/map.go @@ -2,8 +2,10 @@ package extypes import "reflect" +// HashMap is a generic wrapper around Go's built-in map type. type HashMap[K comparable, V any] map[K]V +// NewMapFrom returns a shallow copy of m as a HashMap. func NewMapFrom[K comparable, V any](m map[K]V) HashMap[K, V] { out := make(HashMap[K, V]) for k, v := range m { @@ -11,16 +13,26 @@ func NewMapFrom[K comparable, V any](m map[K]V) HashMap[K, V] { } return out } + +// Len returns the number of entries in m. func (m HashMap[K, V]) Len() int { return len(m) } + +// Add stores val under key and returns m. func (m HashMap[K, V]) Add(key K, val V) HashMap[K, V] { m[key] = val return m } + +// Remove deletes key and returns m. func (m HashMap[K, V]) Remove(key K) HashMap[K, V] { delete(m, key) return m } + +// Get returns the value stored under key. func (m HashMap[K, V]) Get(key K) V { return m[key] } + +// GetOrDefault returns the value stored under key or def if the key is absent. func (m HashMap[K, V]) GetOrDefault(key K, def V) V { v, ok := m[key] if !ok { @@ -28,10 +40,15 @@ func (m HashMap[K, V]) GetOrDefault(key K, def V) V { } return v } + +// Contains reports whether key is present in m. func (m HashMap[K, V]) Contains(key K) bool { _, ok := m[key] return ok } + +// IndexKey returns the iteration index of key or -1 when absent. +// The result depends on Go map iteration order and is therefore not stable. func (m HashMap[K, V]) IndexKey(key K) int { index := 0 for k, _ := range m { @@ -42,6 +59,10 @@ func (m HashMap[K, V]) IndexKey(key K) int { } return -1 } + +// IndexValue returns the iteration index of value or -1 when absent. +// The result depends on Go map iteration order and comparison uses +// reflect.DeepEqual. func (m HashMap[K, V]) IndexValue(value V) int { index := 0 for _, v := range m { @@ -52,6 +73,8 @@ func (m HashMap[K, V]) IndexValue(value V) int { } return -1 } + +// Keys returns the current keys in unspecified order. func (m HashMap[K, V]) Keys() []K { keys := make([]K, 0, m.Len()) for k := range m { @@ -59,6 +82,8 @@ func (m HashMap[K, V]) Keys() []K { } return keys } + +// Values returns the current values in unspecified order. func (m HashMap[K, V]) Values() []V { values := make([]V, 0, m.Len()) for _, v := range m { @@ -66,6 +91,8 @@ func (m HashMap[K, V]) Values() []V { } return values } + +// Items returns keys and values collected in the same iteration order. func (m HashMap[K, V]) Items() ([]K, []V) { keys := make(Slice[K], 0, m.Len()) values := make(Slice[V], 0, m.Len()) @@ -76,6 +103,7 @@ func (m HashMap[K, V]) Items() ([]K, []V) { return keys, values } +// Filter returns a new HashMap containing only entries accepted by f. func (m HashMap[K, V]) Filter(f func(K, V) bool) HashMap[K, V] { out := make(HashMap[K, V]) for k, v := range m { @@ -86,12 +114,16 @@ func (m HashMap[K, V]) Filter(f func(K, V) bool) HashMap[K, V] { } return out } + +// ForEach calls f for every entry and returns m. func (m HashMap[K, V]) ForEach(f func(K, V)) HashMap[K, V] { for k, v := range m { f(k, v) } return m } + +// Map replaces every value with the result of f and returns m. func (m HashMap[K, V]) Map(f func(K, V) V) HashMap[K, V] { for k, v := range m { m[k] = f(k, v) diff --git a/queue.go b/queue.go index 94d1719..be8adcb 100644 --- a/queue.go +++ b/queue.go @@ -5,28 +5,41 @@ import ( "sync" ) +// QueueFullErr is returned when Enqueue is called on a full queue. var QueueFullErr = errors.New("queue full") + +// QueueEmptyErr is returned when Peak or Dequeue is called on an empty queue. var QueueEmptyErr = errors.New("queue empty") +// Queue is a fixed-size FIFO queue protected by a mutex. type Queue[T any] struct { size uint64 mu sync.RWMutex queue Slice[T] } +// CreateQueue creates a queue with the provided maximum size. func CreateQueue[T any](size uint64) *Queue[T] { return &Queue[T]{queue: make(Slice[T], 0, size), size: size} } +// Len returns the current number of queued elements. func (q *Queue[T]) Len() uint64 { q.mu.RLock() defer q.mu.RUnlock() return uint64(q.queue.Len()) } -func (q *Queue[T]) IsEmpty() bool { return q.Len() == 0 } -func (q *Queue[T]) IsFull() bool { return q.Len() == q.size } -func (q *Queue[T]) Size() uint64 { return q.size } +// IsEmpty reports whether the queue currently holds no elements. +func (q *Queue[T]) IsEmpty() bool { return q.Len() == 0 } + +// IsFull reports whether the queue has reached its configured size. +func (q *Queue[T]) IsFull() bool { return q.Len() == q.size } + +// Size returns the configured queue capacity. +func (q *Queue[T]) Size() uint64 { return q.size } + +// Enqueue appends el to the queue or returns QueueFullErr. func (q *Queue[T]) Enqueue(el T) error { q.mu.Lock() defer q.mu.Unlock() @@ -38,6 +51,7 @@ func (q *Queue[T]) Enqueue(el T) error { return nil } +// Dequeue removes and returns the head element or QueueEmptyErr. func (q *Queue[T]) Dequeue() (T, error) { q.mu.Lock() defer q.mu.Unlock() @@ -55,6 +69,8 @@ func (q *Queue[T]) Dequeue() (T, error) { return el, nil } +// Peak returns the head element without removing it or QueueEmptyErr when the +// queue is empty. func (q *Queue[T]) Peak() (T, error) { q.mu.RLock() defer q.mu.RUnlock() @@ -67,6 +83,7 @@ func (q *Queue[T]) Peak() (T, error) { return q.queue[0], nil } +// Raw returns a copy of the queue contents from head to tail. func (q *Queue[T]) Raw() Slice[T] { q.mu.RLock() defer q.mu.RUnlock() diff --git a/set.go b/set.go index 1b68d58..5f69050 100644 --- a/set.go +++ b/set.go @@ -6,8 +6,13 @@ import ( "strings" ) +// Set is a uniqueness-enforcing collection backed by a slice. +// +// Order is preserved based on first appearance. Equality is order-sensitive +// because the underlying representation is a slice. type Set[T any] []T +// NewSetFrom returns a new Set with duplicate values removed. func NewSetFrom[T any](slice []T) Set[T] { s := make(Set[T], 0) for _, v := range slice { @@ -18,12 +23,24 @@ func NewSetFrom[T any](slice []T) Set[T] { } return s } -func (s Set[T]) Len() int { return len(s) } -func (s Set[T]) Cap() int { return cap(s) } -func (s Set[T]) Get(index int) T { return s[index] } -func (s Set[T]) Last() T { return s.Get(s.Len() - 1) } -func (s Set[T]) First() T { return s.Get(0) } +// Len returns the number of elements in s. +func (s Set[T]) Len() int { return len(s) } + +// Cap returns the capacity of s. +func (s Set[T]) Cap() int { return cap(s) } + +// Get returns the element at index. +func (s Set[T]) Get(index int) T { return s[index] } + +// Last returns the last element. +func (s Set[T]) Last() T { return s.Get(s.Len() - 1) } + +// First returns the first element. +func (s Set[T]) First() T { return s.Get(0) } + +// Index returns the first index of el or -1 when el is not present. +// Comparison uses reflect.DeepEqual. func (s Set[T]) Index(el T) int { for i := range s { if reflect.DeepEqual(s[i], el) { @@ -32,9 +49,13 @@ func (s Set[T]) Index(el T) int { } return -1 } + +// Swap swaps two elements in place. func (s Set[T]) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// Add appends v if it is not already present. func (s Set[T]) Add(v T) Set[T] { index := s.Index(v) if index >= 0 { @@ -42,6 +63,8 @@ func (s Set[T]) Add(v T) Set[T] { } return append(s, v) } + +// Pop removes the element at index and returns the resulting set. func (s Set[T]) Pop(index int) Set[T] { if index == 0 { return s[1:] @@ -51,6 +74,8 @@ func (s Set[T]) Pop(index int) Set[T] { copy(out[index:], s[index+1:]) return out } + +// Remove removes the first matching element and returns the resulting set. func (s Set[T]) Remove(el T) Set[T] { index := s.Index(el) if index == -1 { @@ -58,6 +83,9 @@ func (s Set[T]) Remove(el T) Set[T] { } return s.Pop(index) } + +// Equal reports whether both sets have the same length and pairwise equal +// elements in the same order according to reflect.DeepEqual. func (s Set[T]) Equal(s2 Set[T]) bool { if s.Len() != s2.Len() { return false @@ -70,13 +98,20 @@ func (s Set[T]) Equal(s2 Set[T]) bool { return true } +// ToSlice returns a copy of s as a Slice. func (s Set[T]) ToSlice() Slice[T] { return NewSliceFrom(s) } + +// ToTuple returns a Tuple view of s without copying. func (s Set[T]) ToTuple() Tuple[T] { return NewTupleFrom(s) } + +// ToArray returns a copy of s as a built-in slice. func (s Set[T]) ToArray() []T { out := make([]T, len(s)) copy(out, s) return out } + +// ToAnyArray returns a copy of s converted to []any. func (s Set[T]) ToAnyArray() []any { out := make([]any, len(s)) for i, v := range s { @@ -84,6 +119,9 @@ func (s Set[T]) ToAnyArray() []any { } return out } + +// Filter returns a new Set containing only elements accepted by f. +// The result preserves order and therefore remains duplicate-free. func (s Set[T]) Filter(f func(e T) bool) Set[T] { out := make(Set[T], 0) for _, v := range s { @@ -93,6 +131,8 @@ func (s Set[T]) Filter(f func(e T) bool) Set[T] { } return out } + +// ForEach calls f for every element and returns s. func (s Set[T]) ForEach(f func(int, T)) Set[T] { for i, v := range s { f(i, v) @@ -100,7 +140,8 @@ func (s Set[T]) ForEach(f func(int, T)) Set[T] { return s } -// Map is mutable func +// Map applies f to each element in place and returns s. +// It does not remove duplicates that may be produced by f. func (s Set[T]) Map(f func(e T) T) Set[T] { for i, v := range s { s[i] = f(v) @@ -108,6 +149,8 @@ func (s Set[T]) Map(f func(e T) T) Set[T] { return s } +// Join formats every element with fmt.Sprintf("%v", value) and joins them +// using sep. func (s Set[T]) Join(sep string) string { st := make([]string, len(s)) for i, v := range s { diff --git a/slice.go b/slice.go index f2867c2..a1ce58c 100644 --- a/slice.go +++ b/slice.go @@ -6,19 +6,33 @@ import ( "strings" ) +// Slice is a generic slice wrapper with convenience methods. type Slice[T any] []T +// NewSliceFrom returns a copy of slice as a Slice. func NewSliceFrom[T any](slice []T) Slice[T] { s := make(Slice[T], len(slice)) copy(s, slice) return s } -func (s Slice[T]) Len() int { return len(s) } -func (s Slice[T]) Cap() int { return cap(s) } -func (s Slice[T]) Get(index int) T { return s[index] } -func (s Slice[T]) Last() T { return s.Get(s.Len() - 1) } -func (s Slice[T]) First() T { return s.Get(0) } +// Len returns the number of elements in s. +func (s Slice[T]) Len() int { return len(s) } + +// Cap returns the capacity of s. +func (s Slice[T]) Cap() int { return cap(s) } + +// Get returns the element at index. +func (s Slice[T]) Get(index int) T { return s[index] } + +// Last returns the last element. +func (s Slice[T]) Last() T { return s.Get(s.Len() - 1) } + +// First returns the first element. +func (s Slice[T]) First() T { return s.Get(0) } + +// Index returns the first index of el or -1 when el is not present. +// Comparison uses reflect.DeepEqual. func (s Slice[T]) Index(el T) int { for i := range s { if reflect.DeepEqual(s[i], el) { @@ -28,7 +42,10 @@ func (s Slice[T]) Index(el T) int { return -1 } +// Push appends e and returns the resulting slice. func (s Slice[T]) Push(e T) Slice[T] { return append(s, e) } + +// Pop removes the element at index and returns the resulting slice. func (s Slice[T]) Pop(index int) Slice[T] { if index == 0 { return s[1:] @@ -38,6 +55,8 @@ func (s Slice[T]) Pop(index int) Slice[T] { copy(out[index:], s[index+1:]) return out } + +// Remove removes the first matching element and returns the resulting slice. func (s Slice[T]) Remove(el T) Slice[T] { index := s.Index(el) if index == -1 { @@ -46,12 +65,14 @@ func (s Slice[T]) Remove(el T) Slice[T] { return s.Pop(index) } -// Swap is mutable func +// Swap swaps two elements in place and returns s. func (s Slice[T]) Swap(i, j int) Slice[T] { s[i], s[j] = s[j], s[i] return s } +// Equal reports whether both slices have the same length and pairwise equal +// elements according to reflect.DeepEqual. func (s Slice[T]) Equal(s2 Slice[T]) bool { if s.Len() != s2.Len() { return false @@ -64,11 +85,14 @@ func (s Slice[T]) Equal(s2 Slice[T]) bool { return true } +// ToArray returns a copy of s as a built-in slice. func (s Slice[T]) ToArray() []T { out := make([]T, len(s)) copy(out, s) return out } + +// ToAnyArray returns a copy of s converted to []any. func (s Slice[T]) ToAnyArray() []any { out := make([]any, len(s)) for i, v := range s { @@ -76,9 +100,14 @@ func (s Slice[T]) ToAnyArray() []any { } return out } -func (s Slice[T]) ToSet() Set[T] { return NewSetFrom(s) } + +// ToSet returns a Set built from the slice values. +func (s Slice[T]) ToSet() Set[T] { return NewSetFrom(s) } + +// ToTuple returns a Tuple view of s without copying. func (s Slice[T]) ToTuple() Tuple[T] { return NewTupleFrom(s) } +// Filter returns a new Slice containing only elements accepted by f. func (s Slice[T]) Filter(f func(e T) bool) Slice[T] { out := make(Slice[T], 0) for _, v := range s { @@ -88,6 +117,8 @@ func (s Slice[T]) Filter(f func(e T) bool) Slice[T] { } return out } + +// ForEach calls f for every element and returns s. func (s Slice[T]) ForEach(f func(int, T)) Slice[T] { for i, v := range s { f(i, v) @@ -95,7 +126,7 @@ func (s Slice[T]) ForEach(f func(int, T)) Slice[T] { return s } -// Map is mutable func +// Map applies f to each element in place and returns s. func (s Slice[T]) Map(f func(e T) T) Slice[T] { for i, v := range s { s[i] = f(v) @@ -103,6 +134,8 @@ func (s Slice[T]) Map(f func(e T) T) Slice[T] { return s } +// Join formats every element with fmt.Sprintf("%v", value) and joins them +// using sep. func (s Slice[T]) Join(sep string) string { st := make([]string, len(s)) for i, v := range s { diff --git a/tuple.go b/tuple.go index cd6386f..70a33da 100644 --- a/tuple.go +++ b/tuple.go @@ -7,16 +7,31 @@ import ( "strings" ) +// Tuple is a read-oriented wrapper around Slice. +// +// It exposes indexed access and conversion helpers and is useful when an API +// should return an ordered collection without slice-specific mutation helpers. type Tuple[T any] struct { s Slice[T] } +// NewTupleFrom wraps src as a Tuple without copying it. func NewTupleFrom[T any](src []T) Tuple[T] { return Tuple[T]{src} } -func NewTuple[T any]() Tuple[T] { return Tuple[T]{make(Slice[T], 0)} } -func (t Tuple[T]) Len() int { return t.s.Len() } -func (t Tuple[T]) Cap() int { return t.s.Cap() } -func (t Tuple[T]) Get(index int) T { return t.s.Get(index) } +// NewTuple returns an empty Tuple. +func NewTuple[T any]() Tuple[T] { return Tuple[T]{make(Slice[T], 0)} } + +// Len returns the number of elements in the tuple. +func (t Tuple[T]) Len() int { return t.s.Len() } + +// Cap returns the capacity of the underlying slice. +func (t Tuple[T]) Cap() int { return t.s.Cap() } + +// Get returns the element at index. +func (t Tuple[T]) Get(index int) T { return t.s.Get(index) } + +// Equal reports whether both tuples have the same length and pairwise equal +// elements according to reflect.DeepEqual. func (t Tuple[T]) Equal(t2 Tuple[T]) bool { if t.Len() != t2.Len() { return false @@ -29,11 +44,14 @@ func (t Tuple[T]) Equal(t2 Tuple[T]) bool { return true } +// ToArray returns a copy of the tuple as a built-in slice. func (t Tuple[T]) ToArray() []T { out := make([]T, t.Len()) copy(out, t.s) return out } + +// ToAnyArray returns a copy of the tuple converted to []any. func (t Tuple[T]) ToAnyArray() []any { out := make([]any, t.Len()) for i, v := range t.s { @@ -41,9 +59,14 @@ func (t Tuple[T]) ToAnyArray() []any { } return out } -func (t Tuple[T]) ToSet() Set[T] { return NewSetFrom(t.s) } + +// ToSet returns a Set built from the tuple values. +func (t Tuple[T]) ToSet() Set[T] { return NewSetFrom(t.s) } + +// ToSlice returns a copy of the tuple as a Slice. func (t Tuple[T]) ToSlice() Slice[T] { return NewSliceFrom(t.s) } +// Filter returns a new tuple containing only elements accepted by f. func (t Tuple[T]) Filter(f func(e T) bool) Tuple[T] { out := make(Slice[T], 0) for _, v := range t.s { @@ -54,6 +77,8 @@ func (t Tuple[T]) Filter(f func(e T) bool) Tuple[T] { t.s = out return t } + +// ForEach calls f for every element and returns the original tuple. func (t Tuple[T]) ForEach(f func(int, T)) Tuple[T] { for i, v := range t.s { f(i, v) @@ -61,6 +86,8 @@ func (t Tuple[T]) ForEach(f func(int, T)) Tuple[T] { return t } +// Join formats every element with fmt.Sprintf("%v", value) and joins them +// using sep. func (t Tuple[T]) Join(sep string) string { st := make([]string, len(t.s)) for i, v := range t.s { @@ -69,6 +96,11 @@ func (t Tuple[T]) Join(sep string) string { return strings.Join(st, sep) } -func (t Tuple[T]) String() string { return fmt.Sprint(t.s) } +// String returns the tuple formatted like its underlying slice. +func (t Tuple[T]) String() string { return fmt.Sprint(t.s) } + +// UnmarshalJSON decodes the tuple from a JSON array. func (t *Tuple[T]) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &t.s) } -func (t Tuple[T]) MarshalJSON() ([]byte, error) { return json.Marshal(t.s) } + +// MarshalJSON encodes the tuple as a JSON array. +func (t Tuple[T]) MarshalJSON() ([]byte, error) { return json.Marshal(t.s) }