Date: Fri, 2 Jan 2026 01:43:58 -0600
Subject: [PATCH 17/30] docs: readme
---
README.md | 2 +-
prepend.go | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index cb96ca0..5ac7b9a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
diff --git a/prepend.go b/prepend.go
index 408d151..f4dd7e3 100644
--- a/prepend.go
+++ b/prepend.go
@@ -7,6 +7,7 @@ package collection
// @terminal false
//
// This method mutates the collection in place and returns the same instance.
+// It allocates a new backing slice to insert the values at the front.
//
// Example: integers
//
From 1c3d41d999e8d479746ea4fe95d1fdb80c1e04a7 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 01:44:17 -0600
Subject: [PATCH 18/30] docs: readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 5ac7b9a..b9e2c84 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
From 7840f4d4ef7ee359627656c4d99c966b2e014afb Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 01:58:19 -0600
Subject: [PATCH 19/30] docs: benchmarks
---
BENCHMARKS.md | 130 +++++++++++++++++++--------------------
README.md | 32 +++++-----
examples/shuffle/main.go | 10 +--
shuffle.go | 25 ++++----
shuffle_test.go | 10 +--
5 files changed, 102 insertions(+), 105 deletions(-)
diff --git a/BENCHMARKS.md b/BENCHMARKS.md
index df26246..ffc2965 100644
--- a/BENCHMARKS.md
+++ b/BENCHMARKS.md
@@ -4,74 +4,74 @@ Raw results for `collection.New` (borrowed) vs `lo`.
| Op | ns/op (vs lo) | × (faster) | bytes/op (vs lo) | × (less memory) | allocs/op (vs lo) |
|---:|----------------|:--:|------------------|:--:|--------------------|
-| **All** | 262ns / 240ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Any** | 256ns / 236ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Chunk** | 140ns / 1.1µs | **7.57x** | 1.3KB / 9.3KB | **7.12x less** | 2 / 51 |
-| **Contains** | 257ns / 237ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **CountBy** | 8.3µs / 8.5µs | ≈ | 9.4KB / 9.4KB | ≈ | 12 / 11 |
-| **CountByValue** | 8.6µs / 8.6µs | ≈ | 9.4KB / 9.4KB | ≈ | 12 / 11 |
-| **Difference** | 19.9µs / 45.1µs | **2.27x** | 82.2KB / 108.8KB | **1.32x less** | 14 / 43 |
-| **Each** | 258ns / 236ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Filter** | 658ns / 1.0µs | **1.57x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
+| **All** | 251ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Any** | 249ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Chunk** | 141ns / 1.1µs | **7.49x** | 1.3KB / 9.3KB | **7.12x less** | 2 / 51 |
+| **Contains** | 249ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **CountBy** | 8.2µs / 8.0µs | ≈ | 9.4KB / 9.4KB | ≈ | 12 / 11 |
+| **CountByValue** | 8.2µs / 8.1µs | ≈ | 9.4KB / 9.4KB | ≈ | 12 / 11 |
+| **Difference** | 19.1µs / 44.2µs | **2.31x** | 82.2KB / 108.8KB | **1.32x less** | 14 / 43 |
+| **Each** | 253ns / 231ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Filter** | 644ns / 1.1µs | **1.64x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
| **First** | 11ns / <1ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **FirstWhere** | 254ns / 236ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **GroupBySlice** | 8.2µs / 8.7µs | ≈ | 21.0KB / 21.0KB | ≈ | 84 / 83 |
-| **IndexWhere** | 278ns / 254ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Intersect** | 11.6µs / 10.9µs | ≈ | 11.5KB / 11.4KB | ≈ | 22 / 19 |
-| **Last** | 12ns / <1ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Map** | 366ns / 796ns | **2.18x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
-| **Max** | 260ns / 244ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
-| **Min** | 266ns / 247ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
-| **None** | 252ns / 237ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Pipeline F→M→T→R** | 502ns / 1.4µs | **2.70x** | 48B / 12.3KB | **256.00x less** | 2 / 2 |
-| **Reduce (sum)** | 256ns / 241ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Reverse** | 236ns / 239ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Shuffle** | 4.1µs / 5.7µs | **1.41x** | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Skip** | 12ns / 727ns | **59.96x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
-| **SkipLast** | 12ns / 717ns | **61.68x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
-| **Sum** | 262ns / 241ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
-| **Take** | 23ns / <1ns | ≈ | 48B / 0B | ∞x more | 2 / 0 |
-| **ToMap** | 7.9µs / 8.0µs | ≈ | 37.0KB / 37.0KB | ≈ | 6 / 6 |
-| **Union** | 18.0µs / 18.2µs | ≈ | 90.3KB / 90.3KB | ≈ | 13 / 10 |
-| **Unique** | 6.7µs / 6.7µs | ≈ | 45.2KB / 45.1KB | ≈ | 7 / 6 |
-| **UniqueBy** | 6.9µs / 6.7µs | ≈ | 45.2KB / 45.1KB | ≈ | 8 / 6 |
-| **Zip** | 1.5µs / 3.6µs | **2.44x** | 16.4KB / 16.4KB | ≈ | 3 / 1 |
-| **ZipWith** | 1.1µs / 3.3µs | **3.09x** | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **FirstWhere** | 249ns / 234ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **GroupBySlice** | 8.0µs / 8.4µs | ≈ | 21.0KB / 21.0KB | ≈ | 84 / 83 |
+| **IndexWhere** | 250ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Intersect** | 11.0µs / 10.7µs | ≈ | 11.5KB / 11.4KB | ≈ | 22 / 19 |
+| **Last** | 11ns / <1ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Map** | 358ns / 819ns | **2.29x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
+| **Max** | 254ns / 231ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
+| **Min** | 255ns / 236ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
+| **None** | 248ns / 234ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Pipeline F→M→T→R** | 504ns / 1.3µs | **2.53x** | 48B / 12.3KB | **256.00x less** | 2 / 2 |
+| **Reduce (sum)** | 253ns / 236ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Reverse** | 227ns / 235ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Shuffle** | 3.5µs / 5.5µs | **1.56x** | 24B / 0B | ∞x more | 1 / 0 |
+| **Skip** | 11ns / 726ns | **65.27x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
+| **SkipLast** | 11ns / 730ns | **64.89x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
+| **Sum** | 255ns / 236ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
+| **Take** | 22ns / <1ns | ≈ | 48B / 0B | ∞x more | 2 / 0 |
+| **ToMap** | 7.6µs / 8.0µs | ≈ | 37.0KB / 37.0KB | ≈ | 6 / 6 |
+| **Union** | 17.2µs / 17.9µs | ≈ | 90.3KB / 90.3KB | ≈ | 13 / 10 |
+| **Unique** | 6.4µs / 6.4µs | ≈ | 45.2KB / 45.1KB | ≈ | 7 / 6 |
+| **UniqueBy** | 6.8µs / 6.4µs | ≈ | 45.2KB / 45.1KB | ≈ | 8 / 6 |
+| **Zip** | 1.5µs / 3.2µs | **2.22x** | 16.4KB / 16.4KB | ≈ | 3 / 1 |
+| **ZipWith** | 1.0µs / 3.0µs | **2.87x** | 8.2KB / 8.2KB | ≈ | 3 / 1 |
Raw results for `collection.New().Clone()` (explicit copy) vs `lo`.
| Op | ns/op (vs lo) | × (faster) | bytes/op (vs lo) | × (less memory) | allocs/op (vs lo) |
|---:|----------------|:--:|------------------|:--:|--------------------|
-| **All** | 940ns / 242ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Any** | 906ns / 241ns | 0.27x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Chunk** | 856ns / 1.1µs | **1.25x** | 9.5KB / 9.3KB | ≈ | 4 / 51 |
-| **Contains** | 904ns / 242ns | 0.27x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **CountBy** | 9.1µs / 8.5µs | ≈ | 17.6KB / 9.4KB | 0.53x more | 14 / 11 |
-| **CountByValue** | 9.1µs / 8.4µs | ≈ | 17.6KB / 9.4KB | 0.53x more | 14 / 11 |
-| **Difference** | 21.1µs / 45.1µs | **2.14x** | 98.6KB / 108.8KB | **1.10x less** | 18 / 43 |
-| **Each** | 911ns / 234ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Filter** | 1.2µs / 1.0µs | 0.90x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
-| **First** | 739ns / <1ns | 0.00x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **FirstWhere** | 913ns / 241ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **GroupBySlice** | 8.9µs / 8.9µs | ≈ | 29.3KB / 21.0KB | 0.72x more | 86 / 83 |
-| **IndexWhere** | 916ns / 244ns | 0.27x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Intersect** | 12.3µs / 10.9µs | 0.89x | 27.9KB / 11.4KB | 0.41x more | 26 / 19 |
-| **Last** | 729ns / <1ns | 0.00x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Map** | 924ns / 803ns | 0.87x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
-| **Max** | 923ns / 236ns | 0.26x | 8.3KB / 0B | ∞x more | 5 / 0 |
-| **Min** | 930ns / 241ns | 0.26x | 8.3KB / 0B | ∞x more | 5 / 0 |
-| **None** | 946ns / 245ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Pipeline F→M→T→R** | 1.1µs / 1.3µs | **1.15x** | 8.3KB / 12.3KB | **1.49x less** | 4 / 2 |
-| **Reduce (sum)** | 908ns / 239ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Reverse** | 825ns / 238ns | 0.29x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Shuffle** | 4.5µs / 5.7µs | **1.26x** | 16.5KB / 0B | ∞x more | 5 / 0 |
-| **Skip** | 722ns / 723ns | ≈ | 8.2KB / 8.2KB | ≈ | 3 / 1 |
-| **SkipLast** | 728ns / 723ns | ≈ | 8.2KB / 8.2KB | ≈ | 3 / 1 |
-| **Sum** | 927ns / 241ns | 0.26x | 8.3KB / 0B | ∞x more | 5 / 0 |
-| **Take** | 734ns / <1ns | 0.00x | 8.3KB / 0B | ∞x more | 4 / 0 |
-| **ToMap** | 8.5µs / 8.2µs | ≈ | 45.2KB / 37.0KB | 0.82x more | 8 / 6 |
-| **Union** | 18.9µs / 18.2µs | ≈ | 106.8KB / 90.3KB | 0.85x more | 17 / 10 |
-| **Unique** | 7.2µs / 6.4µs | 0.89x | 53.4KB / 45.1KB | 0.85x more | 9 / 6 |
-| **UniqueBy** | 7.5µs / 6.5µs | 0.87x | 53.4KB / 45.1KB | 0.85x more | 10 / 6 |
-| **Zip** | 2.8µs / 3.3µs | **1.18x** | 32.9KB / 16.4KB | 0.50x more | 7 / 1 |
-| **ZipWith** | 2.4µs / 3.2µs | **1.33x** | 24.7KB / 8.2KB | 0.33x more | 7 / 1 |
+| **All** | 916ns / 234ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Any** | 920ns / 235ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Chunk** | 862ns / 1.1µs | **1.26x** | 9.5KB / 9.3KB | ≈ | 4 / 51 |
+| **Contains** | 924ns / 234ns | 0.25x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **CountBy** | 8.9µs / 8.6µs | ≈ | 17.6KB / 9.4KB | 0.53x more | 14 / 11 |
+| **CountByValue** | 9.0µs / 15.2µs | **1.68x** | 17.6KB / 9.4KB | 0.53x more | 14 / 11 |
+| **Difference** | 20.6µs / 44.8µs | **2.17x** | 98.6KB / 108.8KB | **1.10x less** | 18 / 43 |
+| **Each** | 913ns / 232ns | 0.25x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Filter** | 1.2µs / 1.0µs | 0.89x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **First** | 737ns / <1ns | 0.00x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **FirstWhere** | 934ns / 236ns | 0.25x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **GroupBySlice** | 8.7µs / 8.7µs | ≈ | 29.3KB / 21.0KB | 0.72x more | 86 / 83 |
+| **IndexWhere** | 925ns / 234ns | 0.25x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Intersect** | 12.2µs / 10.9µs | 0.90x | 27.9KB / 11.4KB | 0.41x more | 26 / 19 |
+| **Last** | 740ns / <1ns | 0.00x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Map** | 958ns / 816ns | 0.85x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **Max** | 930ns / 231ns | 0.25x | 8.3KB / 0B | ∞x more | 5 / 0 |
+| **Min** | 925ns / 236ns | 0.26x | 8.3KB / 0B | ∞x more | 5 / 0 |
+| **None** | 916ns / 236ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Pipeline F→M→T→R** | 1.1µs / 1.3µs | **1.21x** | 8.3KB / 12.3KB | **1.49x less** | 4 / 2 |
+| **Reduce (sum)** | 911ns / 236ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Reverse** | 841ns / 240ns | 0.29x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Shuffle** | 4.2µs / 5.7µs | **1.34x** | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Skip** | 771ns / 982ns | **1.27x** | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **SkipLast** | 2.2µs / 736ns | 0.33x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **Sum** | 918ns / 236ns | 0.26x | 8.3KB / 0B | ∞x more | 5 / 0 |
+| **Take** | 746ns / <1ns | 0.00x | 8.3KB / 0B | ∞x more | 4 / 0 |
+| **ToMap** | 8.3µs / 8.0µs | ≈ | 45.2KB / 37.0KB | 0.82x more | 8 / 6 |
+| **Union** | 18.6µs / 18.1µs | ≈ | 106.8KB / 90.3KB | 0.85x more | 17 / 10 |
+| **Unique** | 7.1µs / 6.4µs | ≈ | 53.4KB / 45.1KB | 0.85x more | 9 / 6 |
+| **UniqueBy** | 7.5µs / 6.5µs | 0.86x | 53.4KB / 45.1KB | 0.85x more | 10 / 6 |
+| **Zip** | 2.8µs / 3.3µs | **1.17x** | 32.9KB / 16.4KB | 0.50x more | 7 / 1 |
+| **ZipWith** | 2.4µs / 3.0µs | **1.26x** | 24.7KB / 8.2KB | 0.33x more | 7 / 1 |
diff --git a/README.md b/README.md
index b9e2c84..b662d66 100644
--- a/README.md
+++ b/README.md
@@ -115,17 +115,17 @@ Full raw tables: see `BENCHMARKS.md`.
| Op | Speed vs lo | Memory | Allocs |
|---:|:-----------:|:------:|:------:|
-| **Chunk** | **7.57x** | -8.0KB | -49 |
+| **Chunk** | **7.49x** | -8.0KB | -49 |
| **Take** | ≈ | +48B | +2 |
-| **Skip** | **59.96x** | -8.2KB | ≈ |
-| **SkipLast** | **61.68x** | -8.2KB | ≈ |
-| **Zip** | **2.44x** | +48B | +2 |
-| **ZipWith** | **3.09x** | +48B | +2 |
+| **Skip** | **65.27x** | -8.2KB | ≈ |
+| **SkipLast** | **64.89x** | -8.2KB | ≈ |
+| **Zip** | **2.22x** | +48B | +2 |
+| **ZipWith** | **2.87x** | +48B | +2 |
| **Unique** | ≈ | +24B | +1 |
| **UniqueBy** | ≈ | +48B | +2 |
| **Union** | ≈ | +72B | +3 |
| **Intersect** | ≈ | +72B | +3 |
-| **Difference** | **2.27x** | -26.7KB | -29 |
+| **Difference** | **2.31x** | -26.7KB | -29 |
| **GroupBySlice** | ≈ | +24B | +1 |
| **CountBy** | ≈ | +24B | +1 |
| **CountByValue** | ≈ | +24B | +1 |
@@ -135,16 +135,16 @@ Full raw tables: see `BENCHMARKS.md`.
| Op | Speed vs lo | Memory | Allocs |
|---:|:-----------:|:------:|:------:|
-| **Pipeline F→M→T→R** | **2.70x** | -12.2KB | ≈ |
+| **Pipeline F→M→T→R** | **2.53x** | -12.2KB | ≈ |
#### Mutating ops
| Op | Speed vs lo | Memory | Allocs |
|---:|:-----------:|:------:|:------:|
-| **Map** | **2.18x** | -8.2KB | ≈ |
-| **Filter** | **1.57x** | -8.2KB | ≈ |
+| **Map** | **2.29x** | -8.2KB | ≈ |
+| **Filter** | **1.64x** | -8.2KB | ≈ |
| **Reverse** | ≈ | +24B | +1 |
-| **Shuffle** | **1.41x** | +8.2KB | +3 |
+| **Shuffle** | **1.56x** | +24B | +1 |
## How to read the benchmarks
@@ -1470,16 +1470,16 @@ collection.Dump(users.Items())
// ]
```
-### Shuffle · immutable · chainable
+### Shuffle · mutable · chainable
-Shuffle returns a shuffled copy of the collection.
+Shuffle shuffles the collection in place and returns the same collection.
_Example: integers_
```go
c := collection.New([]int{1, 2, 3, 4, 5})
-out1 := c.Shuffle()
-collection.Dump(out1.Items())
+c.Shuffle()
+collection.Dump(c.Items())
```
_Example: strings – chaining_
@@ -1507,8 +1507,8 @@ users := collection.New([]User{
{ID: 4},
})
-out3 := users.Shuffle()
-collection.Dump(out3.Items())
+users.Shuffle()
+collection.Dump(users.Items())
```
### Sort · mutable · chainable
diff --git a/examples/shuffle/main.go b/examples/shuffle/main.go
index 90f4da6..7735c4a 100644
--- a/examples/shuffle/main.go
+++ b/examples/shuffle/main.go
@@ -6,12 +6,12 @@ package main
import "github.com/goforj/collection"
func main() {
- // Shuffle returns a shuffled copy of the collection.
+ // Shuffle shuffles the collection in place and returns the same collection.
// Example: integers
c := collection.New([]int{1, 2, 3, 4, 5})
- out1 := c.Shuffle()
- collection.Dump(out1.Items())
+ c.Shuffle()
+ collection.Dump(c.Items())
// Example: strings – chaining
out2 := collection.New([]string{"a", "b", "c"}).
@@ -33,6 +33,6 @@ func main() {
{ID: 4},
})
- out3 := users.Shuffle()
- collection.Dump(out3.Items())
+ users.Shuffle()
+ collection.Dump(users.Items())
}
diff --git a/shuffle.go b/shuffle.go
index 56ba341..3c021c7 100644
--- a/shuffle.go
+++ b/shuffle.go
@@ -15,13 +15,13 @@ func setShuffleRand(r *rand.Rand) {
shuffleRand = r
}
-// Shuffle returns a shuffled copy of the collection.
+// Shuffle shuffles the collection in place and returns the same collection.
// @group Ordering
-// @behavior immutable
+// @behavior mutable
// @chainable true
// @terminal false
//
-// This operation allocates a new slice and does not mutate the receiver.
+// This operation mutates the receiver's backing slice.
//
// The shuffle uses an internal random source. Tests may override
// this source to achieve deterministic behavior.
@@ -29,8 +29,8 @@ func setShuffleRand(r *rand.Rand) {
// Example: integers
//
// c := collection.New([]int{1, 2, 3, 4, 5})
-// out1 := c.Shuffle()
-// collection.Dump(out1.Items())
+// c.Shuffle()
+// collection.Dump(c.Items())
//
// Example: strings – chaining
//
@@ -54,19 +54,16 @@ func setShuffleRand(r *rand.Rand) {
// {ID: 4},
// })
//
-// out3 := users.Shuffle()
-// collection.Dump(out3.Items())
+// users.Shuffle()
+// collection.Dump(users.Items())
func (c *Collection[T]) Shuffle() *Collection[T] {
- items := c.items
- out := make([]T, len(items))
- copy(out, items)
- n := len(out)
+ n := len(c.items)
- // Fisher–Yates shuffle (in place on the copy)
+ // Fisher–Yates shuffle (in place)
for i := n - 1; i > 0; i-- {
j := shuffleRand.Intn(i + 1)
- out[i], out[j] = out[j], out[i]
+ c.items[i], c.items[j] = c.items[j], c.items[i]
}
- return New(out)
+ return c
}
diff --git a/shuffle_test.go b/shuffle_test.go
index 6f85aef..aa989fe 100644
--- a/shuffle_test.go
+++ b/shuffle_test.go
@@ -103,19 +103,19 @@ func TestShuffle_Structs(t *testing.T) {
})
}
-func TestShuffle_DoesNotMutateOriginal(t *testing.T) {
+func TestShuffle_MutatesInPlace(t *testing.T) {
withDeterministicShuffle(t, 99, func() {
items := []int{1, 2, 3, 4}
c := New(items)
out := c.Shuffle()
- if !reflect.DeepEqual(items, []int{1, 2, 3, 4}) {
- t.Fatalf("expected source slice to remain unchanged")
+ if out != c {
+ t.Fatalf("Shuffle should return the same collection")
}
- if reflect.DeepEqual(out.Items(), []int{1, 2, 3, 4}) {
- t.Fatalf("expected shuffled output to differ from original order")
+ if reflect.DeepEqual(items, []int{1, 2, 3, 4}) {
+ t.Fatalf("expected source slice to be shuffled in place")
}
})
}
From a10a107048e0cd2eff45a903fe6345f53151e844 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 02:00:55 -0600
Subject: [PATCH 20/30] docs: readme
---
README.md | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index b662d66..0399cd5 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,11 @@
# Features
+This library is ideal when:
+- you write multi-step slice pipelines
+- allocation behavior matters
+- you want fluent APIs without hidden costs
+
- **Fluent chaining** - pipeline your operations like Laravel Collections
- **Fully generic** (`Collection[T]`) - no reflection, no `interface{}`
- **Minimal dependencies** - small footprint (godump for debugging)
@@ -43,7 +48,7 @@
Many methods return the collection itself, allowing for fluent method chaining.
-Some methods maybe limited to due to go's generic constraints.
+Some methods may be limited due to Go's generic constraints.
> **Fluent example:**
> [`examples/chaining/main.go`](./examples/chaining/main.go)
@@ -150,7 +155,7 @@ Full raw tables: see `BENCHMARKS.md`.
## How to read the benchmarks
* **≈** means the two libraries are effectively equivalent
-* **∞x less** means one side allocates while the other allocates nothing
+* Explicit memory deltas show fixed wrapper overhead vs avoided allocations
* Single-operation helpers are intentionally close in performance if not exceeds
* Multi-step pipelines highlight the architectural difference
@@ -947,7 +952,7 @@ collection.Dump(odds.Items())
### New · immutable · chainable
-New creates a new Collection from the provided slice and borrows it.
+New creates a new Collection that borrows the provided slice without copying.
### NewNumeric · immutable · chainable
From 2ee93a91fd742e124e3865b200953d06d2032b98 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 02:11:55 -0600
Subject: [PATCH 21/30] docs: readme fix
---
README.md | 5 -----
1 file changed, 5 deletions(-)
diff --git a/README.md b/README.md
index 0399cd5..680f5e3 100644
--- a/README.md
+++ b/README.md
@@ -27,11 +27,6 @@
# Features
-This library is ideal when:
-- you write multi-step slice pipelines
-- allocation behavior matters
-- you want fluent APIs without hidden costs
-
- **Fluent chaining** - pipeline your operations like Laravel Collections
- **Fully generic** (`Collection[T]`) - no reflection, no `interface{}`
- **Minimal dependencies** - small footprint (godump for debugging)
From 67c915ec650a955e7d18ae912e36974320a614ff Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 02:12:22 -0600
Subject: [PATCH 22/30] docs: readme
---
README.md | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/README.md b/README.md
index 680f5e3..de38488 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,13 @@
- **Developer-friendly debug helpers** (`Dump()`, `Dd()`, `DumpStr()`)
- **Works with any Go type**, including structs, pointers, and deeply nested composites
+# Why Collection?
+
+This library is ideal when:
+- you write multi-step slice pipelines
+- allocation behavior matters
+- you want fluent APIs without hidden costs
+
## Fluent Chaining
Many methods return the collection itself, allowing for fluent method chaining.
From 0d19434126029dc1d73e25fa2dc6e0eef02190af Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 02:12:59 -0600
Subject: [PATCH 23/30] docs: readme tweak
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index de38488..436171e 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@
Designed to feel natural to Go developers - and luxurious to everyone else.
-# Features
+## Features
- **Fluent chaining** - pipeline your operations like Laravel Collections
- **Fully generic** (`Collection[T]`) - no reflection, no `interface{}`
@@ -39,7 +39,7 @@
- **Developer-friendly debug helpers** (`Dump()`, `Dd()`, `DumpStr()`)
- **Works with any Go type**, including structs, pointers, and deeply nested composites
-# Why Collection?
+## Why Collection?
This library is ideal when:
- you write multi-step slice pipelines
From 2b4b5d20b9280ceeed965337015960bc0b5d5a65 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 02:13:57 -0600
Subject: [PATCH 24/30] docs: readme
---
README.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 436171e..da6ccbd 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,8 @@
- collection brings an expressive, fluent API to Go.
- Iterate, filter, transform, sort, reduce, group, and debug your data with zero dependencies.
- Designed to feel natural to Go developers - and luxurious to everyone else.
+ collection brings an expressive, fluent API to Go.
+ Iterate, filter, transform, sort, reduce, group, and debug data with zero dependencies — familiar to Go developers, pleasant to use everywhere.
## Features
From 5543405e9eba0d04ca7489c7f072daae273d6299 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 02:14:22 -0600
Subject: [PATCH 25/30] docs: readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index da6ccbd..f5c851f 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@
collection brings an expressive, fluent API to Go.
- Iterate, filter, transform, sort, reduce, group, and debug data with zero dependencies — familiar to Go developers, pleasant to use everywhere.
+ Iterate, filter, transform, sort, reduce, group, and debug data with zero dependencies - familiar to Go developers, pleasant to use everywhere.
## Features
From bb5eace07b862db3ebf7625127a3f8f7665e7a63 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 02:16:46 -0600
Subject: [PATCH 26/30] docs: readme
---
README.md | 7 -------
1 file changed, 7 deletions(-)
diff --git a/README.md b/README.md
index f5c851f..256aba9 100644
--- a/README.md
+++ b/README.md
@@ -38,13 +38,6 @@
- **Developer-friendly debug helpers** (`Dump()`, `Dd()`, `DumpStr()`)
- **Works with any Go type**, including structs, pointers, and deeply nested composites
-## Why Collection?
-
-This library is ideal when:
-- you write multi-step slice pipelines
-- allocation behavior matters
-- you want fluent APIs without hidden costs
-
## Fluent Chaining
Many methods return the collection itself, allowing for fluent method chaining.
From 5a908ffb125f40eab66a26920386a79146506766 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 02:53:07 -0600
Subject: [PATCH 27/30] chore: mutability guarantees test coverage
---
README.md | 4 +-
mutability_guarantees_test.go | 223 ++++++++++++++++++++++++++++++++++
2 files changed, 225 insertions(+), 2 deletions(-)
create mode 100644 mutability_guarantees_test.go
diff --git a/README.md b/README.md
index 256aba9..4ccce3b 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
@@ -946,7 +946,7 @@ collection.Dump(odds.Items())
### New · immutable · chainable
-New creates a new Collection that borrows the provided slice without copying.
+New creates a new Collection from the provided slice and borrows it.
### NewNumeric · immutable · chainable
diff --git a/mutability_guarantees_test.go b/mutability_guarantees_test.go
new file mode 100644
index 0000000..06c0a59
--- /dev/null
+++ b/mutability_guarantees_test.go
@@ -0,0 +1,223 @@
+package collection
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestMutators_PreserveNilSlice(t *testing.T) {
+ cases := []struct {
+ name string
+ fn func(*Collection[int])
+ }{
+ {
+ name: "Map",
+ fn: func(c *Collection[int]) {
+ c.Map(func(v int) int { return v * 2 })
+ },
+ },
+ {
+ name: "Filter",
+ fn: func(c *Collection[int]) {
+ c.Filter(func(v int) bool { return v%2 == 0 })
+ },
+ },
+ {
+ name: "Sort",
+ fn: func(c *Collection[int]) {
+ c.Sort(func(a, b int) bool { return a < b })
+ },
+ },
+ {
+ name: "Reverse",
+ fn: func(c *Collection[int]) {
+ c.Reverse()
+ },
+ },
+ {
+ name: "Shuffle",
+ fn: func(c *Collection[int]) {
+ c.Shuffle()
+ },
+ },
+ {
+ name: "Transform",
+ fn: func(c *Collection[int]) {
+ c.Transform(func(v int) int { return v + 1 })
+ },
+ },
+ {
+ name: "Pop",
+ fn: func(c *Collection[int]) {
+ _, _ = c.Pop()
+ },
+ },
+ {
+ name: "PopN",
+ fn: func(c *Collection[int]) {
+ _ = c.PopN(2)
+ },
+ },
+ {
+ name: "ConcatEmpty",
+ fn: func(c *Collection[int]) {
+ c.Concat([]int{})
+ },
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ c := New([]int(nil))
+ tc.fn(c)
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+ })
+ }
+}
+
+func TestMap_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{1, 2, 3}
+ c := New(items)
+
+ c.Map(func(v int) int { return v * 2 })
+
+ want := []int{2, 4, 6}
+ if !reflect.DeepEqual(items, want) {
+ t.Fatalf("expected source slice %v, got %v", want, items)
+ }
+}
+
+func TestFilter_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{1, 2, 3, 4}
+ c := New(items)
+
+ c.Filter(func(v int) bool { return v%2 == 0 })
+
+ want := []int{2, 4}
+ if !reflect.DeepEqual(items[:len(want)], want) {
+ t.Fatalf("expected source prefix %v, got %v", want, items[:len(want)])
+ }
+
+ if len(c.Items()) != len(want) {
+ t.Fatalf("expected filtered length %d, got %d", len(want), len(c.Items()))
+ }
+}
+
+func TestSort_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{3, 1, 2}
+ c := New(items)
+
+ c.Sort(func(a, b int) bool { return a < b })
+
+ want := []int{1, 2, 3}
+ if !reflect.DeepEqual(items, want) {
+ t.Fatalf("expected source slice %v, got %v", want, items)
+ }
+}
+
+func TestTransform_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{1, 2, 3}
+ c := New(items)
+
+ c.Transform(func(v int) int { return v + 10 })
+
+ want := []int{11, 12, 13}
+ if !reflect.DeepEqual(items, want) {
+ t.Fatalf("expected source slice %v, got %v", want, items)
+ }
+}
+
+func TestReverse_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{1, 2, 3}
+ c := New(items)
+
+ c.Reverse()
+
+ want := []int{3, 2, 1}
+ if !reflect.DeepEqual(items, want) {
+ t.Fatalf("expected source slice %v, got %v", want, items)
+ }
+}
+
+func TestMutators_LengthGuarantees(t *testing.T) {
+ cases := []struct {
+ name string
+ input []int
+ fn func(*Collection[int]) any
+ wantLen int
+ }{
+ {
+ name: "Map",
+ input: []int{1, 2, 3},
+ fn: func(c *Collection[int]) any { return c.Map(func(v int) int { return v * 2 }) },
+ wantLen: 3,
+ },
+ {
+ name: "Filter",
+ input: []int{1, 2, 3, 4},
+ fn: func(c *Collection[int]) any { return c.Filter(func(v int) bool { return v%2 == 0 }) },
+ wantLen: 2,
+ },
+ {
+ name: "Sort",
+ input: []int{3, 1, 2},
+ fn: func(c *Collection[int]) any { return c.Sort(func(a, b int) bool { return a < b }) },
+ wantLen: 3,
+ },
+ {
+ name: "Transform",
+ input: []int{1, 2, 3},
+ fn: func(c *Collection[int]) any { c.Transform(func(v int) int { return v + 1 }); return nil },
+ wantLen: 3,
+ },
+ {
+ name: "Reverse",
+ input: []int{1, 2, 3},
+ fn: func(c *Collection[int]) any { return c.Reverse() },
+ wantLen: 3,
+ },
+ {
+ name: "Shuffle",
+ input: []int{1, 2, 3, 4},
+ fn: func(c *Collection[int]) any { return c.Shuffle() },
+ wantLen: 4,
+ },
+ {
+ name: "Pop",
+ input: []int{1, 2, 3},
+ fn: func(c *Collection[int]) any { _, _ = c.Pop(); return nil },
+ wantLen: 2,
+ },
+ {
+ name: "PopN",
+ input: []int{1, 2, 3, 4},
+ fn: func(c *Collection[int]) any { _ = c.PopN(2); return nil },
+ wantLen: 2,
+ },
+ {
+ name: "Prepend",
+ input: []int{3, 4},
+ fn: func(c *Collection[int]) any { return c.Prepend(1, 2) },
+ wantLen: 4,
+ },
+ {
+ name: "Concat",
+ input: []int{1, 2},
+ fn: func(c *Collection[int]) any { return c.Concat([]int{3, 4, 5}) },
+ wantLen: 5,
+ },
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ c := New(append([]int(nil), tc.input...))
+ tc.fn(c)
+ if len(c.items) != tc.wantLen {
+ t.Fatalf("expected len %d, got %d", tc.wantLen, len(c.items))
+ }
+ })
+ }
+}
From e55a61aecd121fccf2fe7c5b2c86a3d4f0361458 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 13:24:11 -0600
Subject: [PATCH 28/30] chore: test colocation
---
concat_test.go | 21 ++++
filter_test.go | 26 ++++
map_test.go | 32 +++++
mutability_guarantees_test.go | 223 ----------------------------------
pop_test.go | 28 +++++
prepend_test.go | 24 ++++
reverse_test.go | 10 ++
shuffle_test.go | 10 ++
sort_test.go | 32 +++++
transform_test.go | 32 +++++
10 files changed, 215 insertions(+), 223 deletions(-)
delete mode 100644 mutability_guarantees_test.go
diff --git a/concat_test.go b/concat_test.go
index 62936b1..3070754 100644
--- a/concat_test.go
+++ b/concat_test.go
@@ -116,3 +116,24 @@ func TestConcat_NoAllocationWhenCapacityAllows(t *testing.T) {
t.Fatalf("expected %v, got %v", expected, c.items)
}
}
+
+func TestConcat_PreservesNilSliceWhenEmptyValues(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Concat([]int{})
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
+
+func TestConcat_NilSliceWithValues(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Concat([]int{1, 2})
+
+ expected := []int{1, 2}
+ if !reflect.DeepEqual(c.Items(), expected) {
+ t.Fatalf("expected %v, got %v", expected, c.Items())
+ }
+}
diff --git a/filter_test.go b/filter_test.go
index ea9901f..0e04b54 100644
--- a/filter_test.go
+++ b/filter_test.go
@@ -90,3 +90,29 @@ func TestFilter_Chaining(t *testing.T) {
t.Fatalf("expected %v, got %v", expected, result.items)
}
}
+
+func TestFilter_PreservesNilSlice(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Filter(func(v int) bool { return v%2 == 0 })
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
+
+func TestFilter_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{1, 2, 3, 4}
+ c := New(items)
+
+ c.Filter(func(v int) bool { return v%2 == 0 })
+
+ want := []int{2, 4}
+ if !reflect.DeepEqual(items[:len(want)], want) {
+ t.Fatalf("expected source prefix %v, got %v", want, items[:len(want)])
+ }
+
+ if len(c.Items()) != len(want) {
+ t.Fatalf("expected filtered length %d, got %d", len(want), len(c.Items()))
+ }
+}
diff --git a/map_test.go b/map_test.go
index 76dd367..4d6365a 100644
--- a/map_test.go
+++ b/map_test.go
@@ -76,3 +76,35 @@ func TestMap_Empty(t *testing.T) {
t.Fatalf("Map should return the same collection")
}
}
+
+func TestMap_PreservesNilSlice(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Map(func(v int) int { return v * 2 })
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
+
+func TestMap_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{1, 2, 3}
+ c := New(items)
+
+ c.Map(func(v int) int { return v * 2 })
+
+ want := []int{2, 4, 6}
+ if !reflect.DeepEqual(items, want) {
+ t.Fatalf("expected source slice %v, got %v", want, items)
+ }
+}
+
+func TestMap_LengthUnchanged(t *testing.T) {
+ c := New([]int{1, 2, 3})
+
+ c.Map(func(v int) int { return v + 1 })
+
+ if len(c.Items()) != 3 {
+ t.Fatalf("expected length 3, got %d", len(c.Items()))
+ }
+}
diff --git a/mutability_guarantees_test.go b/mutability_guarantees_test.go
deleted file mode 100644
index 06c0a59..0000000
--- a/mutability_guarantees_test.go
+++ /dev/null
@@ -1,223 +0,0 @@
-package collection
-
-import (
- "reflect"
- "testing"
-)
-
-func TestMutators_PreserveNilSlice(t *testing.T) {
- cases := []struct {
- name string
- fn func(*Collection[int])
- }{
- {
- name: "Map",
- fn: func(c *Collection[int]) {
- c.Map(func(v int) int { return v * 2 })
- },
- },
- {
- name: "Filter",
- fn: func(c *Collection[int]) {
- c.Filter(func(v int) bool { return v%2 == 0 })
- },
- },
- {
- name: "Sort",
- fn: func(c *Collection[int]) {
- c.Sort(func(a, b int) bool { return a < b })
- },
- },
- {
- name: "Reverse",
- fn: func(c *Collection[int]) {
- c.Reverse()
- },
- },
- {
- name: "Shuffle",
- fn: func(c *Collection[int]) {
- c.Shuffle()
- },
- },
- {
- name: "Transform",
- fn: func(c *Collection[int]) {
- c.Transform(func(v int) int { return v + 1 })
- },
- },
- {
- name: "Pop",
- fn: func(c *Collection[int]) {
- _, _ = c.Pop()
- },
- },
- {
- name: "PopN",
- fn: func(c *Collection[int]) {
- _ = c.PopN(2)
- },
- },
- {
- name: "ConcatEmpty",
- fn: func(c *Collection[int]) {
- c.Concat([]int{})
- },
- },
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- c := New([]int(nil))
- tc.fn(c)
-
- if c.Items() != nil {
- t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
- }
- })
- }
-}
-
-func TestMap_WritesThroughSourceSlice(t *testing.T) {
- items := []int{1, 2, 3}
- c := New(items)
-
- c.Map(func(v int) int { return v * 2 })
-
- want := []int{2, 4, 6}
- if !reflect.DeepEqual(items, want) {
- t.Fatalf("expected source slice %v, got %v", want, items)
- }
-}
-
-func TestFilter_WritesThroughSourceSlice(t *testing.T) {
- items := []int{1, 2, 3, 4}
- c := New(items)
-
- c.Filter(func(v int) bool { return v%2 == 0 })
-
- want := []int{2, 4}
- if !reflect.DeepEqual(items[:len(want)], want) {
- t.Fatalf("expected source prefix %v, got %v", want, items[:len(want)])
- }
-
- if len(c.Items()) != len(want) {
- t.Fatalf("expected filtered length %d, got %d", len(want), len(c.Items()))
- }
-}
-
-func TestSort_WritesThroughSourceSlice(t *testing.T) {
- items := []int{3, 1, 2}
- c := New(items)
-
- c.Sort(func(a, b int) bool { return a < b })
-
- want := []int{1, 2, 3}
- if !reflect.DeepEqual(items, want) {
- t.Fatalf("expected source slice %v, got %v", want, items)
- }
-}
-
-func TestTransform_WritesThroughSourceSlice(t *testing.T) {
- items := []int{1, 2, 3}
- c := New(items)
-
- c.Transform(func(v int) int { return v + 10 })
-
- want := []int{11, 12, 13}
- if !reflect.DeepEqual(items, want) {
- t.Fatalf("expected source slice %v, got %v", want, items)
- }
-}
-
-func TestReverse_WritesThroughSourceSlice(t *testing.T) {
- items := []int{1, 2, 3}
- c := New(items)
-
- c.Reverse()
-
- want := []int{3, 2, 1}
- if !reflect.DeepEqual(items, want) {
- t.Fatalf("expected source slice %v, got %v", want, items)
- }
-}
-
-func TestMutators_LengthGuarantees(t *testing.T) {
- cases := []struct {
- name string
- input []int
- fn func(*Collection[int]) any
- wantLen int
- }{
- {
- name: "Map",
- input: []int{1, 2, 3},
- fn: func(c *Collection[int]) any { return c.Map(func(v int) int { return v * 2 }) },
- wantLen: 3,
- },
- {
- name: "Filter",
- input: []int{1, 2, 3, 4},
- fn: func(c *Collection[int]) any { return c.Filter(func(v int) bool { return v%2 == 0 }) },
- wantLen: 2,
- },
- {
- name: "Sort",
- input: []int{3, 1, 2},
- fn: func(c *Collection[int]) any { return c.Sort(func(a, b int) bool { return a < b }) },
- wantLen: 3,
- },
- {
- name: "Transform",
- input: []int{1, 2, 3},
- fn: func(c *Collection[int]) any { c.Transform(func(v int) int { return v + 1 }); return nil },
- wantLen: 3,
- },
- {
- name: "Reverse",
- input: []int{1, 2, 3},
- fn: func(c *Collection[int]) any { return c.Reverse() },
- wantLen: 3,
- },
- {
- name: "Shuffle",
- input: []int{1, 2, 3, 4},
- fn: func(c *Collection[int]) any { return c.Shuffle() },
- wantLen: 4,
- },
- {
- name: "Pop",
- input: []int{1, 2, 3},
- fn: func(c *Collection[int]) any { _, _ = c.Pop(); return nil },
- wantLen: 2,
- },
- {
- name: "PopN",
- input: []int{1, 2, 3, 4},
- fn: func(c *Collection[int]) any { _ = c.PopN(2); return nil },
- wantLen: 2,
- },
- {
- name: "Prepend",
- input: []int{3, 4},
- fn: func(c *Collection[int]) any { return c.Prepend(1, 2) },
- wantLen: 4,
- },
- {
- name: "Concat",
- input: []int{1, 2},
- fn: func(c *Collection[int]) any { return c.Concat([]int{3, 4, 5}) },
- wantLen: 5,
- },
- }
-
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- c := New(append([]int(nil), tc.input...))
- tc.fn(c)
- if len(c.items) != tc.wantLen {
- t.Fatalf("expected len %d, got %d", tc.wantLen, len(c.items))
- }
- })
- }
-}
diff --git a/pop_test.go b/pop_test.go
index e22855a..04a4295 100644
--- a/pop_test.go
+++ b/pop_test.go
@@ -94,3 +94,31 @@ func TestPopN_ZeroOrNegative(t *testing.T) {
t.Fatalf("PopN(-n) should not modify original")
}
}
+
+func TestPop_PreservesNilSlice(t *testing.T) {
+ c := New([]int(nil))
+
+ v, ok := c.Pop()
+
+ if v != 0 || ok {
+ t.Fatalf("Pop() on nil should return zero-value and ok=false")
+ }
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
+
+func TestPopN_PreservesNilSlice(t *testing.T) {
+ c := New([]int(nil))
+
+ popped := c.PopN(2)
+
+ if popped != nil {
+ t.Fatalf("PopN on nil should return nil")
+ }
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
diff --git a/prepend_test.go b/prepend_test.go
index f878334..3e74c7a 100644
--- a/prepend_test.go
+++ b/prepend_test.go
@@ -98,3 +98,27 @@ func TestPrepend_DoesNotMutateSourceSlice(t *testing.T) {
t.Fatalf("expected %v, got %v", expected, c.items)
}
}
+
+func TestPrepend_NilSliceWithValues(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Prepend(1, 2)
+
+ expected := []int{1, 2}
+ if !reflect.DeepEqual(c.Items(), expected) {
+ t.Fatalf("expected %v, got %v", expected, c.Items())
+ }
+}
+
+func TestPrepend_NilSliceNoValuesBecomesEmpty(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Prepend()
+
+ if c.Items() == nil {
+ t.Fatalf("expected empty slice, got nil")
+ }
+ if len(c.Items()) != 0 {
+ t.Fatalf("expected empty slice, got %v", c.Items())
+ }
+}
diff --git a/reverse_test.go b/reverse_test.go
index 98eb50e..12ac3c4 100644
--- a/reverse_test.go
+++ b/reverse_test.go
@@ -96,3 +96,13 @@ func TestReverse_MutatesInPlace(t *testing.T) {
t.Fatalf("expected underlying slice to be mutated, got %v", items)
}
}
+
+func TestReverse_PreservesNilSlice(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Reverse()
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
diff --git a/shuffle_test.go b/shuffle_test.go
index aa989fe..843a30f 100644
--- a/shuffle_test.go
+++ b/shuffle_test.go
@@ -119,3 +119,13 @@ func TestShuffle_MutatesInPlace(t *testing.T) {
}
})
}
+
+func TestShuffle_PreservesNilSlice(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Shuffle()
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
diff --git a/sort_test.go b/sort_test.go
index a359a34..0ed2b00 100644
--- a/sort_test.go
+++ b/sort_test.go
@@ -111,3 +111,35 @@ func TestSort_SingleElement(t *testing.T) {
t.Fatalf("expected %v, got %v", expected, sorted.items)
}
}
+
+func TestSort_PreservesNilSlice(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Sort(func(a, b int) bool { return a < b })
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
+
+func TestSort_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{3, 1, 2}
+ c := New(items)
+
+ c.Sort(func(a, b int) bool { return a < b })
+
+ want := []int{1, 2, 3}
+ if !reflect.DeepEqual(items, want) {
+ t.Fatalf("expected source slice %v, got %v", want, items)
+ }
+}
+
+func TestSort_LengthUnchanged(t *testing.T) {
+ c := New([]int{3, 1, 2})
+
+ c.Sort(func(a, b int) bool { return a < b })
+
+ if len(c.Items()) != 3 {
+ t.Fatalf("expected length 3, got %d", len(c.Items()))
+ }
+}
diff --git a/transform_test.go b/transform_test.go
index 11614d5..4ced533 100644
--- a/transform_test.go
+++ b/transform_test.go
@@ -92,3 +92,35 @@ func TestTransform_ChainingCompatibility(t *testing.T) {
t.Fatalf("expected %v, got %v", expected, out.items)
}
}
+
+func TestTransform_PreservesNilSlice(t *testing.T) {
+ c := New([]int(nil))
+
+ c.Transform(func(v int) int { return v + 1 })
+
+ if c.Items() != nil {
+ t.Fatalf("expected nil slice to remain nil, got %v", c.Items())
+ }
+}
+
+func TestTransform_WritesThroughSourceSlice(t *testing.T) {
+ items := []int{1, 2, 3}
+ c := New(items)
+
+ c.Transform(func(v int) int { return v + 10 })
+
+ want := []int{11, 12, 13}
+ if !reflect.DeepEqual(items, want) {
+ t.Fatalf("expected source slice %v, got %v", want, items)
+ }
+}
+
+func TestTransform_LengthUnchanged(t *testing.T) {
+ c := New([]int{1, 2, 3})
+
+ c.Transform(func(v int) int { return v + 1 })
+
+ if len(c.Items()) != 3 {
+ t.Fatalf("expected length 3, got %d", len(c.Items()))
+ }
+}
From fe2911f45d5f82513c1f122f6b26064b974d6d62 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 13:49:02 -0600
Subject: [PATCH 29/30] chore: remove benchInner
---
BENCHMARKS.md | 132 +++++++++----------
README.md | 25 ++--
docs/bench/main.go | 317 +++++++++++++--------------------------------
3 files changed, 168 insertions(+), 306 deletions(-)
diff --git a/BENCHMARKS.md b/BENCHMARKS.md
index ffc2965..0c3a9e8 100644
--- a/BENCHMARKS.md
+++ b/BENCHMARKS.md
@@ -4,74 +4,74 @@ Raw results for `collection.New` (borrowed) vs `lo`.
| Op | ns/op (vs lo) | × (faster) | bytes/op (vs lo) | × (less memory) | allocs/op (vs lo) |
|---:|----------------|:--:|------------------|:--:|--------------------|
-| **All** | 251ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Any** | 249ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Chunk** | 141ns / 1.1µs | **7.49x** | 1.3KB / 9.3KB | **7.12x less** | 2 / 51 |
-| **Contains** | 249ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **CountBy** | 8.2µs / 8.0µs | ≈ | 9.4KB / 9.4KB | ≈ | 12 / 11 |
-| **CountByValue** | 8.2µs / 8.1µs | ≈ | 9.4KB / 9.4KB | ≈ | 12 / 11 |
-| **Difference** | 19.1µs / 44.2µs | **2.31x** | 82.2KB / 108.8KB | **1.32x less** | 14 / 43 |
-| **Each** | 253ns / 231ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Filter** | 644ns / 1.1µs | **1.64x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
-| **First** | 11ns / <1ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **FirstWhere** | 249ns / 234ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **GroupBySlice** | 8.0µs / 8.4µs | ≈ | 21.0KB / 21.0KB | ≈ | 84 / 83 |
-| **IndexWhere** | 250ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Intersect** | 11.0µs / 10.7µs | ≈ | 11.5KB / 11.4KB | ≈ | 22 / 19 |
-| **Last** | 11ns / <1ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Map** | 358ns / 819ns | **2.29x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
-| **Max** | 254ns / 231ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
-| **Min** | 255ns / 236ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
-| **None** | 248ns / 234ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Pipeline F→M→T→R** | 504ns / 1.3µs | **2.53x** | 48B / 12.3KB | **256.00x less** | 2 / 2 |
-| **Reduce (sum)** | 253ns / 236ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Reverse** | 227ns / 235ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
-| **Shuffle** | 3.5µs / 5.5µs | **1.56x** | 24B / 0B | ∞x more | 1 / 0 |
-| **Skip** | 11ns / 726ns | **65.27x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
-| **SkipLast** | 11ns / 730ns | **64.89x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
-| **Sum** | 255ns / 236ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
-| **Take** | 22ns / <1ns | ≈ | 48B / 0B | ∞x more | 2 / 0 |
-| **ToMap** | 7.6µs / 8.0µs | ≈ | 37.0KB / 37.0KB | ≈ | 6 / 6 |
-| **Union** | 17.2µs / 17.9µs | ≈ | 90.3KB / 90.3KB | ≈ | 13 / 10 |
-| **Unique** | 6.4µs / 6.4µs | ≈ | 45.2KB / 45.1KB | ≈ | 7 / 6 |
-| **UniqueBy** | 6.8µs / 6.4µs | ≈ | 45.2KB / 45.1KB | ≈ | 8 / 6 |
-| **Zip** | 1.5µs / 3.2µs | **2.22x** | 16.4KB / 16.4KB | ≈ | 3 / 1 |
-| **ZipWith** | 1.0µs / 3.0µs | **2.87x** | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **All** | 248ns / 232ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Any** | 248ns / 235ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Chunk** | 138ns / 1.1µs | **7.64x** | 1.3KB / 9.3KB | **7.12x less** | 2 / 51 |
+| **Contains** | 250ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **CountBy** | 8.6µs / 8.4µs | ≈ | 9.4KB / 9.4KB | ≈ | 12 / 11 |
+| **CountByValue** | 8.3µs / 8.3µs | ≈ | 9.4KB / 9.4KB | ≈ | 12 / 11 |
+| **Difference** | 19.5µs / 45.6µs | **2.33x** | 82.2KB / 108.8KB | **1.32x less** | 14 / 43 |
+| **Each** | 256ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Filter** | 734ns / 1.0µs | **1.41x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
+| **First** | 10ns / <1ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **FirstWhere** | 251ns / 244ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **GroupBySlice** | 8.1µs / 8.3µs | ≈ | 21.0KB / 21.0KB | ≈ | 84 / 83 |
+| **IndexWhere** | 247ns / 233ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Intersect** | 11.1µs / 10.8µs | ≈ | 11.5KB / 11.4KB | ≈ | 22 / 19 |
+| **Last** | 10ns / <1ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Map** | 358ns / 790ns | **2.21x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
+| **Max** | 263ns / 230ns | 0.87x | 32B / 0B | ∞x more | 2 / 0 |
+| **Min** | 253ns / 230ns | ≈ | 32B / 0B | ∞x more | 2 / 0 |
+| **None** | 248ns / 231ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Pipeline F→M→T→R** | 690ns / 1.2µs | **1.79x** | 48B / 12.3KB | **256.00x less** | 2 / 2 |
+| **Reduce (sum)** | 262ns / 233ns | 0.89x | 24B / 0B | ∞x more | 1 / 0 |
+| **Reverse** | 230ns / 230ns | ≈ | 24B / 0B | ∞x more | 1 / 0 |
+| **Shuffle** | 3.6µs / 5.6µs | **1.57x** | 24B / 0B | ∞x more | 1 / 0 |
+| **Skip** | 10ns / 714ns | **71.40x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
+| **SkipLast** | 10ns / 719ns | **71.90x** | 24B / 8.2KB | **341.33x less** | 1 / 1 |
+| **Sum** | 267ns / 231ns | 0.87x | 32B / 0B | ∞x more | 2 / 0 |
+| **Take** | 21ns / <1ns | ≈ | 48B / 0B | ∞x more | 2 / 0 |
+| **ToMap** | 7.8µs / 7.9µs | ≈ | 37.0KB / 37.0KB | ≈ | 6 / 6 |
+| **Union** | 17.4µs / 17.9µs | ≈ | 90.3KB / 90.3KB | ≈ | 13 / 10 |
+| **Unique** | 6.5µs / 6.2µs | ≈ | 45.2KB / 45.1KB | ≈ | 7 / 6 |
+| **UniqueBy** | 6.6µs / 6.4µs | ≈ | 45.2KB / 45.1KB | ≈ | 8 / 6 |
+| **Zip** | 1.4µs / 3.3µs | **2.35x** | 16.4KB / 16.4KB | ≈ | 3 / 1 |
+| **ZipWith** | 1.0µs / 3.2µs | **3.15x** | 8.2KB / 8.2KB | ≈ | 3 / 1 |
Raw results for `collection.New().Clone()` (explicit copy) vs `lo`.
| Op | ns/op (vs lo) | × (faster) | bytes/op (vs lo) | × (less memory) | allocs/op (vs lo) |
|---:|----------------|:--:|------------------|:--:|--------------------|
-| **All** | 916ns / 234ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Any** | 920ns / 235ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Chunk** | 862ns / 1.1µs | **1.26x** | 9.5KB / 9.3KB | ≈ | 4 / 51 |
-| **Contains** | 924ns / 234ns | 0.25x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **CountBy** | 8.9µs / 8.6µs | ≈ | 17.6KB / 9.4KB | 0.53x more | 14 / 11 |
-| **CountByValue** | 9.0µs / 15.2µs | **1.68x** | 17.6KB / 9.4KB | 0.53x more | 14 / 11 |
-| **Difference** | 20.6µs / 44.8µs | **2.17x** | 98.6KB / 108.8KB | **1.10x less** | 18 / 43 |
-| **Each** | 913ns / 232ns | 0.25x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Filter** | 1.2µs / 1.0µs | 0.89x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
-| **First** | 737ns / <1ns | 0.00x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **FirstWhere** | 934ns / 236ns | 0.25x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **GroupBySlice** | 8.7µs / 8.7µs | ≈ | 29.3KB / 21.0KB | 0.72x more | 86 / 83 |
-| **IndexWhere** | 925ns / 234ns | 0.25x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Intersect** | 12.2µs / 10.9µs | 0.90x | 27.9KB / 11.4KB | 0.41x more | 26 / 19 |
-| **Last** | 740ns / <1ns | 0.00x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Map** | 958ns / 816ns | 0.85x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
-| **Max** | 930ns / 231ns | 0.25x | 8.3KB / 0B | ∞x more | 5 / 0 |
-| **Min** | 925ns / 236ns | 0.26x | 8.3KB / 0B | ∞x more | 5 / 0 |
-| **None** | 916ns / 236ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Pipeline F→M→T→R** | 1.1µs / 1.3µs | **1.21x** | 8.3KB / 12.3KB | **1.49x less** | 4 / 2 |
-| **Reduce (sum)** | 911ns / 236ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Reverse** | 841ns / 240ns | 0.29x | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Shuffle** | 4.2µs / 5.7µs | **1.34x** | 8.2KB / 0B | ∞x more | 3 / 0 |
-| **Skip** | 771ns / 982ns | **1.27x** | 8.2KB / 8.2KB | ≈ | 3 / 1 |
-| **SkipLast** | 2.2µs / 736ns | 0.33x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
-| **Sum** | 918ns / 236ns | 0.26x | 8.3KB / 0B | ∞x more | 5 / 0 |
-| **Take** | 746ns / <1ns | 0.00x | 8.3KB / 0B | ∞x more | 4 / 0 |
-| **ToMap** | 8.3µs / 8.0µs | ≈ | 45.2KB / 37.0KB | 0.82x more | 8 / 6 |
-| **Union** | 18.6µs / 18.1µs | ≈ | 106.8KB / 90.3KB | 0.85x more | 17 / 10 |
-| **Unique** | 7.1µs / 6.4µs | ≈ | 53.4KB / 45.1KB | 0.85x more | 9 / 6 |
-| **UniqueBy** | 7.5µs / 6.5µs | 0.86x | 53.4KB / 45.1KB | 0.85x more | 10 / 6 |
-| **Zip** | 2.8µs / 3.3µs | **1.17x** | 32.9KB / 16.4KB | 0.50x more | 7 / 1 |
-| **ZipWith** | 2.4µs / 3.0µs | **1.26x** | 24.7KB / 8.2KB | 0.33x more | 7 / 1 |
+| **All** | 921ns / 245ns | 0.27x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Any** | 908ns / 245ns | 0.27x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Chunk** | 855ns / 1.1µs | **1.28x** | 9.5KB / 9.3KB | ≈ | 4 / 51 |
+| **Contains** | 912ns / 244ns | 0.27x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **CountBy** | 9.2µs / 8.6µs | ≈ | 17.6KB / 9.4KB | 0.53x more | 14 / 11 |
+| **CountByValue** | 9.2µs / 8.6µs | ≈ | 17.6KB / 9.4KB | 0.53x more | 14 / 11 |
+| **Difference** | 21.1µs / 45.9µs | **2.17x** | 98.6KB / 108.8KB | **1.10x less** | 18 / 43 |
+| **Each** | 887ns / 235ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Filter** | 1.2µs / 1.0µs | 0.85x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **First** | 707ns / <1ns | 0.00x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **FirstWhere** | 903ns / 247ns | 0.27x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **GroupBySlice** | 9.4µs / 8.7µs | ≈ | 29.3KB / 21.0KB | 0.72x more | 86 / 83 |
+| **IndexWhere** | 887ns / 236ns | 0.27x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Intersect** | 12.6µs / 11.2µs | 0.89x | 27.9KB / 11.4KB | 0.41x more | 26 / 19 |
+| **Last** | 730ns / <1ns | 0.00x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Map** | 903ns / 782ns | 0.87x | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **Max** | 928ns / 236ns | 0.25x | 8.3KB / 0B | ∞x more | 5 / 0 |
+| **Min** | 931ns / 235ns | 0.25x | 8.3KB / 0B | ∞x more | 5 / 0 |
+| **None** | 882ns / 232ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Pipeline F→M→T→R** | 1.2µs / 1.2µs | ≈ | 8.3KB / 12.3KB | **1.49x less** | 4 / 2 |
+| **Reduce (sum)** | 887ns / 231ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Reverse** | 904ns / 235ns | 0.26x | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Shuffle** | 4.1µs / 5.7µs | **1.39x** | 8.2KB / 0B | ∞x more | 3 / 0 |
+| **Skip** | 730ns / 955ns | **1.31x** | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **SkipLast** | 768ns / 772ns | ≈ | 8.2KB / 8.2KB | ≈ | 3 / 1 |
+| **Sum** | 944ns / 239ns | 0.25x | 8.3KB / 0B | ∞x more | 5 / 0 |
+| **Take** | 727ns / <1ns | 0.00x | 8.3KB / 0B | ∞x more | 4 / 0 |
+| **ToMap** | 8.4µs / 8.1µs | ≈ | 45.2KB / 37.0KB | 0.82x more | 8 / 6 |
+| **Union** | 19.6µs / 18.3µs | ≈ | 106.8KB / 90.3KB | 0.85x more | 17 / 10 |
+| **Unique** | 7.4µs / 7.5µs | ≈ | 53.4KB / 45.1KB | 0.85x more | 9 / 6 |
+| **UniqueBy** | 10.4µs / 6.6µs | 0.64x | 53.4KB / 45.1KB | 0.85x more | 10 / 6 |
+| **Zip** | 2.9µs / 3.3µs | **1.15x** | 32.9KB / 16.4KB | 0.50x more | 7 / 1 |
+| **ZipWith** | 2.5µs / 3.1µs | **1.27x** | 24.7KB / 8.2KB | 0.33x more | 7 / 1 |
diff --git a/README.md b/README.md
index 4ccce3b..f7d5a62 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
@@ -29,7 +29,7 @@
- **Fluent chaining** - pipeline your operations like Laravel Collections
- **Fully generic** (`Collection[T]`) - no reflection, no `interface{}`
- **Minimal dependencies** - small footprint (godump for debugging)
-- **Minimal allocations** - views for slice selections; in-place ops reuse backing when possible
+- **Minimal allocations** - slice views where possible; in-place ops reuse backing storage when semantics allow
- **Map / Filter / Reduce** - clean functional transforms
- **First / Last / FirstWhere / IndexWhere / Contains** helpers
- **Sort, GroupBy, Chunk**, and more
@@ -114,17 +114,17 @@ Full raw tables: see `BENCHMARKS.md`.
| Op | Speed vs lo | Memory | Allocs |
|---:|:-----------:|:------:|:------:|
-| **Chunk** | **7.49x** | -8.0KB | -49 |
+| **Chunk** | **7.64x** | -8.0KB | -49 |
| **Take** | ≈ | +48B | +2 |
-| **Skip** | **65.27x** | -8.2KB | ≈ |
-| **SkipLast** | **64.89x** | -8.2KB | ≈ |
-| **Zip** | **2.22x** | +48B | +2 |
-| **ZipWith** | **2.87x** | +48B | +2 |
+| **Skip** | **71.40x** | -8.2KB | ≈ |
+| **SkipLast** | **71.90x** | -8.2KB | ≈ |
+| **Zip** | **2.35x** | +48B | +2 |
+| **ZipWith** | **3.15x** | +48B | +2 |
| **Unique** | ≈ | +24B | +1 |
| **UniqueBy** | ≈ | +48B | +2 |
| **Union** | ≈ | +72B | +3 |
| **Intersect** | ≈ | +72B | +3 |
-| **Difference** | **2.31x** | -26.7KB | -29 |
+| **Difference** | **2.33x** | -26.7KB | -29 |
| **GroupBySlice** | ≈ | +24B | +1 |
| **CountBy** | ≈ | +24B | +1 |
| **CountByValue** | ≈ | +24B | +1 |
@@ -134,16 +134,16 @@ Full raw tables: see `BENCHMARKS.md`.
| Op | Speed vs lo | Memory | Allocs |
|---:|:-----------:|:------:|:------:|
-| **Pipeline F→M→T→R** | **2.53x** | -12.2KB | ≈ |
+| **Pipeline F→M→T→R** | **1.79x** | -12.2KB | ≈ |
#### Mutating ops
| Op | Speed vs lo | Memory | Allocs |
|---:|:-----------:|:------:|:------:|
-| **Map** | **2.29x** | -8.2KB | ≈ |
-| **Filter** | **1.64x** | -8.2KB | ≈ |
+| **Map** | **2.21x** | -8.2KB | ≈ |
+| **Filter** | **1.41x** | -8.2KB | ≈ |
| **Reverse** | ≈ | +24B | +1 |
-| **Shuffle** | **1.56x** | +24B | +1 |
+| **Shuffle** | **1.57x** | +24B | +1 |
## How to read the benchmarks
@@ -234,6 +234,7 @@ No hidden copies. No surprises.
- 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
diff --git a/docs/bench/main.go b/docs/bench/main.go
index 3297c45..4aea988 100644
--- a/docs/bench/main.go
+++ b/docs/bench/main.go
@@ -20,8 +20,6 @@ const (
benchEnd = ""
hotPathIters = 10_000
-
- benchInner = 8
)
type benchResult struct {
@@ -172,11 +170,6 @@ func measure(name, impl string, fn func(*testing.B)) benchResult {
nsPerOp := float64(res.NsPerOp())
bytesPerOp := res.AllocedBytesPerOp()
allocsPerOp := res.AllocsPerOp()
- if benchInner > 1 {
- nsPerOp = nsPerOp / float64(benchInner)
- bytesPerOp = bytesPerOp / int64(benchInner)
- allocsPerOp = allocsPerOp / int64(benchInner)
- }
return benchResult{
name: name,
@@ -239,684 +232,552 @@ func init() {
func benchPipelineCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- input := collectionInputForMutating(benchInts)
- _ = ctorInts(input).
- Filter(func(v int) bool { return v%2 == 0 }).
- Map(func(v int) int { return v * v }).
- Take(benchPipelineLen).
- Reduce(0, func(acc, v int) int { return acc + v })
+ input := collectionInputForMutating(benchInts)
+ _ = ctorInts(input).
+ Filter(func(v int) bool { return v%2 == 0 }).
+ Map(func(v int) int { return v * v }).
+ Take(benchPipelineLen).
+ Reduce(0, func(acc, v int) int { return acc + v })
- }
}
}
func benchPipelineLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- input := benchInts
+ input := benchInts
- out := lo.Filter(input, func(v int, _ int) bool { return v%2 == 0 })
- out2 := lo.Map(out, func(v int, _ int) int { return v * v })
- out3 := lo.Subset(out2, 0, benchPipelineLen)
- _ = lo.Reduce(out3, func(acc int, v int, _ int) int { return acc + v }, 0)
+ out := lo.Filter(input, func(v int, _ int) bool { return v%2 == 0 })
+ out2 := lo.Map(out, func(v int, _ int) int { return v * v })
+ out3 := lo.Subset(out2, 0, benchPipelineLen)
+ _ = lo.Reduce(out3, func(acc int, v int, _ int) int { return acc + v }, 0)
- }
}
}
func benchAllCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorInts(benchInts).All(func(v int) bool { return v < benchSize+1 })
+ _ = ctorInts(benchInts).All(func(v int) bool { return v < benchSize+1 })
- }
}
}
func benchAllLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.EveryBy(benchInts, func(v int) bool { return v < benchSize+1 })
+ _ = lo.EveryBy(benchInts, func(v int) bool { return v < benchSize+1 })
- }
}
}
func benchAnyCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorInts(benchInts).Any(func(v int) bool { return v == benchSize-1 })
+ _ = ctorInts(benchInts).Any(func(v int) bool { return v == benchSize-1 })
- }
}
}
func benchAnyLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.SomeBy(benchInts, func(v int) bool { return v == benchSize-1 })
+ _ = lo.SomeBy(benchInts, func(v int) bool { return v == benchSize-1 })
- }
}
}
func benchNoneCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorInts(benchInts).None(func(v int) bool { return v < 0 })
+ _ = ctorInts(benchInts).None(func(v int) bool { return v < 0 })
- }
}
}
func benchNoneLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.NoneBy(benchInts, func(v int) bool { return v < 0 })
+ _ = lo.NoneBy(benchInts, func(v int) bool { return v < 0 })
- }
}
}
func benchFirstCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = ctorInts(benchInts).First()
+ _, _ = ctorInts(benchInts).First()
- }
}
}
func benchFirstLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = lo.First(benchInts)
+ _, _ = lo.First(benchInts)
- }
}
}
func benchLastCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = ctorInts(benchInts).Last()
+ _, _ = ctorInts(benchInts).Last()
- }
}
}
func benchLastLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = lo.Last(benchInts)
+ _, _ = lo.Last(benchInts)
- }
}
}
func benchIndexWhereCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = ctorInts(benchInts).IndexWhere(func(v int) bool { return v == benchSize-1 })
+ _, _ = ctorInts(benchInts).IndexWhere(func(v int) bool { return v == benchSize-1 })
- }
}
}
func benchIndexWhereLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _, _ = lo.FindIndexOf(benchInts, func(v int) bool { return v == benchSize-1 })
+ _, _, _ = lo.FindIndexOf(benchInts, func(v int) bool { return v == benchSize-1 })
- }
}
}
func benchEachCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- sum := 0
- ctorInts(benchInts).Each(func(v int) { sum += v })
+ sum := 0
+ ctorInts(benchInts).Each(func(v int) { sum += v })
- }
}
}
func benchEachLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- sum := 0
- lo.ForEach(benchInts, func(v int, _ int) { sum += v })
+ sum := 0
+ lo.ForEach(benchInts, func(v int, _ int) { sum += v })
- }
}
}
func benchMapCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- input := collectionInputForMutating(benchInts)
- _ = ctorInts(input).Map(func(v int) int { return v * 3 })
+ input := collectionInputForMutating(benchInts)
+ _ = ctorInts(input).Map(func(v int) int { return v * 3 })
- }
}
}
func benchMapLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- input := benchInts
- _ = lo.Map(input, func(v int, _ int) int { return v * 3 })
+ input := benchInts
+ _ = lo.Map(input, func(v int, _ int) int { return v * 3 })
- }
}
}
func benchReduceCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorInts(benchInts).Reduce(0, func(acc, v int) int { return acc + v })
+ _ = ctorInts(benchInts).Reduce(0, func(acc, v int) int { return acc + v })
- }
}
}
func benchReduceLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Reduce(benchInts, func(acc int, v int, _ int) int { return acc + v }, 0)
+ _ = lo.Reduce(benchInts, func(acc int, v int, _ int) int { return acc + v }, 0)
- }
}
}
func benchFilterCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- input := collectionInputForMutating(benchInts)
- _ = ctorInts(input).Filter(func(v int) bool { return v%3 == 0 })
+ input := collectionInputForMutating(benchInts)
+ _ = ctorInts(input).Filter(func(v int) bool { return v%3 == 0 })
- }
}
}
func benchFilterLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- input := benchInts
- _ = lo.Filter(input, func(v int, _ int) bool { return v%3 == 0 })
+ input := benchInts
+ _ = lo.Filter(input, func(v int, _ int) bool { return v%3 == 0 })
- }
}
}
func benchChunkCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorInts(benchInts).Chunk(benchChunkSize)
+ _ = ctorInts(benchInts).Chunk(benchChunkSize)
- }
}
}
func benchChunkLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Chunk(benchInts, benchChunkSize)
+ _ = lo.Chunk(benchInts, benchChunkSize)
- }
}
}
func benchTakeCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorInts(benchInts).Take(benchTakeN)
+ _ = ctorInts(benchInts).Take(benchTakeN)
- }
}
}
func benchTakeLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Subset(benchInts, 0, uint(benchTakeN))
+ _ = lo.Subset(benchInts, 0, uint(benchTakeN))
- }
}
}
func benchContainsCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.Contains(ctorInts(benchInts), benchSize-1)
+ _ = collection.Contains(ctorInts(benchInts), benchSize-1)
- }
}
}
func benchContainsLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.ContainsBy(benchInts, func(v int) bool { return v == benchSize-1 })
+ _ = lo.ContainsBy(benchInts, func(v int) bool { return v == benchSize-1 })
- }
}
}
func benchFindCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = ctorInts(benchInts).FirstWhere(func(v int) bool { return v == benchSize-1 })
+ _, _ = ctorInts(benchInts).FirstWhere(func(v int) bool { return v == benchSize-1 })
- }
}
}
func benchFindLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = lo.Find(benchInts, func(v int) bool { return v == benchSize-1 })
+ _, _ = lo.Find(benchInts, func(v int) bool { return v == benchSize-1 })
- }
}
}
func benchGroupByCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.GroupBySlice(ctorInts(benchInts), func(v int) int { return v % benchGroupByMod })
+ _ = collection.GroupBySlice(ctorInts(benchInts), func(v int) int { return v % benchGroupByMod })
- }
}
}
func benchGroupByLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.GroupBy(benchInts, func(v int) int { return v % benchGroupByMod })
+ _ = lo.GroupBy(benchInts, func(v int) int { return v % benchGroupByMod })
- }
}
}
func benchCountByCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.CountBy(ctorInts(benchIntsDup), func(v int) int { return v })
+ _ = collection.CountBy(ctorInts(benchIntsDup), func(v int) int { return v })
- }
}
}
func benchCountByLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.CountValuesBy(benchIntsDup, func(v int) int { return v })
+ _ = lo.CountValuesBy(benchIntsDup, func(v int) int { return v })
- }
}
}
func benchCountByValueCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.CountByValue(ctorInts(benchIntsDup))
+ _ = collection.CountByValue(ctorInts(benchIntsDup))
- }
}
}
func benchCountByValueLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.CountValues(benchIntsDup)
+ _ = lo.CountValues(benchIntsDup)
- }
}
}
func benchSkipCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorInts(benchInts).Skip(benchSkipN)
+ _ = ctorInts(benchInts).Skip(benchSkipN)
- }
}
}
func benchSkipLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Drop(benchInts, benchSkipN)
+ _ = lo.Drop(benchInts, benchSkipN)
- }
}
}
func benchSkipLastCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorInts(benchInts).SkipLast(benchSkipN)
+ _ = ctorInts(benchInts).SkipLast(benchSkipN)
- }
}
}
func benchSkipLastLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.DropRight(benchInts, benchSkipN)
+ _ = lo.DropRight(benchInts, benchSkipN)
- }
}
}
func benchReverseCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- input := collectionInputForMutating(benchInts)
- _ = ctorInts(input).Reverse()
+ input := collectionInputForMutating(benchInts)
+ _ = ctorInts(input).Reverse()
- }
}
}
func benchReverseLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- copy(workB, benchInts)
- _ = lo.Reverse(workB)
+ copy(workB, benchInts)
+ _ = lo.Reverse(workB)
- }
}
}
func benchShuffleCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- input := collectionInputForMutating(benchInts)
- _ = ctorInts(input).Shuffle()
+ input := collectionInputForMutating(benchInts)
+ _ = ctorInts(input).Shuffle()
- }
}
}
func benchShuffleLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- copy(workB, benchInts)
- _ = lo.Shuffle(workB)
+ copy(workB, benchInts)
+ _ = lo.Shuffle(workB)
- }
}
}
func benchZipCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.Zip(ctorInts(benchInts), ctorInts(benchIntsDup))
+ _ = collection.Zip(ctorInts(benchInts), ctorInts(benchIntsDup))
- }
}
}
func benchZipLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Zip2(benchInts, benchIntsDup)
+ _ = lo.Zip2(benchInts, benchIntsDup)
- }
}
}
func benchZipWithCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.ZipWith(ctorInts(benchInts), ctorInts(benchIntsDup), func(a, b int) int {
- return a + b
- })
+ _ = collection.ZipWith(ctorInts(benchInts), ctorInts(benchIntsDup), func(a, b int) int {
+ return a + b
+ })
- }
}
}
func benchZipWithLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.ZipBy2(benchInts, benchIntsDup, func(a, b int) int {
- return a + b
- })
+ _ = lo.ZipBy2(benchInts, benchIntsDup, func(a, b int) int {
+ return a + b
+ })
- }
}
}
func benchUniqueCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.UniqueComparable(ctorInts(benchIntsDup))
+ _ = collection.UniqueComparable(ctorInts(benchIntsDup))
- }
}
}
func benchUniqueLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Uniq(benchIntsDup)
+ _ = lo.Uniq(benchIntsDup)
- }
}
}
func benchUniqueByCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.UniqueBy(ctorInts(benchIntsDup), func(v int) int { return v })
+ _ = collection.UniqueBy(ctorInts(benchIntsDup), func(v int) int { return v })
- }
}
}
func benchUniqueByLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.UniqBy(benchIntsDup, func(v int) int { return v })
+ _ = lo.UniqBy(benchIntsDup, func(v int) int { return v })
- }
}
}
func benchUnionCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.Union(ctorInts(unionLeft), ctorInts(unionRight))
+ _ = collection.Union(ctorInts(unionLeft), ctorInts(unionRight))
- }
}
}
func benchUnionLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Union(unionLeft, unionRight)
+ _ = lo.Union(unionLeft, unionRight)
- }
}
}
func benchIntersectCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.Intersect(ctorInts(intersectLeft), ctorInts(intersectRight))
+ _ = collection.Intersect(ctorInts(intersectLeft), ctorInts(intersectRight))
- }
}
}
func benchIntersectLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Intersect(intersectLeft, intersectRight)
+ _ = lo.Intersect(intersectLeft, intersectRight)
- }
}
}
func benchDifferenceCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.Difference(ctorInts(differenceLeft), ctorInts(differenceRight))
+ _ = collection.Difference(ctorInts(differenceLeft), ctorInts(differenceRight))
- }
}
}
func benchDifferenceLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = lo.Difference(differenceLeft, differenceRight)
+ _, _ = lo.Difference(differenceLeft, differenceRight)
- }
}
}
func benchToMapCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = collection.ToMap(ctorInts(benchInts), func(v int) int { return v }, func(v int) int { return v })
+ _ = collection.ToMap(ctorInts(benchInts), func(v int) int { return v }, func(v int) int { return v })
- }
}
}
func benchToMapLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.SliceToMap(benchInts, func(v int) (int, int) { return v, v })
+ _ = lo.SliceToMap(benchInts, func(v int) (int, int) { return v, v })
- }
}
}
func benchSumCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = ctorNumericInt(benchInts).Sum()
+ _ = ctorNumericInt(benchInts).Sum()
- }
}
}
func benchSumLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Sum(benchInts)
+ _ = lo.Sum(benchInts)
- }
}
}
func benchMinCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = ctorNumericInt(benchInts).Min()
+ _, _ = ctorNumericInt(benchInts).Min()
- }
}
}
func benchMinLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Min(benchInts)
+ _ = lo.Min(benchInts)
- }
}
}
func benchMaxCollection(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _, _ = ctorNumericInt(benchInts).Max()
+ _, _ = ctorNumericInt(benchInts).Max()
- }
}
}
func benchMaxLo(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- for j := 0; j < benchInner; j++ {
- _ = lo.Max(benchInts)
+ _ = lo.Max(benchInts)
- }
}
}
From d8bc819577e3285ff4ab195fb79abb2b042ea2d9 Mon Sep 17 00:00:00 2001
From: Chris Miles
Date: Fri, 2 Jan 2026 13:52:56 -0600
Subject: [PATCH 30/30] docs: readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index f7d5a62..b36a4c6 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
- Fluent, Laravel-style Collections for Go - with generics, chainable pipelines, and expressive data transforms.
+ Fluent collections for Go - with generics, chainable pipelines, and expressive data transforms. Inspired by Laravel, designed to feel natural in Go.