Repository: rakeeb-hossain/functools
Branch: master
Commit: 58f775af362d
Files: 20
Total size: 42.8 KB
Directory structure:
gitextract_s452whe8/
├── .github/
│ └── workflows/
│ └── go.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── doc.go
├── examples/
│ ├── error-handling/
│ │ └── error_handling.go
│ └── portfolio/
│ └── portfolio.go
├── functionals.go
├── functionals_test.go
├── go.mod
├── go.sum
├── spined_buffer.go
├── spined_buffer_test.go
├── spliterator.go
├── spliterator_test.go
├── stream.go
├── stream_test.go
├── terminals.go
└── terminals_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/go.yml
================================================
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
stable: false
go-version: 1.18.0-beta1
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Project files
.idea/
java_tests/
================================================
FILE: CONTRIBUTING.md
================================================
# functools Contributing guide
Thank you for considering contributing to functools! To get an overview of the project, read the [README](README.md) and peruse through the codebase.
### Open an Issue
All contributions should be linked to issues, so if you think a functional component or optimization should be added to functools, start by filing a request there. After discussion with the community, the issue will be assigned to you if deemed a good fit for the library.
### Fork and PR
Make a fork of this repository and commit your approriate changes. Afterwards, submit a PR for review.
### PR merged!
If approved, your work will be merged and added to the Golang functools library 🎉🎉 Thank you for your contribution!
### Questions?
Email me at rakeeb.hossain1@gmail.com and I'll get back to you ASAP
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Rakeeb Hossain
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# functools
[](https://github.com/rakeeb-hossain/functools/actions/workflows/go.yml)
functools is a simple Go library that brings you your favourite functional paradigms without sacrificing type-safety using
`interface{}` or `reflect`
Made possible by Go 1.18 using the newly introduced generics.
## Features
- Any
- All
- Count
- Filter
- ForEach
- Map
- Reduce
- ReduceRight
- Sum
- Chunk
## Installation
`go get -u github.com/rakeeb-hossain/functools`
## Usage
```go
import (
"github.com/rakeeb-hossain/functools"
"fmt"
)
type User struct {
username string
hasPortfolio bool
}
var users = []User{
{"gopher", true},
{"rakeeb", false},
{"jack", true}}
func main() {
// Count users with linked portfolios
fmt.Printf("num users with linked portfolios: %d",
functools.Count(users, func(u User) bool { return u.hasPortfolio }))
// Print usernames of users with linked portfolios
functools.ForEach(
functools.Filter(users, func(u User) bool { return u.hasPortfolio }),
func(u User) { fmt.Printf("%s has a linked portfolio\n", u.username) })
}
```
## Documentation
https://pkg.go.dev does not yet support Go 1.18 packages that use generics: https://github.com/golang/go/issues/48264
For now, documentation is provided via comments and by running `go doc -all` from the package directory.
## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md)
================================================
FILE: doc.go
================================================
// Package functools provides type-safe functional helpers using Go 1.18 generics.
//
// functools is intended to allow writing concise functionally-inspired code without sacrificing performance or type-safety,
// as is the case for functional libraries pre-Go generics that had to use `interface{}` or `reflect`.
package functools
================================================
FILE: examples/error-handling/error_handling.go
================================================
// This showcases how we can handle errors using functools helpers, despite there being no explicit error
// return types by the functools functions. We make use of passing errors via closures.
//
// This is more explicit than it likely would be in practice just to showcase what safe handling capabilities
// this functools still enables. In practice, checking for errant input as a pre-step before dividing would likely
// be more effective.
package main
import (
"errors"
"github.com/rakeeb-hossain/functools"
"log"
)
type fraction struct {
dividend int
divisor int
}
func main() {
fractions := []fraction{{5, 1}, {3, 6}, {2, 0}}
// We handle errors by populating an error which we pass to our mapper function via a closure.
// We also return a pointer to a float64 instead of a float64 itself, so we can handle nil types
// in case we encounter an error.
var err error
safeDivide := func(f fraction) *float64 {
if f.divisor == 0 {
err = errors.New("cannot divide by 0")
return nil
}
res := float64(f.dividend) / float64(f.divisor)
return &res
}
rationalResults := functools.Map(fractions, safeDivide)
if err != nil {
log.Println(err)
}
// We can sum the safe rational results using a custom Reduce function
res := functools.Reduce(rationalResults, 0.0, func(accum float64, n *float64) float64 {
if n == nil {
return accum
}
return accum + *n
})
log.Printf("the safe sum of fractions %v is %f\n", fractions, res)
}
================================================
FILE: examples/portfolio/portfolio.go
================================================
// This showcases several applications of functools helpers to a store of user objects and their associated
// portfolio holdings.
package main
import (
"github.com/rakeeb-hossain/functools"
"log"
)
type user struct {
username string
age int
hasPortfolio bool
}
type holding struct {
ticker string
boughtTime int
quantity float64
price float64
}
type portfolio struct {
holdings []holding
}
var (
users = []user{
{"gopher", 21, true},
{"rakeeb", 20, false},
{"jack", 22, true}}
usersPortfolioMap = map[string]portfolio{
"gopher": {[]holding{{"TSLA", 1639768692, 4.5, 1000}, {"ABNB", 1639163892, 2.5, 200}}},
"jack": {[]holding{{"BTC", 1512933492, 5, 1000}, {"ETH", 1639768692, 10, 100}}}}
)
func main() {
// Count users with linked portfolios
log.Printf("num users with linked portfolios: %d", functools.Count(users, func(u user) bool { return u.hasPortfolio }))
// Print usernames of users with linked portfolios
functools.ForEach(
functools.Filter(users, func(u user) bool { return u.hasPortfolio }),
func(u user) { log.Printf("%s has a linked portfolio\n", u.username) },
)
// For users with connected portfolios, get portfolio values
usersWithPortfolio := functools.Filter(users, func(u user) bool { return u.hasPortfolio })
userPortfolioValues := functools.Map(usersWithPortfolio, func(u user) float64 {
return functools.Reduce(usersPortfolioMap[u.username].holdings, 0, func(accum float64, h holding) float64 {
return accum + h.quantity*h.price
})
})
for i, _ := range usersWithPortfolio {
log.Printf("user %s has portfolio value %f\n", usersWithPortfolio[i].username, userPortfolioValues[i])
}
// Get total price of assets in all connected portfolios
totalVal := functools.Sum(userPortfolioValues)
log.Printf("total asset value: %f", totalVal)
}
================================================
FILE: functionals.go
================================================
// Contains classic generic functional methods
package functools
//
//// Map consumes a slice of a generic type and returns a slice with the supplied mapping function
//// applied to each element.
////
//// mapper should be error-safe. It should handle any errors internally and return the desired type.
//// If other arguments are required by mapper, mapper should be made a closure with the appropriate
//// variables referenced.
//func Map_[A any, B any](mapper func(A) B, iter Spliterator[A]) (res Spliterator[B]) {
// res.tryAdvance = func(fn func(B)) bool {
// _mapper := func(a A) {
// fn(mapper(a))
// }
// return iter.tryAdvance(_mapper)
// }
// // res.trySplit = iter.trySplit
//
// return res
//}
//
//// Stateful op
//
//func Sorted[T any](iter Spliterator[T]) (res Spliterator[T]) {
// return res
//}
//
////func ChunkIter[T any](iter Iterator[T], len int) Iterator[[]T] {
//// return func() (lst []T, b bool) {
//// res, ok := Next(iter)
//// if !ok {
//// return lst, ok
//// }
////
//// lst = make([]T, len)
//// lst[0] = res
//// for i := 1; i < len; i++ {
//// res, ok := Next(iter)
//// if !ok {
//// return lst, true
//// }
//// lst[i] = res
//// }
//// return lst, true
//// }
////}
//
//// Filter consumes a slice of a generic type and returns a slice with only the elements which returned true after
//// applying the predicate.
////
//// Elements are returned in the same order that they were supplied in the slice.
////
//// predicate should be error-safe. It should handle any errors internally and return only a bool.
//// If other arguments are required by predicate, predicate should be made a closure with the appropriate
//// variables referenced.
//func Filter[T any, A ~[]T](slice A, predicate func(T) bool) A {
// res := make(A, 0, len(slice))
// for _, v := range slice {
// if predicate(v) {
// res = append(res, v)
// }
// }
// return res
//}
//
//func FilterIter[T any](iter Iterator[T], predicate func(T) bool) Iterator[T] {
// return func() (t T, b bool) {
// for val, ok := Next(iter); ok; val, ok = Next(iter) {
// if !ok || predicate(val) {
// return val, ok
// }
// }
// return t, b // b is false here
// }
//}
//
//// Reduce consumes a slice of a generic type and an initial value. It reduces the slice to a single value by applying
//// the binary reducer function to each successive element in the slice.
////
//// Vacuously, empty slices return the initial value provided.
////
//// reducer should be error-safe. It should handle any errors internally and return the desired type.
//// If other arguments are required by reducer, reducer should be made a closure with the appropriate
//// variables referenced.
//func Reduce[T any, R any](iter Spliterator[T], initial R, reducer func(R, T) R) R {
// accum := initial
// return accum
//}
//
//// ReduceRight consumes a slice of a generic type and an initial value. It
//// reduces the slice to a single value by applying the binary reducer function
//// to each element in the slice. ReduceRight differs from Reduce by iterating
//// from the last element to the first element.
////
//// Vacuously, empty slices return the initial value provided.
////
//// reducer should error-safe. It should handle any errors internally and return
//// the desired type. If other arguments are required by reducer, reducer should
//// be made a closure with the appropriate variables referenced.
//func ReduceRight[T any, A ~[]T, R any](slice A, initial R, reducer func(R, T) R) R {
// accum := initial
// for i := len(slice) - 1; i >= 0; i-- {
// accum = reducer(accum, slice[i])
// }
// return accum
//}
//
//// ForEach applies fun to each element in slice
////
//// fun should be error-safe and handle errors internally. If other arguments are required by predicate,
//// predicate should be made a closure with the appropriate variables referenced.
//func ForEach[T any, A ~[]T](slice A, fun func(T)) {
// for _, v := range slice {
// fun(v)
// }
//}
//
//// Count consumes a slice of a generic type and counts the elements that return true after applying
//// the predicate.
////
//// Vacuously, empty slices return 0 regardless of the predicate.
////
//// predicate should be error-safe. It should handle any errors internally and return only a bool.
//// If other arguments are required by predicate, predicate should be made a closure with the appropriate
//// variables referenced.
//func Count[T any, A ~[]T](slice A, predicate func(T) bool) int {
// res := 0
// for _, v := range slice {
// if predicate(v) {
// res++
// }
// }
// return res
//}
================================================
FILE: functionals_test.go
================================================
package functools
//type user struct {
// age int
//}
//
//// Filter tests
//func TestGeqFilter(t *testing.T) {
// slice := []user{{17}, {21}, {18}, {32}, {49}, {76}}
// geqTwentyOne := func(u user) bool { return u.age >= 21 }
// res := Map(Filter(slice, geqTwentyOne), func(u user) int { return u.age })
// expect := []int{21, 32, 49, 76}
//
// for i, _ := range res {
// if i >= len(expect) || res[i] != expect[i] {
// t.Errorf("TestGeqFilter was incorrect, got: %v, expected: %v", res, expect)
// return
// }
// }
//
// mapper := func(val user) int { return val.age }
// Map(func(val int) int { return val + 1 }, Map(mapper, SliceIter[user](slice)))
//}
//
//// Map tests
//func TestAddMap(t *testing.T) {
// slice := []int{1, 2, 3}
// adder := func(val int) int { return val + 1 }
// res := Map(slice, adder)
// expect := []int{2, 3, 4}
//
// for i, _ := range res {
// if i >= len(expect) || res[i] != expect[i] {
// t.Errorf("TestAddMap was incorrect, got: %v, expected: %v", res, expect)
// return
// }
// }
//}
//
//func TestAddMapIter(t *testing.T) {
// slice := Iter([]int{1, 2, 3})
// adder := func(val int) int { return val + 1 }
// res := Slice(MapIter(slice, adder))
// expect := []int{2, 3, 4}
//
// for i, _ := range res {
// if i >= len(expect) || res[i] != expect[i] {
// t.Errorf("TestAddMapIter was incorrect, got: %v, expected: %v", res, expect)
// return
// }
// }
//}
//
//func TestUserMap(t *testing.T) {
// slice := []user{{32}, {29}, {42}}
// ageTransformer := func(val user) int { return val.age }
// res := Map[user, []user, int](slice, ageTransformer)
// expect := []int{32, 29, 42}
//
// for i, _ := range res {
// if i >= len(expect) || res[i] != expect[i] {
// t.Errorf("TestUserMap was incorrect, got: %v, expected: %v", res, expect)
// return
// }
// }
//}
//
//// Reduce tests
//func TestReduceSum(t *testing.T) {
// slice := []int{1, 2, 3}
// adder := func(a, b int) int { return a + b }
// res := Reduce(slice, 0, adder)
// expect := 6
//
// if res != expect {
// t.Errorf("TestReduceSum was incorrect, got: %d, expected: %d", res, expect)
// }
//}
//
//func TestReduceUserAge(t *testing.T) {
// slice := []user{{32}, {29}, {42}}
// adder := func(accum int, val user) int { return accum + val.age }
// res := Reduce[user, []user, int](slice, 0, adder)
// expect := 103
//
// if res != expect {
// t.Errorf("TestReduceUserAge was incorrect, got: %d, expected: %d", res, expect)
// }
//}
//
//// ReduceRight tests
//type reduceRightCase[T any, R comparable] struct {
// name string
// slice []T
// initial R
// reducer func(R, T) R
// want R
//}
//
//func TestReduceRight(t *testing.T) {
// t.Run("integers", func(t *testing.T) {
//
// cases := []reduceRightCase[int, int]{
// {
// name: "addition",
// slice: []int{1, 2, 3},
// initial: 0,
// reducer: func(a, b int) int { return a + b },
// want: 6,
// },
// {
// name: "subtraction",
// slice: []int{1, 2, 3},
// initial: 0,
// reducer: func(a, b int) int { return a - b },
// want: -6,
// },
// {
// name: "multiplication",
// slice: []int{1, 2, 3},
// initial: 1,
// reducer: func(a, b int) int { return a * b },
// want: 6,
// },
// }
//
// for _, c := range cases {
// t.Run(c.name, func(t *testing.T) {
// got := ReduceRight(c.slice, c.initial, c.reducer)
//
// if got != c.want {
// t.Errorf("got %v, want %v", got, c.want)
// }
// })
// }
// })
//
// t.Run("integers and floats", func(t *testing.T) {
// cases := []reduceRightCase[int, float64]{
// {
// name: "division",
// slice: []int{1, 2, 3},
// initial: 1.0,
// reducer: func(accum float64, curr int) float64 { return float64(curr) / accum },
// want: 1.5,
// },
// }
//
// for _, c := range cases {
// t.Run(c.name, func(t *testing.T) {
// got := ReduceRight(c.slice, c.initial, c.reducer)
//
// if got != c.want {
// t.Errorf("got %v, want %v", got, c.want)
// }
// })
// }
// })
//}
//
//// ForEach tests
//func TestClosureForEach(t *testing.T) {
// slice := []int{1, 2, 3}
// res := 0
// ForEach(slice, func(val int) { res += val })
// expect := 6
//
// if res != expect {
// t.Errorf("TestClosureForEach was incorrect, got: %d, expected: %d", res, expect)
// }
//}
//
//// Count tests
//func TestGeqCount(t *testing.T) {
// slice := []int{1, 100, 200, 3, 14, 21, 32}
// res := Count(slice, func(val int) bool { return val >= 21 })
// expected := 4
//
// if res != expected {
// t.Errorf("TestLtAny with %v was incorrect, got: %d, expected: %d", slice, res, expected)
// }
//}
================================================
FILE: go.mod
================================================
module github.com/rakeeb-hossain/functools
go 1.18
require golang.org/x/exp v0.0.0-20220428152302-39d4317da171 // indirect
================================================
FILE: go.sum
================================================
golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4=
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
================================================
FILE: spined_buffer.go
================================================
package functools
import (
"fmt"
)
const FirstBuffPower int = 4
const MinSpineSize int = 2 // must be >= 1
const SpineExtendCount int = 1
type AbstractBuffer[T any] interface {
Push(T)
At(int)
Flatten()
Len() int
Capacity() int
}
// SpinedBuffer is an optimization on a regular slice that doesn't require copying elements on re-sizing.
// This has good performance in cases where an unknown size stream is being processed, since copying from
// re-sizing is minimized.
type SpinedBuffer[T any] struct {
// Spine data structures
// We optimistically assume everything will fit into currBuff in most cases
currBuff []T
spines [][]T
// Spine state management
sizeOfPrevBuffers []int
spineIdx int
flatIdx int
sizePower int
capacity int
inflated bool
}
// Checks if copy is required and copies currBuff to spines
func (s *SpinedBuffer[T]) inflateSpine() {
if s.spineIdx == 0 && s.flatIdx == s.capacity {
// Create spines
s.spines = make([][]T, MinSpineSize)
// Assign currBuff to first spine and set sizeOfPrevBuffers
s.spines[0] = s.currBuff[:] // should be O(1) since just copying slice
s.sizeOfPrevBuffers = make([]int, 1, MinSpineSize)
s.sizeOfPrevBuffers[0] = s.flatIdx
// Update subsequent spines
for i := 1; i < MinSpineSize; i++ {
s.sizePower++
s.spines[i] = make([]T, 0, 1<<s.sizePower)
s.capacity += 1 << s.sizePower
}
}
s.inflated = true
}
func CreateSpinedBuffer[T any]() (s SpinedBuffer[T]) {
s.spineIdx = 0
s.flatIdx = 0
s.sizePower = FirstBuffPower
s.capacity = 1 << FirstBuffPower
s.currBuff = make([]T, s.capacity)
s.spines = nil
s.sizeOfPrevBuffers = nil
return s
}
func (s SpinedBuffer[T]) Len() int {
return s.flatIdx
}
func (s SpinedBuffer[T]) Capacity() int {
return s.capacity
}
func (s *SpinedBuffer[T]) Push(elem T) {
if s.flatIdx < cap(s.currBuff) {
// Assign elem to currBuff
s.currBuff[s.flatIdx] = elem
s.flatIdx++
} else {
if !s.inflated {
s.inflateSpine()
}
// Check if len == cap
if len(s.spines[s.spineIdx]) == (1 << (s.spineIdx + FirstBuffPower)) {
// Check if we need to extend capacity
if s.flatIdx == s.capacity {
// Allocate new array into spines, update capacity, and increment spineIdx
for i := 0; i < SpineExtendCount; i++ {
s.sizePower++
newBuff := make([]T, 0, 1<<s.sizePower)
s.capacity += 1 << s.sizePower
// NOTE: this is where the main optimization happens; only need to copy over existing slice
// pointers, NOT their respective entries
s.spines = append(s.spines, newBuff)
}
}
s.spineIdx++
// Assign value to new spine
s.spines[s.spineIdx] = append(s.spines[s.spineIdx], elem)
s.flatIdx++
// Create new sizeOfPrevBuffers entry
s.sizeOfPrevBuffers = append(s.sizeOfPrevBuffers, 0)
} else {
s.spines[s.spineIdx] = append(s.spines[s.spineIdx], elem)
s.flatIdx++
}
s.sizeOfPrevBuffers[len(s.sizeOfPrevBuffers)-1] = s.flatIdx
}
}
func (s SpinedBuffer[T]) Flatten() []T {
if s.spineIdx == 0 {
return s.currBuff[:s.flatIdx]
}
res := make([]T, s.flatIdx)
currIdx := 0
for i := 0; i <= s.spineIdx; i++ {
j := 0
for ; currIdx < s.sizeOfPrevBuffers[i]; currIdx++ {
res[currIdx] = s.spines[i][j]
j++
}
}
return res
}
func (s SpinedBuffer[T]) At(index int) (res T) {
if index < s.flatIdx && index >= 0 {
if s.spineIdx == 0 {
res = s.currBuff[index]
} else {
// binary-search for upper-bound; gives index of first elem in sizeOfPrevBuffers that is >= index
// this index is guaranteed to be valid since sizeOfPrevBuffers last elem is s.flatIdx and index < s.flatIdx
spineSizeIdx := upperBoundGuaranteed(index, s.sizeOfPrevBuffers)
// Equality-case where index actually belongs to next spine
if s.sizeOfPrevBuffers[spineSizeIdx] == index {
res = s.spines[spineSizeIdx+1][0]
} else {
offset := index // case where index belongs to first spine so spineSizeIdx == 0
if spineSizeIdx > 0 {
offset = index - s.sizeOfPrevBuffers[spineSizeIdx-1]
}
res = s.spines[spineSizeIdx][offset]
}
}
}
return res
}
func (s SpinedBuffer[T]) PrintStats() {
fmt.Printf("spineIdx: %d\n", s.spineIdx)
fmt.Printf("flatIdx: %d\n", s.flatIdx)
fmt.Printf("capacity: %d\n", s.capacity)
fmt.Printf("sizePower: %d\n", s.sizePower)
}
func upperBoundGuaranteed(val int, arr []int) int {
lo := 0
hi := len(arr)
for lo < hi {
mid := (hi-lo)/2 + lo
if arr[mid] >= val {
hi = mid
} else {
lo = mid + 1
}
}
return lo
}
================================================
FILE: spined_buffer_test.go
================================================
package functools
import (
"math/rand"
"reflect"
"testing"
)
func TestSpinedBuffer_Push(t *testing.T) {
NUM_TRIALS := 10000
buff := CreateSpinedBuffer[int]()
arr := make([]int, 0, NUM_TRIALS)
for i := 1; i <= NUM_TRIALS; i++ {
buff.Push(i)
arr = append(arr, i)
if !reflect.DeepEqual(arr, buff.Flatten()) {
buff.PrintStats()
t.Errorf("error at %d", i)
}
}
}
func TestSpinedBuffer_At(t *testing.T) {
NUM_TRIALS := 10000
buff := CreateSpinedBuffer[int]()
for i := 1; i <= NUM_TRIALS; i++ {
buff.Push(i)
if buff.Len() != i {
t.Errorf("error at %d", i)
}
r := rand.Int() % buff.Len()
if buff.At(r) != (r + 1) {
t.Errorf("error at index %d on iteration %d", r, i)
}
}
}
const BENCHMARK_SIZE int = 10000
func BenchmarkCreateSpinedBuffer_Push(b *testing.B) {
buff := CreateSpinedBuffer[int]()
for i := 0; i < BENCHMARK_SIZE; i++ {
buff.Push(i)
}
}
func BenchmarkSlicePush(b *testing.B) {
slice := make([]int, 0)
for i := 0; i < BENCHMARK_SIZE; i++ {
slice = append(slice, i)
}
}
//func BenchmarkArrayPush(b *testing.B) {
// arr := [BENCHMARK_SIZE]int{}
//
// for i := 0; i < BENCHMARK_SIZE; i++ {
// arr[i] = i
// }
//}
================================================
FILE: spliterator.go
================================================
package functools
import (
"golang.org/x/exp/constraints"
)
type Spliterator[T any] struct {
tryAdvance func(func(T)) bool
forEachRemaining func(func(T))
trySplit func() (Spliterator[T], bool)
characteristics uint
}
func EmptyIter[T any]() (res Spliterator[T]) {
res.tryAdvance = func(func(T)) bool {
return false
}
res.forEachRemaining = func(func(T)) {}
res.trySplit = func() (r Spliterator[T], b bool) {
return r, b
}
return res
}
func sliceIterRec[T any, A ~[]T](slice A, lo int, hi int) (res Spliterator[T]) {
res.tryAdvance = func(fn func(T)) bool {
if lo >= hi {
return false
}
fn(slice[lo])
lo++
return true
}
res.forEachRemaining = func(fn func(T)) {
for ; lo < hi; lo++ {
fn(slice[lo])
}
}
res.trySplit = func() (s Spliterator[T], b bool) {
mid := (hi-lo)/2 + lo
if mid != lo {
s, b = sliceIterRec[T, A](slice, mid, hi), true
// Modify current sliceIter before returning
hi = mid
}
return s, b
}
return res
}
func SliceIter[T any, A ~[]T](slice A) (res Spliterator[T]) {
return sliceIterRec[T, A](slice, 0, len(slice))
}
func RuleIter[T any, A ~func() (T, bool)](rule A) (res Spliterator[T]) {
res.tryAdvance = func(fn func(T)) bool {
r, b := rule()
if b {
fn(r)
}
return b
}
res.forEachRemaining = func(fn func(T)) {
for r, b := rule(); b; r, b = rule() {
fn(r)
}
}
res.trySplit = func() (s Spliterator[T], b bool) {
return s, b
}
return res
}
func sign[T constraints.Integer](x T) int8 {
if x > 0 {
return 1
} else if x < 0 {
return -1
} else {
return 0
}
}
func RangeIter[T constraints.Integer](start, stop T, step T) (res Spliterator[T]) {
// Check to ensure no infinite-loop
if sign(stop-start)*sign(step) < 0 {
return EmptyIter[T]()
}
res.tryAdvance = func(fn func(T)) bool {
if start >= stop {
return false
}
fn(start)
start += step
return true
}
res.forEachRemaining = func(fn func(T)) {
for ; start < stop; start += step {
fn(start)
}
}
res.trySplit = func() (s Spliterator[T], b bool) {
mid := (stop-start)/2 + start
if mid != start {
s, b = RangeIter[T](mid, stop, step), true
// Modify stop for this iter
stop = mid
}
return s, b
}
return res
}
func ChanIter[T any, C ~chan T](ch C) (res Spliterator[T]) {
res.tryAdvance = func(fn func(T)) bool {
v, ok := <-ch
if ok {
fn(v)
}
return ok
}
//res.forNextK = func(fn func(T)) {
// for elem := range ch {
// fn(elem)
// }
//}
res.trySplit = func() (s Spliterator[T], b bool) {
return s, b
}
return res
}
// Iterator is a generic iterator on a slice that lazily evaluates the next element in the slice.
// This is used to lazily evaluate a slice's next value, allowing several applications of functional
// methods on a single list while only incurring a O(1) memory overhead.
type Iterator[T any] func() (T, bool)
// Iter consumes a generic slice and generates a forward-advancing Iterator
//
// Iter is passed a copy of the slice. This does not copy the contents of the slice, but the size of
// the slice is fixed. Therefore, modifications of element the internal slice will affect the Iterator
func Iter[T any, A ~[]T](slice A) Iterator[T] {
index := 0
return func() (t T, b bool) {
if index >= len(slice) {
return t, b // b is false here
}
index++
return slice[index-1], true
}
}
// ReverseIter consumes a generic slice and generates a reverse-advancing Iterator
func ReverseIter[T any, A ~[]T](slice A) Iterator[T] {
index := len(slice) - 1
return func() (t T, b bool) {
if index < 0 || index >= len(slice) {
return t, b // b is false here
}
index--
return slice[index+1], true
}
}
// Slice converts a generic Iterator to a slice of the appropriate type
func Slice[T any](iter Iterator[T]) []T {
res := make([]T, 0)
for val, ok := iter(); ok; val, ok = iter() {
res = append(res, val)
}
return res
}
// Next is an alias for advancing the Iterator
func Next[T any](iter Iterator[T]) (T, bool) {
return iter()
}
================================================
FILE: spliterator_test.go
================================================
package functools
import (
"reflect"
"testing"
)
type iterTestCase[T any] struct {
name string
slice []T
}
func TestIter(t *testing.T) {
t.Run("integer", func(t *testing.T) {
cases := []iterTestCase[int]{
{
"integer1",
[]int{1, 2, 3},
},
{
"integer2",
[]int{100, 0, -1, -1000},
},
{
"empty slice",
[]int{},
},
}
for _, c := range cases {
iter := Iter(c.slice)
for i, _ := range c.slice {
val, ok := Next(iter)
if !ok {
t.Errorf("got %v, want %v", nil, c.slice[i])
continue
}
if val != c.slice[i] {
t.Errorf("got %v, want %v", val, c.slice[i])
}
}
// Make sure iter is empty
val, ok := Next(iter)
if ok {
t.Errorf("got %v, want %v", val, nil)
}
}
})
t.Run("slice of string slices", func(t *testing.T) {
cases := []iterTestCase[[]string]{
{
"empty string slice of slices",
[][]string{},
},
{
"string1",
[][]string{{"asdf", "rakeeb", "gopher"}, {"kevin", "trevor"}},
},
}
for _, c := range cases {
iter := Iter(c.slice)
for i, _ := range c.slice {
val, ok := Next(iter)
if !ok {
t.Errorf("got %v, want %v", nil, c.slice[i])
}
if !reflect.DeepEqual(val, c.slice[i]) {
t.Errorf("got %v, want %v", val, c.slice[i])
}
}
// Make sure iter is empty
val, ok := Next(iter)
if ok {
t.Errorf("got %v, want %v", val, nil)
}
}
})
}
func TestReverseIter(t *testing.T) {
t.Run("integer", func(t *testing.T) {
cases := []iterTestCase[int]{
{
"integer1",
[]int{1, 2, 3},
},
{
"integer2",
[]int{100, 0, -1, -1000},
},
{
"empty slice",
[]int{},
},
}
for _, c := range cases {
iter := ReverseIter(c.slice)
for i := len(c.slice) - 1; i >= 0; i-- {
val, ok := Next(iter)
if !ok {
t.Errorf("got %v, want %v", nil, c.slice[i])
}
if val != c.slice[i] {
t.Errorf("got %v, want %v", val, c.slice[i])
}
}
// Make sure iter is empty
val, ok := Next(iter)
if ok {
t.Errorf("got %v, want %v", val, nil)
}
}
})
t.Run("slice of string slices", func(t *testing.T) {
cases := []iterTestCase[[]string]{
{
"empty string slice of slices",
[][]string{},
},
{
"string1",
[][]string{{"asdf", "rakeeb", "gopher"}, {"kevin", "trevor"}},
},
}
for _, c := range cases {
iter := ReverseIter(c.slice)
for i := len(c.slice) - 1; i >= 0; i-- {
val, ok := Next(iter)
if !ok {
t.Errorf("got %v, want %v", nil, c.slice[i])
}
if !reflect.DeepEqual(val, c.slice[i]) {
t.Errorf("got %v, want %v", val, c.slice[i])
}
}
// Make sure iter is empty
val, ok := Next(iter)
if ok {
t.Errorf("got %v, want %v", val, nil)
}
}
})
}
func reverse[T any](slice []T) {
start := 0
last := len(slice) - 1
for start < last {
slice[start], slice[last] = slice[last], slice[start]
start++
last--
}
}
func TestSlice(t *testing.T) {
t.Run("integer", func(t *testing.T) {
cases := []iterTestCase[int]{
{
"integer1",
[]int{1, 2, 3},
},
{
"integer2",
[]int{100, 0, -1, -1000},
},
{
"empty slice",
[]int{},
},
}
for _, c := range cases {
slice := Slice(Iter(c.slice))
if !reflect.DeepEqual(slice, c.slice) {
t.Errorf("got %v, want %v", slice, c.slice)
}
}
for _, c := range cases {
slice := Slice(ReverseIter(c.slice))
reverse(slice)
if !reflect.DeepEqual(slice, c.slice) {
t.Errorf("got %v, want %v", slice, c.slice)
}
}
})
t.Run("slice of string slices", func(t *testing.T) {
cases := []iterTestCase[[]string]{
{
"empty string slice of slices",
[][]string{},
},
{
"string1",
[][]string{{"asdf", "rakeeb", "gopher"}, {"kevin", "trevor"}},
},
}
for _, c := range cases {
slice := Slice(Iter(c.slice))
if !reflect.DeepEqual(slice, c.slice) {
t.Errorf("got %v, want %v", slice, c.slice)
}
}
for _, c := range cases {
slice := Slice(ReverseIter(c.slice))
reverse(slice)
if !reflect.DeepEqual(slice, c.slice) {
t.Errorf("got %v, want %v", slice, c.slice)
}
}
})
}
================================================
FILE: stream.go
================================================
package functools
const (
SIZED = 1 << iota
SORTED
DISTINCT
ORDERED
UNORDERED
)
type StreamStage[T any] interface {
spliterator() Spliterator[T]
isStateful() bool
getParallelism() int
characteristics() uint
opEvalParallelLazy(int)
}
// StatelessOp struct embedding
type InheritUpstream[T any] struct {
upstream *StreamStage[T]
}
func (s InheritUpstream[T]) getParallelism() int {
return (*s.upstream).getParallelism()
}
func (s InheritUpstream[T]) characteristics() uint {
return (*s.upstream).characteristics()
}
func (s InheritUpstream[T]) opEvalParallelLazy(n int) {
(*s.upstream).opEvalParallelLazy(n)
}
type StatelessOp struct{}
func (s StatelessOp) isStateful() bool {
return false
}
// SourceStage definition
type SourceStage[T any] struct {
StatelessOp
src Spliterator[T]
parallelism int
}
func Stream[T any](spliterator Spliterator[T]) StreamStage[T] {
return SourceStage[T]{src: spliterator}
}
func ParallelStream[T any](spliterator Spliterator[T], parallelism int) StreamStage[T] {
return SourceStage[T]{src: spliterator, parallelism: parallelism}
}
func (s SourceStage[T]) spliterator() Spliterator[T] {
return s.src
}
func (s SourceStage[T]) getParallelism() int {
return s.parallelism
}
func (s SourceStage[T]) characteristics() uint {
return s.src.characteristics
}
func (s SourceStage[T]) opEvalParallelLazy(n int) {
}
// All this stuff should probably go into a separate file
// Helpers
func UpstreamToBuffer[T any](src StreamStage[T]) []T {
slice := make([]T, 0)
src.spliterator().forEachRemaining(func(e T) {
slice = append(slice, e)
})
return slice
}
// Map
type MapOp[TIn any, TOut any] struct {
StatelessOp
InheritUpstream[TIn]
mapper func(TIn) TOut
}
func Map[TIn any, TOut any](mapper func(TIn) TOut, upstream StreamStage[TIn]) StreamStage[TOut] {
return MapOp[TIn, TOut]{
StatelessOp{},
InheritUpstream[TIn]{upstream: &upstream},
mapper,
}
}
func mapSpliterator[T any, O any](mapper func(T) O, src Spliterator[T]) (res Spliterator[O]) {
res.tryAdvance = func(fn func(O)) bool {
wrapper_fn := func(e T) {
v := mapper(e)
fn(v)
}
return src.tryAdvance(wrapper_fn)
}
res.forEachRemaining = func(fn func(O)) {
wrapper_fn := func(e T) {
v := mapper(e)
fn(v)
}
src.forEachRemaining(wrapper_fn)
}
// Recursive split!!!
res.trySplit = func() (Spliterator[O], bool) {
r, b := src.trySplit()
if !b {
return Spliterator[O]{}, false
} else {
return mapSpliterator[T, O](mapper, r), true
}
}
return res
}
func (m MapOp[TIn, TOut]) spliterator() (res Spliterator[TOut]) {
s := (*m.InheritUpstream.upstream).spliterator()
return mapSpliterator[TIn, TOut](m.mapper, s)
}
// SortOp
type SortOp[T any] struct {
InheritUpstream[T]
cmp func(T, T) bool
}
func Sort[T any](cmp func(T, T) bool, upstream StreamStage[T]) StreamStage[T] {
return SortOp[T]{
InheritUpstream[T]{upstream: &upstream},
cmp,
}
}
func quicksort[T any](cmp func(T, T) bool, slice []T) {
for i, _ := range slice {
min_so_far := slice[i]
min_ind := i
for j := i + 1; j < len(slice); j++ {
if cmp(slice[j], min_so_far) {
min_so_far = slice[j]
min_ind = j
}
}
tmp := slice[i]
slice[i] = slice[min_ind]
slice[min_ind] = tmp
}
}
func (m SortOp[T]) spliteratorRec(src Spliterator[T]) (res Spliterator[T]) {
done := false
buffer := make([]T, 0, 2)
index := 0
res.tryAdvance = func(fn func(T)) bool {
if !done {
src.forEachRemaining(func(e T) {
buffer = append(buffer, e)
})
quicksort(m.cmp, buffer)
done = true
}
if index >= len(buffer) {
return false
}
fn(buffer[index])
index++
return true
}
res.forEachRemaining = func(fn func(T)) {
if !done {
src.forEachRemaining(func(e T) {
buffer = append(buffer, e)
})
quicksort(m.cmp, buffer)
done = true
}
for _, x := range buffer {
fn(x)
}
}
res.trySplit = func() (Spliterator[T], bool) {
r, b := src.trySplit()
if !b {
return r, b
}
return m.spliteratorRec(r), b
}
return res
}
func (m SortOp[T]) spliterator() (res Spliterator[T]) {
s := (*m.upstream).spliterator()
return m.spliteratorRec(s)
}
func (s SortOp[T]) isStateful() bool {
return true
}
func (s SortOp[T]) characteristics() uint {
return (*s.upstream).characteristics() | SIZED | SORTED | ORDERED
}
func (s SortOp[T]) opEvalParallelLazy(n int) {
(*s.upstream).opEvalParallelLazy(n)
}
================================================
FILE: stream_test.go
================================================
package functools
import (
"testing"
)
func isPrime(n int) bool {
if n <= 1 {
return false
}
for i := 2; i < n; i++ {
if n%i == 0 {
return false
}
}
return true
}
func TestAny(t *testing.T) {
slice := make([]int, 10000000)
for i, _ := range slice {
slice[i] = i
}
iter := ParallelStream(SliceIter(slice), 5)
// s2 := Sort(func(x int, y int) bool { return x < y }, s1)
print(Any(isPrime, iter))
}
func BenchmarkMap(b *testing.B) {
slice := make([]int, 10000000)
for i, _ := range slice {
slice[i] = i
}
iter := ParallelStream(SliceIter(slice), 100)
s1 := Map(func(e int) int { return e * -1 }, iter)
// s2 := Sort(func(x int, y int) bool { return x < y }, s1)
Sum(s1)
}
func BenchmarkMapSeq(b *testing.B) {
slice := make([]int, 10000000)
for i, _ := range slice {
slice[i] = i
}
iter := Stream(SliceIter(slice))
s1 := Map(func(e int) int { return e * -1 }, iter)
// s2 := Sort(func(x int, y int) bool { return x < y }, s1)
Sum(s1)
}
func BenchmarkMapFor(b *testing.B) {
slice := make([]int, 10000000)
for i, _ := range slice {
slice[i] = i
}
for i, _ := range slice {
slice[i] *= -1
}
res := 0
for _, v := range slice {
res += v
}
}
================================================
FILE: terminals.go
================================================
// Terminal ops
package functools
import "sync"
// Helpers
func buildNSplits[T any](n uint32, src Spliterator[T]) []Spliterator[T] {
if n <= 1 {
return []Spliterator[T]{src}
}
// Round N down to a power of 2
var mask uint32 = 1 << 31
for n&mask == 0 {
mask >>= 1
}
// Alloc results slice
res := make([]Spliterator[T], 0, mask)
// In-order traversal of split tree
var buildNSplitsRec func(uint32, Spliterator[T])
buildNSplitsRec = func(n uint32, src Spliterator[T]) {
if n == 1 {
res = append(res, src)
} else {
split, ok := src.trySplit()
buildNSplitsRec(n/2, src)
if ok {
buildNSplitsRec(n/2, split)
}
}
}
buildNSplitsRec(mask, src)
return res
}
// ForEach
// TODO: figure out if you can abstract most of this. opEvalParallelLazy, buildNSplits, etc. always happen so we might be able to make ForEachOp implement a TerminalOp interface and abstract these
func ForEach[T any](fn func(T), stream StreamStage[T]) {
n := stream.getParallelism()
if n <= 1 {
stream.spliterator().forEachRemaining(fn)
} else {
// Evaluate up to last stateful op
stream.opEvalParallelLazy(n)
// Get n splits
splits := buildNSplits(uint32(n), stream.spliterator())
n = len(splits)
// Perform go-routines
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
// TODO: abstract into evalSequential so you don't need to rewrite this everytime
splits[i].forEachRemaining(fn)
}(i)
}
wg.Wait()
}
}
// Summable encompasses all builtin types with the + operator defined on them or any type aliases
// of these types
type Summable interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}
// Sum
func Sum[T Summable](stream StreamStage[T]) T {
n := stream.getParallelism()
if n <= 1 {
var res T
stream.spliterator().forEachRemaining(func(e T) {
res += e
})
return res
} else {
// Evaluate up to last stateful op
stream.opEvalParallelLazy(n)
// Get n splits
splits := buildNSplits(uint32(n), stream.spliterator())
n = len(splits)
var wg sync.WaitGroup
var mutex sync.Mutex
var res T
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
var tmp T
splits[i].forEachRemaining(func(e T) {
tmp += e
})
mutex.Lock()
res += tmp
mutex.Unlock()
}(i)
}
wg.Wait()
return res
}
}
// Any
func Any[T any](pred func(T) bool, stream StreamStage[T]) bool {
n := stream.getParallelism()
res := false
if n <= 1 {
wrapPred := func(e T) {
if pred(e) {
res = true
}
}
s := stream.spliterator()
for ok := s.tryAdvance(wrapPred); ok && !res; ok = s.tryAdvance(wrapPred) {
}
return res
} else {
// Evaluate up to last stateful op
stream.opEvalParallelLazy(n)
// Get n splits
splits := buildNSplits(uint32(n), stream.spliterator())
n = len(splits)
var wg sync.WaitGroup
var mutex sync.Mutex
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
tmp := false
wrapPred := func(e T) {
if pred(e) {
tmp = true
}
}
for ok := splits[i].tryAdvance(wrapPred); ok && !tmp && !res; ok = splits[i].tryAdvance(wrapPred) {
}
if tmp {
mutex.Lock()
res = tmp
mutex.Unlock()
}
}(i)
}
wg.Wait()
return res
}
}
// CollectSlice
// Reduce
//// All consumes a slice of a generic type and applies the predicate to each element in the slice.
//// All return true if and only if no element returns false after applying the predicate.
////
//// Vacuously, empty slices return true regardless of the predicate.
////
//// predicate should be error-safe. It should handle any errors internally and return only a bool.
//// If other arguments are required by predicate, predicate should be made a closure with the appropriate
//// variables referenced.
//func All[T any, A ~[]T](slice A, predicate func(T) bool) bool {
// for _, v := range slice {
// if !predicate(v) {
// return false
// }
// }
// return true
//}
//
//// Any consumes a slice of a generic type and applies the predicate to each element in the slice.
//// If any element returns true after applying the predicate, Any returns true.
////
//// Vacuously, empty slices return false regardless of the predicate.
////
//// predicate should be error-safe. It should handle any errors internally and return only a bool.
//// If other arguments are required by predicate, predicate should be made a closure with the appropriate
//// variables referenced.
//func Any[T any, A ~[]T](slice A, predicate func(T) bool) bool {
// for _, v := range slice {
// if predicate(v) {
// return true
// }
// }
// return false
//}
//
//
//// Sum consumes a slice of a Summable type and sums the elements
////
//// Vacuously, empty slices return the zero value of the provided Summable
//func Sum[S Summable, A ~[]S](slice A) S {
// var res S
// for _, v := range slice {
// res += v
// }
// return res
//}
================================================
FILE: terminals_test.go
================================================
package functools
// All tests
//func TestGeqAll(t *testing.T) {
// slice := []int{100, 25, 20, 31, 30}
// if All(slice, func(val int) bool { return val >= 21 }) {
// t.Errorf("TestGeqAll with %v was incorrect, got: %v, expected: %v", slice, true, false)
// }
// slice[2] = 21
// if !All(slice, func(val int) bool { return val >= 21 }) {
// t.Errorf("TestGeqAll with %v was incorrect, got: %v, expected: %v", slice, false, true)
// }
//}
//
//// Any tests
//func TestLtAny(t *testing.T) {
// slice := []int{20, 31, 22}
//
// if !Any(slice, func(val int) bool { return val < 21 }) {
// t.Errorf("TestLtAny with %v was incorrect, got: %v, expected: %v", slice, false, true)
// }
// slice[0] = 21
// if Any(slice, func(val int) bool { return val < 21 }) {
// t.Errorf("TestLtAny with %v was incorrect, got: %v, expected: %v", slice, true, false)
// }
//}
//
//func TestIntSum(t *testing.T) {
// slice := []int{1, 2, 3}
// res := Sum(slice)
// expect := 6
//
// if res != expect {
// t.Errorf("TestIntSum was incorrect, got: %d, expected: %d", res, expect)
// }
//}
//
//func TestUintptrSum(t *testing.T) {
// slice := []uintptr{1, 2, 3}
// res := Sum(slice)
// expect := uintptr(6)
//
// if res != expect {
// t.Errorf("TestUintptrSum was incorrect, got: %d, expected: %d", res, expect)
// }
//}
//
//func TestFloatSum(t *testing.T) {
// slice := []float64{0.668, 0.666, 0.666}
// res := Sum(slice)
// expect := 2.0
//
// if res != expect {
// t.Errorf("TestFloatSum was incorrect, got: %f, expected: %f", res, expect)
// }
//}
//
//func TestStringSum(t *testing.T) {
// slice := []string{"a", "b", "c"}
// res := Sum(slice)
// expect := "abc"
//
// if res != expect {
// t.Errorf("TestStringSum was incorrect, got: %s, expected: %s", res, expect)
// }
//}
//
//func TestByteSum(t *testing.T) {
// slice := []byte{1, 2, 3}
// res := Sum(slice)
// expect := byte(6)
//
// if res != expect {
// t.Errorf("TestByteSum was incorrect, got: %d, expected: %d", res, expect)
// }
//}
gitextract_s452whe8/ ├── .github/ │ └── workflows/ │ └── go.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── doc.go ├── examples/ │ ├── error-handling/ │ │ └── error_handling.go │ └── portfolio/ │ └── portfolio.go ├── functionals.go ├── functionals_test.go ├── go.mod ├── go.sum ├── spined_buffer.go ├── spined_buffer_test.go ├── spliterator.go ├── spliterator_test.go ├── stream.go ├── stream_test.go ├── terminals.go └── terminals_test.go
SYMBOL INDEX (85 symbols across 9 files)
FILE: examples/error-handling/error_handling.go
type fraction (line 15) | type fraction struct
function main (line 20) | func main() {
FILE: examples/portfolio/portfolio.go
type user (line 10) | type user struct
type holding (line 16) | type holding struct
type portfolio (line 23) | type portfolio struct
function main (line 38) | func main() {
FILE: spined_buffer.go
constant FirstBuffPower (line 7) | FirstBuffPower int = 4
constant MinSpineSize (line 8) | MinSpineSize int = 2
constant SpineExtendCount (line 9) | SpineExtendCount int = 1
type AbstractBuffer (line 11) | type AbstractBuffer interface
type SpinedBuffer (line 22) | type SpinedBuffer struct
method inflateSpine (line 38) | func (s *SpinedBuffer[T]) inflateSpine() {
function CreateSpinedBuffer (line 58) | func CreateSpinedBuffer[T any]() (s SpinedBuffer[T]) {
method Len (line 71) | func (s SpinedBuffer[T]) Len() int {
method Capacity (line 75) | func (s SpinedBuffer[T]) Capacity() int {
method Push (line 79) | func (s *SpinedBuffer[T]) Push(elem T) {
method Flatten (line 119) | func (s SpinedBuffer[T]) Flatten() []T {
method At (line 137) | func (s SpinedBuffer[T]) At(index int) (res T) {
method PrintStats (line 162) | func (s SpinedBuffer[T]) PrintStats() {
function upperBoundGuaranteed (line 169) | func upperBoundGuaranteed(val int, arr []int) int {
FILE: spined_buffer_test.go
function TestSpinedBuffer_Push (line 9) | func TestSpinedBuffer_Push(t *testing.T) {
function TestSpinedBuffer_At (line 24) | func TestSpinedBuffer_At(t *testing.T) {
constant BENCHMARK_SIZE (line 42) | BENCHMARK_SIZE int = 10000
function BenchmarkCreateSpinedBuffer_Push (line 44) | func BenchmarkCreateSpinedBuffer_Push(b *testing.B) {
function BenchmarkSlicePush (line 52) | func BenchmarkSlicePush(b *testing.B) {
FILE: spliterator.go
type Spliterator (line 7) | type Spliterator struct
function EmptyIter (line 14) | func EmptyIter[T any]() (res Spliterator[T]) {
function sliceIterRec (line 25) | func sliceIterRec[T any, A ~[]T](slice A, lo int, hi int) (res Spliterat...
function SliceIter (line 53) | func SliceIter[T any, A ~[]T](slice A) (res Spliterator[T]) {
function RuleIter (line 57) | func RuleIter[T any, A ~func() (T, bool)](rule A) (res Spliterator[T]) {
function sign (line 76) | func sign[T constraints.Integer](x T) int8 {
function RangeIter (line 86) | func RangeIter[T constraints.Integer](start, stop T, step T) (res Splite...
function ChanIter (line 117) | func ChanIter[T any, C ~chan T](ch C) (res Spliterator[T]) {
type Iterator (line 139) | type Iterator
function Iter (line 145) | func Iter[T any, A ~[]T](slice A) Iterator[T] {
function ReverseIter (line 157) | func ReverseIter[T any, A ~[]T](slice A) Iterator[T] {
function Slice (line 169) | func Slice[T any](iter Iterator[T]) []T {
function Next (line 178) | func Next[T any](iter Iterator[T]) (T, bool) {
FILE: spliterator_test.go
type iterTestCase (line 8) | type iterTestCase struct
function TestIter (line 13) | func TestIter(t *testing.T) {
function TestReverseIter (line 85) | func TestReverseIter(t *testing.T) {
function reverse (line 157) | func reverse[T any](slice []T) {
function TestSlice (line 167) | func TestSlice(t *testing.T) {
FILE: stream.go
constant SIZED (line 4) | SIZED = 1 << iota
constant SORTED (line 5) | SORTED
constant DISTINCT (line 6) | DISTINCT
constant ORDERED (line 7) | ORDERED
constant UNORDERED (line 8) | UNORDERED
type StreamStage (line 11) | type StreamStage interface
type InheritUpstream (line 22) | type InheritUpstream struct
method getParallelism (line 26) | func (s InheritUpstream[T]) getParallelism() int {
method characteristics (line 30) | func (s InheritUpstream[T]) characteristics() uint {
method opEvalParallelLazy (line 34) | func (s InheritUpstream[T]) opEvalParallelLazy(n int) {
type StatelessOp (line 38) | type StatelessOp struct
method isStateful (line 40) | func (s StatelessOp) isStateful() bool {
type SourceStage (line 45) | type SourceStage struct
function Stream (line 51) | func Stream[T any](spliterator Spliterator[T]) StreamStage[T] {
function ParallelStream (line 55) | func ParallelStream[T any](spliterator Spliterator[T], parallelism int) ...
method spliterator (line 59) | func (s SourceStage[T]) spliterator() Spliterator[T] {
method getParallelism (line 63) | func (s SourceStage[T]) getParallelism() int {
method characteristics (line 67) | func (s SourceStage[T]) characteristics() uint {
method opEvalParallelLazy (line 71) | func (s SourceStage[T]) opEvalParallelLazy(n int) {
function UpstreamToBuffer (line 78) | func UpstreamToBuffer[T any](src StreamStage[T]) []T {
type MapOp (line 87) | type MapOp struct
function Map (line 93) | func Map[TIn any, TOut any](mapper func(TIn) TOut, upstream StreamStage[...
function mapSpliterator (line 101) | func mapSpliterator[T any, O any](mapper func(T) O, src Spliterator[T]) ...
method spliterator (line 128) | func (m MapOp[TIn, TOut]) spliterator() (res Spliterator[TOut]) {
type SortOp (line 134) | type SortOp struct
function Sort (line 139) | func Sort[T any](cmp func(T, T) bool, upstream StreamStage[T]) StreamSta...
function quicksort (line 146) | func quicksort[T any](cmp func(T, T) bool, slice []T) {
method spliteratorRec (line 162) | func (m SortOp[T]) spliteratorRec(src Spliterator[T]) (res Spliterator[T...
method spliterator (line 203) | func (m SortOp[T]) spliterator() (res Spliterator[T]) {
method isStateful (line 208) | func (s SortOp[T]) isStateful() bool {
method characteristics (line 212) | func (s SortOp[T]) characteristics() uint {
method opEvalParallelLazy (line 216) | func (s SortOp[T]) opEvalParallelLazy(n int) {
FILE: stream_test.go
function isPrime (line 7) | func isPrime(n int) bool {
function TestAny (line 19) | func TestAny(t *testing.T) {
function BenchmarkMap (line 30) | func BenchmarkMap(b *testing.B) {
function BenchmarkMapSeq (line 41) | func BenchmarkMapSeq(b *testing.B) {
function BenchmarkMapFor (line 52) | func BenchmarkMapFor(b *testing.B) {
FILE: terminals.go
function buildNSplits (line 8) | func buildNSplits[T any](n uint32, src Spliterator[T]) []Spliterator[T] {
function ForEach (line 40) | func ForEach[T any](fn func(T), stream StreamStage[T]) {
type Summable (line 70) | type Summable interface
function Sum (line 78) | func Sum[T Summable](stream StreamStage[T]) T {
function Any (line 120) | func Any[T any](pred func(T) bool, stream StreamStage[T]) bool {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (49K chars).
[
{
"path": ".github/workflows/go.yml",
"chars": 391,
"preview": "name: Go\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\njobs:\n\n build:\n runs-on: ub"
},
{
"path": ".gitignore",
"chars": 305,
"preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Ou"
},
{
"path": "CONTRIBUTING.md",
"chars": 816,
"preview": "# functools Contributing guide\n\nThank you for considering contributing to functools! To get an overview of the project, "
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2021 Rakeeb Hossain\n\nPermission is hereby granted, free of charge, to any person obtaining a "
},
{
"path": "README.md",
"chars": 1509,
"preview": "# functools\n\n[](https://github.com/"
},
{
"path": "doc.go",
"chars": 333,
"preview": "// Package functools provides type-safe functional helpers using Go 1.18 generics.\n//\n// functools is intended to allow "
},
{
"path": "examples/error-handling/error_handling.go",
"chars": 1469,
"preview": "// This showcases how we can handle errors using functools helpers, despite there being no explicit error\n// return type"
},
{
"path": "examples/portfolio/portfolio.go",
"chars": 1839,
"preview": "// This showcases several applications of functools helpers to a store of user objects and their associated\n// portfolio"
},
{
"path": "functionals.go",
"chars": 4588,
"preview": "// Contains classic generic functional methods\n\npackage functools\n\n//\n//// Map consumes a slice of a generic type and re"
},
{
"path": "functionals_test.go",
"chars": 4619,
"preview": "package functools\n\n//type user struct {\n//\tage int\n//}\n//\n//// Filter tests\n//func TestGeqFilter(t *testing.T) {\n//\tslic"
},
{
"path": "go.mod",
"chars": 125,
"preview": "module github.com/rakeeb-hossain/functools\n\ngo 1.18\n\nrequire golang.org/x/exp v0.0.0-20220428152302-39d4317da171 // indi"
},
{
"path": "go.sum",
"chars": 207,
"preview": "golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4=\ngolang.org/x/exp v0."
},
{
"path": "spined_buffer.go",
"chars": 4543,
"preview": "package functools\n\nimport (\n\t\"fmt\"\n)\n\nconst FirstBuffPower int = 4\nconst MinSpineSize int = 2 // must be >= 1\nconst Spin"
},
{
"path": "spined_buffer_test.go",
"chars": 1180,
"preview": "package functools\n\nimport (\n\t\"math/rand\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestSpinedBuffer_Push(t *testing.T) {\n\tNUM_TRIALS"
},
{
"path": "spliterator.go",
"chars": 4020,
"preview": "package functools\n\nimport (\n\t\"golang.org/x/exp/constraints\"\n)\n\ntype Spliterator[T any] struct {\n\ttryAdvance func(f"
},
{
"path": "spliterator_test.go",
"chars": 4182,
"preview": "package functools\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\ntype iterTestCase[T any] struct {\n\tname string\n\tslice []T\n}\n\nfunc "
},
{
"path": "stream.go",
"chars": 4410,
"preview": "package functools\n\nconst (\n\tSIZED = 1 << iota\n\tSORTED\n\tDISTINCT\n\tORDERED\n\tUNORDERED\n)\n\ntype StreamStage[T any] interface"
},
{
"path": "stream_test.go",
"chars": 1194,
"preview": "package functools\n\nimport (\n\t\"testing\"\n)\n\nfunc isPrime(n int) bool {\n\tif n <= 1 {\n\t\treturn false\n\t}\n\tfor i := 2; i < n; "
},
{
"path": "terminals.go",
"chars": 5050,
"preview": "// Terminal ops\n\npackage functools\n\nimport \"sync\"\n\n// Helpers\nfunc buildNSplits[T any](n uint32, src Spliterator[T]) []S"
},
{
"path": "terminals_test.go",
"chars": 1983,
"preview": "package functools\n\n// All tests\n//func TestGeqAll(t *testing.T) {\n//\tslice := []int{100, 25, 20, 31, 30}\n//\tif All(slice"
}
]
About this extraction
This page contains the full source code of the rakeeb-hossain/functools GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (42.8 KB), approximately 13.5k tokens, and a symbol index with 85 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.