collection

package module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 1, 2026 License: MIT Imports: 8 Imported by: 0

README

goforj/collection logo

Fluent, Laravel-style Collections for Go - with generics, chainable pipelines, and expressive data transforms.

Go Reference Go Test Go version Latest tag Tests Go Report Card

collection brings an expressive, fluent API to Go. Iterate, filter, transform, sort, reduce, group, and debug your data with zero dependencies. Designed to feel natural to Go developers - and luxurious to everyone else.

Features

  • Fluent chaining - pipeline your operations like Laravel Collections
  • Fully generic (Collection[T]) - no reflection, no interface{}
  • Zero dependencies - pure Go, fast, lightweight
  • Minimal allocations - avoids unnecessary copies; most operations reuse the underlying slice
  • Map / Filter / Reduce - clean functional transforms
  • First / Last / Find / Contains helpers
  • Sort, GroupBy, Chunk, and more
  • Safe-by-default - defensive copies where appropriate
  • Built-in JSON helpers (ToJSON(), ToPrettyJSON())
  • Developer-friendly debug helpers (Dump(), Dd(), DumpStr())
  • Works with any Go type, including structs, pointers, and deeply nested composites

Fluent Chaining

Many methods return the collection itself, allowing for fluent method chaining.

Some methods maybe limited to due to go's generic constraints.

Fluent example:
examples/chaining/main.go

events := []DeviceEvent{
    {Device: "router-1", Region: "us-east", Errors: 3},
    {Device: "router-2", Region: "us-east", Errors: 15},
    {Device: "router-3", Region: "us-west", Errors: 22},
}

// Fluent slice pipeline
collection.
    New(events). // Construction
    Shuffle(). // Ordering
    Filter(func(e DeviceEvent) bool { return e.Errors > 5 }). // Slicing
    Sort(func(a, b DeviceEvent) bool { return a.Errors > b.Errors }). // Ordering
    Take(5). // Slicing
    TakeUntilFn(func(e DeviceEvent) bool { return e.Errors < 10 }). // Slicing (stop when predicate becomes true)
    SkipLast(1). // Slicing
    Dump() // Debugging

// []main.DeviceEvent [
//  0 => #main.DeviceEvent {
//    +Device => "router-3" #string
//    +Region => "us-west" #string
//    +Errors => 22 #int
//  }
// ]
Performance Benchmarks

lo is a fantastic library and a major inspiration for this project. Our focus differs: collection is built for fluent chaining with explicit mutability, which lets hot paths avoid intermediate allocations. That shows up most in chained pipelines and in-place operations where we can keep work on the same backing slice while still being explicit about behavior.

