[
  {
    "path": ".github/workflows/go.yml",
    "content": "name: Go\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Set up Go\n      uses: actions/setup-go@v2\n      with:\n        stable: false\n        go-version: 1.18.0-beta1\n\n    - name: Build\n      run: go build -v ./...\n\n    - name: Test\n      run: go test -v ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\n# Project files\n.idea/\njava_tests/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# functools Contributing guide\n\nThank you for considering contributing to functools! To get an overview of the project, read the [README](README.md) and peruse through the codebase. \n\n### Open an Issue\n\nAll 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.\n\n### Fork and PR\n\nMake a fork of this repository and commit your approriate changes. Afterwards, submit a PR for review. \n\n### PR merged!\n\nIf approved, your work will be merged and added to the Golang functools library 🎉🎉 Thank you for your contribution!\n\n### Questions?\n\nEmail me at rakeeb.hossain1@gmail.com and I'll get back to you ASAP\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Rakeeb Hossain\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# functools\n\n[![Go](https://github.com/rakeeb-hossain/functools/actions/workflows/go.yml/badge.svg)](https://github.com/rakeeb-hossain/functools/actions/workflows/go.yml)\n\nfunctools is a simple Go library that brings you your favourite functional paradigms without sacrificing type-safety using \n`interface{}` or `reflect`\n\nMade possible by Go 1.18 using the newly introduced generics.\n\n## Features\n \n- Any\n- All\n- Count\n- Filter\n- ForEach\n- Map\n- Reduce\n- ReduceRight\n- Sum\n- Chunk\n\n## Installation\n\n`go get -u github.com/rakeeb-hossain/functools`\n\n## Usage\n\n```go\nimport (\n    \"github.com/rakeeb-hossain/functools\"\n    \"fmt\"\n)\n\ntype User struct {\n\tusername     string\n\thasPortfolio bool\n}\n\nvar users = []User{\n\t\t{\"gopher\", true},\n\t\t{\"rakeeb\", false},\n\t\t{\"jack\", true}}\n\nfunc main() {\n    // Count users with linked portfolios\n    fmt.Printf(\"num users with linked portfolios: %d\", \n        functools.Count(users, func(u User) bool { return u.hasPortfolio }))\n\n    // Print usernames of users with linked portfolios\n    functools.ForEach(\n        functools.Filter(users, func(u User) bool { return u.hasPortfolio }),\n        func(u User) { fmt.Printf(\"%s has a linked portfolio\\n\", u.username) })\n}\n```\n\n## Documentation\n\nhttps://pkg.go.dev does not yet support Go 1.18 packages that use generics: https://github.com/golang/go/issues/48264\n\nFor now, documentation is provided via comments and by running `go doc -all` from the package directory. \n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md)\n"
  },
  {
    "path": "doc.go",
    "content": "// Package functools provides type-safe functional helpers using Go 1.18 generics.\n//\n// functools is intended to allow writing concise functionally-inspired code without sacrificing performance or type-safety,\n// as is the case for functional libraries pre-Go generics that had to use `interface{}` or `reflect`.\n\npackage functools\n"
  },
  {
    "path": "examples/error-handling/error_handling.go",
    "content": "// This showcases how we can handle errors using functools helpers, despite there being no explicit error\n// return types by the functools functions. We make use of passing errors via closures.\n//\n// This is more explicit than it likely would be in practice just to showcase what safe handling capabilities\n// this functools still enables. In practice, checking for errant input as a pre-step before dividing would likely\n// be more effective.\npackage main\n\nimport (\n\t\"errors\"\n\t\"github.com/rakeeb-hossain/functools\"\n\t\"log\"\n)\n\ntype fraction struct {\n\tdividend int\n\tdivisor  int\n}\n\nfunc main() {\n\tfractions := []fraction{{5, 1}, {3, 6}, {2, 0}}\n\n\t// We handle errors by populating an error which we pass to our mapper function via a closure.\n\t// We also return a pointer to a float64 instead of a float64 itself, so we can handle nil types\n\t// in case we encounter an error.\n\tvar err error\n\tsafeDivide := func(f fraction) *float64 {\n\t\tif f.divisor == 0 {\n\t\t\terr = errors.New(\"cannot divide by 0\")\n\t\t\treturn nil\n\t\t}\n\t\tres := float64(f.dividend) / float64(f.divisor)\n\t\treturn &res\n\t}\n\trationalResults := functools.Map(fractions, safeDivide)\n\tif err != nil {\n\t\tlog.Println(err)\n\t}\n\n\t// We can sum the safe rational results using a custom Reduce function\n\tres := functools.Reduce(rationalResults, 0.0, func(accum float64, n *float64) float64 {\n\t\tif n == nil {\n\t\t\treturn accum\n\t\t}\n\t\treturn accum + *n\n\t})\n\n\tlog.Printf(\"the safe sum of fractions %v is %f\\n\", fractions, res)\n}\n"
  },
  {
    "path": "examples/portfolio/portfolio.go",
    "content": "// This showcases several applications of functools helpers to a store of user objects and their associated\n// portfolio holdings.\npackage main\n\nimport (\n\t\"github.com/rakeeb-hossain/functools\"\n\t\"log\"\n)\n\ntype user struct {\n\tusername     string\n\tage          int\n\thasPortfolio bool\n}\n\ntype holding struct {\n\tticker     string\n\tboughtTime int\n\tquantity   float64\n\tprice      float64\n}\n\ntype portfolio struct {\n\tholdings []holding\n}\n\nvar (\n\tusers = []user{\n\t\t{\"gopher\", 21, true},\n\t\t{\"rakeeb\", 20, false},\n\t\t{\"jack\", 22, true}}\n\n\tusersPortfolioMap = map[string]portfolio{\n\t\t\"gopher\": {[]holding{{\"TSLA\", 1639768692, 4.5, 1000}, {\"ABNB\", 1639163892, 2.5, 200}}},\n\t\t\"jack\":   {[]holding{{\"BTC\", 1512933492, 5, 1000}, {\"ETH\", 1639768692, 10, 100}}}}\n)\n\nfunc main() {\n\t// Count users with linked portfolios\n\tlog.Printf(\"num users with linked portfolios: %d\", functools.Count(users, func(u user) bool { return u.hasPortfolio }))\n\n\t// Print usernames of users with linked portfolios\n\tfunctools.ForEach(\n\t\tfunctools.Filter(users, func(u user) bool { return u.hasPortfolio }),\n\t\tfunc(u user) { log.Printf(\"%s has a linked portfolio\\n\", u.username) },\n\t)\n\n\t// For users with connected portfolios, get portfolio values\n\tusersWithPortfolio := functools.Filter(users, func(u user) bool { return u.hasPortfolio })\n\tuserPortfolioValues := functools.Map(usersWithPortfolio, func(u user) float64 {\n\t\treturn functools.Reduce(usersPortfolioMap[u.username].holdings, 0, func(accum float64, h holding) float64 {\n\t\t\treturn accum + h.quantity*h.price\n\t\t})\n\t})\n\n\tfor i, _ := range usersWithPortfolio {\n\t\tlog.Printf(\"user %s has portfolio value %f\\n\", usersWithPortfolio[i].username, userPortfolioValues[i])\n\t}\n\n\t// Get total price of assets in all connected portfolios\n\ttotalVal := functools.Sum(userPortfolioValues)\n\tlog.Printf(\"total asset value: %f\", totalVal)\n}\n"
  },
  {
    "path": "functionals.go",
    "content": "// Contains classic generic functional methods\n\npackage functools\n\n//\n//// Map consumes a slice of a generic type and returns a slice with the supplied mapping function\n//// applied to each element.\n////\n//// mapper should be error-safe. It should handle any errors internally and return the desired type.\n//// If other arguments are required by mapper, mapper should be made a closure with the appropriate\n//// variables referenced.\n//func Map_[A any, B any](mapper func(A) B, iter Spliterator[A]) (res Spliterator[B]) {\n//\tres.tryAdvance = func(fn func(B)) bool {\n//\t\t_mapper := func(a A) {\n//\t\t\tfn(mapper(a))\n//\t\t}\n//\t\treturn iter.tryAdvance(_mapper)\n//\t}\n//\t// res.trySplit = iter.trySplit\n//\n//\treturn res\n//}\n//\n//// Stateful op\n//\n//func Sorted[T any](iter Spliterator[T]) (res Spliterator[T]) {\n//\treturn res\n//}\n//\n////func ChunkIter[T any](iter Iterator[T], len int) Iterator[[]T] {\n////\treturn func() (lst []T, b bool) {\n////\t\tres, ok := Next(iter)\n////\t\tif !ok {\n////\t\t\treturn lst, ok\n////\t\t}\n////\n////\t\tlst = make([]T, len)\n////\t\tlst[0] = res\n////\t\tfor i := 1; i < len; i++ {\n////\t\t\tres, ok := Next(iter)\n////\t\t\tif !ok {\n////\t\t\t\treturn lst, true\n////\t\t\t}\n////\t\t\tlst[i] = res\n////\t\t}\n////\t\treturn lst, true\n////\t}\n////}\n//\n//// Filter consumes a slice of a generic type and returns a slice with only the elements which returned true after\n//// applying the predicate.\n////\n//// Elements are returned in the same order that they were supplied in the slice.\n////\n//// predicate should be error-safe. It should handle any errors internally and return only a bool.\n//// If other arguments are required by predicate, predicate should be made a closure with the appropriate\n//// variables referenced.\n//func Filter[T any, A ~[]T](slice A, predicate func(T) bool) A {\n//\tres := make(A, 0, len(slice))\n//\tfor _, v := range slice {\n//\t\tif predicate(v) {\n//\t\t\tres = append(res, v)\n//\t\t}\n//\t}\n//\treturn res\n//}\n//\n//func FilterIter[T any](iter Iterator[T], predicate func(T) bool) Iterator[T] {\n//\treturn func() (t T, b bool) {\n//\t\tfor val, ok := Next(iter); ok; val, ok = Next(iter) {\n//\t\t\tif !ok || predicate(val) {\n//\t\t\t\treturn val, ok\n//\t\t\t}\n//\t\t}\n//\t\treturn t, b // b is false here\n//\t}\n//}\n//\n//// Reduce consumes a slice of a generic type and an initial value. It reduces the slice to a single value by applying\n//// the binary reducer function to each successive element in the slice.\n////\n//// Vacuously, empty slices return the initial value provided.\n////\n//// reducer should be error-safe. It should handle any errors internally and return the desired type.\n//// If other arguments are required by reducer, reducer should be made a closure with the appropriate\n//// variables referenced.\n//func Reduce[T any, R any](iter Spliterator[T], initial R, reducer func(R, T) R) R {\n//\taccum := initial\n//\treturn accum\n//}\n//\n//// ReduceRight consumes a slice of a generic type and an initial value. It\n//// reduces the slice to a single value by applying the binary reducer function\n//// to each element in the slice. ReduceRight differs from Reduce by iterating\n//// from the last element to the first element.\n////\n//// Vacuously, empty slices return the initial value provided.\n////\n//// reducer should error-safe. It should handle any errors internally and return\n//// the desired type. If other arguments are required by reducer, reducer should\n//// be made a closure with the appropriate variables referenced.\n//func ReduceRight[T any, A ~[]T, R any](slice A, initial R, reducer func(R, T) R) R {\n//\taccum := initial\n//\tfor i := len(slice) - 1; i >= 0; i-- {\n//\t\taccum = reducer(accum, slice[i])\n//\t}\n//\treturn accum\n//}\n//\n//// ForEach applies fun to each element in slice\n////\n//// fun should be error-safe and handle errors internally. If other arguments are required by predicate,\n//// predicate should be made a closure with the appropriate variables referenced.\n//func ForEach[T any, A ~[]T](slice A, fun func(T)) {\n//\tfor _, v := range slice {\n//\t\tfun(v)\n//\t}\n//}\n//\n//// Count consumes a slice of a generic type and counts the elements that return true after applying\n//// the predicate.\n////\n//// Vacuously, empty slices return 0 regardless of the predicate.\n////\n//// predicate should be error-safe. It should handle any errors internally and return only a bool.\n//// If other arguments are required by predicate, predicate should be made a closure with the appropriate\n//// variables referenced.\n//func Count[T any, A ~[]T](slice A, predicate func(T) bool) int {\n//\tres := 0\n//\tfor _, v := range slice {\n//\t\tif predicate(v) {\n//\t\t\tres++\n//\t\t}\n//\t}\n//\treturn res\n//}\n"
  },
  {
    "path": "functionals_test.go",
    "content": "package functools\n\n//type user struct {\n//\tage int\n//}\n//\n//// Filter tests\n//func TestGeqFilter(t *testing.T) {\n//\tslice := []user{{17}, {21}, {18}, {32}, {49}, {76}}\n//\tgeqTwentyOne := func(u user) bool { return u.age >= 21 }\n//\tres := Map(Filter(slice, geqTwentyOne), func(u user) int { return u.age })\n//\texpect := []int{21, 32, 49, 76}\n//\n//\tfor i, _ := range res {\n//\t\tif i >= len(expect) || res[i] != expect[i] {\n//\t\t\tt.Errorf(\"TestGeqFilter was incorrect, got: %v, expected: %v\", res, expect)\n//\t\t\treturn\n//\t\t}\n//\t}\n//\n//\tmapper := func(val user) int { return val.age }\n//\tMap(func(val int) int { return val + 1 }, Map(mapper, SliceIter[user](slice)))\n//}\n//\n//// Map tests\n//func TestAddMap(t *testing.T) {\n//\tslice := []int{1, 2, 3}\n//\tadder := func(val int) int { return val + 1 }\n//\tres := Map(slice, adder)\n//\texpect := []int{2, 3, 4}\n//\n//\tfor i, _ := range res {\n//\t\tif i >= len(expect) || res[i] != expect[i] {\n//\t\t\tt.Errorf(\"TestAddMap was incorrect, got: %v, expected: %v\", res, expect)\n//\t\t\treturn\n//\t\t}\n//\t}\n//}\n//\n//func TestAddMapIter(t *testing.T) {\n//\tslice := Iter([]int{1, 2, 3})\n//\tadder := func(val int) int { return val + 1 }\n//\tres := Slice(MapIter(slice, adder))\n//\texpect := []int{2, 3, 4}\n//\n//\tfor i, _ := range res {\n//\t\tif i >= len(expect) || res[i] != expect[i] {\n//\t\t\tt.Errorf(\"TestAddMapIter was incorrect, got: %v, expected: %v\", res, expect)\n//\t\t\treturn\n//\t\t}\n//\t}\n//}\n//\n//func TestUserMap(t *testing.T) {\n//\tslice := []user{{32}, {29}, {42}}\n//\tageTransformer := func(val user) int { return val.age }\n//\tres := Map[user, []user, int](slice, ageTransformer)\n//\texpect := []int{32, 29, 42}\n//\n//\tfor i, _ := range res {\n//\t\tif i >= len(expect) || res[i] != expect[i] {\n//\t\t\tt.Errorf(\"TestUserMap was incorrect, got: %v, expected: %v\", res, expect)\n//\t\t\treturn\n//\t\t}\n//\t}\n//}\n//\n//// Reduce tests\n//func TestReduceSum(t *testing.T) {\n//\tslice := []int{1, 2, 3}\n//\tadder := func(a, b int) int { return a + b }\n//\tres := Reduce(slice, 0, adder)\n//\texpect := 6\n//\n//\tif res != expect {\n//\t\tt.Errorf(\"TestReduceSum was incorrect, got: %d, expected: %d\", res, expect)\n//\t}\n//}\n//\n//func TestReduceUserAge(t *testing.T) {\n//\tslice := []user{{32}, {29}, {42}}\n//\tadder := func(accum int, val user) int { return accum + val.age }\n//\tres := Reduce[user, []user, int](slice, 0, adder)\n//\texpect := 103\n//\n//\tif res != expect {\n//\t\tt.Errorf(\"TestReduceUserAge was incorrect, got: %d, expected: %d\", res, expect)\n//\t}\n//}\n//\n//// ReduceRight tests\n//type reduceRightCase[T any, R comparable] struct {\n//\tname    string\n//\tslice   []T\n//\tinitial R\n//\treducer func(R, T) R\n//\twant    R\n//}\n//\n//func TestReduceRight(t *testing.T) {\n//\tt.Run(\"integers\", func(t *testing.T) {\n//\n//\t\tcases := []reduceRightCase[int, int]{\n//\t\t\t{\n//\t\t\t\tname:    \"addition\",\n//\t\t\t\tslice:   []int{1, 2, 3},\n//\t\t\t\tinitial: 0,\n//\t\t\t\treducer: func(a, b int) int { return a + b },\n//\t\t\t\twant:    6,\n//\t\t\t},\n//\t\t\t{\n//\t\t\t\tname:    \"subtraction\",\n//\t\t\t\tslice:   []int{1, 2, 3},\n//\t\t\t\tinitial: 0,\n//\t\t\t\treducer: func(a, b int) int { return a - b },\n//\t\t\t\twant:    -6,\n//\t\t\t},\n//\t\t\t{\n//\t\t\t\tname:    \"multiplication\",\n//\t\t\t\tslice:   []int{1, 2, 3},\n//\t\t\t\tinitial: 1,\n//\t\t\t\treducer: func(a, b int) int { return a * b },\n//\t\t\t\twant:    6,\n//\t\t\t},\n//\t\t}\n//\n//\t\tfor _, c := range cases {\n//\t\t\tt.Run(c.name, func(t *testing.T) {\n//\t\t\t\tgot := ReduceRight(c.slice, c.initial, c.reducer)\n//\n//\t\t\t\tif got != c.want {\n//\t\t\t\t\tt.Errorf(\"got %v, want %v\", got, c.want)\n//\t\t\t\t}\n//\t\t\t})\n//\t\t}\n//\t})\n//\n//\tt.Run(\"integers and floats\", func(t *testing.T) {\n//\t\tcases := []reduceRightCase[int, float64]{\n//\t\t\t{\n//\t\t\t\tname:    \"division\",\n//\t\t\t\tslice:   []int{1, 2, 3},\n//\t\t\t\tinitial: 1.0,\n//\t\t\t\treducer: func(accum float64, curr int) float64 { return float64(curr) / accum },\n//\t\t\t\twant:    1.5,\n//\t\t\t},\n//\t\t}\n//\n//\t\tfor _, c := range cases {\n//\t\t\tt.Run(c.name, func(t *testing.T) {\n//\t\t\t\tgot := ReduceRight(c.slice, c.initial, c.reducer)\n//\n//\t\t\t\tif got != c.want {\n//\t\t\t\t\tt.Errorf(\"got %v, want %v\", got, c.want)\n//\t\t\t\t}\n//\t\t\t})\n//\t\t}\n//\t})\n//}\n//\n//// ForEach tests\n//func TestClosureForEach(t *testing.T) {\n//\tslice := []int{1, 2, 3}\n//\tres := 0\n//\tForEach(slice, func(val int) { res += val })\n//\texpect := 6\n//\n//\tif res != expect {\n//\t\tt.Errorf(\"TestClosureForEach was incorrect, got: %d, expected: %d\", res, expect)\n//\t}\n//}\n//\n//// Count tests\n//func TestGeqCount(t *testing.T) {\n//\tslice := []int{1, 100, 200, 3, 14, 21, 32}\n//\tres := Count(slice, func(val int) bool { return val >= 21 })\n//\texpected := 4\n//\n//\tif res != expected {\n//\t\tt.Errorf(\"TestLtAny with %v was incorrect, got: %d, expected: %d\", slice, res, expected)\n//\t}\n//}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/rakeeb-hossain/functools\n\ngo 1.18\n\nrequire golang.org/x/exp v0.0.0-20220428152302-39d4317da171 // indirect\n"
  },
  {
    "path": "go.sum",
    "content": "golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5DcL5XQr4au4sZ2F2NV4=\ngolang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=\n"
  },
  {
    "path": "spined_buffer.go",
    "content": "package functools\n\nimport (\n\t\"fmt\"\n)\n\nconst FirstBuffPower int = 4\nconst MinSpineSize int = 2 // must be >= 1\nconst SpineExtendCount int = 1\n\ntype AbstractBuffer[T any] interface {\n\tPush(T)\n\tAt(int)\n\tFlatten()\n\tLen() int\n\tCapacity() int\n}\n\n// SpinedBuffer is an optimization on a regular slice that doesn't require copying elements on re-sizing.\n// This has good performance in cases where an unknown size stream is being processed, since copying from\n// re-sizing is minimized.\ntype SpinedBuffer[T any] struct {\n\t// Spine data structures\n\t// We optimistically assume everything will fit into currBuff in most cases\n\tcurrBuff []T\n\tspines   [][]T\n\n\t// Spine state management\n\tsizeOfPrevBuffers []int\n\tspineIdx          int\n\tflatIdx           int\n\tsizePower         int\n\tcapacity          int\n\tinflated          bool\n}\n\n// Checks if copy is required and copies currBuff to spines\nfunc (s *SpinedBuffer[T]) inflateSpine() {\n\tif s.spineIdx == 0 && s.flatIdx == s.capacity {\n\t\t// Create spines\n\t\ts.spines = make([][]T, MinSpineSize)\n\n\t\t// Assign currBuff to first spine and set sizeOfPrevBuffers\n\t\ts.spines[0] = s.currBuff[:] // should be O(1) since just copying slice\n\t\ts.sizeOfPrevBuffers = make([]int, 1, MinSpineSize)\n\t\ts.sizeOfPrevBuffers[0] = s.flatIdx\n\n\t\t// Update subsequent spines\n\t\tfor i := 1; i < MinSpineSize; i++ {\n\t\t\ts.sizePower++\n\t\t\ts.spines[i] = make([]T, 0, 1<<s.sizePower)\n\t\t\ts.capacity += 1 << s.sizePower\n\t\t}\n\t}\n\ts.inflated = true\n}\n\nfunc CreateSpinedBuffer[T any]() (s SpinedBuffer[T]) {\n\ts.spineIdx = 0\n\ts.flatIdx = 0\n\ts.sizePower = FirstBuffPower\n\ts.capacity = 1 << FirstBuffPower\n\n\ts.currBuff = make([]T, s.capacity)\n\ts.spines = nil\n\ts.sizeOfPrevBuffers = nil\n\n\treturn s\n}\n\nfunc (s SpinedBuffer[T]) Len() int {\n\treturn s.flatIdx\n}\n\nfunc (s SpinedBuffer[T]) Capacity() int {\n\treturn s.capacity\n}\n\nfunc (s *SpinedBuffer[T]) Push(elem T) {\n\tif s.flatIdx < cap(s.currBuff) {\n\t\t// Assign elem to currBuff\n\t\ts.currBuff[s.flatIdx] = elem\n\t\ts.flatIdx++\n\t} else {\n\t\tif !s.inflated {\n\t\t\ts.inflateSpine()\n\t\t}\n\t\t// Check if len == cap\n\t\tif len(s.spines[s.spineIdx]) == (1 << (s.spineIdx + FirstBuffPower)) {\n\t\t\t// Check if we need to extend capacity\n\t\t\tif s.flatIdx == s.capacity {\n\t\t\t\t// Allocate new array into spines, update capacity, and increment spineIdx\n\t\t\t\tfor i := 0; i < SpineExtendCount; i++ {\n\t\t\t\t\ts.sizePower++\n\t\t\t\t\tnewBuff := make([]T, 0, 1<<s.sizePower)\n\t\t\t\t\ts.capacity += 1 << s.sizePower\n\n\t\t\t\t\t// NOTE: this is where the main optimization happens; only need to copy over existing slice\n\t\t\t\t\t// pointers, NOT their respective entries\n\t\t\t\t\ts.spines = append(s.spines, newBuff)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.spineIdx++\n\n\t\t\t// Assign value to new spine\n\t\t\ts.spines[s.spineIdx] = append(s.spines[s.spineIdx], elem)\n\t\t\ts.flatIdx++\n\n\t\t\t// Create new sizeOfPrevBuffers entry\n\t\t\ts.sizeOfPrevBuffers = append(s.sizeOfPrevBuffers, 0)\n\t\t} else {\n\t\t\ts.spines[s.spineIdx] = append(s.spines[s.spineIdx], elem)\n\t\t\ts.flatIdx++\n\t\t}\n\t\ts.sizeOfPrevBuffers[len(s.sizeOfPrevBuffers)-1] = s.flatIdx\n\t}\n}\n\nfunc (s SpinedBuffer[T]) Flatten() []T {\n\tif s.spineIdx == 0 {\n\t\treturn s.currBuff[:s.flatIdx]\n\t}\n\n\tres := make([]T, s.flatIdx)\n\tcurrIdx := 0\n\tfor i := 0; i <= s.spineIdx; i++ {\n\t\tj := 0\n\t\tfor ; currIdx < s.sizeOfPrevBuffers[i]; currIdx++ {\n\t\t\tres[currIdx] = s.spines[i][j]\n\t\t\tj++\n\t\t}\n\t}\n\n\treturn res\n}\n\nfunc (s SpinedBuffer[T]) At(index int) (res T) {\n\tif index < s.flatIdx && index >= 0 {\n\t\tif s.spineIdx == 0 {\n\t\t\tres = s.currBuff[index]\n\t\t} else {\n\t\t\t// binary-search for upper-bound; gives index of first elem in sizeOfPrevBuffers that is >= index\n\t\t\t// this index is guaranteed to be valid since sizeOfPrevBuffers last elem is s.flatIdx and index < s.flatIdx\n\t\t\tspineSizeIdx := upperBoundGuaranteed(index, s.sizeOfPrevBuffers)\n\n\t\t\t// Equality-case where index actually belongs to next spine\n\t\t\tif s.sizeOfPrevBuffers[spineSizeIdx] == index {\n\t\t\t\tres = s.spines[spineSizeIdx+1][0]\n\t\t\t} else {\n\t\t\t\toffset := index // case where index belongs to first spine so spineSizeIdx == 0\n\t\t\t\tif spineSizeIdx > 0 {\n\t\t\t\t\toffset = index - s.sizeOfPrevBuffers[spineSizeIdx-1]\n\t\t\t\t}\n\t\t\t\tres = s.spines[spineSizeIdx][offset]\n\t\t\t}\n\n\t\t}\n\t}\n\treturn res\n}\n\nfunc (s SpinedBuffer[T]) PrintStats() {\n\tfmt.Printf(\"spineIdx: %d\\n\", s.spineIdx)\n\tfmt.Printf(\"flatIdx: %d\\n\", s.flatIdx)\n\tfmt.Printf(\"capacity: %d\\n\", s.capacity)\n\tfmt.Printf(\"sizePower: %d\\n\", s.sizePower)\n}\n\nfunc upperBoundGuaranteed(val int, arr []int) int {\n\tlo := 0\n\thi := len(arr)\n\tfor lo < hi {\n\t\tmid := (hi-lo)/2 + lo\n\n\t\tif arr[mid] >= val {\n\t\t\thi = mid\n\t\t} else {\n\t\t\tlo = mid + 1\n\t\t}\n\t}\n\treturn lo\n}\n"
  },
  {
    "path": "spined_buffer_test.go",
    "content": "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 := 10000\n\tbuff := CreateSpinedBuffer[int]()\n\tarr := make([]int, 0, NUM_TRIALS)\n\n\tfor i := 1; i <= NUM_TRIALS; i++ {\n\t\tbuff.Push(i)\n\t\tarr = append(arr, i)\n\t\tif !reflect.DeepEqual(arr, buff.Flatten()) {\n\t\t\tbuff.PrintStats()\n\t\t\tt.Errorf(\"error at %d\", i)\n\t\t}\n\t}\n}\n\nfunc TestSpinedBuffer_At(t *testing.T) {\n\tNUM_TRIALS := 10000\n\tbuff := CreateSpinedBuffer[int]()\n\n\tfor i := 1; i <= NUM_TRIALS; i++ {\n\t\tbuff.Push(i)\n\n\t\tif buff.Len() != i {\n\t\t\tt.Errorf(\"error at %d\", i)\n\t\t}\n\n\t\tr := rand.Int() % buff.Len()\n\t\tif buff.At(r) != (r + 1) {\n\t\t\tt.Errorf(\"error at index %d on iteration %d\", r, i)\n\t\t}\n\t}\n}\n\nconst BENCHMARK_SIZE int = 10000\n\nfunc BenchmarkCreateSpinedBuffer_Push(b *testing.B) {\n\tbuff := CreateSpinedBuffer[int]()\n\n\tfor i := 0; i < BENCHMARK_SIZE; i++ {\n\t\tbuff.Push(i)\n\t}\n}\n\nfunc BenchmarkSlicePush(b *testing.B) {\n\tslice := make([]int, 0)\n\n\tfor i := 0; i < BENCHMARK_SIZE; i++ {\n\t\tslice = append(slice, i)\n\t}\n}\n\n//func BenchmarkArrayPush(b *testing.B) {\n//\tarr := [BENCHMARK_SIZE]int{}\n//\n//\tfor i := 0; i < BENCHMARK_SIZE; i++ {\n//\t\tarr[i] = i\n//\t}\n//}\n"
  },
  {
    "path": "spliterator.go",
    "content": "package functools\n\nimport (\n\t\"golang.org/x/exp/constraints\"\n)\n\ntype Spliterator[T any] struct {\n\ttryAdvance       func(func(T)) bool\n\tforEachRemaining func(func(T))\n\ttrySplit         func() (Spliterator[T], bool)\n\tcharacteristics  uint\n}\n\nfunc EmptyIter[T any]() (res Spliterator[T]) {\n\tres.tryAdvance = func(func(T)) bool {\n\t\treturn false\n\t}\n\tres.forEachRemaining = func(func(T)) {}\n\tres.trySplit = func() (r Spliterator[T], b bool) {\n\t\treturn r, b\n\t}\n\treturn res\n}\n\nfunc sliceIterRec[T any, A ~[]T](slice A, lo int, hi int) (res Spliterator[T]) {\n\tres.tryAdvance = func(fn func(T)) bool {\n\t\tif lo >= hi {\n\t\t\treturn false\n\t\t}\n\t\tfn(slice[lo])\n\t\tlo++\n\t\treturn true\n\t}\n\n\tres.forEachRemaining = func(fn func(T)) {\n\t\tfor ; lo < hi; lo++ {\n\t\t\tfn(slice[lo])\n\t\t}\n\t}\n\n\tres.trySplit = func() (s Spliterator[T], b bool) {\n\t\tmid := (hi-lo)/2 + lo\n\t\tif mid != lo {\n\t\t\ts, b = sliceIterRec[T, A](slice, mid, hi), true\n\t\t\t// Modify current sliceIter before returning\n\t\t\thi = mid\n\t\t}\n\t\treturn s, b\n\t}\n\treturn res\n}\n\nfunc SliceIter[T any, A ~[]T](slice A) (res Spliterator[T]) {\n\treturn sliceIterRec[T, A](slice, 0, len(slice))\n}\n\nfunc RuleIter[T any, A ~func() (T, bool)](rule A) (res Spliterator[T]) {\n\tres.tryAdvance = func(fn func(T)) bool {\n\t\tr, b := rule()\n\t\tif b {\n\t\t\tfn(r)\n\t\t}\n\t\treturn b\n\t}\n\tres.forEachRemaining = func(fn func(T)) {\n\t\tfor r, b := rule(); b; r, b = rule() {\n\t\t\tfn(r)\n\t\t}\n\t}\n\tres.trySplit = func() (s Spliterator[T], b bool) {\n\t\treturn s, b\n\t}\n\treturn res\n}\n\nfunc sign[T constraints.Integer](x T) int8 {\n\tif x > 0 {\n\t\treturn 1\n\t} else if x < 0 {\n\t\treturn -1\n\t} else {\n\t\treturn 0\n\t}\n}\n\nfunc RangeIter[T constraints.Integer](start, stop T, step T) (res Spliterator[T]) {\n\t// Check to ensure no infinite-loop\n\tif sign(stop-start)*sign(step) < 0 {\n\t\treturn EmptyIter[T]()\n\t}\n\n\tres.tryAdvance = func(fn func(T)) bool {\n\t\tif start >= stop {\n\t\t\treturn false\n\t\t}\n\t\tfn(start)\n\t\tstart += step\n\t\treturn true\n\t}\n\tres.forEachRemaining = func(fn func(T)) {\n\t\tfor ; start < stop; start += step {\n\t\t\tfn(start)\n\t\t}\n\t}\n\tres.trySplit = func() (s Spliterator[T], b bool) {\n\t\tmid := (stop-start)/2 + start\n\t\tif mid != start {\n\t\t\ts, b = RangeIter[T](mid, stop, step), true\n\t\t\t// Modify stop for this iter\n\t\t\tstop = mid\n\t\t}\n\t\treturn s, b\n\t}\n\treturn res\n}\n\nfunc ChanIter[T any, C ~chan T](ch C) (res Spliterator[T]) {\n\tres.tryAdvance = func(fn func(T)) bool {\n\t\tv, ok := <-ch\n\t\tif ok {\n\t\t\tfn(v)\n\t\t}\n\t\treturn ok\n\t}\n\t//res.forNextK = func(fn func(T)) {\n\t//\tfor elem := range ch {\n\t//\t\tfn(elem)\n\t//\t}\n\t//}\n\tres.trySplit = func() (s Spliterator[T], b bool) {\n\t\treturn s, b\n\t}\n\treturn res\n}\n\n// Iterator is a generic iterator on a slice that lazily evaluates the next element in the slice.\n// This is used to lazily evaluate a slice's next value, allowing several applications of functional\n// methods on a single list while only incurring a O(1) memory overhead.\ntype Iterator[T any] func() (T, bool)\n\n// Iter consumes a generic slice and generates a forward-advancing Iterator\n//\n// Iter is passed a copy of the slice. This does not copy the contents of the slice, but the size of\n// the slice is fixed. Therefore, modifications of element the internal slice will affect the Iterator\nfunc Iter[T any, A ~[]T](slice A) Iterator[T] {\n\tindex := 0\n\treturn func() (t T, b bool) {\n\t\tif index >= len(slice) {\n\t\t\treturn t, b // b is false here\n\t\t}\n\t\tindex++\n\t\treturn slice[index-1], true\n\t}\n}\n\n// ReverseIter consumes a generic slice and generates a reverse-advancing Iterator\nfunc ReverseIter[T any, A ~[]T](slice A) Iterator[T] {\n\tindex := len(slice) - 1\n\treturn func() (t T, b bool) {\n\t\tif index < 0 || index >= len(slice) {\n\t\t\treturn t, b // b is false here\n\t\t}\n\t\tindex--\n\t\treturn slice[index+1], true\n\t}\n}\n\n// Slice converts a generic Iterator to a slice of the appropriate type\nfunc Slice[T any](iter Iterator[T]) []T {\n\tres := make([]T, 0)\n\tfor val, ok := iter(); ok; val, ok = iter() {\n\t\tres = append(res, val)\n\t}\n\treturn res\n}\n\n// Next is an alias for advancing the Iterator\nfunc Next[T any](iter Iterator[T]) (T, bool) {\n\treturn iter()\n}\n"
  },
  {
    "path": "spliterator_test.go",
    "content": "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 TestIter(t *testing.T) {\n\tt.Run(\"integer\", func(t *testing.T) {\n\t\tcases := []iterTestCase[int]{\n\t\t\t{\n\t\t\t\t\"integer1\",\n\t\t\t\t[]int{1, 2, 3},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"integer2\",\n\t\t\t\t[]int{100, 0, -1, -1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"empty slice\",\n\t\t\t\t[]int{},\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range cases {\n\t\t\titer := Iter(c.slice)\n\t\t\tfor i, _ := range c.slice {\n\t\t\t\tval, ok := Next(iter)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Errorf(\"got %v, want %v\", nil, c.slice[i])\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif val != c.slice[i] {\n\t\t\t\t\tt.Errorf(\"got %v, want %v\", val, c.slice[i])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Make sure iter is empty\n\t\t\tval, ok := Next(iter)\n\t\t\tif ok {\n\t\t\t\tt.Errorf(\"got %v, want %v\", val, nil)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"slice of string slices\", func(t *testing.T) {\n\t\tcases := []iterTestCase[[]string]{\n\t\t\t{\n\t\t\t\t\"empty string slice of slices\",\n\t\t\t\t[][]string{},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"string1\",\n\t\t\t\t[][]string{{\"asdf\", \"rakeeb\", \"gopher\"}, {\"kevin\", \"trevor\"}},\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range cases {\n\t\t\titer := Iter(c.slice)\n\n\t\t\tfor i, _ := range c.slice {\n\t\t\t\tval, ok := Next(iter)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Errorf(\"got %v, want %v\", nil, c.slice[i])\n\t\t\t\t}\n\t\t\t\tif !reflect.DeepEqual(val, c.slice[i]) {\n\t\t\t\t\tt.Errorf(\"got %v, want %v\", val, c.slice[i])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Make sure iter is empty\n\t\t\tval, ok := Next(iter)\n\t\t\tif ok {\n\t\t\t\tt.Errorf(\"got %v, want %v\", val, nil)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestReverseIter(t *testing.T) {\n\tt.Run(\"integer\", func(t *testing.T) {\n\t\tcases := []iterTestCase[int]{\n\t\t\t{\n\t\t\t\t\"integer1\",\n\t\t\t\t[]int{1, 2, 3},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"integer2\",\n\t\t\t\t[]int{100, 0, -1, -1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"empty slice\",\n\t\t\t\t[]int{},\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range cases {\n\t\t\titer := ReverseIter(c.slice)\n\n\t\t\tfor i := len(c.slice) - 1; i >= 0; i-- {\n\t\t\t\tval, ok := Next(iter)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Errorf(\"got %v, want %v\", nil, c.slice[i])\n\t\t\t\t}\n\t\t\t\tif val != c.slice[i] {\n\t\t\t\t\tt.Errorf(\"got %v, want %v\", val, c.slice[i])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Make sure iter is empty\n\t\t\tval, ok := Next(iter)\n\t\t\tif ok {\n\t\t\t\tt.Errorf(\"got %v, want %v\", val, nil)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"slice of string slices\", func(t *testing.T) {\n\t\tcases := []iterTestCase[[]string]{\n\t\t\t{\n\t\t\t\t\"empty string slice of slices\",\n\t\t\t\t[][]string{},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"string1\",\n\t\t\t\t[][]string{{\"asdf\", \"rakeeb\", \"gopher\"}, {\"kevin\", \"trevor\"}},\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range cases {\n\t\t\titer := ReverseIter(c.slice)\n\n\t\t\tfor i := len(c.slice) - 1; i >= 0; i-- {\n\t\t\t\tval, ok := Next(iter)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Errorf(\"got %v, want %v\", nil, c.slice[i])\n\t\t\t\t}\n\t\t\t\tif !reflect.DeepEqual(val, c.slice[i]) {\n\t\t\t\t\tt.Errorf(\"got %v, want %v\", val, c.slice[i])\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Make sure iter is empty\n\t\t\tval, ok := Next(iter)\n\t\t\tif ok {\n\t\t\t\tt.Errorf(\"got %v, want %v\", val, nil)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc reverse[T any](slice []T) {\n\tstart := 0\n\tlast := len(slice) - 1\n\tfor start < last {\n\t\tslice[start], slice[last] = slice[last], slice[start]\n\t\tstart++\n\t\tlast--\n\t}\n}\n\nfunc TestSlice(t *testing.T) {\n\tt.Run(\"integer\", func(t *testing.T) {\n\t\tcases := []iterTestCase[int]{\n\t\t\t{\n\t\t\t\t\"integer1\",\n\t\t\t\t[]int{1, 2, 3},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"integer2\",\n\t\t\t\t[]int{100, 0, -1, -1000},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"empty slice\",\n\t\t\t\t[]int{},\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range cases {\n\t\t\tslice := Slice(Iter(c.slice))\n\n\t\t\tif !reflect.DeepEqual(slice, c.slice) {\n\t\t\t\tt.Errorf(\"got %v, want %v\", slice, c.slice)\n\t\t\t}\n\t\t}\n\n\t\tfor _, c := range cases {\n\t\t\tslice := Slice(ReverseIter(c.slice))\n\t\t\treverse(slice)\n\n\t\t\tif !reflect.DeepEqual(slice, c.slice) {\n\t\t\t\tt.Errorf(\"got %v, want %v\", slice, c.slice)\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"slice of string slices\", func(t *testing.T) {\n\t\tcases := []iterTestCase[[]string]{\n\t\t\t{\n\t\t\t\t\"empty string slice of slices\",\n\t\t\t\t[][]string{},\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"string1\",\n\t\t\t\t[][]string{{\"asdf\", \"rakeeb\", \"gopher\"}, {\"kevin\", \"trevor\"}},\n\t\t\t},\n\t\t}\n\n\t\tfor _, c := range cases {\n\t\t\tslice := Slice(Iter(c.slice))\n\n\t\t\tif !reflect.DeepEqual(slice, c.slice) {\n\t\t\t\tt.Errorf(\"got %v, want %v\", slice, c.slice)\n\t\t\t}\n\t\t}\n\n\t\tfor _, c := range cases {\n\t\t\tslice := Slice(ReverseIter(c.slice))\n\t\t\treverse(slice)\n\n\t\t\tif !reflect.DeepEqual(slice, c.slice) {\n\t\t\t\tt.Errorf(\"got %v, want %v\", slice, c.slice)\n\t\t\t}\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "stream.go",
    "content": "package functools\n\nconst (\n\tSIZED = 1 << iota\n\tSORTED\n\tDISTINCT\n\tORDERED\n\tUNORDERED\n)\n\ntype StreamStage[T any] interface {\n\tspliterator() Spliterator[T]\n\n\tisStateful() bool\n\tgetParallelism() int\n\tcharacteristics() uint\n\n\topEvalParallelLazy(int)\n}\n\n// StatelessOp struct embedding\ntype InheritUpstream[T any] struct {\n\tupstream *StreamStage[T]\n}\n\nfunc (s InheritUpstream[T]) getParallelism() int {\n\treturn (*s.upstream).getParallelism()\n}\n\nfunc (s InheritUpstream[T]) characteristics() uint {\n\treturn (*s.upstream).characteristics()\n}\n\nfunc (s InheritUpstream[T]) opEvalParallelLazy(n int) {\n\t(*s.upstream).opEvalParallelLazy(n)\n}\n\ntype StatelessOp struct{}\n\nfunc (s StatelessOp) isStateful() bool {\n\treturn false\n}\n\n// SourceStage definition\ntype SourceStage[T any] struct {\n\tStatelessOp\n\tsrc         Spliterator[T]\n\tparallelism int\n}\n\nfunc Stream[T any](spliterator Spliterator[T]) StreamStage[T] {\n\treturn SourceStage[T]{src: spliterator}\n}\n\nfunc ParallelStream[T any](spliterator Spliterator[T], parallelism int) StreamStage[T] {\n\treturn SourceStage[T]{src: spliterator, parallelism: parallelism}\n}\n\nfunc (s SourceStage[T]) spliterator() Spliterator[T] {\n\treturn s.src\n}\n\nfunc (s SourceStage[T]) getParallelism() int {\n\treturn s.parallelism\n}\n\nfunc (s SourceStage[T]) characteristics() uint {\n\treturn s.src.characteristics\n}\n\nfunc (s SourceStage[T]) opEvalParallelLazy(n int) {\n\n}\n\n// All this stuff should probably go into a separate file\n\n// Helpers\nfunc UpstreamToBuffer[T any](src StreamStage[T]) []T {\n\tslice := make([]T, 0)\n\tsrc.spliterator().forEachRemaining(func(e T) {\n\t\tslice = append(slice, e)\n\t})\n\treturn slice\n}\n\n// Map\ntype MapOp[TIn any, TOut any] struct {\n\tStatelessOp\n\tInheritUpstream[TIn]\n\tmapper func(TIn) TOut\n}\n\nfunc Map[TIn any, TOut any](mapper func(TIn) TOut, upstream StreamStage[TIn]) StreamStage[TOut] {\n\treturn MapOp[TIn, TOut]{\n\t\tStatelessOp{},\n\t\tInheritUpstream[TIn]{upstream: &upstream},\n\t\tmapper,\n\t}\n}\n\nfunc mapSpliterator[T any, O any](mapper func(T) O, src Spliterator[T]) (res Spliterator[O]) {\n\tres.tryAdvance = func(fn func(O)) bool {\n\t\twrapper_fn := func(e T) {\n\t\t\tv := mapper(e)\n\t\t\tfn(v)\n\t\t}\n\t\treturn src.tryAdvance(wrapper_fn)\n\t}\n\tres.forEachRemaining = func(fn func(O)) {\n\t\twrapper_fn := func(e T) {\n\t\t\tv := mapper(e)\n\t\t\tfn(v)\n\t\t}\n\t\tsrc.forEachRemaining(wrapper_fn)\n\t}\n\t// Recursive split!!!\n\tres.trySplit = func() (Spliterator[O], bool) {\n\t\tr, b := src.trySplit()\n\t\tif !b {\n\t\t\treturn Spliterator[O]{}, false\n\t\t} else {\n\t\t\treturn mapSpliterator[T, O](mapper, r), true\n\t\t}\n\t}\n\treturn res\n}\n\nfunc (m MapOp[TIn, TOut]) spliterator() (res Spliterator[TOut]) {\n\ts := (*m.InheritUpstream.upstream).spliterator()\n\treturn mapSpliterator[TIn, TOut](m.mapper, s)\n}\n\n// SortOp\ntype SortOp[T any] struct {\n\tInheritUpstream[T]\n\tcmp func(T, T) bool\n}\n\nfunc Sort[T any](cmp func(T, T) bool, upstream StreamStage[T]) StreamStage[T] {\n\treturn SortOp[T]{\n\t\tInheritUpstream[T]{upstream: &upstream},\n\t\tcmp,\n\t}\n}\n\nfunc quicksort[T any](cmp func(T, T) bool, slice []T) {\n\tfor i, _ := range slice {\n\t\tmin_so_far := slice[i]\n\t\tmin_ind := i\n\t\tfor j := i + 1; j < len(slice); j++ {\n\t\t\tif cmp(slice[j], min_so_far) {\n\t\t\t\tmin_so_far = slice[j]\n\t\t\t\tmin_ind = j\n\t\t\t}\n\t\t}\n\t\ttmp := slice[i]\n\t\tslice[i] = slice[min_ind]\n\t\tslice[min_ind] = tmp\n\t}\n}\n\nfunc (m SortOp[T]) spliteratorRec(src Spliterator[T]) (res Spliterator[T]) {\n\tdone := false\n\tbuffer := make([]T, 0, 2)\n\tindex := 0\n\tres.tryAdvance = func(fn func(T)) bool {\n\t\tif !done {\n\t\t\tsrc.forEachRemaining(func(e T) {\n\t\t\t\tbuffer = append(buffer, e)\n\t\t\t})\n\t\t\tquicksort(m.cmp, buffer)\n\t\t\tdone = true\n\t\t}\n\t\tif index >= len(buffer) {\n\t\t\treturn false\n\t\t}\n\t\tfn(buffer[index])\n\t\tindex++\n\t\treturn true\n\t}\n\tres.forEachRemaining = func(fn func(T)) {\n\t\tif !done {\n\t\t\tsrc.forEachRemaining(func(e T) {\n\t\t\t\tbuffer = append(buffer, e)\n\t\t\t})\n\t\t\tquicksort(m.cmp, buffer)\n\t\t\tdone = true\n\t\t}\n\t\tfor _, x := range buffer {\n\t\t\tfn(x)\n\t\t}\n\t}\n\tres.trySplit = func() (Spliterator[T], bool) {\n\t\tr, b := src.trySplit()\n\t\tif !b {\n\t\t\treturn r, b\n\t\t}\n\t\treturn m.spliteratorRec(r), b\n\t}\n\treturn res\n}\n\nfunc (m SortOp[T]) spliterator() (res Spliterator[T]) {\n\ts := (*m.upstream).spliterator()\n\treturn m.spliteratorRec(s)\n}\n\nfunc (s SortOp[T]) isStateful() bool {\n\treturn true\n}\n\nfunc (s SortOp[T]) characteristics() uint {\n\treturn (*s.upstream).characteristics() | SIZED | SORTED | ORDERED\n}\n\nfunc (s SortOp[T]) opEvalParallelLazy(n int) {\n\t(*s.upstream).opEvalParallelLazy(n)\n}\n"
  },
  {
    "path": "stream_test.go",
    "content": "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; i++ {\n\t\tif n%i == 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc TestAny(t *testing.T) {\n\tslice := make([]int, 10000000)\n\tfor i, _ := range slice {\n\t\tslice[i] = i\n\t}\n\titer := ParallelStream(SliceIter(slice), 5)\n\t// s2 := Sort(func(x int, y int) bool { return x < y }, s1)\n\n\tprint(Any(isPrime, iter))\n}\n\nfunc BenchmarkMap(b *testing.B) {\n\tslice := make([]int, 10000000)\n\tfor i, _ := range slice {\n\t\tslice[i] = i\n\t}\n\titer := ParallelStream(SliceIter(slice), 100)\n\ts1 := Map(func(e int) int { return e * -1 }, iter)\n\t// s2 := Sort(func(x int, y int) bool { return x < y }, s1)\n\tSum(s1)\n}\n\nfunc BenchmarkMapSeq(b *testing.B) {\n\tslice := make([]int, 10000000)\n\tfor i, _ := range slice {\n\t\tslice[i] = i\n\t}\n\titer := Stream(SliceIter(slice))\n\ts1 := Map(func(e int) int { return e * -1 }, iter)\n\t// s2 := Sort(func(x int, y int) bool { return x < y }, s1)\n\tSum(s1)\n}\n\nfunc BenchmarkMapFor(b *testing.B) {\n\tslice := make([]int, 10000000)\n\tfor i, _ := range slice {\n\t\tslice[i] = i\n\t}\n\tfor i, _ := range slice {\n\t\tslice[i] *= -1\n\t}\n\tres := 0\n\tfor _, v := range slice {\n\t\tres += v\n\t}\n}\n"
  },
  {
    "path": "terminals.go",
    "content": "// Terminal ops\n\npackage functools\n\nimport \"sync\"\n\n// Helpers\nfunc buildNSplits[T any](n uint32, src Spliterator[T]) []Spliterator[T] {\n\tif n <= 1 {\n\t\treturn []Spliterator[T]{src}\n\t}\n\t// Round N down to a power of 2\n\tvar mask uint32 = 1 << 31\n\tfor n&mask == 0 {\n\t\tmask >>= 1\n\t}\n\t// Alloc results slice\n\tres := make([]Spliterator[T], 0, mask)\n\n\t// In-order traversal of split tree\n\tvar buildNSplitsRec func(uint32, Spliterator[T])\n\tbuildNSplitsRec = func(n uint32, src Spliterator[T]) {\n\t\tif n == 1 {\n\t\t\tres = append(res, src)\n\t\t} else {\n\t\t\tsplit, ok := src.trySplit()\n\t\t\tbuildNSplitsRec(n/2, src)\n\t\t\tif ok {\n\t\t\t\tbuildNSplitsRec(n/2, split)\n\t\t\t}\n\t\t}\n\t}\n\tbuildNSplitsRec(mask, src)\n\n\treturn res\n}\n\n// ForEach\n// 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\nfunc ForEach[T any](fn func(T), stream StreamStage[T]) {\n\tn := stream.getParallelism()\n\tif n <= 1 {\n\t\tstream.spliterator().forEachRemaining(fn)\n\t} else {\n\t\t// Evaluate up to last stateful op\n\t\tstream.opEvalParallelLazy(n)\n\n\t\t// Get n splits\n\t\tsplits := buildNSplits(uint32(n), stream.spliterator())\n\t\tn = len(splits)\n\n\t\t// Perform go-routines\n\t\tvar wg sync.WaitGroup\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\twg.Add(1)\n\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t// TODO: abstract into evalSequential so you don't need to rewrite this everytime\n\t\t\t\tsplits[i].forEachRemaining(fn)\n\t\t\t}(i)\n\t\t}\n\t\twg.Wait()\n\t}\n}\n\n// Summable encompasses all builtin types with the + operator defined on them or any type aliases\n// of these types\ntype Summable interface {\n\t~int | ~int8 | ~int16 | ~int32 | ~int64 |\n\t\t~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |\n\t\t~float32 | ~float64 |\n\t\t~string\n}\n\n// Sum\nfunc Sum[T Summable](stream StreamStage[T]) T {\n\tn := stream.getParallelism()\n\tif n <= 1 {\n\t\tvar res T\n\t\tstream.spliterator().forEachRemaining(func(e T) {\n\t\t\tres += e\n\t\t})\n\t\treturn res\n\t} else {\n\t\t// Evaluate up to last stateful op\n\t\tstream.opEvalParallelLazy(n)\n\n\t\t// Get n splits\n\t\tsplits := buildNSplits(uint32(n), stream.spliterator())\n\t\tn = len(splits)\n\n\t\tvar wg sync.WaitGroup\n\t\tvar mutex sync.Mutex\n\t\tvar res T\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\twg.Add(1)\n\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\tvar tmp T\n\t\t\t\tsplits[i].forEachRemaining(func(e T) {\n\t\t\t\t\ttmp += e\n\t\t\t\t})\n\t\t\t\tmutex.Lock()\n\t\t\t\tres += tmp\n\t\t\t\tmutex.Unlock()\n\t\t\t}(i)\n\t\t}\n\t\twg.Wait()\n\n\t\treturn res\n\t}\n}\n\n// Any\nfunc Any[T any](pred func(T) bool, stream StreamStage[T]) bool {\n\tn := stream.getParallelism()\n\tres := false\n\n\tif n <= 1 {\n\t\twrapPred := func(e T) {\n\t\t\tif pred(e) {\n\t\t\t\tres = true\n\t\t\t}\n\t\t}\n\t\ts := stream.spliterator()\n\t\tfor ok := s.tryAdvance(wrapPred); ok && !res; ok = s.tryAdvance(wrapPred) {\n\t\t}\n\t\treturn res\n\t} else {\n\t\t// Evaluate up to last stateful op\n\t\tstream.opEvalParallelLazy(n)\n\n\t\t// Get n splits\n\t\tsplits := buildNSplits(uint32(n), stream.spliterator())\n\t\tn = len(splits)\n\n\t\tvar wg sync.WaitGroup\n\t\tvar mutex sync.Mutex\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\twg.Add(1)\n\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\n\t\t\t\ttmp := false\n\t\t\t\twrapPred := func(e T) {\n\t\t\t\t\tif pred(e) {\n\t\t\t\t\t\ttmp = true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor ok := splits[i].tryAdvance(wrapPred); ok && !tmp && !res; ok = splits[i].tryAdvance(wrapPred) {\n\t\t\t\t}\n\n\t\t\t\tif tmp {\n\t\t\t\t\tmutex.Lock()\n\t\t\t\t\tres = tmp\n\t\t\t\t\tmutex.Unlock()\n\t\t\t\t}\n\t\t\t}(i)\n\t\t}\n\t\twg.Wait()\n\n\t\treturn res\n\t}\n}\n\n// CollectSlice\n\n// Reduce\n\n//// All consumes a slice of a generic type and applies the predicate to each element in the slice.\n//// All return true if and only if no element returns false after applying the predicate.\n////\n//// Vacuously, empty slices return true regardless of the predicate.\n////\n//// predicate should be error-safe. It should handle any errors internally and return only a bool.\n//// If other arguments are required by predicate, predicate should be made a closure with the appropriate\n//// variables referenced.\n//func All[T any, A ~[]T](slice A, predicate func(T) bool) bool {\n//\tfor _, v := range slice {\n//\t\tif !predicate(v) {\n//\t\t\treturn false\n//\t\t}\n//\t}\n//\treturn true\n//}\n//\n//// Any consumes a slice of a generic type and applies the predicate to each element in the slice.\n//// If any element returns true after applying the predicate, Any returns true.\n////\n//// Vacuously, empty slices return false regardless of the predicate.\n////\n//// predicate should be error-safe. It should handle any errors internally and return only a bool.\n//// If other arguments are required by predicate, predicate should be made a closure with the appropriate\n//// variables referenced.\n//func Any[T any, A ~[]T](slice A, predicate func(T) bool) bool {\n//\tfor _, v := range slice {\n//\t\tif predicate(v) {\n//\t\t\treturn true\n//\t\t}\n//\t}\n//\treturn false\n//}\n//\n//\n//// Sum consumes a slice of a Summable type and sums the elements\n////\n//// Vacuously, empty slices return the zero value of the provided Summable\n//func Sum[S Summable, A ~[]S](slice A) S {\n//\tvar res S\n//\tfor _, v := range slice {\n//\t\tres += v\n//\t}\n//\treturn res\n//}\n"
  },
  {
    "path": "terminals_test.go",
    "content": "package functools\n\n// All tests\n//func TestGeqAll(t *testing.T) {\n//\tslice := []int{100, 25, 20, 31, 30}\n//\tif All(slice, func(val int) bool { return val >= 21 }) {\n//\t\tt.Errorf(\"TestGeqAll with %v was incorrect, got: %v, expected: %v\", slice, true, false)\n//\t}\n//\tslice[2] = 21\n//\tif !All(slice, func(val int) bool { return val >= 21 }) {\n//\t\tt.Errorf(\"TestGeqAll with %v was incorrect, got: %v, expected: %v\", slice, false, true)\n//\t}\n//}\n//\n//// Any tests\n//func TestLtAny(t *testing.T) {\n//\tslice := []int{20, 31, 22}\n//\n//\tif !Any(slice, func(val int) bool { return val < 21 }) {\n//\t\tt.Errorf(\"TestLtAny with %v was incorrect, got: %v, expected: %v\", slice, false, true)\n//\t}\n//\tslice[0] = 21\n//\tif Any(slice, func(val int) bool { return val < 21 }) {\n//\t\tt.Errorf(\"TestLtAny with %v was incorrect, got: %v, expected: %v\", slice, true, false)\n//\t}\n//}\n//\n//func TestIntSum(t *testing.T) {\n//\tslice := []int{1, 2, 3}\n//\tres := Sum(slice)\n//\texpect := 6\n//\n//\tif res != expect {\n//\t\tt.Errorf(\"TestIntSum was incorrect, got: %d, expected: %d\", res, expect)\n//\t}\n//}\n//\n//func TestUintptrSum(t *testing.T) {\n//\tslice := []uintptr{1, 2, 3}\n//\tres := Sum(slice)\n//\texpect := uintptr(6)\n//\n//\tif res != expect {\n//\t\tt.Errorf(\"TestUintptrSum was incorrect, got: %d, expected: %d\", res, expect)\n//\t}\n//}\n//\n//func TestFloatSum(t *testing.T) {\n//\tslice := []float64{0.668, 0.666, 0.666}\n//\tres := Sum(slice)\n//\texpect := 2.0\n//\n//\tif res != expect {\n//\t\tt.Errorf(\"TestFloatSum was incorrect, got: %f, expected: %f\", res, expect)\n//\t}\n//}\n//\n//func TestStringSum(t *testing.T) {\n//\tslice := []string{\"a\", \"b\", \"c\"}\n//\tres := Sum(slice)\n//\texpect := \"abc\"\n//\n//\tif res != expect {\n//\t\tt.Errorf(\"TestStringSum was incorrect, got: %s, expected: %s\", res, expect)\n//\t}\n//}\n//\n//func TestByteSum(t *testing.T) {\n//\tslice := []byte{1, 2, 3}\n//\tres := Sum(slice)\n//\texpect := byte(6)\n//\n//\tif res != expect {\n//\t\tt.Errorf(\"TestByteSum was incorrect, got: %d, expected: %d\", res, expect)\n//\t}\n//}\n"
  }
]