collection

package module
v4.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: MIT Imports: 6 Imported by: 0

README

goforj/collection logo

Fluent collections for Go. Iterate, filter, transform, sort, reduce, group, and debug data with a tiny dependency footprint.

Go Reference Go Test Go version Latest tag Tests

Features

  • Fluent chaining - pipeline your operations like Laravel Collections
  • Fully generic (Slice[T]) - the public collection API uses typed generic signatures; debugging helpers accept arbitrary values for inspection
  • Tiny dependency footprint - only godump for debugging helpers
  • Go-native slice access - use len, indexing, range, and ordinary slice conversions directly
  • Explicit ownership behavior - slice views, independent results, and in-place mutations are documented per operation
  • Map / Filter / Reduce - clean functional transforms
  • Generic methods - type-changing transforms remain fluent on Go 1.27+
  • First / Last / FirstWhere / IndexWhere helpers
  • Sort, GroupBy, Chunk, and more
  • Borrow-by-default - no defensive copies unless you ask for them
  • Standard-library interop - use slices, iter, and encoding/json directly
  • Developer-friendly debug helpers (Dump(), Dd(), DumpStr())
  • Works with any Go type, including structs, pointers, and deeply nested composites

Fluent Chaining

Many methods return a Slice, allowing fluent method chaining without a wrapper object.

Some methods may be limited 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},
    {Device: "router-4", Region: "us-west", Errors: 9},
    {Device: "router-5", Region: "eu-west", Errors: 7},
}

// Clone creates a top-level ownership boundary before the mutable stages.
collection.
    New(events). // Construction
    Clone(). // Construction
    Retain(func(e DeviceEvent) bool { return e.Errors > 5 }). // Mutation
    Sort(func(a, b DeviceEvent) bool { return a.Errors > b.Errors }). // Ordering
    Take(3). // Slicing
    TakeUntil(func(e DeviceEvent) bool { return e.Errors <= 9 }). // Slicing (stop when predicate becomes true)
    Reverse(). // Ordering
    Dump() // Debugging

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

Go 1.27 generic methods keep type-changing pipelines fluent:

regionsByPrefix := collection.New(events).
    Map(func(event DeviceEvent) string { return event.Region }).
    UniqueBy(func(region string) string { return region }).
    GroupBy(func(region string) string { return region[:2] })
fmt.Println(len(regionsByPrefix))
// 2

Performance Benchmarks

Equivalent operations are benchmarked against lo, with ownership and API differences labeled separately. The summary uses collection.New, which borrows its input; the full results also include explicit Clone costs.

Benchmark tables and methodology

lo is a major inspiration for this project.

Both libraries provide generic operations over ordinary slices. The main difference is API shape: collection.Slice adds receiver methods for eager fluent chains, while lo primarily uses free functions and provides mutable and iterator variants in separate packages. The tables label allocation, view, mutation, API, and different-work cases rather than treating them as equivalent.

The below tables are automatically generated from ./docs/bench/main.go.

Matched v2/v4 regression benchmarks for mutating, copied, and pipeline workloads live in ./docs/regression; its go.mod records the exact v2 baseline.

Full raw tables: see BENCHMARKS.md.

Read-only scalar ops
Op Speed vs lo Memory Allocs
All
Any
None
First below floor
Last below floor
FirstWhere same loop
IndexWhere
slices.Contains
Reduce (sum)
Sum
Min
Max
Each
Transforming ops
Op Speed vs lo Memory Allocs
Chunk view trade-off ownership trade-off ownership trade-off
Filter
Map
Take below floor
Skip view trade-off ownership trade-off ownership trade-off
SkipLast view trade-off ownership trade-off ownership trade-off
Zip 2.5x faster
ZipWith 3.1x faster
UniqueComparable
UniqueBy
Union
Intersect
Difference different work API trade-off API trade-off
GroupBy
CountBy
CountByValue
ToMap
Pipelines
Op Speed vs lo Memory Allocs
Pipeline F→M→T→R
Mutating ops
Op Speed vs lo Memory Allocs
Retain inconclusive
Reverse
Shuffle 3.7x faster
Transform
How to read the benchmarks
  • In Speed/Timing, means the median is inside ±10% (±15% in the condensed read-only scalar table)
  • below floor means both timings are under 50 ns, so no relative conclusion is drawn
  • inconclusive means the median is outside that band but paired samples did not consistently establish the difference
  • Nx faster/slower is calculated from the measured, unrounded medians and appears only when all paired samples establish the same direction; exact values remain machine- and build-specific
  • In Memory/Allocs, means both implementations produced the same measured result
  • same loop means both implementations compile to the same machine loop, so binary-placement skew is not presented as a library difference
  • Explicit memory deltas show allocation differences for equivalent work; ownership and API trade-offs are labeled separately
  • Single-operation helpers are expected to be close when they perform equivalent work
  • Multi-step pipelines show the cost of the selected ownership model

Allocation and mutation are explicit

Version 3 has one Go-native, slice-backed representation. Every Slice supports len, indexing, slicing, and range; each operation documents how it treats the backing array.

  • Map, Filter, Concat, and Prepend return independent results
  • Retain and Transform mutate in place; the shortened result from Retain must be captured
  • Sort, Reverse, and Shuffle mutate elements in place
  • View-producing slicing operations return capacity-capped views
  • Clone creates an intentional ownership boundary before mutation

The benchmark tables compare equivalent pure operations separately from Retain and Transform. Chunk, Skip, and SkipLast are ownership trade-offs because collection returns capacity-capped views while lo returns copied slices.

Explicit branching with Clone

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

New borrows slices by default. Use Clone() before an in-place operation when the original slice's element slots or order must remain unchanged.

When you want to branch a pipeline or preserve the original data, Clone() creates a shallow copy of the top-level slice. Subsequent operations that replace, reorder, or overwrite collection entries are isolated; Clone() does not deep-copy element values or values they reference.

events := collection.New(deviceEvents)

// In-place alert filtering with a bounded result
alerts := events.
    Clone().
    Retain(func(e DeviceEvent) bool { return e.Severity >= Critical }).
    Take(10)

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

This makes divergence points explicit and intentional.

Copying and mutation behavior is documented per operation.

Design Principles

  • Type-safe: the collection API uses typed generic signatures; debugging helpers are the exception for arbitrary-value inspection
  • 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
  • Not designed to hide allocation, mutation, or ordering semantics

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 a collection; its initial order is unspecified, so sort when a particular order matters
  • ToMap reduces collections back into maps explicitly

This makes map materialization visible and leaves deterministic ordering to an explicit Sort.

Behavior semantics

Each exported function and method declares how it interacts with the collection:

  • readonly - does not directly mutate the receiver's backing array; callbacks and debugging helpers can still have side effects
  • immutable - returns a value without mutating the receiver; individual method docs state whether it allocates or returns a view
  • mutable - may modify elements in the receiver's backing array
  • terminal - ends the fluent pipeline and returns a non-collection result

These annotations describe observable behavior, not implementation details.

Terminal operations do not return a Slice and cannot be chained further. They are designed to be allocation-free under New() where possible.

Ownership and copying are explicitly documented per operation. Some readonly or immutable operations may allocate internally when required (e.g. grouping, chunking, scratch copies), but never mutate the receiver.

Borrowed slices, independent results, in-place element mutation, and view semantics are intentional and visible.

Native slice interoperability

Slice[T] is a named slice, so ordinary Go operations work directly:

values := collection.New([]int{10, 20, 30})

fmt.Println(len(values))
// 3
fmt.Println(values[1])
// 20

for _, value := range values {
    fmt.Println(value)
}
// 10
// 20
// 30

Use slices.Values(values) or slices.All(values) when an iterator is useful; no collection-specific lazy wrapper is required.

Runnable examples

Every exported function and method has a corresponding runnable example under ./examples.

The checked-in examples are generated from GoDoc comments when the documentation is refreshed; they are intended to stay aligned with the README and GoDoc.

Automated checks build and execute the checked-in examples, then compare their output with the documentation.

This helps catch example regressions as the API evolves.

Installation

This package requires Go 1.27 or newer. Consumers that cannot upgrade their toolchain can remain on v2.

Existing users should read Migrating to v4 for the complete API and ownership changes.

go get github.com/goforj/collection/v4

API Index

Group Functions and methods
Aggregation Avg - CountBy - CountByValue - Max - MaxBy - Median - Min - MinBy - Mode - Reduce - Sum
Construction Clone - New
Debugging Dd - Dump - DumpStr - Slice.Dump
Grouping GroupBy
Maps FromMap - ToMap
Ordering After - Reverse - Shuffle - Sort
Querying All - Any - At - First - FirstWhere - IndexWhere - Last - LastWhere - None
Set Operations Difference - Intersect - SymmetricDifference - Union - Unique - UniqueBy - UniqueComparable
Slicing Chunk - Filter - Partition - Retain - Skip - SkipLast - Take - TakeLast - TakeUntil - Window
Transformation Concat - Each - Map - Multiply - Prepend - Tap - Times - Transform - Zip - ZipWith

Aggregation

Avg - readonly - terminal

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

Example: integers

collection.Dump(collection.Avg([]int{2, 4, 6}))
// 4.000000 #float64

Example: float

collection.Dump(collection.Avg([]float64{1.5, 2.5, 3.0}))
// 2.333333 #float64

CountBy - readonly - terminal

CountBy returns occurrence counts keyed by the extracted value.