Op ns/op (vs lo) × bytes/op (vs lo) × allocs/op (vs lo)
All 232ns / 230ns 0B / 0B 0 / 0
Any 232ns / 234ns 0B / 0B 0 / 0
Chunk 128ns / 1.1µs 8.30x 1.3KB / 9.3KB 7.25x less 1 / 51
Contains 238ns / 250ns 1.05x 0B / 0B 0 / 0
CountBy 8.1µs / 8.2µs 9.4KB / 9.4KB 11 / 11
CountByValue 8.1µs / 8.1µs 9.4KB / 9.4KB 11 / 11
Difference 19.4µs / 44.5µs 2.29x 82.1KB / 108.8KB 1.33x less 12 / 43
Each 235ns / 230ns 0B / 0B 0 / 0
Filter 647ns / 1.1µs 1.67x 0B / 8.2KB ∞x less 0 / 1
Find 239ns / 235ns 0B / 0B 0 / 0
First 0ns / 0ns 0B / 0B 0 / 0
GroupBySlice 8.2µs / 8.3µs 21.0KB / 21.0KB 83 / 83
IndexWhere 232ns / 231ns 0B / 0B 0 / 0
Intersect 11.0µs / 10.8µs 11.4KB / 11.4KB 19 / 19
Last 0ns / 0ns 0B / 0B 0 / 0
Map 347ns / 821ns 2.37x 0B / 8.2KB ∞x less 0 / 1
Max 230ns / 231ns 0B / 0B 0 / 0
Min 232ns / 229ns 0B / 0B 0 / 0
None 232ns / 232ns 0B / 0B 0 / 0
Pipeline F→M→T→R 496ns / 1.3µs 2.62x 0B / 12.3KB ∞x less 0 / 2
Reduce (sum) 230ns / 231ns 0B / 0B 0 / 0
Reverse 216ns / 230ns 1.06x 0B / 0B 0 / 0
Shuffle 3.6µs / 5.3µs 1.49x 0B / 0B 0 / 0
Skip 0ns / 721ns 0B / 8.2KB ∞x less 0 / 1
SkipLast 0ns / 730ns 0B / 8.2KB ∞x less 0 / 1
Sum 232ns / 233ns 0B / 0B 0 / 0
Take 0ns / 0ns 0B / 0B 0 / 0
ToMap 7.7µs / 7.8µs 36.9KB / 37.0KB 5 / 6
Union 17.4µs / 17.7µs 90.3KB / 90.3KB 11 / 10
Unique 6.4µs / 6.5µs 45.1KB / 45.1KB 6 / 6
UniqueBy 6.9µs / 6.7µs 45.2KB / 45.1KB 7 / 6
Zip 1.4µs / 3.2µs 2.27x 16.4KB / 16.4KB 1 / 1
ZipWith 1.0µs / 3.1µs 3.07x 8.2KB / 8.2KB 1 / 1

How to read the benchmarks

  • means the two libraries are effectively equivalent
  • ∞x less means one side allocates while the other allocates nothing
  • Single-operation helpers are intentionally close in performance if not exceeds
  • Multi-step pipelines highlight the architectural difference

If you prefer immutable, one-off helpers - lo is outstanding. If you write expressive, chained data pipelines and care about hot-path performance - collection is built for that job.

Performance Philosophy

tl;dr: lo is excellent. We solve a different problem - and in chained pipelines, that difference matters.

lo is a fantastic library and a major inspiration for this project. It is battle-tested, idiomatic, and often the right choice when you want small, standalone helpers that operate on slices in isolation.

collection takes a different approach.

Rather than treating each operation as an independent transformation, collection is built around explicit, fluent pipelines. Many operations are designed to mutate the same backing slice intentionally, allowing chained workflows to avoid intermediate allocations and unnecessary copying - while still making that behavior visible and documented.

That design choice doesn't matter much for some single operations. It matters a lot once you start chaining and especially in hot paths.

Why chaining changes the performance story

Most functional helpers (including lo) operate like this:

input → Map → new slice → Filter → new slice → Take → new slice

That model is simple and safe - but each step typically allocates.

collection pipelines are designed to look more like this:

input → Filter (in place) → Sort (in place) → Take (slice view)

When you opt into mutation, the pipeline stays on the same backing array unless an operation explicitly documents that it allocates. The result is:

  • Fewer allocations
  • Less GC pressure
  • Lower end-to-end latency in hot paths
  • Much stronger scaling in multi-step pipelines

That's why the biggest deltas appear in benchmarks like:

  • Pipeline F→M→T→R
  • Map
  • Filter
  • Chunk
  • Zip / ZipWith
  • Skip / SkipLast

In these cases, collection can be 2×–30× faster and often reduce allocations to zero, not by doing "clever tricks", but by making mutation explicit and opt-in.

Explicit branching with Clone

Fluent pipelines don't mean you're locked into mutation.

When you want to branch a pipeline or preserve the original data, Clone() creates a shallow copy of the collection so subsequent operations are isolated and predictable.

events := collection.New(deviceEvents)

// Fast alerting path: cheap filters, early exit
alerts := events.
    Clone().
    Filter(func(e DeviceEvent) bool { return e.Severity >= Critical }).
    Take(10)

