├── .github └── workflows │ ├── fuzz.yml │ ├── gen_docs.yml │ ├── go1.19.yml │ ├── go1.20.yml │ ├── go1.21.yml │ ├── go1.22.yml │ ├── go1.23.yml │ └── go1.24.yml ├── LICENSE ├── README.md ├── chans ├── chans.go ├── chans_example_test.go ├── chans_test.go └── testdata │ └── fuzz │ └── FuzzMerge │ ├── 31ad7ffe9ccdf3670ead1115352add5c1f9b9847c9ade353726c041f25abbdd9 │ ├── 35368ec77b179c2ae7b3379d2159f97a05bf52a7c2a028cf23c53e343c8b7593 │ ├── 4b3caf8fedf34cc4a7e8c9cef0ee18e4556efea151e3dba019a77f98c96b55dc │ ├── 58872eb4fd54bfa1c1ff16814550728ce2dfc8d532b84c6451a826747762c0c1 │ ├── 68339ee60dbae299533e33490df40c22646e18733524f8f44f496981eb4652c3 │ ├── 9c748438639367b45e1d2ecb88cd66cee0a253b4151fc7298ee11bbee509389d │ ├── ce5376a3d7c0fb2c30dbf2d7c1c0689ab57856b45491a6f70d5056b4d1f700c0 │ ├── d1c44ba01efc79fbf09fb88f020eb832fecfce8d59b3486a653e237dd20c55c9 │ ├── de56e72f63217f604fa5425cea6c2be8a29a30aaaede2ccac6f3f986d0289351 │ ├── e5c84966ac39cebda994ab3840a5d5256a9d393ed68e8dd588a0bc444b15d2b7 │ └── fcfc72958cc76fcae152178010a2803f711e673a4d8f2d74bfb02e95f722a938 ├── container ├── deque │ ├── deque.go │ ├── deque_test.go │ └── testdata │ │ └── fuzz │ │ └── FuzzDeque │ │ ├── 0e6b5039cdfee0ac96768fd480f4ce3df459b2efc4b4e5f6a483d3e054a545c1 │ │ ├── 72531e6b85b76902d3422675767453013308be8f9fffcd7bcb967de222505b15 │ │ ├── 7b7d4885c9b49aa51ed822d183ebdd7e7df6b8ccfdc4bf4f4eb571efeeff1826 │ │ ├── 7e979f8aefe6ae8a185596b0e4367e5c613a5db09a5ccee8ca967d7c528c5bed │ │ ├── 90c5c9e175d7a53ecf420f8dd3971579f05696eafb38c76d01430adf864d86f8 │ │ ├── d1a30eda24471b382e49d8f7139f30c0a25b6682aa44ad004d080376676bc926 │ │ └── f46f2a31137a82cc0820eab458e80abe967b547c6a1973dffc55c5d154d8d9d2 ├── tree │ ├── btree.go │ ├── btree_benchmark_test.go │ ├── btree_test.go │ ├── doc.go │ ├── map.go │ ├── set.go │ └── testdata │ │ └── fuzz │ │ └── FuzzBtree │ │ ├── 009153937833fdc3c5f705f9ff01b85217e89cc4bc1ceca3cdb0c74fea9b2e81 │ │ ├── 00c1ffca0ecb5725b8395831e8af3fa7b7fabb7433a1d1ca2d97d741b90ec812 │ │ ├── 031dc34785fde00f308cf6a0b029b5c6d5fa2594ca59bd9de06e339386925995 │ │ ├── 0c53970894bde3a58813c3176068bc71906d5a2224fcacd2ba45355da1e8c078 │ │ ├── 1371a5eb44205936250944fc89b672086edc44d47ce83b92a1c5fb2bb713bf07 │ │ ├── 15ce7945e0616665cca67c8fa696e98daff25efdc6b7818294c14a023d1f276f │ │ ├── 3186c429e1e9457d3a9781f1574c9e425a2722699c873d9bd029ba8ef0bf1251 │ │ ├── 397e77a7302992824e071c74afb6fc92bcae888518beec4e925fa04bd5f6109a │ │ ├── 4daaf3fc572445d3e2abfecdd581c5fe3e9223c7e4f99d9024f1067ccc0fdd13 │ │ ├── 582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 │ │ ├── 66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d │ │ ├── 6846001db8403e686afe2cccc16baf98e7614a02fa3cae6377879774a557c815 │ │ ├── 77bc6ab776b752e5affdebf8086b3b41a61db036dc0e301c9b6a5cddb6aa8de9 │ │ ├── 8b9e3d4777155ac218c623d5f60b358ef54154f768cef8dc88e5f8762d470486 │ │ ├── 8fb23cdaf598ef83b3b1ffe0adfd8afb9355230669630e347dcb0b6dccf2c96d │ │ ├── 93f613aa37dee6860c4c209d01a4b0a55a60f09b3c899ff4c08296db8a0e3757 │ │ ├── 9480302240b5d8165509b6ee1a3010e0b8c626d177c018a321848b006e0f6feb │ │ ├── 9e3edbbc4d6da50f8dd2f64f399c77e0a8cd9eeaadc48cd01aa522d616ad6fc1 │ │ ├── b602bd178b58b816d472c7033078290f38f8043e2909166fec0a5294768e9281 │ │ ├── c5f5c514e05dd9777d15eebd16b522d33f7af1ce681588ec7926cf4224a17f1e │ │ ├── ca4f83585749886bc517c43d19817c64024511976fb9aabaebdd0a708cf3ed32 │ │ ├── d1f25b1ddd4b728a3e601f1b50dc0f011524dba4d4667cca33481bef38acdef3 │ │ ├── d71b22a346a2fa61e51ba06a6491a21b90f91f5d32898b830322b98c7db15f4f │ │ ├── d9db7005f68c1bb12871a742701b82f92f10e8a998bf52f29fc2faee0ce449a3 │ │ ├── e0767f8f563d128ae718a2088c21f090d9525789fe108dec624274a3d16b18fe │ │ ├── e1ca410ff2c96828ac46109e9ccc44cbb6af746f18558564519b9c4b3e333c23 │ │ └── ee2e988348496277d0979c29024c575be9789a337f19b06a9d5fb8829777f2f8 ├── xheap │ ├── testdata │ │ └── fuzz │ │ │ ├── FuzzBasic │ │ │ ├── 1cb6ac353221cd94e905c3633b67191b65d08bc02ee23306f77e095223ce051c │ │ │ └── 70a32a8bd28f5c4e4d81261c2400079adaf373c2e5d923da25fe4c04058faeb5 │ │ │ └── FuzzPriorityQueue │ │ │ ├── c689c7ddbfd538bfa952c2740ba43fdfd83cb0d19796e15b3c30613f8a52ec9e │ │ │ ├── d032b98f2fb2ea478028c32a0f45d04a549eb054f07654b3d4c5a374216a18a7 │ │ │ └── ee874908d2a4e971a8bce76a75ac6af8f2c82c2c47b225146f4b3c131cb3fc1c │ ├── xheap.go │ └── xheap_test.go └── xlist │ ├── testdata │ └── fuzz │ │ └── FuzzList │ │ ├── 052df2b9addb4b5728625f2d904855ac4d11c08736fd5f0c514fa4093bf012a4 │ │ ├── 082f5d3003e4b7eecadd5da481360ab6c5d27d61f9c8dd3c3726b9997e8abd7c │ │ ├── 19ad1cdd3cda96ceaa49bad30f44d9b974ad0551d8c37a4b21fe5d10d8703c4b │ │ ├── 36bbbd91660d42e6fbe6e5de21891a1c39dbddfe395a0001506dd742792092e1 │ │ ├── 45a5647fbf2a9781808dc942c4cfc85df42c72ecd8673cb46c9625c69859e895 │ │ ├── 61f9c13a583fd565a960887f61fab4b1d20988bed1ed3eed6dddbbc745eaf72e │ │ ├── 732421c657c5db19e5e068faf0d3c7c44bc9960e8f354da429b58f297b1a5cbc │ │ ├── 871e27fb3bb3ddcdfbd1e38ae83677110d4a78ad4ee347256eae2a7563bd23b7 │ │ ├── 8853315897765b7769cf4d45dee20bfa199683ba2778135f2d73263e2b8717ea │ │ └── aaeabb36ec0ba5e68dd1a6937842e2f4ed3ae6c69e4233639e6095cf20966d04 │ ├── xlist.go │ └── xlist_test.go ├── fuzz.sh ├── go.mod ├── go.sum ├── internal ├── fuzz │ └── fuzz.go ├── heap │ ├── heap.go │ └── heap_test.go ├── orderedhashmap │ └── orderedhashmap.go ├── require2 │ └── require.go └── tseq │ ├── tseq.go │ └── tseq_test.go ├── iterator ├── iterator.go └── iterator_example_test.go ├── parallel ├── doc.go ├── parallel.go └── parallel_test.go ├── stream ├── stream.go ├── stream_example_test.go ├── stream_test.go └── testdata │ └── fuzz │ └── FuzzBatch │ ├── 086dc23b3648d1b798461ec898a20079dc501a8b7dbad7cea2cd03b745802029 │ ├── 10d95885964d1ae653be1405b9dce20f7e51b56e73efa0fb57c33685490152eb │ ├── 1bef05a7f3aa67e3f6ebf9a30abcdb1006c8b6f8213bda3c1d88aef20cb6d5a0 │ ├── 23561f2fec377b58a23fc9ae112672cb799fb004a106a07891671b3f2d17b882 │ ├── 2b49f746d1be4692b93e5697b8098a8b9cf332c79288cea46a0d8277fa923d2e │ ├── 37aeba375901ac1e1a06110754e790ccb6fdb56d762369b632a9ac7568b02c18 │ ├── 3d6d329a0d771d9fcf822119bce41a6dfa09bf2c694c5f0770c8ebccbe667d6a │ ├── 463b6bdff2fea2ffe7c295a165301ddd1a58a1f7e4c3c03cae3ba20e8476c394 │ ├── 4dd43c30ae77dd634b96350d60419c975143da6a399fad7e050a747d3140362d │ └── f94e99ff4311c69dd759058d3f3e987241a8614d329464fe36c04ce57c3860cc ├── test_all_versions.sh ├── xerrors ├── xerrors.go └── xerrors_test.go ├── xmaps ├── xmaps.go └── xmaps_test.go ├── xmath ├── xmath.go ├── xmath_go1.21.go ├── xmath_old.go └── xrand │ ├── testdata │ └── fuzz │ │ └── FuzzSampleInner │ │ ├── 026df4d84a92eefda60cf8f15aef08a4002b1faad98e27586df1de381b7922b5 │ │ ├── 03cc0292c8d26ab239059a35ced1322911297f07dd25db1237d0005fe0e19841 │ │ ├── 05156fd053a2d13b3c9cbe36ad5755fab5f31ab3242550a49692aa79ee1e4045 │ │ ├── 086d599a93f0c1c236c6ff45cacfc93e9141c3ccf07a59535bdb572967798436 │ │ ├── 0a0eb5ba2014c951af24c8b8f5d3fa9d19272442bee86d80d79384e9fe18abf2 │ │ ├── 21a46963f603d3cbe3bafc40ae07dfdbfcb66058f8d3466f4ec3c5b3d3b3d618 │ │ ├── 2fbcb46cbae7d9a3d2dc693a06510cfcd404fba718dce27e17b821f76ecfedb1 │ │ └── 32ebf3925cbf5e62bbf03b28b11b28a721d1f2e024b99ca5f6d93fc90aaf97df │ ├── xrand.go │ ├── xrand_example_test.go │ └── xrand_test.go ├── xslices ├── testdata │ └── fuzz │ │ └── FuzzRemoveUnordered │ │ ├── cfcad4015f83ee9d954573d1a6f85b097098aa91d6191ef5ca7ca8f83de48020 │ │ └── f7bbe0c14562f70cc3d89e6d9fe8862df888ab393d37a2168390c749c5d87579 ├── xslices.go ├── xslices_example_test.go ├── xslices_go1.21.go ├── xslices_old.go └── xslices_test.go ├── xsort ├── testdata │ └── fuzz │ │ └── FuzzMerge │ │ ├── 13809475f3d15cec40695eb42e92cedb1340834bde29443e88744b84494638af │ │ ├── 3a13beea93b6a963f9a530b1d6a25a89013281d73a1692d4a7e82f7f7a59c263 │ │ └── f4a395632ec0839487b2172c9916470d80756b1893f000796d0414c7aca99cdd ├── xsort.go ├── xsort_go1.21.go ├── xsort_old.go └── xsort_test.go ├── xsync ├── xsync.go ├── xsync_go1.19.go ├── xsync_go1.19_test.go ├── xsync_go1.21.go ├── xsync_old.go └── xsync_test.go └── xtime ├── xtime.go └── xtime_test.go /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | name: Fuzz 2 | 3 | on: 4 | schedule: 5 | - cron: '0 8 * * *' 6 | 7 | jobs: 8 | 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Set up Go 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: 1.21 18 | stable: false 19 | 20 | - name: Build 21 | run: bash fuzz.sh 22 | -------------------------------------------------------------------------------- /.github/workflows/gen_docs.yml: -------------------------------------------------------------------------------- 1 | name: Gen Docs 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Set up Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.18.0-beta2 17 | stable: false 18 | 19 | - name: Generate 20 | run: | 21 | set -euxo pipefail 22 | git config --global user.name 'godoc_gh_pages workflow' 23 | git config --global user.email 'godoc_gh_pages@users.noreply.github.com' 24 | go run github.com/bradenaw/godoc_gh_pages@latest --out_dir docs/ 25 | git add docs/ 26 | git commit -am "generate docs" 27 | git push origin `git subtree split --prefix docs main`:refs/heads/gh-pages --force 28 | -------------------------------------------------------------------------------- /.github/workflows/go1.19.yml: -------------------------------------------------------------------------------- 1 | name: Go 1.19 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.19' 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test --race ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/go1.20.yml: -------------------------------------------------------------------------------- 1 | name: Go 1.20 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.20' 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test --race ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/go1.21.yml: -------------------------------------------------------------------------------- 1 | name: Go 1.21 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.21' 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test --race ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/go1.22.yml: -------------------------------------------------------------------------------- 1 | name: Go 1.22 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.22' 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test --race ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/go1.23.yml: -------------------------------------------------------------------------------- 1 | name: Go 1.23 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.23' 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test --race ./... 26 | -------------------------------------------------------------------------------- /.github/workflows/go1.24.yml: -------------------------------------------------------------------------------- 1 | name: Go 1.24 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.24' 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test --race ./... 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Braden Walker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Juniper 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/bradenaw/juniper.svg)](https://pkg.go.dev/github.com/bradenaw/juniper) 4 | [![Go 1.21](https://github.com/bradenaw/juniper/actions/workflows/go1.21.yml/badge.svg)](https://github.com/bradenaw/juniper/actions/workflows/go1.21.yml) 5 | [![Go 1.22](https://github.com/bradenaw/juniper/actions/workflows/go1.22.yml/badge.svg)](https://github.com/bradenaw/juniper/actions/workflows/go1.22.yml) 6 | [![Go 1.23](https://github.com/bradenaw/juniper/actions/workflows/go1.23.yml/badge.svg)](https://github.com/bradenaw/juniper/actions/workflows/go1.23.yml) 7 | [![Go 1.24](https://github.com/bradenaw/juniper/actions/workflows/go1.24.yml/badge.svg)](https://github.com/bradenaw/juniper/actions/workflows/go1.24.yml) 8 | [![Fuzz](https://github.com/bradenaw/juniper/actions/workflows/fuzz.yml/badge.svg)](https://github.com/bradenaw/juniper/actions/workflows/fuzz.yml) 9 | 10 | Juniper is a library of extensions to the Go standard library using generics, including containers, 11 | iterators, and streams. 12 | 13 | - `container/tree` contains a `Map` and `Set` that keep elements in sorted order. They are 14 | implemented using a B-tree, which performs better than a binary search tree. 15 | - `container/deque` contains a double-ended queue implemented with a ring buffer. 16 | - `container/xheap` contains a min-heap similar to the standard library's `container/heap` but 17 | more ergonomic, along with a `PriorityQueue` that allows setting priorities by key. 18 | - `container/xlist` contains a linked-list similar to the standard library's `container/list`, but 19 | type-safe. 20 | - `xslices` contains some commonly-used slice operations, like `Chunk`, `Reverse`, `Clear`, and 21 | `Join`. 22 | - `iterator` contains an iterator interface used by the containers, along with functions to 23 | manipulate them, like `Map`, `While`, and `Reduce`. 24 | - `stream` contains a stream interface, which is an iterator that can fail. Useful for iterating 25 | over collections that require I/O. It has most of the same combinators as `iterator`, plus some 26 | extras like `Pipe` and `Batch`. 27 | - `parallel` contains some shorthand for common uses of goroutines to process slices, iterators, and 28 | streams in parallel, like `parallel.MapStream`. 29 | - `xsort` contains extensions to the standard library package `sort`. Notably, it also has the 30 | definition for `xsort.Less`, which is how custom orderings can be defined for sorting and also for 31 | ordered collections like from `container/tree`. 32 | - You can probably guess what's in the packages `xerrors`, `xmath`, `xmath/xrand`, `xsync`, and 33 | `xtime`. 34 | 35 | Packages that overlap directly with a standard library package are named the same but with an `x` 36 | prefix for "extensions", e.g. `sort` and `xsort`. 37 | 38 | See the [docs](https://pkg.go.dev/github.com/bradenaw/juniper) for more. 39 | -------------------------------------------------------------------------------- /chans/chans.go: -------------------------------------------------------------------------------- 1 | // Package chans contains functions for manipulating channels. 2 | package chans 3 | 4 | import ( 5 | "context" 6 | "reflect" 7 | 8 | "github.com/bradenaw/juniper/xslices" 9 | ) 10 | 11 | // SendContext sends item on channel c and returns nil, unless ctx expires in which case it returns 12 | // ctx.Err(). 13 | func SendContext[T any](ctx context.Context, c chan<- T, item T) error { 14 | select { 15 | case <-ctx.Done(): 16 | return ctx.Err() 17 | case c <- item: 18 | return nil 19 | } 20 | } 21 | 22 | // RecvContext attempts to receive from channel c. If c is closed before or during, returns (_, 23 | // false, nil). If ctx expires before or during, returns (_, _, ctx.Err()). 24 | func RecvContext[T any](ctx context.Context, c <-chan T) (T, bool, error) { 25 | select { 26 | case <-ctx.Done(): 27 | var zero T 28 | return zero, false, ctx.Err() 29 | case item, ok := <-c: 30 | return item, ok, nil 31 | } 32 | } 33 | 34 | // Merge sends all values from all in channels to out. 35 | // 36 | // Merge blocks until all ins have closed and all values have been sent. It does not close out. 37 | func Merge[T any](out chan<- T, in ...<-chan T) { 38 | if len(in) == 1 { 39 | for item := range in[0] { 40 | out <- item 41 | } 42 | return 43 | } else if len(in) == 2 { 44 | merge2(out, in[0], in[1]) 45 | return 46 | } else if len(in) == 3 { 47 | merge3(out, in[0], in[1], in[2]) 48 | return 49 | } 50 | 51 | selectCases := xslices.Map(in, func(x <-chan T) reflect.SelectCase { 52 | return reflect.SelectCase{ 53 | Dir: reflect.SelectRecv, 54 | Chan: reflect.ValueOf(x), 55 | } 56 | }) 57 | for { 58 | if len(selectCases) == 0 { 59 | return 60 | } 61 | chosen, item, ok := reflect.Select(selectCases) 62 | if ok { 63 | out <- item.Interface().(T) 64 | } else { 65 | selectCases = xslices.RemoveUnordered(selectCases, chosen, 1) 66 | } 67 | } 68 | } 69 | 70 | // Merge special-case with no reflection. 71 | func merge2[T any](out chan<- T, in0, in1 <-chan T) { 72 | nDone := 0 73 | for { 74 | select { 75 | case item, ok := <-in0: 76 | if ok { 77 | out <- item 78 | } else { 79 | in0 = nil 80 | nDone++ 81 | if nDone == 2 { 82 | return 83 | } 84 | } 85 | case item, ok := <-in1: 86 | if ok { 87 | out <- item 88 | } else { 89 | in1 = nil 90 | nDone++ 91 | if nDone == 2 { 92 | return 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | // Merge special-case with no reflection. 100 | func merge3[T any](out chan<- T, in0, in1, in2 <-chan T) { 101 | nDone := 0 102 | for { 103 | select { 104 | case item, ok := <-in0: 105 | if ok { 106 | out <- item 107 | } else { 108 | in0 = nil 109 | nDone++ 110 | if nDone == 3 { 111 | return 112 | } 113 | } 114 | case item, ok := <-in1: 115 | if ok { 116 | out <- item 117 | } else { 118 | in1 = nil 119 | nDone++ 120 | if nDone == 3 { 121 | return 122 | } 123 | } 124 | case item, ok := <-in2: 125 | if ok { 126 | out <- item 127 | } else { 128 | in2 = nil 129 | nDone++ 130 | if nDone == 3 { 131 | return 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | // Replicate sends all values sent to src to every channel in dsts. 139 | // 140 | // Replicate blocks until src is closed and all values have been sent to all dsts. It does not close 141 | // dsts. 142 | func Replicate[T any](src <-chan T, dsts ...chan<- T) { 143 | for item := range src { 144 | for _, dst := range dsts { 145 | dst <- item 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /chans/chans_example_test.go: -------------------------------------------------------------------------------- 1 | package chans_test 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/bradenaw/juniper/chans" 8 | ) 9 | 10 | func ExampleMerge() { 11 | a := make(chan int) 12 | go func() { 13 | a <- 0 14 | a <- 1 15 | a <- 2 16 | close(a) 17 | }() 18 | b := make(chan int) 19 | go func() { 20 | b <- 5 21 | b <- 6 22 | b <- 7 23 | b <- 8 24 | close(b) 25 | }() 26 | 27 | out := make(chan int) 28 | done := make(chan struct{}) 29 | go func() { 30 | for i := range out { 31 | fmt.Println(i) 32 | } 33 | close(done) 34 | }() 35 | 36 | chans.Merge(out, a, b) 37 | close(out) 38 | <-done 39 | 40 | // Unordered output: 41 | // 0 42 | // 1 43 | // 2 44 | // 5 45 | // 6 46 | // 7 47 | // 8 48 | } 49 | 50 | func ExampleReplicate() { 51 | in := make(chan int) 52 | go func() { 53 | in <- 0 54 | in <- 1 55 | in <- 2 56 | in <- 3 57 | close(in) 58 | }() 59 | 60 | var wg sync.WaitGroup 61 | wg.Add(2) 62 | a := make(chan int) 63 | go func() { 64 | for i := range a { 65 | fmt.Println(i * 2) 66 | } 67 | wg.Done() 68 | }() 69 | 70 | b := make(chan int) 71 | go func() { 72 | x := 0 73 | for i := range b { 74 | x += i 75 | fmt.Println(x) 76 | } 77 | wg.Done() 78 | }() 79 | 80 | chans.Replicate(in, a, b) 81 | close(a) 82 | close(b) 83 | wg.Wait() 84 | 85 | // Unordered output: 86 | // 0 87 | // 2 88 | // 4 89 | // 6 90 | // 0 91 | // 1 92 | // 3 93 | // 6 94 | } 95 | -------------------------------------------------------------------------------- /chans/chans_test.go: -------------------------------------------------------------------------------- 1 | package chans 2 | 3 | import ( 4 | "math/rand" 5 | "runtime" 6 | "sync/atomic" 7 | "testing" 8 | "time" 9 | 10 | "github.com/bradenaw/juniper/internal/require2" 11 | "github.com/bradenaw/juniper/xslices" 12 | ) 13 | 14 | func FuzzMerge(f *testing.F) { 15 | f.Fuzz(func(t *testing.T, n int, b []byte) { 16 | if n > 5 || n <= 0 { 17 | return 18 | } 19 | 20 | t.Logf("n = %d", n) 21 | 22 | out := make(chan byte) 23 | ins := make([]chan byte, n) 24 | for i := range ins { 25 | ins[i] = make(chan byte) 26 | } 27 | ins2 := xslices.Map(ins, func(c chan byte) <-chan byte { return c }) 28 | 29 | go func() { 30 | Merge(out, ins2...) 31 | close(out) 32 | }() 33 | 34 | var inSlice []byte 35 | var outSlice []byte 36 | done := make(chan struct{}) 37 | go func() { 38 | for item := range out { 39 | outSlice = append(outSlice, item) 40 | } 41 | close(done) 42 | }() 43 | 44 | Loop: 45 | for { 46 | if len(b) < 3 { 47 | break 48 | } 49 | idx := int(b[0]) 50 | if idx >= len(ins) { 51 | break 52 | } 53 | switch b[1] { 54 | case 0: 55 | inSlice = append(inSlice, b[2]) 56 | ins[idx] <- b[2] 57 | case 1: 58 | close(ins[idx]) 59 | ins = xslices.RemoveUnordered(ins, idx, 1) 60 | default: 61 | break Loop 62 | } 63 | b = b[3:] 64 | } 65 | for _, in := range ins { 66 | close(in) 67 | } 68 | <-done 69 | 70 | require2.SlicesEqual(t, inSlice, outSlice) 71 | }) 72 | } 73 | 74 | func TestStressMerge(t *testing.T) { 75 | t.Skip() 76 | count := uint64(0) 77 | start := time.Now() 78 | 79 | go func() { 80 | for { 81 | t.Logf("%s %d", time.Since(start).Round(time.Second), count) 82 | time.Sleep(3 * time.Second) 83 | } 84 | }() 85 | 86 | for i := 0; i < runtime.GOMAXPROCS(-1); i++ { 87 | go func() { 88 | r := rand.New(rand.NewSource(time.Now().Unix())) 89 | 90 | for { 91 | n := r.Intn(4) + 1 92 | 93 | atomic.AddUint64(&count, 1) 94 | 95 | out := make(chan byte) 96 | ins := make([]chan byte, n) 97 | for i := range ins { 98 | ins[i] = make(chan byte) 99 | } 100 | 101 | ins2 := xslices.Map(ins, func(c chan byte) <-chan byte { return c }) 102 | go func() { 103 | Merge(out, ins2...) 104 | close(out) 105 | }() 106 | 107 | var inS []byte 108 | var outS []byte 109 | done := make(chan struct{}) 110 | go func() { 111 | for item := range out { 112 | outS = append(outS, item) 113 | } 114 | close(done) 115 | }() 116 | 117 | for { 118 | if len(ins) == 0 { 119 | break 120 | } 121 | idx := r.Intn(len(ins)) 122 | switch r.Intn(2) { 123 | case 0: 124 | v := byte(r.Intn(256)) 125 | inS = append(inS, v) 126 | ins[idx] <- v 127 | case 1: 128 | close(ins[idx]) 129 | nBefore := len(ins) 130 | ins = xslices.RemoveUnordered(ins, idx, 1) 131 | require2.Equal(t, len(ins), nBefore-1) 132 | } 133 | } 134 | <-done 135 | 136 | require2.SlicesEqual(t, inS, outS) 137 | } 138 | }() 139 | } 140 | 141 | c := make(chan struct{}) 142 | <-c 143 | } 144 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/31ad7ffe9ccdf3670ead1115352add5c1f9b9847c9ade353726c041f25abbdd9: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(4) 3 | []byte("\x00\x010\x00\x010\x00\x01\x88 \x00") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/35368ec77b179c2ae7b3379d2159f97a05bf52a7c2a028cf23c53e343c8b7593: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(4) 3 | []byte("\x00\x01=\x00\x010\x00\x010\x00\x010") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/4b3caf8fedf34cc4a7e8c9cef0ee18e4556efea151e3dba019a77f98c96b55dc: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(2) 3 | []byte("\x00\x01\x00\x00\x01\x00\x0000\x00\x000") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/58872eb4fd54bfa1c1ff16814550728ce2dfc8d532b84c6451a826747762c0c1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(3) 3 | []byte("\x00\x010\x00\xee0\x02\x000") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/68339ee60dbae299533e33490df40c22646e18733524f8f44f496981eb4652c3: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(4) 3 | []byte("\x00\x010\x00\x010\x00\x01\x88 \x010") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/9c748438639367b45e1d2ecb88cd66cee0a253b4151fc7298ee11bbee509389d: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(4) 3 | []byte("\x00\x010\x00\x010\x00\x01\x88 \x000") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/ce5376a3d7c0fb2c30dbf2d7c1c0689ab57856b45491a6f70d5056b4d1f700c0: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(2) 3 | []byte("\x02\x000") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/d1c44ba01efc79fbf09fb88f020eb832fecfce8d59b3486a653e237dd20c55c9: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(3) 3 | []byte("\x02z\xec") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/de56e72f63217f604fa5425cea6c2be8a29a30aaaede2ccac6f3f986d0289351: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(5) 3 | []byte("\x03\x010\x0000\x0000\x000\x00\x020000\x00000\x0200") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/e5c84966ac39cebda994ab3840a5d5256a9d393ed68e8dd588a0bc444b15d2b7: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(2) 3 | []byte("\x00\x01\x00\x00\x010\x00\x000\x00\x000") 4 | -------------------------------------------------------------------------------- /chans/testdata/fuzz/FuzzMerge/fcfc72958cc76fcae152178010a2803f711e673a4d8f2d74bfb02e95f722a938: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(5) 3 | []byte("\x03\x010\x0000\x0000\x0000\x0000\x000\x02000\x0200") 4 | -------------------------------------------------------------------------------- /container/deque/deque.go: -------------------------------------------------------------------------------- 1 | // Package deque contains a double-ended queue. 2 | package deque 3 | 4 | import ( 5 | "errors" 6 | 7 | "github.com/bradenaw/juniper/iterator" 8 | "github.com/bradenaw/juniper/xmath" 9 | ) 10 | 11 | var errDequeEmpty = errors.New("pop from empty deque") 12 | var errDequeModified = errors.New("deque modified during iteration") 13 | 14 | const ( 15 | // A non-empty deque has space for at least this many items. 16 | minSize = 16 17 | // When growing a full deque, reallocate with len(d.a)*growFactor. 18 | growFactor = 2 19 | ) 20 | 21 | // Deque is a double-ended queue, allowing push and pop to both the front and back of the queue. 22 | // Pushes and pops are amortized O(1). The zero-value is ready to use. Deque should not be copied 23 | // after first use. 24 | type Deque[T any] struct { 25 | // Backing slice for the deque. 26 | a []T 27 | // Index of the first item. 28 | front int 29 | // Index of the last item, or -1 if the deque is empty but a is allocated. 30 | back int 31 | gen int 32 | } 33 | 34 | // Len returns the number of items in the deque. 35 | func (d *Deque[T]) Len() int { 36 | if d.a == nil || d.back == -1 { 37 | return 0 38 | } 39 | 40 | if d.front <= d.back { 41 | return d.back - d.front + 1 42 | } 43 | return len(d.a) - d.front + d.back + 1 44 | } 45 | 46 | // Grow allocates sufficient space to add n more items without needing to reallocate. 47 | func (d *Deque[T]) Grow(n int) { 48 | extraCap := len(d.a) - d.Len() 49 | if extraCap < n { 50 | d.resize(len(d.a) + n) 51 | } 52 | } 53 | 54 | // Shrink reallocates the backing buffer for d, if necessary, so that it fits only the current size 55 | // plus at most n extra items. 56 | func (d *Deque[T]) Shrink(n int) { 57 | if n < 0 { 58 | panic("Shrink() with a negative number of extras") 59 | } 60 | if len(d.a)-d.Len() > n { 61 | d.resize(d.Len() + n) 62 | } 63 | } 64 | 65 | // PushFront adds item to the front of the deque. 66 | func (d *Deque[T]) PushFront(item T) { 67 | d.maybeExpand() 68 | d.front = positiveMod(d.front-1, len(d.a)) 69 | d.a[d.front] = item 70 | if d.back == -1 { 71 | d.back = d.front 72 | } 73 | d.gen++ 74 | } 75 | 76 | // PushFront adds item to the back of the deque. 77 | func (d *Deque[T]) PushBack(item T) { 78 | d.maybeExpand() 79 | if d.back == -1 { 80 | d.back = d.front 81 | } else { 82 | d.back = (d.back + 1) % len(d.a) 83 | } 84 | d.a[d.back] = item 85 | d.gen++ 86 | } 87 | 88 | // Guarantees that there is room in the deque. 89 | func (d *Deque[T]) maybeExpand() { 90 | if d.Len() == len(d.a) { 91 | d.resize(xmath.Max(minSize, len(d.a)*2)) 92 | } 93 | } 94 | 95 | func (d *Deque[T]) resize(n int) { 96 | oldLen := d.Len() 97 | newA := make([]T, n) 98 | if !(d.a == nil || d.back == -1) { 99 | if d.front <= d.back { 100 | copy(newA, d.a[d.front:d.back+1]) 101 | } else { 102 | copy(newA, d.a[d.front:]) 103 | copy(newA[len(d.a)-d.front:], d.a[:d.back+1]) 104 | } 105 | } 106 | d.a = newA 107 | d.front = 0 108 | d.back = oldLen - 1 109 | } 110 | 111 | // PopFront removes and returns the item at the front of the deque. It panics if the deque is empty. 112 | func (d *Deque[T]) PopFront() T { 113 | l := d.Len() 114 | if l == 0 { 115 | panic(errDequeEmpty) 116 | } 117 | item := d.a[d.front] 118 | var zero T 119 | if l == 1 { 120 | d.a[d.front] = zero 121 | d.front = 0 122 | d.back = -1 123 | return item 124 | } 125 | d.a[d.front] = zero 126 | d.front = (d.front + 1) % len(d.a) 127 | d.gen++ 128 | return item 129 | } 130 | 131 | // PopBack removes and returns the item at the back of the deque. It panics if the deque is empty. 132 | func (d *Deque[T]) PopBack() T { 133 | l := d.Len() 134 | if l == 0 { 135 | panic(errDequeEmpty) 136 | } 137 | item := d.a[d.back] 138 | var zero T 139 | if l == 1 { 140 | d.a[d.back] = zero 141 | d.front = 0 142 | d.back = -1 143 | return item 144 | } 145 | d.a[d.back] = zero 146 | d.back = positiveMod(d.back-1, len(d.a)) 147 | d.gen++ 148 | return item 149 | } 150 | 151 | // Front returns the item at the front of the deque. It panics if the deque is empty. 152 | func (d *Deque[T]) Front() T { 153 | if d.back == -1 { 154 | panic("deque index out of range") 155 | } 156 | return d.a[d.front] 157 | } 158 | 159 | // Back returns the item at the back of the deque. It panics if the deque is empty. 160 | func (d *Deque[T]) Back() T { 161 | return d.a[d.back] 162 | } 163 | 164 | // Item returns the ith item in the deque. 0 is the front and d.Len()-1 is the back. 165 | func (d *Deque[T]) Item(i int) T { 166 | if i < 0 || i >= d.Len() { 167 | panic("deque index out of range") 168 | } 169 | idx := (d.front + i) % len(d.a) 170 | return d.a[idx] 171 | } 172 | 173 | // Set sets the ith item in the deque. 0 is the front and d.Len()-1 is the back. 174 | func (d *Deque[T]) Set(i int, t T) { 175 | if i < 0 || i >= d.Len() { 176 | panic("deque index out of range") 177 | } 178 | idx := (d.front + i) % len(d.a) 179 | d.a[idx] = t 180 | } 181 | 182 | func positiveMod(l, d int) int { 183 | x := l % d 184 | if x < 0 { 185 | return x + d 186 | } 187 | return x 188 | } 189 | 190 | type dequeIterator[T any] struct { 191 | d *Deque[T] 192 | i int 193 | done bool 194 | gen int 195 | } 196 | 197 | func (iter *dequeIterator[T]) Next() (T, bool) { 198 | if iter.gen != iter.d.gen { 199 | panic(errDequeModified) 200 | } 201 | var zero T 202 | if iter.d.Len() == 0 { 203 | return zero, false 204 | } 205 | if iter.done { 206 | return zero, false 207 | } 208 | item := iter.d.a[iter.i] 209 | if iter.i == iter.d.back { 210 | iter.done = true 211 | } 212 | iter.i = (iter.i + 1) % len(iter.d.a) 213 | return item, true 214 | } 215 | 216 | // Iterate iterates over the elements of the deque. 217 | // 218 | // The iterator panics if the deque has been modified since iteration started. 219 | func (d *Deque[T]) Iterate() iterator.Iterator[T] { 220 | return &dequeIterator[T]{ 221 | d: d, 222 | i: d.front, 223 | done: false, 224 | gen: d.gen, 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /container/deque/deque_test.go: -------------------------------------------------------------------------------- 1 | package deque 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/bradenaw/juniper/internal/fuzz" 8 | "github.com/bradenaw/juniper/internal/require2" 9 | "github.com/bradenaw/juniper/iterator" 10 | ) 11 | 12 | func FuzzDeque(f *testing.F) { 13 | f.Fuzz(func(t *testing.T, b []byte) { 14 | var oracle []byte 15 | var deque Deque[byte] 16 | 17 | fuzz.Operations( 18 | b, 19 | func() { 20 | require2.Equal(t, len(oracle), deque.Len()) 21 | t.Logf(" len = %d", len(oracle)) 22 | t.Logf(" oracle state: %#v", oracle) 23 | t.Logf(" deque state: (len(r.a) = %d) %#v", len(deque.a), deque) 24 | }, // check 25 | func(x byte) { 26 | t.Logf("PushFront(%#v)", x) 27 | deque.PushFront(x) 28 | oracle = append([]byte{x}, oracle...) 29 | }, 30 | func(x byte) { 31 | t.Logf("PushBack(%#v)", x) 32 | deque.PushBack(x) 33 | oracle = append(oracle, x) 34 | }, 35 | func() { 36 | if len(oracle) == 0 { 37 | return 38 | } 39 | oracleItem := oracle[0] 40 | t.Logf("PopFront() -> %#v", oracleItem) 41 | oracle = oracle[1:] 42 | dequeItem := deque.PopFront() 43 | require2.Equal(t, oracleItem, dequeItem) 44 | }, 45 | func() { 46 | if len(oracle) == 0 { 47 | return 48 | } 49 | oracleItem := oracle[len(oracle)-1] 50 | t.Logf("PopBack() -> %#v", oracleItem) 51 | oracle = oracle[:len(oracle)-1] 52 | dequeItem := deque.PopBack() 53 | require2.Equal(t, oracleItem, dequeItem) 54 | }, 55 | func() { 56 | if len(oracle) == 0 { 57 | t.Log("Front() should panic") 58 | func() { 59 | defer func() { recover() }() 60 | deque.Front() 61 | t.FailNow() 62 | }() 63 | return 64 | } 65 | oracleItem := oracle[0] 66 | t.Logf("Front() -> %#v", oracleItem) 67 | dequeItem := deque.Front() 68 | require2.Equal(t, oracleItem, dequeItem) 69 | }, 70 | func() { 71 | if len(oracle) == 0 { 72 | t.Log("Back() should panic") 73 | func() { 74 | defer func() { recover() }() 75 | deque.Back() 76 | t.FailNow() 77 | }() 78 | return 79 | } 80 | oracleItem := oracle[len(oracle)-1] 81 | t.Logf("Back() -> %#v", oracleItem) 82 | dequeItem := deque.Back() 83 | require2.Equal(t, oracleItem, dequeItem) 84 | }, 85 | func(i int) { 86 | if i < 0 || i >= len(oracle) { 87 | t.Logf("Item(%d) should panic", i) 88 | func() { 89 | defer func() { recover() }() 90 | deque.Item(i) 91 | t.FailNow() 92 | }() 93 | return 94 | } 95 | oracleItem := oracle[i] 96 | t.Logf("Item(%d) -> %#v", i, oracleItem) 97 | dequeItem := deque.Item(i) 98 | require2.Equal(t, oracleItem, dequeItem) 99 | }, 100 | func(i int, x byte) { 101 | if i < 0 || i >= len(oracle) { 102 | t.Logf("Set(%d, x) should panic", i) 103 | func() { 104 | defer func() { recover() }() 105 | deque.Item(i) 106 | t.FailNow() 107 | }() 108 | return 109 | } 110 | t.Logf("Set(%d, %d)", i, x) 111 | oracle[i] = x 112 | deque.Set(i, x) 113 | }, 114 | func() { 115 | t.Log("Iterate()") 116 | oracleAll := oracle 117 | if len(oracleAll) == 0 { 118 | oracleAll = nil 119 | } 120 | dequeAll := iterator.Collect(deque.Iterate()) 121 | if len(dequeAll) == 0 { 122 | dequeAll = nil 123 | } 124 | require2.SlicesEqual(t, oracleAll, dequeAll) 125 | }, 126 | func(n byte) { 127 | t.Logf("Grow(%d)", n) 128 | deque.Grow(int(n)) 129 | }, 130 | ) 131 | }) 132 | } 133 | 134 | func Example() { 135 | var deque Deque[string] 136 | 137 | deque.PushFront("a") 138 | deque.PushFront("b") 139 | fmt.Println(deque.PopFront()) 140 | deque.PushBack("c") 141 | deque.PushBack("d") 142 | fmt.Println(deque.PopBack()) 143 | fmt.Println(deque.PopFront()) 144 | 145 | // Output: 146 | // b 147 | // d 148 | // a 149 | } 150 | -------------------------------------------------------------------------------- /container/deque/testdata/fuzz/FuzzDeque/0e6b5039cdfee0ac96768fd480f4ce3df459b2efc4b4e5f6a483d3e054a545c1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("70707070") 3 | -------------------------------------------------------------------------------- /container/deque/testdata/fuzz/FuzzDeque/72531e6b85b76902d3422675767453013308be8f9fffcd7bcb967de222505b15: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("70!00000000") 3 | -------------------------------------------------------------------------------- /container/deque/testdata/fuzz/FuzzDeque/7b7d4885c9b49aa51ed822d183ebdd7e7df6b8ccfdc4bf4f4eb571efeeff1826: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0000000000000070000000000000000000007\xea002") 3 | -------------------------------------------------------------------------------- /container/deque/testdata/fuzz/FuzzDeque/7e979f8aefe6ae8a185596b0e4367e5c613a5db09a5ccee8ca967d7c528c5bed: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\x95\x95\x02a\xa4\x97\xcd1\xaa\x8d;\xbc\xa3\x9eD3\xfb\xc0\x00M\xc6\xfb\xaes\n\r;") 3 | -------------------------------------------------------------------------------- /container/deque/testdata/fuzz/FuzzDeque/90c5c9e175d7a53ecf420f8dd3971579f05696eafb38c76d01430adf864d86f8: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\xcd\xcb\xdf\xc4\xdb\xd60\xca\xca") 3 | -------------------------------------------------------------------------------- /container/deque/testdata/fuzz/FuzzDeque/d1a30eda24471b382e49d8f7139f30c0a25b6682aa44ad004d080376676bc926: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("000070C") 3 | -------------------------------------------------------------------------------- /container/deque/testdata/fuzz/FuzzDeque/f46f2a31137a82cc0820eab458e80abe967b547c6a1973dffc55c5d154d8d9d2: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0A\xd5") 3 | -------------------------------------------------------------------------------- /container/tree/doc.go: -------------------------------------------------------------------------------- 1 | // Package tree contains an implementation of a B-tree Map and Set. These are similar to Go's map 2 | // built-in, but keep elements in sorted order. 3 | package tree 4 | -------------------------------------------------------------------------------- /container/tree/map.go: -------------------------------------------------------------------------------- 1 | package tree 2 | 3 | import ( 4 | "github.com/bradenaw/juniper/iterator" 5 | "github.com/bradenaw/juniper/xsort" 6 | ) 7 | 8 | type KVPair[K any, V any] struct { 9 | Key K 10 | Value V 11 | } 12 | 13 | // Map is a tree-structured key-value map, similar to Go's built-in map but keeps elements in sorted 14 | // order by key. 15 | // 16 | // It is safe for multiple goroutines to Put concurrently with keys that are already in the map. 17 | type Map[K any, V any] struct { 18 | // An extra indirect here so that tree.Map behaves like a reference type like the map builtin. 19 | t *btree[K, V] 20 | } 21 | 22 | // NewMap returns a Map that uses less to determine the sort order of keys. If !less(a, b) && 23 | // !less(b, a), then a and b are considered the same key. The output of less must not change for any 24 | // pair of keys while they are in the map. 25 | func NewMap[K any, V any](less xsort.Less[K]) Map[K, V] { 26 | return Map[K, V]{ 27 | t: newBtree[K, V](xsort.LessCompare(less)), 28 | } 29 | } 30 | 31 | func NewMapCmp[K any, V any](compare func(K, K) int) Map[K, V] { 32 | return Map[K, V]{ 33 | t: newBtree[K, V](compare), 34 | } 35 | } 36 | 37 | // Len returns the number of elements in the map. 38 | func (m Map[K, V]) Len() int { 39 | return m.t.size 40 | } 41 | 42 | // Put inserts the key-value pair into the map, overwriting the value for the key if it already 43 | // exists. 44 | func (m Map[K, V]) Put(k K, v V) { 45 | m.t.Put(k, v) 46 | } 47 | 48 | // Delete removes the given key from the map. 49 | func (m Map[K, V]) Delete(k K) { 50 | m.t.Delete(k) 51 | } 52 | 53 | // Get returns the value associated with the given key if it is present in the map. Otherwise, it 54 | // returns the zero-value of V. 55 | func (m Map[K, V]) Get(k K) V { 56 | return m.t.Get(k) 57 | } 58 | 59 | // Contains returns true if the given key is present in the map. 60 | func (m Map[K, V]) Contains(k K) bool { 61 | return m.t.Contains(k) 62 | } 63 | 64 | // First returns the lowest-keyed entry in the map according to less. 65 | func (m Map[K, V]) First() (K, V) { 66 | return m.t.First() 67 | } 68 | 69 | // Last returns the highest-keyed entry in the map according to less. 70 | func (m Map[K, V]) Last() (K, V) { 71 | return m.t.Last() 72 | } 73 | 74 | // Iterate returns an iterator that yields the elements of the map in ascending order by key. 75 | // 76 | // The map may be safely modified during iteration and the iterator will continue from the 77 | // next-lowest key. Thus the iterator will see new elements that are after the current position of 78 | // the iterator according to less, but will not necessarily see a consistent snapshot of the state 79 | // of the map. 80 | func (m Map[K, V]) Iterate() iterator.Iterator[KVPair[K, V]] { 81 | return m.Range(Unbounded[K](), Unbounded[K]()) 82 | } 83 | 84 | type boundType int 85 | 86 | const ( 87 | boundInclude boundType = iota + 1 88 | boundExclude 89 | boundUnbounded 90 | ) 91 | 92 | // Bound is an endpoint for a range. 93 | type Bound[K any] struct { 94 | type_ boundType 95 | key K 96 | } 97 | 98 | // Included returns a Bound that goes up to and including key. 99 | func Included[K any](key K) Bound[K] { return Bound[K]{type_: boundInclude, key: key} } 100 | 101 | // Excluded returns a Bound that goes up to but not including key. 102 | func Excluded[K any](key K) Bound[K] { return Bound[K]{type_: boundExclude, key: key} } 103 | 104 | // Unbounded returns a Bound at the end of the collection. 105 | func Unbounded[K any]() Bound[K] { return Bound[K]{type_: boundUnbounded} } 106 | 107 | // Range returns an iterator that yields the elements of the map between the given bounds in 108 | // ascending order by key. 109 | // 110 | // The map may be safely modified during iteration and the iterator will continue from the 111 | // next-lowest key. Thus the iterator will see new elements that are after the current position of 112 | // the iterator according to less, but will not necessarily see a consistent snapshot of the state 113 | // of the map. 114 | func (m Map[K, V]) Range(lower Bound[K], upper Bound[K]) iterator.Iterator[KVPair[K, V]] { 115 | return m.t.Range(lower, upper) 116 | } 117 | 118 | // RangeReverse returns an iterator that yields the elements of the map between the given bounds in 119 | // descending order by key. 120 | // 121 | // The map may be safely modified during iteration and the iterator will continue from the 122 | // next-lowest key. Thus the iterator will see new elements that are after the current position of 123 | // the iterator according to less, but will not necessarily see a consistent snapshot of the state 124 | // of the map. 125 | func (m Map[K, V]) RangeReverse(lower Bound[K], upper Bound[K]) iterator.Iterator[KVPair[K, V]] { 126 | return m.t.RangeReverse(lower, upper) 127 | } 128 | -------------------------------------------------------------------------------- /container/tree/set.go: -------------------------------------------------------------------------------- 1 | package tree 2 | 3 | import ( 4 | "github.com/bradenaw/juniper/iterator" 5 | "github.com/bradenaw/juniper/xsort" 6 | ) 7 | 8 | // Set is a tree-structured set. Sets are a collection of unique elements. Similar to Go's built-in 9 | // map[T]struct{} but keeps elements in sorted order. 10 | type Set[T any] struct { 11 | // An extra indirect here so that tree.Set behaves like a reference type like the map builtin. 12 | t *btree[T, struct{}] 13 | } 14 | 15 | // NewSet returns a Set that uses less to determine the sort order of items. If !less(a, b) && 16 | // !less(b, a), then a and b are considered the same item. The output of less must not change for 17 | // any pair of items while they are in the set. 18 | func NewSet[T any](less xsort.Less[T]) Set[T] { 19 | return Set[T]{ 20 | t: newBtree[T, struct{}](xsort.LessCompare(less)), 21 | } 22 | } 23 | 24 | func NewSetCmp[T any](compare func(T, T) int) Set[T] { 25 | return Set[T]{ 26 | t: newBtree[T, struct{}](compare), 27 | } 28 | } 29 | 30 | // Len returns the number of elements in the set. 31 | func (s Set[T]) Len() int { 32 | return s.t.size 33 | } 34 | 35 | // Add adds item to the set if it is not already present. 36 | func (s Set[T]) Add(item T) { 37 | s.t.Put(item, struct{}{}) 38 | } 39 | 40 | // Remove removes item from the set if it is present, and does nothing otherwise. 41 | func (s Set[T]) Remove(item T) { 42 | s.t.Delete(item) 43 | } 44 | 45 | // Contains returns true if item is present in the set. 46 | func (s Set[T]) Contains(item T) bool { 47 | return s.t.Contains(item) 48 | } 49 | 50 | // First returns the lowest item in the set according to less. 51 | func (s Set[T]) First() T { 52 | item, _ := s.t.First() 53 | return item 54 | } 55 | 56 | // Last returns the highest item in the set according to less. 57 | func (s Set[T]) Last() T { 58 | item, _ := s.t.Last() 59 | return item 60 | } 61 | 62 | // Iterate returns an iterator that yields the elements of the set in ascending order. 63 | // 64 | // The set may be safely modified during iteration and the iterator will continue from the 65 | // next-lowest item. Thus the iterator will see new items that are after the current position 66 | // of the iterator according to less, but will not necessarily see a consistent snapshot of the 67 | // state of the set. 68 | func (s Set[T]) Iterate() iterator.Iterator[T] { 69 | return s.Range(Unbounded[T](), Unbounded[T]()) 70 | } 71 | 72 | // Range returns an iterator that yields the elements of the set between the given bounds in 73 | // ascending order. 74 | // 75 | // The set may be safely modified during iteration and the iterator will continue from the 76 | // next-lowest item. Thus the iterator will see new items that are after the current position 77 | // of the iterator according to less, but will not necessarily see a consistent snapshot of the 78 | // state of the set. 79 | func (s Set[T]) Range(lower Bound[T], upper Bound[T]) iterator.Iterator[T] { 80 | return iterator.Map(s.t.Range(lower, upper), func(pair KVPair[T, struct{}]) T { 81 | return pair.Key 82 | }) 83 | } 84 | 85 | // RangeReverse returns an iterator that yields the elements of the set between the given bounds in 86 | // descending order. 87 | // 88 | // The set may be safely modified during iteration and the iterator will continue from the 89 | // next-lowest item. Thus the iterator will see new items that are after the current position 90 | // of the iterator according to less, but will not necessarily see a consistent snapshot of the 91 | // state of the set. 92 | func (s Set[T]) RangeReverse(lower Bound[T], upper Bound[T]) iterator.Iterator[T] { 93 | return iterator.Map(s.t.RangeReverse(lower, upper), func(pair KVPair[T, struct{}]) T { 94 | return pair.Key 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/009153937833fdc3c5f705f9ff01b85217e89cc4bc1ceca3cdb0c74fea9b2e81: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("000100100200700800000000001010") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/00c1ffca0ecb5725b8395831e8af3fa7b7fabb7433a1d1ca2d97d741b90ec812: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("100000100z00\xd300200000z00z0") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/031dc34785fde00f308cf6a0b029b5c6d5fa2594ca59bd9de06e339386925995: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("?W000Xyq\xe0\x03\xbb\xc1\xb12\xdfd\xae\x9d\x13t\xf4\x91C\x12\xf5\x95\xb1.f\xcb\xff\x14\xa2sM#\x8a\xd3&@V\x8e㡮\x04}F\xba\x83\xae\xec/\xfc+\xa0M\x12\a\x18K/\xd8x\xbbtm\xba-\xa9|!\xd3*6\xcd\xe2\xa4\xe8u\xb4\x90ߒ\xb3.h\xaak\xb0^\xa9y\xb1\xd1\xc1\xbf\a\xce3\xbd\x04\xb5\xaa\x91Τ\xa9\xa9u\xd9#\xe5\xe8\x1b\xec\xea_.\x1b\xfc\xea\x84\x1c\xbff\xb7\xaerÝM\xb4\U00089224]\x94Xs\t\xa0\xf9\xa3\xc7\xe1\xdb\xc1|\xb8[\x8c\xeeI\x8cqOE\xaa\x0f\x06\xd1\xf4/[i\xcf\x1b\xf7L>\xa9\xfd\xa2\x1b\n$\x83\xc4\xc1\xea\x98w\xf1\x9a\xf7\xeb)\xe5RqI\x10\x03\xed\xee\fe\x02\x12w\xbfeKF}\xf5v~\x14\x11`\xbb\xf2\xa4\x8c6S\xc0.\x19\x0f\x14V\xecn\x81it[\xa2\xa1Ѻ\x8b\x03\xa6^\xb5`\xcb\xe6R\xcf\x18\xdc\x01\xfc\xb4\xc2\xc4⾛\x89\xd1\x17\xb8\x99\x15>\x16\x88\x82\xde/\xb1\v\x86\xc0f\x1bUJf\x00s\x94\x8eZ\xbdR)A\xce@\xfczw\x04\x80\xf6\x1b\xc7\xf1\x8e(\xf0̺zυ\xf0\x0eL>p?絴`ä\x10\xd3\xdb7\x94\xbb\x11\x9b\x12\n\xa1\xb0ʪ%\x10\xec\xec \xff\xa0\x89\xbe\b\x87W\x1f\x00W}\xfe]r\xd6}欱}^\x8e\xefΑ5\xf9feX\xcf\x14\xc6'\xf6\xb2j|\b\xfc\xb2\x1a\xc9\a,\xb6m\xef$\r~\xa5\\nj\xc0\xaeDD?d\x9d26\xb5\xf4\xe4\x19\xe1\xe4\x1fɿ\x89\xab\x10d\x97i\x1e-\xf5\xd8O!\x02ۉ\xb6\x9dK\xc4ж\xd6~^x=\xc70n\xca\"\xfa\xba\x910\xea\x05\xd9\xe5О\xcd\xef\x1e#װ\x9c\x17O\x10\xd5\xe7\x1fh,\xa2{\xc2\x17cO\x19\x90\xa9\xc1\xd9z\xef&דٞ*1f.\xad{@(\x8e\x04|S p\xcb\xc4\xf7\x9a\x11\xeb\xbf\fc6m\x16\xbf\x82Z\xae\xb26h\xf9[\xcb\xf4`\xe4\x91 \xc3\xebb\x8eT\xf7\xd7\xd8X}\x0f=\xe6l\x81\xe8\x99\xcd5\xc1id\x16\xae\xff\x99uk\xf4\x1a\x9a)\xef\f\xd3\x02\xd0\xcb\x13i\x99\n\xe8j\x98}\b\xe7Q\x87v?\uedca&\xff\x9eiROY\xfc\x96E\xe0\xf3\xf3\xf0\x1f\x85\x10q\x9ba\x1e9e\x19ƒ\x16\x896\xaaǓ\x8e\xb2\xc2:\vR7·2q\xb0\x81\x9fp\xc8'\x1c%\x88y\v\x86ž\xffR\\\xb5\x86Jx:\xe7y\x8c8v\v\xf7\u007f\xe0\x9fƘ\x13\x16\xdc\xe7\"]0\xae\x9a\xe1Ш[ff\xd4F1EႧeP\xda\xfd\x05k\xe2\xd0?\xdaQ\xfc\xcd(\x83\x98Dd\x8a\xe2\xbf\ap\x1d\xba\xf3\xcd#\xd65\x04.n\x16l\x89}\x8a\xb2%\b\xa1\xfc\x9dN_\xd7\xee\x81OP\xe5%/\x05)Z\x1f:\x9c\xa84\xa08x\x82\xd2\t\x95K\x1c\xaf\x9e?e\xbc\x8c\x84[ٮ\x99\xbcx\x9f\rM,\x96\xb6s\xcc\u05f7x犿A=\xd2T钖\xa2S\xef\xf8\xcf12\xf9\x884\xa0\x8cw4\xb9p5a\xbc\xbd?JR\xf2\xa1\x188)\xad\xdek\xe1N;\xc0\xfb\xb7k\xb7\xfdP&~6@k\x1a\xd0\xde_\xfe\xa8\xdf\xcfxXC^;0\xcb-&\xe6ʼn\x9d\xce\"\x89\xa23`\x96\xc8!K\xfe|\x9e\x84\xba\xab\x90\a\xaf\xc7\xec\xa0\aA\xa8p{M\xf2K\xc8\xf0\xf9\xc4\xdaX\xab \xe0|wk\xcf\xd2\x00-\xec\x8a\bφ\xa3\xbb\xbd2P\xaf\xb4R\xf7\xf0\xef\xb88S!\x94'\x13F\xc67\xc0\xd3z\xf6\xff\xd3\xe2)V\x13{\xd6t\xb9\x13\x8d\x81\a\xf9\xa9۴\x17\xad\x1d4\xd5&T\xb0\x14~\xd1I(<@\xe3020") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/0c53970894bde3a58813c3176068bc71906d5a2224fcacd2ba45355da1e8c078: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0000 00!002007008009001011") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/1371a5eb44205936250944fc89b672086edc44d47ce83b92a1c5fb2bb713bf07: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\xac0\xc8882\xac&") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/15ce7945e0616665cca67c8fa696e98daff25efdc6b7818294c14a023d1f276f: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0102007000000\xa0\x03\xc3\xceJ$\x17wg\xf9\xffٜ\xeb\xa7\x18\x9d\x13~\xea=w\xd41\fKڮz\xb33\xe7^\xefu\fC&ȇ\xfe\xcf!\xb3\x17\xd39\x91~u\x0fq\xe4\x89g\xc1w.\x8f\xb8\x1a\f;\t\xe8K\u007f\xac\xac\xdeT]6\x1e!\x14BҌ\xf0\xbf\xcdJ\xfe\x9b\xfbV\x84'\x13/\xa9\x17C\xe2\x9fn\x8al\xf1\x11i6|\x95\x88.V\x1d\xa3\xfd\x81\x0e\xd5\xcd\v\xb3\xc7\xe4\x99\xc3\u007f\xaf\x81\x93pE&[\x1f\x06\xa0\xa9\x178\xa5^\x96[o¥\xb2\xbf\xa3\x06\x92d4n\xb4OM\x9e\xfa7[\x0f\xea\x89_\xc7ɓ\x12\x1c\xa2-\xaa\x93\xbe*\xeć$\x1c\x05\xdc~ƍ\x81\x81\xe5\x9fjb造\x8f7`\x8cG\x9aC.\x1c\xc3\t\x82\xe3\xc0\xdc\xe1\xff\x93\xa0\xa7W\xfe\xa4\xabi\xff\xa8\x81\xb6מĉ\xaf\xa7\xdd\x01=\x954\xbb\xb9#\x13\x80)kiq\xbd\x81\xa3\xdev\xa5WTz{s/\xe9\xa9\xf6\x80(\x1ci\fC\xc3-\xa5M\xf9\xb1^\x92v0\xe2\a\xda\x14{0$\x06\x93t\x15\xfe^\xa6\v\\\xafDr\u007f*<\xc1\x97\xc5\x11\x18ѯ\x1d\x12|\x04)\xef\r\xb7#\x00\x99\x1cخ\xf3\x9bQ\xfbj\x1cz\x0f\x89\x90\xa0)\nu\\\xeb\xbc\xfbfK\xbd\x18|\xa7\x95\xf80\x92\xa1K\x01\xc8X\x89\xe1R\\D\x05\xd1C\xdc\x1c\xacUe\xa2\x18\x17\xf0\xf0\x10\xd0Lx\x01i]Gq\x83\x8b\xdd\x0e\x1fC@\x0eɗ\x8d¨p\x8a\xb8[\x92t\xd2(c<\xf7@\x89n\x92\xa0C\x97\"<\xc0P\x92\x98\xb9\xfe\xc7\xd9ޟ]\xec\x8c2\x8d\xc1\x96z\xc0\\\x8aG\xb4\xbf\x1b]k\xd7\xec\x8fr\x80h\x9a\xc6\xc9_\x06\u007f\x04\xaf\x03\xbe|\xb1fp\x03\xb2\xdeu\xe8}\x84\x04\xc9\b\x13\xc6\x14\xc5\xd1\x1f\x04d\xe1˖\xa0\xeć$\x1c\x05\xe9\xc3\xfb\x8a\xa8<$\xb9\x8c\xb5\x1a\xbc\fcP\v\xa4\xfe5?д\x97{T\xf2\xc0\xfc\xcem쿀sQ\xef\x04k\xc6\n\xa2\x01\xf6\x19\x91\xcd\xed\x8a\xf27\xec\xd9تw\xe8ٽ\xd7010000") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/3186c429e1e9457d3a9781f1574c9e425a2722699c873d9bd029ba8ef0bf1251: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("010020070000080070") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/397e77a7302992824e071c74afb6fc92bcae888518beec4e925fa04bd5f6109a: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0000\x9a00\x9a010") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/4daaf3fc572445d3e2abfecdd581c5fe3e9223c7e4f99d9024f1067ccc0fdd13: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\xb400\xaf00\xe200000100200700800900A00B0") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("000000") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/6846001db8403e686afe2cccc16baf98e7614a02fa3cae6377879774a557c815: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("00.") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/77bc6ab776b752e5affdebf8086b3b41a61db036dc0e301c9b6a5cddb6aa8de9: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\xb70\x80000\x9f0\x8f0102070 0!2\x80") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/8b9e3d4777155ac218c623d5f60b358ef54154f768cef8dc88e5f8762d470486: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\xfe0\xdc0\xff00010\xfd02070\xdf2\xdc0") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/8fb23cdaf598ef83b3b1ffe0adfd8afb9355230669630e347dcb0b6dccf2c96d: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("01002000020") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/93f613aa37dee6860c4c209d01a4b0a55a60f09b3c899ff4c08296db8a0e3757: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("000000000000000010") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/9480302240b5d8165509b6ee1a3010e0b8c626d177c018a321848b006e0f6feb: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0\xf0+00\xf0.") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/9e3edbbc4d6da50f8dd2f64f399c77e0a8cd9eeaadc48cd01aa522d616ad6fc1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0000100700800900A00B00010C000200700800900A021020200220") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/b602bd178b58b816d472c7033078290f38f8043e2909166fec0a5294768e9281: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0X000\xd30\xeb0Y010Z020\xbe070a2\xbe") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/c5f5c514e05dd9777d15eebd16b522d33f7af1ce681588ec7926cf4224a17f1e: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("020k0H0022202k2H") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/ca4f83585749886bc517c43d19817c64024511976fb9aabaebdd0a708cf3ed32: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("00010207080A090C0X0Y0Z0a0b0B0c0x0y0z0\xfd0\xe40\xaf0\xa90^0[0\xc20\xb2") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/d1f25b1ddd4b728a3e601f1b50dc0f011524dba4d4667cca33481bef38acdef3: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0200700800900A00100000A0") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/d71b22a346a2fa61e51ba06a6491a21b90f91f5d32898b830322b98c7db15f4f: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0001020708090A0B0C0X0Y0\x040Z0a0b0c0x0y0z0\xd70\xfe0\x8e0g0\xa60\xcc0i0f200") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/d9db7005f68c1bb12871a742701b82f92f10e8a998bf52f29fc2faee0ce449a3: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("00010") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/e0767f8f563d128ae718a2088c21f090d9525789fe108dec624274a3d16b18fe: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0000100200700800B00900A01B") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/e1ca410ff2c96828ac46109e9ccc44cbb6af746f18558564519b9c4b3e333c23: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0100000200 00700800900A010") 3 | -------------------------------------------------------------------------------- /container/tree/testdata/fuzz/FuzzBtree/ee2e988348496277d0979c29024c575be9789a337f19b06a9d5fb8829777f2f8: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0100700200000800900A00B0080") 3 | -------------------------------------------------------------------------------- /container/xheap/testdata/fuzz/FuzzBasic/1cb6ac353221cd94e905c3633b67191b65d08bc02ee23306f77e095223ce051c: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("~\x9d\x1cʿ\x17\r") 3 | []byte("~") 4 | -------------------------------------------------------------------------------- /container/xheap/testdata/fuzz/FuzzBasic/70a32a8bd28f5c4e4d81261c2400079adaf373c2e5d923da25fe4c04058faeb5: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("") 3 | []byte("") 4 | -------------------------------------------------------------------------------- /container/xheap/testdata/fuzz/FuzzPriorityQueue/c689c7ddbfd538bfa952c2740ba43fdfd83cb0d19796e15b3c30613f8a52ec9e: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("1,.") 3 | []byte("0") 4 | -------------------------------------------------------------------------------- /container/xheap/testdata/fuzz/FuzzPriorityQueue/d032b98f2fb2ea478028c32a0f45d04a549eb054f07654b3d4c5a374216a18a7: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("90") 3 | []byte("\xb9") 4 | -------------------------------------------------------------------------------- /container/xheap/testdata/fuzz/FuzzPriorityQueue/ee874908d2a4e971a8bce76a75ac6af8f2c82c2c47b225146f4b3c131cb3fc1c: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0") 3 | []byte("\xc1") 4 | -------------------------------------------------------------------------------- /container/xheap/xheap.go: -------------------------------------------------------------------------------- 1 | // Package xheap contains extensions to the standard library package container/heap. 2 | package xheap 3 | 4 | import ( 5 | "github.com/bradenaw/juniper/internal/heap" 6 | "github.com/bradenaw/juniper/iterator" 7 | "github.com/bradenaw/juniper/xsort" 8 | ) 9 | 10 | // Heap is a min-heap (https://en.wikipedia.org/wiki/Binary_heap). Min-heaps are a collection 11 | // structure that provide constant-time access to the minimum element, and logarithmic-time removal. 12 | // They are most commonly used as a priority queue. 13 | // 14 | // Push and Pop take amortized O(log(n)) time where n is the number of items in the heap. 15 | // 16 | // Len and Peek take O(1) time. 17 | type Heap[T any] struct { 18 | // Indirect here so that Heap behaves as a reference type, like the map builtin. 19 | inner *heap.Heap[T] 20 | } 21 | 22 | // New returns a new Heap which uses less to determine the minimum element. 23 | // 24 | // The elements from initial are added to the heap. initial is modified by New and utilized by the 25 | // Heap, so it should not be used after passing to New(). Passing initial is faster (O(n)) than 26 | // creating an empty heap and pushing each item (O(n * log(n))). 27 | func New[T any](less xsort.Less[T], initial []T) Heap[T] { 28 | inner := heap.New( 29 | func(a, b T) bool { 30 | return less(a, b) 31 | }, 32 | func(a T, i int) {}, 33 | initial, 34 | ) 35 | return Heap[T]{ 36 | inner: &inner, 37 | } 38 | } 39 | 40 | func NewCmp[T any](compare func(T, T) int, initial []T) Heap[T] { 41 | return New(func(a, b T) bool { 42 | return compare(a, b) < 0 43 | }, initial) 44 | } 45 | 46 | // Len returns the current number of elements in the heap. 47 | func (h Heap[T]) Len() int { 48 | return h.inner.Len() 49 | } 50 | 51 | // Grow allocates sufficient space to add n more elements without needing to reallocate. 52 | func (h Heap[T]) Grow(n int) { 53 | h.inner.Grow(n) 54 | } 55 | 56 | // Shrink reallocates the backing buffer for h, if necessary, so that it fits only the current size 57 | // plus at most n extra items. 58 | func (h Heap[T]) Shrink(n int) { 59 | h.inner.Shrink(n) 60 | } 61 | 62 | // Push adds item to the heap. 63 | func (h Heap[T]) Push(item T) { 64 | h.inner.Push(item) 65 | } 66 | 67 | // Pop removes and returns the minimum item in the heap. It panics if h.Len()==0. 68 | func (h Heap[T]) Pop() T { 69 | return h.inner.Pop() 70 | } 71 | 72 | // Peek returns the minimum item in the heap. It panics if h.Len()==0. 73 | func (h Heap[T]) Peek() T { 74 | return h.inner.Peek() 75 | } 76 | 77 | // Iterate iterates over the elements of the heap. 78 | // 79 | // The iterator panics if the heap has been modified since iteration started. 80 | func (h Heap[T]) Iterate() iterator.Iterator[T] { 81 | return h.inner.Iterate() 82 | } 83 | 84 | // KP holds key and priority for PriorityQueue. 85 | type KP[K any, P any] struct { 86 | K K 87 | P P 88 | } 89 | 90 | // PriorityQueue is a queue that yields items in increasing order of priority. 91 | type PriorityQueue[K comparable, P any] struct { 92 | // Indirect here so that Heap behaves as a reference type, like the map builtin. 93 | inner *heap.Heap[KP[K, P]] 94 | m map[K]int 95 | } 96 | 97 | // NewPriorityQueue returns a new PriorityQueue which uses less to determine the minimum element. 98 | // 99 | // The elements from initial are added to the priority queue. initial is modified by 100 | // NewPriorityQueue and utilized by the PriorityQueue, so it should not be used after passing to 101 | // NewPriorityQueue. Passing initial is faster (O(n)) than creating an empty priority queue and 102 | // pushing each item (O(n * log(n))). 103 | // 104 | // Pop, Remove, and Update all take amortized O(log(n)) time where n is the number of items in the 105 | // queue. 106 | // 107 | // Len, Peek, Contains, and Priority take O(1) time. 108 | func NewPriorityQueue[K comparable, P any]( 109 | less xsort.Less[P], 110 | initial []KP[K, P], 111 | ) PriorityQueue[K, P] { 112 | h := PriorityQueue[K, P]{ 113 | m: make(map[K]int), 114 | } 115 | filtered := initial[:0] 116 | for _, kp := range initial { 117 | _, ok := h.m[kp.K] 118 | if ok { 119 | continue 120 | } 121 | h.m[kp.K] = -1 122 | filtered = append(filtered, kp) 123 | } 124 | initial = filtered 125 | inner := heap.New( 126 | func(a, b KP[K, P]) bool { 127 | return less(a.P, b.P) 128 | }, 129 | func(x KP[K, P], i int) { 130 | h.m[x.K] = i 131 | }, 132 | initial, 133 | ) 134 | h.inner = &inner 135 | return h 136 | } 137 | 138 | func NewPriorityQueueCmp[K comparable, P any]( 139 | compare func(P, P) int, 140 | initial []KP[K, P], 141 | ) PriorityQueue[K, P] { 142 | return NewPriorityQueue(func(a, b P) bool { 143 | return compare(a, b) < 0 144 | }, initial) 145 | } 146 | 147 | // Len returns the current number of elements in the priority queue. 148 | func (h PriorityQueue[K, P]) Len() int { 149 | return h.inner.Len() 150 | } 151 | 152 | // Grow allocates sufficient space to add n more elements without needing to reallocate. 153 | func (h PriorityQueue[K, P]) Grow(n int) { 154 | h.inner.Grow(n) 155 | } 156 | 157 | // Update updates the priority of k to p, or adds it to the priority queue if not present. 158 | func (h PriorityQueue[K, P]) Update(k K, p P) { 159 | idx, ok := h.m[k] 160 | if ok { 161 | h.inner.UpdateAt(idx, KP[K, P]{k, p}) 162 | } else { 163 | h.inner.Push(KP[K, P]{k, p}) 164 | } 165 | } 166 | 167 | // Pop removes and returns the lowest-P item in the priority queue. It panics if h.Len()==0. 168 | func (h PriorityQueue[K, P]) Pop() K { 169 | item := h.inner.Pop() 170 | delete(h.m, item.K) 171 | return item.K 172 | } 173 | 174 | // Peek returns the key of the lowest-P item in the priority queue. It panics if h.Len()==0. 175 | func (h PriorityQueue[K, P]) Peek() K { 176 | return h.inner.Peek().K 177 | } 178 | 179 | // Contains returns true if the given key is present in the priority queue. 180 | func (h PriorityQueue[K, P]) Contains(k K) bool { 181 | _, ok := h.m[k] 182 | return ok 183 | } 184 | 185 | // Priority returns the priority of k, or the zero value of P if k is not present. 186 | func (h PriorityQueue[K, P]) Priority(k K) P { 187 | idx, ok := h.m[k] 188 | if ok { 189 | return h.inner.Item(idx).P 190 | } 191 | var zero P 192 | return zero 193 | } 194 | 195 | // Remove removes the item with the given key if present. 196 | func (h PriorityQueue[K, P]) Remove(k K) { 197 | i, ok := h.m[k] 198 | if !ok { 199 | return 200 | } 201 | h.inner.RemoveAt(i) 202 | delete(h.m, k) 203 | } 204 | 205 | // Iterate iterates over the elements of the priority queue. 206 | // 207 | // The iterator panics if the priority queue has been modified since iteration started. 208 | func (h PriorityQueue[K, P]) Iterate() iterator.Iterator[K] { 209 | return iterator.Map(h.inner.Iterate(), func(kp KP[K, P]) K { return kp.K }) 210 | } 211 | -------------------------------------------------------------------------------- /container/xheap/xheap_test.go: -------------------------------------------------------------------------------- 1 | package xheap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bradenaw/juniper/internal/require2" 7 | "github.com/bradenaw/juniper/iterator" 8 | "github.com/bradenaw/juniper/xsort" 9 | ) 10 | 11 | func FuzzHeap(f *testing.F) { 12 | f.Fuzz(func(t *testing.T, b1 []byte, b2 []byte) { 13 | t.Logf("initial: %#v", b1) 14 | t.Logf("pushed: %#v", b2) 15 | h := New(xsort.OrderedLess[byte], append([]byte{}, b1...)) 16 | for i := range b2 { 17 | h.Push(b2[i]) 18 | } 19 | 20 | outByIterate := iterator.Collect(h.Iterate()) 21 | xsort.Slice(outByIterate, xsort.OrderedLess[byte]) 22 | if outByIterate == nil { 23 | outByIterate = []byte{} 24 | } 25 | 26 | outByPop := []byte{} 27 | for h.Len() > 0 { 28 | item := h.Pop() 29 | outByPop = append(outByPop, item) 30 | } 31 | 32 | expected := append(append([]byte{}, b1...), b2...) 33 | t.Logf("expected: %#v", expected) 34 | xsort.Slice(expected, xsort.OrderedLess[byte]) 35 | t.Logf("expected sorted: %#v", expected) 36 | 37 | require2.SlicesEqual(t, expected, outByPop) 38 | require2.SlicesEqual(t, expected, outByIterate) 39 | }) 40 | } 41 | 42 | func FuzzPriorityQueue(f *testing.F) { 43 | const ( 44 | Update = iota 45 | Pop 46 | Peek 47 | Contains 48 | Priority 49 | Remove 50 | Iterate 51 | ) 52 | f.Fuzz(func(t *testing.T, b1 []byte, b2 []byte) { 53 | initial := make([]KP[int, float32], 0, len(b1)) 54 | oracle := make(map[int]float32) 55 | for i := range b1 { 56 | k := int((b1[i] & 0b00011100) >> 2) 57 | p := float32((b1[i] & 0b00000011)) 58 | 59 | _, ok := oracle[k] 60 | if ok { 61 | continue 62 | } 63 | 64 | initial = append(initial, KP[int, float32]{k, p}) 65 | oracle[k] = p 66 | } 67 | t.Logf("initial: %#v", initial) 68 | t.Logf("initial oracle: %#v", oracle) 69 | 70 | h := NewPriorityQueue(xsort.OrderedLess[float32], initial) 71 | 72 | oracleLowestP := func() float32 { 73 | first := true 74 | lowest := float32(0) 75 | for _, p := range oracle { 76 | if first || p < lowest { 77 | lowest = p 78 | } 79 | first = false 80 | } 81 | return lowest 82 | } 83 | 84 | for _, b := range b2 { 85 | op := (b & 0b11100000) >> 5 86 | k := int((b & 0b00011100) >> 2) 87 | p := float32(b & 0b00000011) 88 | 89 | switch op { 90 | case Update: 91 | t.Logf("Update(%d, %f)", k, p) 92 | oracle[k] = p 93 | h.Update(k, p) 94 | case Pop: 95 | t.Logf("Pop()") 96 | if len(oracle) == 0 { 97 | require2.Equal(t, 0, h.Len()) 98 | continue 99 | } 100 | lowestP := oracleLowestP() 101 | hPopped := h.Pop() 102 | require2.Equal(t, lowestP, oracle[hPopped]) 103 | delete(oracle, hPopped) 104 | case Peek: 105 | t.Logf("Peek()") 106 | if len(oracle) == 0 { 107 | require2.Equal(t, 0, h.Len()) 108 | continue 109 | } 110 | lowestP := oracleLowestP() 111 | hPeeked := h.Peek() 112 | require2.Equal(t, lowestP, oracle[hPeeked]) 113 | case Contains: 114 | t.Logf("Contains(%d)", k) 115 | _, oracleContains := oracle[k] 116 | require2.Equal(t, oracleContains, h.Contains(k)) 117 | case Priority: 118 | t.Logf("Priority(%d)", k) 119 | require2.Equal(t, oracle[k], h.Priority(k)) 120 | case Remove: 121 | t.Logf("Remove(%d)", k) 122 | delete(oracle, k) 123 | h.Remove(k) 124 | case Iterate: 125 | t.Logf("Iterate()") 126 | oracleItems := make([]int, 0, len(oracle)) 127 | for k := range oracle { 128 | oracleItems = append(oracleItems, k) 129 | } 130 | items := iterator.Collect(h.Iterate()) 131 | require2.ElementsMatch(t, oracleItems, items) 132 | } 133 | 134 | require2.Equal(t, len(oracle), h.Len()) 135 | } 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/052df2b9addb4b5728625f2d904855ac4d11c08736fd5f0c514fa4093bf012a4: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("000000000100000000100000000100000000100000000100000000100000000100000000000000000000000000100000000100000000100000000100000000100000000100000000") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/082f5d3003e4b7eecadd5da481360ab6c5d27d61f9c8dd3c3726b9997e8abd7c: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("100000000000000000") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/19ad1cdd3cda96ceaa49bad30f44d9b974ad0551d8c37a4b21fe5d10d8703c4b: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/36bbbd91660d42e6fbe6e5de21891a1c39dbddfe395a0001506dd742792092e1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("100000000100000000000000000") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/45a5647fbf2a9781808dc942c4cfc85df42c72ecd8673cb46c9625c69859e895: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("70000000020000000000000000") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/61f9c13a583fd565a960887f61fab4b1d20988bed1ed3eed6dddbbc745eaf72e: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("700000000700000000700000000700000000700000000200012000101001100") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/732421c657c5db19e5e068faf0d3c7c44bc9960e8f354da429b58f297b1a5cbc: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("000000000000000000000000000200000000\xf11000000") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/871e27fb3bb3ddcdfbd1e38ae83677110d4a78ad4ee347256eae2a7563bd23b7: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("700000000!00000000000000000") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/8853315897765b7769cf4d45dee20bfa199683ba2778135f2d73263e2b8717ea: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("000000000") 3 | -------------------------------------------------------------------------------- /container/xlist/testdata/fuzz/FuzzList/aaeabb36ec0ba5e68dd1a6937842e2f4ed3ae6c69e4233639e6095cf20966d04: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("20000000000000000") 3 | -------------------------------------------------------------------------------- /container/xlist/xlist.go: -------------------------------------------------------------------------------- 1 | // Package xlist contains extensions to the standard library package container/list. 2 | package xlist 3 | 4 | // List is a doubly-linked list. 5 | type List[T any] struct { 6 | front *Node[T] 7 | back *Node[T] 8 | size int 9 | } 10 | 11 | // Len returns the number of items in the list. 12 | func (l *List[T]) Len() int { return l.size } 13 | 14 | // Front returns the node at the front of the list. 15 | func (l *List[T]) Front() *Node[T] { return l.front } 16 | 17 | // Back returns the node at the back of the list. 18 | func (l *List[T]) Back() *Node[T] { return l.back } 19 | 20 | // Clear removes all nodes from the list. 21 | func (l *List[T]) Clear() { l.front = nil; l.back = nil; l.size = 0 } 22 | 23 | // PushFront adds a new node with the given value to the front of the list. 24 | func (l *List[T]) PushFront(value T) *Node[T] { 25 | node := &Node[T]{ 26 | next: l.front, 27 | Value: value, 28 | } 29 | if l.front != nil { 30 | l.front.prev = node 31 | } 32 | l.front = node 33 | if l.back == nil { 34 | l.back = node 35 | } 36 | l.size++ 37 | return node 38 | } 39 | 40 | // PushBack adds a new node with the given value to the back of the list. 41 | func (l *List[T]) PushBack(value T) *Node[T] { 42 | node := &Node[T]{ 43 | prev: l.back, 44 | Value: value, 45 | } 46 | if l.back != nil { 47 | l.back.next = node 48 | } 49 | l.back = node 50 | if l.front == nil { 51 | l.front = node 52 | } 53 | l.size++ 54 | return node 55 | } 56 | 57 | // InsertBefore adds a new node with the given value before the node mark. 58 | func (l *List[T]) InsertBefore(value T, mark *Node[T]) *Node[T] { 59 | node := &Node[T]{ 60 | Value: value, 61 | prev: mark.prev, 62 | next: mark, 63 | } 64 | mark.prev = node 65 | if node.prev != nil { 66 | node.prev.next = node 67 | } 68 | if l.front == mark { 69 | l.front = node 70 | } 71 | l.size++ 72 | return node 73 | } 74 | 75 | // InsertBefore adds a new node with the given value after the node mark. 76 | func (l *List[T]) InsertAfter(value T, mark *Node[T]) *Node[T] { 77 | node := &Node[T]{ 78 | Value: value, 79 | prev: mark, 80 | next: mark.next, 81 | } 82 | mark.next = node 83 | if node.next != nil { 84 | node.next.prev = node 85 | } 86 | if l.back == mark { 87 | l.back = node 88 | } 89 | l.size++ 90 | return node 91 | } 92 | 93 | // Remove removes node from the list. 94 | func (l *List[T]) Remove(node *Node[T]) { 95 | l.remove(node) 96 | node.prev = nil 97 | node.next = nil 98 | l.size-- 99 | } 100 | 101 | func (l *List[T]) remove(node *Node[T]) { 102 | if l.front == node { 103 | l.front = l.front.next 104 | } else { 105 | node.prev.next = node.next 106 | } 107 | if l.back == node { 108 | l.back = l.back.prev 109 | } else { 110 | node.next.prev = node.prev 111 | } 112 | } 113 | 114 | // MoveBefore moves node just before mark. Afterwards, mark.Prev() == node && node.Next() == mark. 115 | func (l *List[T]) MoveBefore(node *Node[T], mark *Node[T]) { 116 | if node == mark { 117 | return 118 | } 119 | l.remove(node) 120 | node.prev = mark.prev 121 | mark.prev = node 122 | node.next = mark 123 | if node.prev != nil { 124 | node.prev.next = node 125 | } 126 | if l.front == mark { 127 | l.front = node 128 | } 129 | } 130 | 131 | // MoveAfter moves node just after mark. Afterwards, mark.Next() == node && node.Prev() == mark. 132 | func (l *List[T]) MoveAfter(node *Node[T], mark *Node[T]) { 133 | if node == mark { 134 | return 135 | } 136 | l.remove(node) 137 | node.next = mark.next 138 | mark.next = node 139 | node.prev = mark 140 | if node.next != nil { 141 | node.next.prev = node 142 | } 143 | if l.back == mark { 144 | l.back = node 145 | } 146 | } 147 | 148 | // MoveToFront moves node to the front of the list. 149 | func (l *List[T]) MoveToFront(node *Node[T]) { 150 | l.MoveBefore(node, l.Front()) 151 | } 152 | 153 | // MoveToFront moves node to the back of the list. 154 | func (l *List[T]) MoveToBack(node *Node[T]) { 155 | l.MoveAfter(node, l.Back()) 156 | } 157 | 158 | // Node is a node in a linked-list. 159 | type Node[T any] struct { 160 | prev *Node[T] 161 | next *Node[T] 162 | // Value is user-controlled, and never modified by this package. 163 | Value T 164 | } 165 | 166 | // Next returns the next node in the list that n is a part of, if there is one. 167 | func (n *Node[T]) Next() *Node[T] { 168 | return n.next 169 | } 170 | 171 | // Prev returns the previous node in the list that n is a part of, if there is one. 172 | func (n *Node[T]) Prev() *Node[T] { 173 | return n.prev 174 | } 175 | -------------------------------------------------------------------------------- /container/xlist/xlist_test.go: -------------------------------------------------------------------------------- 1 | package xlist 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bradenaw/juniper/internal/fuzz" 7 | "github.com/bradenaw/juniper/internal/require2" 8 | "github.com/bradenaw/juniper/xslices" 9 | ) 10 | 11 | func FuzzList(f *testing.F) { 12 | f.Fuzz(func(t *testing.T, b []byte) { 13 | var l List[int] 14 | var oracle []int 15 | 16 | nodeAt := func(i int) *Node[int] { 17 | j := 0 18 | curr := l.Front() 19 | for j < i { 20 | curr = curr.Next() 21 | j++ 22 | } 23 | return curr 24 | } 25 | 26 | fuzz.Operations( 27 | b, 28 | func() { // check 29 | t.Logf("%v", oracle) 30 | require2.Equal(t, len(oracle), l.Len()) 31 | 32 | if len(oracle) == 0 { 33 | require2.Nil(t, l.Front()) 34 | require2.Nil(t, l.Back()) 35 | return 36 | } 37 | 38 | node := l.Front() 39 | for i := range oracle { 40 | require2.NotNilf(t, node, "node nil at index %d, len(oracle)==%d", i, len(oracle)) 41 | require2.Equal(t, oracle[i], node.Value) 42 | if node.Next() != nil { 43 | require2.Equal(t, node, node.Next().Prev()) 44 | } 45 | node = node.Next() 46 | } 47 | require2.Nil(t, node) 48 | require2.NotNil(t, l.Back()) 49 | require2.Equal(t, oracle[len(oracle)-1], l.Back().Value) 50 | }, 51 | func(value int) { 52 | t.Logf("PushFront(%d)", value) 53 | l.PushFront(value) 54 | oracle = append([]int{value}, oracle...) 55 | }, 56 | func(value int) { 57 | t.Logf("PushBack(%d)", value) 58 | l.PushBack(value) 59 | oracle = append(oracle, value) 60 | }, 61 | func(value int, idx int) { 62 | if len(oracle) == 0 || idx < 0 { 63 | return 64 | } 65 | idx = idx % len(oracle) 66 | t.Logf("InsertBefore(%d, node @ %d)", value, idx) 67 | l.InsertBefore(value, nodeAt(idx)) 68 | oracle = xslices.Insert(oracle, idx, value) 69 | }, 70 | func(value int, idx int) { 71 | if len(oracle) == 0 || idx < 0 { 72 | return 73 | } 74 | idx = idx % len(oracle) 75 | t.Logf("InsertAfter(%d, node @ %d)", value, idx) 76 | l.InsertAfter(value, nodeAt(idx)) 77 | oracle = xslices.Insert(oracle, idx+1, value) 78 | }, 79 | func(idx int) { 80 | if len(oracle) == 0 || idx < 0 { 81 | return 82 | } 83 | idx = idx % len(oracle) 84 | t.Logf("Remove(node @ %d)", idx) 85 | l.Remove(nodeAt(idx)) 86 | oracle = xslices.Remove(oracle, idx, 1) 87 | }, 88 | func(src, dest int) { 89 | if len(oracle) == 0 || src < 0 || dest < 0 { 90 | return 91 | } 92 | src = src % len(oracle) 93 | dest = dest % len(oracle) 94 | t.Logf("MoveBefore(node @ %d, node @ %d)", src, dest) 95 | l.MoveBefore(nodeAt(src), nodeAt(dest)) 96 | item := oracle[src] 97 | oracle = xslices.Remove(oracle, src, 1) 98 | if dest > src { 99 | dest-- 100 | } 101 | oracle = xslices.Insert(oracle, dest, item) 102 | }, 103 | func(src, dest int) { 104 | if len(oracle) == 0 || src < 0 || dest < 0 { 105 | return 106 | } 107 | src = src % len(oracle) 108 | dest = dest % len(oracle) 109 | t.Logf("MoveAfter(node @ %d, node @ %d)", src, dest) 110 | l.MoveAfter(nodeAt(src), nodeAt(dest)) 111 | item := oracle[src] 112 | oracle = xslices.Remove(oracle, src, 1) 113 | if dest >= src { 114 | dest-- 115 | } 116 | oracle = xslices.Insert(oracle, dest+1, item) 117 | }, 118 | func(idx int) { 119 | if len(oracle) == 0 || idx < 0 { 120 | return 121 | } 122 | idx = idx % len(oracle) 123 | t.Logf("MoveToFront(node @ %d)", idx) 124 | l.MoveToFront(nodeAt(idx)) 125 | item := oracle[idx] 126 | oracle = xslices.Remove(oracle, idx, 1) 127 | oracle = append([]int{item}, oracle...) 128 | }, 129 | func(idx int) { 130 | if len(oracle) == 0 || idx < 0 { 131 | return 132 | } 133 | idx = idx % len(oracle) 134 | t.Logf("MoveToBack(node @ %d)", idx) 135 | l.MoveToBack(nodeAt(idx)) 136 | item := oracle[idx] 137 | oracle = xslices.Remove(oracle, idx, 1) 138 | oracle = append(oracle, item) 139 | }, 140 | ) 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | find . -name "*_test.go" \ 6 | | xargs grep "func Fuzz" \ 7 | | sed -E -e "s/^\.\/(([a-zA-Z0-9]+\/)+).+?(Fuzz[a-zA-Z0-9]+).+?$/\1 \3/g" \ 8 | | while read package_name fuzz_test_name; do 9 | echo "$package_name $fuzz_test_name" 10 | "$GOROOT/bin/go" test --fuzz "$fuzz_test_name" --fuzztime=15s "github.com/bradenaw/juniper/$package_name" 11 | done 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bradenaw/juniper 2 | 3 | go 1.18 4 | 5 | require ( 6 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d 7 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= 2 | golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 3 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 4 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 5 | -------------------------------------------------------------------------------- /internal/fuzz/fuzz.go: -------------------------------------------------------------------------------- 1 | package fuzz 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | func Operations(b []byte, check func(), fns ...interface{}) { 10 | choose := func(n int) (int, bool) { 11 | if len(b) < 1 { 12 | return 0, false 13 | } 14 | if n > 255 { 15 | panic("") 16 | } 17 | choice := int(b[0]) 18 | if choice >= n { 19 | return 0, false 20 | } 21 | b = b[1:] 22 | return choice, true 23 | } 24 | takeByte := func() (byte, bool) { 25 | if len(b) < 1 { 26 | return 0, false 27 | } 28 | x := b[0] 29 | b = b[1:] 30 | return x, true 31 | } 32 | takeUint16 := func() (uint16, bool) { 33 | if len(b) < 2 { 34 | return 0, false 35 | } 36 | x := uint16(binary.BigEndian.Uint16(b[:2])) 37 | b = b[2:] 38 | return x, true 39 | } 40 | takeInt := func() (int, bool) { 41 | if len(b) < 8 { 42 | return 0, false 43 | } 44 | x := int(binary.BigEndian.Uint64(b[:8])) 45 | b = b[8:] 46 | return x, true 47 | } 48 | takeBool := func() (bool, bool) { 49 | b, ok := takeByte() 50 | return b != 0, ok 51 | } 52 | 53 | Loop: 54 | for { 55 | check() 56 | i, ok := choose(len(fns)) 57 | if !ok { 58 | break 59 | } 60 | fnV := reflect.ValueOf(fns[i]) 61 | 62 | args := make([]reflect.Value, fnV.Type().NumIn()) 63 | for j := range args { 64 | argType := fnV.Type().In(j) 65 | 66 | switch argType.Kind() { 67 | case reflect.Int: 68 | x, ok := takeInt() 69 | if !ok { 70 | break Loop 71 | } 72 | args[j] = reflect.ValueOf(x) 73 | case reflect.Uint8: 74 | x, ok := takeByte() 75 | if !ok { 76 | break Loop 77 | } 78 | args[j] = reflect.ValueOf(x) 79 | case reflect.Uint16: 80 | x, ok := takeUint16() 81 | if !ok { 82 | break Loop 83 | } 84 | args[j] = reflect.ValueOf(x) 85 | case reflect.Bool: 86 | x, ok := takeBool() 87 | if !ok { 88 | break Loop 89 | } 90 | args[j] = reflect.ValueOf(x) 91 | default: 92 | panic(fmt.Sprintf("arg type %s not supported", argType.Kind())) 93 | } 94 | } 95 | fnV.Call(args) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /internal/heap/heap.go: -------------------------------------------------------------------------------- 1 | package heap 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/bradenaw/juniper/iterator" 7 | "github.com/bradenaw/juniper/xslices" 8 | ) 9 | 10 | var ErrHeapModified = errors.New("heap modified during iteration") 11 | 12 | // Duplicated from xsort to avoid dependency cycle. 13 | type Less[T any] func(a, b T) bool 14 | 15 | type Heap[T any] struct { 16 | lessFn Less[T] 17 | indexChanged func(x T, i int) 18 | a []T 19 | gen int 20 | } 21 | 22 | func New[T any](less Less[T], indexChanged func(x T, i int), initial []T) Heap[T] { 23 | h := Heap[T]{ 24 | lessFn: less, 25 | indexChanged: indexChanged, 26 | a: initial, 27 | } 28 | 29 | for i := len(initial)/2 - 1; i >= 0; i-- { 30 | h.percolateDown(i) 31 | } 32 | for i := range initial { 33 | h.notifyIndexChanged(i) 34 | } 35 | 36 | return h 37 | } 38 | 39 | func (h *Heap[T]) Len() int { 40 | return len(h.a) 41 | } 42 | 43 | func (h *Heap[T]) Grow(n int) { 44 | h.a = xslices.Grow(h.a, n) 45 | } 46 | 47 | func (h *Heap[T]) Shrink(n int) { 48 | h.a = xslices.Shrink(h.a, n) 49 | } 50 | 51 | func (h *Heap[T]) Push(item T) { 52 | h.a = append(h.a, item) 53 | h.notifyIndexChanged(len(h.a) - 1) 54 | h.percolateUp(len(h.a) - 1) 55 | h.gen++ 56 | } 57 | 58 | func (h *Heap[T]) Pop() T { 59 | var zero T 60 | item := h.a[0] 61 | (h.a)[0] = (h.a)[len(h.a)-1] 62 | // In case T is a pointer, clear this out to keep the ref from being live. 63 | (h.a)[len(h.a)-1] = zero 64 | h.a = (h.a)[:len(h.a)-1] 65 | if len(h.a) > 0 { 66 | h.notifyIndexChanged(0) 67 | } 68 | h.percolateDown(0) 69 | h.gen++ 70 | return item 71 | } 72 | 73 | func (h *Heap[T]) Peek() T { 74 | return h.a[0] 75 | } 76 | 77 | func (h *Heap[T]) RemoveAt(i int) { 78 | var zero T 79 | h.a[i] = h.a[len(h.a)-1] 80 | h.a[len(h.a)-1] = zero 81 | h.a = h.a[:len(h.a)-1] 82 | if i < len(h.a) { 83 | h.notifyIndexChanged(i) 84 | h.percolateUp(i) 85 | h.percolateDown(i) 86 | } 87 | h.gen++ 88 | } 89 | 90 | func (h *Heap[T]) Item(i int) T { 91 | return h.a[i] 92 | } 93 | 94 | func (h *Heap[T]) UpdateAt(i int, item T) { 95 | h.a[i] = item 96 | h.notifyIndexChanged(i) 97 | h.percolateUp(i) 98 | h.percolateDown(i) 99 | } 100 | 101 | func (h *Heap[T]) percolateUp(i int) { 102 | for i > 0 { 103 | p := parent(i) 104 | if h.less(i, p) { 105 | h.swap(i, p) 106 | } 107 | i = p 108 | } 109 | } 110 | 111 | func (h *Heap[T]) swap(i, j int) { 112 | (h.a)[i], (h.a)[j] = (h.a)[j], (h.a)[i] 113 | h.notifyIndexChanged(i) 114 | h.notifyIndexChanged(j) 115 | } 116 | 117 | func (h *Heap[T]) notifyIndexChanged(i int) { 118 | h.indexChanged(h.a[i], i) 119 | } 120 | 121 | func (h *Heap[T]) less(i, j int) bool { 122 | return h.lessFn((h.a)[i], (h.a)[j]) 123 | } 124 | 125 | func (h *Heap[T]) percolateDown(i int) { 126 | for { 127 | left, right := children(i) 128 | if left >= len(h.a) { 129 | // no children 130 | return 131 | } else if right >= len(h.a) { 132 | // only has a left child 133 | if h.less(left, i) { 134 | h.swap(left, i) 135 | i = left 136 | } else { 137 | return 138 | } 139 | } else { 140 | // has both children 141 | least := left 142 | if h.less(right, left) { 143 | least = right 144 | } 145 | if h.less(least, i) { 146 | h.swap(least, i) 147 | i = least 148 | } else { 149 | return 150 | } 151 | } 152 | } 153 | } 154 | 155 | type heapIterator[T any] struct { 156 | h *Heap[T] 157 | inner iterator.Iterator[T] 158 | gen int 159 | } 160 | 161 | func (iter *heapIterator[T]) Next() (T, bool) { 162 | if iter.gen == -1 { 163 | iter.gen = iter.h.gen 164 | iter.inner = iterator.Slice(iter.h.a) 165 | } else if iter.gen != iter.h.gen { 166 | panic(ErrHeapModified) 167 | } 168 | return iter.inner.Next() 169 | } 170 | 171 | func (h *Heap[T]) Iterate() iterator.Iterator[T] { 172 | return &heapIterator[T]{h: h, gen: -1} 173 | } 174 | 175 | func parent(i int) int { 176 | return (i - 1) / 2 177 | } 178 | 179 | func children(i int) (int, int) { 180 | return i*2 + 1, i*2 + 2 181 | } 182 | -------------------------------------------------------------------------------- /internal/heap/heap_test.go: -------------------------------------------------------------------------------- 1 | package heap 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bradenaw/juniper/internal/require2" 7 | ) 8 | 9 | func TestParentChildren(t *testing.T) { 10 | require2.Equal(t, 0, parent(1)) 11 | require2.Equal(t, 0, parent(2)) 12 | left, right := children(0) 13 | require2.Equal(t, 1, left) 14 | require2.Equal(t, 2, right) 15 | 16 | require2.Equal(t, 1, parent(3)) 17 | require2.Equal(t, 1, parent(4)) 18 | left, right = children(1) 19 | require2.Equal(t, 3, left) 20 | require2.Equal(t, 4, right) 21 | 22 | require2.Equal(t, 2, parent(5)) 23 | require2.Equal(t, 2, parent(6)) 24 | left, right = children(2) 25 | require2.Equal(t, 5, left) 26 | require2.Equal(t, 6, right) 27 | } 28 | -------------------------------------------------------------------------------- /internal/orderedhashmap/orderedhashmap.go: -------------------------------------------------------------------------------- 1 | // package orderedhashmap contains a simple and very inefficient ordered map using the map builtin 2 | // for comparing against other ordered containers in tests. 3 | package orderedhashmap 4 | 5 | import ( 6 | "github.com/bradenaw/juniper/iterator" 7 | "github.com/bradenaw/juniper/xsort" 8 | ) 9 | 10 | type KVPair[K any, V any] struct { 11 | K K 12 | V V 13 | } 14 | 15 | type Map[K comparable, V any] struct { 16 | less xsort.Less[K] 17 | m map[K]V 18 | } 19 | 20 | func NewMap[K comparable, V any](less xsort.Less[K]) Map[K, V] { 21 | return Map[K, V]{ 22 | less: less, 23 | m: make(map[K]V), 24 | } 25 | } 26 | 27 | func (m Map[K, V]) Len() int { 28 | return len(m.m) 29 | } 30 | 31 | func (m Map[K, V]) Put(k K, v V) { 32 | m.m[k] = v 33 | } 34 | 35 | func (m Map[K, V]) Delete(k K) { 36 | delete(m.m, k) 37 | } 38 | 39 | func (m Map[K, V]) Get(k K) V { 40 | return m.m[k] 41 | } 42 | 43 | func (m Map[K, V]) Contains(k K) bool { 44 | _, ok := m.m[k] 45 | return ok 46 | } 47 | 48 | func (m Map[K, V]) First() (K, V) { 49 | first := true 50 | var min K 51 | for k := range m.m { 52 | if first || m.less(k, min) { 53 | min = k 54 | first = false 55 | } 56 | } 57 | return min, m.m[min] 58 | } 59 | 60 | func (m Map[K, V]) Last() (K, V) { 61 | first := true 62 | var max K 63 | for k := range m.m { 64 | if first || m.less(max, k) { 65 | max = k 66 | first = false 67 | } 68 | } 69 | return max, m.m[max] 70 | } 71 | 72 | func (m Map[K, V]) Iterate() iterator.Iterator[KVPair[K, V]] { 73 | return m.Cursor().Forward() 74 | } 75 | 76 | func (m Map[K, V]) Cursor() *Cursor[K, V] { 77 | c := &Cursor[K, V]{ 78 | m: m, 79 | } 80 | c.SeekFirst() 81 | return c 82 | } 83 | 84 | func (m Map[K, V]) lastLess(k K) (K, bool) { 85 | first := true 86 | var out K 87 | for existingK := range m.m { 88 | if xsort.GreaterOrEqual(m.less, existingK, k) { 89 | continue 90 | } 91 | if first || m.less(out, existingK) { 92 | out = existingK 93 | first = false 94 | } 95 | } 96 | return out, !first 97 | } 98 | 99 | func (m Map[K, V]) firstGreater(k K) (K, bool) { 100 | first := true 101 | var out K 102 | for existingK := range m.m { 103 | if xsort.LessOrEqual(m.less, existingK, k) { 104 | continue 105 | } 106 | if first || m.less(existingK, out) { 107 | out = existingK 108 | first = false 109 | } 110 | } 111 | return out, !first 112 | } 113 | 114 | type Cursor[K comparable, V any] struct { 115 | m Map[K, V] 116 | offEdge bool 117 | k K 118 | } 119 | 120 | func (c *Cursor[K, V]) SeekFirst() { 121 | c.k, _ = c.m.First() 122 | c.offEdge = len(c.m.m) == 0 123 | } 124 | 125 | func (c *Cursor[K, V]) SeekLast() { 126 | c.k, _ = c.m.Last() 127 | c.offEdge = len(c.m.m) == 0 128 | } 129 | 130 | func (c *Cursor[K, V]) set(k K) { 131 | c.k = k 132 | c.offEdge = false 133 | } 134 | 135 | func (c *Cursor[K, V]) SeekLastLess(k K) { 136 | k, ok := c.m.lastLess(k) 137 | c.offEdge = !ok 138 | if ok { 139 | c.set(k) 140 | } 141 | } 142 | 143 | func (c *Cursor[K, V]) SeekLastLessOrEqual(k K) { 144 | if c.m.Contains(k) { 145 | c.set(k) 146 | return 147 | } 148 | k, ok := c.m.lastLess(k) 149 | c.offEdge = !ok 150 | if ok { 151 | c.set(k) 152 | } 153 | } 154 | 155 | func (c *Cursor[K, V]) SeekFirstGreaterOrEqual(k K) { 156 | if c.m.Contains(k) { 157 | c.set(k) 158 | return 159 | } 160 | k, ok := c.m.firstGreater(k) 161 | c.offEdge = !ok 162 | if ok { 163 | c.set(k) 164 | } 165 | } 166 | 167 | func (c *Cursor[K, V]) SeekFirstGreater(k K) { 168 | k, ok := c.m.firstGreater(k) 169 | c.offEdge = !ok 170 | if ok { 171 | c.set(k) 172 | } 173 | } 174 | 175 | func (c *Cursor[K, V]) Next() { 176 | if c.offEdge { 177 | return 178 | } 179 | k, ok := c.m.firstGreater(c.k) 180 | c.offEdge = !ok 181 | if ok { 182 | c.set(k) 183 | } 184 | } 185 | 186 | func (c *Cursor[K, V]) Prev() { 187 | if c.offEdge { 188 | return 189 | } 190 | k, ok := c.m.lastLess(c.k) 191 | c.offEdge = !ok 192 | if ok { 193 | c.set(k) 194 | } 195 | } 196 | 197 | func (c *Cursor[K, V]) deleted() bool { 198 | return !c.m.Contains(c.k) 199 | } 200 | 201 | func (c *Cursor[K, V]) Ok() bool { 202 | _, ok := c.m.m[c.k] 203 | return !c.offEdge && ok 204 | } 205 | 206 | func (c *Cursor[K, V]) Key() K { return c.k } 207 | 208 | func (c *Cursor[K, V]) Value() V { 209 | return c.m.m[c.k] 210 | } 211 | 212 | type forwardIterator[K comparable, V any] struct { 213 | c Cursor[K, V] 214 | } 215 | 216 | func (iter *forwardIterator[K, V]) Next() (KVPair[K, V], bool) { 217 | if !iter.c.offEdge && iter.c.deleted() { 218 | iter.c.SeekFirstGreaterOrEqual(iter.c.Key()) 219 | } 220 | if !iter.c.Ok() { 221 | var zero KVPair[K, V] 222 | return zero, false 223 | } 224 | k := iter.c.Key() 225 | v := iter.c.Value() 226 | iter.c.Next() 227 | return KVPair[K, V]{k, v}, true 228 | } 229 | 230 | func (c *Cursor[K, V]) Forward() iterator.Iterator[KVPair[K, V]] { 231 | c2 := *c 232 | if !c2.offEdge && c2.deleted() { 233 | c2.SeekFirstGreater(c2.k) 234 | } 235 | return &forwardIterator[K, V]{c: c2} 236 | } 237 | 238 | type backwardIterator[K comparable, V any] struct { 239 | c Cursor[K, V] 240 | } 241 | 242 | func (iter *backwardIterator[K, V]) Next() (KVPair[K, V], bool) { 243 | if !iter.c.offEdge && iter.c.deleted() { 244 | iter.c.SeekLastLessOrEqual(iter.c.Key()) 245 | } 246 | if !iter.c.Ok() { 247 | var zero KVPair[K, V] 248 | return zero, false 249 | } 250 | k := iter.c.Key() 251 | v := iter.c.Value() 252 | iter.c.Prev() 253 | return KVPair[K, V]{k, v}, true 254 | } 255 | 256 | func (c *Cursor[K, V]) Backward() iterator.Iterator[KVPair[K, V]] { 257 | c2 := *c 258 | if !c2.offEdge && c2.deleted() { 259 | c2.SeekLastLess(c.k) 260 | } 261 | return &backwardIterator[K, V]{c: c2} 262 | } 263 | -------------------------------------------------------------------------------- /internal/require2/require.go: -------------------------------------------------------------------------------- 1 | package require2 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "runtime" 7 | "strconv" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/exp/constraints" 12 | ) 13 | 14 | func Equal[T comparable](t *testing.T, expected T, actual T) { 15 | if expected != actual { 16 | fatalf(t, "assertion failed: %#v == %#v", expected, actual) 17 | } 18 | } 19 | 20 | func DeepEqual[T any](t *testing.T, a T, b T) { 21 | if !reflect.DeepEqual(a, b) { 22 | fatalf(t, "assertion failed: reflect.DeepEqual(%#v, %#v)", a, b) 23 | } 24 | } 25 | 26 | func Equalf[T comparable](t *testing.T, expected T, actual T, s string, fmtArgs ...any) { 27 | if expected != actual { 28 | fatalf(t, "assertion failed: %#v == %#v\n"+s, append([]any{expected, actual}, fmtArgs...)...) 29 | } 30 | } 31 | 32 | func SlicesEqual[T comparable](t *testing.T, expected []T, actual []T) { 33 | n := len(expected) 34 | if len(actual) < n { 35 | n = len(actual) 36 | } 37 | 38 | for i := 0; i < n; i++ { 39 | if expected[i] != actual[i] { 40 | fatalf(t, "differ at index %d: %#v != %#v", i, expected[i], actual[i]) 41 | } 42 | } 43 | 44 | if len(expected) != len(actual) { 45 | fatalf(t, "lengths differ: %d != %d", len(expected), len(actual)) 46 | } 47 | } 48 | 49 | func Nil[T any](t *testing.T, a *T) { 50 | if a != nil { 51 | t.Fatal("expected nil") 52 | } 53 | } 54 | 55 | func NotNil[T any](t *testing.T, a *T) { 56 | if a == nil { 57 | t.Fatal("expected not nil") 58 | } 59 | } 60 | 61 | func NotNilf[T any](t *testing.T, a *T, f string, fmtArgs ...any) { 62 | if a == nil { 63 | fatalf(t, "expected not nil\n"+f, fmtArgs...) 64 | } 65 | } 66 | 67 | func NoError(t *testing.T, err error) { 68 | if err != nil { 69 | fatalf(t, "expected no error, got %#v", err) 70 | } 71 | } 72 | 73 | func Error(t *testing.T, err error) { 74 | if err == nil { 75 | fatalf(t, "expected %T (%s), got no error", err, err) 76 | } 77 | } 78 | 79 | func ErrorIs(t *testing.T, err error, match error) { 80 | if !errors.Is(err, match) { 81 | fatalf(t, "expected %T (%s), got %#v", match, match, err) 82 | } 83 | } 84 | 85 | func Greater[T constraints.Ordered](t *testing.T, a T, b T) { 86 | if !(a > b) { 87 | fatalf(t, "assertion failed: %#v > %#v", a, b) 88 | } 89 | } 90 | 91 | func GreaterOrEqual[T constraints.Ordered](t *testing.T, a T, b T) { 92 | if !(a >= b) { 93 | fatalf(t, "assertion failed: %#v >= %#v", a, b) 94 | } 95 | } 96 | 97 | func Less[T constraints.Ordered](t *testing.T, a T, b T) { 98 | if !(a < b) { 99 | fatalf(t, "assertion failed: %#v < %#v", a, b) 100 | } 101 | } 102 | 103 | func LessOrEqual[T constraints.Ordered](t *testing.T, a T, b T) { 104 | if !(a <= b) { 105 | fatalf(t, "assertion failed: %#v <= %#v", a, b) 106 | } 107 | } 108 | 109 | func InDelta[T ~float32 | ~float64](t *testing.T, actual T, expected T, delta T) { 110 | diff := actual - expected 111 | if diff < 0 { 112 | diff = -diff 113 | } 114 | if diff > delta { 115 | fatalf(t, "expected %#v to be within %#v of %#v, actually %#v", actual, delta, expected, diff) 116 | } 117 | } 118 | 119 | func True(t *testing.T, b bool) { 120 | if !b { 121 | fatalf(t, "expected true") 122 | } 123 | } 124 | 125 | func Truef(t *testing.T, b bool, s string, fmtArgs ...any) { 126 | if !b { 127 | fatalf(t, "expected true\n"+s, fmtArgs...) 128 | } 129 | } 130 | 131 | func ElementsMatch[T comparable](t *testing.T, a []T, b []T) { 132 | aSet := make(map[T]struct{}, len(a)) 133 | for _, ai := range a { 134 | aSet[ai] = struct{}{} 135 | } 136 | bSet := make(map[T]struct{}, len(b)) 137 | for _, bi := range b { 138 | bSet[bi] = struct{}{} 139 | } 140 | 141 | for ai := range aSet { 142 | _, ok := bSet[ai] 143 | if !ok { 144 | fatalf(t, "%#v appears in a but not in b", ai) 145 | } 146 | } 147 | for bi := range bSet { 148 | _, ok := aSet[bi] 149 | if !ok { 150 | fatalf(t, "%#v appears in b but not in a", bi) 151 | } 152 | } 153 | } 154 | 155 | func fatalf(t *testing.T, s string, fmtArgs ...any) { 156 | var buf [64]uintptr 157 | var ptrs []uintptr 158 | skip := 2 159 | for { 160 | n := runtime.Callers(skip, buf[:]) 161 | ptrs = append(ptrs, buf[:n]...) 162 | if n < len(buf) { 163 | break 164 | } 165 | skip += n 166 | } 167 | var sb strings.Builder 168 | frames := runtime.CallersFrames(ptrs) 169 | for { 170 | frame, more := frames.Next() 171 | 172 | _, _ = sb.WriteString(frame.Function) 173 | _, _ = sb.WriteString("(...)\n ") 174 | _, _ = sb.WriteString(frame.File) 175 | _, _ = sb.WriteString(":") 176 | _, _ = sb.WriteString(strconv.Itoa(frame.Line)) 177 | _, _ = sb.WriteString("\n") 178 | 179 | if !more { 180 | break 181 | } 182 | } 183 | 184 | t.Fatalf(s+"\n\n%s", append(fmtArgs, sb.String())...) 185 | } 186 | -------------------------------------------------------------------------------- /internal/tseq/tseq.go: -------------------------------------------------------------------------------- 1 | package tseq 2 | 3 | import ( 4 | "encoding/hex" 5 | "flag" 6 | "testing" 7 | ) 8 | 9 | var seed string 10 | 11 | func init() { 12 | flag.StringVar(&seed, "tseq.seed", seed, "") 13 | } 14 | 15 | func Run(t *testing.T, f func(tseq *TSeq)) { 16 | tseq := TSeq{} 17 | 18 | if seed == "" { 19 | defer func() { 20 | if t.Failed() { 21 | t.Logf("rerun with --tseq.seed=%s", scriptToString(tseq.script)) 22 | } 23 | }() 24 | 25 | for { 26 | f(&tseq) 27 | if !tseq.next() { 28 | break 29 | } 30 | } 31 | } else { 32 | var err error 33 | tseq.script, err = scriptFromString(seed) 34 | if err != nil { 35 | t.Fatalf("invalid --tseq.seed %s: %s", seed, err) 36 | } 37 | f(&tseq) 38 | } 39 | } 40 | 41 | type TSeq struct { 42 | script []bool 43 | i int 44 | } 45 | 46 | func scriptToString(script []bool) string { 47 | b := make([]byte, (len(script)+7)/8) 48 | for i := range script { 49 | if script[i] { 50 | idx := i / 8 51 | off := i % 8 52 | b[idx] |= 1 << off 53 | } 54 | } 55 | return "00" + hex.EncodeToString(b) 56 | } 57 | 58 | func scriptFromString(s string) ([]bool, error) { 59 | b, err := hex.DecodeString(s) 60 | if err != nil { 61 | return nil, err 62 | } 63 | b = b[1:] 64 | script := make([]bool, len(b)*8) 65 | for i := range script { 66 | idx := i / 8 67 | off := i % 8 68 | script[i] = ((b[idx] >> off) & 1) == 1 69 | } 70 | return script, nil 71 | } 72 | 73 | func (tseq *TSeq) FlipCoin() bool { 74 | if tseq.i == len(tseq.script) { 75 | tseq.script = append(tseq.script, false) 76 | } 77 | outcome := tseq.script[tseq.i] 78 | tseq.i++ 79 | return outcome 80 | } 81 | 82 | func (tseq *TSeq) Choose(n int) int { 83 | if n == 0 { 84 | panic("can't choose between 0 choices") 85 | } 86 | i := 0 87 | j := n - 1 88 | for i < j { 89 | mid := (i + j) / 2 90 | if tseq.FlipCoin() { 91 | i = mid + 1 92 | } else { 93 | j = mid 94 | } 95 | } 96 | return i 97 | } 98 | 99 | func (tseq *TSeq) next() bool { 100 | for len(tseq.script) > 0 && tseq.script[len(tseq.script)-1] { 101 | tseq.script = tseq.script[:len(tseq.script)-1] 102 | } 103 | if len(tseq.script) == 0 { 104 | return false 105 | } 106 | tseq.script[len(tseq.script)-1] = true 107 | tseq.i = 0 108 | return true 109 | } 110 | -------------------------------------------------------------------------------- /internal/tseq/tseq_test.go: -------------------------------------------------------------------------------- 1 | package tseq 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/bradenaw/juniper/internal/require2" 8 | ) 9 | 10 | func TestTSeqBasic(t *testing.T) { 11 | runs := [][]bool{} 12 | Run(t, func(tseq *TSeq) { 13 | runs = append(runs, []bool{tseq.FlipCoin(), tseq.FlipCoin(), tseq.FlipCoin()}) 14 | }) 15 | 16 | require2.DeepEqual(t, runs, [][]bool{ 17 | {false, false, false}, 18 | {false, false, true}, 19 | {false, true, false}, 20 | {false, true, true}, 21 | {true, false, false}, 22 | {true, false, true}, 23 | {true, true, false}, 24 | {true, true, true}, 25 | }) 26 | } 27 | 28 | func TestTSeqDependent(t *testing.T) { 29 | runs := [][]bool{} 30 | Run(t, func(tseq *TSeq) { 31 | if tseq.FlipCoin() { 32 | runs = append(runs, []bool{true, tseq.FlipCoin()}) 33 | } else { 34 | runs = append(runs, []bool{false}) 35 | } 36 | }) 37 | 38 | require2.DeepEqual(t, runs, [][]bool{ 39 | {false}, 40 | {true, false}, 41 | {true, true}, 42 | }) 43 | } 44 | 45 | func TestTSeqChoose(t *testing.T) { 46 | for i := 1; i < 50; i++ { 47 | t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 48 | expected := []int{} 49 | for j := 0; j < i; j++ { 50 | expected = append(expected, j) 51 | } 52 | runs := []int{} 53 | Run(t, func(tseq *TSeq) { 54 | runs = append(runs, tseq.Choose(i)) 55 | }) 56 | 57 | require2.DeepEqual(t, runs, expected) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /iterator/iterator_example_test.go: -------------------------------------------------------------------------------- 1 | package iterator_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/bradenaw/juniper/iterator" 8 | "github.com/bradenaw/juniper/xmath" 9 | ) 10 | 11 | func ExampleIterator() { 12 | iter := iterator.Counter(5) 13 | 14 | for { 15 | item, ok := iter.Next() 16 | if !ok { 17 | break 18 | } 19 | fmt.Println(item) 20 | } 21 | 22 | // Output: 23 | // 0 24 | // 1 25 | // 2 26 | // 3 27 | // 4 28 | } 29 | 30 | func ExampleChunk() { 31 | iter := iterator.Slice([]string{"a", "b", "c", "d", "e", "f", "g", "h"}) 32 | 33 | chunked := iterator.Chunk(iter, 3) 34 | item, _ := chunked.Next() 35 | fmt.Println(item) 36 | item, _ = chunked.Next() 37 | fmt.Println(item) 38 | item, _ = chunked.Next() 39 | fmt.Println(item) 40 | 41 | // Output: 42 | // [a b c] 43 | // [d e f] 44 | // [g h] 45 | } 46 | 47 | func ExampleCompact() { 48 | iter := iterator.Slice([]string{"a", "a", "b", "c", "c", "c", "a"}) 49 | compacted := iterator.Compact(iter) 50 | fmt.Println(iterator.Collect(compacted)) 51 | 52 | // Output: 53 | // [a b c a] 54 | } 55 | 56 | func ExampleCompactFunc() { 57 | iter := iterator.Slice([]string{ 58 | "bank", 59 | "beach", 60 | "ghost", 61 | "goat", 62 | "group", 63 | "yaw", 64 | "yew", 65 | }) 66 | compacted := iterator.CompactFunc(iter, func(a, b string) bool { 67 | return a[0] == b[0] 68 | }) 69 | fmt.Println(iterator.Collect(compacted)) 70 | 71 | // Output: 72 | // [bank ghost yaw] 73 | } 74 | 75 | func ExampleEqual() { 76 | fmt.Println( 77 | iterator.Equal( 78 | iterator.Slice([]string{"a", "b", "c"}), 79 | iterator.Slice([]string{"a", "b", "c"}), 80 | ), 81 | ) 82 | 83 | fmt.Println( 84 | iterator.Equal( 85 | iterator.Slice([]string{"a", "b", "c"}), 86 | iterator.Slice([]string{"a", "b", "c", "d"}), 87 | ), 88 | ) 89 | 90 | // Output: 91 | // true 92 | // false 93 | } 94 | 95 | func ExampleFilter() { 96 | iter := iterator.Slice([]int{1, 2, 3, 4, 5, 6}) 97 | 98 | evens := iterator.Filter(iter, func(x int) bool { return x%2 == 0 }) 99 | fmt.Println(iterator.Collect(evens)) 100 | 101 | // Output: 102 | // [2 4 6] 103 | } 104 | 105 | func ExampleFlatten() { 106 | iter := iterator.Slice([]iterator.Iterator[int]{ 107 | iterator.Slice([]int{0, 1, 2}), 108 | iterator.Slice([]int{3, 4, 5, 6}), 109 | iterator.Slice([]int{7}), 110 | }) 111 | 112 | all := iterator.Flatten(iter) 113 | 114 | fmt.Println(iterator.Collect(all)) 115 | 116 | // Output: 117 | // [0 1 2 3 4 5 6 7] 118 | } 119 | 120 | func ExampleFirst() { 121 | iter := iterator.Slice([]string{"a", "b", "c", "d", "e"}) 122 | 123 | first3 := iterator.First(iter, 3) 124 | fmt.Println(iterator.Collect(first3)) 125 | 126 | // Output: 127 | // [a b c] 128 | } 129 | 130 | func ExampleJoin() { 131 | iter := iterator.Join( 132 | iterator.Counter(3), 133 | iterator.Counter(5), 134 | iterator.Counter(2), 135 | ) 136 | 137 | fmt.Println(iterator.Collect(iter)) 138 | 139 | // Output: 140 | // [0 1 2 0 1 2 3 4 0 1] 141 | } 142 | 143 | func ExampleLast() { 144 | iter := iterator.Counter(10) 145 | 146 | last3 := iterator.Last(iter, 3) 147 | fmt.Println(last3) 148 | 149 | iter = iterator.Counter(2) 150 | last3 = iterator.Last(iter, 3) 151 | fmt.Println(last3) 152 | 153 | // Output: 154 | // [7 8 9] 155 | // [0 1] 156 | } 157 | 158 | func ExampleOne() { 159 | iter := iterator.Slice([]string{"a"}) 160 | item, ok := iterator.One(iter) 161 | fmt.Println(ok) 162 | fmt.Println(item) 163 | 164 | iter = iterator.Slice([]string{"a", "b"}) 165 | item, ok = iterator.One(iter) 166 | fmt.Println(ok) 167 | 168 | // Output: 169 | // true 170 | // a 171 | // false 172 | } 173 | 174 | func ExampleRuns() { 175 | iter := iterator.Slice([]int{2, 4, 0, 7, 1, 3, 9, 2, 8}) 176 | 177 | parityRuns := iterator.Runs(iter, func(a, b int) bool { 178 | return a%2 == b%2 179 | }) 180 | fmt.Println(iterator.Collect(iterator.Map(parityRuns, iterator.Collect[int]))) 181 | 182 | // Output: 183 | // [[2 4 0] [7 1 3 9] [2 8]] 184 | } 185 | 186 | func ExampleReduce() { 187 | x := []int{3, 1, 2} 188 | 189 | iter := iterator.Slice(x) 190 | sum := iterator.Reduce(iter, 0, func(x, y int) int { return x + y }) 191 | fmt.Println(sum) 192 | 193 | iter = iterator.Slice(x) 194 | min := iterator.Reduce(iter, math.MaxInt, xmath.Min[int]) 195 | fmt.Println(min) 196 | 197 | // Output: 198 | // 6 199 | // 1 200 | } 201 | 202 | func ExampleRepeat() { 203 | iter := iterator.Repeat("a", 4) 204 | fmt.Println(iterator.Collect(iter)) 205 | 206 | // Output: 207 | // [a a a a] 208 | } 209 | 210 | func ExampleWhile() { 211 | iter := iterator.Slice([]string{ 212 | "aardvark", 213 | "badger", 214 | "cheetah", 215 | "dinosaur", 216 | "egret", 217 | }) 218 | 219 | beforeD := iterator.While(iter, func(s string) bool { 220 | return s < "d" 221 | }) 222 | 223 | fmt.Println(iterator.Collect(beforeD)) 224 | 225 | // Output: 226 | // [aardvark badger cheetah] 227 | } 228 | -------------------------------------------------------------------------------- /parallel/doc.go: -------------------------------------------------------------------------------- 1 | // Package parallel provides primitives for running tasks in parallel. 2 | package parallel 3 | -------------------------------------------------------------------------------- /parallel/parallel.go: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | "sync" 7 | "sync/atomic" 8 | 9 | "golang.org/x/sync/errgroup" 10 | 11 | "github.com/bradenaw/juniper/container/xheap" 12 | "github.com/bradenaw/juniper/iterator" 13 | "github.com/bradenaw/juniper/stream" 14 | ) 15 | 16 | // Do calls f from parallelism goroutines n times, providing each invocation a unique i in [0, n). 17 | // 18 | // If parallelism <= 0, uses GOMAXPROCS instead. 19 | func Do( 20 | parallelism int, 21 | n int, 22 | f func(i int), 23 | ) { 24 | if parallelism <= 0 { 25 | parallelism = runtime.GOMAXPROCS(-1) 26 | } 27 | 28 | if parallelism > n { 29 | parallelism = n 30 | } 31 | 32 | if parallelism == 1 { 33 | for i := 0; i < n; i++ { 34 | f(i) 35 | } 36 | return 37 | } 38 | 39 | x := int32(-1) 40 | var wg sync.WaitGroup 41 | wg.Add(parallelism) 42 | for j := 0; j < parallelism; j++ { 43 | go func() { 44 | defer wg.Done() 45 | for { 46 | i := int(atomic.AddInt32(&x, 1)) 47 | if i >= n { 48 | return 49 | } 50 | f(i) 51 | } 52 | }() 53 | } 54 | wg.Wait() 55 | return 56 | } 57 | 58 | // DoContext calls f from parallelism goroutines n times, providing each invocation a unique i in 59 | // [0, n). 60 | // 61 | // If any call to f returns an error the context passed to invocations of f is cancelled, no further 62 | // calls to f are made, and Do returns the first error encountered. 63 | // 64 | // If parallelism <= 0, uses GOMAXPROCS instead. 65 | func DoContext( 66 | ctx context.Context, 67 | parallelism int, 68 | n int, 69 | f func(ctx context.Context, i int) error, 70 | ) error { 71 | if parallelism <= 0 { 72 | parallelism = runtime.GOMAXPROCS(-1) 73 | } 74 | 75 | if parallelism > n { 76 | parallelism = n 77 | } 78 | 79 | if parallelism == 1 { 80 | for i := 0; i < n; i++ { 81 | err := f(ctx, i) 82 | if err != nil { 83 | return err 84 | } 85 | } 86 | return nil 87 | } 88 | 89 | x := int32(-1) 90 | eg, ctx := errgroup.WithContext(ctx) 91 | for j := 0; j < parallelism; j++ { 92 | eg.Go(func() error { 93 | for { 94 | i := int(atomic.AddInt32(&x, 1)) 95 | if i >= n { 96 | return nil 97 | } 98 | 99 | if ctx.Err() != nil { 100 | return ctx.Err() 101 | } 102 | 103 | err := f(ctx, i) 104 | if err != nil { 105 | return err 106 | } 107 | } 108 | }) 109 | } 110 | return eg.Wait() 111 | } 112 | 113 | // Map uses parallelism goroutines to call f once for each element of in. out[i] is the 114 | // result of f for in[i]. 115 | // 116 | // If parallelism <= 0, uses GOMAXPROCS instead. 117 | func Map[T any, U any]( 118 | parallelism int, 119 | in []T, 120 | f func(in T) U, 121 | ) []U { 122 | out := make([]U, len(in)) 123 | Do(parallelism, len(in), func(i int) { 124 | out[i] = f(in[i]) 125 | }) 126 | return out 127 | } 128 | 129 | // MapContext uses parallelism goroutines to call f once for each element of in. out[i] is the 130 | // result of f for in[i]. 131 | // 132 | // If any call to f returns an error the context passed to invocations of f is cancelled, no further 133 | // calls to f are made, and Map returns the first error encountered. 134 | // 135 | // If parallelism <= 0, uses GOMAXPROCS instead. 136 | func MapContext[T any, U any]( 137 | ctx context.Context, 138 | parallelism int, 139 | in []T, 140 | f func(ctx context.Context, in T) (U, error), 141 | ) ([]U, error) { 142 | out := make([]U, len(in)) 143 | err := DoContext(ctx, parallelism, len(in), func(ctx context.Context, i int) error { 144 | var err error 145 | out[i], err = f(ctx, in[i]) 146 | return err 147 | }) 148 | if err != nil { 149 | return nil, err 150 | } 151 | return out, nil 152 | } 153 | 154 | // MapIterator uses parallelism goroutines to call f once for each element yielded by iter. The 155 | // returned iterator returns these results in the same order that iter yielded them in. 156 | // 157 | // This iterator, in contrast with most, must be consumed completely or it will leak the goroutines. 158 | // 159 | // If parallelism <= 0, uses GOMAXPROCS instead. 160 | // 161 | // bufferSize is the size of the work buffer. A larger buffer uses more memory but gives better 162 | // throughput in the face of larger variance in the processing time for f. 163 | func MapIterator[T any, U any]( 164 | iter iterator.Iterator[T], 165 | parallelism int, 166 | bufferSize int, 167 | f func(T) U, 168 | ) iterator.Iterator[U] { 169 | if parallelism <= 0 { 170 | parallelism = runtime.GOMAXPROCS(-1) 171 | } 172 | if bufferSize < parallelism { 173 | bufferSize = parallelism 174 | } 175 | 176 | in := make(chan valueAndIndex[T]) 177 | mIter := &mapIterator[U]{ 178 | ch: make(chan valueAndIndex[U]), 179 | h: xheap.New(func(a, b valueAndIndex[U]) bool { 180 | return a.idx < b.idx 181 | }, nil), 182 | i: 0, 183 | bufferSize: bufferSize, 184 | inFlight: 0, 185 | } 186 | mIter.cond = sync.NewCond(&mIter.m) 187 | 188 | go func() { 189 | i := 0 190 | for { 191 | item, ok := iter.Next() 192 | if !ok { 193 | break 194 | } 195 | 196 | mIter.m.Lock() 197 | for mIter.inFlight >= bufferSize { 198 | mIter.cond.Wait() 199 | } 200 | mIter.inFlight++ 201 | mIter.m.Unlock() 202 | 203 | in <- valueAndIndex[T]{ 204 | value: item, 205 | idx: i, 206 | } 207 | i++ 208 | } 209 | close(in) 210 | }() 211 | 212 | nDone := uint32(0) 213 | for i := 0; i < parallelism; i++ { 214 | go func() { 215 | for item := range in { 216 | u := f(item.value) 217 | mIter.ch <- valueAndIndex[U]{value: u, idx: item.idx} 218 | } 219 | if atomic.AddUint32(&nDone, 1) == uint32(parallelism) { 220 | close(mIter.ch) 221 | } 222 | }() 223 | } 224 | return mIter 225 | } 226 | 227 | type mapIterator[U any] struct { 228 | ch chan valueAndIndex[U] 229 | m sync.Mutex 230 | cond *sync.Cond 231 | bufferSize int 232 | inFlight int 233 | h xheap.Heap[valueAndIndex[U]] 234 | i int 235 | } 236 | 237 | func (iter *mapIterator[U]) Next() (U, bool) { 238 | for { 239 | if iter.h.Len() > 0 && iter.h.Peek().idx == iter.i { 240 | item := iter.h.Pop() 241 | iter.i++ 242 | 243 | iter.m.Lock() 244 | iter.inFlight-- 245 | if iter.inFlight == iter.bufferSize-1 { 246 | iter.cond.Signal() 247 | } 248 | iter.m.Unlock() 249 | return item.value, true 250 | } 251 | item, ok := <-iter.ch 252 | if !ok { 253 | var zero U 254 | return zero, false 255 | } 256 | iter.h.Push(item) 257 | } 258 | } 259 | 260 | type valueAndIndex[T any] struct { 261 | value T 262 | idx int 263 | } 264 | 265 | // MapStream uses parallelism goroutines to call f once for each element yielded by s. The returned 266 | // stream returns these results in the same order that s yielded them in. 267 | // 268 | // If any call to f returns an error the context passed to invocations of f is cancelled, no further 269 | // calls to f are made, and the returned stream's Next returns the first error encountered. 270 | // 271 | // If parallelism <= 0, uses GOMAXPROCS instead. 272 | // 273 | // bufferSize is the size of the work buffer. A larger buffer uses more memory but gives better 274 | // throughput in the face of larger variance in the processing time for f. 275 | func MapStream[T any, U any]( 276 | ctx context.Context, 277 | s stream.Stream[T], 278 | parallelism int, 279 | bufferSize int, 280 | f func(context.Context, T) (U, error), 281 | ) stream.Stream[U] { 282 | if parallelism <= 0 { 283 | parallelism = runtime.GOMAXPROCS(-1) 284 | } 285 | if bufferSize < parallelism { 286 | bufferSize = parallelism 287 | } 288 | 289 | in := make(chan valueAndIndex[T]) 290 | ready := make(chan struct{}, bufferSize) 291 | for i := 0; i < bufferSize; i++ { 292 | ready <- struct{}{} 293 | } 294 | 295 | ctx, cancel := context.WithCancel(ctx) 296 | eg, ctx := errgroup.WithContext(ctx) 297 | eg.Go(func() error { 298 | defer s.Close() 299 | defer close(in) 300 | i := 0 301 | for { 302 | item, err := s.Next(ctx) 303 | if err == stream.End { 304 | break 305 | } else if err != nil { 306 | return err 307 | } 308 | 309 | select { 310 | case <-ctx.Done(): 311 | return ctx.Err() 312 | case <-ready: 313 | } 314 | select { 315 | case <-ctx.Done(): 316 | return ctx.Err() 317 | case in <- valueAndIndex[T]{ 318 | value: item, 319 | idx: i, 320 | }: 321 | } 322 | i++ 323 | } 324 | return nil 325 | }) 326 | 327 | c := make(chan valueAndIndex[U], bufferSize) 328 | nDone := uint32(0) 329 | for i := 0; i < parallelism; i++ { 330 | eg.Go(func() error { 331 | defer func() { 332 | if atomic.AddUint32(&nDone, 1) == uint32(parallelism) { 333 | close(c) 334 | } 335 | }() 336 | for item := range in { 337 | u, err := f(ctx, item.value) 338 | if err != nil { 339 | return err 340 | } 341 | select { 342 | case c <- valueAndIndex[U]{value: u, idx: item.idx}: 343 | case <-ctx.Done(): 344 | return ctx.Err() 345 | } 346 | } 347 | return nil 348 | }) 349 | } 350 | 351 | return &mapStream[U]{ 352 | cancel: cancel, 353 | eg: eg, 354 | c: c, 355 | ready: ready, 356 | h: xheap.New(func(a, b valueAndIndex[U]) bool { 357 | return a.idx < b.idx 358 | }, nil), 359 | i: 0, 360 | } 361 | } 362 | 363 | type mapStream[U any] struct { 364 | cancel context.CancelFunc 365 | eg *errgroup.Group 366 | c <-chan valueAndIndex[U] 367 | ready chan struct{} 368 | h xheap.Heap[valueAndIndex[U]] 369 | i int 370 | } 371 | 372 | func (s *mapStream[U]) Next(ctx context.Context) (U, error) { 373 | var zero U 374 | for { 375 | if s.h.Len() > 0 && s.h.Peek().idx == s.i { 376 | item := s.h.Pop() 377 | s.i++ 378 | s.ready <- struct{}{} 379 | return item.value, nil 380 | } 381 | select { 382 | case item, ok := <-s.c: 383 | if !ok { 384 | err := s.eg.Wait() 385 | if err != nil { 386 | return zero, err 387 | } 388 | return zero, stream.End 389 | } 390 | s.h.Push(item) 391 | case <-ctx.Done(): 392 | return zero, ctx.Err() 393 | } 394 | } 395 | } 396 | 397 | func (s *mapStream[U]) Close() { 398 | s.cancel() 399 | _ = s.eg.Wait() 400 | } 401 | -------------------------------------------------------------------------------- /parallel/parallel_test.go: -------------------------------------------------------------------------------- 1 | package parallel 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/bradenaw/juniper/internal/require2" 11 | "github.com/bradenaw/juniper/iterator" 12 | "github.com/bradenaw/juniper/stream" 13 | ) 14 | 15 | func TestMap(t *testing.T) { 16 | for _, parallelism := range []int{1, 2} { 17 | t.Run(fmt.Sprintf("parallelism=%d", parallelism), func(t *testing.T) { 18 | ints := []int{0, 1, 2, 3, 4} 19 | strs := Map( 20 | parallelism, 21 | ints, 22 | func(i int) string { 23 | return strconv.Itoa(i) 24 | }, 25 | ) 26 | require2.SlicesEqual(t, []string{"0", "1", "2", "3", "4"}, strs) 27 | }) 28 | } 29 | } 30 | 31 | func TestMapContext(t *testing.T) { 32 | for _, parallelism := range []int{1, 2} { 33 | t.Run(fmt.Sprintf("parallelism=%d", parallelism), func(t *testing.T) { 34 | ctx := context.Background() 35 | ints := []int{0, 1, 2, 3, 4} 36 | strs, err := MapContext( 37 | ctx, 38 | parallelism, 39 | ints, 40 | func(ctx context.Context, i int) (string, error) { 41 | return strconv.Itoa(i), nil 42 | }, 43 | ) 44 | require2.NoError(t, err) 45 | require2.SlicesEqual(t, []string{"0", "1", "2", "3", "4"}, strs) 46 | }) 47 | } 48 | } 49 | 50 | func TestMapIterator(t *testing.T) { 51 | strs := MapIterator( 52 | iterator.Counter(5), 53 | 2, // parallelism 54 | 0, // bufferSize 55 | func(i int) string { 56 | return strconv.Itoa(i) 57 | }, 58 | ) 59 | require2.SlicesEqual(t, []string{"0", "1", "2", "3", "4"}, iterator.Collect(strs)) 60 | } 61 | 62 | func TestMapStream(t *testing.T) { 63 | strsStream := MapStream( 64 | context.Background(), 65 | stream.FromIterator(iterator.Counter(5)), 66 | 2, // parallelism 67 | 0, // bufferSize 68 | func(ctx context.Context, i int) (string, error) { 69 | return strconv.Itoa(i), nil 70 | }, 71 | ) 72 | strs, err := stream.Collect(context.Background(), strsStream) 73 | require2.NoError(t, err) 74 | require2.SlicesEqual(t, []string{"0", "1", "2", "3", "4"}, strs) 75 | } 76 | 77 | func TestMapStreamError(t *testing.T) { 78 | sender, receiver := stream.Pipe[int](0) 79 | strsStream := MapStream( 80 | context.Background(), 81 | receiver, 82 | 2, // parallelism 83 | 0, // bufferSize 84 | func(ctx context.Context, i int) (string, error) { 85 | return strconv.Itoa(i), nil 86 | }, 87 | ) 88 | 89 | oopsError := errors.New("oops") 90 | 91 | err := sender.Send(context.Background(), 0) 92 | require2.NoError(t, err) 93 | err = sender.Send(context.Background(), 1) 94 | require2.NoError(t, err) 95 | sender.Close(oopsError) 96 | 97 | for { 98 | _, err := strsStream.Next(context.Background()) 99 | if err == nil { 100 | continue 101 | } 102 | if err != oopsError { 103 | t.Fatalf("%s", err) 104 | } 105 | break 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /stream/stream_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "github.com/bradenaw/juniper/internal/fuzz" 13 | "github.com/bradenaw/juniper/internal/require2" 14 | "github.com/bradenaw/juniper/internal/tseq" 15 | "github.com/bradenaw/juniper/iterator" 16 | "github.com/bradenaw/juniper/xmath" 17 | "github.com/bradenaw/juniper/xslices" 18 | ) 19 | 20 | type intError int 21 | 22 | func (err intError) Error() string { 23 | return strconv.Itoa(int(err)) 24 | } 25 | 26 | func FuzzBatch(f *testing.F) { 27 | f.Fuzz(func(t *testing.T, bufferSize int, batchSize int, b []byte) { 28 | bufferSize = xmath.Clamp(bufferSize, 0, 1000) 29 | batchSize = xmath.Clamp(bufferSize, 0, bufferSize) 30 | 31 | t.Logf("bufferSize = %#v", bufferSize) 32 | t.Logf("batchSize = %#v", batchSize) 33 | 34 | sender, receiver := Pipe[int](bufferSize) 35 | s := Batch(receiver, 10*time.Millisecond, batchSize) 36 | 37 | var oracle []int 38 | sendClosed := false 39 | var sendClosedErr error 40 | recvClosed := false 41 | 42 | x := 0 43 | 44 | fuzz.Operations( 45 | b, 46 | func() { // check 47 | t.Logf(" oracle = %#v", oracle) 48 | t.Logf(" sendClosed = %#v", sendClosed) 49 | t.Logf(" sendClosedErr = %#v", sendClosedErr) 50 | t.Logf(" recvClosed = %#v", recvClosed) 51 | }, 52 | func() { 53 | if sendClosed { 54 | // not allowed 55 | return 56 | } 57 | if len(oracle) == bufferSize { 58 | // might block 59 | return 60 | } 61 | t.Logf("sender.Send(ctx, %d)", x) 62 | err := sender.Send(context.Background(), x) 63 | if !recvClosed { 64 | require2.NoError(t, err) 65 | } else { 66 | require2.True(t, err == nil || errors.Is(err, ErrClosedPipe)) 67 | } 68 | oracle = append(oracle, x) 69 | x++ 70 | }, 71 | func(withErr bool) { 72 | if sendClosed { 73 | // not allowed 74 | return 75 | } 76 | if withErr { 77 | t.Logf("sender.Close(intError(%d))", x) 78 | sendClosedErr = intError(x) 79 | sender.Close(sendClosedErr) 80 | x++ 81 | } else { 82 | t.Log("sender.Close(nil)") 83 | sender.Close(nil) 84 | } 85 | sendClosed = true 86 | }, 87 | func() { 88 | if recvClosed { 89 | // not allowed 90 | return 91 | } 92 | if len(oracle) == 0 { 93 | if sendClosed { 94 | t.Log("s.Next(ctx) at end") 95 | _, err := s.Next(context.Background()) 96 | if sendClosedErr == nil { 97 | if err != End { 98 | t.Fatalf("%s", err) 99 | } 100 | } else { 101 | if err != sendClosedErr { 102 | t.Fatalf("%s", err) 103 | } 104 | } 105 | return 106 | } else { 107 | // would block 108 | return 109 | } 110 | } 111 | 112 | t.Log("s.Next(ctx)") 113 | 114 | batch, err := s.Next(context.Background()) 115 | // because of the select, this can produce either error or success for a little 116 | // while 117 | if sendClosed && err != nil { 118 | if sendClosedErr == nil { 119 | if err != End { 120 | t.Fatalf("%s", err) 121 | } 122 | } else { 123 | if err != sendClosedErr { 124 | t.Fatalf("%s", err) 125 | } 126 | } 127 | return 128 | } 129 | require2.NoError(t, err) 130 | 131 | // Unfortunately we can't actually tell if the receiver has received everything that 132 | // we sent with Send(). 133 | require2.Greater(t, len(batch), 0) 134 | require2.LessOrEqual(t, len(batch), batchSize) 135 | expectedBatch := oracle[:len(batch)] 136 | require2.SlicesEqual(t, expectedBatch, batch) 137 | 138 | t.Logf(" -> %#v", batch) 139 | 140 | oracle = oracle[len(expectedBatch):] 141 | }, 142 | func() { 143 | if recvClosed { 144 | return 145 | } 146 | t.Log("s.Close()") 147 | s.Close() 148 | recvClosed = true 149 | }, 150 | ) 151 | }) 152 | } 153 | 154 | func TestBatch(t *testing.T) { 155 | ctx := context.Background() 156 | sender, receiver := Pipe[int](1) 157 | sender.Send(ctx, 1) 158 | 159 | batches := Batch(receiver, 365*24*time.Hour, 1) 160 | _, err := batches.Next(ctx) 161 | require2.NoError(t, err) 162 | 163 | sender, receiver = Pipe[int](1) 164 | sender.Send(ctx, 1) 165 | 166 | batches = Batch(receiver, 0, 2) 167 | _, err = batches.Next(context.Background()) 168 | require2.NoError(t, err) 169 | } 170 | 171 | func TestPipeConcurrentSend(t *testing.T) { 172 | ctx := context.Background() 173 | sender, receiver := Pipe[int](0) 174 | 175 | var wg sync.WaitGroup 176 | errs := make([]error, 4) 177 | for i := 0; i < 4; i++ { 178 | i := i 179 | wg.Add(1) 180 | go func() { 181 | errs[i] = sender.Send(ctx, i) 182 | wg.Done() 183 | }() 184 | } 185 | 186 | time.Sleep(2 * time.Millisecond) 187 | 188 | results := make([]bool, 4) 189 | 190 | item, err := receiver.Next(ctx) 191 | require2.NoError(t, err) 192 | results[item] = true 193 | 194 | item, err = receiver.Next(ctx) 195 | require2.NoError(t, err) 196 | results[item] = true 197 | 198 | sender.Close(intError(5)) 199 | wg.Wait() 200 | 201 | for i := range results { 202 | require2.True(t, results[i] || errors.Is(errs[i], intError(5))) 203 | } 204 | } 205 | 206 | func TestChunk(t *testing.T) { 207 | for streamLen := 0; streamLen < 10; streamLen++ { 208 | for chunkSize := 1; chunkSize < streamLen; chunkSize++ { 209 | t.Run(fmt.Sprintf("streamLen=%d,chunkSize=%d", streamLen, chunkSize), func(t *testing.T) { 210 | tseq.Run(t, func(tseq *tseq.TSeq) { 211 | x := iterator.Collect(iterator.Counter(streamLen)) 212 | expected := xslices.Chunk(x, chunkSize) 213 | in := FromIterator(iterator.Slice(x)) 214 | s := &tseqStream[int]{in, tseq, false} 215 | chunked := collectWithRetries(Chunk[int](s, chunkSize)) 216 | require2.DeepEqual(t, expected, chunked) 217 | }) 218 | }) 219 | } 220 | } 221 | } 222 | 223 | func TestCollectWithRetries(t *testing.T) { 224 | for streamLen := 0; streamLen < 10; streamLen++ { 225 | t.Run(fmt.Sprintf("streamLen=%d", streamLen), func(t *testing.T) { 226 | tseq.Run(t, func(tseq *tseq.TSeq) { 227 | x := iterator.Collect(iterator.Counter(streamLen)) 228 | in := FromIterator(iterator.Slice(x)) 229 | s := &tseqStream[int]{in, tseq, false} 230 | out := collectWithRetries[int](s) 231 | require2.DeepEqual(t, x, out) 232 | }) 233 | }) 234 | } 235 | } 236 | 237 | type tseqStream[T any] struct { 238 | inner Stream[T] 239 | tseq *tseq.TSeq 240 | prevErr bool 241 | } 242 | 243 | func (s *tseqStream[T]) Next(ctx context.Context) (T, error) { 244 | if !s.prevErr && s.tseq.FlipCoin() { 245 | var zero T 246 | s.prevErr = true 247 | return zero, errors.New("") 248 | } 249 | s.prevErr = false 250 | return s.inner.Next(ctx) 251 | } 252 | 253 | func (s *tseqStream[T]) Close() { 254 | s.inner.Close() 255 | } 256 | 257 | func collectWithRetries[T any](s Stream[T]) []T { 258 | var out []T 259 | for { 260 | item, err := s.Next(context.Background()) 261 | if err == End { 262 | return out 263 | } else if err != nil { 264 | continue 265 | } 266 | out = append(out, item) 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/086dc23b3648d1b798461ec898a20079dc501a8b7dbad7cea2cd03b745802029: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(1) 3 | int(9) 4 | []byte("000000000000000000000000000100000000222") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/10d95885964d1ae653be1405b9dce20f7e51b56e73efa0fb57c33685490152eb: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(68) 3 | int(56) 4 | []byte("000000000000000000000000000100000000222") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/1bef05a7f3aa67e3f6ebf9a30abcdb1006c8b6f8213bda3c1d88aef20cb6d5a0: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(166) 3 | int(156) 4 | []byte("0001000007") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/23561f2fec377b58a23fc9ae112672cb799fb004a106a07891671b3f2d17b882: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(94) 3 | int(46) 4 | []byte("C0") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/2b49f746d1be4692b93e5697b8098a8b9cf332c79288cea46a0d8277fa923d2e: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(-159) 3 | int(0) 4 | []byte("A7+0") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/37aeba375901ac1e1a06110754e790ccb6fdb56d762369b632a9ac7568b02c18: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(65) 3 | int(100) 4 | []byte("00000000000000000072222000000000000000000000000000000000000000000000000000000") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/3d6d329a0d771d9fcf822119bce41a6dfa09bf2c694c5f0770c8ebccbe667d6a: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | int(-13) 4 | []byte("7") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/463b6bdff2fea2ffe7c295a165301ddd1a58a1f7e4c3c03cae3ba20e8476c394: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(65) 3 | int(100) 4 | []byte("00000xX80008000080'02080800X0X000000X0800008 08X0800x000808888008000") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/4dd43c30ae77dd634b96350d60419c975143da6a399fad7e050a747d3140362d: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(0) 3 | int(-13) 4 | []byte("2") 5 | -------------------------------------------------------------------------------- /stream/testdata/fuzz/FuzzBatch/f94e99ff4311c69dd759058d3f3e987241a8614d329464fe36c04ce57c3860cc: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(154) 3 | int(0) 4 | []byte("000\"0000000000000") 5 | -------------------------------------------------------------------------------- /test_all_versions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | go_versions=(1.21.9 1.22.9 1.23.6 1.24.0) 6 | 7 | latest="${go_versions[-1]}" 8 | if ! go version | grep "go$latest" > /dev/null; then 9 | echo >&2 "go version expected $latest, got $(go version)" 10 | exit 1 11 | fi 12 | 13 | for go_version in ${go_versions[@]}; do 14 | if [[ ${go_version} == ${latest} ]]; then 15 | go version 16 | go test --race ./... 17 | else 18 | go install "golang.org/dl/go${go_version}@latest" 19 | go_bin="${HOME}/go/bin/go${go_version}" 20 | $go_bin download 21 | $go_bin version 22 | $go_bin test --race ./... 23 | fi 24 | done 25 | -------------------------------------------------------------------------------- /xerrors/xerrors.go: -------------------------------------------------------------------------------- 1 | // Package xerrors contains extensions to the standard library package errors. 2 | package xerrors 3 | 4 | import ( 5 | "errors" 6 | "runtime" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type withStack struct { 12 | inner error 13 | pc []uintptr 14 | } 15 | 16 | func (err withStack) Error() string { 17 | var sb strings.Builder 18 | frames := runtime.CallersFrames(err.pc) 19 | _, _ = sb.WriteString(err.inner.Error()) 20 | _, _ = sb.WriteString("\n\n") 21 | for { 22 | frame, more := frames.Next() 23 | 24 | _, _ = sb.WriteString(frame.Function) 25 | _, _ = sb.WriteString("(...)\n ") 26 | _, _ = sb.WriteString(frame.File) 27 | _, _ = sb.WriteString(":") 28 | _, _ = sb.WriteString(strconv.Itoa(frame.Line)) 29 | _, _ = sb.WriteString("\n") 30 | 31 | if !more { 32 | break 33 | } 34 | } 35 | return sb.String() 36 | } 37 | 38 | func (err withStack) Unwrap() error { 39 | return err.inner 40 | } 41 | 42 | var noError = errors.New("no error") 43 | 44 | // WithStack returns an error that wraps err and adds the call stack of the call to WithStack to 45 | // Error(). If err is nil or already has a stack attached, returns err. 46 | func WithStack(err error) error { 47 | if err == nil { 48 | return nil 49 | } 50 | // use noError in case anything along err's chain has a custom Is that calls Error() for some 51 | // reason. 52 | if errors.Is(err, withStack{inner: noError}) { 53 | return err 54 | } 55 | var buf [64]uintptr 56 | var ptrs []uintptr 57 | skip := 2 58 | for { 59 | n := runtime.Callers(skip, buf[:]) 60 | ptrs = append(ptrs, buf[:n]...) 61 | if n < len(buf) { 62 | break 63 | } 64 | skip += n 65 | } 66 | return withStack{ 67 | inner: err, 68 | pc: ptrs, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /xerrors/xerrors_test.go: -------------------------------------------------------------------------------- 1 | package xerrors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func ExampleWithStack() { 10 | err := WithStack(errors.New("foo")) 11 | 12 | fmt.Println(strings.Join(strings.Split(err.Error(), "\n")[:3], "\n")) 13 | 14 | // Output: 15 | // 16 | // foo 17 | // 18 | // github.com/bradenaw/juniper/xerrors.ExampleWithStack(...) 19 | } 20 | -------------------------------------------------------------------------------- /xmaps/xmaps.go: -------------------------------------------------------------------------------- 1 | // Package xmaps contains utilities for working with maps. 2 | package xmaps 3 | 4 | import ( 5 | "fmt" 6 | 7 | "github.com/bradenaw/juniper/xslices" 8 | "github.com/bradenaw/juniper/xsort" 9 | ) 10 | 11 | // Reverse returns a map from m's values to each of the keys that mapped to it in arbitrary order. 12 | func Reverse[M ~map[K]V, K comparable, V comparable](m M) map[V][]K { 13 | result := make(map[V][]K, len(m)) 14 | for k, v := range m { 15 | result[v] = append(result[v], k) 16 | } 17 | return result 18 | } 19 | 20 | // ReverseSingle returns a map of m's values to m's keys. If there are any duplicate values, the 21 | // resulting map has an arbitrary choice of the associated keys and the second return is false. 22 | func ReverseSingle[M ~map[K]V, K comparable, V comparable](m M) (map[V]K, bool) { 23 | result := make(map[V]K, len(m)) 24 | allOk := true 25 | for k, v := range m { 26 | if _, ok := result[v]; ok { 27 | allOk = false 28 | } 29 | result[v] = k 30 | } 31 | return result, allOk 32 | } 33 | 34 | // ToIndex returns a map from keys[i] to i. 35 | func ToIndex[K comparable](keys []K) map[K]int { 36 | m := make(map[K]int, len(keys)) 37 | for i := range keys { 38 | m[keys[i]] = i 39 | } 40 | return m 41 | } 42 | 43 | // FromKeysAndValues returns a map from keys[i] to values[i]. If there are any duplicate keys, the 44 | // resulting map has an arbitrary choice of the associated values and the second return is false. It 45 | // panics if len(keys)!=len(values). 46 | func FromKeysAndValues[K comparable, V any](keys []K, values []V) (map[K]V, bool) { 47 | if len(keys) != len(values) { 48 | panic(fmt.Sprintf("len(keys)=%d, len(values)=%d", len(keys), len(values))) 49 | } 50 | m := make(map[K]V, len(keys)) 51 | allOk := true 52 | for i := range keys { 53 | if _, ok := m[keys[i]]; ok { 54 | allOk = false 55 | } 56 | m[keys[i]] = values[i] 57 | } 58 | return m, allOk 59 | } 60 | 61 | // Set[T] is shorthand for map[T]struct{} with convenience methods. 62 | type Set[T comparable] map[T]struct{} 63 | 64 | // Add adds item to the set. 65 | func (s Set[T]) Add(item T) { s[item] = struct{}{} } 66 | 67 | // Remove removes item from the set. 68 | func (s Set[T]) Remove(item T) { delete(s, item) } 69 | 70 | // Contains returns true if item is in the set. 71 | func (s Set[T]) Contains(item T) bool { _, ok := s[item]; return ok } 72 | 73 | // SetFromSlice returns a Set whose elements are items. 74 | func SetFromSlice[T comparable](items []T) Set[T] { 75 | result := make(Set[T], len(items)) 76 | for _, k := range items { 77 | result[k] = struct{}{} 78 | } 79 | return result 80 | } 81 | 82 | // Union returns a set containing all elements of all input sets. 83 | func Union[S ~map[T]struct{}, T comparable](sets ...S) S { 84 | // Size estimate: the smallest possible result is the largest input set, if it's a superset of 85 | // all of the others. 86 | size := 0 87 | for _, set := range sets { 88 | if len(set) > size { 89 | size = len(set) 90 | } 91 | } 92 | out := make(S, size) 93 | 94 | for _, set := range sets { 95 | for k := range set { 96 | out[k] = struct{}{} 97 | } 98 | } 99 | return out 100 | } 101 | 102 | // Intersection returns a set of the items that all input sets have in common. 103 | func Intersection[S ~map[T]struct{}, T comparable](sets ...S) S { 104 | // The smallest intersection is 0, so don't guess about capacity. 105 | out := make(S) 106 | if len(sets) == 0 { 107 | return out 108 | } 109 | 110 | sets = xslices.Clone(sets) 111 | xsort.Slice(sets, func(a, b S) bool { return len(a) < len(b) }) 112 | 113 | for k := range sets[0] { 114 | include := true 115 | for j := 1; j < len(sets); j++ { 116 | if _, ok := sets[j][k]; !ok { 117 | include = false 118 | break 119 | } 120 | } 121 | if include { 122 | out[k] = struct{}{} 123 | } 124 | } 125 | return out 126 | } 127 | 128 | // Intersects returns true if the input sets have any element in common. 129 | func Intersects[S ~map[T]struct{}, T comparable](sets ...S) bool { 130 | if len(sets) == 0 { 131 | return false 132 | } 133 | 134 | // Ideally we check from most-selective to least-selective so we can do the fewest iterations 135 | // of each of the below loops. Use set size as an approximation. 136 | sets = xslices.Clone(sets) 137 | xsort.Slice(sets, func(a, b S) bool { return len(a) < len(b) }) 138 | 139 | for k := range sets[0] { 140 | include := true 141 | for j := 1; j < len(sets); j++ { 142 | if _, ok := sets[j][k]; !ok { 143 | include = false 144 | break 145 | } 146 | } 147 | if include { 148 | return true 149 | } 150 | } 151 | return false 152 | } 153 | 154 | // Difference returns all items of a that do not appear in b. 155 | func Difference[S ~map[T]struct{}, T comparable](a, b S) S { 156 | // Size estimate: the smallest possible result is if all items of b are in a. 157 | size := len(a) - len(b) 158 | if size < 0 { 159 | size = 0 160 | } 161 | result := make(S, size) 162 | 163 | for k := range a { 164 | if _, ok := b[k]; !ok { 165 | result[k] = struct{}{} 166 | } 167 | } 168 | return result 169 | } 170 | -------------------------------------------------------------------------------- /xmaps/xmaps_test.go: -------------------------------------------------------------------------------- 1 | package xmaps_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bradenaw/juniper/xmaps" 7 | ) 8 | 9 | func ExampleReverse() { 10 | a := map[string]int{ 11 | "foo": 2, 12 | "bar": 1, 13 | "baz": 2, 14 | } 15 | 16 | reversed := xmaps.Reverse(a) 17 | 18 | fmt.Println(1, reversed[1][0]) 19 | fmt.Println(2, reversed[2][0]) 20 | fmt.Println(2, reversed[2][1]) 21 | 22 | // Unordered output: 23 | // 1 bar 24 | // 2 foo 25 | // 2 baz 26 | } 27 | 28 | func ExampleReverseSingle() { 29 | a := map[string]int{ 30 | "foo": 1, 31 | "bar": 2, 32 | "baz": 3, 33 | } 34 | 35 | reversed, ok := xmaps.ReverseSingle(a) 36 | fmt.Println(ok) 37 | fmt.Println(reversed) 38 | 39 | // Output: 40 | // true 41 | // map[1:foo 2:bar 3:baz] 42 | } 43 | 44 | func ExampleToIndex() { 45 | m := []string{"foo", "bar", "baz"} 46 | 47 | fmt.Println(xmaps.ToIndex(m)) 48 | 49 | // Output: 50 | // map[bar:1 baz:2 foo:0] 51 | } 52 | 53 | func ExampleUnion() { 54 | a := xmaps.Set[int]{ 55 | 1: {}, 56 | 4: {}, 57 | } 58 | b := xmaps.Set[int]{ 59 | 3: {}, 60 | 4: {}, 61 | } 62 | c := xmaps.Set[int]{ 63 | 1: {}, 64 | 5: {}, 65 | } 66 | 67 | union := xmaps.Union(a, b, c) 68 | 69 | fmt.Println(union) 70 | 71 | // Output: 72 | // map[1:{} 3:{} 4:{} 5:{}] 73 | } 74 | 75 | func ExampleIntersection() { 76 | a := xmaps.Set[int]{ 77 | 1: {}, 78 | 2: {}, 79 | 4: {}, 80 | } 81 | b := xmaps.Set[int]{ 82 | 1: {}, 83 | 3: {}, 84 | 4: {}, 85 | } 86 | c := map[int]struct{}{ 87 | 1: {}, 88 | 4: {}, 89 | 5: {}, 90 | } 91 | 92 | intersection := xmaps.Intersection(a, b, c) 93 | 94 | fmt.Println(intersection) 95 | 96 | // Output: 97 | // map[1:{} 4:{}] 98 | } 99 | 100 | func ExampleIntersects() { 101 | a := xmaps.Set[int]{ 102 | 1: {}, 103 | 2: {}, 104 | } 105 | b := xmaps.Set[int]{ 106 | 1: {}, 107 | 3: {}, 108 | } 109 | c := xmaps.Set[int]{ 110 | 3: {}, 111 | 4: {}, 112 | } 113 | 114 | fmt.Println(xmaps.Intersects(a, b)) 115 | fmt.Println(xmaps.Intersects(b, c)) 116 | fmt.Println(xmaps.Intersects(a, c)) 117 | 118 | // Output: 119 | // true 120 | // true 121 | // false 122 | } 123 | 124 | func ExampleDifference() { 125 | a := xmaps.Set[int]{ 126 | 1: {}, 127 | 4: {}, 128 | 5: {}, 129 | } 130 | b := xmaps.Set[int]{ 131 | 3: {}, 132 | 4: {}, 133 | } 134 | 135 | difference := xmaps.Difference(a, b) 136 | 137 | fmt.Println(difference) 138 | 139 | // Output: 140 | // map[1:{} 5:{}] 141 | } 142 | -------------------------------------------------------------------------------- /xmath/xmath.go: -------------------------------------------------------------------------------- 1 | // Package xmath contains extensions to the standard library package math. 2 | package xmath 3 | 4 | // Abs returns the absolute value of x. It panics if this value is not representable, for example 5 | // because -math.MinInt32 requires more than 32 bits to represent and so does not fit in an int32. 6 | func Abs[T ~int | ~int8 | ~int16 | ~int32 | ~int64](x T) T { 7 | if x < 0 { 8 | if -x == x { 9 | panic("can't xmath.Abs minimum value: positive equivalent not representable") 10 | } 11 | return -x 12 | } 13 | return x 14 | } 15 | -------------------------------------------------------------------------------- /xmath/xmath_go1.21.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package xmath 4 | 5 | import "cmp" 6 | 7 | // Min returns the minimum of a and b based on the < operator. 8 | // 9 | // Deprecated: min is a builtin as of Go 1.21. 10 | func Min[T cmp.Ordered](a, b T) T { 11 | return min(a, b) 12 | } 13 | 14 | // Max returns the maximum of a and b based on the > operator. 15 | // 16 | // Deprecated: max is a builtin as of Go 1.21. 17 | func Max[T cmp.Ordered](a, b T) T { 18 | return max(a, b) 19 | } 20 | 21 | // Clamp clamps the value of x to within min and max. 22 | func Clamp[T cmp.Ordered](x, min, max T) T { 23 | if x < min { 24 | return min 25 | } 26 | if x > max { 27 | return max 28 | } 29 | return x 30 | } 31 | -------------------------------------------------------------------------------- /xmath/xmath_old.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package xmath 4 | 5 | import "golang.org/x/exp/constraints" 6 | 7 | // Min returns the minimum of a and b based on the < operator. 8 | func Min[T constraints.Ordered](a, b T) T { 9 | if a < b { 10 | return a 11 | } 12 | return b 13 | } 14 | 15 | // Max returns the maximum of a and b based on the > operator. 16 | func Max[T constraints.Ordered](a, b T) T { 17 | if a > b { 18 | return a 19 | } 20 | return b 21 | } 22 | 23 | // Clamp clamps the value of x to within min and max. 24 | func Clamp[T constraints.Ordered](x, min, max T) T { 25 | if x < min { 26 | return min 27 | } 28 | if x > max { 29 | return max 30 | } 31 | return x 32 | } 33 | -------------------------------------------------------------------------------- /xmath/xrand/testdata/fuzz/FuzzSampleInner/026df4d84a92eefda60cf8f15aef08a4002b1faad98e27586df1de381b7922b5: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0") 3 | int(63) 4 | -------------------------------------------------------------------------------- /xmath/xrand/testdata/fuzz/FuzzSampleInner/03cc0292c8d26ab239059a35ced1322911297f07dd25db1237d0005fe0e19841: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0000000000000000000000000000000000000000") 3 | int(31) 4 | -------------------------------------------------------------------------------- /xmath/xrand/testdata/fuzz/FuzzSampleInner/05156fd053a2d13b3c9cbe36ad5755fab5f31ab3242550a49692aa79ee1e4045: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("89111B0901A100990A20C201107212718X22c817272C8807B080281X222C0702A1070X9170811272810A2179270277002012097120011812 10Ya1C8112120A111012C11198A100B") 3 | int(27) 4 | -------------------------------------------------------------------------------- /xmath/xrand/testdata/fuzz/FuzzSampleInner/086d599a93f0c1c236c6ff45cacfc93e9141c3ccf07a59535bdb572967798436: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("00000000000000000000000000000000") 3 | int(77) 4 | -------------------------------------------------------------------------------- /xmath/xrand/testdata/fuzz/FuzzSampleInner/0a0eb5ba2014c951af24c8b8f5d3fa9d19272442bee86d80d79384e9fe18abf2: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("00000000") 3 | int(20) 4 | -------------------------------------------------------------------------------- /xmath/xrand/testdata/fuzz/FuzzSampleInner/21a46963f603d3cbe3bafc40ae07dfdbfcb66058f8d3466f4ec3c5b3d3b3d618: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("\xff\xff\xff\xff\xff\xff\xff0") 3 | int(24) 4 | -------------------------------------------------------------------------------- /xmath/xrand/testdata/fuzz/FuzzSampleInner/2fbcb46cbae7d9a3d2dc693a06510cfcd404fba718dce27e17b821f76ecfedb1: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("") 3 | int(20) 4 | -------------------------------------------------------------------------------- /xmath/xrand/testdata/fuzz/FuzzSampleInner/32ebf3925cbf5e62bbf03b28b11b28a721d1f2e024b99ca5f6d93fc90aaf97df: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("") 3 | int(-61) 4 | -------------------------------------------------------------------------------- /xmath/xrand/xrand.go: -------------------------------------------------------------------------------- 1 | // Package xrand contains extensions to the standard library package math/rand. 2 | package xrand 3 | 4 | import ( 5 | "context" 6 | "math" 7 | "math/rand" 8 | 9 | "github.com/bradenaw/juniper/iterator" 10 | "github.com/bradenaw/juniper/stream" 11 | ) 12 | 13 | type randRand interface { 14 | Float64() float64 15 | Intn(int) int 16 | Shuffle(int, func(int, int)) 17 | } 18 | 19 | type defaultRand struct{} 20 | 21 | func (defaultRand) Float64() float64 { return rand.Float64() } 22 | func (defaultRand) Intn(n int) int { return rand.Intn(n) } 23 | func (defaultRand) Shuffle(n int, swap func(int, int)) { rand.Shuffle(n, swap) } 24 | 25 | // Shuffle pseudo-randomizes the order of a. 26 | func Shuffle[T any](a []T) { 27 | rShuffle(defaultRand{}, a) 28 | } 29 | 30 | // RShuffle pseudo-randomizes the order of a. 31 | func RShuffle[T any](r *rand.Rand, a []T) { 32 | rShuffle(r, a) 33 | } 34 | 35 | func rShuffle[T any, R randRand](r R, a []T) { 36 | r.Shuffle(len(a), func(i, j int) { 37 | a[i], a[j] = a[j], a[i] 38 | }) 39 | } 40 | 41 | // Sample pseudo-randomly picks k ints uniformly without replacement from [0, n). 42 | // 43 | // If n < k, returns all ints in [0, n). 44 | // 45 | // Requires O(k) time and space. 46 | func Sample(n int, k int) []int { 47 | return rSample(defaultRand{}, n, k) 48 | } 49 | 50 | // RSample pseudo-randomly picks k ints uniformly without replacement from [0, n). 51 | // 52 | // If n < k, returns all ints in [0, n). 53 | // 54 | // Requires O(k) time and space. 55 | func RSample(r *rand.Rand, n int, k int) []int { 56 | return rSample(r, n, k) 57 | } 58 | 59 | func rSample[R randRand](r R, n int, k int) []int { 60 | out := make([]int, k) 61 | samp := newSampler(r, k) 62 | for { 63 | next, replace := samp.Next() 64 | if next >= n { 65 | break 66 | } 67 | out[replace] = next 68 | } 69 | if n < k { 70 | out = out[:n] 71 | } 72 | rShuffle(r, out) 73 | return out 74 | } 75 | 76 | // SampleIterator pseudo-randomly picks k items uniformly without replacement from iter. 77 | // 78 | // If iter yields fewer than k items, returns all of them. 79 | // 80 | // Uses a reservoir sample (https://en.wikipedia.org/wiki/Reservoir_sampling), which uses time 81 | // linear in the length of iter but only O(k) extra space. 82 | func SampleIterator[T any](iter iterator.Iterator[T], k int) []T { 83 | return rSampleIterator(defaultRand{}, iter, k) 84 | } 85 | 86 | // RSampleIterator pseudo-randomly picks k items uniformly without replacement from iter. 87 | // 88 | // If iter yields fewer than k items, returns all of them. 89 | // 90 | // Uses a reservoir sample (https://en.wikipedia.org/wiki/Reservoir_sampling), which uses time 91 | // linear in the length of iter but only O(k) extra space. 92 | func RSampleIterator[T any](r *rand.Rand, iter iterator.Iterator[T], k int) []T { 93 | return rSampleIterator(r, iter, k) 94 | } 95 | 96 | func rSampleIterator[T any, R randRand](r R, iter iterator.Iterator[T], k int) []T { 97 | out := make([]T, k) 98 | i := 0 99 | samp := newSampler(r, k) 100 | Outer: 101 | for { 102 | next, replace := samp.Next() 103 | for { 104 | item, ok := iter.Next() 105 | if !ok { 106 | break Outer 107 | } 108 | if i == next { 109 | out[replace] = item 110 | i++ 111 | break 112 | } 113 | i++ 114 | } 115 | } 116 | if i < k { 117 | out = out[:i] 118 | } 119 | rShuffle(r, out) 120 | return out 121 | } 122 | 123 | // SampleStream pseudo-randomly picks k items uniformly without replacement from s. 124 | // 125 | // If s yields fewer than k items, returns all of them. 126 | // 127 | // Uses a reservoir sample (https://en.wikipedia.org/wiki/Reservoir_sampling), which uses time 128 | // linear in the length of s but only O(k) extra space. 129 | func SampleStream[T any](ctx context.Context, s stream.Stream[T], k int) ([]T, error) { 130 | return rSampleStream(ctx, defaultRand{}, s, k) 131 | } 132 | 133 | // RSampleStream pseudo-randomly picks k items uniformly without replacement from s. 134 | // 135 | // If s yields fewer than k items, returns all of them. 136 | // 137 | // Uses a reservoir sample (https://en.wikipedia.org/wiki/Reservoir_sampling), which uses time 138 | // linear in the length of s but only O(k) extra space. 139 | func RSampleStream[T any]( 140 | ctx context.Context, 141 | r *rand.Rand, 142 | s stream.Stream[T], 143 | k int, 144 | ) ([]T, error) { 145 | return rSampleStream(ctx, r, s, k) 146 | } 147 | 148 | func rSampleStream[T any, R randRand]( 149 | ctx context.Context, 150 | r R, 151 | s stream.Stream[T], 152 | k int, 153 | ) ([]T, error) { 154 | defer s.Close() 155 | 156 | out := make([]T, k) 157 | i := 0 158 | samp := newSampler(r, k) 159 | Outer: 160 | for { 161 | next, replace := samp.Next() 162 | for { 163 | item, err := s.Next(ctx) 164 | if err == stream.End { 165 | break Outer 166 | } else if err != nil { 167 | return nil, err 168 | } 169 | if i == next { 170 | out[replace] = item 171 | i++ 172 | break 173 | } 174 | i++ 175 | } 176 | } 177 | if i < k { 178 | out = out[:i] 179 | } 180 | rShuffle(r, out) 181 | return out, nil 182 | } 183 | 184 | // SampleSlice pseudo-randomly picks k items uniformly without replacement from a. 185 | // 186 | // If len(a) < k, returns all items in a. 187 | // 188 | // Uses a reservoir sample (https://en.wikipedia.org/wiki/Reservoir_sampling), which uses O(k) time 189 | // and space. 190 | func SampleSlice[T any](a []T, k int) []T { 191 | return rSampleSlice(defaultRand{}, a, k) 192 | } 193 | 194 | // RSampleSlice pseudo-randomly picks k items uniformly without replacement from a. 195 | // 196 | // If len(a) < k, returns all items in a. 197 | // 198 | // Uses a reservoir sample (https://en.wikipedia.org/wiki/Reservoir_sampling), which uses O(k) time 199 | // and space. 200 | func RSampleSlice[T any](r *rand.Rand, a []T, k int) []T { 201 | return rSampleSlice(r, a, k) 202 | } 203 | 204 | func rSampleSlice[T any, R randRand](r R, a []T, k int) []T { 205 | out := make([]T, k) 206 | samp := newSampler(r, k) 207 | for { 208 | next, replace := samp.Next() 209 | if next >= len(a) { 210 | break 211 | } 212 | out[replace] = a[next] 213 | } 214 | if len(a) < k { 215 | out = out[:len(a)] 216 | } 217 | rShuffle(r, out) 218 | return out 219 | } 220 | 221 | type sampler[R randRand] struct { 222 | i int 223 | first bool 224 | w float64 225 | k int 226 | r R 227 | } 228 | 229 | func newSampler[R randRand](r R, k int) sampler[R] { 230 | return sampler[R]{ 231 | i: 0, 232 | first: true, 233 | w: math.Exp(math.Log(r.Float64()) / float64(k)), 234 | k: k, 235 | r: r, 236 | } 237 | } 238 | 239 | // Returns (next, replace) such that next is always increasing, and that the input item at index 240 | // next (if there is one) should replace the reservoir item at index replace. 241 | // 242 | // As such, for the first k iterations, always returns (i, i) to build the reservoir. 243 | func (s *sampler[R]) Next() (int, int) { 244 | if s.i < s.k { 245 | j := s.i 246 | s.i++ 247 | return j, j 248 | } 249 | if s.first && s.i == s.k { 250 | s.i-- 251 | s.first = false 252 | } 253 | skip := math.Floor(math.Log(s.r.Float64()) / math.Log(1-s.w)) 254 | if math.IsInf(skip, 0) || math.IsNaN(skip) { 255 | return math.MaxInt, 0 256 | } 257 | s.i += int(skip) + 1 258 | s.w *= math.Exp(math.Log(s.r.Float64()) / float64(s.k)) 259 | return s.i, s.r.Intn(s.k) 260 | } 261 | -------------------------------------------------------------------------------- /xmath/xrand/xrand_example_test.go: -------------------------------------------------------------------------------- 1 | package xrand_test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "github.com/bradenaw/juniper/xmath/xrand" 8 | ) 9 | 10 | func ExampleSample() { 11 | r := rand.New(rand.NewSource(0)) 12 | 13 | sample := xrand.RSample(r, 100, 5) 14 | 15 | for _, x := range sample { 16 | fmt.Println(x) 17 | } 18 | 19 | // Unordered output: 20 | // 45 21 | // 71 22 | // 88 23 | // 93 24 | // 60 25 | } 26 | -------------------------------------------------------------------------------- /xmath/xrand/xrand_test.go: -------------------------------------------------------------------------------- 1 | package xrand 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "math" 7 | "math/rand" 8 | "testing" 9 | 10 | "github.com/bradenaw/juniper/internal/require2" 11 | "github.com/bradenaw/juniper/iterator" 12 | "github.com/bradenaw/juniper/stream" 13 | ) 14 | 15 | type fuzzRand struct { 16 | t *testing.T 17 | b []byte 18 | } 19 | 20 | func (r *fuzzRand) Intn(n int) int { 21 | if len(r.b) < 4 { 22 | return 0 23 | } 24 | x := binary.BigEndian.Uint32(r.b[:4]) 25 | r.b = r.b[4:] 26 | return int(x) % n 27 | } 28 | func (r *fuzzRand) Float64() float64 { 29 | if len(r.b) < 8 { 30 | return 0 31 | } 32 | x := binary.BigEndian.Uint64(r.b[:8]) 33 | r.b = r.b[8:] 34 | out := float64(x) / math.MaxUint64 35 | if out == 1 { 36 | out = math.Nextafter(out, 0) 37 | } 38 | require2.GreaterOrEqual(r.t, out, float64(0)) 39 | require2.Less(r.t, out, float64(1)) 40 | r.t.Logf("%f", out) 41 | return out 42 | } 43 | func (r *fuzzRand) Shuffle(int, func(int, int)) { 44 | panic("unimplemented") 45 | } 46 | 47 | func FuzzSampleInner(f *testing.F) { 48 | f.Fuzz(func(t *testing.T, b []byte, k int) { 49 | if k <= 0 { 50 | return 51 | } 52 | 53 | t.Logf("k %d", k) 54 | 55 | r := &fuzzRand{t, b} 56 | 57 | samp := newSampler(r, k) 58 | prev := 0 59 | for i := 0; i < 100; i++ { 60 | next, replace := samp.Next() 61 | t.Logf("%d: next %d replace %d", i, next, replace) 62 | if next == math.MaxInt { 63 | break 64 | } 65 | if i < k { 66 | require2.Equal(t, next, i) 67 | require2.Equal(t, replace, i) 68 | } else { 69 | require2.Greater(t, next, prev) 70 | require2.GreaterOrEqual(t, replace, 0) 71 | require2.Less(t, replace, k) 72 | } 73 | 74 | prev = next 75 | } 76 | }) 77 | } 78 | 79 | func stddev(a []int) float64 { 80 | m := mean(a) 81 | sumSquaredDeviation := float64(0) 82 | for i := range a { 83 | deviation := m - float64(a[i]) 84 | sumSquaredDeviation += (deviation * deviation) 85 | } 86 | return math.Sqrt(sumSquaredDeviation / float64(len(a))) 87 | } 88 | 89 | func mean(a []int) float64 { 90 | sum := 0 91 | for i := range a { 92 | sum += a[i] 93 | } 94 | return float64(sum) / float64(len(a)) 95 | } 96 | 97 | // f must return the same as Sample(r, 20, 5). 98 | func testSample(t *testing.T, f func(r *rand.Rand) []int) { 99 | r := rand.New(rand.NewSource(0)) 100 | 101 | counts := make([]int, 20) 102 | 103 | for i := 0; i < 10000; i++ { 104 | sample := f(r) 105 | for _, item := range sample { 106 | counts[item]++ 107 | } 108 | } 109 | m := mean(counts) 110 | 111 | t.Logf("counts %#v", counts) 112 | t.Logf("stddev %#v", stddev(counts)) 113 | t.Logf("stddev / mean %#v", stddev(counts)/m) 114 | 115 | // There's certainly a better statistical test than this, but I haven't bothered to break out 116 | // the stats book yet. 117 | require2.InDelta(t, 0.02, stddev(counts)/m, 0.01) 118 | 119 | } 120 | func TestSample(t *testing.T) { 121 | testSample(t, func(r *rand.Rand) []int { 122 | return RSample(r, 20, 5) 123 | }) 124 | } 125 | 126 | func TestSampleSlice(t *testing.T) { 127 | a := iterator.Collect(iterator.Counter(20)) 128 | testSample(t, func(r *rand.Rand) []int { 129 | return RSampleSlice(r, a, 5) 130 | }) 131 | } 132 | 133 | func TestSampleIterator(t *testing.T) { 134 | testSample(t, func(r *rand.Rand) []int { 135 | return RSampleIterator(r, iterator.Counter(20), 5) 136 | }) 137 | } 138 | 139 | func TestSampleStream(t *testing.T) { 140 | testSample(t, func(r *rand.Rand) []int { 141 | out, err := RSampleStream( 142 | context.Background(), 143 | r, 144 | stream.FromIterator(iterator.Counter(20)), 145 | 5, 146 | ) 147 | require2.NoError(t, err) 148 | return out 149 | }) 150 | } 151 | -------------------------------------------------------------------------------- /xslices/testdata/fuzz/FuzzRemoveUnordered/cfcad4015f83ee9d954573d1a6f85b097098aa91d6191ef5ca7ca8f83de48020: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(82) 3 | int(42) 4 | int(65) 5 | -------------------------------------------------------------------------------- /xslices/testdata/fuzz/FuzzRemoveUnordered/f7bbe0c14562f70cc3d89e6d9fe8862df888ab393d37a2168390c749c5d87579: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | int(73) 3 | int(0) 4 | int(-7) 5 | -------------------------------------------------------------------------------- /xslices/xslices.go: -------------------------------------------------------------------------------- 1 | // Package xslices contains utilities for working with slices of arbitrary types. 2 | package xslices 3 | 4 | // All returns true if f(s[i]) returns true for all i. Trivially, returns true if s is empty. 5 | func All[T any](s []T, f func(T) bool) bool { 6 | for i := range s { 7 | if !f(s[i]) { 8 | return false 9 | } 10 | } 11 | return true 12 | } 13 | 14 | // Chunk returns non-overlapping chunks of s. The last chunk will be smaller than chunkSize if 15 | // len(s) is not a multiple of chunkSize. 16 | // 17 | // Returns an empty slice if len(s)==0. Panics if chunkSize <= 0. 18 | func Chunk[T any](s []T, chunkSize int) [][]T { 19 | out := make([][]T, (len(s)+chunkSize-1)/chunkSize) 20 | for i := range out { 21 | start := i * chunkSize 22 | end := (i + 1) * chunkSize 23 | if end > len(s) { 24 | end = len(s) 25 | } 26 | out[i] = s[start:end] 27 | } 28 | return out 29 | } 30 | 31 | // Clear fills s with the zero value of T. 32 | // 33 | // Deprecated: clear is a builtin as of Go 1.21. 34 | func Clear[T any](s []T) { 35 | var zero T 36 | Fill(s, zero) 37 | } 38 | 39 | // Count returns the number of times x appears in s. 40 | func Count[T comparable](s []T, x T) int { 41 | return CountFunc(s, func(s T) bool { return x == s }) 42 | } 43 | 44 | // Count returns the number of items in s for which f returns true. 45 | func CountFunc[T any](s []T, f func(T) bool) int { 46 | n := 0 47 | for _, s := range s { 48 | if f(s) { 49 | n++ 50 | } 51 | } 52 | return n 53 | } 54 | 55 | // Fill fills s with copies of x. 56 | func Fill[T any](s []T, x T) { 57 | for i := range s { 58 | s[i] = x 59 | } 60 | } 61 | 62 | // Group returns a map from u to all items of s for which f(s[i]) returned u. 63 | func Group[T any, U comparable](s []T, f func(T) U) map[U][]T { 64 | m := make(map[U][]T) 65 | for i := range s { 66 | g := f(s[i]) 67 | m[g] = append(m[g], s[i]) 68 | } 69 | return m 70 | } 71 | 72 | // Join joins together the contents of each in. 73 | func Join[T any](in ...[]T) []T { 74 | n := 0 75 | for i := range in { 76 | n += len(in[i]) 77 | } 78 | out := make([]T, 0, n) 79 | for i := range in { 80 | out = append(out, in[i]...) 81 | } 82 | return out 83 | } 84 | 85 | // LastIndex returns the last index of x in s, or -1 if x is not in s. 86 | func LastIndex[T comparable](s []T, x T) int { 87 | for i := len(s) - 1; i >= 0; i-- { 88 | if s[i] == x { 89 | return i 90 | } 91 | } 92 | return -1 93 | } 94 | 95 | // LastIndexFunc returns the last index in s for which f(s[i]) returns true, or -1 if there are no 96 | // such items. 97 | func LastIndexFunc[T any](s []T, f func(T) bool) int { 98 | for i := len(s) - 1; i >= 0; i-- { 99 | if f(s[i]) { 100 | return i 101 | } 102 | } 103 | return -1 104 | } 105 | 106 | // Map creates a new slice by applying f to each element of s. 107 | func Map[T any, U any](s []T, f func(T) U) []U { 108 | out := make([]U, len(s)) 109 | for i := range s { 110 | out[i] = f(s[i]) 111 | } 112 | return out 113 | } 114 | 115 | // Partition moves elements of s such that all elements for which f returns false are at the 116 | // beginning and all elements for which f returns true are at the end. It makes no other guarantees 117 | // about the final order of elements. Returns the index of the first element for which f returned 118 | // true, or len(s) if there wasn't one. 119 | func Partition[T any](s []T, f func(t T) bool) int { 120 | i := 0 121 | j := len(s) - 1 122 | for { 123 | for i < j { 124 | if !f(s[i]) { 125 | i++ 126 | } else { 127 | break 128 | } 129 | } 130 | for j > i { 131 | if f(s[j]) { 132 | j-- 133 | } else { 134 | break 135 | } 136 | } 137 | if i >= j { 138 | break 139 | } 140 | s[i], s[j] = s[j], s[i] 141 | i++ 142 | j-- 143 | } 144 | if i < len(s) && !f(s[i]) { 145 | i++ 146 | } 147 | return i 148 | } 149 | 150 | // Reduce reduces s to a single value using the reduction function f. 151 | func Reduce[T any, U any](s []T, initial U, f func(U, T) U) U { 152 | out := initial 153 | for i := range s { 154 | out = f(out, s[i]) 155 | } 156 | return out 157 | } 158 | 159 | // RemoveUnordered removes n elements from s starting at index idx and returns the modified slice. 160 | // This is done by moving up to n elements from the end of the slice into the gap left by removal, 161 | // which is linear in n (rather than len(s)-idx as Remove() is), but does not preserve order of the 162 | // remaining elements. 163 | func RemoveUnordered[T any](s []T, idx int, n int) []T { 164 | keepStart := len(s) - n 165 | removeEnd := idx + n 166 | if removeEnd > keepStart { 167 | keepStart = removeEnd 168 | } 169 | copy(s[idx:], s[keepStart:]) 170 | Clear(s[len(s)-n:]) 171 | return s[:len(s)-n] 172 | } 173 | 174 | // Repeat returns a slice with length n where every item is s. 175 | func Repeat[T any](s T, n int) []T { 176 | out := make([]T, n) 177 | for i := range out { 178 | out[i] = s 179 | } 180 | return out 181 | } 182 | 183 | // Reverse reverses the elements of s in place. 184 | func Reverse[T any](s []T) { 185 | for i := 0; i < len(s)/2; i++ { 186 | s[i], s[len(s)-i-1] = s[len(s)-i-1], s[i] 187 | } 188 | } 189 | 190 | // Runs returns a slice of slices. The inner slices are contiguous runs of elements from s such that 191 | // same(a, b) returns true for any a and b in the run. 192 | // 193 | // same(a, a) must return true. If same(a, b) and same(b, c) both return true, then same(a, c) must 194 | // also. 195 | // 196 | // The returned slices use the same underlying array as s. 197 | func Runs[T any](s []T, same func(a, b T) bool) [][]T { 198 | var runs [][]T 199 | start := 0 200 | end := 0 201 | for i := 1; i < len(s); i++ { 202 | if same(s[i-1], s[i]) { 203 | end = i + 1 204 | } else { 205 | runs = append(runs, s[start:end]) 206 | start = i 207 | end = i + 1 208 | } 209 | } 210 | if end > 0 { 211 | runs = append(runs, s[start:]) 212 | } 213 | return runs 214 | } 215 | 216 | // Shrink shrinks s's capacity by reallocating, if necessary, so that cap(s) <= len(s) + n. 217 | func Shrink[T any](s []T, n int) []T { 218 | if cap(s) > len(s)+n { 219 | x2 := make([]T, len(s)+n) 220 | copy(x2, s) 221 | return x2[:len(s)] 222 | } 223 | return s 224 | } 225 | 226 | // Unique returns a slice that contains only the first instance of each unique item in s, preserving 227 | // order. 228 | // 229 | // Compact is more efficient if duplicates are already adjacent in s, for example if s is in sorted 230 | // order. 231 | func Unique[T comparable](s []T) []T { 232 | return uniqueInto([]T{}, s) 233 | } 234 | 235 | // UniqueInPlace returns a slice that contains only the first instance of each unique item in s, 236 | // preserving order. This is done in-place and so modifies the contents of s. The modified slice is 237 | // returned. 238 | // 239 | // Compact is more efficient if duplicates are already adjacent in s, for example if s is in sorted 240 | // order. 241 | func UniqueInPlace[T comparable](s []T) []T { 242 | filtered := uniqueInto(s[:0], s) 243 | Clear(s[len(filtered):]) 244 | return filtered 245 | } 246 | 247 | func uniqueInto[T comparable](into []T, s []T) []T { 248 | m := make(map[T]struct{}, len(s)) 249 | for i := range s { 250 | _, ok := m[s[i]] 251 | if !ok { 252 | into = append(into, s[i]) 253 | m[s[i]] = struct{}{} 254 | } 255 | } 256 | return into 257 | } 258 | -------------------------------------------------------------------------------- /xslices/xslices_example_test.go: -------------------------------------------------------------------------------- 1 | package xslices_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math" 7 | "strings" 8 | 9 | "github.com/bradenaw/juniper/xmath" 10 | "github.com/bradenaw/juniper/xslices" 11 | ) 12 | 13 | func ExampleAll() { 14 | isOdd := func(x int) bool { 15 | return x%2 != 0 16 | } 17 | 18 | allOdd := xslices.All([]int{1, 3, 5}, isOdd) 19 | fmt.Println(allOdd) 20 | 21 | allOdd = xslices.All([]int{1, 3, 6}, isOdd) 22 | fmt.Println(allOdd) 23 | 24 | // Output: 25 | // true 26 | // false 27 | } 28 | 29 | func ExampleAny() { 30 | isOdd := func(x int) bool { 31 | return x%2 != 0 32 | } 33 | 34 | anyOdd := xslices.Any([]int{2, 3, 4}, isOdd) 35 | fmt.Println(anyOdd) 36 | 37 | anyOdd = xslices.Any([]int{2, 4, 6}, isOdd) 38 | fmt.Println(anyOdd) 39 | 40 | // Output: 41 | // true 42 | // false 43 | } 44 | 45 | func ExampleClear() { 46 | s := []int{1, 2, 3} 47 | xslices.Clear(s) 48 | fmt.Println(s) 49 | 50 | // Output: 51 | // [0 0 0] 52 | } 53 | 54 | func ExampleChunk() { 55 | s := []string{"a", "b", "c", "d", "e", "f", "g", "h"} 56 | chunks := xslices.Chunk(s, 3) 57 | fmt.Println(chunks) 58 | 59 | // Output: 60 | // [[a b c] [d e f] [g h]] 61 | } 62 | 63 | func ExampleClone() { 64 | s := []int{1, 2, 3} 65 | cloned := xslices.Clone(s) 66 | fmt.Println(cloned) 67 | 68 | // Output: 69 | // [1 2 3] 70 | } 71 | 72 | func ExampleCompact() { 73 | s := []string{"a", "a", "b", "c", "c", "c", "a"} 74 | compacted := xslices.Compact(s) 75 | fmt.Println(compacted) 76 | 77 | // Output: 78 | // [a b c a] 79 | } 80 | 81 | func ExampleCompactFunc() { 82 | s := []string{ 83 | "bank", 84 | "beach", 85 | "ghost", 86 | "goat", 87 | "group", 88 | "yaw", 89 | "yew", 90 | } 91 | compacted := xslices.CompactFunc(s, func(a, b string) bool { 92 | return a[0] == b[0] 93 | }) 94 | fmt.Println(compacted) 95 | 96 | // Output: 97 | // [bank ghost yaw] 98 | } 99 | 100 | func ExampleCompactInPlace() { 101 | s := []string{"a", "a", "b", "c", "c", "c", "a"} 102 | compacted := xslices.CompactInPlace(s) 103 | fmt.Println(compacted) 104 | 105 | // Output: 106 | // [a b c a] 107 | } 108 | 109 | func ExampleCompactInPlaceFunc() { 110 | s := []string{ 111 | "bank", 112 | "beach", 113 | "ghost", 114 | "goat", 115 | "group", 116 | "yaw", 117 | "yew", 118 | } 119 | compacted := xslices.CompactInPlaceFunc(s, func(a, b string) bool { 120 | return a[0] == b[0] 121 | }) 122 | fmt.Println(compacted) 123 | 124 | // Output: 125 | // [bank ghost yaw] 126 | } 127 | 128 | func ExampleCount() { 129 | s := []string{"a", "b", "a", "a", "b"} 130 | 131 | fmt.Println(xslices.Count(s, "a")) 132 | 133 | // Output: 134 | // 3 135 | } 136 | 137 | func ExampleEqual() { 138 | x := []string{"a", "b", "c"} 139 | y := []string{"a", "b", "c"} 140 | z := []string{"a", "b", "d"} 141 | 142 | fmt.Println(xslices.Equal(x, y)) 143 | fmt.Println(xslices.Equal(x[:2], y)) 144 | fmt.Println(xslices.Equal(z, y)) 145 | 146 | // Output: 147 | // true 148 | // false 149 | // false 150 | } 151 | 152 | func ExampleEqualFunc() { 153 | x := [][]byte{[]byte("a"), []byte("b"), []byte("c")} 154 | y := [][]byte{[]byte("a"), []byte("b"), []byte("c")} 155 | z := [][]byte{[]byte("a"), []byte("b"), []byte("d")} 156 | 157 | fmt.Println(xslices.EqualFunc(x, y, bytes.Equal)) 158 | fmt.Println(xslices.EqualFunc(x[:2], y, bytes.Equal)) 159 | fmt.Println(xslices.EqualFunc(z, y, bytes.Equal)) 160 | 161 | // Output: 162 | // true 163 | // false 164 | // false 165 | } 166 | 167 | func ExampleFill() { 168 | s := []int{1, 2, 3} 169 | xslices.Fill(s, 5) 170 | fmt.Println(s) 171 | 172 | // Output: 173 | // [5 5 5] 174 | } 175 | 176 | func ExampleFilter() { 177 | s := []int{5, -9, -2, 1, -4, 8, 3} 178 | s = xslices.Filter(s, func(value int) bool { 179 | return value > 0 180 | }) 181 | fmt.Println(s) 182 | 183 | // Output: 184 | // [5 1 8 3] 185 | } 186 | 187 | func ExampleFilterInPlace() { 188 | s := []int{5, -9, -2, 1, -4, 8, 3} 189 | s = xslices.FilterInPlace(s, func(value int) bool { 190 | return value > 0 191 | }) 192 | fmt.Println(s) 193 | 194 | // Output: 195 | // [5 1 8 3] 196 | } 197 | 198 | func ExampleGrow() { 199 | s := make([]int, 0, 1) 200 | s = xslices.Grow(s, 4) 201 | fmt.Println(len(s)) 202 | fmt.Println(cap(s)) 203 | s = append(s, 1) 204 | addr := &s[0] 205 | s = append(s, 2) 206 | fmt.Println(addr == &s[0]) 207 | s = append(s, 3) 208 | fmt.Println(addr == &s[0]) 209 | s = append(s, 4) 210 | fmt.Println(addr == &s[0]) 211 | 212 | // Output: 213 | // 0 214 | // 4 215 | // true 216 | // true 217 | // true 218 | } 219 | 220 | func ExampleGroup() { 221 | words := []string{ 222 | "bank", 223 | "beach", 224 | "ghost", 225 | "goat", 226 | "group", 227 | "yaw", 228 | "yew", 229 | } 230 | 231 | groups := xslices.Group(words, func(s string) rune { 232 | return ([]rune(s))[0] 233 | }) 234 | 235 | for firstChar, group := range groups { 236 | fmt.Printf("%c: %v\n", firstChar, group) 237 | } 238 | 239 | // Unordered output: 240 | // b: [bank beach] 241 | // g: [ghost goat group] 242 | // y: [yaw yew] 243 | } 244 | 245 | func ExampleIndex() { 246 | s := []string{"a", "b", "a", "a", "b"} 247 | 248 | fmt.Println(xslices.Index(s, "b")) 249 | fmt.Println(xslices.Index(s, "c")) 250 | 251 | // Output: 252 | // 1 253 | // -1 254 | } 255 | 256 | func ExampleIndexFunc() { 257 | s := []string{ 258 | "blue", 259 | "green", 260 | "yellow", 261 | "gold", 262 | "red", 263 | } 264 | 265 | fmt.Println(xslices.IndexFunc(s, func(s string) bool { 266 | return strings.HasPrefix(s, "g") 267 | })) 268 | fmt.Println(xslices.IndexFunc(s, func(s string) bool { 269 | return strings.HasPrefix(s, "p") 270 | })) 271 | 272 | // Output: 273 | // 1 274 | // -1 275 | } 276 | 277 | func ExampleInsert() { 278 | s := []string{"a", "b", "c", "d", "e"} 279 | s = xslices.Insert(s, 3, "f", "g") 280 | fmt.Println(s) 281 | 282 | // Output: 283 | // [a b c f g d e] 284 | } 285 | 286 | func ExampleJoin() { 287 | joined := xslices.Join( 288 | []string{"a", "b", "c"}, 289 | []string{"x", "y"}, 290 | []string{"l", "m", "n", "o"}, 291 | ) 292 | 293 | fmt.Println(joined) 294 | 295 | // Output: 296 | // [a b c x y l m n o] 297 | } 298 | 299 | func ExampleLastIndex() { 300 | s := []string{"a", "b", "a", "a", "b"} 301 | 302 | fmt.Println(xslices.LastIndex(s, "a")) 303 | fmt.Println(xslices.LastIndex(s, "c")) 304 | 305 | // Output: 306 | // 3 307 | // -1 308 | } 309 | 310 | func ExampleLastIndexFunc() { 311 | s := []string{ 312 | "blue", 313 | "green", 314 | "yellow", 315 | "gold", 316 | "red", 317 | } 318 | 319 | fmt.Println(xslices.LastIndexFunc(s, func(s string) bool { 320 | return strings.HasPrefix(s, "g") 321 | })) 322 | fmt.Println(xslices.LastIndexFunc(s, func(s string) bool { 323 | return strings.HasPrefix(s, "p") 324 | })) 325 | 326 | // Output: 327 | // 3 328 | // -1 329 | } 330 | 331 | func ExampleMap() { 332 | toHalfFloat := func(x int) float32 { 333 | return float32(x) / 2 334 | } 335 | 336 | s := []int{1, 2, 3} 337 | floats := xslices.Map(s, toHalfFloat) 338 | fmt.Println(floats) 339 | 340 | // Output: 341 | // [0.5 1 1.5] 342 | } 343 | 344 | func ExamplePartition() { 345 | s := []int{11, 3, 4, 2, 7, 8, 0, 1, 14} 346 | 347 | xslices.Partition(s, func(x int) bool { return x%2 == 0 }) 348 | 349 | fmt.Println(s) 350 | 351 | // Output: 352 | // [11 3 1 7 2 8 0 4 14] 353 | } 354 | 355 | func ExampleReduce() { 356 | s := []int{3, 1, 2} 357 | 358 | sum := xslices.Reduce(s, 0, func(x, y int) int { return x + y }) 359 | fmt.Println(sum) 360 | 361 | min := xslices.Reduce(s, math.MaxInt, xmath.Min[int]) 362 | fmt.Println(min) 363 | 364 | // Output: 365 | // 6 366 | // 1 367 | } 368 | 369 | func ExampleRemove() { 370 | s := []int{1, 2, 3, 4, 5} 371 | s = xslices.Remove(s, 1, 2) 372 | fmt.Println(s) 373 | 374 | // Output: 375 | // [1 4 5] 376 | } 377 | 378 | func ExampleRemoveUnordered() { 379 | s := []int{1, 2, 3, 4, 5} 380 | s = xslices.RemoveUnordered(s, 1, 1) 381 | fmt.Println(s) 382 | 383 | s = xslices.RemoveUnordered(s, 1, 2) 384 | fmt.Println(s) 385 | 386 | // Output: 387 | // [1 5 3 4] 388 | // [1 4] 389 | } 390 | 391 | func ExampleRepeat() { 392 | s := xslices.Repeat("a", 4) 393 | fmt.Println(s) 394 | 395 | // Output: 396 | // [a a a a] 397 | } 398 | 399 | func ExampleReverse() { 400 | s := []string{"a", "b", "c", "d", "e"} 401 | xslices.Reverse(s) 402 | fmt.Println(s) 403 | 404 | // Output: 405 | // [e d c b a] 406 | } 407 | 408 | func ExampleRuns() { 409 | s := []int{2, 4, 0, 7, 1, 3, 9, 2, 8} 410 | 411 | parityRuns := xslices.Runs(s, func(a, b int) bool { 412 | return a%2 == b%2 413 | }) 414 | 415 | fmt.Println(parityRuns) 416 | 417 | // Output: 418 | // [[2 4 0] [7 1 3 9] [2 8]] 419 | } 420 | 421 | func ExampleShrink() { 422 | s := make([]int, 3, 15) 423 | s[0] = 0 424 | s[1] = 1 425 | s[2] = 2 426 | 427 | fmt.Println(s) 428 | fmt.Println(cap(s)) 429 | 430 | s = xslices.Shrink(s, 0) 431 | 432 | fmt.Println(s) 433 | fmt.Println(cap(s)) 434 | 435 | // Output: 436 | // [0 1 2] 437 | // 15 438 | // [0 1 2] 439 | // 3 440 | } 441 | 442 | func ExampleUnique() { 443 | s := []string{"a", "b", "b", "c", "a", "b", "b", "c"} 444 | unique := xslices.Unique(s) 445 | fmt.Println(unique) 446 | 447 | // Output: 448 | // [a b c] 449 | } 450 | 451 | func ExampleUniqueInPlace() { 452 | s := []string{"a", "b", "b", "c", "a", "b", "b", "c"} 453 | unique := xslices.UniqueInPlace(s) 454 | fmt.Println(unique) 455 | 456 | // Output: 457 | // [a b c] 458 | } 459 | -------------------------------------------------------------------------------- /xslices/xslices_go1.21.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package xslices 4 | 5 | import ( 6 | "slices" 7 | ) 8 | 9 | // Any returns true if f(s[i]) returns true for any i. Trivially, returns false if s is empty. 10 | // 11 | // Deprecated: slices.ContainsFunc is in the standard library as of Go 1.21. 12 | func Any[T any](s []T, f func(T) bool) bool { 13 | return slices.ContainsFunc(s, f) 14 | } 15 | 16 | // Clone creates a new slice and copies the elements of s into it. 17 | // 18 | // Deprecated: slices.Clone is in the standard library as of Go 1.21. 19 | func Clone[T any](s []T) []T { 20 | return slices.Clone(s) 21 | } 22 | 23 | // Compact returns a slice containing only the first item from each contiguous run of the same item. 24 | // 25 | // For example, this can be used to remove duplicates more cheaply than Unique when the slice is 26 | // already in sorted order. 27 | // 28 | // Deprecated: slices.Compact(slices.Clone(s)) is in the standard library as of Go 1.21. 29 | func Compact[T comparable](s []T) []T { 30 | return slices.Compact(slices.Clone(s)) 31 | } 32 | 33 | // CompactInPlace returns a slice containing only the first item from each contiguous run of the 34 | // same item. This is done in-place and so modifies the contents of s. The modified slice is 35 | // returned. 36 | // 37 | // For example, this can be used to remove duplicates more cheaply than Unique when the slice is 38 | // already in sorted order. 39 | // 40 | // Deprecated: slices.Compact is in the standard library as of Go 1.21. 41 | func CompactInPlace[T comparable](s []T) []T { 42 | return slices.Compact(s) 43 | } 44 | 45 | // CompactFunc returns a slice containing only the first item from each contiguous run of items for 46 | // which eq returns true. 47 | // 48 | // Deprecated: slices.CompactFunc(slices.Clone(s)) is in the standard library as of Go 1.21. 49 | func CompactFunc[T any](s []T, eq func(T, T) bool) []T { 50 | return slices.CompactFunc(slices.Clone(s), eq) 51 | } 52 | 53 | // CompactInPlaceFunc returns a slice containing only the first item from each contiguous run of 54 | // items for which eq returns true. This is done in-place and so modifies the contents of s. The 55 | // modified slice is returned. 56 | // 57 | // Deprecated: slices.CompactFunc is in the standard library as of Go 1.21. 58 | func CompactInPlaceFunc[T any](s []T, eq func(T, T) bool) []T { 59 | return slices.CompactFunc(s, eq) 60 | } 61 | 62 | // Equal returns true if a and b contain the same items in the same order. 63 | // 64 | // Deprecated: slices.Equal is in the standard library as of Go 1.21. 65 | func Equal[T comparable](a, b []T) bool { 66 | return slices.Equal(a, b) 67 | } 68 | 69 | // EqualFunc returns true if a and b contain the same items in the same order according to eq. 70 | // 71 | // Deprecated: slices.EqualFunc is in the standard library as of Go 1.21. 72 | func EqualFunc[T any](a, b []T, eq func(T, T) bool) bool { 73 | return slices.EqualFunc(a, b, eq) 74 | } 75 | 76 | // Filter returns a slice containing only the elements of s for which keep() returns true in the 77 | // same order that they appeared in s. 78 | // 79 | // Deprecated: slices.DeleteFunc(slices.Clone(s), f) is in the standard library as of Go 1.21, 80 | // though the polarity of the passed function is opposite: return true to remove, rather than to 81 | // retain. 82 | func Filter[T any](s []T, keep func(t T) bool) []T { 83 | return slices.DeleteFunc(slices.Clone(s), func(t T) bool { return !keep(t) }) 84 | } 85 | 86 | // FilterInPlace returns a slice containing only the elements of s for which keep() returns true in 87 | // the same order that they appeared in s. This is done in-place and so modifies the contents of s. 88 | // The modified slice is returned. 89 | // 90 | // Deprecated: slices.DeleteFunc is in the standard library as of Go 1.21, though the polarity of 91 | // the passed function is opposite: return true to remove, rather than to retain. 92 | func FilterInPlace[T any](s []T, keep func(t T) bool) []T { 93 | return slices.DeleteFunc(s, func(t T) bool { return !keep(t) }) 94 | } 95 | 96 | // Grow grows s's capacity by reallocating, if necessary, to fit n more elements and returns the 97 | // modified slice. This does not change the length of s. After Grow(s, n), the following n 98 | // append()s to s will not need to reallocate. 99 | // 100 | // Deprecated: slices.Grow is in the standard library as of Go 1.21. 101 | func Grow[T any](s []T, n int) []T { 102 | return slices.Grow(s, n) 103 | } 104 | 105 | // Index returns the first index of x in s, or -1 if x is not in s. 106 | // 107 | // Deprecated: slices.Index is in the standard library as of Go 1.21. 108 | func Index[T comparable](s []T, x T) int { 109 | return slices.Index(s, x) 110 | } 111 | 112 | // Index returns the first index in s for which f(s[i]) returns true, or -1 if there are no such 113 | // items. 114 | // 115 | // Deprecated: slices.IndexFunc is in the standard library as of Go 1.21. 116 | func IndexFunc[T any](s []T, f func(T) bool) int { 117 | return slices.IndexFunc(s, f) 118 | } 119 | 120 | // Insert inserts the given values starting at index idx, shifting elements after idx to the right 121 | // and growing the slice to make room. Insert will expand the length of the slice up to its capacity 122 | // if it can, if this isn't desired then s should be resliced to have capacity equal to its length: 123 | // 124 | // s[:len(s):len(s)] 125 | // 126 | // The time cost is O(n+m) where n is len(values) and m is len(s[idx:]). 127 | // 128 | // Deprecated: slices.Insert is in the standard library as of Go 1.21. 129 | func Insert[T any](s []T, idx int, values ...T) []T { 130 | return slices.Insert(s, idx, values...) 131 | } 132 | 133 | // Remove removes n elements from s starting at index idx and returns the modified slice. This 134 | // requires shifting the elements after the removed elements over, and so its cost is linear in the 135 | // number of elements shifted. 136 | // 137 | // Deprecated: slices.Delete is in the standard library as of Go 1.21, though slices.Delete takes 138 | // two indexes rather than an index and a length. 139 | func Remove[T any](s []T, idx int, n int) []T { 140 | return slices.Delete(s, idx, idx+n) 141 | } 142 | -------------------------------------------------------------------------------- /xslices/xslices_old.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package xslices 4 | 5 | // Any returns true if f(s[i]) returns true for any i. Trivially, returns false if s is empty. 6 | func Any[T any](s []T, f func(T) bool) bool { 7 | for i := range s { 8 | if f(s[i]) { 9 | return true 10 | } 11 | } 12 | return false 13 | } 14 | 15 | // Clone creates a new slice and copies the elements of s into it. 16 | func Clone[T any](s []T) []T { 17 | return append([]T{}, s...) 18 | } 19 | 20 | // Compact returns a slice containing only the first item from each contiguous run of the same item. 21 | // 22 | // For example, this can be used to remove duplicates more cheaply than Unique when the slice is 23 | // already in sorted order. 24 | // 25 | // Deprecated: slices.Compact(slices.Clone(s)) is in the standard library as of Go 1.21. 26 | func Compact[T comparable](s []T) []T { 27 | return compactFuncInto([]T{}, s, func(a, b T) bool { return a == b }) 28 | } 29 | 30 | // CompactInPlace returns a slice containing only the first item from each contiguous run of the 31 | // same item. This is done in-place and so modifies the contents of s. The modified slice is 32 | // returned. 33 | // 34 | // For example, this can be used to remove duplicates more cheaply than Unique when the slice is 35 | // already in sorted order. 36 | func CompactInPlace[T comparable](s []T) []T { 37 | compacted := compactFuncInto(s[:0], s, func(a, b T) bool { return a == b }) 38 | Clear(s[len(compacted):]) 39 | return compacted 40 | } 41 | 42 | // CompactFunc returns a slice containing only the first item from each contiguous run of items for 43 | // which eq returns true. 44 | // 45 | // Deprecated: slices.CompactFunc(slices.Clone(s)) is in the standard library as of Go 1.21. 46 | func CompactFunc[T any](s []T, eq func(T, T) bool) []T { 47 | return compactFuncInto([]T{}, s, eq) 48 | } 49 | 50 | // CompactInPlaceFunc returns a slice containing only the first item from each contiguous run of 51 | // items for which eq returns true. This is done in-place and so modifies the contents of s. The 52 | // modified slice is returned. 53 | func CompactInPlaceFunc[T any](s []T, eq func(T, T) bool) []T { 54 | compacted := compactFuncInto(s[:0], s, eq) 55 | Clear(s[len(compacted):]) 56 | return compacted 57 | } 58 | 59 | func compactFuncInto[T any](into []T, s []T, eq func(T, T) bool) []T { 60 | for i := range s { 61 | if i == 0 || !eq(s[i-1], s[i]) { 62 | into = append(into, s[i]) 63 | } 64 | } 65 | return into 66 | } 67 | 68 | // Equal returns true if a and b contain the same items in the same order. 69 | // 70 | // Deprecated: slices.Equal is in the standard library as of Go 1.21. 71 | func Equal[T comparable](a, b []T) bool { 72 | if len(a) != len(b) { 73 | return false 74 | } 75 | for i := range a { 76 | if a[i] != b[i] { 77 | return false 78 | } 79 | } 80 | return true 81 | } 82 | 83 | // EqualFunc returns true if a and b contain the same items in the same order according to eq. 84 | // 85 | // Deprecated: slices.EqualFunc is in the standard library as of Go 1.21. 86 | func EqualFunc[T any](a, b []T, eq func(T, T) bool) bool { 87 | if len(a) != len(b) { 88 | return false 89 | } 90 | for i := range a { 91 | if !eq(a[i], b[i]) { 92 | return false 93 | } 94 | } 95 | return true 96 | } 97 | 98 | // Filter returns a slice containing only the elements of s for which keep() returns true in the 99 | // same order that they appeared in s. 100 | // 101 | // Deprecated: slices.DeleteFunc(slices.Clone(s), f) is in the standard library as of Go 1.21, 102 | // though the polarity of the passed function is opposite: return true to remove, rather than to 103 | // retain. 104 | func Filter[T any](s []T, keep func(t T) bool) []T { 105 | return filterInto([]T{}, s, keep) 106 | } 107 | 108 | // FilterInPlace returns a slice containing only the elements of s for which keep() returns true in 109 | // the same order that they appeared in s. This is done in-place and so modifies the contents of s. 110 | // The modified slice is returned. 111 | // 112 | // Deprecated: slices.DeleteFunc is in the standard library as of Go 1.21, though the polarity of 113 | // the passed function is opposite: return true to remove, rather than to retain. 114 | func FilterInPlace[T any](s []T, keep func(t T) bool) []T { 115 | filtered := filterInto(s[:0], s, keep) 116 | // Zero out the rest in case they contain pointers, so that filtered doesn't retain references. 117 | Clear(s[len(filtered):]) 118 | return filtered 119 | } 120 | 121 | func filterInto[T any](into []T, s []T, keep func(t T) bool) []T { 122 | for i := range s { 123 | if keep(s[i]) { 124 | into = append(into, s[i]) 125 | } 126 | } 127 | return into 128 | } 129 | 130 | // Grow grows s's capacity by reallocating, if necessary, to fit n more elements and returns the 131 | // modified slice. This does not change the length of s. After Grow(s, n), the following n 132 | // append()s to s will not need to reallocate. 133 | // 134 | // Deprecated: slices.Grow is in the standard library as of Go 1.21. 135 | func Grow[T any](s []T, n int) []T { 136 | if cap(s)-len(s) < n { 137 | x2 := make([]T, len(s)+n) 138 | copy(x2, s) 139 | return x2[:len(s)] 140 | } 141 | return s 142 | } 143 | 144 | // Index returns the first index of x in s, or -1 if x is not in s. 145 | // 146 | // Deprecated: slices.Index is in the standard library as of Go 1.21. 147 | func Index[T comparable](s []T, x T) int { 148 | for i := range s { 149 | if s[i] == x { 150 | return i 151 | } 152 | } 153 | return -1 154 | } 155 | 156 | // Index returns the first index in s for which f(s[i]) returns true, or -1 if there are no such 157 | // items. 158 | // 159 | // Deprecated: slices.IndexFunc is in the standard library as of Go 1.21. 160 | func IndexFunc[T any](s []T, f func(T) bool) int { 161 | for i := range s { 162 | if f(s[i]) { 163 | return i 164 | } 165 | } 166 | return -1 167 | } 168 | 169 | // Insert inserts the given values starting at index idx, shifting elements after idx to the right 170 | // and growing the slice to make room. Insert will expand the length of the slice up to its capacity 171 | // if it can, if this isn't desired then s should be resliced to have capacity equal to its length: 172 | // 173 | // s[:len(s):len(s)] 174 | // 175 | // The time cost is O(n+m) where n is len(values) and m is len(s[idx:]). 176 | // 177 | // Deprecated: slices.Insert is in the standard library as of Go 1.21. 178 | func Insert[T any](s []T, idx int, values ...T) []T { 179 | s = Grow(s, len(values)) 180 | s = s[: len(s)+len(values) : len(s)+len(values)] 181 | copy(s[idx+len(values):], s[idx:]) 182 | copy(s[idx:], values) 183 | return s 184 | } 185 | 186 | // Remove removes n elements from s starting at index idx and returns the modified slice. This 187 | // requires shifting the elements after the removed elements over, and so its cost is linear in the 188 | // number of elements shifted. 189 | // 190 | // Deprecated: slices.Delete is in the standard library as of Go 1.21, though slices.Delete takes 191 | // two indexes rather than an index and a length. 192 | func Remove[T any](s []T, idx int, n int) []T { 193 | copy(s[idx:], s[idx+n:]) 194 | Clear(s[len(s)-n:]) 195 | return s[:len(s)-n] 196 | } 197 | -------------------------------------------------------------------------------- /xslices/xslices_test.go: -------------------------------------------------------------------------------- 1 | package xslices 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bradenaw/juniper/internal/require2" 7 | ) 8 | 9 | func FuzzPartition(f *testing.F) { 10 | f.Fuzz(func(t *testing.T, b []byte) { 11 | test := func(x byte) bool { return x%2 == 0 } 12 | 13 | t.Logf("in: %#v", b) 14 | t.Logf("in test: %#v", Map(b, test)) 15 | idx := Partition(b, test) 16 | t.Logf("out: %#v", b) 17 | t.Logf("out test: %#v", Map(b, test)) 18 | t.Logf("out idx: %d", idx) 19 | 20 | for i := 0; i < idx; i++ { 21 | require2.True(t, !test(b[i])) 22 | } 23 | for i := idx; i < len(b); i++ { 24 | require2.True(t, test(b[i])) 25 | } 26 | }) 27 | } 28 | 29 | func FuzzRemoveUnordered(f *testing.F) { 30 | f.Fuzz(func(t *testing.T, l int, idx int, n int) { 31 | if l < 0 || l > 255 || idx < 0 || idx > l-1 || n < 0 || n > l-idx { 32 | return 33 | } 34 | 35 | t.Logf("l = %d", l) 36 | t.Logf("idx = %d", idx) 37 | t.Logf("n = %d", n) 38 | 39 | x := make([]int, l) 40 | expected := make([]int, 0, l) 41 | for i := range x { 42 | x[i] = i 43 | 44 | if !(i >= idx && i < idx+n) { 45 | expected = append(expected, i) 46 | } 47 | } 48 | 49 | actual := RemoveUnordered(Clone(x), idx, n) 50 | 51 | require2.ElementsMatch(t, expected, actual) 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /xsort/testdata/fuzz/FuzzMerge/13809475f3d15cec40695eb42e92cedb1340834bde29443e88744b84494638af: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("0") 3 | int(0) 4 | int64(-44) 5 | -------------------------------------------------------------------------------- /xsort/testdata/fuzz/FuzzMerge/3a13beea93b6a963f9a530b1d6a25a89013281d73a1692d4a7e82f7f7a59c263: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("") 3 | int(0) 4 | int64(-44) 5 | -------------------------------------------------------------------------------- /xsort/testdata/fuzz/FuzzMerge/f4a395632ec0839487b2172c9916470d80756b1893f000796d0414c7aca99cdd: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("00") 3 | int(-37) 4 | int64(65) 5 | -------------------------------------------------------------------------------- /xsort/xsort.go: -------------------------------------------------------------------------------- 1 | // Package xsort contains extensions to the standard library package sort. 2 | package xsort 3 | 4 | import ( 5 | "sort" 6 | 7 | "github.com/bradenaw/juniper/internal/heap" 8 | "github.com/bradenaw/juniper/iterator" 9 | "github.com/bradenaw/juniper/xslices" 10 | ) 11 | 12 | // Returns true if a is less than b. Must follow the same rules as sort.Interface.Less. 13 | type Less[T any] func(a, b T) bool 14 | 15 | // Compile-time assert the types match. 16 | var _ Less[int] = OrderedLess[int] 17 | 18 | // Greater returns true if a > b according to less. 19 | func Greater[T any](less Less[T], a T, b T) bool { 20 | return less(b, a) 21 | } 22 | 23 | // LessOrEqual returns true if a <= b according to less. 24 | func LessOrEqual[T any](less Less[T], a T, b T) bool { 25 | // a <= b 26 | // !(a > b) 27 | // !(b < a) 28 | return !less(b, a) 29 | } 30 | 31 | // LessOrEqual returns true if a >= b according to less. 32 | func GreaterOrEqual[T any](less Less[T], a T, b T) bool { 33 | // a >= b 34 | // !(a < b) 35 | return !less(a, b) 36 | } 37 | 38 | // Equal returns true if a == b according to less. 39 | func Equal[T any](less Less[T], a T, b T) bool { 40 | return !less(a, b) && !less(b, a) 41 | } 42 | 43 | // Reverse returns a Less that orders elements in the opposite order of the provided less. 44 | func Reverse[T any](less Less[T]) Less[T] { 45 | return func(a, b T) bool { 46 | return less(b, a) 47 | } 48 | } 49 | 50 | // LessCompare returns a comparison function (as defined in [sort.SortFunc]) that matches less. 51 | func LessCompare[T any](less Less[T]) func(T, T) int { 52 | return func(a, b T) int { 53 | if less(a, b) { 54 | return -1 55 | } else if less(b, a) { 56 | return 1 57 | } else { 58 | return 0 59 | } 60 | } 61 | } 62 | 63 | // Slice sorts x in-place using the given less function to compare items. 64 | // 65 | // Follows the same rules as sort.Slice. 66 | // 67 | // Deprecated: slices.SortFunc is in the standard library as of Go 1.21. 68 | func Slice[T any](x []T, less Less[T]) { 69 | sort.Slice(x, func(i, j int) bool { 70 | return less(x[i], x[j]) 71 | }) 72 | } 73 | 74 | // SliceStable stably sorts x in-place using the given less function to compare items. 75 | // 76 | // Follows the same rules as sort.SliceStable. 77 | // 78 | // Deprecated: slices.SortStableFunc is in the standard library as of Go 1.21. 79 | func SliceStable[T any](x []T, less Less[T]) { 80 | sort.SliceStable(x, func(i, j int) bool { 81 | return less(x[i], x[j]) 82 | }) 83 | } 84 | 85 | // SliceIsSorted returns true if x is in sorted order according to the given less function. 86 | // 87 | // Follows the same rules as sort.SliceIsSorted. 88 | // 89 | // Deprecated: slices.IsSortedFunc is in the standard library as of Go 1.21. 90 | func SliceIsSorted[T any](x []T, less Less[T]) bool { 91 | return sort.SliceIsSorted(x, func(i, j int) bool { 92 | return less(x[i], x[j]) 93 | }) 94 | } 95 | 96 | // Search searches for item in x, assumed sorted according to less, and returns the index. The 97 | // return value is the index to insert item at if it is not present (it could be len(a)). 98 | // 99 | // Deprecated: slices.BinarySearchFunc is in the standard library as of Go 1.21. 100 | func Search[T any](x []T, less Less[T], item T) int { 101 | return sort.Search(len(x), func(i int) bool { 102 | return less(item, x[i]) || !less(x[i], item) 103 | }) 104 | } 105 | 106 | type valueAndSource[T any] struct { 107 | value T 108 | source int 109 | } 110 | 111 | type mergeIterator[T any] struct { 112 | in []iterator.Iterator[T] 113 | h heap.Heap[valueAndSource[T]] 114 | } 115 | 116 | func (iter *mergeIterator[T]) Next() (T, bool) { 117 | if iter.h.Len() == 0 { 118 | var zero T 119 | return zero, false 120 | } 121 | item := iter.h.Pop() 122 | nextItem, ok := iter.in[item.source].Next() 123 | if ok { 124 | iter.h.Push(valueAndSource[T]{nextItem, item.source}) 125 | } 126 | return item.value, true 127 | } 128 | 129 | // Merge returns an iterator that yields all items from in in sorted order. 130 | // 131 | // The results are undefined if the in iterators do not yield items in sorted order according to 132 | // less. 133 | // 134 | // The time complexity of Next() is O(log(k)) where k is len(in). 135 | func Merge[T any](less Less[T], in ...iterator.Iterator[T]) iterator.Iterator[T] { 136 | initial := make([]valueAndSource[T], 0, len(in)) 137 | for i := range in { 138 | item, ok := in[i].Next() 139 | if !ok { 140 | continue 141 | } 142 | initial = append(initial, valueAndSource[T]{item, i}) 143 | } 144 | h := heap.New( 145 | func(a, b valueAndSource[T]) bool { 146 | return less(a.value, b.value) 147 | }, 148 | func(a valueAndSource[T], i int) {}, 149 | initial, 150 | ) 151 | return &mergeIterator[T]{ 152 | in: in, 153 | h: h, 154 | } 155 | } 156 | 157 | // MergeSlices merges the already-sorted slices of in. Optionally, a pre-allocated out slice can be 158 | // provided to store the result into. 159 | // 160 | // The results are undefined if the in slices are not already sorted. 161 | // 162 | // The time complexity is O(n * log(k)) where n is the total number of items and k is len(in). 163 | func MergeSlices[T any](less Less[T], out []T, in ...[]T) []T { 164 | n := 0 165 | for i := range in { 166 | n += len(in[i]) 167 | } 168 | out = xslices.Grow(out[:0], n) 169 | iter := Merge(less, xslices.Map(in, iterator.Slice[T])...) 170 | for { 171 | item, ok := iter.Next() 172 | if !ok { 173 | break 174 | } 175 | out = append(out, item) 176 | } 177 | return out 178 | } 179 | 180 | // MinK returns the k minimum items according to less from iter in sorted order. If iter yields 181 | // fewer than k items, MinK returns all of them. 182 | func MinK[T any](less Less[T], iter iterator.Iterator[T], k int) []T { 183 | h := heap.New[T](heap.Less[T](Reverse(less)), func(a T, i int) {}, nil) 184 | for { 185 | item, ok := iter.Next() 186 | if !ok { 187 | break 188 | } 189 | h.Push(item) 190 | if h.Len() > k { 191 | h.Pop() 192 | } 193 | } 194 | out := make([]T, h.Len()) 195 | for i := len(out) - 1; i >= 0; i-- { 196 | out[i] = h.Pop() 197 | } 198 | return out 199 | } 200 | -------------------------------------------------------------------------------- /xsort/xsort_go1.21.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package xsort 4 | 5 | import ( 6 | "cmp" 7 | ) 8 | 9 | // OrderedLess is an implementation of Less for cmp.Ordered types by using the < operator. 10 | // 11 | // Deprecated: cmp.Less is in the standard library as of Go 1.21. 12 | func OrderedLess[T cmp.Ordered](a, b T) bool { 13 | return cmp.Less(a, b) 14 | } 15 | -------------------------------------------------------------------------------- /xsort/xsort_old.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package xsort 4 | 5 | import ( 6 | "golang.org/x/exp/constraints" 7 | ) 8 | 9 | // OrderedLess is an implementation of Less for cmp.Ordered types by using the < operator. 10 | func OrderedLess[T constraints.Ordered](a, b T) bool { 11 | return a < b 12 | } 13 | 14 | func Compare[T constraints.Ordered](x, y T) int { 15 | // Copied from the standard library, here for versions older than 1.21 when it was added. 16 | // https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/cmp/cmp.go;l=40 17 | 18 | xNaN := isNaN(x) 19 | yNaN := isNaN(y) 20 | if xNaN && yNaN { 21 | return 0 22 | } 23 | if xNaN || x < y { 24 | return -1 25 | } 26 | if yNaN || x > y { 27 | return +1 28 | } 29 | return 0 30 | } 31 | 32 | // Copied from the standard library, here for versions older than 1.21 when it was added. 33 | // https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/cmp/cmp.go;l=40 34 | // 35 | // isNaN reports whether x is a NaN without requiring the math package. 36 | // This will always return false if T is not floating-point. 37 | func isNaN[T constraints.Ordered](x T) bool { 38 | return x != x 39 | } 40 | -------------------------------------------------------------------------------- /xsort/xsort_test.go: -------------------------------------------------------------------------------- 1 | package xsort_test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | 8 | "github.com/bradenaw/juniper/internal/require2" 9 | "github.com/bradenaw/juniper/iterator" 10 | "github.com/bradenaw/juniper/xsort" 11 | ) 12 | 13 | func TestMergeSlices(t *testing.T) { 14 | check := func(in ...[]int) { 15 | var all []int 16 | for i := range in { 17 | require2.True(t, xsort.SliceIsSorted(in[i], xsort.OrderedLess[int])) 18 | all = append(all, in[i]...) 19 | } 20 | merged := xsort.MergeSlices( 21 | xsort.OrderedLess[int], 22 | nil, 23 | in..., 24 | ) 25 | require2.True(t, xsort.SliceIsSorted(merged, xsort.OrderedLess[int])) 26 | require2.ElementsMatch(t, all, merged) 27 | } 28 | 29 | check([]int{1, 2, 3}) 30 | check( 31 | []int{1, 2, 3}, 32 | []int{4, 5, 6}, 33 | ) 34 | check( 35 | []int{1, 3, 5}, 36 | []int{2, 4, 6}, 37 | ) 38 | check( 39 | []int{1, 12, 19, 27}, 40 | []int{2, 7, 13}, 41 | []int{}, 42 | []int{5}, 43 | ) 44 | } 45 | 46 | func FuzzMerge(f *testing.F) { 47 | f.Fuzz(func(t *testing.T, b []byte, n int, seed int64) { 48 | if len(b) == 0 { 49 | return 50 | } 51 | if n <= 0 { 52 | return 53 | } 54 | r := rand.New(rand.NewSource(seed)) 55 | bs := make([][]byte, (n%len(b))+1) 56 | for i := range b { 57 | j := r.Intn(len(bs)) 58 | bs[j] = append(bs[j], b[i]) 59 | } 60 | for i := range bs { 61 | xsort.Slice(bs[i], xsort.OrderedLess[byte]) 62 | } 63 | 64 | expected := append([]byte{}, b...) 65 | xsort.Slice(expected, xsort.OrderedLess[byte]) 66 | 67 | merged := xsort.Merge( 68 | xsort.OrderedLess[byte], 69 | iterator.Collect( 70 | iterator.Map( 71 | iterator.Slice(bs), 72 | func(b []byte) iterator.Iterator[byte] { return iterator.Slice(b) }, 73 | ), 74 | )..., 75 | ) 76 | 77 | require2.SlicesEqual(t, expected, iterator.Collect(merged)) 78 | }) 79 | } 80 | 81 | func ExampleSearch() { 82 | x := []string{"a", "f", "h", "i", "p", "z"} 83 | 84 | fmt.Println(xsort.Search(x, xsort.OrderedLess[string], "h")) 85 | fmt.Println(xsort.Search(x, xsort.OrderedLess[string], "k")) 86 | 87 | // Output: 88 | // 2 89 | // 4 90 | } 91 | 92 | func ExampleMerge() { 93 | listOne := []string{"a", "f", "p", "x"} 94 | listTwo := []string{"b", "e", "o", "v"} 95 | listThree := []string{"s", "z"} 96 | 97 | merged := xsort.Merge( 98 | xsort.OrderedLess[string], 99 | iterator.Slice(listOne), 100 | iterator.Slice(listTwo), 101 | iterator.Slice(listThree), 102 | ) 103 | 104 | fmt.Println(iterator.Collect(merged)) 105 | 106 | // Output: 107 | // [a b e f o p s v x z] 108 | } 109 | 110 | func ExampleMergeSlices() { 111 | listOne := []string{"a", "f", "p", "x"} 112 | listTwo := []string{"b", "e", "o", "v"} 113 | listThree := []string{"s", "z"} 114 | 115 | merged := xsort.MergeSlices( 116 | xsort.OrderedLess[string], 117 | nil, 118 | listOne, 119 | listTwo, 120 | listThree, 121 | ) 122 | 123 | fmt.Println(merged) 124 | 125 | // Output: 126 | // [a b e f o p s v x z] 127 | } 128 | 129 | func ExampleMinK() { 130 | a := []int{7, 4, 3, 8, 2, 1, 6, 9, 0, 5} 131 | 132 | iter := iterator.Slice(a) 133 | min3 := xsort.MinK(xsort.OrderedLess[int], iter, 3) 134 | fmt.Println(min3) 135 | 136 | iter = iterator.Slice(a) 137 | max3 := xsort.MinK(xsort.Reverse(xsort.OrderedLess[int]), iter, 3) 138 | fmt.Println(max3) 139 | 140 | // Output: 141 | // [0 1 2] 142 | // [9 8 7] 143 | } 144 | -------------------------------------------------------------------------------- /xsync/xsync.go: -------------------------------------------------------------------------------- 1 | // Package xsync contains extensions to the standard library package sync. 2 | package xsync 3 | 4 | import ( 5 | "context" 6 | "math/rand" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // ContextCond is equivalent to sync.Cond, except its Wait function accepts a context.Context. 12 | // 13 | // ContextConds should not be copied after first use. 14 | type ContextCond struct { 15 | m sync.RWMutex 16 | ch chan struct{} 17 | L sync.Locker 18 | } 19 | 20 | // NewContextCond returns a new ContextCond with l as its Locker. 21 | func NewContextCond(l sync.Locker) *ContextCond { 22 | return &ContextCond{ 23 | L: l, 24 | // The 1-buffering here does mean that the cond will 'remember' signals in poor fashion, 25 | // because it means a misused Signal/Wait may still work. 26 | // 27 | // However, it's necessary because otherwise there is a race in Wait() - without the 28 | // buffering here, we could see a Signal() attempt to deliver to the channel after Wait has 29 | // reached c.L.Unlock() but before reaching the select, which will then lead to a missed 30 | // wakeup. 31 | ch: make(chan struct{}, 1), 32 | } 33 | } 34 | 35 | // Broadcast wakes all goroutines blocked in Wait(), if there are any. 36 | // 37 | // It is allowed but not required for the caller to hold c.L during the call. 38 | func (c *ContextCond) Broadcast() { 39 | c.m.Lock() 40 | close(c.ch) 41 | c.ch = make(chan struct{}, 1) 42 | c.m.Unlock() 43 | } 44 | 45 | // Signal wakes one goroutine blocked in Wait(), if there is any. No guarantee is made as to which 46 | // goroutine will wake. 47 | // 48 | // It is allowed but not required for the caller to hold c.L during the call. 49 | func (c *ContextCond) Signal() { 50 | c.m.RLock() 51 | select { 52 | case c.ch <- struct{}{}: 53 | default: 54 | } 55 | c.m.RUnlock() 56 | } 57 | 58 | // Wait is equivalent to sync.Cond.Wait, except it accepts a context.Context. If the context expires 59 | // before this goroutine is woken by Broadcast or Signal, it returns ctx.Err() immediately. If an 60 | // error is returned, does not reaquire c.L before returning. 61 | func (c *ContextCond) Wait(ctx context.Context) error { 62 | c.m.RLock() 63 | ch := c.ch 64 | c.m.RUnlock() 65 | c.L.Unlock() 66 | select { 67 | case <-ctx.Done(): 68 | return ctx.Err() 69 | case <-ch: 70 | c.L.Lock() 71 | } 72 | return nil 73 | } 74 | 75 | // Group manages a group of goroutines. 76 | type Group struct { 77 | ctx context.Context 78 | cancel context.CancelFunc 79 | // held in R when spawning to check if ctx is already cancelled and in W when cancelling ctx to 80 | // make sure we never cause wg to go 0->1 while inside Wait() 81 | m sync.RWMutex 82 | wg sync.WaitGroup 83 | } 84 | 85 | // NewGroup returns a Group ready for use. The context passed to any of the f functions will be a 86 | // descendant of ctx. 87 | func NewGroup(ctx context.Context) *Group { 88 | bgCtx, cancel := context.WithCancel(ctx) 89 | return &Group{ 90 | ctx: bgCtx, 91 | cancel: cancel, 92 | } 93 | } 94 | 95 | // helper even though it's exactly g.Do so that the goroutine stack for a spawned function doesn't 96 | // confusingly show all of them as created by Do. 97 | func (g *Group) spawn(f func()) { 98 | g.m.RLock() 99 | if g.ctx.Err() != nil { 100 | g.m.RUnlock() 101 | return 102 | } 103 | g.wg.Add(1) 104 | g.m.RUnlock() 105 | 106 | go func() { 107 | f() 108 | g.wg.Done() 109 | }() 110 | } 111 | 112 | // Do calls f once from another goroutine. 113 | func (g *Group) Do(f func(ctx context.Context)) { 114 | g.spawn(func() { f(g.ctx) }) 115 | } 116 | 117 | // returns a random duration in [d - jitter, d + jitter] 118 | func jitterDuration(d time.Duration, jitter time.Duration) time.Duration { 119 | return d + time.Duration(float64(jitter)*((rand.Float64()*2)-1)) 120 | } 121 | 122 | // Periodic spawns a goroutine that calls f once per interval +/- jitter. 123 | func (g *Group) Periodic( 124 | interval time.Duration, 125 | jitter time.Duration, 126 | f func(ctx context.Context), 127 | ) { 128 | g.spawn(func() { 129 | t := time.NewTimer(jitterDuration(interval, jitter)) 130 | defer t.Stop() 131 | for { 132 | if g.ctx.Err() != nil { 133 | return 134 | } 135 | select { 136 | case <-g.ctx.Done(): 137 | return 138 | case <-t.C: 139 | } 140 | t.Reset(jitterDuration(interval, jitter)) 141 | f(g.ctx) 142 | } 143 | }) 144 | } 145 | 146 | // Trigger spawns a goroutine which calls f whenever the returned function is called. If f is 147 | // already running when triggered, f will run again immediately when it finishes. 148 | func (g *Group) Trigger(f func(ctx context.Context)) func() { 149 | c := make(chan struct{}, 1) 150 | 151 | g.spawn(func() { 152 | for { 153 | if g.ctx.Err() != nil { 154 | return 155 | } 156 | select { 157 | case <-g.ctx.Done(): 158 | return 159 | case <-c: 160 | } 161 | f(g.ctx) 162 | } 163 | }) 164 | 165 | return func() { 166 | select { 167 | case c <- struct{}{}: 168 | default: 169 | } 170 | } 171 | } 172 | 173 | // PeriodicOrTrigger spawns a goroutine which calls f whenever the returned function is called. If 174 | // f is already running when triggered, f will run again immediately when it finishes. Also calls f 175 | // when it has been interval+/-jitter since the last trigger. 176 | func (g *Group) PeriodicOrTrigger( 177 | interval time.Duration, 178 | jitter time.Duration, 179 | f func(ctx context.Context), 180 | ) func() { 181 | c := make(chan struct{}, 1) 182 | g.spawn(func() { 183 | t := time.NewTimer(jitterDuration(interval, jitter)) 184 | defer t.Stop() 185 | for { 186 | if g.ctx.Err() != nil { 187 | return 188 | } 189 | select { 190 | case <-g.ctx.Done(): 191 | return 192 | case <-t.C: 193 | t.Reset(jitterDuration(interval, jitter)) 194 | case <-c: 195 | if !t.Stop() { 196 | <-t.C 197 | } 198 | t.Reset(jitterDuration(interval, jitter)) 199 | } 200 | f(g.ctx) 201 | } 202 | }) 203 | 204 | return func() { 205 | select { 206 | case c <- struct{}{}: 207 | default: 208 | } 209 | } 210 | } 211 | 212 | // Stop cancels the context passed to spawned goroutines. After the group is stopped, no more 213 | // goroutines will be spawned. 214 | func (g *Group) Stop() { 215 | g.m.Lock() 216 | g.cancel() 217 | g.m.Unlock() 218 | } 219 | 220 | // StopAndWait cancels the context passed to any of the spawned goroutines and waits for all spawned 221 | // goroutines to exit. After the group is stopped, no more goroutines will be spawned. 222 | func (g *Group) StopAndWait() { 223 | g.Stop() 224 | g.wg.Wait() 225 | } 226 | 227 | // Map is a typesafe wrapper over sync.Map. 228 | type Map[K comparable, V any] struct { 229 | m sync.Map 230 | } 231 | 232 | func (m *Map[K, V]) Delete(key K) { 233 | m.m.Delete(key) 234 | } 235 | func (m *Map[K, V]) Load(key K) (value V, ok bool) { 236 | value_, ok := m.m.Load(key) 237 | if !ok { 238 | var zero V 239 | return zero, false 240 | } 241 | return value_.(V), ok 242 | } 243 | func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) { 244 | value_, ok := m.m.LoadAndDelete(key) 245 | if !ok { 246 | var zero V 247 | return zero, false 248 | } 249 | return value_.(V), ok 250 | } 251 | func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { 252 | actual_, loaded := m.m.LoadOrStore(key, value) 253 | return actual_.(V), loaded 254 | } 255 | func (m *Map[K, V]) Range(f func(key K, value V) bool) { 256 | m.m.Range(func(key, value interface{}) bool { 257 | return f(key.(K), value.(V)) 258 | }) 259 | } 260 | func (m *Map[K, V]) Store(key K, value V) { 261 | m.m.Store(key, value) 262 | } 263 | 264 | // Pool is a typesafe wrapper over sync.Pool. 265 | type Pool[T any] struct { 266 | p sync.Pool 267 | } 268 | 269 | func NewPool[T any](new_ func() T) Pool[T] { 270 | return Pool[T]{ 271 | p: sync.Pool{ 272 | New: func() interface{} { 273 | return new_() 274 | }, 275 | }, 276 | } 277 | } 278 | 279 | func (p *Pool[T]) Get() T { 280 | return p.p.Get().(T) 281 | } 282 | 283 | func (p *Pool[T]) Put(x T) { 284 | p.p.Put(x) 285 | } 286 | 287 | // Future can be filled with a value exactly once. Many goroutines can concurrently wait for it to 288 | // be filled. After filling, Wait() immediately returns the value it was filled with. 289 | // 290 | // Futures must be created by NewFuture and should not be copied after first use. 291 | type Future[T any] struct { 292 | c chan struct{} 293 | x T 294 | } 295 | 296 | // NewFuture returns a ready-to-use Future. 297 | func NewFuture[T any]() *Future[T] { 298 | return &Future[T]{ 299 | c: make(chan struct{}), 300 | } 301 | } 302 | 303 | // Fill fills f with value x. All active calls to Wait return x, and all future calls to Wait return 304 | // x immediately. 305 | // 306 | // Panics if f has already been filled. 307 | func (f *Future[T]) Fill(x T) { 308 | f.x = x 309 | close(f.c) 310 | } 311 | 312 | // Wait waits for f to be filled with a value and returns it. Returns immediately if f is already 313 | // filled. 314 | func (f *Future[T]) Wait() T { 315 | <-f.c 316 | return f.x 317 | } 318 | 319 | // Wait waits for f to be filled with a value and returns it, or returns ctx.Err() if ctx expires 320 | // before this happens. Returns immediately if f is already filled. 321 | func (f *Future[T]) WaitContext(ctx context.Context) (T, error) { 322 | select { 323 | case <-ctx.Done(): 324 | var zero T 325 | return zero, ctx.Err() 326 | case <-f.c: 327 | } 328 | return f.x, nil 329 | } 330 | -------------------------------------------------------------------------------- /xsync/xsync_go1.19.go: -------------------------------------------------------------------------------- 1 | //go:build go1.19 2 | 3 | package xsync 4 | 5 | import ( 6 | "sync/atomic" 7 | ) 8 | 9 | // Watchable contains a value. It is similar to an atomic.Pointer[T] but allows notifying callers 10 | // that a new value has been set. 11 | type Watchable[T any] struct { 12 | p atomic.Pointer[watchableInner[T]] 13 | } 14 | 15 | type watchableInner[T any] struct { 16 | t T 17 | c chan struct{} 18 | } 19 | 20 | // Set sets the value in w and notifies callers of Value() that there is a new value. 21 | func (w *Watchable[T]) Set(t T) { 22 | newInner := &watchableInner[T]{ 23 | t: t, 24 | c: make(chan struct{}), 25 | } 26 | oldInner := w.p.Swap(newInner) 27 | if oldInner != nil { 28 | close(oldInner.c) 29 | } 30 | } 31 | 32 | // Value returns the current value inside w and a channel that will be closed when w is Set() to a 33 | // newer value than the returned one. 34 | // 35 | // If called before the first Set(), returns the zero value of T. 36 | // 37 | // Normal usage has an observer waiting for new values in a loop: 38 | // 39 | // for { 40 | // v, changed := w.Value() 41 | // 42 | // // do something with v 43 | // 44 | // <-changed 45 | // } 46 | // 47 | // Note that the value in w may have changed multiple times between successive calls to Value(), 48 | // Value() only ever returns the last-set value. This is by design so that slow observers cannot 49 | // block Set(), unlike sending values on a channel. 50 | func (w *Watchable[T]) Value() (T, chan struct{}) { 51 | inner := w.p.Load() 52 | if inner == nil { 53 | // There's no inner, meaning w has not been Set() yet. Try filling it with an empty inner, 54 | // so that we have a channel to listen on. 55 | c := make(chan struct{}) 56 | emptyInner := &watchableInner[T]{ 57 | c: c, 58 | } 59 | // CompareAndSwap so we don't accidentally smash a real value that got put between our Load 60 | // and here. 61 | if w.p.CompareAndSwap(nil, emptyInner) { 62 | var zero T 63 | return zero, c 64 | } 65 | // If we fell through to here somebody Set() while we were trying to do this, so there's 66 | // definitely an inner now. 67 | inner = w.p.Load() 68 | } 69 | return inner.t, inner.c 70 | } 71 | -------------------------------------------------------------------------------- /xsync/xsync_go1.19_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.19 2 | 3 | package xsync 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | func ExampleWatchable() { 11 | start := time.Now() 12 | 13 | var w Watchable[int] 14 | w.Set(0) 15 | go func() { 16 | for i := 1; i < 20; i++ { 17 | w.Set(i) 18 | fmt.Printf("set %d at %s\n", i, time.Since(start).Round(time.Millisecond)) 19 | time.Sleep(5 * time.Millisecond) 20 | } 21 | }() 22 | 23 | for { 24 | v, changed := w.Value() 25 | if v == 19 { 26 | return 27 | } 28 | 29 | fmt.Printf("observed %d at %s\n", v, time.Since(start).Round(time.Millisecond)) 30 | 31 | // Sleep for longer between iterations to show that we don't slow down the setter. 32 | time.Sleep(17 * time.Millisecond) 33 | 34 | <-changed 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /xsync/xsync_go1.21.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package xsync 4 | 5 | import ( 6 | "sync" 7 | ) 8 | 9 | // Lazy makes a lazily-initialized value. On first access, it uses f to create the value. Later 10 | // accesses all receive the same value. 11 | // 12 | // Deprecated: sync.OnceValue is in the standard library as of Go 1.21. 13 | func Lazy[T any](f func() T) func() T { 14 | return sync.OnceValue(f) 15 | } 16 | 17 | func (m *Map[K, V]) CompareAndDelete(key K, old V) (deleted bool) { 18 | return m.m.CompareAndDelete(key, old) 19 | } 20 | func (m *Map[K, V]) CompareAndSwap(key K, old V, new V) (deleted bool) { 21 | return m.m.CompareAndSwap(key, old, new) 22 | } 23 | func (m *Map[K, V]) Swap(key K, value V) (previous V, loaded bool) { 24 | previousUntyped, loaded := m.m.Swap(key, value) 25 | return previousUntyped.(V), loaded 26 | } 27 | -------------------------------------------------------------------------------- /xsync/xsync_old.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package xsync 4 | 5 | import ( 6 | "sync" 7 | ) 8 | 9 | // Lazy makes a lazily-initialized value. On first access, it uses f to create the value. Later 10 | // accesses all receive the same value. 11 | func Lazy[T any](f func() T) func() T { 12 | var once sync.Once 13 | var val T 14 | return func() T { 15 | once.Do(func() { 16 | val = f() 17 | }) 18 | return val 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /xsync/xsync_test.go: -------------------------------------------------------------------------------- 1 | package xsync 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/bradenaw/juniper/xtime" 10 | ) 11 | 12 | func ExampleLazy() { 13 | var ( 14 | expensive = Lazy(func() string { 15 | fmt.Println("doing expensive init") 16 | return "foo" 17 | }) 18 | ) 19 | 20 | fmt.Println(expensive()) 21 | fmt.Println(expensive()) 22 | 23 | // Output: 24 | // doing expensive init 25 | // foo 26 | // foo 27 | } 28 | 29 | func TestGroup(t *testing.T) { 30 | g := NewGroup(context.Background()) 31 | 32 | dos := make(chan struct{}, 100) 33 | g.Do(func(ctx context.Context) { 34 | for { 35 | err := xtime.SleepContext(ctx, 50*time.Millisecond) 36 | if err != nil { 37 | return 38 | } 39 | 40 | select { 41 | case dos <- struct{}{}: 42 | default: 43 | } 44 | } 45 | }) 46 | 47 | periodics := make(chan struct{}, 100) 48 | g.Periodic(35*time.Millisecond, 0 /*jitter*/, func(ctx context.Context) { 49 | select { 50 | case periodics <- struct{}{}: 51 | default: 52 | } 53 | }) 54 | 55 | periodicOrTriggers := make(chan struct{}, 100) 56 | periodicOrTrigger := g.PeriodicOrTrigger(75*time.Millisecond, 0 /*jitter*/, func(ctx context.Context) { 57 | select { 58 | case periodicOrTriggers <- struct{}{}: 59 | default: 60 | } 61 | }) 62 | 63 | triggers := make(chan struct{}, 100) 64 | trigger := g.Trigger(func(ctx context.Context) { 65 | select { 66 | case triggers <- struct{}{}: 67 | default: 68 | } 69 | }) 70 | 71 | trigger() 72 | periodicOrTrigger() 73 | time.Sleep(200 * time.Millisecond) 74 | trigger() 75 | 76 | <-dos 77 | <-dos 78 | <-dos 79 | <-dos 80 | <-periodics 81 | <-periodics 82 | <-periodics 83 | <-periodics 84 | <-periodics 85 | <-periodicOrTriggers 86 | <-periodicOrTriggers 87 | <-periodicOrTriggers 88 | <-triggers 89 | <-triggers 90 | 91 | g.StopAndWait() 92 | 93 | g.Do(func(ctx context.Context) { 94 | panic("this will never spawn because StopAndWait was already called") 95 | }) 96 | 97 | // Jank, but just in case we'd be safe from the above panic just because the test is over. 98 | time.Sleep(200 * time.Millisecond) 99 | } 100 | -------------------------------------------------------------------------------- /xtime/xtime.go: -------------------------------------------------------------------------------- 1 | // Package xtime contains extensions to the standard library package time. 2 | package xtime 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | "math/rand" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type DeadlineTooSoonError struct { 13 | remaining time.Duration 14 | d time.Duration 15 | } 16 | 17 | func (err DeadlineTooSoonError) Error() string { 18 | return fmt.Sprintf( 19 | "not enough time remaining in context: %s remaining for %s sleep", 20 | err.remaining, 21 | err.d, 22 | ) 23 | } 24 | 25 | // SleepContext pauses the current goroutine for at least the duration d and returns nil, unless ctx 26 | // expires in the mean time in which case it returns ctx.Err(). 27 | // 28 | // A negative or zero duration causes SleepContext to return nil immediately. 29 | // 30 | // If there is less than d left until ctx's deadline, returns DeadlineTooSoonError immediately. 31 | func SleepContext(ctx context.Context, d time.Duration) error { 32 | if d <= 0 { 33 | return nil 34 | } 35 | deadline, ok := ctx.Deadline() 36 | if ok { 37 | remaining := time.Until(deadline) 38 | if remaining > d { 39 | return DeadlineTooSoonError{remaining: remaining, d: d} 40 | } 41 | } 42 | t := time.NewTimer(d) 43 | select { 44 | case <-ctx.Done(): 45 | t.Stop() 46 | return ctx.Err() 47 | case <-t.C: 48 | return nil 49 | } 50 | } 51 | 52 | // A JitterTicker holds a channel that delivers "ticks" of a clock at intervals. 53 | type JitterTicker struct { 54 | C <-chan time.Time 55 | 56 | c chan time.Time 57 | m sync.Mutex 58 | d time.Duration 59 | gen int 60 | jitter time.Duration 61 | timer *time.Timer 62 | } 63 | 64 | // NewJitterTicker is similar to time.NewTicker, but jitters the ticks by the given amount. That is, 65 | // each tick will be d+/-jitter apart. 66 | // 67 | // The duration d must be greater than zero and jitter must be less than d; if not, NewJitterTicker 68 | // will panic. 69 | func NewJitterTicker(d time.Duration, jitter time.Duration) *JitterTicker { 70 | if d <= 0 { 71 | panic("non-positive interval for NewJitterTicker") 72 | } 73 | if jitter >= d { 74 | panic("jitter greater than d") 75 | } 76 | 77 | c := make(chan time.Time, 1) 78 | t := &JitterTicker{ 79 | C: c, 80 | c: c, 81 | d: d, 82 | jitter: jitter, 83 | } 84 | t.m.Lock() 85 | t.schedule() 86 | t.m.Unlock() 87 | return t 88 | } 89 | 90 | func (t *JitterTicker) schedule() { 91 | if t.timer != nil { 92 | t.timer.Stop() 93 | } 94 | next := t.d + time.Duration(rand.Int63n(int64(t.jitter*2))) - (t.jitter) 95 | 96 | // To prevent a latent goroutine already spawned but not yet running the below function from 97 | // delivering a tick after Stop/Reset. 98 | t.gen++ 99 | gen := t.gen 100 | 101 | t.timer = time.AfterFunc(next, func() { 102 | t.m.Lock() 103 | if t.gen == gen { 104 | select { 105 | case t.c <- time.Now(): 106 | default: 107 | } 108 | t.schedule() 109 | } 110 | t.m.Unlock() 111 | }) 112 | } 113 | 114 | // Reset stops the ticker and resets its period to be the specified duration and jitter. The next 115 | // tick will arrive after the new period elapses. 116 | // 117 | // The duration d must be greater than zero and jitter must be less than d; if not, Reset will 118 | // panic. 119 | func (t *JitterTicker) Reset(d time.Duration, jitter time.Duration) { 120 | if d <= 0 { 121 | panic("non-positive interval for NewJitterTicker") 122 | } 123 | if jitter >= d { 124 | panic("jitter greater than d") 125 | } 126 | 127 | t.m.Lock() 128 | t.d = d 129 | t.jitter = jitter 130 | t.schedule() 131 | t.m.Unlock() 132 | } 133 | 134 | // Stop turns off the JitterTicker. After it returns, no more ticks will be sent. Stop does not 135 | // close the channel, to prevent a concurrent goroutine reading from the channel from seeing an 136 | // erroneous "tick". 137 | func (t *JitterTicker) Stop() { 138 | t.m.Lock() 139 | t.timer.Stop() 140 | t.gen++ 141 | t.timer = nil 142 | t.m.Unlock() 143 | } 144 | -------------------------------------------------------------------------------- /xtime/xtime_test.go: -------------------------------------------------------------------------------- 1 | package xtime 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestJitterTicker(t *testing.T) { 9 | d := 5 * time.Millisecond 10 | jitter := 2 * time.Millisecond 11 | ticker := NewJitterTicker(d, jitter) 12 | 13 | last := time.Now() 14 | check := func() { 15 | now := time.Now() 16 | elapsed := now.Sub(last) 17 | minTick := d - jitter 18 | // Add a little extra slack because of scheduling. 19 | maxTick := d + jitter + 3*time.Millisecond 20 | 21 | if elapsed < minTick { 22 | t.Fatalf("tick was %s, expected in [%s, %s]", elapsed, minTick, maxTick) 23 | } 24 | if elapsed > maxTick { 25 | t.Fatalf("tick was %s, expected in [%s, %s]", elapsed, minTick, maxTick) 26 | } 27 | 28 | last = now 29 | } 30 | 31 | for i := 0; i < 50; i++ { 32 | <-ticker.C 33 | check() 34 | } 35 | 36 | d = 10 * time.Millisecond 37 | jitter = 8 * time.Millisecond 38 | ticker.Reset(d, jitter) 39 | for i := 0; i < 20; i++ { 40 | <-ticker.C 41 | check() 42 | } 43 | } 44 | --------------------------------------------------------------------------------