numbers := collection.New([]int{1, 2, 3, 5})
counts := numbers.CountBy(func(number int) string {
	if number%2 == 0 {
		return "even"
	}
	return "odd"
})
collection.Dump(counts)
// #map[string]int {
//   even => 1 #int
//   odd => 3 #int
// }

CountByValue - readonly - terminal

CountByValue returns the number of occurrences of each distinct item in c.

T must be comparable.

collection.Dump(collection.CountByValue([]string{"go", "forj", "go"}))
// #map[string]int {
//   forj => 1 #int
//   go => 2 #int
// }

Max - readonly - terminal

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

Example: integers

values := []int{3, 1, 2}

max1, ok1 := collection.Max(values)
collection.Dump(max1, ok1)
// 3 #int
// true #bool

Example: floats

values2 := []float64{1.5, 9.2, 4.4}

max2, ok2 := collection.Max(values2)
collection.Dump(max2, ok2)
// 9.200000 #float64
// true #bool

Example: empty numeric slice

empty := []int{}

max3, ok3 := collection.Max(empty)
collection.Dump(max3, ok3)
// 0 #int
// false #bool

MaxBy - readonly - terminal

MaxBy returns the item whose extracted key is the largest.

words := collection.New([]string{"pear", "fig", "banana"})
longest, ok := words.MaxBy(func(word string) int {
	return len(word)
})
collection.Dump(longest, ok)
// "banana" #string
// true #bool

Median - readonly - terminal

Median returns the statistical median of a numeric slice as float64. It returns (0, false) if the slice is empty. Median copies the input before sorting, so it allocates O(n) storage and does not mutate the input slice.

  • Odd count: middle value.
  • Even count: average of the two middle values.

Example: integers - odd number of items

values := []int{3, 1, 2}

median1, ok1 := collection.Median(values)
collection.Dump(median1, ok1)
// 2.000000 #float64
// true #bool

Example: integers - even number of items

values2 := []int{10, 2, 4, 6}

median2, ok2 := collection.Median(values2)
collection.Dump(median2, ok2)
// 5.000000 #float64
// true #bool

Example: floats

values3 := []float64{1.1, 9.9, 3.3}

median3, ok3 := collection.Median(values3)
collection.Dump(median3, ok3)
// 3.300000 #float64
// true #bool

Example: integers - empty numeric slice

empty := []int{}

median4, ok4 := collection.Median(empty)
collection.Dump(median4, ok4)
// 0.000000 #float64
// false #bool

Min - readonly - terminal

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

Example: integers

values := []int{3, 1, 2}
min, ok := collection.Min(values)
collection.Dump(min, ok)
// 1 #int
// true #bool

Example: floats

values2 := []float64{2.5, 9.1, 1.2}
min2, ok2 := collection.Min(values2)
collection.Dump(min2, ok2)
// 1.200000 #float64
// true #bool

Example: integers - empty collection

empty := []int{}
min3, ok3 := collection.Min(empty)
collection.Dump(min3, ok3)
// 0 #int
// false #bool

MinBy - readonly - terminal

MinBy returns the item whose extracted key is the smallest.

words := collection.New([]string{"pear", "fig", "banana"})
shortest, ok := words.MinBy(func(word string) int {
	return len(word)
})
collection.Dump(shortest, ok)
// "fig" #string
// true #bool

Mode - readonly - terminal

Mode returns the most frequent numeric value or values in a slice. If multiple values tie for highest frequency, all are returned in first-seen order.

Example: integers - single mode

collection.Dump(collection.Mode([]int{1, 2, 2, 3}))
// #[]int [
//   0 => 2 #int
// ]

Example: integers - tie for mode

collection.Dump(collection.Mode([]int{1, 2, 1, 2}))
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
// ]

Example: floats

collection.Dump(collection.Mode([]float64{1.1, 2.2, 1.1, 3.3}))
// #[]float64 [
//   0 => 1.100000 #float64
// ]

Example: integers - empty collection

collection.Dump(collection.Mode([]int{}))
// []int(nil)

Reduce - readonly - terminal

Reduce collapses the collection into a single accumulated value. The accumulator may have a different type R from the collection's elements.

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

Sum - readonly - terminal

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

Example: integers

collection.Dump(collection.Sum([]int{1, 2, 3}))
// 6 #int

Example: floats

collection.Dump(collection.Sum([]float64{1.5, 2.5}))
// 4.000000 #float64

Example: integers - empty collection

collection.Dump(collection.Sum([]int{}))
// 0 #int

Construction

Clone - immutable - chainable

Clone returns a copy of the collection.

The returned collection has its own backing slice, so element assignments and slice operations on the clone do not affect the original collection. Clone is shallow: pointers, maps, slices, and other references stored in elements remain shared.

Clone is intended to be used when branching a pipeline while preserving the original collection.

Example: basic cloning

c := collection.New([]int{1, 2, 3})
clone := c.Clone()

clone.Transform(func(value int) int { return value * 10 })

collection.Dump(c)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
// ]

collection.Dump(clone)
// #[]int [
//   0 => 10 #int
//   1 => 20 #int
//   2 => 30 #int
// ]

Example: branching pipelines

base := collection.New([]int{1, 2, 3, 4, 5})

evens := base.Clone().Retain(func(v int) bool {
	return v%2 == 0
})

odds := base.Clone().Retain(func(v int) bool {
	return v%2 != 0
})

collection.Dump(base)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
//   3 => 4 #int
//   4 => 5 #int
// ]

collection.Dump(evens)
// #[]int [
//   0 => 2 #int
//   1 => 4 #int
// ]

collection.Dump(odds)
// #[]int [
//   0 => 1 #int
//   1 => 3 #int
//   2 => 5 #int
// ]

New - immutable - chainable

New creates a Slice from items and borrows their backing array.

values := collection.New([]int{10, 20, 30})
fmt.Println(len(values))
// 3
fmt.Println(values[1])
// 20

total := 0
for _, value := range values {
	total += value
}
fmt.Println(total)
// 60

Debugging

Dd - readonly - terminal

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

This method never returns.

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

Dump - readonly - terminal

Dump is a convenience function that calls godump.Dump.

collection.Dump(collection.New([]int{1, 2, 3}))
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
// ]

DumpStr - readonly - terminal

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.

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

Slice.Dump - readonly - chainable

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

Example: integers

collection.New([]int{1, 2, 3}).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
// ]

Grouping

GroupBy - readonly - terminal

GroupBy partitions this Slice into independent built-in slices keyed by the extracted value.

numbers := collection.New([]int{1, 2, 3, 4})
groups := numbers.GroupBy(func(number int) string {
	if number%2 == 0 {
		return "even"
	}
	return "odd"
})
collection.Dump(groups["even"], groups["odd"])
// #[]int [
//   0 => 2 #int
//   1 => 4 #int
// ]
// #[]int [
//   0 => 1 #int
//   1 => 3 #int
// ]
fmt.Println(len(groups["even"]))
// 2
fmt.Println(groups["odd"][0])
// 1
collection.Dump(groups["even"][:1])
// #[]int [
//   0 => 2 #int
// ]

Maps

FromMap - immutable - chainable

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

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)
c.Sort(func(a, b collection.Pair[string, int]) bool {
	return a.First < b.First
})
collection.Dump(c)
// #[]collection.Pair[string,int] [
//   0 => #collection.Pair[string,int] {
//     +First  => "a" #string
//     +Second => 1 #int
//   }
//   1 => #collection.Pair[string,int] {
//     +First  => "b" #string
//     +Second => 2 #int
//   }
//   2 => #collection.Pair[string,int] {
//     +First  => "c" #string
//     +Second => 3 #int
//   }
// ]

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.Second.Enabled
	}).
	Sort(func(a, b collection.Pair[string, Config]) bool {
		return a.First < b.First
	})

collection.Dump(out)
// #[]collection.Pair[string,main.Config·1] [
//   0 => #collection.Pair[string,main.Config·1] {
//     +First     => "router-1" #string
//     +Second    => #main.Config {
//       +Enabled => true #bool
//       +Timeout => 30 #int
//     }
//   }
//   1 => #collection.Pair[string,main.Config·1] {
//     +First     => "router-3" #string
//     +Second    => #main.Config {
//       +Enabled => true #bool
//       +Timeout => 45 #int
//     }
//   }
// ]

ToMap - readonly - terminal

ToMap reduces this collection into a map using the provided key and value functions. If multiple items produce the same key, the value derived from the last item wins.

words := collection.New([]string{"go", "forj"})
lengths := words.ToMap(
	func(word string) string { return word },
	func(word string) int { return len(word) },
)
collection.Dump(lengths)
// #map[string]int {
//   forj => 4 #int
//   go => 2 #int
// }

Ordering

After - immutable - chainable

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

NOTE: returns a view (shares backing array). Use Clone() to detach.

collection.New([]int{1, 2, 3, 4, 5}).After(func(v int) bool { return v == 3 }).Dump()
// #[]int [
//  0 => 4 #int
//  1 => 5 #int
// ]

Reverse - mutable - chainable

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

This operation performs no allocations.

Example: integers

c := collection.New([]int{1, 2, 3, 4})
c.Reverse()
collection.Dump(c)
// #[]int [
//   0 => 4 #int
//   1 => 3 #int
//   2 => 2 #int
//   3 => 1 #int
// ]

Example: strings - chaining