// Deeper analysis path: heavier work, full ordering
report := events.
    Clone().
    Filter(func(e DeviceEvent) bool { return e.Region == "us-east" }).
    Sort(func(a, b DeviceEvent) bool { return a.Timestamp.Before(b.Timestamp) })

This keeps the performance benefits of in-place operations where they matter, while making divergence points explicit and intentional.

No hidden copies. No surprises.

Design Principles

  • Type-safe: no reflection, no any leaks
  • Explicit semantics: order, mutation, and allocation are documented
  • Go-native: respects generics and stdlib patterns
  • Eager evaluation: no lazy pipelines or hidden concurrency
  • Maps are boundaries: unordered data is handled explicitly

What this library is not

  • Not a lazy or streaming library
  • Not concurrency-aware
  • Not immutable-by-default
  • Not a replacement for idiomatic loops in simple cases

Working with maps

Maps are unordered in Go. This library does not pretend otherwise.

Instead, map interaction is explicit and intentional:

  • FromMap materializes key/value pairs into an ordered workflow
  • ToMap reduces collections back into maps explicitly
  • ToMapKV provides a convenience for Pair[K,V]

This makes transitions between unordered and ordered data visible and honest.

Behavior semantics

Each method declares how it interacts with the collection:

  • readonly – reads data only, returns a derived value
  • immutable – returns a new collection, original unchanged
  • mutable – modifies the collection in place

Annotations describe observable behavior, not implementation details.

Runnable examples

Every function has a corresponding runnable example under ./examples.

These examples are generated directly from the documentation blocks of each function, ensuring the docs and code never drift. These are the same examples you see here in the README and GoDoc.

An automated test executes every example to verify it builds and runs successfully.

This guarantees all examples are valid, up-to-date, and remain functional as the API evolves.

Installation

go get github.com/goforj/collection

API Index

Group Functions
Access Items
Aggregation Avg Count CountBy CountByValue Max MaxBy Median Min MinBy Mode Reduce Sum
Construction Clone New NewNumeric
Debugging Dd Dump DumpStr
Grouping GroupBy GroupBySlice
Maps FromMap ToMap ToMapKV
Ordering After Before Reverse Shuffle Sort
Querying All Any At Contains FindWhere First FirstWhere IndexWhere IsEmpty Last LastWhere None
Serialization ToJSON ToPrettyJSON
Set Operations Difference Intersect SymmetricDifference Union Unique UniqueBy UniqueComparable
Slicing Chunk Filter Partition Pop PopN Skip SkipLast Take TakeLast TakeUntil TakeUntilFn Where Window
Transformation Append Concat Each Map MapTo Merge Multiply Pipe Pluck Prepend Push Tap Times Transform Zip ZipWith

Access

Items · readonly · fluent

Items returns the underlying slice of items.

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

Aggregation

Avg · readonly

Avg returns the average of the collection values as a float64. If the collection is empty, Avg returns 0.

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
Count · readonly · fluent

Count returns the total number of items in the collection.

count := collection.New([]int{1, 2, 3, 4}).Count()
collection.Dump(count)
// 4 #int
CountBy · readonly

CountBy returns a map of keys extracted by fn to their occurrence counts. K must be comparable.

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
// }
CountByValue · readonly

CountByValue returns a map where each distinct item in the collection is mapped to the number of times it appears.

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
// ]
Max · readonly

Max returns the largest numeric item in the collection. The second return value is false if the collection is empty.

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
MaxBy · readonly

MaxBy returns the item whose key (produced by keyFn) is the largest. The second return value is false if the collection is empty.

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
Median · readonly

Median returns the statistical median of the numeric collection as float64. Returns (0, false) if the collection is empty.

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
Min · readonly

Min returns the smallest numeric item in the collection. The second return value is false if the collection is empty.

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
MinBy · readonly

MinBy returns the item whose key (produced by keyFn) is the smallest. The second return value is false if the collection is empty.

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
Mode · readonly

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.

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>
Reduce · readonly · fluent

