- Add TryMap, Filter, Reduce, KeyBy, GroupBy, Uniq, UniqBy, Partition and Find, plus the Set type built with SetOf (Has, Add, Slice). - Add unit tests at 100% statement coverage and a runnable example for every function and Set method. - Complete the module README: contracts, use cases, Pluck rationale, standard-library pointers and the full API reference. - docs/architecture/introduction.md: add a Utilities row for sieve. - docs/setup/coming-from-wintercms.md: map Laravel collection methods to sieve and narrow the not-provided row to the fluent collection object.
153 lines
4.9 KiB
Go
153 lines
4.9 KiB
Go
// Package sieve provides generic, dependency-free collection helpers that the
|
|
// standard library slices and maps packages lack.
|
|
//
|
|
// Every function is eager and never modifies its input. Results never share a
|
|
// backing array with the input, so changing or appending to a result leaves the
|
|
// caller's slice unchanged. Callbacks run once per element, in input order.
|
|
// Nil or empty input yields a non-nil empty slice or map, so encoding/json
|
|
// writes [] or {} rather than null.
|
|
package sieve
|
|
|
|
// Map returns a new slice holding f applied to each element of s, in order.
|
|
// f is called once per element. A nil or empty s yields a non-nil empty slice.
|
|
//
|
|
// Map is also the pluck operation: pass an accessor func to extract one field
|
|
// from every element, for example the titles of a slice of posts.
|
|
func Map[S ~[]T, T, U any](s S, f func(T) U) []U {
|
|
out := make([]U, 0, len(s))
|
|
for _, v := range s {
|
|
out = append(out, f(v))
|
|
}
|
|
return out
|
|
}
|
|
|
|
// TryMap returns a new slice holding f applied to each element of s, in order.
|
|
// It stops at the first error and returns nil together with that error,
|
|
// unchanged; f is not called for the remaining elements. A caller that needs
|
|
// the failing index wraps the error inside f. A nil or empty s yields a
|
|
// non-nil empty slice and a nil error.
|
|
func TryMap[S ~[]T, T, U any](s S, f func(T) (U, error)) ([]U, error) {
|
|
out := make([]U, 0, len(s))
|
|
for _, v := range s {
|
|
u, err := f(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, u)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Filter returns a new slice holding the elements of s for which keep returns
|
|
// true, in input order. The result has the caller's slice type. A nil or empty
|
|
// s, or one with no matches, yields a non-nil empty slice.
|
|
func Filter[S ~[]T, T any](s S, keep func(T) bool) S {
|
|
out := make(S, 0)
|
|
for _, v := range s {
|
|
if keep(v) {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Reduce folds s from left to right: it starts with init and replaces the
|
|
// accumulator with f(accumulator, element) for each element in order. The
|
|
// accumulator type may differ from the element type. A nil or empty s returns
|
|
// init unchanged.
|
|
func Reduce[S ~[]T, T, A any](s S, init A, f func(A, T) A) A {
|
|
acc := init
|
|
for _, v := range s {
|
|
acc = f(acc, v)
|
|
}
|
|
return acc
|
|
}
|
|
|
|
// KeyBy returns a new map from key(element) to element. When two elements
|
|
// share a key, the later one wins. key is called once per element, in order.
|
|
// A nil or empty s yields a non-nil empty map.
|
|
func KeyBy[S ~[]T, T any, K comparable](s S, key func(T) K) map[K]T {
|
|
out := make(map[K]T, len(s))
|
|
for _, v := range s {
|
|
out[key(v)] = v
|
|
}
|
|
return out
|
|
}
|
|
|
|
// GroupBy returns a new map from key(element) to the elements with that key.
|
|
// Each group keeps input order and has the caller's slice type; groups are
|
|
// independent of the input and of each other. key is called once per element,
|
|
// in order. A nil or empty s yields a non-nil empty map.
|
|
func GroupBy[S ~[]T, T any, K comparable](s S, key func(T) K) map[K]S {
|
|
out := make(map[K]S)
|
|
for _, v := range s {
|
|
k := key(v)
|
|
out[k] = append(out[k], v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Uniq returns a new slice holding the elements of s without duplicates,
|
|
// keeping the first occurrence of each, in input order. The result has the
|
|
// caller's slice type. A nil or empty s yields a non-nil empty slice.
|
|
func Uniq[S ~[]T, T comparable](s S) S {
|
|
out := make(S, 0, len(s))
|
|
seen := make(map[T]struct{}, len(s))
|
|
for _, v := range s {
|
|
if _, ok := seen[v]; ok {
|
|
continue
|
|
}
|
|
seen[v] = struct{}{}
|
|
out = append(out, v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// UniqBy returns a new slice holding the elements of s whose key(element) has
|
|
// not been seen before, keeping the first occurrence of each key, in input
|
|
// order. key is called once per element. The result has the caller's slice
|
|
// type. A nil or empty s yields a non-nil empty slice.
|
|
func UniqBy[S ~[]T, T any, K comparable](s S, key func(T) K) S {
|
|
out := make(S, 0, len(s))
|
|
seen := make(map[K]struct{}, len(s))
|
|
for _, v := range s {
|
|
k := key(v)
|
|
if _, ok := seen[k]; ok {
|
|
continue
|
|
}
|
|
seen[k] = struct{}{}
|
|
out = append(out, v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Partition splits s into two new slices: matched holds the elements for which
|
|
// match returns true and rest holds the others, both in input order and both
|
|
// with the caller's slice type. match is called once per element. Both results
|
|
// are non-nil, even when empty, and for a nil or empty s.
|
|
func Partition[S ~[]T, T any](s S, match func(T) bool) (matched, rest S) {
|
|
matched = make(S, 0)
|
|
rest = make(S, 0)
|
|
for _, v := range s {
|
|
if match(v) {
|
|
matched = append(matched, v)
|
|
} else {
|
|
rest = append(rest, v)
|
|
}
|
|
}
|
|
return matched, rest
|
|
}
|
|
|
|
// Find returns the first element of s for which match returns true, and true.
|
|
// match is not called after the first match. When nothing matches, or s is nil
|
|
// or empty, Find returns the zero value of T and false.
|
|
func Find[S ~[]T, T any](s S, match func(T) bool) (T, bool) {
|
|
for _, v := range s {
|
|
if match(v) {
|
|
return v, true
|
|
}
|
|
}
|
|
var zero T
|
|
return zero, false
|
|
}
|