out := collection.New([]string{"a", "b", "c"}).
	Reverse().
	Concat([]string{"d"})

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)
// #[]main.User [
//   0 => #main.User {
//     +ID => 3 #int
//   }
//   1 => #main.User {
//     +ID => 2 #int
//   }
//   2 => #main.User {
//     +ID => 1 #int
//   }
// ]

Shuffle - mutable - chainable

Shuffle shuffles the collection in place and returns the same collection.

This operation mutates the receiver's backing slice.

Example: integers

c := collection.New([]int{1, 2, 3, 4, 5})
c.Shuffle()
fmt.Println(len(c), collection.Sum(c))
// 5 15

Example: strings - chaining

out2 := collection.New([]string{"a", "b", "c"}).
	Shuffle().
	Concat([]string{"d"})

fmt.Println(len(out2))
// 4

Example: structs

type User struct {
	ID int
}

users := collection.New([]User{
	{ID: 1},
	{ID: 2},
	{ID: 3},
	{ID: 4},
})

users.Shuffle()
fmt.Println(len(users))
// 4

Sort - mutable - chainable

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

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 and does not allocate a new element backing slice. The underlying sort implementation may make small internal allocations.

Example: integers

c := collection.New([]int{5, 1, 4, 2})
c.Sort(func(a, b int) bool { return a < b })
collection.Dump(c)
// #[]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)
// #[]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)
// #[]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 - terminal

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

collection.Dump(collection.New([]int{2, 4, 6}).All(func(v int) bool { return v%2 == 0 }))
// true #bool

Example: integers - not all even

collection.Dump(collection.New([]int{2, 3, 4}).All(func(v int) bool { return v%2 == 0 }))
// false #bool

Example: strings - all non-empty

collection.Dump(collection.New([]string{"a", "b", "c"}).All(func(s string) bool { return s != "" }))
// true #bool

Example: empty collection (vacuously true)

collection.Dump(collection.New([]int{}).All(func(v int) bool { return v > 0 }))
// true #bool

Any - readonly - terminal

Any returns true if at least one item satisfies fn.

collection.Dump(collection.New([]int{1, 2, 3, 4}).Any(func(v int) bool { return v%2 == 0 }))
// true #bool

At - readonly - terminal

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

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 #int
// true #bool

Example: out of bounds

v2, ok2 := c.At(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"},
})

u, ok3 := users.At(0)
collection.Dump(u, ok3)
// #main.User {
//   +ID   => 1 #int
//   +Name => "Alice" #string
// }
// true #bool

First - readonly - terminal

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 - terminal

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.

This method is equivalent to Laravel's collection->first(fn) and mirrors the behavior found in functional collections in other languages.

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 - terminal

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

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 #int
// true #bool

Example: not found

idx2, ok2 := c.IndexWhere(func(v int) bool { return v == 99 })
collection.Dump(idx2, 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: "Carol"},
})

idx3, ok3 := users.IndexWhere(func(u User) bool {
	return u.Name == "Bob"
})

collection.Dump(idx3, ok3)
// 1 #int
// true #bool

Last - readonly - terminal

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 - terminal

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 - terminal

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

collection.Dump(collection.New([]int{1, 3, 5}).None(func(v int) bool { return v%2 == 0 }))
// true #bool

Example: integers - some even

collection.Dump(collection.New([]int{1, 2, 3}).None(func(v int) bool { return v%2 == 0 }))
// false #bool

Example: empty collection

collection.Dump(collection.New([]int{}).None(func(v int) bool { return v > 0 }))
// true #bool

Set Operations

Difference - immutable - chainable

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

collection.Dump(collection.Difference(a, b))
// #[]int [
//   0 => 1 #int
//   1 => 3 #int
// ]

Example: strings

left := collection.New([]string{"apple", "banana", "cherry"})
right := collection.New([]string{"banana"})

collection.Dump(collection.Difference(left, right))
// #[]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"},
})

collection.Dump(collection.Difference(groupA, groupB))
// #[]main.User [
//   0 => #main.User {
//     +ID   => 1 #int
//     +Name => "Alice" #string
//   }
//   1 => #main.User {
//     +ID   => 3 #int
//     +Name => "Carol" #string
//   }
// ]

Intersect - immutable - chainable

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

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

collection.Dump(collection.Intersect(a, b))
// #[]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"})

collection.Dump(collection.Intersect(left, right))
// #[]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"},
})

collection.Dump(collection.Intersect(groupA, groupB))
// #[]main.User [
//   0 => #main.User {
//     +ID   => 2 #int
//     +Name => "Bob" #string
//   }
//   1 => #main.User {
//     +ID   => 3 #int
//     +Name => "Carol" #string
//   }
// ]

SymmetricDifference - immutable - chainable

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

collection.Dump(collection.SymmetricDifference(a, b))
// #[]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"})

collection.Dump(collection.SymmetricDifference(left, right))
// #[]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"},
})

collection.Dump(collection.SymmetricDifference(groupA, groupB))
// #[]main.User [
//   0 => #main.User {
//     +ID   => 1 #int
//     +Name => "Alice" #string
//   }
//   1 => #main.User {
//     +ID   => 3 #int
//     +Name => "Carol" #string
//   }
// ]

Union - immutable - chainable

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

collection.Dump(collection.Union(a, b))
// #[]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"})

collection.Dump(collection.Union(left, right))
// #[]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"},
})

collection.Dump(collection.Union(groupA, groupB))
// #[]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 - chainable

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.

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})
collection.Dump(c1.Unique(func(a, b int) bool { return a == b }))
// #[]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)
// #[]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)
// #[]main.User [
//  0 => #main.User {
//    +ID   => 1 #int
//    +Name => "Alice" #string
//  }
//  1 => #main.User {
//    +ID   => 2 #int
//    +Name => "Bob" #string
//  }
// ]

UniqueBy - immutable - chainable

UniqueBy returns a collection containing the first item for each extracted key.

words := collection.New([]string{"go", "up", "forj", "code"})
unique := words.UniqueBy(func(word string) int {
	return len(word)
})
collection.Dump(unique)
// #[]string [
//   0 => "go" #string
//   1 => "forj" #string
// ]

UniqueComparable - immutable - chainable

UniqueComparable returns a new collection with duplicate comparable items removed. The first occurrence of each value is kept, and order is preserved. It uses a map to track seen values, so it has expected linear time and allocates storage for both the map and the result.

Example: integers

collection.Dump(collection.UniqueComparable([]int{1, 2, 2, 3, 4, 4, 5}))
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
//   3 => 4 #int
//   4 => 5 #int
// ]

Example: strings

collection.Dump(collection.UniqueComparable([]string{"A", "a", "B", "B"}))
// #[]string [
//   0 => "A" #string
//   1 => "a" #string
//   2 => "B" #string
// ]

Slicing

Chunk - readonly - terminal

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

If size <= 0, nil is returned.

Chunk allocates the outer result slice. Each chunk is a capacity-capped view that shares the backing array with the source collection.

Example: integers

collection.Dump(collection.New([]int{1, 2, 3, 4, 5}).Chunk(2))
// #[][]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)
// #[][]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 - immutable - chainable

Filter keeps only the elements for which fn returns true.

Filter allocates a new Slice and leaves c and its backing storage unchanged.

Example: integers

source := collection.New([]int{1, 2, 3, 4})
filtered := source.Filter(func(v int) bool {
	return v%2 == 0
})
collection.Dump(filtered)
// #[]int [
//   0 => 2 #int
//   1 => 4 #int
// ]
fmt.Println(source[0])
// 1

Example: strings

c2 := collection.New([]string{"apple", "banana", "cherry", "avocado"})
c2 = c2.Filter(func(v string) bool {
	return strings.HasPrefix(v, "a")
})
collection.Dump(c2)
// #[]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 = users.Filter(func(u User) bool {
	return strings.HasPrefix(u.Name, "A")
})

collection.Dump(users)
// #[]main.User [
//   0 => #main.User {
//     +ID   => 1 #int
//     +Name => "Alice" #string
//   }
//   1 => #main.User {
//     +ID   => 3 #int
//     +Name => "Andrew" #string
//   }
// ]

Partition - immutable - terminal

Partition splits the collection into two new slices based on predicate fn. The first slice 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, odds)
// #[]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, other)
// #[]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, inactive)
// #[]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
//   }
// ]

Retain - mutable - chainable

Retain keeps items for which fn returns true in c's existing backing array.

Retain returns a capacity-capped, shortened slice header, so callers should retain its result when subsequent operations must observe the new length.

values := collection.New([]int{1, 2, 3, 4})
evens := values.Retain(func(value int) bool { return value%2 == 0 })
collection.Dump(evens)
// #[]int [
//   0 => 2 #int
//   1 => 4 #int
// ]
fmt.Println(values)
// [2 4 0 0]

Skip - immutable - chainable

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.

This operation performs no element allocations; it re-slices the underlying slice.

NOTE: returns a view (shares backing array). Use Clone() to detach.

Example: integers

c := collection.New([]int{1, 2, 3, 4, 5})
out := c.Skip(2)
collection.Dump(out)
// #[]int [
//   0 => 3 #int
//   1 => 4 #int
//   2 => 5 #int
// ]

Example: skip none

out2 := c.Skip(0)
collection.Dump(out2)
// #[]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)
// #[]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)
// #[]main.User [
//  0 => #main.User {
//    +ID => 2 #int
//  }
//  1 => #main.User {
//    +ID => 3 #int
//  }
// ]

SkipLast - immutable - chainable

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.

This operation performs no element allocations; it re-slices the underlying slice.

NOTE: returns a view (shares backing array). Use Clone() to detach.

Example: integers

c := collection.New([]int{1, 2, 3, 4, 5})
out := c.SkipLast(2)
collection.Dump(out)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
// ]

Example: skip none

out2 := c.SkipLast(0)
collection.Dump(out2)
// #[]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)
// #[]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)
// #[]main.User [
//  0 => #main.User {
//    +ID => 1 #int
//  }
//  1 => #main.User {
//    +ID => 2 #int
//  }
// ]

Take - immutable - chainable

Take returns a capacity-capped view containing the first n items.

If n exceeds the collection length, the entire collection is returned. If n == 0, an empty collection is returned.

NOTE: returns a view (shares backing array). Use Clone() to detach.

Example: integers - take first 3

c1 := collection.New([]int{0, 1, 2, 3, 4, 5})
out1 := c1.Take(3)
collection.Dump(out1)
// #[]int [
//	0 => 0 #int
//	1 => 1 #int
//	2 => 2 #int
// ]

Example: integers - n exceeds length → whole collection

c3 := collection.New([]int{10, 20})
out3 := c3.Take(10)
collection.Dump(out3)
// #[]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)
// #[]int [
// ]

TakeLast - immutable - chainable

TakeLast returns a capacity-capped view 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.

This operation performs no element allocations; it re-slices the underlying slice.

NOTE: returns a view (shares backing array). Use Clone() to detach.

Example: integers

c := collection.New([]int{1, 2, 3, 4, 5})
out := c.TakeLast(2)
collection.Dump(out)
// #[]int [
//   0 => 4 #int
//   1 => 5 #int
// ]

Example: take none

out2 := c.TakeLast(0)
collection.Dump(out2)
// #[]int [
// ]

Example: take all

out3 := c.TakeLast(10)
collection.Dump(out3)
// #[]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)
// #[]main.User [
//  0 => #main.User {
//    +ID => 3 #int
//  }
// ]

TakeUntil - immutable - chainable

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

NOTE: returns a view (shares backing array). Use Clone() to detach.

Example: integers - stop when value >= 3

c1 := collection.New([]int{1, 2, 3, 4})
out1 := c1.TakeUntil(func(v int) bool { return v >= 3 })
collection.Dump(out1)
// #[]int [
//	0 => 1 #int
//	1 => 2 #int
// ]

Example: integers - predicate immediately true → empty result

c2 := collection.New([]int{10, 20, 30})
out2 := c2.TakeUntil(func(v int) bool { return v < 50 })
collection.Dump(out2)
// #[]int [
// ]

Example: integers - no match → full list returned

c3 := collection.New([]int{1, 2, 3})
out3 := c3.TakeUntil(func(v int) bool { return v == 99 })
collection.Dump(out3)
// #[]int [
//	0 => 1 #int
//	1 => 2 #int
//	2 => 3 #int
// ]

Window - readonly - terminal

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.

Window allocates the outer result slice. Each window is a capacity-capped view that shares the backing array with the source collection.

Example: integers - step 1

collection.Dump(collection.New([]int{1, 2, 3, 4, 5}).Window(3, 1))
// #[][]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

collection.Dump(collection.New([]string{"a", "b", "c", "d", "e"}).Window(2, 2))
// #[][]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 := points.Window(2, 1)
collection.Dump(win3)
// #[][]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

Concat - immutable - chainable

Concat returns an independent collection containing c followed by values.

Callers must capture the returned Slice because a value receiver cannot extend c's slice header. The returned collection never shares backing storage with c.

Example: strings

c := collection.New([]string{"John Doe"})
concatenated := c.
	Concat([]string{"Jane Doe"}).
	Concat([]string{"Johnny Doe"})
collection.Dump(concatenated)
// #[]string [
//  0 => "John Doe" #string
//  1 => "Jane Doe" #string
//  2 => "Johnny Doe" #string
// ]

Example: spare capacity

backing := make([]int, 2, 4)
copy(backing, []int{1, 2})
values := collection.New(backing)
values = values.Concat([]int{3, 4})
fmt.Println(values)
// [1 2 3 4]

Each - readonly - chainable

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 - chainable

Map maps this Slice to a newly allocated Slice with a potentially different element type.

numbers := collection.New([]int{1, 2, 3, 4})
labels := numbers.Map(func(number int) string {
	if number%2 == 0 {
		return "even"
	}
	return "odd"
})
collection.Dump(labels)
// #[]string [
//   0 => "odd" #string
//   1 => "even" #string
//   2 => "odd" #string
//   3 => "even" #string
// ]
fmt.Println(numbers[0])
// 1

Multiply - immutable - chainable

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

Example: integers

ints := collection.New([]int{1, 2})
collection.Dump(ints.Multiply(3))
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 1 #int
//   3 => 2 #int
//   4 => 1 #int
//   5 => 2 #int
// ]

Example: strings

collection.Dump(collection.New([]string{"a", "b"}).Multiply(2))
// #[]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"}})
collection.Dump(users.Multiply(2))
// #[]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

collection.Dump(ints.Multiply(0))
// #[]int [
// ]

Prepend - immutable - chainable

Prepend returns an independently backed Slice containing values followed by c.

It allocates exactly enough storage for the result and leaves c unchanged.

Example: integers

c := collection.New([]int{3, 4})
result := c.Prepend(1, 2)
collection.Dump(result)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
//   3 => 4 #int
// ]

Example: strings

letters := collection.New([]string{"c", "d"})
result2 := letters.Prepend("a", "b")
collection.Dump(result2)
// #[]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"},
})

result3 := users.Prepend(User{ID: 1, Name: "Alice"})
collection.Dump(result3)
// #[]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{})
result4 := empty.Prepend(9, 8)
collection.Dump(result4)
// #[]int [
//   0 => 9 #int
//   1 => 8 #int
// ]

Example: integers - Prepending no values → no change

c2 := collection.New([]int{1, 2})
result5 := c2.Prepend()
collection.Dump(result5)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
// ]

Tap - mutable - chainable

Tap invokes fn with the Slice value for side effects such as logging, debugging, or inspection, then returns the Slice to allow chaining.

The callback receives a borrowed Slice and may mutate its elements. Use Clone before Tap when the original backing array must remain isolated. The slice header is passed by value, so reslicing, appending, or assigning a shortened Slice inside fn does not change the header returned by Tap.

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.Slice[int]) {
		captured1 = append([]int(nil), col...) // 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)
collection.Dump(captured1)
// #[]int [
//  0 => 2 #int
//  1 => 3 #int
// ]
// #[]int [
//  0 => 1 #int
//  1 => 2 #int
//  2 => 3 #int
// ]

Example: integers - tap for debugging without changing flow

c2 := collection.New([]int{10, 20, 30}).
	Tap(func(col collection.Slice[int]) {
		collection.Dump(col)
		// #[]int [
		//  0 => 10 #int
		//  1 => 20 #int
		//  2 => 30 #int
		// ]
	}).
	Filter(func(v int) bool { return v > 10 })

collection.Dump(c2) // ensures c2 is used
// #[]int [
//  0 => 20 #int
//  1 => 30 #int
// ]

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.Slice[User]) {
	collection.Dump(col)
	// #[]main.User [
	//  0 => #main.User {
	//    +ID   => 1 #int
	//    +Name => "Alice" #string
	//  }
	//  1 => #main.User {
	//    +ID   => 2 #int
	//    +Name => "Bob" #string
	//  }
	// ]
})

collection.Dump(users2) // ensures users2 is used
// #[]main.User [
//  0 => #main.User {
//    +ID   => 1 #int
//    +Name => "Alice" #string
//  }
//  1 => #main.User {
//    +ID   => 2 #int
//    +Name => "Bob" #string
//  }
// ]

Times - immutable - chainable

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

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)
// #[]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)
// #[]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)
// #[]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 - chainable

Transform applies a same-type transformation in place and returns the same collection.

Transform mutates the receiver's backing slice. Use Clone() if you need isolation.

Example: integers

c := collection.New([]int{1, 2, 3})

c.Transform(func(v int) int {
	return v * 10
})

collection.Dump(c)
// #[]int [
//   0 => 10 #int
//   1 => 20 #int
//   2 => 30 #int
// ]

Example: strings

c2 := collection.New([]string{"apple", "banana", "cherry"})

upper := c2.Transform(func(s string) string {
	return strings.ToUpper(s)
})

collection.Dump(upper)
// #[]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.Transform(func(u User) User {
	u.Name = strings.ToUpper(u.Name)
	return u
})

collection.Dump(updated)
// #[]main.User [
//   0 => #main.User {
//     +ID   => 1 #int
//     +Name => "ALICE" #string
//   }
//   1 => #main.User {
//     +ID   => 2 #int
//     +Name => "BOB" #string
//   }
// ]

Zip - immutable - terminal

Zip combines this collection with values element-wise into pairs. The resulting length is the smaller of the two inputs.

nums := collection.New([]int{1, 2, 3})
words := []string{"one", "two"}

out := nums.Zip(words)
collection.Dump(out)
// #[]collection.Pair[int,string] [
//   0 => #collection.Pair[int,string] {
//     +First  => 1 #int
//     +Second => "one" #string
//   }
//   1 => #collection.Pair[int,string] {
//     +First  => 2 #int
//     +Second => "two" #string
//   }
// ]

ZipWith - immutable - chainable

ZipWith combines this collection with a slice using fn up to the shorter length.

left := collection.New([]int{1, 2, 3})
right := collection.New([]int{10, 20})
sums := left.ZipWith(right, func(a, b int) int {
	return a + b
})
collection.Dump(sums)
// #[]int [
//   0 => 11 #int
//   1 => 22 #int
// ]

Development

Use make test for the root module, make vet for static checks, and make generate to refresh the generated README API reference. The docs and examples directories are separate Go modules and can be tested from their own directories when changed.

Documentation

Overview

Package collection provides fluent, explicit pipelines over slices.

New borrows slices by default, while pure transformations return independent results and view operations document their shared storage. Use Clone to make backing-array ownership independent explicitly; element cloning is shallow.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Avg

func Avg[S ~[]T, T Number](s S) float64

Avg returns the average of the numeric slice values as a float64. If the slice is empty, Avg returns 0. @group Aggregation @behavior readonly @chainable false @terminal true

Example: integers

collection.Dump(collection.Avg([]int{2, 4, 6}))
// 4.000000 #float64

Example: float

collection.Dump(collection.Avg([]float64{1.5, 2.5, 3.0}))
// 2.333333 #float64

func CountByValue

func CountByValue[S ~[]T, T comparable](c S) map[T]int

CountByValue returns the number of occurrences of each distinct item in c. @group Aggregation @behavior readonly @chainable false @terminal true

T must be comparable.

Example: strings

collection.Dump(collection.CountByValue([]string{"go", "forj", "go"}))
// #map[string]int {
//   forj => 1 #int
//   go => 2 #int
// }

func Dump

func Dump(vs ...any)

Dump is a convenience function that calls godump.Dump. @group Debugging @behavior readonly @chainable false @terminal true

Example: integers

collection.Dump(collection.New([]int{1, 2, 3}))
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
// ]

func Max

func Max[S ~[]T, T Number](s S) (T, bool)

Max returns the largest item in a numeric slice. The second return value is false if the slice is empty. @group Aggregation @behavior readonly @chainable false @terminal true

Example: integers

values := []int{3, 1, 2}

max1, ok1 := collection.Max(values)
collection.Dump(max1, ok1)
// 3 #int
// true #bool

Example: floats

values2 := []float64{1.5, 9.2, 4.4}

max2, ok2 := collection.Max(values2)
collection.Dump(max2, ok2)
// 9.200000 #float64
// true #bool

Example: empty numeric slice

empty := []int{}

max3, ok3 := collection.Max(empty)
collection.Dump(max3, ok3)
// 0 #int
// false #bool

func Median

func Median[S ~[]T, T Number](s S) (float64, bool)

Median returns the statistical median of a numeric slice as float64. It returns (0, false) if the slice is empty. Median copies the input before sorting, so it allocates O(n) storage and does not mutate the input slice. @group Aggregation @behavior readonly @chainable false @terminal true

- Odd count: middle value. - Even count: average of the two middle values.

Example: integers - odd number of items

values := []int{3, 1, 2}

median1, ok1 := collection.Median(values)
collection.Dump(median1, ok1)
// 2.000000 #float64
// true #bool

Example: integers - even number of items

values2 := []int{10, 2, 4, 6}

median2, ok2 := collection.Median(values2)
collection.Dump(median2, ok2)
// 5.000000 #float64
// true #bool

Example: floats

values3 := []float64{1.1, 9.9, 3.3}

median3, ok3 := collection.Median(values3)
collection.Dump(median3, ok3)
// 3.300000 #float64
// true #bool

Example: integers - empty numeric slice

empty := []int{}

median4, ok4 := collection.Median(empty)
collection.Dump(median4, ok4)
// 0.000000 #float64
// false #bool

func Min

func Min[S ~[]T, T Number](s S) (T, bool)

Min returns the smallest item in a numeric slice. The second return value is false if the slice is empty. @group Aggregation @behavior readonly @chainable false @terminal true

Example: integers

values := []int{3, 1, 2}
min, ok := collection.Min(values)
collection.Dump(min, ok)
// 1 #int
// true #bool

Example: floats

values2 := []float64{2.5, 9.1, 1.2}
min2, ok2 := collection.Min(values2)
collection.Dump(min2, ok2)
// 1.200000 #float64
// true #bool

Example: integers - empty collection

empty := []int{}
min3, ok3 := collection.Min(empty)
collection.Dump(min3, ok3)
// 0 #int
// false #bool

func Mode

func Mode[S ~[]T, T Number](items S) []T

Mode returns the most frequent numeric value or values in a slice. If multiple values tie for highest frequency, all are returned in first-seen order. @group Aggregation @behavior readonly @chainable false @terminal true

Example: integers - single mode

collection.Dump(collection.Mode([]int{1, 2, 2, 3}))
// #[]int [
//   0 => 2 #int
// ]

Example: integers - tie for mode

collection.Dump(collection.Mode([]int{1, 2, 1, 2}))
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
// ]

Example: floats

collection.Dump(collection.Mode([]float64{1.1, 2.2, 1.1, 3.3}))
// #[]float64 [
//   0 => 1.100000 #float64
// ]

Example: integers - empty collection

collection.Dump(collection.Mode([]int{}))
// []int(nil)

func Sum

func Sum[S ~[]T, T Number](s S) T

Sum returns the sum of all items in a numeric slice. If the slice is empty, Sum returns the zero value of T. @group Aggregation @behavior readonly @chainable false @terminal true

Example: integers

collection.Dump(collection.Sum([]int{1, 2, 3}))
// 6 #int

Example: floats

collection.Dump(collection.Sum([]float64{1.5, 2.5}))
// 4.000000 #float64

Example: integers - empty collection

collection.Dump(collection.Sum([]int{}))
// 0 #int

Types

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 Pair

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

Pair is an ordered pair of values, used by FromMap and Zip.

type Slice

type Slice[T any] []T

Slice is a named slice with fluent collection operations.

Because Slice is slice-backed, Go's built-in len, index, and range operations work directly on it. New borrows the supplied slice; use Clone when subsequent mutations must not share its backing array.

func Difference

func Difference[S1 ~[]T, S2 ~[]T, T comparable](a S1, b S2) Slice[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 @chainable true @terminal false

Example: integers

a := collection.New([]int{1, 2, 2, 3, 4})
b := collection.New([]int{2, 4})

collection.Dump(collection.Difference(a, b))
// #[]int [
//   0 => 1 #int
//   1 => 3 #int
// ]

Example: strings

left := collection.New([]string{"apple", "banana", "cherry"})
right := collection.New([]string{"banana"})

collection.Dump(collection.Difference(left, right))
// #[]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"},
})

collection.Dump(collection.Difference(groupA, groupB))
// #[]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) Slice[Pair[K, V]]

FromMap materializes a map into a collection of key/value pairs. @group Maps @behavior immutable @chainable true @terminal false

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)
c.Sort(func(a, b collection.Pair[string, int]) bool {
	return a.First < b.First
})
collection.Dump(c)
// #[]collection.Pair[string,int] [
//   0 => #collection.Pair[string,int] {
//     +First  => "a" #string
//     +Second => 1 #int
//   }
//   1 => #collection.Pair[string,int] {
//     +First  => "b" #string
//     +Second => 2 #int
//   }
//   2 => #collection.Pair[string,int] {
//     +First  => "c" #string
//     +Second => 3 #int
//   }
// ]

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.Second.Enabled
	}).
	Sort(func(a, b collection.Pair[string, Config]) bool {
		return a.First < b.First
	})

collection.Dump(out)
// #[]collection.Pair[string,main.Config·1] [
//   0 => #collection.Pair[string,main.Config·1] {
//     +First     => "router-1" #string
//     +Second    => #main.Config {
//       +Enabled => true #bool
//       +Timeout => 30 #int
//     }
//   }
//   1 => #collection.Pair[string,main.Config·1] {
//     +First     => "router-3" #string
//     +Second    => #main.Config {
//       +Enabled => true #bool
//       +Timeout => 45 #int
//     }
//   }
// ]

func Intersect

func Intersect[S1 ~[]T, S2 ~[]T, T comparable](a S1, b S2) Slice[T]

Intersect returns a new collection containing elements from the second collection that are also present in the first. @group Set Operations @behavior immutable @chainable true @terminal false

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

collection.Dump(collection.Intersect(a, b))
// #[]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"})

collection.Dump(collection.Intersect(left, right))
// #[]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"},
})

collection.Dump(collection.Intersect(groupA, groupB))
// #[]main.User [
//   0 => #main.User {
//     +ID   => 2 #int
//     +Name => "Bob" #string
//   }
//   1 => #main.User {
//     +ID   => 3 #int
//     +Name => "Carol" #string
//   }
// ]

func New

func New[T any](items []T) Slice[T]

New creates a Slice from items and borrows their backing array. @group Construction @behavior immutable @chainable true @terminal false

Example: native slice operations

values := collection.New([]int{10, 20, 30})
fmt.Println(len(values))
// 3
fmt.Println(values[1])
// 20

total := 0
for _, value := range values {
	total += value
}
fmt.Println(total)
// 60

func SymmetricDifference

func SymmetricDifference[S1 ~[]T, S2 ~[]T, T comparable](a S1, b S2) Slice[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 @chainable true @terminal false

Example: integers

a := collection.New([]int{1, 2, 3, 3})
b := collection.New([]int{3, 4, 4, 5})

collection.Dump(collection.SymmetricDifference(a, b))
// #[]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"})

collection.Dump(collection.SymmetricDifference(left, right))
// #[]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"},
})