Reduce collapses the collection into a single accumulated value. The accumulator has the same type T as the collection's elements.

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
// ]
Sum · readonly

Sum returns the sum of all numeric items in the NumericCollection. If the collection is empty, Sum returns the zero value of T.

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

Construction

Clone · allocates · fluent

Clone returns a shallow copy of the collection.

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
// ]
New · immutable · fluent

New creates a new Collection from the provided slice.

NewNumeric · immutable · fluent

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.

Debugging

Dd · fluent

Dd prints items then terminates execution. Like Laravel's dd(), this is intended for debugging and should not be used in production control flow.

c := collection.New([]string{"a", "b"})
c.Dd()
// #[]string [
//   0 => "a" #string
//   1 => "b" #string
// ]
// Process finished with the exit code 1
Dump · readonly · fluent

Dump prints items with godump and returns the same collection. This is a no-op on the collection itself and never panics.

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

Example: integers

c2 := collection.New([]int{1, 2, 3})
collection.Dump(c2.Items())
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
// ]
DumpStr · readonly · fluent

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.

c := collection.New([]int{10, 20})
s := c.DumpStr()
fmt.Println(s)
// #[]int [
//   0 => 10 #int
//   1 => 20 #int
// ]

Grouping

GroupBy · readonly

GroupBy partitions the collection into groups keyed by the value returned from keyFn.

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
//  }
// ]
GroupBySlice · readonly

GroupBySlice partitions the collection into groups keyed by the value returned from keyFn.

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

Maps

FromMap · immutable · fluent

FromMap materializes a map into a collection of key/value pairs.

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
// ]
ToMap · readonly

ToMap reduces a collection into a map using the provided key and value selector functions.

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)
ToMapKV · readonly

ToMapKV converts a collection of key/value pairs into a map.

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

Ordering

After · immutable · fluent

After returns all items after the first element for which pred returns true. If no element matches, an empty collection is returned.

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
// ]
Before · immutable · fluent

Before returns a new collection containing all items that appear before the first element for which pred returns true.

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
// ]
Reverse · mutable · fluent

Reverse reverses the order of items in the collection in place and returns the same collection for chaining.

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
// ]
Shuffle · mutable · fluent

Shuffle randomly shuffles the items in the collection in place and returns the same collection for chaining.

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())
Sort · mutable · fluent

Sort sorts the collection in place using the provided comparison function and returns the same collection for chaining.

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

Querying

All · readonly · fluent

All returns true if fn returns true for every item in the collection. If the collection is empty, All returns true (vacuously 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
Any · readonly · fluent

Any returns true if at least one item satisfies fn.

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
At · readonly · fluent

At returns the item at the given index and a boolean indicating whether the index was within bounds.

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
Contains · readonly · fluent

Contains returns true if any item satisfies the predicate.

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
FindWhere · readonly · fluent

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.

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
First · readonly · fluent

First returns the first element in the collection. If the collection is empty, ok will be false.

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
FirstWhere · readonly · fluent

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.

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
IndexWhere · readonly · fluent

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).

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
IsEmpty · readonly · fluent

IsEmpty returns true if the collection has no items.

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
Last · readonly · fluent

Last returns the last element in the collection. If the collection is empty, ok will be false.

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
LastWhere · readonly · fluent

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.

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
None · readonly · fluent

None returns true if fn returns false for every item in the collection. If the collection is empty, None returns 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

Serialization

ToJSON · readonly · fluent

ToJSON converts the collection's items into a compact JSON string.

pj1 := collection.New([]string{"a", "b"})
out1, _ := pj1.ToJSON()
fmt.Println(out1)
// ["a","b"]
ToPrettyJSON · readonly · fluent

ToPrettyJSON converts the collection's items into a human-readable, indented JSON string.

pj1 := collection.New([]string{"a", "b"})
out1, _ := pj1.ToPrettyJSON()
fmt.Println(out1)
// [
//  "a",
//  "b"
// ]

