From bcb4dd1e9dd3a26f4561866929ccb7835bcd7ffe Mon Sep 17 00:00:00 2001 From: Ruidy Date: Sun, 16 Nov 2025 08:48:38 +0100 Subject: [PATCH] docs: add documentation for new collection functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive documentation for all new functions: - TakeWhile: take elements while predicate is true - DropWhile: drop elements while predicate is true - Scan: running accumulator (prefix scan) - First/FirstN: get first element(s) safely - Init: all but last element - Intersperse: insert separator between elements - Sliding: sliding window views - FoldRight: right-to-left fold/reduce - Tap: side effects without mutation - Transpose: flip matrix rows/columns - Unzip: split tuples into separate slices - ParallelReduce: parallel reduction (experimental) - Replicate: create n copies of a value Each doc includes: - Clear description - Code examples with output - Common use cases - Edge case handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .claude/settings.local.json | 13 ++++++ docs/content/collections/dropwhile.md | 25 ++++++++++ docs/content/collections/first.md | 31 +++++++++++++ docs/content/collections/firstn.md | 23 ++++++++++ docs/content/collections/foldright.md | 39 ++++++++++++++++ docs/content/collections/init.md | 32 +++++++++++++ docs/content/collections/intersperse.md | 28 +++++++++++ docs/content/collections/parallel_reduce.md | 51 +++++++++++++++++++++ docs/content/collections/replicate.md | 43 +++++++++++++++++ docs/content/collections/scan.md | 37 +++++++++++++++ docs/content/collections/sliding.md | 43 +++++++++++++++++ docs/content/collections/takewhile.md | 25 ++++++++++ docs/content/collections/tap.md | 47 +++++++++++++++++++ docs/content/collections/transpose.md | 45 ++++++++++++++++++ docs/content/collections/unzip.md | 43 +++++++++++++++++ 15 files changed, 525 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 docs/content/collections/dropwhile.md create mode 100644 docs/content/collections/first.md create mode 100644 docs/content/collections/firstn.md create mode 100644 docs/content/collections/foldright.md create mode 100644 docs/content/collections/init.md create mode 100644 docs/content/collections/intersperse.md create mode 100644 docs/content/collections/parallel_reduce.md create mode 100644 docs/content/collections/replicate.md create mode 100644 docs/content/collections/scan.md create mode 100644 docs/content/collections/sliding.md create mode 100644 docs/content/collections/takewhile.md create mode 100644 docs/content/collections/tap.md create mode 100644 docs/content/collections/transpose.md create mode 100644 docs/content/collections/unzip.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..a5e210f --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(TestDropWhile\"\ngo test -bench=\"BenchmarkTakeWhile)", + "Bash(gh pr create:*)", + "Bash(TestTranspose)", + "Bash(TestUnzip)", + "Bash(TestParallelReduce)" + ], + "deny": [], + "ask": [] + } +} diff --git a/docs/content/collections/dropwhile.md b/docs/content/collections/dropwhile.md new file mode 100644 index 0000000..dae19c2 --- /dev/null +++ b/docs/content/collections/dropwhile.md @@ -0,0 +1,25 @@ +--- +title: "DropWhile" +date: 2025-01-16T00:00:00-00:00 +--- + +`DropWhile` drops elements from the beginning of the slice while the predicate returns true. It returns the remaining elements starting from the first element where the predicate returns false. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} + lessThan5 := func(n int) bool { return n < 5 } + fmt.Println(u.DropWhile(nums, lessThan5)) // [5, 6, 7, 8, 9] + + words := []string{"apple", "banana", "cherry", "date"} + shortWords := func(s string) bool { return len(s) < 6 } + fmt.Println(u.DropWhile(words, shortWords)) // ["banana", "cherry", "date"] +} +``` diff --git a/docs/content/collections/first.md b/docs/content/collections/first.md new file mode 100644 index 0000000..9346167 --- /dev/null +++ b/docs/content/collections/first.md @@ -0,0 +1,31 @@ +--- +title: "First" +date: 2025-01-16T00:00:00-00:00 +--- + +`First` returns the first element of the slice. Returns an error if the slice is empty. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + nums := []int{1, 2, 3, 4, 5} + first, err := u.First(nums) + if err != nil { + panic(err) + } + fmt.Println(first) // 1 + + // Handle empty slice + empty := []int{} + _, err = u.First(empty) + if err != nil { + fmt.Println("Error:", err) // Error: underscore: empty slice + } +} +``` diff --git a/docs/content/collections/firstn.md b/docs/content/collections/firstn.md new file mode 100644 index 0000000..e93132d --- /dev/null +++ b/docs/content/collections/firstn.md @@ -0,0 +1,23 @@ +--- +title: "FirstN" +date: 2025-01-16T00:00:00-00:00 +--- + +`FirstN` returns the first n elements of the slice. If n is greater than the slice length, returns the entire slice. If n is less than or equal to 0, returns an empty slice. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} + fmt.Println(u.FirstN(nums, 3)) // [1, 2, 3] + fmt.Println(u.FirstN(nums, 0)) // [] + fmt.Println(u.FirstN(nums, 10)) // [1, 2, 3, 4, 5, 6, 7, 8, 9] + fmt.Println(u.FirstN(nums, -5)) // [] +} +``` diff --git a/docs/content/collections/foldright.md b/docs/content/collections/foldright.md new file mode 100644 index 0000000..3d01870 --- /dev/null +++ b/docs/content/collections/foldright.md @@ -0,0 +1,39 @@ +--- +title: "FoldRight" +date: 2025-01-16T00:00:00-00:00 +--- + +`FoldRight` is like Reduce but processes elements from right to left. Also known as foldr in Haskell. Important for non-associative operations where the order of evaluation matters. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + // Subtraction is non-associative + nums := []int{1, 2, 3} + + // FoldRight: 1 - (2 - (3 - 0)) = 1 - (2 - 3) = 1 - (-1) = 2 + result := u.FoldRight(nums, 0, func(n, acc int) int { return n - acc }) + fmt.Println(result) // 2 + + // Compare with Reduce (left fold): (0 - 1) - 2 - 3 = -6 + leftResult := u.Reduce(nums, func(n, acc int) int { return acc - n }, 0) + fmt.Println(leftResult) // -6 + + // Building a list in order + buildList := u.FoldRight(nums, []int{}, func(n int, acc []int) []int { + return append([]int{n}, acc...) + }) + fmt.Println(buildList) // [1, 2, 3] + + // String concatenation + words := []string{"a", "b", "c"} + concat := u.FoldRight(words, "", func(s, acc string) string { return s + acc }) + fmt.Println(concat) // "abc" +} +``` diff --git a/docs/content/collections/init.md b/docs/content/collections/init.md new file mode 100644 index 0000000..2ae7bc7 --- /dev/null +++ b/docs/content/collections/init.md @@ -0,0 +1,32 @@ +--- +title: "Init" +date: 2025-01-16T00:00:00-00:00 +--- + +`Init` returns all elements except the last one, and the last element separately. Returns an empty slice and zero value if the input slice is empty. Useful for destructuring lists from the right. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + nums := []int{1, 2, 3, 4, 5} + init, last := u.Init(nums) + fmt.Println(init) // [1, 2, 3, 4] + fmt.Println(last) // 5 + + // Single element + single, val := u.Init([]int{42}) + fmt.Println(single) // [] + fmt.Println(val) // 42 + + // Empty slice + empty, zero := u.Init([]int{}) + fmt.Println(empty) // [] + fmt.Println(zero) // 0 +} +``` diff --git a/docs/content/collections/intersperse.md b/docs/content/collections/intersperse.md new file mode 100644 index 0000000..a083fec --- /dev/null +++ b/docs/content/collections/intersperse.md @@ -0,0 +1,28 @@ +--- +title: "Intersperse" +date: 2025-01-16T00:00:00-00:00 +--- + +`Intersperse` inserts a separator between each element of the slice. Returns an empty slice if the input is empty. Returns the original element if the input has only one element. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + nums := []int{1, 2, 3, 4, 5} + fmt.Println(u.Intersperse(nums, 0)) // [1, 0, 2, 0, 3, 0, 4, 0, 5] + + // Useful for formatting + words := []string{"apple", "banana", "cherry"} + fmt.Println(u.Intersperse(words, ",")) // ["apple", ",", "banana", ",", "cherry"] + + // Single element - no separator added + single := []int{42} + fmt.Println(u.Intersperse(single, 0)) // [42] +} +``` diff --git a/docs/content/collections/parallel_reduce.md b/docs/content/collections/parallel_reduce.md new file mode 100644 index 0000000..a45b1fe --- /dev/null +++ b/docs/content/collections/parallel_reduce.md @@ -0,0 +1,51 @@ +--- +title: "ParallelReduce" +date: 2025-01-16T00:00:00-00:00 +--- + +`ParallelReduce` applies a reduction function in parallel using a worker pool. The operation must be associative and commutative for correct results. If workers <= 0, defaults to GOMAXPROCS. On error, the first error is returned and processing is canceled. + +**Note:** This is an experimental function. Order of operations is not guaranteed, so use only with associative and commutative operations (like addition, multiplication, min, max). + +```go +package main + +import ( + "context" + "fmt" + "time" + u "github.com/rjNemo/underscore" +) + +func main() { + nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + ctx := context.Background() + + // Parallel sum (safe - addition is associative and commutative) + result, err := u.ParallelReduce(ctx, nums, 4, func(ctx context.Context, n int, acc int) (int, error) { + // Simulate expensive computation + time.Sleep(10 * time.Millisecond) + return n + acc, nil + }, 0) + + if err != nil { + panic(err) + } + fmt.Println(result) // Result will vary due to parallel execution + + // With context cancellation + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + _, err = u.ParallelReduce(ctx, nums, 4, func(ctx context.Context, n int, acc int) (int, error) { + time.Sleep(100 * time.Millisecond) + return n + acc, nil + }, 0) + + if err != nil { + fmt.Println("Operation was cancelled:", err) + } +} +``` + +**Warning:** Do not use ParallelReduce for non-associative operations like subtraction or division, as the results will be unpredictable due to parallel execution order. diff --git a/docs/content/collections/replicate.md b/docs/content/collections/replicate.md new file mode 100644 index 0000000..7bdcdc6 --- /dev/null +++ b/docs/content/collections/replicate.md @@ -0,0 +1,43 @@ +--- +title: "Replicate" +date: 2025-01-16T00:00:00-00:00 +--- + +`Replicate` creates a slice containing count copies of value. Returns an empty slice if count is less than or equal to 0. Useful for initialization and testing. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + // Basic usage + fmt.Println(u.Replicate(3, "hello")) + // ["hello", "hello", "hello"] + + // Numbers + fmt.Println(u.Replicate(5, 0)) + // [0, 0, 0, 0, 0] + + // Zero count + fmt.Println(u.Replicate(0, 42)) + // [] + + // Negative count + fmt.Println(u.Replicate(-5, "x")) + // [] + + // Use case: initialize with default values + defaultScores := u.Replicate(10, 100) + fmt.Println(defaultScores) + // [100, 100, 100, 100, 100, 100, 100, 100, 100, 100] + + // Use case: creating separators + separator := u.Replicate(40, "-") + fmt.Println(u.Reduce(separator, func(s, acc string) string { return acc + s }, "")) + // ---------------------------------------- +} +``` diff --git a/docs/content/collections/scan.md b/docs/content/collections/scan.md new file mode 100644 index 0000000..ef96002 --- /dev/null +++ b/docs/content/collections/scan.md @@ -0,0 +1,37 @@ +--- +title: "Scan" +date: 2025-01-16T00:00:00-00:00 +--- + +`Scan` is like Reduce but returns all intermediate accumulator values. Also known as prefix scan or cumulative fold. Useful for tracking running totals, running maximums, or other cumulative operations. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + // Running sum + nums := []int{1, 2, 3, 4} + add := func(acc, n int) int { return acc + n } + fmt.Println(u.Scan(nums, 0, add)) // [1, 3, 6, 10] + + // Running maximum + values := []int{3, 1, 4, 1, 5, 9, 2} + max := func(acc, n int) int { + if n > acc { + return n + } + return acc + } + fmt.Println(u.Scan(values, 0, max)) // [3, 3, 4, 4, 5, 9, 9] + + // String concatenation + words := []string{"hello", "world", "!"} + concat := func(acc, s string) string { return acc + s } + fmt.Println(u.Scan(words, "", concat)) // ["hello", "helloworld", "helloworld!"] +} +``` diff --git a/docs/content/collections/sliding.md b/docs/content/collections/sliding.md new file mode 100644 index 0000000..db86d50 --- /dev/null +++ b/docs/content/collections/sliding.md @@ -0,0 +1,43 @@ +--- +title: "Sliding" +date: 2025-01-16T00:00:00-00:00 +--- + +`Sliding` creates a sliding window view of the slice with the specified window size. Returns an empty slice if size is less than or equal to 0 or greater than the slice length. Useful for moving averages, n-grams, and pattern matching. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + nums := []int{1, 2, 3, 4, 5} + fmt.Println(u.Sliding(nums, 3)) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]] + + // Size 2 + fmt.Println(u.Sliding(nums, 2)) // [[1, 2], [2, 3], [3, 4], [4, 5]] + + // N-grams for text + words := []string{"the", "quick", "brown", "fox"} + bigrams := u.Sliding(words, 2) + fmt.Println(bigrams) // [["the", "quick"], ["quick", "brown"], ["brown", "fox"]] + + // Moving average example + data := []int{10, 20, 30, 40, 50} + windows := u.Sliding(data, 3) + for _, window := range windows { + sum := 0 + for _, v := range window { + sum += v + } + avg := sum / len(window) + fmt.Printf("Window: %v, Average: %d\n", window, avg) + } + // Window: [10 20 30], Average: 20 + // Window: [20 30 40], Average: 30 + // Window: [30 40 50], Average: 40 +} +``` diff --git a/docs/content/collections/takewhile.md b/docs/content/collections/takewhile.md new file mode 100644 index 0000000..c6e71b6 --- /dev/null +++ b/docs/content/collections/takewhile.md @@ -0,0 +1,25 @@ +--- +title: "TakeWhile" +date: 2025-01-16T00:00:00-00:00 +--- + +`TakeWhile` returns elements from the beginning of the slice while the predicate returns true. It stops at the first element where the predicate returns false. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} + lessThan5 := func(n int) bool { return n < 5 } + fmt.Println(u.TakeWhile(nums, lessThan5)) // [1, 2, 3, 4] + + words := []string{"apple", "banana", "cherry", "date"} + shortWords := func(s string) bool { return len(s) < 6 } + fmt.Println(u.TakeWhile(words, shortWords)) // ["apple"] +} +``` diff --git a/docs/content/collections/tap.md b/docs/content/collections/tap.md new file mode 100644 index 0000000..3cd75eb --- /dev/null +++ b/docs/content/collections/tap.md @@ -0,0 +1,47 @@ +--- +title: "Tap" +date: 2025-01-16T00:00:00-00:00 +--- + +`Tap` applies a function to each element for side effects (like debugging or logging) and returns the original slice unchanged. Useful for debugging pipelines without breaking the flow. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + // Debugging a pipeline + nums := []int{1, 2, 3, 4, 5} + + result := u.Tap( + u.Map( + u.Filter(nums, func(n int) bool { return n%2 == 0 }), + func(n int) int { return n * 2 }, + ), + func(n int) { + fmt.Printf("Debug: %d\n", n) // Prints each value + }, + ) + + fmt.Println(result) // [4, 8] + + // Counting elements that pass through + count := 0 + filtered := u.Tap( + u.Filter(nums, func(n int) bool { return n > 2 }), + func(n int) { count++ }, + ) + fmt.Printf("Found %d elements: %v\n", count, filtered) + // Found 3 elements: [3 4 5] + + // Logging transformations + data := []string{"hello", "world"} + u.Tap(data, func(s string) { + fmt.Printf("Processing: %s\n", s) + }) +} +``` diff --git a/docs/content/collections/transpose.md b/docs/content/collections/transpose.md new file mode 100644 index 0000000..6f7a6bf --- /dev/null +++ b/docs/content/collections/transpose.md @@ -0,0 +1,45 @@ +--- +title: "Transpose" +date: 2025-01-16T00:00:00-00:00 +--- + +`Transpose` flips a matrix over its diagonal, swapping rows and columns. Returns an empty slice if the input is empty. Assumes all rows have the same length (uses the length of the first row). + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + // 2x3 matrix becomes 3x2 matrix + matrix := [][]int{ + {1, 2, 3}, + {4, 5, 6}, + } + transposed := u.Transpose(matrix) + fmt.Println(transposed) + // [[1, 4], [2, 5], [3, 6]] + + // Square matrix + square := [][]int{ + {1, 2}, + {3, 4}, + } + fmt.Println(u.Transpose(square)) + // [[1, 3], [2, 4]] + + // Use case: converting rows to columns for processing + data := [][]string{ + {"Name", "Age", "City"}, + {"Alice", "30", "NYC"}, + {"Bob", "25", "LA"}, + } + byColumn := u.Transpose(data) + fmt.Println("Names:", byColumn[0]) // [Name Alice Bob] + fmt.Println("Ages:", byColumn[1]) // [Age 30 25] + fmt.Println("Cities:", byColumn[2]) // [City NYC LA] +} +``` diff --git a/docs/content/collections/unzip.md b/docs/content/collections/unzip.md new file mode 100644 index 0000000..d4fa505 --- /dev/null +++ b/docs/content/collections/unzip.md @@ -0,0 +1,43 @@ +--- +title: "Unzip" +date: 2025-01-16T00:00:00-00:00 +--- + +`Unzip` splits a slice of tuples into two separate slices. The inverse operation of Zip. Useful for separating paired data. + +```go +package main + +import ( + "fmt" + u "github.com/rjNemo/underscore" +) + +func main() { + // Basic usage + pairs := []u.Tuple[int, string]{ + {Left: 1, Right: "a"}, + {Left: 2, Right: "b"}, + {Left: 3, Right: "c"}, + } + + nums, letters := u.Unzip(pairs) + fmt.Println(nums) // [1, 2, 3] + fmt.Println(letters) // ["a", "b", "c"] + + // Use case: separating keys and values + keyValuePairs := []u.Tuple[string, int]{ + {Left: "apple", Right: 5}, + {Left: "banana", Right: 3}, + {Left: "cherry", Right: 8}, + } + + items, counts := u.Unzip(keyValuePairs) + fmt.Println("Items:", items) // Items: [apple banana cherry] + fmt.Println("Counts:", counts) // Counts: [5 3 8] + + // Empty slice + emptyNums, emptyStrs := u.Unzip([]u.Tuple[int, string]{}) + fmt.Println(emptyNums, emptyStrs) // [] [] +} +```