// 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 }