Set Operations

Difference · immutable · fluent

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.

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
//   }
// ]
Intersect · immutable · fluent

Intersect returns a new collection containing elements from the second collection that are also present in the first.

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
//   }
// ]
SymmetricDifference · immutable · fluent

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.

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
//   }
// ]
Union · immutable · fluent

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.

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
//   }
// ]
Unique · immutable · fluent

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.

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
// ]
UniqueBy · immutable · fluent

UniqueBy returns a new collection containing only the first occurrence of each element as determined by keyFn.

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
// ]
UniqueComparable · immutable · fluent

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.

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

Slicing

Chunk · readonly · fluent

Chunk splits the collection into chunks of the given size. The final chunk may be smaller if len(items) is not divisible by size.

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
//    }
//  ]
//]
Filter · mutable · fluent

Filter keeps only the elements for which fn returns true. This method mutates the collection in place and returns the same instance.

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
//   }
// ]
Partition · immutable · fluent

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.

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
//   }
// ]
Pop · mutable · fluent

Pop returns the last item and a new collection with that item removed. The original collection remains unchanged.

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 [
// ]
PopN · mutable · fluent

PopN removes and returns the last n items as a new collection, and returns a second collection containing the remaining items.

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 [
// ]
Skip · immutable · fluent

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.

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
//  }
// ]
SkipLast · immutable · fluent

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.

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
// ]
Take · immutable · fluent

Take returns a new collection containing the first n items when n > 0, or the last |n| items when n < 0.

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 [
// ]
TakeLast · immutable · fluent

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.

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
// ]
TakeUntil · immutable · fluent

TakeUntil returns items until the first element equals value. The matching item is NOT included.

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 [
// ]
TakeUntilFn · immutable · fluent

TakeUntilFn returns items until the predicate function returns true. The matching item is NOT included.

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
// ]
Where · mutable · fluent

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.

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
//   }
// ]
Window · allocates · fluent

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.

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

Transformation

Append · immutable · fluent

Append returns a new collection with the given values appended.

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
//  }
// ]
Concat · mutable · fluent

Concat appends the values from the given slice onto the end of the collection,

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
// ]
Each · immutable · fluent

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.).

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
// ]
Map · immutable · fluent

Map applies a same-type transformation and returns a new collection.

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
//   }
// ]
MapTo · immutable · fluent

MapTo maps a Collection[T] to a Collection[R] using fn(T) 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
// ]
Merge · mutable · fluent

Merge merges the given data into the current collection.

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
//   }
// ]
Multiply · mutable · fluent

Multiply creates n copies of all items in the collection and returns a new collection.

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 [
// ]
Pipe · readonly · fluent

Pipe passes the entire collection into the given function and returns the function's result.

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
// ]
Pluck · immutable · fluent

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.

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
// ]
Prepend · mutable · fluent

Prepend returns a new collection with the given values added to the beginning of the collection.

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
// ]
Push · immutable · fluent

Push returns a new collection with the given values appended.

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
//  }
// ]
Tap · immutable · fluent

Tap invokes fn with the collection pointer for side effects (logging, debugging, inspection) and returns the same collection to allow chaining.

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
Times · immutable · fluent

Times creates a new collection by calling fn(i) for i = 1..count. This mirrors Laravel's Collection::times(), which is 1-indexed.

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
//	}
// ]
Transform · mutable · fluent

Transform applies fn to every item in place, mutating the 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
// ]
Zip · immutable · fluent

Zip combines two collections element-wise into a collection of tuples. The resulting length is the smaller of the two inputs.

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
//   }
// ]
ZipWith · immutable · fluent

ZipWith combines two collections element-wise using combiner fn. The resulting length is the smaller of the two inputs.

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

Documentation

Index

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.

type Tuple

type Tuple[A any, B any] struct {
	First  A
	Second B
}

Tuple is a generic ordered pair of values, used by helpers like Zip.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL