Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ jobs:
- name: Run tests
run: go test ./... -coverprofile=coverage.out -covermode=atomic

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: coverage.out
fail_ci_if_error: false

- name: Validate coverage threshold
shell: bash
run: |
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Run tests
run: go test ./... -covermode=atomic

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

[![Go Reference](https://pkg.go.dev/badge/github.com/gocanto/collection.svg)](https://pkg.go.dev/github.com/gocanto/collection)
[![Go Version](https://img.shields.io/badge/go-1.25-blue.svg)](https://golang.org/doc/devel/release.html#go1.25)
[![CI](https://github.com/gocanto/collection/actions/workflows/ci.yml/badge.svg)](https://github.com/gocanto/collection/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/gocanto/collection/branch/main/graph/badge.svg)](https://codecov.io/gh/gocanto/collection)
[![Go Report Card](https://goreportcard.com/badge/github.com/gocanto/collection)](https://goreportcard.com/report/github.com/gocanto/collection)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A powerful Go port of [Laravel Collections](https://laravel.com/docs/collections) — fluent, type-safe, and powered by Go generics and `iter.Seq` lazy evaluation.
Expand Down
5 changes: 4 additions & 1 deletion arr/sorting.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,8 @@ func Random[T any](items []T, counts ...int) []T {
return shuffled
}

return shuffled[:count]
result := make([]T, count)
copy(result, shuffled[:count])

return result
}
43 changes: 41 additions & 2 deletions collection/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ func (c *Collection[T]) Pop(counts ...int) (T, bool) {

_ = count // For single pop
item := c.items[len(c.items)-1]

// Zero the removed element so the GC can reclaim it — without this,
// the backing array keeps a stale reference and the value is never collected.
// Reslicing in-place avoids the O(N) alloc+copy that a new slice would require.
var zero T

c.items[len(c.items)-1] = zero
c.items = c.items[:len(c.items)-1]

return item, true
Expand All @@ -82,7 +89,20 @@ func (c *Collection[T]) PopMany(count int) *Collection[T] {
}

idx := len(c.items) - count
popped := Collect(c.items[idx:])

items := make([]T, count)
copy(items, c.items[idx:])
popped := Collect(items)

// Zero the removed tail so the GC can reclaim those elements — without this,
// the backing array keeps stale references that are never collected.
// Reslicing in-place avoids the O(N) alloc+copy that a new slice would require.
var zero T

for i := idx; i < len(c.items); i++ {
c.items[i] = zero
}

c.items = c.items[:idx]

return popped
Expand All @@ -98,6 +118,13 @@ func (c *Collection[T]) Shift() (T, bool) {
}

item := c.items[0]

// Zero the removed element so the GC can reclaim it — without this,
// the backing array keeps a stale reference and the value is never collected.
// Reslicing in-place avoids the O(N) alloc+copy that a new slice would require.
var zero T

c.items[0] = zero
c.items = c.items[1:]

return item, true
Expand All @@ -112,7 +139,19 @@ func (c *Collection[T]) ShiftMany(count int) *Collection[T] {
return shifted
}

shifted := Collect(c.items[:count])
items := make([]T, count)
copy(items, c.items[:count])
shifted := Collect(items)

// Zero the removed head so the GC can reclaim those elements — without this,
// the backing array keeps stale references that are never collected.
// Reslicing in-place avoids the O(N) alloc+copy that a new slice would require.
var zero T

for i := 0; i < count; i++ {
c.items[i] = zero
}

c.items = c.items[count:]

return shifted
Expand Down
5 changes: 4 additions & 1 deletion collection/sorting.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,8 @@ func (c *Collection[T]) Random(counts ...int) *Collection[T] {
return shuffled
}

return Collect(shuffled.items[:count])
result := make([]T, count)
copy(result, shuffled.items[:count])

return Collect(result)
}
Loading