collection.Dump(collection.SymmetricDifference(groupA, groupB))
// #[]main.User [
//   0 => #main.User {
//     +ID   => 1 #int
//     +Name => "Alice" #string
//   }
//   1 => #main.User {
//     +ID   => 3 #int
//     +Name => "Carol" #string
//   }
// ]

func Times

func Times[T any](count int, fn func(int) T) Slice[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 @chainable true @terminal false

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)
// #[]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)
// #[]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)
// #[]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[S1 ~[]T, S2 ~[]T, T comparable](a S1, b S2) Slice[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 @chainable true @terminal false

Example: integers

a := collection.New([]int{1, 2, 2, 3})
b := collection.New([]int{3, 4, 4, 5})

collection.Dump(collection.Union(a, b))
// #[]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"})

collection.Dump(collection.Union(left, right))
// #[]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"},
})

collection.Dump(collection.Union(groupA, groupB))
// #[]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 UniqueComparable

func UniqueComparable[S ~[]T, T comparable](c S) Slice[T]

UniqueComparable returns a new collection with duplicate comparable items removed. The first occurrence of each value is kept, and order is preserved. It uses a map to track seen values, so it has expected linear time and allocates storage for both the map and the result. @group Set Operations @behavior immutable @chainable true @terminal false

Example: integers

collection.Dump(collection.UniqueComparable([]int{1, 2, 2, 3, 4, 4, 5}))
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
//   3 => 4 #int
//   4 => 5 #int
// ]

Example: strings

collection.Dump(collection.UniqueComparable([]string{"A", "a", "B", "B"}))
// #[]string [
//   0 => "A" #string
//   1 => "a" #string
//   2 => "B" #string
// ]

func (Slice[T]) After

