Documentation
¶
Index ¶
- func CountBy[T any, K comparable](c *Collection[T], fn func(T) K) map[K]int
- func CountByValue[T comparable](c *Collection[T]) map[T]int
- func Dump(vs ...any)
- func GroupBy[T any, K comparable](c *Collection[T], keyFn func(T) K) map[K]*Collection[T]
- func GroupBySlice[T any, K comparable](c *Collection[T], keyFn func(T) K) map[K][]T
- func MaxBy[T any, K Number | ~string](c *Collection[T], keyFn func(T) K) (T, bool)
- func MinBy[T any, K Number | ~string](c *Collection[T], keyFn func(T) K) (T, bool)
- func ToMap[T any, K comparable, V any](c *Collection[T], keyFn func(T) K, valueFn func(T) V) map[K]V
- func ToMapKV[K comparable, V any](c *Collection[Pair[K, V]]) map[K]V
- type Collection
- func Difference[T comparable](a, b *Collection[T]) *Collection[T]
- func FromMap[K comparable, V any](m map[K]V) *Collection[Pair[K, V]]
- func Intersect[T comparable](a, b *Collection[T]) *Collection[T]
- func MapTo[T any, R any](c *Collection[T], fn func(T) R) *Collection[R]
- func New[T any](items []T) *Collection[T]
- func Pluck[T any, R any](c *Collection[T], fn func(T) R) *Collection[R]
- func SymmetricDifference[T comparable](a, b *Collection[T]) *Collection[T]
- func TakeUntil[T comparable](c *Collection[T], value T) *Collection[T]
- func Times[T any](count int, fn func(int) T) *Collection[T]
- func Union[T comparable](a, b *Collection[T]) *Collection[T]
- func UniqueBy[T any, K comparable](c *Collection[T], keyFn func(T) K) *Collection[T]
- func UniqueComparable[T comparable](c *Collection[T]) *Collection[T]
- func Window[T any](c *Collection[T], size int, step int) *Collection[[]T]
- func Zip[A any, B any](a *Collection[A], b *Collection[B]) *Collection[Tuple[A, B]]
- func ZipWith[A any, B any, R any](a *Collection[A], b *Collection[B], fn func(A, B) R) *Collection[R]
- func (c *Collection[T]) After(pred func(T) bool) *Collection[T]
- func (c *Collection[T]) All(fn func(T) bool) bool
- func (c *Collection[T]) Any(fn func(T) bool) bool
- func (c *Collection[T]) Append(values ...T) *Collection[T]
- func (c *Collection[T]) At(i int) (T, bool)
- func (c *Collection[T]) Before(pred func(T) bool) *Collection[T]
- func (c *Collection[T]) Chunk(size int) [][]T
- func (c *Collection[T]) Clone() *Collection[T]
- func (c *Collection[T]) Concat(values []T) *Collection[T]
- func (c *Collection[T]) Contains(pred func(T) bool) bool
- func (c *Collection[T]) Count() int
- func (c *Collection[T]) Dd()
- func (c *Collection[T]) Dump() *Collection[T]
- func (c *Collection[T]) DumpStr() string
- func (c *Collection[T]) Each(fn func(T)) *Collection[T]
- func (c *Collection[T]) Filter(fn func(T) bool) *Collection[T]
- func (c *Collection[T]) FindWhere(fn func(T) bool) (T, bool)
- func (c *Collection[T]) First() (value T, ok bool)
- func (c *Collection[T]) FirstWhere(fn func(T) bool) (value T, ok bool)
- func (c *Collection[T]) IndexWhere(fn func(T) bool) (int, bool)
- func (c *Collection[T]) IsEmpty() bool
- func (c *Collection[T]) Items() []T
- func (c *Collection[T]) Last() (value T, ok bool)
- func (c *Collection[T]) LastWhere(fn func(T, int) bool) (value T, ok bool)
- func (c *Collection[T]) Map(fn func(T) T) *Collection[T]
- func (c *Collection[T]) Merge(other any) *Collection[T]
- func (c *Collection[T]) Multiply(n int) *Collection[T]
- func (c *Collection[T]) None(fn func(T) bool) bool
- func (c *Collection[T]) Partition(fn func(T) bool) (*Collection[T], *Collection[T])
- func (c *Collection[T]) Pipe(fn func(*Collection[T]) any) any
- func (c *Collection[T]) Pop() (T, *Collection[T])
- func (c *Collection[T]) PopN(n int) (*Collection[T], *Collection[T])
- func (c *Collection[T]) Prepend(values ...T) *Collection[T]
- func (c *Collection[T]) Push(values ...T) *Collection[T]
- func (c *Collection[T]) Reduce(initial T, fn func(T, T) T) T
- func (c *Collection[T]) Reverse() *Collection[T]
- func (c *Collection[T]) Shuffle() *Collection[T]
- func (c *Collection[T]) Skip(n int) *Collection[T]
- func (c *Collection[T]) SkipLast(n int) *Collection[T]
- func (c *Collection[T]) Sort(less func(a, b T) bool) *Collection[T]
- func (c *Collection[T]) Take(n int) *Collection[T]
- func (c *Collection[T]) TakeLast(n int) *Collection[T]
- func (c *Collection[T]) TakeUntilFn(pred func(T) bool) *Collection[T]
- func (c *Collection[T]) Tap(fn func(*Collection[T])) *Collection[T]
- func (c *Collection[T]) ToJSON() (string, error)
- func (c *Collection[T]) ToPrettyJSON() (string, error)
- func (c *Collection[T]) Transform(fn func(T) T)
- func (c *Collection[T]) Unique(eq func(a, b T) bool) *Collection[T]
- func (c *Collection[T]) Where(fn func(T) bool) *Collection[T]
- type Number
- type NumericCollection
- type Pair
- type Tuple
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CountBy ¶
func CountBy[T any, K comparable](c *Collection[T], fn func(T) K) map[K]int
CountBy returns a map of keys extracted by fn to their occurrence counts. K must be comparable. @group Aggregation @behavior readonly @fluent false
Example: integers
c := collection.New([]int{1, 2, 2, 3, 3, 3})
counts := collection.CountBy(c, func(v int) int {
return v
})
collection.Dump(counts)
// map[int]int {
// 1: 1 #int
// 2: 2 #int
// 3: 3 #int
// }
Example: strings
c2 := collection.New([]string{"apple", "banana", "apple", "cherry", "banana"})
counts2 := collection.CountBy(c2, func(v string) string {
return v
})
collection.Dump(counts2)
// map[string]int {
// "apple": 2 #int
// "banana": 2 #int
// "cherry": 1 #int
// }
Example: structs
type User struct {
Name string
Role string
}
users := collection.New([]User{
{Name: "Alice", Role: "admin"},
{Name: "Bob", Role: "user"},
{Name: "Carol", Role: "admin"},
{Name: "Dave", Role: "user"},
{Name: "Eve", Role: "admin"},
})
roleCounts := collection.CountBy(users, func(u User) string {
return u.Role
})
collection.Dump(roleCounts)
// map[string]int {
// "admin": 3 #int
// "user": 2 #int
// }
func CountByValue ¶
func CountByValue[T comparable](c *Collection[T]) map[T]int
CountByValue returns a map where each distinct item in the collection is mapped to the number of times it appears. @group Aggregation @behavior readonly @fluent false
T must be comparable.
Example: strings
c1 := collection.New([]string{"a", "b", "a"})
counts1 := collection.CountByValue(c1)
collection.Dump(counts1)
// #map[string]int [
// "a" => 2 #int
// "b" => 1 #int
// ]
Example: integers
c2 := collection.New([]int{1, 2, 2, 3, 3, 3})
counts2 := collection.CountByValue(c2)
collection.Dump(counts2)
// #map[int]int [
// 1 => 1 #int
// 2 => 2 #int
// 3 => 3 #int
// ]
Example: structs (comparable)
type Point struct {
X int
Y int
}
c3 := collection.New([]Point{
{X: 1, Y: 1},
{X: 2, Y: 2},
{X: 1, Y: 1},
})
counts3 := collection.CountByValue(c3)
collection.Dump(counts3)
// #map[collection.Point]int [
// {X:1 Y:1} => 2 #int
// {X:2 Y:2} => 1 #int
// ]
func Dump ¶
func Dump(vs ...any)
Dump is a convenience function that calls godump.Dump. @group Debugging @fluent false
Example: integers
c2 := collection.New([]int{1, 2, 3})
collection.Dump(c2.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// ]
func GroupBy ¶
func GroupBy[T any, K comparable]( c *Collection[T], keyFn func(T) K, ) map[K]*Collection[T]
GroupBy partitions the collection into groups keyed by the value returned from keyFn. @group Grouping @behavior readonly @fluent false
The order of items within each group is preserved. The order of the groups themselves is unspecified.
This function does not mutate the source collection.
Example: grouping integers by parity
values := []int{1, 2, 3, 4, 5}
groups := collection.GroupBy(
collection.New(values),
func(v int) string {
if v%2 == 0 {
return "even"
}
return "odd"
},
)
collection.Dump(groups["even"].Items())
// []int [
// 0 => 2 #int
// 1 => 4 #int
// ]
collection.Dump(groups["odd"].Items())
// []int [
// 0 => 1 #int
// 1 => 3 #int
// 2 => 5 #int
// ]
Example: grouping structs by field
type User struct {
ID int
Role string
}
users := []User{
{ID: 1, Role: "admin"},
{ID: 2, Role: "user"},
{ID: 3, Role: "admin"},
}
groups2 := collection.GroupBy(
collection.New(users),
func(u User) string { return u.Role },
)
collection.Dump(groups2["admin"].Items())
// []main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Role => "admin" #string
// }
// 1 => #main.User {
// +ID => 3 #int
// +Role => "admin" #string
// }
// ]
collection.Dump(groups2["user"].Items())
// []main.User [
// 0 => #main.User {
// +ID => 2 #int
// +Role => "user" #string
// }
// ]
func GroupBySlice ¶ added in v1.1.0
func GroupBySlice[T any, K comparable]( c *Collection[T], keyFn func(T) K, ) map[K][]T
GroupBySlice partitions the collection into groups keyed by the value returned from keyFn. @group Grouping @behavior readonly @fluent false
The order of items within each group is preserved. The order of the groups themselves is unspecified.
This function does not mutate the source collection.
Example: grouping integers by parity
values := []int{1, 2, 3, 4, 5}
groups := collection.GroupBySlice(
collection.New(values),
func(v int) string {
if v%2 == 0 {
return "even"
}
return "odd"
},
)
collection.Dump(groups["even"])
// []int [
// 0 => 2 #int
// 1 => 4 #int
// ]
collection.Dump(groups["odd"])
// []int [
// 0 => 1 #int
// 1 => 3 #int
// 2 => 5 #int
// ]
Example: grouping structs by field
type User struct {
ID int
Role string
}
users := []User{
{ID: 1, Role: "admin"},
{ID: 2, Role: "user"},
{ID: 3, Role: "admin"},
}
groups2 := collection.GroupBySlice(
collection.New(users),
func(u User) string { return u.Role },
)
collection.Dump(groups2["admin"])
// []main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Role => "admin" #string
// }
// 1 => #main.User {
// +ID => 3 #int
// +Role => "admin" #string
// }
// ]
collection.Dump(groups2["user"])
// []main.User [
// 0 => #main.User {
// +ID => 2 #int
// +Role => "user" #string
// }
// ]
func MaxBy ¶
func MaxBy[T any, K Number | ~string](c *Collection[T], keyFn func(T) K) (T, bool)
MaxBy returns the item whose key (produced by keyFn) is the largest. The second return value is false if the collection is empty. @group Aggregation @behavior readonly @fluent false
This cannot be a method because methods can't introduce a new type parameter K. When multiple items share the same maximal key, the first such item is returned.
Example: structs - highest score
type Player struct {
Name string
Score int
}
players := collection.New([]Player{
{Name: "Alice", Score: 10},
{Name: "Bob", Score: 25},
{Name: "Carol", Score: 18},
})
top, ok := collection.MaxBy(players, func(p Player) int {
return p.Score
})
collection.Dump(top, ok)
// #main.Player {
// +Name => "Bob" #string
// +Score => 25 #int
// }
// true #bool
Example: strings - longest length
words := collection.New([]string{"go", "collection", "rocks"})
longest, ok := collection.MaxBy(words, func(s string) int {
return len(s)
})
collection.Dump(longest, ok)
// "collection" #string
// true #bool
Example: empty collection
empty := collection.New([]int{})
maxVal, ok := collection.MaxBy(empty, func(v int) int { return v })
collection.Dump(maxVal, ok)
// 0 #int
// false #bool
func MinBy ¶
func MinBy[T any, K Number | ~string](c *Collection[T], keyFn func(T) K) (T, bool)
MinBy returns the item whose key (produced by keyFn) is the smallest. The second return value is false if the collection is empty. @group Aggregation @behavior readonly @fluent false
This cannot be a method because methods can't introduce a new type parameter K. When multiple items share the same minimal key, the first such item is returned.
Example: structs - smallest age
type User struct {
Name string
Age int
}
users := collection.New([]User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Carol", Age: 40},
})
minUser, ok := collection.MinBy(users, func(u User) int {
return u.Age
})
collection.Dump(minUser, ok)
// #main.User {
// +Name => "Bob" #string
// +Age => 25 #int
// }
// true #bool
Example: strings - shortest length
words := collection.New([]string{"apple", "fig", "banana"})
shortest, ok := collection.MinBy(words, func(s string) int {
return len(s)
})
collection.Dump(shortest, ok)
// "fig" #string
// true #bool
Example: empty collection
empty := collection.New([]int{})
minVal, ok := collection.MinBy(empty, func(v int) int { return v })
collection.Dump(minVal, ok)
// 0 #int
// false #bool
func ToMap ¶
func ToMap[T any, K comparable, V any]( c *Collection[T], keyFn func(T) K, valueFn func(T) V, ) map[K]V
ToMap reduces a collection into a map using the provided key and value selector functions. @group Maps @behavior readonly @fluent false
If multiple items produce the same key, the last value wins.
This operation allocates a map sized to the collection length.
Example: basic usage
users := []string{"alice", "bob", "carol"}
out := collection.ToMap(
collection.New(users),
func(name string) string { return name },
func(name string) int { return len(name) },
)
collection.Dump(out)
Example: re-keying structs
type User struct {
ID int
Name string
}
users2 := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
}
byID := collection.ToMap(
collection.New(users2),
func(u User) int { return u.ID },
func(u User) User { return u },
)
collection.Dump(byID)
func ToMapKV ¶
func ToMapKV[K comparable, V any](c *Collection[Pair[K, V]]) map[K]V
ToMapKV converts a collection of key/value pairs into a map. @group Maps @behavior readonly @fluent false
If multiple pairs contain the same key, the last value wins.
This operation allocates a map sized to the collection length.
Example: basic usage
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
c := collection.FromMap(m)
out := collection.ToMapKV(c)
collection.Dump(out)
// #map[string]int [
// "a" => 1
// "b" => 2
// "c" => 3
// ]
Example: filtering before conversion
type Config struct {
Enabled bool
Timeout int
}
configs := map[string]Config{
"router-1": {Enabled: true, Timeout: 30},
"router-2": {Enabled: false, Timeout: 10},
"router-3": {Enabled: true, Timeout: 45},
}
c2 := collection.
FromMap(configs).
Filter(func(p collection.Pair[string, Config]) bool {
return p.Value.Enabled
})
out2 := collection.ToMapKV(c2)
collection.Dump(out2)
// #map[string]collection.Config [
// "router-1" => {Enabled:true Timeout:30}
// "router-3" => {Enabled:true Timeout:45}
// ]
Types ¶
type Collection ¶
type Collection[T any] struct { // contains filtered or unexported fields }
Collection is a strongly-typed, fluent wrapper around a slice of T.
func Difference ¶
func Difference[T comparable](a, b *Collection[T]) *Collection[T]
Difference returns a new collection containing elements from the first collection that are not present in the second. Order follows the first collection, and duplicates are removed. @group Set Operations @behavior immutable @fluent true
Example: integers
a := collection.New([]int{1, 2, 2, 3, 4})
b := collection.New([]int{2, 4})
out := collection.Difference(a, b)
collection.Dump(out.Items())
// #[]int [
// 0 => 1 #int
// 1 => 3 #int
// ]
Example: strings
left := collection.New([]string{"apple", "banana", "cherry"})
right := collection.New([]string{"banana"})
out2 := collection.Difference(left, right)
collection.Dump(out2.Items())
// #[]string [
// 0 => "apple" #string
// 1 => "cherry" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
groupA := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
})
groupB := collection.New([]User{
{ID: 2, Name: "Bob"},
})
out3 := collection.Difference(groupA, groupB)
collection.Dump(out3.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// ]
func FromMap ¶
func FromMap[K comparable, V any](m map[K]V) *Collection[Pair[K, V]]
FromMap materializes a map into a collection of key/value pairs. @group Maps @behavior immutable @fluent true
The iteration order of the resulting collection is unspecified, matching Go's map iteration semantics.
This function does not mutate the input map.
Example: basic usage
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
c := collection.FromMap(m)
collection.Dump(c.Items())
// #[]collection.Pair[string,int] [
// 0 => {Key:"a" Value:1}
// 1 => {Key:"b" Value:2}
// 2 => {Key:"c" Value:3}
// ]
Example: filtering map entries
type Config struct {
Enabled bool
Timeout int
}
configs := map[string]Config{
"router-1": {Enabled: true, Timeout: 30},
"router-2": {Enabled: false, Timeout: 10},
"router-3": {Enabled: true, Timeout: 45},
}
out := collection.
FromMap(configs).
Filter(func(p collection.Pair[string, Config]) bool {
return p.Value.Enabled
}).
Items()
collection.Dump(out)
// #[]collection.Pair[string,collection.Config] [
// 0 => {Key:"router-1" Value:{Enabled:true Timeout:30}}
// 1 => {Key:"router-3" Value:{Enabled:true Timeout:45}}
// ]
Example: map → collection → map
users := map[string]int{
"alice": 1,
"bob": 2,
}
c2 := collection.FromMap(users)
out2 := collection.ToMapKV(c2)
collection.Dump(out2)
// #map[string]int [
// "alice" => 1
// "bob" => 2
// ]
func Intersect ¶
func Intersect[T comparable](a, b *Collection[T]) *Collection[T]
Intersect returns a new collection containing elements from the second collection that are also present in the first. @group Set Operations @behavior immutable @fluent true
Order follows the second collection. Duplicates are preserved based on the second collection.
Example: integers
a := collection.New([]int{1, 2, 2, 3, 4})
b := collection.New([]int{2, 4, 4, 5})
out := collection.Intersect(a, b)
collection.Dump(out.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// 2 => 4 #int
// ]
Example: strings
left := collection.New([]string{"apple", "banana", "cherry"})
right := collection.New([]string{"banana", "date", "cherry", "banana"})
out2 := collection.Intersect(left, right)
collection.Dump(out2.Items())
// #[]string [
// 0 => "banana" #string
// 1 => "cherry" #string
// 2 => "banana" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
groupA := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
})
groupB := collection.New([]User{
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
{ID: 4, Name: "Dave"},
})
out3 := collection.Intersect(groupA, groupB)
collection.Dump(out3.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// 1 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// ]
func MapTo ¶
func MapTo[T any, R any](c *Collection[T], fn func(T) R) *Collection[R]
MapTo maps a Collection[T] to a Collection[R] using fn(T) R. @group Transformation @behavior immutable @fluent true
This cannot be a method because methods can't introduce a new type parameter R.
Example: integers - extract parity label
nums := collection.New([]int{1, 2, 3, 4})
parity := collection.MapTo(nums, func(n int) string {
if n%2 == 0 {
return "even"
}
return "odd"
})
collection.Dump(parity.Items())
// #[]string [
// 0 => "odd" #string
// 1 => "even" #string
// 2 => "odd" #string
// 3 => "even" #string
// ]
Example: strings - length of each value
words := collection.New([]string{"go", "forj", "rocks"})
lengths := collection.MapTo(words, func(s string) int {
return len(s)
})
collection.Dump(lengths.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// 2 => 5 #int
// ]
Example: structs - MapTo a field
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
names := collection.MapTo(users, func(u User) string {
return u.Name
})
collection.Dump(names.Items())
// #[]string [
// 0 => "Alice" #string
// 1 => "Bob" #string
// ]
func New ¶
func New[T any](items []T) *Collection[T]
New creates a new Collection from the provided slice. @group Construction @behavior immutable @fluent true
The returned Collection is a lightweight, strongly-typed wrapper around the slice, enabling fluent, chainable operations such as filtering, mapping, reducing, sorting, and more.
The underlying slice is stored as-is (no copy is made), allowing New to be both fast and allocation-friendly. Callers should clone the input beforehand if they need to prevent shared mutation.
func Pluck ¶
func Pluck[T any, R any](c *Collection[T], fn func(T) R) *Collection[R]
Pluck is an alias for MapTo with a more semantic name when projecting fields. It extracts a single field or computed value from every element and returns a new typed collection. @group Transformation @behavior immutable @fluent true
Example: integers - extract parity label
nums := collection.New([]int{1, 2, 3, 4})
parity := collection.Pluck(nums, func(n int) string {
if n%2 == 0 {
return "even"
}
return "odd"
})
collection.Dump(parity.Items())
// #[]string [
// 0 => "odd" #string
// 1 => "even" #string
// 2 => "odd" #string
// 3 => "even" #string
// ]
Example: strings - length of each value
words := collection.New([]string{"go", "forj", "rocks"})
lengths := collection.Pluck(words, func(s string) int {
return len(s)
})
collection.Dump(lengths.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// 2 => 5 #int
// ]
Example: structs - pluck a field
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
names := collection.Pluck(users, func(u User) string {
return u.Name
})
collection.Dump(names.Items())
// #[]string [
// 0 => "Alice" #string
// 1 => "Bob" #string
// ]
func SymmetricDifference ¶
func SymmetricDifference[T comparable](a, b *Collection[T]) *Collection[T]
SymmetricDifference returns a new collection containing elements that appear in exactly one of the two collections. Order follows the first collection for its unique items, then the second for its unique items. Duplicates are removed. @group Set Operations @behavior immutable @fluent true
Example: integers
a := collection.New([]int{1, 2, 3, 3})
b := collection.New([]int{3, 4, 4, 5})
out := collection.SymmetricDifference(a, b)
collection.Dump(out.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 4 #int
// 3 => 5 #int
// ]
Example: strings
left := collection.New([]string{"apple", "banana"})
right := collection.New([]string{"banana", "date"})
out2 := collection.SymmetricDifference(left, right)
collection.Dump(out2.Items())
// #[]string [
// 0 => "apple" #string
// 1 => "date" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
groupA := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
groupB := collection.New([]User{
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
})
out3 := collection.SymmetricDifference(groupA, groupB)
collection.Dump(out3.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// ]
func TakeUntil ¶
func TakeUntil[T comparable](c *Collection[T], value T) *Collection[T]
TakeUntil returns items until the first element equals `value`. The matching item is NOT included. @fluent true
Uses == comparison, so T must be comparable. @group Slicing @behavior immutable Example: integers - stop at value 3
c4 := collection.New([]int{1, 2, 3, 4})
out4 := collection.TakeUntil(c4, 3)
collection.Dump(out4.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// ]
Example: strings - value never appears → full slice
c5 := collection.New([]string{"a", "b", "c"})
out5 := collection.TakeUntil(c5, "x")
collection.Dump(out5.Items())
// #[]string [
// 0 => "a" #string
// 1 => "b" #string
// 2 => "c" #string
// ]
Example: integers - match is first item → empty result
c6 := collection.New([]int{9, 10, 11})
out6 := collection.TakeUntil(c6, 9)
collection.Dump(out6.Items())
// #[]int [
// ]
func Times ¶
func Times[T any](count int, fn func(int) T) *Collection[T]
Times creates a new collection by calling fn(i) for i = 1..count. This mirrors Laravel's Collection::times(), which is 1-indexed. @group Transformation @behavior immutable @fluent true
If count <= 0, an empty collection is returned.
Example: integers - double each index
cTimes1 := collection.Times(5, func(i int) int {
return i * 2
})
collection.Dump(cTimes1.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// 2 => 6 #int
// 3 => 8 #int
// 4 => 10 #int
// ]
Example: strings
cTimes2 := collection.Times(3, func(i int) string {
return fmt.Sprintf("item-%d", i)
})
collection.Dump(cTimes2.Items())
// #[]string [
// 0 => "item-1" #string
// 1 => "item-2" #string
// 2 => "item-3" #string
// ]
Example: structs
type Point struct {
X int
Y int
}
cTimes3 := collection.Times(4, func(i int) Point {
return Point{X: i, Y: i * i}
})
collection.Dump(cTimes3.Items())
// #[]main.Point [
// 0 => #main.Point {
// +X => 1 #int
// +Y => 1 #int
// }
// 1 => #main.Point {
// +X => 2 #int
// +Y => 4 #int
// }
// 2 => #main.Point {
// +X => 3 #int
// +Y => 9 #int
// }
// 3 => #main.Point {
// +X => 4 #int
// +Y => 16 #int
// }
// ]
func Union ¶
func Union[T comparable](a, b *Collection[T]) *Collection[T]
Union returns a new collection containing the unique elements from both collections. Items from the first collection are kept in order, followed by items from the second that were not already present. @group Set Operations @behavior immutable @fluent true
Example: integers
a := collection.New([]int{1, 2, 2, 3})
b := collection.New([]int{3, 4, 4, 5})
out := collection.Union(a, b)
collection.Dump(out.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// 4 => 5 #int
// ]
Example: strings
left := collection.New([]string{"apple", "banana"})
right := collection.New([]string{"banana", "date"})
out2 := collection.Union(left, right)
collection.Dump(out2.Items())
// #[]string [
// 0 => "apple" #string
// 1 => "banana" #string
// 2 => "date" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
groupA := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
groupB := collection.New([]User{
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
})
out3 := collection.Union(groupA, groupB)
collection.Dump(out3.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// 2 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// ]
func UniqueBy ¶
func UniqueBy[T any, K comparable](c *Collection[T], keyFn func(T) K) *Collection[T]
UniqueBy returns a new collection containing only the first occurrence of each element as determined by keyFn. @group Set Operations @behavior immutable @fluent true
The key returned by keyFn must be comparable. Order is preserved.
Example: structs – unique by ID
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 1, Name: "Alice Duplicate"},
})
out := collection.UniqueBy(users, func(u User) int { return u.ID })
collection.Dump(out.Items())
// #[]collection.User [
// 0 => {ID:1 Name:"Alice"} #collection.User
// 1 => {ID:2 Name:"Bob"} #collection.User
// ]
Example: strings – case-insensitive uniqueness
values := collection.New([]string{"A", "a", "B", "b", "A"})
out2 := collection.UniqueBy(values, func(s string) string {
return strings.ToLower(s)
})
collection.Dump(out2.Items())
// #[]string [
// 0 => "A" #string
// 1 => "B" #string
// ]
Example: integers – identity key
nums := collection.New([]int{3, 1, 2, 1, 3})
out3 := collection.UniqueBy(nums, func(v int) int { return v })
collection.Dump(out3.Items())
// #[]int [
// 0 => 3 #int
// 1 => 1 #int
// 2 => 2 #int
// ]
func UniqueComparable ¶
func UniqueComparable[T comparable](c *Collection[T]) *Collection[T]
UniqueComparable returns a new collection with duplicate comparable items removed. The first occurrence of each value is kept, and order is preserved. This is a faster, allocation-friendly path for comparable types. @group Set Operations @behavior immutable @fluent true
Example: integers
c := collection.New([]int{1, 2, 2, 3, 4, 4, 5})
out := collection.UniqueComparable(c)
collection.Dump(out.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// 4 => 5 #int
// ]
Example: strings
c2 := collection.New([]string{"A", "a", "B", "B"})
out2 := collection.UniqueComparable(c2)
collection.Dump(out2.Items())
// #[]string [
// 0 => "A" #string
// 1 => "a" #string
// 2 => "B" #string
// ]
func Window ¶
func Window[T any](c *Collection[T], size int, step int) *Collection[[]T]
Window returns overlapping (or stepped) windows of the collection. Each window is a slice of length size; iteration advances by step (default 1 if step <= 0). Windows that are shorter than size are omitted. @group Slicing @behavior allocates @fluent true
Example: integers - step 1
nums := collection.New([]int{1, 2, 3, 4, 5})
win := collection.Window(nums, 3, 1)
collection.Dump(win.Items())
// #[][]int [
// 0 => #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// ]
// 1 => #[]int [
// 0 => 2 #int
// 1 => 3 #int
// 2 => 4 #int
// ]
// 2 => #[]int [
// 0 => 3 #int
// 1 => 4 #int
// 2 => 5 #int
// ]
// ]
Example: strings - step 2
words := collection.New([]string{"a", "b", "c", "d", "e"})
win2 := collection.Window(words, 2, 2)
collection.Dump(win2.Items())
// #[][]string [
// 0 => #[]string [
// 0 => "a" #string
// 1 => "b" #string
// ]
// 1 => #[]string [
// 0 => "c" #string
// 1 => "d" #string
// ]
// ]
Example: structs
type Point struct {
X int
Y int
}
points := collection.New([]Point{
{X: 0, Y: 0},
{X: 1, Y: 1},
{X: 2, Y: 4},
{X: 3, Y: 9},
})
win3 := collection.Window(points, 2, 1)
collection.Dump(win3.Items())
// #[][]main.Point [
// 0 => #[]main.Point [
// 0 => #main.Point {
// +X => 0 #int
// +Y => 0 #int
// }
// 1 => #main.Point {
// +X => 1 #int
// +Y => 1 #int
// }
// ]
// 1 => #[]main.Point [
// 0 => #main.Point {
// +X => 1 #int
// +Y => 1 #int
// }
// 1 => #main.Point {
// +X => 2 #int
// +Y => 4 #int
// }
// ]
// 2 => #[]main.Point [
// 0 => #main.Point {
// +X => 2 #int
// +Y => 4 #int
// }
// 1 => #main.Point {
// +X => 3 #int
// +Y => 9 #int
// }
// ]
// ]
func Zip ¶
func Zip[A any, B any](a *Collection[A], b *Collection[B]) *Collection[Tuple[A, B]]
Zip combines two collections element-wise into a collection of tuples. The resulting length is the smaller of the two inputs. @group Transformation @behavior immutable @fluent true
Example: integers and strings
nums := collection.New([]int{1, 2, 3})
words := collection.New([]string{"one", "two"})
out := collection.Zip(nums, words)
collection.Dump(out.Items())
// #[]collection.Tuple[int,string] [
// 0 => #collection.Tuple[int,string] {
// +First => 1 #int
// +Second => "one" #string
// }
// 1 => #collection.Tuple[int,string] {
// +First => 2 #int
// +Second => "two" #string
// }
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
roles := collection.New([]string{"admin", "user", "extra"})
out2 := collection.Zip(users, roles)
collection.Dump(out2.Items())
// #[]collection.Tuple[main.User,string] [
// 0 => #collection.Tuple[main.User,string] {
// +First => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// +Second => "admin" #string
// }
// 1 => #collection.Tuple[main.User,string] {
// +First => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// +Second => "user" #string
// }
// ]
func ZipWith ¶
func ZipWith[A any, B any, R any](a *Collection[A], b *Collection[B], fn func(A, B) R) *Collection[R]
ZipWith combines two collections element-wise using combiner fn. The resulting length is the smaller of the two inputs. @group Transformation @behavior immutable @fluent true
Example: sum ints
a := collection.New([]int{1, 2, 3})
b := collection.New([]int{10, 20})
out := collection.ZipWith(a, b, func(x, y int) int {
return x + y
})
collection.Dump(out.Items())
// #[]int [
// 0 => 11 #int
// 1 => 22 #int
// ]
Example: format strings
names := collection.New([]string{"alice", "bob"})
roles := collection.New([]string{"admin", "user", "extra"})
out2 := collection.ZipWith(names, roles, func(name, role string) string {
return name + ":" + role
})
collection.Dump(out2.Items())
// #[]string [
// 0 => "alice:admin" #string
// 1 => "bob:user" #string
// ]
Example: structs
type User struct {
Name string
}
type Role struct {
Title string
}
users := collection.New([]User{{Name: "Alice"}, {Name: "Bob"}})
roles2 := collection.New([]Role{{Title: "admin"}})
out3 := collection.ZipWith(users, roles2, func(u User, r Role) string {
return u.Name + " -> " + r.Title
})
collection.Dump(out3.Items())
// #[]string [
// 0 => "Alice -> admin" #string
// ]
func (*Collection[T]) After ¶
func (c *Collection[T]) After(pred func(T) bool) *Collection[T]
After returns all items after the first element for which pred returns true. If no element matches, an empty collection is returned. @group Ordering @behavior immutable @fluent true
Example: integers
c := collection.New([]int{1, 2, 3, 4, 5})
c.After(func(v int) bool { return v == 3 }).Dump()
// #[]int [
// 0 => 4 #int
// 1 => 5 #int
// ]
func (*Collection[T]) All ¶
func (c *Collection[T]) All(fn func(T) bool) bool
All returns true if fn returns true for every item in the collection. If the collection is empty, All returns true (vacuously true). @group Querying @behavior readonly @fluent true
Example: integers – all even
c := collection.New([]int{2, 4, 6})
allEven := c.All(func(v int) bool { return v%2 == 0 })
collection.Dump(allEven)
// true #bool
Example: integers – not all even
c2 := collection.New([]int{2, 3, 4})
allEven2 := c2.All(func(v int) bool { return v%2 == 0 })
collection.Dump(allEven2)
// false #bool
Example: strings – all non-empty
c3 := collection.New([]string{"a", "b", "c"})
allNonEmpty := c3.All(func(s string) bool { return s != "" })
collection.Dump(allNonEmpty)
// true #bool
Example: empty collection (vacuously true)
empty := collection.New([]int{})
all := empty.All(func(v int) bool { return v > 0 })
collection.Dump(all)
// true #bool
func (*Collection[T]) Any ¶
func (c *Collection[T]) Any(fn func(T) bool) bool
Any returns true if at least one item satisfies fn. @group Querying @behavior readonly @fluent true Example: integers
c := collection.New([]int{1, 2, 3, 4})
has := c.Any(func(v int) bool { return v%2 == 0 }) // true
collection.Dump(has)
// true #bool
func (*Collection[T]) Append ¶
func (c *Collection[T]) Append(values ...T) *Collection[T]
Append returns a new collection with the given values appended. @group Transformation @behavior immutable @fluent true Example: integers
c := collection.New([]int{1, 2})
c.Append(3, 4).Dump()
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
users.Append(
User{ID: 3, Name: "Carol"},
User{ID: 4, Name: "Dave"},
).Dump()
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// 2 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// 3 => #main.User {
// +ID => 4 #int
// +Name => "Dave" #string
// }
// ]
func (*Collection[T]) At ¶
func (c *Collection[T]) At(i int) (T, bool)
At returns the item at the given index and a boolean indicating whether the index was within bounds. @group Querying @behavior readonly @fluent true
This method is safe and does not panic for out-of-range indices.
Example: integers
c := collection.New([]int{10, 20, 30})
v, ok := c.At(1)
collection.Dump(v, ok)
// 20 true
Example: out of bounds
v2, ok2 := c.At(10) collection.Dump(v2, ok2) // 0 false
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
u, ok3 := users.At(0)
collection.Dump(u, ok3)
// {ID:1 Name:"Alice"} true
func (*Collection[T]) Before ¶
func (c *Collection[T]) Before(pred func(T) bool) *Collection[T]
Before returns a new collection containing all items that appear *before* the first element for which pred returns true. @group Ordering @behavior immutable @fluent true
If no element matches the predicate, the entire collection is returned.
Example: integers
c1 := collection.New([]int{1, 2, 3, 4, 5})
out1 := c1.Before(func(v int) bool { return v >= 3 })
collection.Dump(out1.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// ]
Example: predicate never matches → whole collection returned
c2 := collection.New([]int{10, 20, 30})
out2 := c2.Before(func(v int) bool { return v == 99 })
collection.Dump(out2.Items())
// #[]int [
// 0 => 10 #int
// 1 => 20 #int
// 2 => 30 #int
// ]
Example: structs: get all users before the first admin
type User struct {
Name string
Admin bool
}
c3 := collection.New([]User{
{Name: "Alice", Admin: false},
{Name: "Bob", Admin: false},
{Name: "Eve", Admin: true},
{Name: "Mallory", Admin: false},
})
out3 := c3.Before(func(u User) bool { return u.Admin })
collection.Dump(out3.Items())
// #[]collection.User [
// 0 => {Name:"Alice" Admin:false} #collection.User
// 1 => {Name:"Bob" Admin:false} #collection.User
// ]
func (*Collection[T]) Chunk ¶
func (c *Collection[T]) Chunk(size int) [][]T
Chunk splits the collection into chunks of the given size. The final chunk may be smaller if len(items) is not divisible by size. @group Slicing @behavior readonly @fluent true
If size <= 0, nil is returned. Example: integers
c := collection.New([]int{1, 2, 3, 4, 5}).Chunk(2)
collection.Dump(c)
// #[][]int [
// 0 => #[]int [
// 0 => 1 #int
// 1 => 2 #int
// ]
// 1 => #[]int [
// 0 => 3 #int
// 1 => 4 #int
// ]
// 2 => #[]int [
// 0 => 5 #int
// ]
//]
Example: structs
type User struct {
ID int
Name string
}
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
{ID: 4, Name: "Dave"},
}
userChunks := collection.New(users).Chunk(2)
collection.Dump(userChunks)
// Dump output will show [][]User grouped in size-2 chunks, e.g.:
// #[][]main.User [
// 0 => #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// ]
// 1 => #[]main.User [
// 0 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// 1 => #main.User {
// +ID => 4 #int
// +Name => "Dave" #string
// }
// ]
//]
func (*Collection[T]) Clone ¶
func (c *Collection[T]) Clone() *Collection[T]
Clone returns a shallow copy of the collection. @fluent true
The returned collection has its own backing slice, so subsequent mutations do not affect the original collection.
Clone is intended to be used when branching a pipeline while preserving the original collection.
@group Construction @behavior allocates
Example: basic cloning
c := collection.New([]int{1, 2, 3})
clone := c.Clone()
clone.Push(4)
collection.Dump(c.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// ]
collection.Dump(clone.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// ]
Example: branching pipelines
base := collection.New([]int{1, 2, 3, 4, 5})
evens := base.Clone().Filter(func(v int) bool {
return v%2 == 0
})
odds := base.Clone().Filter(func(v int) bool {
return v%2 != 0
})
collection.Dump(base.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// 4 => 5 #int
// ]
collection.Dump(evens.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// ]
collection.Dump(odds.Items())
// #[]int [
// 0 => 1 #int
// 1 => 3 #int
// 2 => 5 #int
// ]
func (*Collection[T]) Concat ¶
func (c *Collection[T]) Concat(values []T) *Collection[T]
Concat appends the values from the given slice onto the end of the collection, @group Transformation @behavior mutable @fluent true
Example: strings
c := collection.New([]string{"John Doe"})
concatenated := c.
Concat([]string{"Jane Doe"}).
Concat([]string{"Johnny Doe"}).
Items()
collection.Dump(concatenated)
// #[]string [
// 0 => "John Doe" #string
// 1 => "Jane Doe" #string
// 2 => "Johnny Doe" #string
// ]
func (*Collection[T]) Contains ¶
func (c *Collection[T]) Contains(pred func(T) bool) bool
Contains returns true if any item satisfies the predicate. @group Querying @behavior readonly @fluent true Example: integers
c := collection.New([]int{1, 2, 3, 4, 5})
hasEven := c.Contains(func(v int) bool {
return v%2 == 0
})
collection.Dump(hasEven)
// true #bool
Example: strings
c2 := collection.New([]string{"apple", "banana", "cherry"})
hasBanana := c2.Contains(func(v string) bool {
return v == "banana"
})
collection.Dump(hasBanana)
// true #bool
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
})
hasBob := users.Contains(func(u User) bool {
return u.Name == "Bob"
})
collection.Dump(hasBob)
// true #bool
func (*Collection[T]) Count ¶
func (c *Collection[T]) Count() int
Count returns the total number of items in the collection. @group Aggregation @behavior readonly @fluent true Example: integers
count := collection.New([]int{1, 2, 3, 4}).Count()
collection.Dump(count)
// 4 #int
func (*Collection[T]) Dd ¶
func (c *Collection[T]) Dd()
Dd prints items then terminates execution. Like Laravel's dd(), this is intended for debugging and should not be used in production control flow. @group Debugging @fluent true
This method never returns.
Example: strings
c := collection.New([]string{"a", "b"})
c.Dd()
// #[]string [
// 0 => "a" #string
// 1 => "b" #string
// ]
// Process finished with the exit code 1
func (*Collection[T]) Dump ¶
func (c *Collection[T]) Dump() *Collection[T]
Dump prints items with godump and returns the same collection. This is a no-op on the collection itself and never panics. @group Debugging @behavior readonly @fluent true
Example: integers
c := collection.New([]int{1, 2, 3})
c.Dump()
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// ]
Example: integers - chaining
collection.New([]int{1, 2, 3}).
Filter(func(v int) bool { return v > 1 }).
Dump()
// #[]int [
// 0 => 2 #int
// 1 => 3 #int
// ]
func (*Collection[T]) DumpStr ¶
func (c *Collection[T]) DumpStr() string
DumpStr returns the pretty-printed dump of the items as a string, without printing or exiting. Useful for logging, snapshot testing, and non-interactive debugging. @group Debugging @behavior readonly @fluent true
Example: integers
c := collection.New([]int{10, 20})
s := c.DumpStr()
fmt.Println(s)
// #[]int [
// 0 => 10 #int
// 1 => 20 #int
// ]
func (*Collection[T]) Each ¶
func (c *Collection[T]) Each(fn func(T)) *Collection[T]
Each runs fn for every item in the collection and returns the same collection, so it can be used in chains for side effects (logging, debugging, etc.). @group Transformation @behavior immutable @fluent true
Example: integers
c := collection.New([]int{1, 2, 3})
sum := 0
c.Each(func(v int) {
sum += v
})
collection.Dump(sum)
// 6 #int
Example: strings
c2 := collection.New([]string{"apple", "banana", "cherry"})
var out []string
c2.Each(func(s string) {
out = append(out, strings.ToUpper(s))
})
collection.Dump(out)
// #[]string [
// 0 => "APPLE" #string
// 1 => "BANANA" #string
// 2 => "CHERRY" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
})
var names []string
users.Each(func(u User) {
names = append(names, u.Name)
})
collection.Dump(names)
// #[]string [
// 0 => "Alice" #string
// 1 => "Bob" #string
// 2 => "Charlie" #string
// ]
func (*Collection[T]) Filter ¶
func (c *Collection[T]) Filter(fn func(T) bool) *Collection[T]
Filter keeps only the elements for which fn returns true. This method mutates the collection in place and returns the same instance. @group Slicing @behavior mutable @fluent true Example: integers
c := collection.New([]int{1, 2, 3, 4})
c.Filter(func(v int) bool {
return v%2 == 0
})
collection.Dump(c.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// ]
Example: strings
c2 := collection.New([]string{"apple", "banana", "cherry", "avocado"})
c2.Filter(func(v string) bool {
return strings.HasPrefix(v, "a")
})
collection.Dump(c2.Items())
// #[]string [
// 0 => "apple" #string
// 1 => "avocado" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Andrew"},
{ID: 4, Name: "Carol"},
})
users.Filter(func(u User) bool {
return strings.HasPrefix(u.Name, "A")
})
collection.Dump(users.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 3 #int
// +Name => "Andrew" #string
// }
// ]
func (*Collection[T]) FindWhere ¶
func (c *Collection[T]) FindWhere(fn func(T) bool) (T, bool)
FindWhere returns the first item in the collection for which the provided predicate function returns true. This is an alias for FirstWhere(fn) and exists for ergonomic parity with functional languages (JavaScript, Rust, C#, Python) where developers expect a “find” helper. @group Querying @behavior readonly @fluent true
Example: integers
nums := collection.New([]int{1, 2, 3, 4, 5})
v1, ok1 := nums.FindWhere(func(n int) bool {
return n == 3
})
collection.Dump(v1, ok1)
// 3 #int
// true #bool
Example: no match
v2, ok2 := nums.FindWhere(func(n int) bool {
return n > 10
})
collection.Dump(v2, ok2)
// 0 #int
// false #bool
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
})
u, ok3 := users.FindWhere(func(u User) bool {
return u.ID == 2
})
collection.Dump(u, ok3)
// #collection.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// true #bool
Example: integers - empty collection
empty := collection.New([]int{})
v4, ok4 := empty.FindWhere(func(n int) bool { return n == 1 })
collection.Dump(v4, ok4)
// 0 #int
// false #bool
func (*Collection[T]) First ¶
func (c *Collection[T]) First() (value T, ok bool)
First returns the first element in the collection. If the collection is empty, ok will be false. @group Querying @behavior readonly @fluent true
Example: integers
c := collection.New([]int{10, 20, 30})
v, ok := c.First()
collection.Dump(v, ok)
// 10 #int
// true #bool
Example: strings
c2 := collection.New([]string{"alpha", "beta", "gamma"})
v2, ok2 := c2.First()
collection.Dump(v2, ok2)
// "alpha" #string
// true #bool
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
u, ok3 := users.First()
collection.Dump(u, ok3)
// #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// true #bool
Example: integers - empty collection
c3 := collection.New([]int{})
v3, ok4 := c3.First()
collection.Dump(v3, ok4)
// 0 #int
// false #bool
func (*Collection[T]) FirstWhere ¶
func (c *Collection[T]) FirstWhere(fn func(T) bool) (value T, ok bool)
FirstWhere returns the first item in the collection for which the provided predicate function returns true. If no items match, ok=false is returned along with the zero value of T. @group Querying @behavior readonly @fluent true
This method is equivalent to Laravel's collection->first(fn) and mirrors the behavior found in functional collections in other languages.
Example: integers
nums := collection.New([]int{1, 2, 3, 4, 5})
v, ok := nums.FirstWhere(func(n int) bool {
return n%2 == 0
})
collection.Dump(v, ok)
// 2 #int
// true #bool
v, ok = nums.FirstWhere(func(n int) bool {
return n > 10
})
collection.Dump(v, ok)
// 0 #int
// false #bool
func (*Collection[T]) IndexWhere ¶
func (c *Collection[T]) IndexWhere(fn func(T) bool) (int, bool)
IndexWhere returns the index of the first item in the collection for which the provided predicate function returns true. If no item matches, it returns (0, false). @group Querying @behavior readonly @fluent true
This operation performs no allocations and short-circuits on the first match.
Example: integers
c := collection.New([]int{10, 20, 30, 40})
idx, ok := c.IndexWhere(func(v int) bool { return v == 30 })
collection.Dump(idx, ok)
// 2 true
Example: not found
idx2, ok2 := c.IndexWhere(func(v int) bool { return v == 99 })
collection.Dump(idx2, ok2)
// 0 false
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
})
idx3, ok3 := users.IndexWhere(func(u User) bool {
return u.Name == "Bob"
})
collection.Dump(idx3, ok3)
// 1 true
func (*Collection[T]) IsEmpty ¶
func (c *Collection[T]) IsEmpty() bool
IsEmpty returns true if the collection has no items. @group Querying @behavior readonly @fluent true
Example: integers (non-empty)
c := collection.New([]int{1, 2, 3})
empty := c.IsEmpty()
collection.Dump(empty)
// false #bool
Example: strings (empty)
c2 := collection.New([]string{})
empty2 := c2.IsEmpty()
collection.Dump(empty2)
// true #bool
Example: structs (non-empty)
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
})
empty3 := users.IsEmpty()
collection.Dump(empty3)
// false #bool
Example: structs (empty)
none := collection.New([]User{})
empty4 := none.IsEmpty()
collection.Dump(empty4)
// true #bool
func (*Collection[T]) Items ¶
func (c *Collection[T]) Items() []T
Items returns the underlying slice of items. @group Access @behavior readonly @fluent true
Example: integers
c := collection.New([]int{1, 2, 3})
items := c.Items()
collection.Dump(items)
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// ]
Example: strings
c2 := collection.New([]string{"apple", "banana"})
items2 := c2.Items()
collection.Dump(items2)
// #[]string [
// 0 => "apple" #string
// 1 => "banana" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
out := users.Items()
collection.Dump(out)
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// ]
func (*Collection[T]) Last ¶
func (c *Collection[T]) Last() (value T, ok bool)
Last returns the last element in the collection. If the collection is empty, ok will be false. @group Querying @behavior readonly @fluent true
Example: integers
c := collection.New([]int{10, 20, 30})
v, ok := c.Last()
collection.Dump(v, ok)
// 30 #int
// true #bool
Example: strings
c2 := collection.New([]string{"alpha", "beta", "gamma"})
v2, ok2 := c2.Last()
collection.Dump(v2, ok2)
// "gamma" #string
// true #bool
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
})
u, ok3 := users.Last()
collection.Dump(u, ok3)
// #main.User {
// +ID => 3 #int
// +Name => "Charlie" #string
// }
// true #bool
Example: empty collection
c3 := collection.New([]int{})
v3, ok4 := c3.Last()
collection.Dump(v3, ok4)
// 0 #int
// false #bool
func (*Collection[T]) LastWhere ¶
func (c *Collection[T]) LastWhere(fn func(T, int) bool) (value T, ok bool)
LastWhere returns the last element in the collection that satisfies the predicate fn. If fn is nil, LastWhere returns the final element in the underlying slice. If the collection is empty or no element matches, ok will be false. @group Querying @behavior readonly @fluent true
Example: integers
c := collection.New([]int{1, 2, 3, 4})
v, ok := c.LastWhere(func(v int, i int) bool {
return v < 3
})
collection.Dump(v, ok)
// 2 #int
// true #bool
Example: integers without predicate (equivalent to Last())
c2 := collection.New([]int{10, 20, 30, 40})
v2, ok2 := c2.LastWhere(nil)
collection.Dump(v2, ok2)
// 40 #int
// true #bool
Example: strings
c3 := collection.New([]string{"alpha", "beta", "gamma", "delta"})
v3, ok3 := c3.LastWhere(func(s string, i int) bool {
return strings.HasPrefix(s, "g")
})
collection.Dump(v3, ok3)
// "gamma" #string
// true #bool
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Alex"},
{ID: 4, Name: "Brian"},
})
u, ok4 := users.LastWhere(func(u User, i int) bool {
return strings.HasPrefix(u.Name, "A")
})
collection.Dump(u, ok4)
// #main.User {
// +ID => 3 #int
// +Name => "Alex" #string
// }
// true #bool
Example: no matching element
c4 := collection.New([]int{5, 6, 7})
v4, ok5 := c4.LastWhere(func(v int, i int) bool {
return v > 10
})
collection.Dump(v4, ok5)
// 0 #int
// false #bool
Example: empty collection
c5 := collection.New([]int{})
v5, ok6 := c5.LastWhere(nil)
collection.Dump(v5, ok6)
// 0 #int
// false #bool
func (*Collection[T]) Map ¶
func (c *Collection[T]) Map(fn func(T) T) *Collection[T]
Map applies a same-type transformation and returns a new collection. @group Transformation @behavior immutable @fluent true
Use this when you're transforming T -> T (e.g., enrichment, normalization).
Example: integers
c := collection.New([]int{1, 2, 3})
mapped := c.Map(func(v int) int {
return v * 10
})
collection.Dump(mapped.Items())
// #[]int [
// 0 => 10 #int
// 1 => 20 #int
// 2 => 30 #int
// ]
Example: strings
c2 := collection.New([]string{"apple", "banana", "cherry"})
upper := c2.Map(func(s string) string {
return strings.ToUpper(s)
})
collection.Dump(upper.Items())
// #[]string [
// 0 => "APPLE" #string
// 1 => "BANANA" #string
// 2 => "CHERRY" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
updated := users.Map(func(u User) User {
u.Name = strings.ToUpper(u.Name)
return u
})
collection.Dump(updated.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "ALICE" #string
// }
// 1 => #main.User {
// +ID => 2 #int
// +Name => "BOB" #string
// }
// ]
func (*Collection[T]) Merge ¶
func (c *Collection[T]) Merge(other any) *Collection[T]
Merge merges the given data into the current collection. @group Transformation @behavior mutable @fluent true
Example: integers - merging slices
ints := collection.New([]int{1, 2})
extra := []int{3, 4}
// Merge the extra slice into the ints collection
merged1 := ints.Merge(extra)
collection.Dump(merged1.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// ]
Example: strings - merging another collection
strs := collection.New([]string{"a", "b"})
more := collection.New([]string{"c", "d"})
merged2 := strs.Merge(more)
collection.Dump(merged2.Items())
// #[]string [
// 0 => "a" #string
// 1 => "b" #string
// 2 => "c" #string
// 3 => "d" #string
// ]
Example: structs - merging struct slices
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
moreUsers := []User{
{ID: 3, Name: "Carol"},
{ID: 4, Name: "Dave"},
}
merged3 := users.Merge(moreUsers)
collection.Dump(merged3.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// 2 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// 3 => #main.User {
// +ID => 4 #int
// +Name => "Dave" #string
// }
// ]
func (*Collection[T]) Multiply ¶
func (c *Collection[T]) Multiply(n int) *Collection[T]
Multiply creates `n` copies of all items in the collection and returns a new collection. @group Transformation @behavior mutable @fluent true
Example: integers
ints := collection.New([]int{1, 2})
out := ints.Multiply(3)
collection.Dump(out.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 1 #int
// 3 => 2 #int
// 4 => 1 #int
// 5 => 2 #int
// ]
Example: strings
strs := collection.New([]string{"a", "b"})
out2 := strs.Multiply(2)
collection.Dump(out2.Items())
// #[]string [
// 0 => "a" #string
// 1 => "b" #string
// 2 => "a" #string
// 3 => "b" #string
// ]
Example: structs
type User struct {
Name string
}
users := collection.New([]User{{Name: "Alice"}, {Name: "Bob"}})
out3 := users.Multiply(2)
collection.Dump(out3.Items())
// #[]main.User [
// 0 => #main.User {
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +Name => "Bob" #string
// }
// 2 => #main.User {
// +Name => "Alice" #string
// }
// 3 => #main.User {
// +Name => "Bob" #string
// }
// ]
Example: multiplying by zero or negative returns empty
none := ints.Multiply(0) collection.Dump(none.Items()) // #[]int [ // ]
func (*Collection[T]) None ¶
func (c *Collection[T]) None(fn func(T) bool) bool
None returns true if fn returns false for every item in the collection. If the collection is empty, None returns true. @group Querying @behavior readonly @fluent true
Example: integers – none even
c := collection.New([]int{1, 3, 5})
noneEven := c.None(func(v int) bool { return v%2 == 0 })
collection.Dump(noneEven)
// true #bool
Example: integers – some even
c2 := collection.New([]int{1, 2, 3})
noneEven2 := c2.None(func(v int) bool { return v%2 == 0 })
collection.Dump(noneEven2)
// false #bool
Example: empty collection
empty := collection.New([]int{})
none := empty.None(func(v int) bool { return v > 0 })
collection.Dump(none)
// true #bool
func (*Collection[T]) Partition ¶
func (c *Collection[T]) Partition(fn func(T) bool) (*Collection[T], *Collection[T])
Partition splits the collection into two new collections based on predicate fn. The first collection contains items where fn returns true; the second contains items where fn returns false. Order is preserved within each partition. @group Slicing @behavior immutable @fluent true
Example: integers - even/odd
nums := collection.New([]int{1, 2, 3, 4, 5})
evens, odds := nums.Partition(func(n int) bool {
return n%2 == 0
})
collection.Dump(evens.Items(), odds.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// ]
// #[]int [
// 0 => 1 #int
// 1 => 3 #int
// 2 => 5 #int
// ]
Example: strings - prefix match
words := collection.New([]string{"go", "gopher", "rust", "ruby"})
goWords, other := words.Partition(func(s string) bool {
return strings.HasPrefix(s, "go")
})
collection.Dump(goWords.Items(), other.Items())
// #[]string [
// 0 => "go" #string
// 1 => "gopher" #string
// ]
// #[]string [
// 0 => "rust" #string
// 1 => "ruby" #string
// ]
Example: structs - active vs inactive
type User struct {
Name string
Active bool
}
users := collection.New([]User{
{Name: "Alice", Active: true},
{Name: "Bob", Active: false},
{Name: "Carol", Active: true},
})
active, inactive := users.Partition(func(u User) bool {
return u.Active
})
collection.Dump(active.Items(), inactive.Items())
// #[]main.User [
// 0 => #main.User {
// +Name => "Alice" #string
// +Active => true #bool
// }
// 1 => #main.User {
// +Name => "Carol" #string
// +Active => true #bool
// }
// ]
// #[]main.User [
// 0 => #main.User {
// +Name => "Bob" #string
// +Active => false #bool
// }
// ]
func (*Collection[T]) Pipe ¶
func (c *Collection[T]) Pipe(fn func(*Collection[T]) any) any
Pipe passes the entire collection into the given function and returns the function's result. @group Transformation @behavior readonly @fluent true
This is useful for inline transformations, aggregations, or "exiting" a chain with a non-collection value.
Example: integers – computing a sum
c := collection.New([]int{1, 2, 3})
sum := c.Pipe(func(col *collection.Collection[int]) any {
total := 0
for _, v := range col.Items() {
total += v
}
return total
})
collection.Dump(sum)
// 6 #int
Example: strings – joining values
c2 := collection.New([]string{"a", "b", "c"})
joined := c2.Pipe(func(col *collection.Collection[string]) any {
out := ""
for _, v := range col.Items() {
out += v
}
return out
})
collection.Dump(joined)
// "abc" #string
Example: structs – extracting just the names
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
names := users.Pipe(func(col *collection.Collection[User]) any {
result := make([]string, 0, len(col.Items()))
for _, u := range col.Items() {
result = append(result, u.Name)
}
return result
})
collection.Dump(names)
// #[]string [
// 0 => "Alice" #string
// 1 => "Bob" #string
// ]
func (*Collection[T]) Pop ¶
func (c *Collection[T]) Pop() (T, *Collection[T])
Pop returns the last item and a new collection with that item removed. The original collection remains unchanged. @group Slicing @behavior mutable @fluent true
If the collection is empty, the zero value of T is returned along with an empty collection.
Example: integers
c := collection.New([]int{1, 2, 3})
item, rest := c.Pop()
collection.Dump(item, rest.Items())
// 3 #int
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// ]
Example: strings
c2 := collection.New([]string{"a", "b", "c"})
item2, rest2 := c2.Pop()
collection.Dump(item2, rest2.Items())
// "c" #string
// #[]string [
// 0 => "a" #string
// 1 => "b" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
item3, rest3 := users.Pop()
collection.Dump(item3, rest3.Items())
// #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// ]
Example: empty collection
empty := collection.New([]int{})
item4, rest4 := empty.Pop()
collection.Dump(item4, rest4.Items())
// 0 #int
// #[]int [
// ]
func (*Collection[T]) PopN ¶
func (c *Collection[T]) PopN(n int) (*Collection[T], *Collection[T])
PopN removes and returns the last n items as a new collection, and returns a second collection containing the remaining items. @group Slicing @behavior mutable @fluent true
The popped items are returned in reverse order, matching the behavior of repeated Pop() calls.
Example: integers – pop 2
c := collection.New([]int{1, 2, 3, 4})
popped, rest := c.PopN(2)
collection.Dump(popped.Items(), rest.Items())
// #[]int [
// 0 => 4 #int
// 1 => 3 #int
// ]
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// ]
Example: strings – pop 1
c2 := collection.New([]string{"a", "b", "c"})
popped2, rest2 := c2.PopN(1)
collection.Dump(popped2.Items(), rest2.Items())
// #[]string [
// 0 => "c" #string
// ]
// #[]string [
// 0 => "a" #string
// 1 => "b" #string
// ]
Example: structs – pop 2
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
})
popped3, rest3 := users.PopN(2)
collection.Dump(popped3.Items(), rest3.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// 1 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// ]
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// ]
Example: integers - n <= 0 → returns empty popped + original collection
c3 := collection.New([]int{1, 2, 3})
popped4, rest4 := c3.PopN(0)
collection.Dump(popped4.Items(), rest4.Items())
// #[]int [
// ]
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// ]
Example: strings - n exceeds length → all items popped, rest empty
c4 := collection.New([]string{"x", "y"})
popped5, rest5 := c4.PopN(10)
collection.Dump(popped5.Items(), rest5.Items())
// #[]string [
// 0 => "y" #string
// 1 => "x" #string
// ]
// #[]string [
// ]
func (*Collection[T]) Prepend ¶
func (c *Collection[T]) Prepend(values ...T) *Collection[T]
Prepend returns a new collection with the given values added to the *beginning* of the collection. @group Transformation @behavior mutable @fluent true
The original collection is not modified.
Example: integers
c := collection.New([]int{3, 4})
newC := c.Prepend(1, 2)
collection.Dump(newC.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// ]
Example: strings
letters := collection.New([]string{"c", "d"})
out := letters.Prepend("a", "b")
collection.Dump(out.Items())
// #[]string [
// 0 => "a" #string
// 1 => "b" #string
// 2 => "c" #string
// 3 => "d" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 2, Name: "Bob"},
})
out2 := users.Prepend(User{ID: 1, Name: "Alice"})
collection.Dump(out2.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 1 #int
// +Name => "Alice" #string
// }
// 1 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// ]
Example: integers - Prepending into an empty collection
empty := collection.New([]int{})
out3 := empty.Prepend(9, 8)
collection.Dump(out3.Items())
// #[]int [
// 0 => 9 #int
// 1 => 8 #int
// ]
Example: integers - Prepending no values → returns a copy of original
c2 := collection.New([]int{1, 2})
out4 := c2.Prepend()
collection.Dump(out4.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// ]
func (*Collection[T]) Push ¶
func (c *Collection[T]) Push(values ...T) *Collection[T]
Push returns a new collection with the given values appended. @group Transformation @behavior immutable @fluent true
Example: integers
nums := collection.New([]int{1, 2}).Push(3, 4)
nums.Dump()
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// ]
// Complex type (structs)
type User struct {
Name string
Age int
}
users := collection.New([]User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
}).Push(
User{Name: "Carol", Age: 40},
User{Name: "Dave", Age: 20},
)
users.Dump()
// #[]main.User [
// 0 => #main.User {
// +Name => "Alice" #string
// +Age => 30 #int
// }
// 1 => #main.User {
// +Name => "Bob" #string
// +Age => 25 #int
// }
// 2 => #main.User {
// +Name => "Carol" #string
// +Age => 40 #int
// }
// 3 => #main.User {
// +Name => "Dave" #string
// +Age => 20 #int
// }
// ]
func (*Collection[T]) Reduce ¶
func (c *Collection[T]) Reduce(initial T, fn func(T, T) T) T
Reduce collapses the collection into a single accumulated value. The accumulator has the same type T as the collection's elements. @group Aggregation @behavior readonly @fluent true
This is useful for computing sums, concatenations, aggregates, or any fold-style reduction.
Example: integers - sum
sum := collection.New([]int{1, 2, 3}).Reduce(0, func(acc, n int) int {
return acc + n
})
collection.Dump(sum)
// 6 #int
Example: strings
joined := collection.New([]string{"a", "b", "c"}).Reduce("", func(acc, s string) string {
return acc + s
})
collection.Dump(joined)
// "abc" #string
Example: structs
type Stats struct {
Count int
Sum int
}
stats := collection.New([]Stats{
{Count: 1, Sum: 10},
{Count: 1, Sum: 20},
{Count: 1, Sum: 30},
})
total := stats.Reduce(Stats{}, func(acc, s Stats) Stats {
acc.Count += s.Count
acc.Sum += s.Sum
return acc
})
collection.Dump(total)
// #main.Stats [
// +Count => 3 #int
// +Sum => 60 #int
// ]
func (*Collection[T]) Reverse ¶
func (c *Collection[T]) Reverse() *Collection[T]
Reverse reverses the order of items in the collection in place and returns the same collection for chaining. @group Ordering @behavior mutable @fluent true
This operation performs no allocations.
Example: integers
c := collection.New([]int{1, 2, 3, 4})
c.Reverse()
collection.Dump(c.Items())
// #[]int [
// 0 => 4 #int
// 1 => 3 #int
// 2 => 2 #int
// 3 => 1 #int
// ]
Example: strings – chaining
out := collection.New([]string{"a", "b", "c"}).
Reverse().
Append("d").
Items()
collection.Dump(out)
// #[]string [
// 0 => "c" #string
// 1 => "b" #string
// 2 => "a" #string
// 3 => "d" #string
// ]
Example: structs
type User struct {
ID int
}
users := collection.New([]User{
{ID: 1},
{ID: 2},
{ID: 3},
})
users.Reverse()
collection.Dump(users.Items())
// #[]collection.User [
// 0 => {ID:3} #collection.User
// 1 => {ID:2} #collection.User
// 2 => {ID:1} #collection.User
// ]
func (*Collection[T]) Shuffle ¶
func (c *Collection[T]) Shuffle() *Collection[T]
Shuffle randomly shuffles the items in the collection in place and returns the same collection for chaining. @group Ordering @behavior mutable @fluent true
This operation performs no allocations.
The shuffle uses an internal random source. Tests may override this source to achieve deterministic behavior.
Example: integers
c := collection.New([]int{1, 2, 3, 4, 5})
c.Shuffle()
collection.Dump(c.Items())
Example: strings – chaining
out := collection.New([]string{"a", "b", "c"}).
Shuffle().
Append("d").
Items()
collection.Dump(out)
Example: structs
type User struct {
ID int
}
users := collection.New([]User{
{ID: 1},
{ID: 2},
{ID: 3},
{ID: 4},
})
users.Shuffle()
collection.Dump(users.Items())
func (*Collection[T]) Skip ¶
func (c *Collection[T]) Skip(n int) *Collection[T]
Skip returns a new collection with the first n items skipped. If n is less than or equal to zero, Skip returns the full collection. If n is greater than or equal to the collection length, Skip returns an empty collection. @group Slicing @behavior immutable @fluent true
This operation performs no element allocations; it re-slices the underlying slice.
Example: integers
c := collection.New([]int{1, 2, 3, 4, 5})
out := c.Skip(2)
collection.Dump(out.Items())
// #[]int [
// 0 => 3 #int
// 1 => 4 #int
// 2 => 5 #int
// ]
Example: skip none
out2 := c.Skip(0) collection.Dump(out2.Items()) // #[]int [ // 0 => 1 #int // 1 => 2 #int // 2 => 3 #int // 3 => 4 #int // 4 => 5 #int // ]
Example: skip all
out3 := c.Skip(10) collection.Dump(out3.Items()) // #[]int []
Example: structs
type User struct {
ID int
}
users := collection.New([]User{
{ID: 1},
{ID: 2},
{ID: 3},
})
out4 := users.Skip(1)
collection.Dump(out4.Items())
// []main.User [
// 0 => #main.User {
// +ID => 2 #int
// }
// 1 => #main.User {
// +ID => 3 #int
// }
// ]
func (*Collection[T]) SkipLast ¶
func (c *Collection[T]) SkipLast(n int) *Collection[T]
SkipLast returns a new collection with the last n items skipped. If n is less than or equal to zero, SkipLast returns the full collection. If n is greater than or equal to the collection length, SkipLast returns an empty collection. @group Slicing @behavior immutable @fluent true
This operation performs no element allocations; it re-slices the underlying slice.
Example: integers
c := collection.New([]int{1, 2, 3, 4, 5})
out := c.SkipLast(2)
collection.Dump(out.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// ]
Example: skip none
out2 := c.SkipLast(0) collection.Dump(out2.Items()) // #[]int [ // 0 => 1 #int // 1 => 2 #int // 2 => 3 #int // 3 => 4 #int // 4 => 5 #int // ]
Example: skip all
out3 := c.SkipLast(10) collection.Dump(out3.Items()) // #[]int []
Example: structs
type User struct {
ID int
}
users := collection.New([]User{
{ID: 1},
{ID: 2},
{ID: 3},
})
out4 := users.SkipLast(1)
collection.Dump(out4.Items())
// #[]collection.User [
// 0 => {ID:1} #collection.User
// 1 => {ID:2} #collection.User
// ]
func (*Collection[T]) Sort ¶
func (c *Collection[T]) Sort(less func(a, b T) bool) *Collection[T]
Sort sorts the collection in place using the provided comparison function and returns the same collection for chaining. @group Ordering @behavior mutable @fluent true
The comparison function `less(a, b)` should return true if `a` should come before `b` in the sorted order.
This operation mutates the underlying slice (no allocation).
Example: integers
c := collection.New([]int{5, 1, 4, 2})
c.Sort(func(a, b int) bool { return a < b })
collection.Dump(c.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 4 #int
// 3 => 5 #int
// ]
Example: strings (descending)
c2 := collection.New([]string{"apple", "banana", "cherry"})
c2.Sort(func(a, b string) bool { return a > b })
collection.Dump(c2.Items())
// #[]string [
// 0 => "cherry" #string
// 1 => "banana" #string
// 2 => "apple" #string
// ]
Example: structs
type User struct {
Name string
Age int
}
users := collection.New([]User{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Carol", Age: 40},
})
// Sort by age ascending
users.Sort(func(a, b User) bool {
return a.Age < b.Age
})
collection.Dump(users.Items())
// #[]main.User [
// 0 => #main.User {
// +Name => "Bob" #string
// +Age => 25 #int
// }
// 1 => #main.User {
// +Name => "Alice" #string
// +Age => 30 #int
// }
// 2 => #main.User {
// +Name => "Carol" #string
// +Age => 40 #int
// }
// ]
func (*Collection[T]) Take ¶
func (c *Collection[T]) Take(n int) *Collection[T]
Take returns a new collection containing the first `n` items when n > 0, or the last `|n|` items when n < 0. @fluent true
If n exceeds the collection length, the entire collection is returned. If n == 0, an empty collection is returned.
Mirrors Laravel's take() semantics.
@group Slicing @behavior immutable Example: integers - take first 3
c1 := collection.New([]int{0, 1, 2, 3, 4, 5})
out1 := c1.Take(3)
collection.Dump(out1.Items())
// #[]int [
// 0 => 0 #int
// 1 => 1 #int
// 2 => 2 #int
// ]
Example: integers - take last 2 (negative n)
c2 := collection.New([]int{0, 1, 2, 3, 4, 5})
out2 := c2.Take(-2)
collection.Dump(out2.Items())
// #[]int [
// 0 => 4 #int
// 1 => 5 #int
// ]
Example: integers - n exceeds length → whole collection
c3 := collection.New([]int{10, 20})
out3 := c3.Take(10)
collection.Dump(out3.Items())
// #[]int [
// 0 => 10 #int
// 1 => 20 #int
// ]
Example: integers - zero → empty
c4 := collection.New([]int{1, 2, 3})
out4 := c4.Take(0)
collection.Dump(out4.Items())
// #[]int [
// ]
func (*Collection[T]) TakeLast ¶
func (c *Collection[T]) TakeLast(n int) *Collection[T]
TakeLast returns a new collection containing the last n items. If n is less than or equal to zero, TakeLast returns an empty collection. If n is greater than or equal to the collection length, TakeLast returns the full collection. @fluent true
This operation performs no element allocations; it re-slices the underlying slice. @group Slicing @behavior immutable Example: integers
c := collection.New([]int{1, 2, 3, 4, 5})
out := c.TakeLast(2)
collection.Dump(out.Items())
// #[]int [
// 0 => 4 #int
// 1 => 5 #int
// ]
Example: take none
out2 := c.TakeLast(0) collection.Dump(out2.Items()) // #[]int []
Example: take all
out3 := c.TakeLast(10) collection.Dump(out3.Items()) // #[]int [ // 0 => 1 #int // 1 => 2 #int // 2 => 3 #int // 3 => 4 #int // 4 => 5 #int // ]
Example: structs
type User struct {
ID int
}
users := collection.New([]User{
{ID: 1},
{ID: 2},
{ID: 3},
})
out4 := users.TakeLast(1)
collection.Dump(out4.Items())
// #[]collection.User [
// 0 => {ID:3} #collection.User
// ]
func (*Collection[T]) TakeUntilFn ¶
func (c *Collection[T]) TakeUntilFn(pred func(T) bool) *Collection[T]
TakeUntilFn returns items until the predicate function returns true. The matching item is NOT included. @group Slicing @behavior immutable @fluent true Example: integers - stop when value >= 3
c1 := collection.New([]int{1, 2, 3, 4})
out1 := c1.TakeUntilFn(func(v int) bool { return v >= 3 })
collection.Dump(out1.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// ]
Example: integers - predicate immediately true → empty result
c2 := collection.New([]int{10, 20, 30})
out2 := c2.TakeUntilFn(func(v int) bool { return v < 50 })
collection.Dump(out2.Items())
// #[]int [
// ]
Example: integers - no match → full list returned
c3 := collection.New([]int{1, 2, 3})
out3 := c3.TakeUntilFn(func(v int) bool { return v == 99 })
collection.Dump(out3.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// ]
func (*Collection[T]) Tap ¶
func (c *Collection[T]) Tap(fn func(*Collection[T])) *Collection[T]
Tap invokes fn with the collection pointer for side effects (logging, debugging, inspection) and returns the same collection to allow chaining. @group Transformation @behavior immutable @fluent true
Tap does NOT modify the collection itself; it simply exposes the current state during a fluent chain.
Example: integers - capture intermediate state during a chain
captured1 := []int{}
c1 := collection.New([]int{3, 1, 2}).
Sort(func(a, b int) bool { return a < b }). // → [1, 2, 3]
Tap(func(col *collection.Collection[int]) {
captured1 = append([]int(nil), col.Items()...) // snapshot copy
}).
Filter(func(v int) bool { return v >= 2 }).
Dump()
// #[]int [
// 0 => 2 #int
// 1 => 3 #int
// ]
// Use BOTH variables so nothing is "declared and not used"
collection.Dump(c1.Items())
collection.Dump(captured1)
// c1 → #[]int [2,3]
// captured1 → #[]int [1,2,3]
Example: integers - tap for debugging without changing flow
c2 := collection.New([]int{10, 20, 30}).
Tap(func(col *collection.Collection[int]) {
collection.Dump(col.Items())
}).
Filter(func(v int) bool { return v > 10 })
collection.Dump(c2.Items()) // ensures c2 is used
Example: structs - Tap with struct collection
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
})
users2 := users.Tap(func(col *collection.Collection[User]) {
collection.Dump(col.Items())
})
collection.Dump(users2.Items()) // ensures users2 is used
func (*Collection[T]) ToJSON ¶
func (c *Collection[T]) ToJSON() (string, error)
ToJSON converts the collection's items into a compact JSON string. @group Serialization @behavior readonly @fluent true
If marshalling succeeds, a JSON-encoded string and a nil error are returned. If marshalling fails, the method unwraps any json.Marshal wrapping so that user-defined MarshalJSON errors surface directly.
Returns:
- string: JSON-encoded representation of the collection
- error : nil on success, or the unwrapped marshalling error
Example: strings - pretty JSON
pj1 := collection.New([]string{"a", "b"})
out1, _ := pj1.ToJSON()
fmt.Println(out1)
// ["a","b"]
func (*Collection[T]) ToPrettyJSON ¶
func (c *Collection[T]) ToPrettyJSON() (string, error)
ToPrettyJSON converts the collection's items into a human-readable, indented JSON string. @group Serialization @behavior readonly @fluent true
If marshalling succeeds, a formatted JSON string and nil error are returned. If marshalling fails, the underlying error is unwrapped so user-defined MarshalJSON failures surface directly.
Returns:
- string: the pretty-printed JSON representation
- error : nil on success, or the unwrapped marshalling error
Example: strings - pretty JSON
pj1 := collection.New([]string{"a", "b"})
out1, _ := pj1.ToPrettyJSON()
fmt.Println(out1)
// [
// "a",
// "b"
// ]
func (*Collection[T]) Transform ¶
func (c *Collection[T]) Transform(fn func(T) T)
Transform applies fn to every item *in place*, mutating the collection. @group Transformation @behavior mutable @fluent true
This mirrors Laravel's transform(), which modifies the underlying values instead of returning a new collection.
Example: integers
c1 := collection.New([]int{1, 2, 3})
c1.Transform(func(v int) int { return v * 2 })
collection.Dump(c1.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// 2 => 6 #int
// ]
Example: strings
c2 := collection.New([]string{"a", "b", "c"})
c2.Transform(func(s string) string { return strings.ToUpper(s) })
collection.Dump(c2.Items())
// #[]string [
// 0 => "A" #string
// 1 => "B" #string
// 2 => "C" #string
// ]
Example: structs
type User struct {
ID int
Name string
}
c3 := collection.New([]User{
{ID: 1, Name: "alice"},
{ID: 2, Name: "bob"},
})
c3.Transform(func(u User) User {
u.Name = strings.ToUpper(u.Name)
return u
})
collection.Dump(c3.Items())
// #[]collection.User [
// 0 => {ID:1 Name:"ALICE"} #collection.User
// 1 => {ID:2 Name:"BOB"} #collection.User
// ]
func (*Collection[T]) Unique ¶
func (c *Collection[T]) Unique(eq func(a, b T) bool) *Collection[T]
Unique returns a new collection with duplicate items removed, based on the equality function `eq`. The first occurrence of each unique value is kept, and order is preserved. @group Set Operations @behavior immutable @fluent true
The `eq` function should return true when two values are considered equal.
Example: integers
c1 := collection.New([]int{1, 2, 2, 3, 4, 4, 5})
out1 := c1.Unique(func(a, b int) bool { return a == b })
collection.Dump(out1.Items())
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// 2 => 3 #int
// 3 => 4 #int
// 4 => 5 #int
// ]
Example: strings (case-insensitive uniqueness)
c2 := collection.New([]string{"A", "a", "B", "b", "A"})
out2 := c2.Unique(func(a, b string) bool {
return strings.EqualFold(a, b)
})
collection.Dump(out2.Items())
// #[]string [
// 0 => "A" #string
// 1 => "B" #string
// ]
Example: structs (unique by ID)
type User struct {
ID int
Name string
}
c3 := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 1, Name: "Alice Duplicate"},
})
out3 := c3.Unique(func(a, b User) bool {
return a.ID == b.ID
})
collection.Dump(out3.Items())
// #[]collection.User [
// 0 => {ID:1 Name:"Alice"} #collection.User
// 1 => {ID:2 Name:"Bob"} #collection.User
// ]
func (*Collection[T]) Where ¶ added in v1.3.0
func (c *Collection[T]) Where(fn func(T) bool) *Collection[T]
Where keeps only the elements for which fn returns true. This is an alias for Filter(fn) for SQL-style ergonomics. This method mutates the collection in place and returns the same instance. @group Slicing @behavior mutable @fluent true
Example: integers
nums := collection.New([]int{1, 2, 3, 4})
nums.Where(func(v int) bool {
return v%2 == 0
})
collection.Dump(nums.Items())
// #[]int [
// 0 => 2 #int
// 1 => 4 #int
// ]
Example: structs
type User struct {
ID int
Name string
}
users := collection.New([]User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Carol"},
})
users.Where(func(u User) bool {
return u.ID >= 2
})
collection.Dump(users.Items())
// #[]main.User [
// 0 => #main.User {
// +ID => 2 #int
// +Name => "Bob" #string
// }
// 1 => #main.User {
// +ID => 3 #int
// +Name => "Carol" #string
// }
// ]
type Number ¶
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64
}
Number is a constraint that permits any numeric type.
type NumericCollection ¶
type NumericCollection[T Number] struct { *Collection[T] }
NumericCollection is a Collection specialized for numeric types.
func NewNumeric ¶
func NewNumeric[T Number](items []T) *NumericCollection[T]
NewNumeric wraps a slice of numeric types in a NumericCollection. A shallow copy is made so that further operations don't mutate the original slice. @group Construction @behavior immutable @fluent true
func (*NumericCollection[T]) Avg ¶
func (c *NumericCollection[T]) Avg() float64
Avg returns the average of the collection values as a float64. If the collection is empty, Avg returns 0. @group Aggregation @behavior readonly @fluent false
Example: integers
c := collection.NewNumeric([]int{2, 4, 6})
collection.Dump(c.Avg())
// 4.000000 #float64
Example: float
c2 := collection.NewNumeric([]float64{1.5, 2.5, 3.0})
collection.Dump(c2.Avg())
// 2.333333 #float64
func (*NumericCollection[T]) Max ¶
func (c *NumericCollection[T]) Max() (T, bool)
Max returns the largest numeric item in the collection. The second return value is false if the collection is empty. @group Aggregation @behavior readonly @fluent false
Example: integers
c := collection.NewNumeric([]int{3, 1, 2})
max1, ok1 := c.Max()
collection.Dump(max1, ok1)
// 3 #int
// true #bool
Example: floats
c2 := collection.NewNumeric([]float64{1.5, 9.2, 4.4})
max2, ok2 := c2.Max()
collection.Dump(max2, ok2)
// 9.200000 #float64
// true #bool
Example: empty numeric collection
c3 := collection.NewNumeric([]int{})
max3, ok3 := c3.Max()
collection.Dump(max3, ok3)
// 0 #int
// false #bool
func (*NumericCollection[T]) Median ¶
func (c *NumericCollection[T]) Median() (float64, bool)
Median returns the statistical median of the numeric collection as float64. Returns (0, false) if the collection is empty. @group Aggregation @behavior readonly @fluent false
Odd count → middle value Even count → average of the two middle values
Example: integers - odd number of items
c := collection.NewNumeric([]int{3, 1, 2})
median1, ok1 := c.Median()
collection.Dump(median1, ok1)
// 2.000000 #float64
// true #bool
Example: integers - even number of items
c2 := collection.NewNumeric([]int{10, 2, 4, 6})
median2, ok2 := c2.Median()
collection.Dump(median2, ok2)
// 5.000000 #float64
// true #bool
Example: floats
c3 := collection.NewNumeric([]float64{1.1, 9.9, 3.3})
median3, ok3 := c3.Median()
collection.Dump(median3, ok3)
// 3.300000 #float64
// true #bool
Example: integers - empty numeric collection
c4 := collection.NewNumeric([]int{})
median4, ok4 := c4.Median()
collection.Dump(median4, ok4)
// 0.000000 #float64
// false #bool
func (*NumericCollection[T]) Min ¶
func (c *NumericCollection[T]) Min() (T, bool)
Min returns the smallest numeric item in the collection. The second return value is false if the collection is empty. @group Aggregation @behavior readonly @fluent false
Example: integers
c := collection.NewNumeric([]int{3, 1, 2})
min, ok := c.Min()
collection.Dump(min, ok)
// 1 #int
// true #bool
Example: floats
c2 := collection.NewNumeric([]float64{2.5, 9.1, 1.2})
min2, ok2 := c2.Min()
collection.Dump(min2, ok2)
// 1.200000 #float64
// true #bool
Example: integers - empty collection
empty := collection.NewNumeric([]int{})
min3, ok3 := empty.Min()
collection.Dump(min3, ok3)
// 0 #int
// false #bool
func (*NumericCollection[T]) Mode ¶
func (c *NumericCollection[T]) Mode() []T
Mode returns the most frequent numeric value(s) in the collection. If multiple values tie for highest frequency, all are returned in first-seen order. @group Aggregation @behavior readonly @fluent false
Example: integers – single mode
c := collection.NewNumeric([]int{1, 2, 2, 3})
mode := c.Mode()
collection.Dump(mode)
// #[]int [
// 0 => 2 #int
// ]
Example: integers – tie for mode
c2 := collection.NewNumeric([]int{1, 2, 1, 2})
mode2 := c2.Mode()
collection.Dump(mode2)
// #[]int [
// 0 => 1 #int
// 1 => 2 #int
// ]
Example: floats
c3 := collection.NewNumeric([]float64{1.1, 2.2, 1.1, 3.3})
mode3 := c3.Mode()
collection.Dump(mode3)
// #[]float64 [
// 0 => 1.100000 #float64
// ]
Example: integers - empty collection
empty := collection.NewNumeric([]int{})
mode4 := empty.Mode()
collection.Dump(mode4)
// <nil>
func (*NumericCollection[T]) Sum ¶
func (c *NumericCollection[T]) Sum() T
Sum returns the sum of all numeric items in the NumericCollection. If the collection is empty, Sum returns the zero value of T. @group Aggregation @behavior readonly @fluent false
Example: integers
c := collection.NewNumeric([]int{1, 2, 3})
total := c.Sum()
collection.Dump(total)
// 6 #int
Example: floats
c2 := collection.NewNumeric([]float64{1.5, 2.5})
total2 := c2.Sum()
collection.Dump(total2)
// 4.000000 #float64
Example: integers - empty collection
c3 := collection.NewNumeric([]int{})
total3 := c3.Sum()
collection.Dump(total3)
// 0 #int
type Pair ¶
type Pair[K comparable, V any] struct { Key K Value V }
Pair represents a key/value pair, typically originating from a map.
Pair is used to explicitly materialize unordered map data into an ordered collection workflow.
Source Files
¶
- after.go
- all.go
- any.go
- append.go
- at.go
- avg.go
- before.go
- chunk.go
- clone.go
- collection.go
- concat.go
- contains.go
- count.go
- count_by.go
- difference.go
- dump.go
- each.go
- filter.go
- find_where.go
- first.go
- first_where.go
- from_map.go
- group_by.go
- group_by_slice.go
- index_where.go
- intersect.go
- is_empty.go
- last.go
- last_where.go
- map.go
- max.go
- max_by.go
- median.go
- merge.go
- min.go
- min_by.go
- mode.go
- multiply.go
- none.go
- partition.go
- pipe.go
- pluck.go
- pop.go
- prepend.go
- reduce.go
- reverse.go
- shuffle.go
- skip.go
- skip_last.go
- sort.go
- sum.go
- symmetric_difference.go
- take.go
- take_last.go
- take_until.go
- tap.go
- times.go
- to_json.go
- to_map.go
- to_map_kv.go
- transform.go
- tuple.go
- union.go
- unique.go
- unique_by.go
- unique_comparable.go
- where.go
- window.go
- zip.go