func (c Slice[T]) After(pred func(T) bool) Slice[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 @chainable true @terminal false

NOTE: returns a view (shares backing array). Use Clone() to detach.

Example: integers

collection.New([]int{1, 2, 3, 4, 5}).After(func(v int) bool { return v == 3 }).Dump()
// #[]int [
//  0 => 4 #int
//  1 => 5 #int
// ]

func (Slice[T]) All

func (c Slice[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 @chainable false @terminal true

Example: integers - all even

collection.Dump(collection.New([]int{2, 4, 6}).All(func(v int) bool { return v%2 == 0 }))
// true #bool

Example: integers - not all even

collection.Dump(collection.New([]int{2, 3, 4}).All(func(v int) bool { return v%2 == 0 }))
// false #bool

Example: strings - all non-empty

collection.Dump(collection.New([]string{"a", "b", "c"}).All(func(s string) bool { return s != "" }))
// true #bool

Example: empty collection (vacuously true)

collection.Dump(collection.New([]int{}).All(func(v int) bool { return v > 0 }))
// true #bool

func (Slice[T]) Any

func (c Slice[T]) Any(fn func(T) bool) bool

Any returns true if at least one item satisfies fn. @group Querying @behavior readonly @chainable false @terminal true Example: integers

collection.Dump(collection.New([]int{1, 2, 3, 4}).Any(func(v int) bool { return v%2 == 0 }))
// true #bool

func (Slice[T]) At

func (c Slice[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 @chainable false @terminal 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 #int
// true #bool

Example: out of bounds

v2, ok2 := c.At(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"},
})

u, ok3 := users.At(0)
collection.Dump(u, ok3)
// #main.User {
//   +ID   => 1 #int
//   +Name => "Alice" #string
// }
// true #bool

func (Slice[T]) Chunk

func (c Slice[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 @chainable false @terminal true

If size <= 0, nil is returned.

Chunk allocates the outer result slice. Each chunk is a capacity-capped view that shares the backing array with the source collection. Example: integers

collection.Dump(collection.New([]int{1, 2, 3, 4, 5}).Chunk(2))
// #[][]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)
// #[][]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 (Slice[T]) Clone

func (c Slice[T]) Clone() Slice[T]

Clone returns a copy of the collection.

The returned collection has its own backing slice, so element assignments and slice operations on the clone do not affect the original collection. Clone is shallow: pointers, maps, slices, and other references stored in elements remain shared.

Clone is intended to be used when branching a pipeline while preserving the original collection.

@group Construction @behavior immutable @chainable true @terminal false

Example: basic cloning

c := collection.New([]int{1, 2, 3})
clone := c.Clone()

clone.Transform(func(value int) int { return value * 10 })

collection.Dump(c)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
// ]

collection.Dump(clone)
// #[]int [
//   0 => 10 #int
//   1 => 20 #int
//   2 => 30 #int
// ]

Example: branching pipelines

base := collection.New([]int{1, 2, 3, 4, 5})

evens := base.Clone().Retain(func(v int) bool {
	return v%2 == 0
})

odds := base.Clone().Retain(func(v int) bool {
	return v%2 != 0
})

collection.Dump(base)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
//   3 => 4 #int
//   4 => 5 #int
// ]

collection.Dump(evens)
// #[]int [
//   0 => 2 #int
//   1 => 4 #int
// ]

collection.Dump(odds)
// #[]int [
//   0 => 1 #int
//   1 => 3 #int
//   2 => 5 #int
// ]

func (Slice[T]) Concat

func (c Slice[T]) Concat(values ...[]T) Slice[T]

Concat returns an independent collection containing c followed by values. @group Transformation @behavior immutable @chainable true @terminal false

Callers must capture the returned Slice because a value receiver cannot extend c's slice header. The returned collection never shares backing storage with c.

Example: strings

c := collection.New([]string{"John Doe"})
concatenated := c.
	Concat([]string{"Jane Doe"}).
	Concat([]string{"Johnny Doe"})
collection.Dump(concatenated)
// #[]string [
//  0 => "John Doe" #string
//  1 => "Jane Doe" #string
//  2 => "Johnny Doe" #string
// ]

Example: spare capacity

backing := make([]int, 2, 4)
copy(backing, []int{1, 2})
values := collection.New(backing)
values = values.Concat([]int{3, 4})
fmt.Println(values)
// [1 2 3 4]

func (Slice[T]) CountBy

func (c Slice[T]) CountBy[K comparable](keyFn func(T) K) map[K]int

CountBy returns occurrence counts keyed by the extracted value. @group Aggregation @behavior readonly @chainable false @terminal true

Example: count integers by parity

numbers := collection.New([]int{1, 2, 3, 5})
counts := numbers.CountBy(func(number int) string {
	if number%2 == 0 {
		return "even"
	}
	return "odd"
})
collection.Dump(counts)
// #map[string]int {
//   even => 1 #int
//   odd => 3 #int
// }

func (Slice[T]) Dd

func (c Slice[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 @behavior readonly @chainable false @terminal true

This method never returns.

Example: strings

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

func (Slice[T]) Dump

func (c Slice[T]) Dump() Slice[T]

Dump prints items with godump and returns the same collection. This is a no-op on the collection itself. @group Debugging @behavior readonly @chainable true @terminal false

Example: integers

collection.New([]int{1, 2, 3}).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 (Slice[T]) DumpStr

func (c Slice[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 @chainable false @terminal true

Example: integers

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

func (Slice[T]) Each

func (c Slice[T]) Each(fn func(T)) Slice[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 readonly @chainable true @terminal false

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 (Slice[T]) Filter

func (c Slice[T]) Filter(fn func(T) bool) Slice[T]

Filter keeps only the elements for which fn returns true.

Filter allocates a new Slice and leaves c and its backing storage unchanged. @group Slicing @behavior immutable @chainable true @terminal false Example: integers

source := collection.New([]int{1, 2, 3, 4})
filtered := source.Filter(func(v int) bool {
	return v%2 == 0
})
collection.Dump(filtered)
// #[]int [
//   0 => 2 #int
//   1 => 4 #int
// ]
fmt.Println(source[0])
// 1

Example: strings

c2 := collection.New([]string{"apple", "banana", "cherry", "avocado"})
c2 = c2.Filter(func(v string) bool {
	return strings.HasPrefix(v, "a")
})
collection.Dump(c2)
// #[]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 = users.Filter(func(u User) bool {
	return strings.HasPrefix(u.Name, "A")
})

collection.Dump(users)
// #[]main.User [
//   0 => #main.User {
//     +ID   => 1 #int
//     +Name => "Alice" #string
//   }
//   1 => #main.User {
//     +ID   => 3 #int
//     +Name => "Andrew" #string
//   }
// ]

func (Slice[T]) First

func (c Slice[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 @chainable false @terminal 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 (Slice[T]) FirstWhere

func (c Slice[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 @chainable false @terminal 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 (Slice[T]) GroupBy

func (c Slice[T]) GroupBy[K comparable](keyFn func(T) K) map[K][]T

GroupBy partitions this Slice into independent built-in slices keyed by the extracted value. @group Grouping @behavior readonly @chainable false @terminal true

Example: group integers by parity

numbers := collection.New([]int{1, 2, 3, 4})
groups := numbers.GroupBy(func(number int) string {
	if number%2 == 0 {
		return "even"
	}
	return "odd"
})
collection.Dump(groups["even"], groups["odd"])
// #[]int [
//   0 => 2 #int
//   1 => 4 #int
// ]
// #[]int [
//   0 => 1 #int
//   1 => 3 #int
// ]
fmt.Println(len(groups["even"]))
// 2
fmt.Println(groups["odd"][0])
// 1
collection.Dump(groups["even"][:1])
// #[]int [
//   0 => 2 #int
// ]

func (Slice[T]) IndexWhere

func (c Slice[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 @chainable false @terminal 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 #int
// true #bool

Example: not found

idx2, ok2 := c.IndexWhere(func(v int) bool { return v == 99 })
collection.Dump(idx2, 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: "Carol"},
})

idx3, ok3 := users.IndexWhere(func(u User) bool {
	return u.Name == "Bob"
})

collection.Dump(idx3, ok3)
// 1 #int
// true #bool

func (Slice[T]) Last

func (c Slice[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 @chainable false @terminal 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 (Slice[T]) LastWhere

func (c Slice[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 @chainable false @terminal 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 (Slice[T]) Map

func (c Slice[T]) Map[R any](fn func(T) R) Slice[R]

Map maps this Slice to a newly allocated Slice with a potentially different element type. @group Transformation @behavior immutable @chainable true @terminal false

Example: map integers to labels

numbers := collection.New([]int{1, 2, 3, 4})
labels := numbers.Map(func(number int) string {
	if number%2 == 0 {
		return "even"
	}
	return "odd"
})
collection.Dump(labels)
// #[]string [
//   0 => "odd" #string
//   1 => "even" #string
//   2 => "odd" #string
//   3 => "even" #string
// ]
fmt.Println(numbers[0])
// 1

func (Slice[T]) MaxBy

func (c Slice[T]) MaxBy[K Number | ~string](keyFn func(T) K) (T, bool)

MaxBy returns the item whose extracted key is the largest. @group Aggregation @behavior readonly @chainable false @terminal true

Example: longest string

words := collection.New([]string{"pear", "fig", "banana"})
longest, ok := words.MaxBy(func(word string) int {
	return len(word)
})
collection.Dump(longest, ok)
// "banana" #string
// true #bool

func (Slice[T]) MinBy

func (c Slice[T]) MinBy[K Number | ~string](keyFn func(T) K) (T, bool)

MinBy returns the item whose extracted key is the smallest. @group Aggregation @behavior readonly @chainable false @terminal true

Example: shortest string

words := collection.New([]string{"pear", "fig", "banana"})
shortest, ok := words.MinBy(func(word string) int {
	return len(word)
})
collection.Dump(shortest, ok)
// "fig" #string
// true #bool

func (Slice[T]) Multiply

func (c Slice[T]) Multiply(n int) Slice[T]

Multiply creates `n` copies of all items in the collection and returns a new collection. @group Transformation @behavior immutable @chainable true @terminal false

Example: integers

ints := collection.New([]int{1, 2})
collection.Dump(ints.Multiply(3))
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 1 #int
//   3 => 2 #int
//   4 => 1 #int
//   5 => 2 #int
// ]

Example: strings

collection.Dump(collection.New([]string{"a", "b"}).Multiply(2))
// #[]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"}})
collection.Dump(users.Multiply(2))
// #[]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

collection.Dump(ints.Multiply(0))
// #[]int [
// ]

func (Slice[T]) None

func (c Slice[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 @chainable false @terminal true

Example: integers - none even

collection.Dump(collection.New([]int{1, 3, 5}).None(func(v int) bool { return v%2 == 0 }))
// true #bool

Example: integers - some even

collection.Dump(collection.New([]int{1, 2, 3}).None(func(v int) bool { return v%2 == 0 }))
// false #bool

Example: empty collection

collection.Dump(collection.New([]int{}).None(func(v int) bool { return v > 0 }))
// true #bool

func (Slice[T]) Partition

func (c Slice[T]) Partition(fn func(T) bool) ([]T, []T)

Partition splits the collection into two new slices based on predicate fn. The first slice contains items where fn returns true; the second contains items where fn returns false. Order is preserved within each partition. @group Slicing @behavior immutable @chainable false @terminal 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, odds)
// #[]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, other)
// #[]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, inactive)
// #[]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 (Slice[T]) Prepend

func (c Slice[T]) Prepend(values ...T) Slice[T]

Prepend returns an independently backed Slice containing values followed by c. @group Transformation @behavior immutable @chainable true @terminal false

It allocates exactly enough storage for the result and leaves c unchanged.

Example: integers

c := collection.New([]int{3, 4})
result := c.Prepend(1, 2)
collection.Dump(result)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
//   3 => 4 #int
// ]

Example: strings

letters := collection.New([]string{"c", "d"})
result2 := letters.Prepend("a", "b")
collection.Dump(result2)
// #[]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"},
})

result3 := users.Prepend(User{ID: 1, Name: "Alice"})
collection.Dump(result3)
// #[]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{})
result4 := empty.Prepend(9, 8)
collection.Dump(result4)
// #[]int [
//   0 => 9 #int
//   1 => 8 #int
// ]

Example: integers - Prepending no values → no change

c2 := collection.New([]int{1, 2})
result5 := c2.Prepend()
collection.Dump(result5)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
// ]

func (Slice[T]) Reduce

func (c Slice[T]) Reduce[R any](initial R, fn func(R, T) R) R

Reduce collapses the collection into a single accumulated value. The accumulator may have a different type R from the collection's elements. @group Aggregation @behavior readonly @chainable false @terminal 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 (Slice[T]) Retain

func (c Slice[T]) Retain(fn func(T) bool) Slice[T]

Retain keeps items for which fn returns true in c's existing backing array. @group Slicing @behavior mutable @chainable true @terminal false

Retain returns a capacity-capped, shortened slice header, so callers should retain its result when subsequent operations must observe the new length.

Example: keep even integers without allocating another backing array

values := collection.New([]int{1, 2, 3, 4})
evens := values.Retain(func(value int) bool { return value%2 == 0 })
collection.Dump(evens)
// #[]int [
//   0 => 2 #int
//   1 => 4 #int
// ]
fmt.Println(values)
// [2 4 0 0]

func (Slice[T]) Reverse

func (c Slice[T]) Reverse() Slice[T]

Reverse reverses the order of items in the collection in place and returns the same collection for chaining. @group Ordering @behavior mutable @chainable true @terminal false

This operation performs no allocations.

Example: integers

c := collection.New([]int{1, 2, 3, 4})
c.Reverse()
collection.Dump(c)
// #[]int [
//   0 => 4 #int
//   1 => 3 #int
//   2 => 2 #int
//   3 => 1 #int
// ]

Example: strings - chaining

out := collection.New([]string{"a", "b", "c"}).
	Reverse().
	Concat([]string{"d"})

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)
// #[]main.User [
//   0 => #main.User {
//     +ID => 3 #int
//   }
//   1 => #main.User {
//     +ID => 2 #int
//   }
//   2 => #main.User {
//     +ID => 1 #int
//   }
// ]

func (Slice[T]) Shuffle

func (c Slice[T]) Shuffle() Slice[T]

Shuffle shuffles the collection in place and returns the same collection. @group Ordering @behavior mutable @chainable true @terminal false

This operation mutates the receiver's backing slice.

Example: integers

c := collection.New([]int{1, 2, 3, 4, 5})
c.Shuffle()
fmt.Println(len(c), collection.Sum(c))
// 5 15

Example: strings - chaining

out2 := collection.New([]string{"a", "b", "c"}).
	Shuffle().
	Concat([]string{"d"})

fmt.Println(len(out2))
// 4

Example: structs

type User struct {
	ID int
}

users := collection.New([]User{
	{ID: 1},
	{ID: 2},
	{ID: 3},
	{ID: 4},
})

users.Shuffle()
fmt.Println(len(users))
// 4

func (Slice[T]) Skip

func (c Slice[T]) Skip(n int) Slice[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 @chainable true @terminal false

This operation performs no element allocations; it re-slices the underlying slice.

NOTE: returns a view (shares backing array). Use Clone() to detach.

Example: integers

c := collection.New([]int{1, 2, 3, 4, 5})
out := c.Skip(2)
collection.Dump(out)
// #[]int [
//   0 => 3 #int
//   1 => 4 #int
//   2 => 5 #int
// ]

Example: skip none

out2 := c.Skip(0)
collection.Dump(out2)
// #[]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)
// #[]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)
// #[]main.User [
//  0 => #main.User {
//    +ID => 2 #int
//  }
//  1 => #main.User {
//    +ID => 3 #int
//  }
// ]

func (Slice[T]) SkipLast

func (c Slice[T]) SkipLast(n int) Slice[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 @chainable true @terminal false

This operation performs no element allocations; it re-slices the underlying slice.

NOTE: returns a view (shares backing array). Use Clone() to detach.

Example: integers

c := collection.New([]int{1, 2, 3, 4, 5})
out := c.SkipLast(2)
collection.Dump(out)
// #[]int [
//   0 => 1 #int
//   1 => 2 #int
//   2 => 3 #int
// ]

Example: skip none

out2 := c.SkipLast(0)
collection.Dump(out2)
// #[]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)
// #[]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)
// #[]main.User [
//  0 => #main.User {
//    +ID => 1 #int
//  }
//  1 => #main.User {
//    +ID => 2 #int
//  }
// ]

func (Slice[T]) Sort

func (c Slice[T]) Sort(less func(a, b T) bool) Slice[T]

Sort sorts the collection in place using the provided comparison function and returns the same collection for chaining. @group Ordering @behavior mutable @chainable true @terminal false

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 and does not allocate a new element backing slice. The underlying sort implementation may make small internal allocations.

Example: integers

c := collection.New([]int{5, 1, 4, 2})
c.Sort(func(a, b int) bool { return a < b })
collection.Dump(c)
// #[]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)
// #[]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)
// #[]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 (Slice[T]) Take

func (c Slice[T]) Take(n int) Slice[T]

Take returns a capacity-capped view containing the first n items.

If n exceeds the collection length, the entire collection is returned. If n == 0, an empty collection is returned.

NOTE: returns a view (shares backing array). Use Clone() to detach.

@group Slicing @behavior immutable @chainable true @terminal false Example: integers - take first 3

c1 := collection.New([]int{0, 1, 2, 3, 4, 5})
out1 := c1.Take(3)
collection.Dump(out1)
// #[]int [
//	0 => 0 #int
//	1 => 1 #int
//	2 => 2 #int
// ]

Example: integers - n exceeds length → whole collection

c3 := collection.New([]int{10, 20})
out3 := c3.Take(10)
collection.Dump(out3)
// #[]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)
// #[]int [
// ]

func (Slice[T]) TakeLast

func (c Slice[T]) TakeLast(n int) Slice[T]

TakeLast returns a capacity-capped view 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.

This operation performs no element allocations; it re-slices the underlying slice.

NOTE: returns a view (shares backing array). Use Clone() to detach. @group Slicing @behavior immutable @chainable true @terminal false Example: integers

c := collection.New([]int{1, 2, 3, 4, 5})
out := c.TakeLast(2)
collection.Dump(out)
// #[]int [
//   0 => 4 #int
//   1 => 5 #int
// ]

Example: take none

out2 := c.TakeLast(0)
collection.Dump(out2)
// #[]int [
// ]

Example: take all

out3 := c.TakeLast(10)
collection.Dump(out3)
// #[]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)
// #[]main.User [
//  0 => #main.User {
//    +ID => 3 #int
//  }
// ]

func (Slice[T]) TakeUntil

func (c Slice[T]) TakeUntil(pred func(T) bool) Slice[T]

TakeUntil returns items until the predicate function returns true. The matching item is NOT included. @group Slicing @behavior immutable @chainable true @terminal false

NOTE: returns a view (shares backing array). Use Clone() to detach. Example: integers - stop when value >= 3

c1 := collection.New([]int{1, 2, 3, 4})
out1 := c1.TakeUntil(func(v int) bool { return v >= 3 })
collection.Dump(out1)
// #[]int [
//	0 => 1 #int
//	1 => 2 #int
// ]

Example: integers - predicate immediately true → empty result

c2 := collection.New([]int{10, 20, 30})
out2 := c2.TakeUntil(func(v int) bool { return v < 50 })
collection.Dump(out2)
// #[]int [
// ]

Example: integers - no match → full list returned

c3 := collection.New([]int{1, 2, 3})
out3 := c3.TakeUntil(func(v int) bool { return v == 99 })
collection.Dump(out3)
// #[]int [
//	0 => 1 #int
//	1 => 2 #int
//	2 => 3 #int
// ]

func (Slice[T]) Tap

func (c Slice[T]) Tap(fn func(Slice[T])) Slice[T]

Tap invokes fn with the Slice value for side effects such as logging, debugging, or inspection, then returns the Slice to allow chaining. @group Transformation @behavior mutable @chainable true @terminal false

The callback receives a borrowed Slice and may mutate its elements. Use Clone before Tap when the original backing array must remain isolated. The slice header is passed by value, so reslicing, appending, or assigning a shortened Slice inside fn does not change the header returned by Tap.

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.Slice[int]) {
		captured1 = append([]int(nil), col...) // 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)
collection.Dump(captured1)
// #[]int [
//  0 => 2 #int
//  1 => 3 #int
// ]
// #[]int [
//  0 => 1 #int
//  1 => 2 #int
//  2 => 3 #int
// ]

Example: integers - tap for debugging without changing flow

c2 := collection.New([]int{10, 20, 30}).
	Tap(func(col collection.Slice[int]) {
		collection.Dump(col)
		// #[]int [
		//  0 => 10 #int
		//  1 => 20 #int
		//  2 => 30 #int
		// ]
	}).
	Filter(func(v int) bool { return v > 10 })

collection.Dump(c2) // ensures c2 is used
// #[]int [
//  0 => 20 #int
//  1 => 30 #int
// ]

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.Slice[User]) {
	collection.Dump(col)
	// #[]main.User [
	//  0 => #main.User {
	//    +ID   => 1 #int
	//    +Name => "Alice" #string
	//  }
	//  1 => #main.User {
	//    +ID   => 2 #int
	//    +Name => "Bob" #string
	//  }
	// ]
})

collection.Dump(users2) // ensures users2 is used
// #[]main.User [
//  0 => #main.User {
//    +ID   => 1 #int
//    +Name => "Alice" #string
//  }
//  1 => #main.User {
//    +ID   => 2 #int
//    +Name => "Bob" #string
//  }
// ]

func (Slice[T]) ToMap

func (c Slice[T]) ToMap[K comparable, V any](keyFn func(T) K, valueFn func(T) V) map[K]V

ToMap reduces this collection into a map using the provided key and value functions. If multiple items produce the same key, the value derived from the last item wins. @group Maps @behavior readonly @chainable false @terminal true

Example: index words by their value

words := collection.New([]string{"go", "forj"})
lengths := words.ToMap(
	func(word string) string { return word },
	func(word string) int { return len(word) },
)
collection.Dump(lengths)
// #map[string]int {
//   forj => 4 #int
//   go => 2 #int
// }

func (Slice[T]) Transform

func (c Slice[T]) Transform(fn func(T) T) Slice[T]

Transform applies a same-type transformation in place and returns the same collection. @group Transformation @behavior mutable @chainable true @terminal false

Transform mutates the receiver's backing slice. Use Clone() if you need isolation.

Example: integers

c := collection.New([]int{1, 2, 3})

c.Transform(func(v int) int {
	return v * 10
})

collection.Dump(c)
// #[]int [
//   0 => 10 #int
//   1 => 20 #int
//   2 => 30 #int
// ]

Example: strings

c2 := collection.New([]string{"apple", "banana", "cherry"})

upper := c2.Transform(func(s string) string {
	return strings.ToUpper(s)
})

collection.Dump(upper)
// #[]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.Transform(func(u User) User {
	u.Name = strings.ToUpper(u.Name)
	return u
})

collection.Dump(updated)
// #[]main.User [
//   0 => #main.User {
//     +ID   => 1 #int
//     +Name => "ALICE" #string
//   }
//   1 => #main.User {
//     +ID   => 2 #int
//     +Name => "BOB" #string
//   }
// ]

func (Slice[T]) Unique

func (c Slice[T]) Unique(eq func(a, b T) bool) Slice[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 @chainable true @terminal false

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})
collection.Dump(c1.Unique(func(a, b int) bool { return a == b }))
// #[]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)
// #[]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)
// #[]main.User [
//  0 => #main.User {
//    +ID   => 1 #int
//    +Name => "Alice" #string
//  }
//  1 => #main.User {
//    +ID   => 2 #int
//    +Name => "Bob" #string
//  }
// ]

func (Slice[T]) UniqueBy

func (c Slice[T]) UniqueBy[K comparable](keyFn func(T) K) Slice[T]

UniqueBy returns a collection containing the first item for each extracted key. @group Set Operations @behavior immutable @chainable true @terminal false

Example: keep the first word of each length

words := collection.New([]string{"go", "up", "forj", "code"})
unique := words.UniqueBy(func(word string) int {
	return len(word)
})
collection.Dump(unique)
// #[]string [
//   0 => "go" #string
//   1 => "forj" #string
// ]

func (Slice[T]) Window

func (c Slice[T]) Window(size int, step int) [][]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 readonly @chainable false @terminal true

Window allocates the outer result slice. Each window is a capacity-capped view that shares the backing array with the source collection.

Example: integers - step 1

collection.Dump(collection.New([]int{1, 2, 3, 4, 5}).Window(3, 1))
// #[][]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

collection.Dump(collection.New([]string{"a", "b", "c", "d", "e"}).Window(2, 2))
// #[][]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 := points.Window(2, 1)
collection.Dump(win3)
// #[][]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 (Slice[T]) Zip

func (c Slice[T]) Zip[U any](values []U) []Pair[T, U]

Zip combines this collection with values element-wise into pairs. The resulting length is the smaller of the two inputs. @group Transformation @behavior immutable @chainable false @terminal true

Example: integers and strings

nums := collection.New([]int{1, 2, 3})
words := []string{"one", "two"}

out := nums.Zip(words)
collection.Dump(out)
// #[]collection.Pair[int,string] [
//   0 => #collection.Pair[int,string] {
//     +First  => 1 #int
//     +Second => "one" #string
//   }
//   1 => #collection.Pair[int,string] {
//     +First  => 2 #int
//     +Second => "two" #string
//   }
// ]

func (Slice[T]) ZipWith

func (c Slice[T]) ZipWith[U, R any](other []U, fn func(T, U) R) Slice[R]

ZipWith combines this collection with a slice using fn up to the shorter length. @group Transformation @behavior immutable @chainable true @terminal false

Example: add corresponding integers

left := collection.New([]int{1, 2, 3})
right := collection.New([]int{10, 20})
sums := left.ZipWith(right, func(a, b int) int {
	return a + b
})
collection.Dump(sums)
// #[]int [
//   0 => 11 #int
//   1 => 22 #int
// ]

Jump to

Keyboard shortcuts

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