Repository: samber/lo
Branch: master
Commit: 4d1dbfadf753
Files: 650
Total size: 2.2 MB
Directory structure:
gitextract_vboab2u1/
├── .github/
│ ├── FUNDING.yml
│ ├── PULL_REQUEST_TEMPLATE/
│ │ ├── config.yml
│ │ ├── new_helper.md
│ │ └── other.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── doc.yml
│ ├── lint.yml
│ ├── release.yml
│ ├── test.simd.yml
│ └── test.yml
├── .gitignore
├── .golangci.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── benchmark/
│ ├── CLAUDE.md
│ ├── core_condition_bench_test.go
│ ├── core_find_bench_test.go
│ ├── core_intersect_bench_test.go
│ ├── core_map_bench_test.go
│ ├── core_math_bench_test.go
│ ├── core_slice_bench_test.go
│ ├── core_string_bench_test.go
│ ├── core_tuples_bench_test.go
│ ├── core_type_manipulation_bench_test.go
│ ├── helpers_test.go
│ ├── it_find_bench_test.go
│ ├── it_helpers_test.go
│ ├── it_map_bench_test.go
│ ├── it_math_bench_test.go
│ ├── it_slice_bench_test.go
│ ├── it_type_manipulation_bench_test.go
│ ├── mutable_slice_bench_test.go
│ └── parallel_slice_bench_test.go
├── channel.go
├── channel_test.go
├── concurrency.go
├── concurrency_test.go
├── condition.go
├── condition_test.go
├── constraints.go
├── docs/
│ ├── .gitignore
│ ├── CLAUDE.md
│ ├── README.md
│ ├── data/
│ │ ├── core-assert.md
│ │ ├── core-assign.md
│ │ ├── core-associate.md
│ │ ├── core-asyncx.md
│ │ ├── core-attempt.md
│ │ ├── core-attemptwhile.md
│ │ ├── core-attemptwhilewithdelay.md
│ │ ├── core-attemptwithdelay.md
│ │ ├── core-buffer.md
│ │ ├── core-bufferwithtimeout.md
│ │ ├── core-camelcase.md
│ │ ├── core-capitalize.md
│ │ ├── core-channeldispatcher.md
│ │ ├── core-channeltoslice.md
│ │ ├── core-chunk.md
│ │ ├── core-chunkentries.md
│ │ ├── core-chunkstring.md
│ │ ├── core-clamp.md
│ │ ├── core-clone.md
│ │ ├── core-coalesce.md
│ │ ├── core-coalescemap.md
│ │ ├── core-coalescemaporempty.md
│ │ ├── core-coalesceorempty.md
│ │ ├── core-coalesceslice.md
│ │ ├── core-coalescesliceorempty.md
│ │ ├── core-compact.md
│ │ ├── core-concat.md
│ │ ├── core-contains.md
│ │ ├── core-containsby.md
│ │ ├── core-count.md
│ │ ├── core-countby.md
│ │ ├── core-countbyerr.md
│ │ ├── core-countvalues.md
│ │ ├── core-countvaluesby.md
│ │ ├── core-crossjoinbyerrx.md
│ │ ├── core-crossjoinbyx.md
│ │ ├── core-crossjoinx.md
│ │ ├── core-cut.md
│ │ ├── core-cutprefix.md
│ │ ├── core-cutsuffix.md
│ │ ├── core-difference.md
│ │ ├── core-dispatchingstrategy.md
│ │ ├── core-drop.md
│ │ ├── core-dropbyindex.md
│ │ ├── core-dropright.md
│ │ ├── core-droprightwhile.md
│ │ ├── core-dropwhile.md
│ │ ├── core-durationx.md
│ │ ├── core-earliest.md
│ │ ├── core-earliestby.md
│ │ ├── core-earliestbyerr.md
│ │ ├── core-elementsmatch.md
│ │ ├── core-elementsmatchby.md
│ │ ├── core-ellipsis.md
│ │ ├── core-empty.md
│ │ ├── core-emptyabletoptr.md
│ │ ├── core-entries.md
│ │ ├── core-errorsas.md
│ │ ├── core-every.md
│ │ ├── core-everyby.md
│ │ ├── core-fanin.md
│ │ ├── core-fanout.md
│ │ ├── core-fill.md
│ │ ├── core-filter.md
│ │ ├── core-filtererr.md
│ │ ├── core-filterkeys.md
│ │ ├── core-filterkeyserr.md
│ │ ├── core-filtermap.md
│ │ ├── core-filtermaptoslice.md
│ │ ├── core-filtermaptosliceerr.md
│ │ ├── core-filterreject.md
│ │ ├── core-filterslicetomap.md
│ │ ├── core-filtervalues.md
│ │ ├── core-filtervalueserr.md
│ │ ├── core-find.md
│ │ ├── core-findduplicates.md
│ │ ├── core-findduplicatesby.md
│ │ ├── core-findduplicatesbyerr.md
│ │ ├── core-finderr.md
│ │ ├── core-findindexof.md
│ │ ├── core-findkey.md
│ │ ├── core-findkeyby.md
│ │ ├── core-findlastindexof.md
│ │ ├── core-findorelse.md
│ │ ├── core-finduniques.md
│ │ ├── core-finduniquesby.md
│ │ ├── core-first.md
│ │ ├── core-firstor.md
│ │ ├── core-firstorempty.md
│ │ ├── core-flatmap.md
│ │ ├── core-flatmaperr.md
│ │ ├── core-flatten.md
│ │ ├── core-foreach.md
│ │ ├── core-foreachwhile.md
│ │ ├── core-fromanyslice.md
│ │ ├── core-fromentries.md
│ │ ├── core-frompairs.md
│ │ ├── core-fromptr.md
│ │ ├── core-fromptror.md
│ │ ├── core-fromsliceptr.md
│ │ ├── core-generator.md
│ │ ├── core-groupby.md
│ │ ├── core-groupbyerr.md
│ │ ├── core-groupbymap.md
│ │ ├── core-groupbymaperr.md
│ │ ├── core-haskey.md
│ │ ├── core-hasprefix.md
│ │ ├── core-hassuffix.md
│ │ ├── core-if.md
│ │ ├── core-indexof.md
│ │ ├── core-interleave.md
│ │ ├── core-intersect.md
│ │ ├── core-intersectby.md
│ │ ├── core-invert.md
│ │ ├── core-isempty.md
│ │ ├── core-isnil.md
│ │ ├── core-isnotempty.md
│ │ ├── core-isnotnil.md
│ │ ├── core-issorted.md
│ │ ├── core-issortedby.md
│ │ ├── core-kebabcase.md
│ │ ├── core-keyby.md
│ │ ├── core-keybyerr.md
│ │ ├── core-keyify.md
│ │ ├── core-keys.md
│ │ ├── core-last.md
│ │ ├── core-lastindexof.md
│ │ ├── core-lastor.md
│ │ ├── core-lastorempty.md
│ │ ├── core-latest.md
│ │ ├── core-latestby.md
│ │ ├── core-latestbyerr.md
│ │ ├── core-map.md
│ │ ├── core-mapentries.md
│ │ ├── core-mapentrieserr.md
│ │ ├── core-maperr.md
│ │ ├── core-mapkeys.md
│ │ ├── core-mapkeyserr.md
│ │ ├── core-maptoslice.md
│ │ ├── core-maptosliceerr.md
│ │ ├── core-mapvalues.md
│ │ ├── core-mapvalueserr.md
│ │ ├── core-max.md
│ │ ├── core-maxby.md
│ │ ├── core-maxbyerr.md
│ │ ├── core-maxindex.md
│ │ ├── core-maxindexby.md
│ │ ├── core-maxindexbyerr.md
│ │ ├── core-mean.md
│ │ ├── core-meanby.md
│ │ ├── core-meanbyerr.md
│ │ ├── core-min.md
│ │ ├── core-minby.md
│ │ ├── core-minbyerr.md
│ │ ├── core-minindex.md
│ │ ├── core-minindexby.md
│ │ ├── core-minindexbyerr.md
│ │ ├── core-mode.md
│ │ ├── core-mustx.md
│ │ ├── core-newdebounce.md
│ │ ├── core-newdebounceby.md
│ │ ├── core-newthrottle.md
│ │ ├── core-newthrottleby.md
│ │ ├── core-newthrottlebywithcount.md
│ │ ├── core-newthrottlewithcount.md
│ │ ├── core-newtransaction.md
│ │ ├── core-nil.md
│ │ ├── core-none.md
│ │ ├── core-noneby.md
│ │ ├── core-nth.md
│ │ ├── core-nthor.md
│ │ ├── core-nthorempty.md
│ │ ├── core-omitby.md
│ │ ├── core-omitbyerr.md
│ │ ├── core-omitbykeys.md
│ │ ├── core-omitbyvalues.md
│ │ ├── core-partialx.md
│ │ ├── core-partitionby.md
│ │ ├── core-partitionbyerr.md
│ │ ├── core-pascalcase.md
│ │ ├── core-pickby.md
│ │ ├── core-pickbyerr.md
│ │ ├── core-pickbykeys.md
│ │ ├── core-pickbyvalues.md
│ │ ├── core-product.md
│ │ ├── core-productby.md
│ │ ├── core-productbyerr.md
│ │ ├── core-randomstring.md
│ │ ├── core-range.md
│ │ ├── core-rangefrom.md
│ │ ├── core-rangewithsteps.md
│ │ ├── core-reduce.md
│ │ ├── core-reduceerr.md
│ │ ├── core-reduceright.md
│ │ ├── core-reducerighterr.md
│ │ ├── core-reject.md
│ │ ├── core-rejecterr.md
│ │ ├── core-rejectmap.md
│ │ ├── core-repeat.md
│ │ ├── core-repeatby.md
│ │ ├── core-repeatbyerr.md
│ │ ├── core-replace.md
│ │ ├── core-replaceall.md
│ │ ├── core-reverse.md
│ │ ├── core-runelength.md
│ │ ├── core-sample.md
│ │ ├── core-sampleby.md
│ │ ├── core-samples.md
│ │ ├── core-samplesby.md
│ │ ├── core-shuffle.md
│ │ ├── core-slice.md
│ │ ├── core-slicetochannel.md
│ │ ├── core-slicetomap.md
│ │ ├── core-sliding.md
│ │ ├── core-snakecase.md
│ │ ├── core-some.md
│ │ ├── core-someby.md
│ │ ├── core-splice.md
│ │ ├── core-subset.md
│ │ ├── core-substring.md
│ │ ├── core-sum.md
│ │ ├── core-sumby.md
│ │ ├── core-sumbyerr.md
│ │ ├── core-switch.md
│ │ ├── core-synchronize.md
│ │ ├── core-take.md
│ │ ├── core-takefilter.md
│ │ ├── core-takewhile.md
│ │ ├── core-ternary.md
│ │ ├── core-times.md
│ │ ├── core-toanyslice.md
│ │ ├── core-topairs.md
│ │ ├── core-toptr.md
│ │ ├── core-tosliceptr.md
│ │ ├── core-trim.md
│ │ ├── core-trimleft.md
│ │ ├── core-trimprefix.md
│ │ ├── core-trimright.md
│ │ ├── core-trimsuffix.md
│ │ ├── core-trycatch.md
│ │ ├── core-trycatchwitherrorvalue.md
│ │ ├── core-tryorx.md
│ │ ├── core-trywitherrorvalue.md
│ │ ├── core-tryx.md
│ │ ├── core-tuplex.md
│ │ ├── core-union.md
│ │ ├── core-uniq.md
│ │ ├── core-uniqby.md
│ │ ├── core-uniqbyerr.md
│ │ ├── core-uniqkeys.md
│ │ ├── core-uniqmap.md
│ │ ├── core-uniqvalues.md
│ │ ├── core-unpackx.md
│ │ ├── core-unzipbyerrx.md
│ │ ├── core-unzipbyx.md
│ │ ├── core-unzipx.md
│ │ ├── core-validate.md
│ │ ├── core-valueor.md
│ │ ├── core-values.md
│ │ ├── core-waitfor.md
│ │ ├── core-window.md
│ │ ├── core-without.md
│ │ ├── core-withoutby.md
│ │ ├── core-withoutbyerr.md
│ │ ├── core-withoutempty.md
│ │ ├── core-withoutnth.md
│ │ ├── core-words.md
│ │ ├── core-zipbyerrx.md
│ │ ├── core-zipbyx.md
│ │ ├── core-zipx.md
│ │ ├── it-assign.md
│ │ ├── it-associate.md
│ │ ├── it-buffer.md
│ │ ├── it-channelseq.md
│ │ ├── it-channeltoseq.md
│ │ ├── it-chunk.md
│ │ ├── it-chunkentries.md
│ │ ├── it-chunkstring.md
│ │ ├── it-coalesceseq.md
│ │ ├── it-coalesceseqorempty.md
│ │ ├── it-compact.md
│ │ ├── it-concat.md
│ │ ├── it-contains.md
│ │ ├── it-containsby.md
│ │ ├── it-count.md
│ │ ├── it-countby.md
│ │ ├── it-countvalues.md
│ │ ├── it-countvaluesby.md
│ │ ├── it-crossjoinbyx.md
│ │ ├── it-crossjoinx.md
│ │ ├── it-cutprefix.md
│ │ ├── it-cutsuffix.md
│ │ ├── it-drain.md
│ │ ├── it-drop.md
│ │ ├── it-dropbyindex.md
│ │ ├── it-droplast.md
│ │ ├── it-droplastwhile.md
│ │ ├── it-dropslice.md
│ │ ├── it-dropwhile.md
│ │ ├── it-earliest.md
│ │ ├── it-earliestby.md
│ │ ├── it-elementsmatch.md
│ │ ├── it-elementsmatchby.md
│ │ ├── it-empty.md
│ │ ├── it-entries.md
│ │ ├── it-every.md
│ │ ├── it-everyby.md
│ │ ├── it-fill.md
│ │ ├── it-filter.md
│ │ ├── it-filterkeys.md
│ │ ├── it-filtermap.md
│ │ ├── it-filtermaptoseq.md
│ │ ├── it-filtervalues.md
│ │ ├── it-find.md
│ │ ├── it-findduplicates.md
│ │ ├── it-findduplicatesby.md
│ │ ├── it-findindexof.md
│ │ ├── it-findlastindexof.md
│ │ ├── it-findorelse.md
│ │ ├── it-finduniques.md
│ │ ├── it-finduniquesby.md
│ │ ├── it-first.md
│ │ ├── it-firstor.md
│ │ ├── it-firstorempty.md
│ │ ├── it-flatmap.md
│ │ ├── it-flatten.md
│ │ ├── it-foreach.md
│ │ ├── it-foreachwhile.md
│ │ ├── it-fromanyseq.md
│ │ ├── it-fromentries.md
│ │ ├── it-frompairs.md
│ │ ├── it-fromseqptr.md
│ │ ├── it-fromseqptror.md
│ │ ├── it-groupby.md
│ │ ├── it-hasprefix.md
│ │ ├── it-hassuffix.md
│ │ ├── it-indexof.md
│ │ ├── it-interleave.md
│ │ ├── it-intersect.md
│ │ ├── it-intersectby.md
│ │ ├── it-invert.md
│ │ ├── it-isempty.md
│ │ ├── it-isnotempty.md
│ │ ├── it-issorted.md
│ │ ├── it-issortedby.md
│ │ ├── it-keyby.md
│ │ ├── it-keyify.md
│ │ ├── it-keys.md
│ │ ├── it-last.md
│ │ ├── it-lastindexof.md
│ │ ├── it-lastor.md
│ │ ├── it-lastorempty.md
│ │ ├── it-latest.md
│ │ ├── it-latestby.md
│ │ ├── it-length.md
│ │ ├── it-map.md
│ │ ├── it-maptoseq.md
│ │ ├── it-max.md
│ │ ├── it-maxby.md
│ │ ├── it-maxindex.md
│ │ ├── it-maxindexby.md
│ │ ├── it-mean.md
│ │ ├── it-meanby.md
│ │ ├── it-min.md
│ │ ├── it-minby.md
│ │ ├── it-minindex.md
│ │ ├── it-minindexby.md
│ │ ├── it-mode.md
│ │ ├── it-none.md
│ │ ├── it-noneby.md
│ │ ├── it-nth.md
│ │ ├── it-nthor.md
│ │ ├── it-nthorempty.md
│ │ ├── it-partitionby.md
│ │ ├── it-product.md
│ │ ├── it-productby.md
│ │ ├── it-range.md
│ │ ├── it-rangefrom.md
│ │ ├── it-rangewithsteps.md
│ │ ├── it-reduce.md
│ │ ├── it-reducelast.md
│ │ ├── it-reject.md
│ │ ├── it-rejectmap.md
│ │ ├── it-repeat.md
│ │ ├── it-repeatby.md
│ │ ├── it-replace.md
│ │ ├── it-replaceall.md
│ │ ├── it-reverse.md
│ │ ├── it-sample.md
│ │ ├── it-sampleby.md
│ │ ├── it-samples.md
│ │ ├── it-samplesby.md
│ │ ├── it-seqtochannel.md
│ │ ├── it-sequencestate.md
│ │ ├── it-shuffle.md
│ │ ├── it-slice.md
│ │ ├── it-sliding.md
│ │ ├── it-some.md
│ │ ├── it-someby.md
│ │ ├── it-splice.md
│ │ ├── it-subset.md
│ │ ├── it-sum.md
│ │ ├── it-sumby.md
│ │ ├── it-take.md
│ │ ├── it-takefilter.md
│ │ ├── it-takewhile.md
│ │ ├── it-times.md
│ │ ├── it-toanyseq.md
│ │ ├── it-toseqptr.md
│ │ ├── it-trim.md
│ │ ├── it-trimfirst.md
│ │ ├── it-trimlast.md
│ │ ├── it-trimprefix.md
│ │ ├── it-trimsuffix.md
│ │ ├── it-union.md
│ │ ├── it-uniq.md
│ │ ├── it-uniqby.md
│ │ ├── it-uniqkeys.md
│ │ ├── it-uniqvalues.md
│ │ ├── it-values.md
│ │ ├── it-window.md
│ │ ├── it-without.md
│ │ ├── it-withoutby.md
│ │ ├── it-withoutnth.md
│ │ ├── it-zipbyx.md
│ │ ├── it-zipx.md
│ │ ├── mutable-fill.md
│ │ ├── mutable-filter.md
│ │ ├── mutable-map.md
│ │ ├── mutable-reverse.md
│ │ ├── mutable-shuffle.md
│ │ ├── parallel-foreach.md
│ │ ├── parallel-groupby.md
│ │ ├── parallel-map.md
│ │ ├── parallel-partitionby.md
│ │ ├── parallel-times.md
│ │ ├── simd-clamp.md
│ │ ├── simd-contains.md
│ │ ├── simd-max.md
│ │ ├── simd-mean.md
│ │ ├── simd-meanby.md
│ │ ├── simd-min.md
│ │ ├── simd-sum.md
│ │ └── simd-sumby.md
│ ├── docs/
│ │ ├── _category_.json
│ │ ├── about.md
│ │ ├── contributing.md
│ │ ├── core/
│ │ │ ├── _category_.json
│ │ │ ├── channel.md
│ │ │ ├── concurrency.md
│ │ │ ├── condition.md
│ │ │ ├── error-handling.md
│ │ │ ├── find.md
│ │ │ ├── function.md
│ │ │ ├── intersect.md
│ │ │ ├── map.md
│ │ │ ├── math.md
│ │ │ ├── retry.md
│ │ │ ├── slice.md
│ │ │ ├── string.md
│ │ │ ├── time.md
│ │ │ ├── tuple.md
│ │ │ └── type.md
│ │ ├── experimental/
│ │ │ ├── _category_.json
│ │ │ └── simd.md
│ │ ├── getting-started.md
│ │ ├── glossary.md
│ │ ├── iter/
│ │ │ ├── _category_.json
│ │ │ ├── channel.md
│ │ │ ├── find.md
│ │ │ ├── intersect.md
│ │ │ ├── map.md
│ │ │ ├── sequence.md
│ │ │ ├── slice.md
│ │ │ ├── string.md
│ │ │ ├── tuple.md
│ │ │ └── type.md
│ │ ├── mutable/
│ │ │ ├── _category_.json
│ │ │ └── slice.md
│ │ └── parallel/
│ │ ├── _category_.json
│ │ └── slice.md
│ ├── docusaurus.config.ts
│ ├── package.json
│ ├── plugins/
│ │ └── helpers-pages/
│ │ ├── components/
│ │ │ ├── HelperCard.tsx
│ │ │ ├── HelperList.tsx
│ │ │ ├── HelperTOC.tsx
│ │ │ ├── helper-components.css
│ │ │ └── highlightPrototypeGenerics.ts
│ │ └── index.ts
│ ├── scripts/
│ │ ├── check-cross-references.js
│ │ ├── check-duplicates-in-category.js
│ │ ├── check-filename-matches-frontmatter.js
│ │ ├── check-function-signatures.js
│ │ ├── check-helpers-visible-in-pages.js
│ │ ├── check-similar-exists.js
│ │ ├── check-similar-keys-exist-in-directory.js
│ │ └── utils.js
│ ├── sidebars.ts
│ ├── src/
│ │ ├── css/
│ │ │ └── custom.css
│ │ ├── pages/
│ │ │ ├── community.module.css
│ │ │ ├── community.tsx
│ │ │ ├── index.module.css
│ │ │ └── index.tsx
│ │ ├── prism-include-languages.js
│ │ └── theme/
│ │ ├── DocSidebar/
│ │ │ └── index.tsx
│ │ ├── NotFound/
│ │ │ └── index.tsx
│ │ └── prism-include-languages.js
│ ├── static/
│ │ ├── .nojekyll
│ │ ├── img/
│ │ │ ├── README.md
│ │ │ ├── logo-full.xcf
│ │ │ └── social-preview.xcf
│ │ └── llms.txt
│ └── tsconfig.json
├── errors.go
├── errors_test.go
├── exp/
│ └── simd/
│ ├── BENCHMARK.md
│ ├── README.md
│ ├── cpu_amd64.go
│ ├── cpu_amd64_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── intersect_avx512.go
│ ├── intersect_avx512_test.go
│ ├── intersect_bench_test.go
│ ├── math.go
│ ├── math_avx.go
│ ├── math_avx2.go
│ ├── math_avx2_test.go
│ ├── math_avx512.go
│ ├── math_avx512_test.go
│ ├── math_avx_test.go
│ ├── math_bench_test.go
│ ├── simd.go
│ ├── simd_test.go
│ └── unsafe.go
├── find.go
├── find_test.go
├── func.go
├── func_test.go
├── go.mod
├── go.sum
├── internal/
│ ├── constraints/
│ │ ├── README.md
│ │ ├── constraints.go
│ │ ├── ordered_go118.go
│ │ └── ordered_go121.go
│ ├── xrand/
│ │ ├── ordered_go118.go
│ │ └── ordered_go122.go
│ └── xtime/
│ ├── README.md
│ ├── fake.go
│ ├── noCopy.go
│ ├── real.go
│ └── time.go
├── intersect.go
├── intersect_test.go
├── it/
│ ├── channel.go
│ ├── channel_test.go
│ ├── find.go
│ ├── find_example_test.go
│ ├── find_test.go
│ ├── intersect.go
│ ├── intersect_example_test.go
│ ├── intersect_test.go
│ ├── lo_test.go
│ ├── map.go
│ ├── map_example_test.go
│ ├── map_test.go
│ ├── math.go
│ ├── math_example_test.go
│ ├── math_test.go
│ ├── seq.go
│ ├── seq_example_test.go
│ ├── seq_test.go
│ ├── string.go
│ ├── string_example_test.go
│ ├── string_test.go
│ ├── tuples.go
│ ├── tuples_example_test.go
│ ├── tuples_test.go
│ ├── type_manipulation.go
│ └── type_manipulation_test.go
├── lo_example_test.go
├── lo_test.go
├── map.go
├── map_test.go
├── math.go
├── math_test.go
├── mutable/
│ ├── slice.go
│ ├── slice_example_test.go
│ └── slice_test.go
├── parallel/
│ ├── slice.go
│ └── slice_test.go
├── retry.go
├── retry_example_test.go
├── retry_test.go
├── slice.go
├── slice_test.go
├── string.go
├── string_test.go
├── time.go
├── time_test.go
├── tuples.go
├── tuples_test.go
├── type_manipulation.go
├── type_manipulation_test.go
└── types.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: [samber]
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/config.yml
================================================
template_chooser:
enabled: true
default: "other.md"
choices:
- name: "New Helper"
file: "new_helper.md"
- name: "Other Changes"
file: "other.md"
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/new_helper.md
================================================
## Describe your changes
...
## Checklist before requesting a review
- [ ] 👓 I have performed a self-review of my code
- [ ] 👶 This helper does not already exist
- [ ] 🧪 This helper is tested
- [ ] 🏎️ My code limits memory allocation and is fast
- [ ] 🧞♂️ This helper is immutable and my tests prove it
- [ ] ✍️ I implemented the parallel, iterator and mutable variants
- [ ] 🔬 An example has been added to lo_example_test.go
- [ ] ⛹️ An example has been created on https://go.dev/play and added in comments
- [ ] 📖 My helper has been added to documentation
- [ ] in README.md
- [ ] in docs/data/*.md
- [ ] in docs/static/llms.txt
## Conventions
- Returning `(ok bool)` is often better than `(err error)`
- `panic(...)` must be limited
- Helpers should receive variadic arguments when relevent
- Add variants of your helper when relevant:
- Variable number of arguments: `lo.Must0`, `lo.Must1`, `lo.Must2`, ...
- Predicate with index: `lo.SliceToMap` vs `lo.SliceToMapI`
- With lazy callback: `lo.Ternary` vs `lo.TernaryF`
- ...
================================================
FILE: .github/PULL_REQUEST_TEMPLATE/other.md
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
- package-ecosystem: gomod
directory: /
schedule:
interval: weekly
- package-ecosystem: npm
directory: /docs
schedule:
interval: monthly
ignore:
- dependency-name: '*'
update-types: ["version-update:semver-patch"]
groups:
docusaurus:
patterns:
- "@docusaurus/*"
react:
patterns:
- "react"
- "react-dom"
================================================
FILE: .github/workflows/doc.yml
================================================
name: Documentation
on:
pull_request:
paths:
- 'docs/**'
push:
paths:
- 'docs/**'
jobs:
build-doc:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
- name: Install dependencies
run: npm i
- name: Build Docusaurus project
run: npm run build
# validate-doc:
# runs-on: ubuntu-latest
# defaults:
# run:
# working-directory: ./docs
# steps:
# - name: Checkout repository
# uses: actions/checkout@v6
# - name: Setup Node.js
# uses: actions/setup-node@v6
# - name: Install dependencies
# run: npm i
# - name: Run docs helpers checks
# run: |
# node docs/scripts/check-duplicates-in-category.js
# node docs/scripts/check-similar-exists.js
# node docs/scripts/check-filename-matches-frontmatter.js
# node docs/scripts/check-similar-keys-exist-in-directory.js
# node docs/scripts/check-cross-references.js
# - name: Sync function signatures
# run: npm run sync:function-signatures
# - name: Commit and push changes
# uses: stefanzweifel/git-auto-commit-action@v5
# with:
# commit_message: 'docs: sync function signatures from source'
# commit_user_name: github-actions[bot]
# commit_user_email: github-actions[bot]@users.noreply.github.com
# file_pattern: docs/data/*.md
================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint
on:
push:
branches:
- master
pull_request:
schedule:
- cron: '0 3 * * 1'
jobs:
analyze:
name: lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
permissions:
# required for codeql analysis
security-events: write
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
args: --timeout 120s --max-same-issues 50
- name: Bearer
uses: bearer/bearer-action@v2
with:
skip-path: 'docs/'
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
# - name: Install mdsf
# uses: hougesen/mdsf@main
# - name: Run mdsf
# run: mdsf --debug --log-level warn docs/
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
workflow_dispatch:
inputs:
semver:
type: string
description: 'Semver (eg: v1.2.3)'
required: true
jobs:
release:
if: github.triggering_actor == 'samber'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Test
run: make test
# remove tests to clean dependencies
- name: Remove xxx_test.go files
run: rm -rf $(find . -type f -name "*_test.go" ! -name "*_example_test.go") docs/ benchmark/
# cleanup test dependencies
- name: Cleanup dependencies
run: go mod tidy
- name: List files
run: tree -Cfi
- name: Write new go.mod into logs
run: cat go.mod
- name: Write new go.sum into logs
run: cat go.sum
- name: Create tag
run: |
git config --global user.name '${{ github.triggering_actor }}'
git config --global user.email "${{ github.triggering_actor}}@users.noreply.github.com"
git add .
git commit --allow-empty -m 'bump ${{ inputs.semver }}'
git tag ${{ inputs.semver }}
git push origin ${{ inputs.semver }}
- name: Release
uses: softprops/action-gh-release@v2
with:
name: ${{ inputs.semver }}
tag_name: ${{ inputs.semver }}
================================================
FILE: .github/workflows/test.simd.yml
================================================
name: Tests (SIMD)
on:
push:
branches:
- master
paths:
- 'exp/simd/**'
pull_request:
paths:
- 'exp/simd/**'
env:
GOEXPERIMENT: simd
jobs:
test-simd:
# GitHub hosted runners run on several architectures.
# Using Ubicloud ensures we run on AVX512.
runs-on: ubicloud-standard-2
strategy:
fail-fast: false
matrix:
go:
- "stable"
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
- name: Test
run: make test
- name: Benchmark
run: |
cd exp/simd/
GOEXPERIMENT=simd go test -run=^Benchmark -benchmem -bench ./...
================================================
FILE: .github/workflows/test.yml
================================================
name: Tests
on:
push:
branches:
- master
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go:
- "1.18"
- "1.19"
- "1.20"
- "1.21"
- "1.22"
- "1.23"
- "1.24"
- "1.25"
- "1.26"
- "stable"
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
- name: Build
run: make build
- name: Test
run: make test
- name: Test coverage
run: make coverage
if: matrix.go == 'stable'
- name: Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unittests
verbose: true
if: matrix.go == 'stable'
================================================
FILE: .gitignore
================================================
# Created by https://www.toptal.com/developers/gitignore/api/go
# Edit at https://www.toptal.com/developers/gitignore?templates=go
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### Go Patch ###
/vendor/
/Godeps/
# End of https://www.toptal.com/developers/gitignore/api/go
cover.out
cover.html
.vscode
.idea/
================================================
FILE: .golangci.yml
================================================
version: "2"
run:
concurrency: 4
# also lint _test.go files
tests: true
timeout: 5m
linters:
enable:
- govet
- staticcheck
- unused
- errcheck
- gocritic
- gocyclo
- revive
- ineffassign
- unconvert
- goconst
# - depguard
- prealloc
# - dupl
- misspell
- bodyclose
- sqlclosecheck
- nilerr
- nestif
- forcetypeassert
- exhaustive
- funlen
# - wsl_v5
- testifylint
- whitespace
- perfsprint
- nolintlint
- godot
- thelper
- tparallel
- paralleltest
- predeclared
- modernize
# disable noisy/controversial ones which you might enable later
disable:
- lll # line length — handled by gofmt/gofumpt
settings:
dupl:
threshold: 20 # lower => stricter (tokens)
errcheck:
check-type-assertions: true
funlen:
lines: 120
statements: 80
goconst:
min-len: 2
min-occurrences: 3
gocyclo:
min-complexity: 15 # strict; lower => stricter
wsl_v5:
allow-first-in-block: true
allow-whole-block: false
branch-max-lines: 2
testifylint:
disable:
- require-error
- float-compare
exclusions:
generated: lax
paths:
- examples$
rules:
- linters:
- revive
text: "^unused-parameter:"
- linters:
- revive
text: "^package-comments:"
- linters:
- errcheck
text: "Error return value of `.*\\.Body\\.Close` is not checked"
# linters disabled in tests
- linters:
- dupl
- goconst
- funlen
path: "_test\\.go$"
issues:
max-issues-per-linter: 0 # 0 = unlimited (we want ALL issues)
max-same-issues: 100
formatters:
enable:
- gofmt
- gofumpt
settings:
gofumpt:
extra-rules: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
================================================
FILE: Dockerfile
================================================
FROM golang:1.23.1
WORKDIR /go/src/github.com/samber/lo
COPY Makefile go.* ./
RUN make tools
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022-2025 Samuel Berthe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
# Only build/test/lint exp/simd when Go version is >= 1.26 (requires goexperiment.simd)
GO_VERSION := $(shell go version 2>/dev/null | sed -n 's/.*go\([0-9]*\)\.\([0-9]*\).*/\1.\2/p')
GO_SIMD_SUPPORT := $(shell ver="$(GO_VERSION)"; [ -n "$$ver" ] && [ "$$(printf '%s\n1.26\n' "$$ver" | sort -V | tail -1)" = "$$ver" ] && echo yes)
build:
go build -v ./...
@if [ -n "$(GO_SIMD_SUPPORT)" ]; then cd ./exp/simd && GOEXPERIMENT=simd go build -v ./; fi
test:
go test -race ./...
@if [ -n "$(GO_SIMD_SUPPORT)" ]; then cd ./exp/simd && GOEXPERIMENT=simd go test -race ./; fi
watch-test:
reflex -t 50ms -s -- sh -c 'gotest -race ./...'
bench:
go test -v -run=^Benchmark -benchmem -count 3 -bench ./...
watch-bench:
reflex -t 50ms -s -- sh -c 'go test -v -run=^Benchmark -benchmem -count 3 -bench ./...'
coverage:
go test -v -coverprofile=cover.out -covermode=atomic ./...
go tool cover -html=cover.out -o cover.html
tools:
go install github.com/cespare/reflex@latest
go install github.com/rakyll/gotest@latest
go install github.com/psampaz/go-mod-outdated@latest
go install github.com/jondot/goweight@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go get -t -u golang.org/x/tools/cmd/cover
go install github.com/sonatype-nexus-community/nancy@latest
go install golang.org/x/perf/cmd/benchstat@latest
go install github.com/cespare/prettybench@latest
go mod tidy
# brew install hougesen/tap/mdsf
lint:
golangci-lint run --timeout 60s --max-same-issues 50 ./...
@if [ -n "$(GO_SIMD_SUPPORT)" ]; then cd ./exp/simd && golangci-lint run --timeout 60s --max-same-issues 50 ./; fi
# mdsf verify --debug --log-level warn docs/
lint-fix:
golangci-lint run --timeout 60s --max-same-issues 50 --fix ./...
@if [ -n "$(GO_SIMD_SUPPORT)" ]; then cd ./exp/simd && golangci-lint run --timeout 60s --max-same-issues 50 --fix ./; fi
# mdsf format --debug --log-level warn docs/
audit:
go list -json -m all | nancy sleuth
outdated:
go list -u -m -json all | go-mod-outdated -update -direct
weight:
goweight
doc:
cd docs && npm install && npm start
================================================
FILE: README.md
================================================
# lo - Iterate over slices, maps, channels...
[](https://github.com/samber/lo/releases)

[](https://pkg.go.dev/github.com/samber/lo)

[](https://goreportcard.com/report/github.com/samber/lo)
[](https://codecov.io/gh/samber/lo)
[](https://github.com/samber/lo/graphs/contributors)
[](./LICENSE)
✨ **`samber/lo` is a Lodash-style Go library based on Go 1.18+ Generics.**
A utility library based on Go 1.18+ generics that makes it easier to work with slices, maps, strings, channels, and functions. It provides dozens of handy methods to simplify common coding tasks and improve code readability. It may look like [Lodash](https://github.com/lodash/lodash) in some aspects.
5 to 10 helpers may overlap with those from the Go standard library, in packages `slices` and `maps`. I feel this library is legitimate and offers many more valuable abstractions.
**See also:**
- [samber/ro](https://github.com/samber/ro): Reactive Programming for Go: declarative and composable API for event-driven applications
- [samber/do](https://github.com/samber/do): A dependency injection toolkit based on Go 1.18+ Generics
- [samber/mo](https://github.com/samber/mo): Monads based on Go 1.18+ Generics (Option, Result, Either...)
What makes it different from **samber/ro**?
- lo: synchronous helpers across finite sequences (maps, slices...)
- ro: processing of infinite data streams for event-driven scenarios
----
----
**Why this name?**
I wanted a **short name**, similar to "Lodash", and no Go package uses this name.

## 🚀 Install
```sh
go get github.com/samber/lo@v1
```
This library is v1 and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0, except for experimental packages under `exp/`.
This library has no dependencies outside the Go standard library.
## 💡 Usage
You can import `lo` using:
```go
import (
"github.com/samber/lo"
lop "github.com/samber/lo/parallel"
lom "github.com/samber/lo/mutable"
loi "github.com/samber/lo/it"
)
```
Then use one of the helpers below:
```go
names := lo.Uniq([]string{"Samuel", "John", "Samuel"})
// []string{"Samuel", "John"}
```
### Tips for lazy developers
I cannot recommend it, but in case you are too lazy for repeating `lo.` everywhere, you can import the entire library into the namespace.
```go
import (
. "github.com/samber/lo"
)
```
I take no responsibility for this junk. 😁 💩
## 🤠 Spec
GoDoc: [godoc.org/github.com/samber/lo](https://godoc.org/github.com/samber/lo)
Documentation: [lo.samber.dev](https://lo.samber.dev/docs/about)
Supported helpers for slices:
- [Filter](#filter)
- [Map](#map)
- [UniqMap](#uniqmap)
- [FilterMap](#filtermap)
- [FlatMap](#flatmap)
- [Reduce](#reduce)
- [ReduceRight](#reduceright)
- [ForEach](#foreach)
- [ForEachWhile](#foreachwhile)
- [Times](#times)
- [Uniq](#uniq)
- [UniqBy](#uniqby)
- [GroupBy](#groupby)
- [GroupByMap](#groupbymap)
- [Chunk](#chunk)
- [Window](#window)
- [Sliding](#sliding)
- [PartitionBy](#partitionby)
- [Flatten](#flatten)
- [Concat](#concat)
- [Interleave](#interleave)
- [Shuffle](#shuffle)
- [Reverse](#reverse)
- [Fill](#fill)
- [Repeat](#repeat)
- [RepeatBy](#repeatby)
- [KeyBy](#keyby)
- [SliceToMap / Associate](#slicetomap-alias-associate)
- [FilterSliceToMap](#filterslicetomap)
- [Keyify](#keyify)
- [Take](#take)
- [TakeWhile](#takewhile)
- [TakeFilter](#takefilter)
- [Drop](#drop)
- [DropRight](#dropright)
- [DropWhile](#dropwhile)
- [DropRightWhile](#droprightwhile)
- [DropByIndex](#DropByIndex)
- [Reject](#reject)
- [RejectMap](#rejectmap)
- [FilterReject](#filterreject)
- [Count](#count)
- [CountBy](#countby)
- [CountValues](#countvalues)
- [CountValuesBy](#countvaluesby)
- [Subset](#subset)
- [Slice](#slice)
- [Replace](#replace)
- [ReplaceAll](#replaceall)
- [Clone](#clone)
- [Compact](#compact)
- [IsSorted](#issorted)
- [IsSortedBy](#issortedby)
- [Splice](#Splice)
- [Cut](#Cut)
- [CutPrefix](#CutPrefix)
- [CutSuffix](#CutSuffix)
- [Trim](#Trim)
- [TrimLeft](#TrimLeft)
- [TrimPrefix](#TrimPrefix)
- [TrimRight](#TrimRight)
- [TrimSuffix](#TrimSuffix)
Supported helpers for maps:
- [Keys](#keys)
- [UniqKeys](#uniqkeys)
- [HasKey](#haskey)
- [ValueOr](#valueor)
- [Values](#values)
- [UniqValues](#uniqvalues)
- [PickBy](#pickby)
- [PickByKeys](#pickbykeys)
- [PickByValues](#pickbyvalues)
- [OmitBy](#omitby)
- [OmitByKeys](#omitbykeys)
- [OmitByValues](#omitbyvalues)
- [Entries / ToPairs](#entries-alias-topairs)
- [FromEntries / FromPairs](#fromentries-alias-frompairs)
- [Invert](#invert)
- [Assign (merge of maps)](#assign)
- [ChunkEntries](#chunkentries)
- [MapKeys](#mapkeys)
- [MapValues](#mapvalues)
- [MapEntries](#mapentries)
- [MapToSlice](#maptoslice)
- [FilterMapToSlice](#FilterMapToSlice)
- [FilterKeys](#FilterKeys)
- [FilterValues](#FilterValues)
Supported math helpers:
- [Range / RangeFrom / RangeWithSteps](#range--rangefrom--rangewithsteps)
- [Clamp](#clamp)
- [Sum](#sum)
- [SumBy](#sumby)
- [Product](#product)
- [ProductBy](#productby)
- [Mean](#mean)
- [MeanBy](#meanby)
- [Mode](#mode)
Supported helpers for strings:
- [RandomString](#randomstring)
- [Substring](#substring)
- [ChunkString](#chunkstring)
- [RuneLength](#runelength)
- [PascalCase](#pascalcase)
- [CamelCase](#camelcase)
- [KebabCase](#kebabcase)
- [SnakeCase](#snakecase)
- [Words](#words)
- [Capitalize](#capitalize)
- [Ellipsis](#ellipsis)
Supported helpers for tuples:
- [T2 -> T9](#t2---t9)
- [Unpack2 -> Unpack9](#unpack2---unpack9)
- [Zip2 -> Zip9](#zip2---zip9)
- [ZipBy2 -> ZipBy9](#zipby2---zipby9)
- [Unzip2 -> Unzip9](#unzip2---unzip9)
- [UnzipBy2 -> UnzipBy9](#unzipby2---unzipby9)
- [CrossJoin2 -> CrossJoin2](#crossjoin2---crossjoin9)
- [CrossJoinBy2 -> CrossJoinBy2](#crossjoinby2---crossjoinby9)
Supported helpers for time and duration:
- [Duration](#duration)
- [Duration0 -> Duration10](#duration0---duration10)
Supported helpers for channels:
- [ChannelDispatcher](#channeldispatcher)
- [SliceToChannel](#slicetochannel)
- [ChannelToSlice](#channeltoslice)
- [Generator](#generator)
- [Buffer](#buffer)
- [BufferWithContext](#bufferwithcontext)
- [BufferWithTimeout](#bufferwithtimeout)
- [FanIn](#fanin)
- [FanOut](#fanout)
Supported intersection helpers:
- [Contains](#contains)
- [ContainsBy](#containsby)
- [Every](#every)
- [EveryBy](#everyby)
- [Some](#some)
- [SomeBy](#someby)
- [None](#none)
- [NoneBy](#noneby)
- [Intersect](#intersect)
- [IntersectBy](#intersectby)
- [Difference](#difference)
- [Union](#union)
- [Without](#without)
- [WithoutBy](#withoutby)
- [WithoutEmpty](#withoutempty)
- [WithoutNth](#withoutnth)
- [ElementsMatch](#ElementsMatch)
- [ElementsMatchBy](#ElementsMatchBy)
Supported search helpers:
- [IndexOf](#indexof)
- [LastIndexOf](#lastindexof)
- [HasPrefix](#hasprefix)
- [HasSuffix](#hassuffix)
- [Find](#find)
- [FindIndexOf](#findindexof)
- [FindLastIndexOf](#findlastindexof)
- [FindOrElse](#findorelse)
- [FindKey](#findkey)
- [FindKeyBy](#findkeyby)
- [FindUniques](#finduniques)
- [FindUniquesBy](#finduniquesby)
- [FindDuplicates](#findduplicates)
- [FindDuplicatesBy](#findduplicatesby)
- [Min](#min)
- [MinIndex](#minindex)
- [MinBy](#minby)
- [MinIndexBy](#minindexby)
- [Earliest](#earliest)
- [EarliestBy](#earliestby)
- [Max](#max)
- [MaxIndex](#maxindex)
- [MaxBy](#maxby)
- [MaxIndexBy](#maxindexby)
- [Latest](#latest)
- [LatestBy](#latestby)
- [First](#first)
- [FirstOrEmpty](#FirstOrEmpty)
- [FirstOr](#FirstOr)
- [Last](#last)
- [LastOrEmpty](#LastOrEmpty)
- [LastOr](#LastOr)
- [Nth](#nth)
- [NthOr](#nthor)
- [NthOrEmpty](#nthorempty)
- [Sample](#sample)
- [SampleBy](#sampleby)
- [Samples](#samples)
- [SamplesBy](#samplesby)
Conditional helpers:
- [Ternary](#ternary)
- [TernaryF](#ternaryf)
- [If / ElseIf / Else](#if--elseif--else)
- [Switch / Case / Default](#switch--case--default)
Type manipulation helpers:
- [IsNil](#isnil)
- [IsNotNil](#isnotnil)
- [ToPtr](#toptr)
- [Nil](#nil)
- [EmptyableToPtr](#emptyabletoptr)
- [FromPtr](#fromptr)
- [FromPtrOr](#fromptror)
- [ToSlicePtr](#tosliceptr)
- [FromSlicePtr](#fromsliceptr)
- [FromSlicePtrOr](#fromsliceptror)
- [ToAnySlice](#toanyslice)
- [FromAnySlice](#fromanyslice)
- [Empty](#empty)
- [IsEmpty](#isempty)
- [IsNotEmpty](#isnotempty)
- [Coalesce](#coalesce)
- [CoalesceOrEmpty](#coalesceorempty)
- [CoalesceSlice](#coalesceslice)
- [CoalesceSliceOrEmpty](#coalescesliceorempty)
- [CoalesceMap](#coalescemap)
- [CoalesceMapOrEmpty](#coalescemaporempty)
Function helpers:
- [Partial](#partial)
- [Partial2 -> Partial5](#partial2---partial5)
Concurrency helpers:
- [Attempt](#attempt)
- [AttemptWhile](#attemptwhile)
- [AttemptWithDelay](#attemptwithdelay)
- [AttemptWhileWithDelay](#attemptwhilewithdelay)
- [Debounce](#debounce)
- [DebounceBy](#debounceby)
- [Throttle](#throttle)
- [ThrottleWithCount](#throttle)
- [ThrottleBy](#throttle)
- [ThrottleByWithCount](#throttle)
- [Synchronize](#synchronize)
- [Async](#async)
- [Async{0->6}](#async0-6)
- [Transaction](#transaction)
- [WaitFor](#waitfor)
- [WaitForWithContext](#waitforwithcontext)
Error handling:
- [Validate](#validate)
- [Must](#must)
- [Try](#try)
- [Try1 -> Try6](#try0-6)
- [TryOr](#tryor)
- [TryOr1 -> TryOr6](#tryor0-6)
- [TryCatch](#trycatch)
- [TryWithErrorValue](#trywitherrorvalue)
- [TryCatchWithErrorValue](#trycatchwitherrorvalue)
- [ErrorsAs](#errorsas)
- [Assert](#assert)
- [Assertf](#assertf)
Constraints:
- Clonable
### Filter
Iterates over a collection and returns a slice of all the elements the predicate function returns `true` for.
```go
even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool {
return x%2 == 0
})
// []int{2, 4}
```
```go
// Use FilterErr when the predicate can return an error
even, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, _ int) (bool, error) {
if x == 3 {
return false, fmt.Errorf("number 3 is not allowed")
}
return x%2 == 0, nil
})
// []int(nil), error("number 3 is not allowed")
```
[[play](https://go.dev/play/p/Apjg3WeSi7K)]
Mutable: like `lo.Filter()`, but the slice is updated in place.
```go
import lom "github.com/samber/lo/mutable"
list := []int{1, 2, 3, 4}
newList := lom.Filter(list, func(x int) bool {
return x%2 == 0
})
list
// []int{2, 4, 3, 4}
newList
// []int{2, 4}
```
### Map
Manipulates a slice of one type and transforms it into a slice of another type:
```go
import "github.com/samber/lo"
lo.Map([]int64{1, 2, 3, 4}, func(x int64, index int) string {
return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}
```
```go
// Use MapErr when the transform function can return an error
result, err := lo.MapErr([]int{1, 2, 3, 4}, func(x int, _ int) (string, error) {
if x == 3 {
return "", fmt.Errorf("number 3 is not allowed")
}
return strconv.Itoa(x), nil
})
// []string(nil), error("number 3 is not allowed")
```
[[play](https://go.dev/play/p/OkPcYAhBo0D)]
Parallel processing: like `lo.Map()`, but the transform function is called in a goroutine. Results are returned in the same order.
```go
import lop "github.com/samber/lo/parallel"
lop.Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string {
return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}
```
[[play](https://go.dev/play/p/sCJaB3quRMC)]
Mutable: like `lo.Map()`, but the slice is updated in place.
```go
import lom "github.com/samber/lo/mutable"
list := []int{1, 2, 3, 4}
lom.Map(list, func(x int) int {
return x*2
})
// []int{2, 4, 6, 8}
```
[[play](https://go.dev/play/p/0jY3Z0B7O_5)]
### UniqMap
Manipulates a slice and transforms it to a slice of another type with unique values.
```go
type User struct {
Name string
Age int
}
users := []User{{Name: "Alex", Age: 10}, {Name: "Alex", Age: 12}, {Name: "Bob", Age: 11}, {Name: "Alice", Age: 20}}
names := lo.UniqMap(users, func(u User, index int) string {
return u.Name
})
// []string{"Alex", "Bob", "Alice"}
```
[[play](https://go.dev/play/p/fygzLBhvUdB)]
### FilterMap
Returns a slice obtained after both filtering and mapping using the given callback function.
The callback function should return two values: the result of the mapping operation and whether the result element should be included or not.
```go
matching := lo.FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", true
}
return "", false
})
// []string{"xpu", "xpu"}
```
[[play](https://go.dev/play/p/-AuYXfy7opz)]
### FlatMap
Manipulates a slice and transforms and flattens it to a slice of another type. The transform function can either return a slice or a `nil`, and in the `nil` case no value is added to the final slice.
```go
lo.FlatMap([]int64{0, 1, 2}, func(x int64, _ int) []string {
return []string{
strconv.FormatInt(x, 10),
strconv.FormatInt(x, 10),
}
})
// []string{"0", "0", "1", "1", "2", "2"}
```
```go
// Use FlatMapErr when the transform function can return an error
result, err := lo.FlatMapErr([]int64{0, 1, 2, 3}, func(x int64, _ int) ([]string, error) {
if x == 2 {
return nil, fmt.Errorf("number 2 is not allowed")
}
return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}, nil
})
// []string(nil), error("number 2 is not allowed")
```
[[play](https://go.dev/play/p/YSoYmQTA8-U)]
### Reduce
Reduces a collection to a single value. The value is calculated by accumulating the result of running each element in the collection through an accumulator function. Each successive invocation is supplied with the return value returned by the previous call.
```go
sum := lo.Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int {
return agg + item
}, 0)
// 10
```
```go
// Use ReduceErr when the accumulator function can return an error
result, err := lo.ReduceErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
if item == 3 {
return 0, fmt.Errorf("number 3 is not allowed")
}
return agg + item, nil
}, 0)
// 0, error("number 3 is not allowed")
```
[[play](https://go.dev/play/p/R4UHXZNaaUG)]
### ReduceRight
Like `lo.Reduce` except that it iterates over elements of collection from right to left.
```go
result := lo.ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item []int, _ int) []int {
return append(agg, item...)
}, []int{})
// []int{4, 5, 2, 3, 0, 1}
```
```go
// Use ReduceRightErr when the accumulator function can return an error
result, err := lo.ReduceRightErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
if item == 2 {
return 0, fmt.Errorf("number 2 is not allowed")
}
return agg + item, nil
}, 0)
// 0, error("number 2 is not allowed")
```
[[play](https://go.dev/play/p/Fq3W70l7wXF)]
### ForEach
Iterates over elements of a collection and invokes the function over each element.
```go
import "github.com/samber/lo"
lo.ForEach([]string{"hello", "world"}, func(x string, _ int) {
println(x)
})
// prints "hello\nworld\n"
```
[[play](https://go.dev/play/p/oofyiUPRf8t)]
Parallel processing: like `lo.ForEach()`, but the callback is called as a goroutine.
```go
import lop "github.com/samber/lo/parallel"
lop.ForEach([]string{"hello", "world"}, func(x string, _ int) {
println(x)
})
// prints "hello\nworld\n" or "world\nhello\n"
```
### ForEachWhile
Iterates over collection elements and invokes iteratee for each element collection return value decide to continue or break, like do while().
```go
list := []int64{1, 2, -42, 4}
lo.ForEachWhile(list, func(x int64, _ int) bool {
if x < 0 {
return false
}
fmt.Println(x)
return true
})
// 1
// 2
```
[[play](https://go.dev/play/p/QnLGt35tnow)]
### Times
Times invokes the iteratee n times, returning a slice of the results of each invocation. The iteratee is invoked with index as argument.
```go
import "github.com/samber/lo"
lo.Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
// []string{"0", "1", "2"}
```
[[play](https://go.dev/play/p/vgQj3Glr6lT)]
Parallel processing: like `lo.Times()`, but callback is called in goroutine.
```go
import lop "github.com/samber/lo/parallel"
lop.Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
// []string{"0", "1", "2"}
```
### Uniq
Returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept. The order of result values is determined by the order they occur in the slice.
```go
uniqValues := lo.Uniq([]int{1, 2, 2, 1})
// []int{1, 2}
```
[[play](https://go.dev/play/p/DTzbeXZ6iEN)]
### UniqBy
Returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept. The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is invoked for each element in the slice to generate the criterion by which uniqueness is computed.
```go
uniqValues := lo.UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// []int{0, 1, 2}
```
```go
// Use UniqByErr when the iteratee function can return an error
result, err := lo.UniqByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
if i == 3 {
return 0, fmt.Errorf("number 3 is not allowed")
}
return i % 3, nil
})
// []int(nil), error("number 3 is not allowed")
```
[[play](https://go.dev/play/p/g42Z3QSb53u)]
### GroupBy
Returns an object composed of keys generated from the results of running each element of collection through iteratee.
```go
import lo "github.com/samber/lo"
groups := lo.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}}
```
```go
// Use GroupByErr when the iteratee function can return an error
result, err := lo.GroupByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
if i == 3 {
return 0, fmt.Errorf("number 3 is not allowed")
}
return i % 3, nil
})
// map[int][]int(nil), error("number 3 is not allowed")
```
[[play](https://go.dev/play/p/XnQBd_v6brd)]
Parallel processing: like `lo.GroupBy()`, but callback is called in goroutine.
```go
import lop "github.com/samber/lo/parallel"
lop.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}}
```
### GroupByMap
Returns an object composed of keys generated from the results of running each element of collection through iteratee.
```go
import lo "github.com/samber/lo"
groups := lo.GroupByMap([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, int) {
return i%3, i*2
})
// map[int][]int{0: []int{0, 6}, 1: []int{2, 8}, 2: []int{4, 10}}
```
```go
// Use GroupByMapErr when the transform function can return an error
result, err := lo.GroupByMapErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, int, error) {
if i == 3 {
return 0, 0, fmt.Errorf("number 3 is not allowed")
}
return i % 3, i * 2, nil
})
// map[int][]int(nil), error("number 3 is not allowed")
```
[[play](https://go.dev/play/p/iMeruQ3_W80)]
### Chunk
Returns a slice of elements split into groups of length size. If the slice can't be split evenly, the final chunk will be the remaining elements.
```go
lo.Chunk([]int{0, 1, 2, 3, 4, 5}, 2)
// [][]int{{0, 1}, {2, 3}, {4, 5}}
lo.Chunk([]int{0, 1, 2, 3, 4, 5, 6}, 2)
// [][]int{{0, 1}, {2, 3}, {4, 5}, {6}}
lo.Chunk([]int{}, 2)
// [][]int{}
lo.Chunk([]int{0}, 2)
// [][]int{{0}}
```
[[play](https://go.dev/play/p/kEMkFbdu85g)]
### Window
Creates a slice of sliding windows of a given size. Each window shares size-1 elements with the previous one. This is equivalent to `Sliding(collection, size, 1)`.
```go
lo.Window([]int{1, 2, 3, 4, 5}, 3)
// [][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}}
lo.Window([]float64{20, 22, 21, 23, 24}, 3)
// [][]float64{{20, 22, 21}, {22, 21, 23}, {21, 23, 24}}
```
### Sliding
Creates a slice of sliding windows of a given size with a given step. If step is equal to size, windows have no common elements (similar to Chunk). If step is less than size, windows share common elements.
```go
// Windows with shared elements (step < size)
lo.Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 1)
// [][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}
// Windows with no shared elements (step == size, like Chunk)
lo.Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 3)
// [][]int{{1, 2, 3}, {4, 5, 6}}
// Step > size (skipping elements)
lo.Sliding([]int{1, 2, 3, 4, 5, 6, 7, 8}, 2, 3)
// [][]int{{1, 2}, {4, 5}, {7, 8}}
```
### PartitionBy
Returns a slice of elements split into groups. The order of grouped values is determined by the order they occur in collection. The grouping is generated from the results of running each element of collection through iteratee.
```go
import lo "github.com/samber/lo"
partitions := lo.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
})
// [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}
```
```go
// Use PartitionByErr when the iteratee function can return an error
result, err := lo.PartitionByErr([]int{-2, -1, 0, 1, 2}, func(x int) (string, error) {
if x == 0 {
return "", fmt.Errorf("zero is not allowed")
}
if x < 0 {
return "negative", nil
} else if x%2 == 0 {
return "even", nil
}
return "odd", nil
})
// [][]int(nil), error("zero is not allowed")
```
[[play](https://go.dev/play/p/NfQ_nGjkgXW)]
Parallel processing: like `lo.PartitionBy()`, but callback is called in goroutine. Results are returned in the same order.
```go
import lop "github.com/samber/lo/parallel"
partitions := lop.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
})
// [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}
```
### Flatten
Returns a slice a single level deep.
```go
flat := lo.Flatten([][]int{{0, 1}, {2, 3, 4, 5}})
// []int{0, 1, 2, 3, 4, 5}
```
[[play](https://go.dev/play/p/rbp9ORaMpjw)]
### Concat
Returns a new slice containing all the elements in collections. Concat conserves the order of the elements.
```go
slice := lo.Concat([]int{1, 2}, []int{3, 4})
// []int{1, 2, 3, 4}
slice := lo.Concat(nil, []int{1, 2}, nil, []int{3, 4}, nil)
// []int{1, 2, 3, 4}
slice := lo.Concat[int]()
// []int{}
```
### Interleave
Round-robin alternating input slices and sequentially appending value at index into result.
```go
interleaved := lo.Interleave([]int{1, 4, 7}, []int{2, 5, 8}, []int{3, 6, 9})
// []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
interleaved := lo.Interleave([]int{1}, []int{2, 5, 8}, []int{3, 6}, []int{4, 7, 9, 10})
// []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
```
[[play](https://go.dev/play/p/-RJkTLQEDVt)]
### Shuffle
Returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
⚠️ This helper is **mutable**.
```go
import lom "github.com/samber/lo/mutable"
list := []int{0, 1, 2, 3, 4, 5}
lom.Shuffle(list)
list
// []int{1, 4, 0, 3, 5, 2}
```
[[play](https://go.dev/play/p/2xb3WdLjeSJ)]
### Reverse
Reverses a slice so that the first element becomes the last, the second element becomes the second to last, and so on.
⚠️ This helper is **mutable**.
```go
import lom "github.com/samber/lo/mutable"
list := []int{0, 1, 2, 3, 4, 5}
lom.Reverse(list)
list
// []int{5, 4, 3, 2, 1, 0}
```
[[play](https://go.dev/play/p/O-M5pmCRgzV)]
### Fill
Fills elements of a slice with `initial` value.
```go
type foo struct {
bar string
}
func (f foo) Clone() foo {
return foo{f.bar}
}
initializedSlice := lo.Fill([]foo{foo{"a"}, foo{"a"}}, foo{"b"})
// []foo{foo{"b"}, foo{"b"}}
```
[[play](https://go.dev/play/p/VwR34GzqEub)]
### Repeat
Builds a slice with N copies of initial value.
```go
type foo struct {
bar string
}
func (f foo) Clone() foo {
return foo{f.bar}
}
slice := lo.Repeat(2, foo{"a"})
// []foo{foo{"a"}, foo{"a"}}
```
[[play](https://go.dev/play/p/g3uHXbmc3b6)]
### RepeatBy
Builds a slice with values returned by N calls of callback.
```go
slice := lo.RepeatBy(0, func (i int) string {
return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10)
})
// []string{}
slice := lo.RepeatBy(5, func(i int) string {
return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10)
})
// []string{"0", "1", "4", "9", "16"}
```
[[play](https://go.dev/play/p/ozZLCtX_hNU)]
With error handling:
```go
slice, err := lo.RepeatByErr(5, func(i int) (string, error) {
if i == 3 {
return "", fmt.Errorf("index 3 is not allowed")
}
return fmt.Sprintf("item-%d", i), nil
})
// []string(nil), error("index 3 is not allowed")
```
### KeyBy
Transforms a slice or a slice of structs to a map based on a pivot callback.
```go
m := lo.KeyBy([]string{"a", "aa", "aaa"}, func(str string) int {
return len(str)
})
// map[int]string{1: "a", 2: "aa", 3: "aaa"}
type Character struct {
dir string
code int
}
characters := []Character{
{dir: "left", code: 97},
{dir: "right", code: 100},
}
result := lo.KeyBy(characters, func(char Character) string {
return string(rune(char.code))
})
//map[a:{dir:left code:97} d:{dir:right code:100}]
```
```go
result, err := lo.KeyByErr([]string{"a", "aa", "aaa", ""}, func(str string) (int, error) {
if str == "" {
return 0, fmt.Errorf("empty string not allowed")
}
return len(str), nil
})
// map[int]string(nil), error("empty string not allowed")
```
[[play](https://go.dev/play/p/mdaClUAT-zZ)]
### SliceToMap (alias: Associate)
Returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
If any of two pairs have the same key the last one gets added to the map.
The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
```go
in := []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}
aMap := lo.SliceToMap(in, func (f *foo) (string, int) {
return f.baz, f.bar
})
// map[string][int]{ "apple":1, "banana":2 }
```
[[play](https://go.dev/play/p/WHa2CfMO3Lr)]
### FilterSliceToMap
Returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
If any of two pairs have the same key the last one gets added to the map.
The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
The third return value of the transform function is a boolean that indicates whether the key-value pair should be included in the map.
```go
list := []string{"a", "aa", "aaa"}
result := lo.FilterSliceToMap(list, func(str string) (string, int, bool) {
return str, len(str), len(str) > 1
})
// map[string][int]{"aa":2 "aaa":3}
```
[[play](https://go.dev/play/p/2z0rDz2ZSGU)]
### Keyify
Returns a map with each unique element of the slice as a key.
```go
set := lo.Keyify([]int{1, 1, 2, 3, 4})
// map[int]struct{}{1:{}, 2:{}, 3:{}, 4:{}}
```
[[play](https://go.dev/play/p/RYhhM_csqIG)]
### Take
Takes the first n elements from a slice.
```go
l := lo.Take([]int{0, 1, 2, 3, 4, 5}, 3)
// []int{0, 1, 2}
l := lo.Take([]int{0, 1, 2}, 5)
// []int{0, 1, 2}
```
### TakeWhile
Takes elements from the beginning while the predicate returns true.
```go
l := lo.TakeWhile([]int{0, 1, 2, 3, 4, 5}, func(val int) bool {
return val < 3
})
// []int{0, 1, 2}
l := lo.TakeWhile([]string{"a", "aa", "aaa", "aa"}, func(val string) bool {
return len(val) <= 2
})
// []string{"a", "aa"}
```
### TakeFilter
Filters elements and takes the first n elements that match the predicate. Equivalent to calling Take(Filter(...)), but more efficient as it stops after finding n matches.
```go
l := lo.TakeFilter([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3, func(val int, index int) bool {
return val%2 == 0
})
// []int{2, 4, 6}
l := lo.TakeFilter([]string{"a", "aa", "aaa", "aaaa"}, 2, func(val string, index int) bool {
return len(val) > 1
})
// []string{"aa", "aaa"}
```
### Drop
Drops n elements from the beginning of a slice.
```go
l := lo.Drop([]int{0, 1, 2, 3, 4, 5}, 2)
// []int{2, 3, 4, 5}
```
[[play](https://go.dev/play/p/JswS7vXRJP2)]
### DropRight
Drops n elements from the end of a slice.
```go
l := lo.DropRight([]int{0, 1, 2, 3, 4, 5}, 2)
// []int{0, 1, 2, 3}
```
[[play](https://go.dev/play/p/GG0nXkSJJa3)]
### DropWhile
Drop elements from the beginning of a slice while the predicate returns true.
```go
l := lo.DropWhile([]string{"a", "aa", "aaa", "aa", "aa"}, func(val string) bool {
return len(val) <= 2
})
// []string{"aaa", "aa", "aa"}
```
[[play](https://go.dev/play/p/7gBPYw2IK16)]
### DropRightWhile
Drop elements from the end of a slice while the predicate returns true.
```go
l := lo.DropRightWhile([]string{"a", "aa", "aaa", "aa", "aa"}, func(val string) bool {
return len(val) <= 2
})
// []string{"a", "aa", "aaa"}
```
[[play](https://go.dev/play/p/3-n71oEC0Hz)]
### DropByIndex
Drops elements from a slice by the index. A negative index will drop elements from the end of the slice.
```go
l := lo.DropByIndex([]int{0, 1, 2, 3, 4, 5}, 2, 4, -1)
// []int{0, 1, 3}
```
[[play](https://go.dev/play/p/JswS7vXRJP2)]
### Reject
The opposite of Filter, this method returns the elements of collection that predicate does not return true for.
```go
odd := lo.Reject([]int{1, 2, 3, 4}, func(x int, _ int) bool {
return x%2 == 0
})
// []int{1, 3}
```
```go
// Use RejectErr when the predicate can return an error
odd, err := lo.RejectErr([]int{1, 2, 3, 4}, func(x int, _ int) (bool, error) {
if x == 3 {
return false, fmt.Errorf("number 3 is not allowed")
}
return x%2 == 0, nil
})
// []int(nil), error("number 3 is not allowed")
```
[[play](https://go.dev/play/p/YkLMODy1WEL)]
### RejectMap
The opposite of FilterMap, this method returns a slice obtained after both filtering and mapping using the given callback function.
The callback function should return two values:
- the result of the mapping operation and
- whether the result element should be included or not.
```go
items := lo.RejectMap([]int{1, 2, 3, 4}, func(x int, _ int) (int, bool) {
return x*10, x%2 == 0
})
// []int{10, 30}
```
### FilterReject
Mixes Filter and Reject, this method returns two slices, one for the elements of collection that predicate returns true for and one for the elements that predicate does not return true for.
```go
kept, rejected := lo.FilterReject([]int{1, 2, 3, 4}, func(x int, _ int) bool {
return x%2 == 0
})
// []int{2, 4}
// []int{1, 3}
```
### Count
Counts the number of elements in the collection that equal value.
```go
count := lo.Count([]int{1, 5, 1}, 1)
// 2
```
[[play](https://go.dev/play/p/Y3FlK54yveC)]
### CountBy
Counts the number of elements in the collection for which predicate is true.
```go
count := lo.CountBy([]int{1, 5, 1}, func(i int) bool {
return i < 4
})
// 2
```
```go
// Use CountByErr when the predicate can return an error
count, err := lo.CountByErr([]int{1, 5, 1}, func(i int) (bool, error) {
if i == 5 {
return false, fmt.Errorf("5 not allowed")
}
return i < 4, nil
})
// 0, error("5 not allowed")
```
[[play](https://go.dev/play/p/ByQbNYQQi4X)]
### CountValues
Counts the number of each element in the collection.
```go
lo.CountValues([]int{})
// map[int]int{}
lo.CountValues([]int{1, 2})
// map[int]int{1: 1, 2: 1}
lo.CountValues([]int{1, 2, 2})
// map[int]int{1: 1, 2: 2}
lo.CountValues([]string{"foo", "bar", ""})
// map[string]int{"": 1, "foo": 1, "bar": 1}
lo.CountValues([]string{"foo", "bar", "bar"})
// map[string]int{"foo": 1, "bar": 2}
```
[[play](https://go.dev/play/p/-p-PyLT4dfy)]
### CountValuesBy
Counts the number of each element in the collection. It is equivalent to chaining lo.Map and lo.CountValues.
```go
isEven := func(v int) bool {
return v%2==0
}
lo.CountValuesBy([]int{}, isEven)
// map[bool]int{}
lo.CountValuesBy([]int{1, 2}, isEven)
// map[bool]int{false: 1, true: 1}
lo.CountValuesBy([]int{1, 2, 2}, isEven)
// map[bool]int{false: 1, true: 2}
length := func(v string) int {
return len(v)
}
lo.CountValuesBy([]string{"foo", "bar", ""}, length)
// map[int]int{0: 1, 3: 2}
lo.CountValuesBy([]string{"foo", "bar", "bar"}, length)
// map[int]int{3: 3}
```
[[play](https://go.dev/play/p/2U0dG1SnOmS)]
### Subset
Returns a copy of a slice from `offset` up to `length` elements. Like `slice[start:start+length]`, but does not panic on overflow.
```go
in := []int{0, 1, 2, 3, 4}
sub := lo.Subset(in, 2, 3)
// []int{2, 3, 4}
sub := lo.Subset(in, -4, 3)
// []int{1, 2, 3}
sub := lo.Subset(in, -2, math.MaxUint)
// []int{3, 4}
```
[[play](https://go.dev/play/p/tOQu1GhFcog)]
### Slice
Returns a copy of a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow.
```go
in := []int{0, 1, 2, 3, 4}
slice := lo.Slice(in, 0, 5)
// []int{0, 1, 2, 3, 4}
slice := lo.Slice(in, 2, 3)
// []int{2}
slice := lo.Slice(in, 2, 6)
// []int{2, 3, 4}
slice := lo.Slice(in, 4, 3)
// []int{}
```
[[play](https://go.dev/play/p/8XWYhfMMA1h)]
### Replace
Returns a copy of the slice with the first n non-overlapping instances of old replaced by new.
```go
in := []int{0, 1, 0, 1, 2, 3, 0}
slice := lo.Replace(in, 0, 42, 1)
// []int{42, 1, 0, 1, 2, 3, 0}
slice := lo.Replace(in, -1, 42, 1)
// []int{0, 1, 0, 1, 2, 3, 0}
slice := lo.Replace(in, 0, 42, 2)
// []int{42, 1, 42, 1, 2, 3, 0}
slice := lo.Replace(in, 0, 42, -1)
// []int{42, 1, 42, 1, 2, 3, 42}
```
[[play](https://go.dev/play/p/XfPzmf9gql6)]
### ReplaceAll
Returns a copy of the slice with all non-overlapping instances of old replaced by new.
```go
in := []int{0, 1, 0, 1, 2, 3, 0}
slice := lo.ReplaceAll(in, 0, 42)
// []int{42, 1, 42, 1, 2, 3, 42}
slice := lo.ReplaceAll(in, -1, 42)
// []int{0, 1, 0, 1, 2, 3, 0}
```
[[play](https://go.dev/play/p/a9xZFUHfYcV)]
### Clone
Returns a shallow copy of the collection.
```go
in := []int{1, 2, 3, 4, 5}
cloned := lo.Clone(in)
// Verify it's a different slice by checking that modifying one doesn't affect the other
in[0] = 99
// cloned is []int{1, 2, 3, 4, 5}
```
[[play](https://go.dev/play/p/hgHmoOIxmuH)]
### Compact
Returns a slice of all non-zero elements.
```go
in := []string{"", "foo", "", "bar", ""}
slice := lo.Compact(in)
// []string{"foo", "bar"}
```
[[play](https://go.dev/play/p/tXiy-iK6PAc)]
### IsSorted
Checks if a slice is sorted.
```go
slice := lo.IsSorted([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
// true
```
[[play](https://go.dev/play/p/mc3qR-t4mcx)]
### IsSortedBy
Checks if a slice is sorted by iteratee.
```go
slice := lo.IsSortedBy([]string{"a", "bb", "ccc"}, func(s string) int {
return len(s)
})
// true
```
[[play](https://go.dev/play/p/wiG6XyBBu49)]
### Splice
Splice inserts multiple elements at index i. A negative index counts back from the end of the slice. The helper is protected against overflow errors.
```go
result := lo.Splice([]string{"a", "b"}, 1, "1", "2")
// []string{"a", "1", "2", "b"}
// negative
result = lo.Splice([]string{"a", "b"}, -1, "1", "2")
// []string{"a", "1", "2", "b"}
// overflow
result = lo.Splice([]string{"a", "b"}, 42, "1", "2")
// []string{"a", "b", "1", "2"}
```
[[play](https://go.dev/play/p/G5_GhkeSUBA)]
### Cut
Slices collection around the first instance of separator, returning the part of collection before and after separator. The found result reports whether separator appears in collection. If separator does not appear in s, cut returns collection, empty slice of []T, false.
```go
actualLeft, actualRight, result = lo.Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b", "c", "d"})
// actualLeft: []string{"a"}
// actualRight: []string{"e", "f", "g"}
// result: true
result = lo.Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"z"})
// actualLeft: []string{"a", "b", "c", "d", "e", "f", "g"}
// actualRight: []string{}
// result: false
result = lo.Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"})
// actualLeft: []string{}
// actualRight: []string{"c", "d", "e", "f", "g"}
// result: true
```
[[play](https://go.dev/play/p/GiL3qhpIP3f)]
### CutPrefix
Returns collection without the provided leading prefix []T and reports whether it found the prefix. If s doesn't start with prefix, CutPrefix returns collection, false. If prefix is the empty []T, CutPrefix returns collection, true.
```go
actualRight, result = lo.CutPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c"})
// actualRight: []string{"d", "e", "f", "g"}
// result: true
result = lo.CutPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b"})
// actualRight: []string{"a", "b", "c", "d", "e", "f", "g"}
// result: false
result = lo.CutPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{})
// actualRight: []string{"a", "b", "c", "d", "e", "f", "g"}
// result: true
```
[[play](https://go.dev/play/p/7Plak4a1ICl)]
### CutSuffix
Returns collection without the provided ending suffix []T and reports whether it found the suffix. If it doesn't end with suffix, CutSuffix returns collection, false. If suffix is the empty []T, CutSuffix returns collection, true.
```go
actualLeft, result = lo.CutSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"f", "g"})
// actualLeft: []string{"a", "b", "c", "d", "e"}
// result: true
actualLeft, result = lo.CutSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b"})
// actualLeft: []string{"a", "b", "c", "d", "e", "f", "g"}
// result: false
actualLeft, result = lo.CutSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{})
// actualLeft: []string{"a", "b", "c", "d", "e", "f", "g"}
// result: true
```
[[play](https://go.dev/play/p/7FKfBFvPTaT)]
### Trim
Removes all the leading and trailing cutset from the collection.
```go
result := lo.Trim([]int{0, 1, 2, 0, 3, 0}, []int{1, 0})
// []int{2, 0, 3}
result := lo.Trim([]string{"hello", "world", " "}, []string{" ", ""})
// []string{"hello", "world"}
```
[[play](https://go.dev/play/p/1an9mxLdRG5)]
### TrimLeft
Removes all the leading cutset from the collection.
```go
result := lo.TrimLeft([]int{0, 1, 2, 0, 3, 0}, []int{1, 0})
// []int{2, 0, 3, 0}
result := lo.TrimLeft([]string{"hello", "world", " "}, []string{" ", ""})
// []string{"hello", "world", " "}
```
[[play](https://go.dev/play/p/74aqfAYLmyi)]
### TrimPrefix
Removes all the leading prefix from the collection.
```go
result := lo.TrimPrefix([]int{1, 2, 1, 2, 3, 1, 2, 4}, []int{1, 2})
// []int{3, 1, 2, 4}
result := lo.TrimPrefix([]string{"hello", "world", "hello", "test"}, []string{"hello"})
// []string{"world", "hello", "test"}
```
[[play](https://go.dev/play/p/SHO6X-YegPg)]
### TrimRight
Removes all the trailing cutset from the collection.
```go
result := lo.TrimRight([]int{0, 1, 2, 0, 3, 0}, []int{0, 3})
// []int{0, 1, 2}
result := lo.TrimRight([]string{"hello", "world", " "}, []string{" ", ""})
// []string{"hello", "world", ""}
```
[[play](https://go.dev/play/p/MRpAfR6sf0g)]
### TrimSuffix
Removes all the trailing suffix from the collection.
```go
result := lo.TrimSuffix([]int{1, 2, 3, 1, 2, 4, 2, 4, 2, 4}, []int{2, 4})
// []int{1, 2, 3, 1}
result := lo.TrimSuffix([]string{"hello", "world", "hello", "test"}, []string{"test"})
// []string{"hello", "world", "hello"}
```
[[play](https://go.dev/play/p/IjEUrV0iofq)]
### Keys
Creates a slice of the map keys.
Use the UniqKeys variant to deduplicate common keys.
```go
keys := lo.Keys(map[string]int{"foo": 1, "bar": 2})
// []string{"foo", "bar"}
keys := lo.Keys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
// []string{"foo", "bar", "baz"}
keys := lo.Keys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 3})
// []string{"foo", "bar", "bar"}
```
[[play](https://go.dev/play/p/Uu11fHASqrU)]
### UniqKeys
Creates a slice of unique map keys.
```go
keys := lo.UniqKeys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
// []string{"foo", "bar", "baz"}
keys := lo.UniqKeys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 3})
// []string{"foo", "bar"}
```
[[play](https://go.dev/play/p/TPKAb6ILdHk)]
### HasKey
Returns whether the given key exists.
```go
exists := lo.HasKey(map[string]int{"foo": 1, "bar": 2}, "foo")
// true
exists := lo.HasKey(map[string]int{"foo": 1, "bar": 2}, "baz")
// false
```
[[play](https://go.dev/play/p/aVwubIvECqS)]
### Values
Creates a slice of the map values.
Use the UniqValues variant to deduplicate common values.
```go
values := lo.Values(map[string]int{"foo": 1, "bar": 2})
// []int{1, 2}
values := lo.Values(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
// []int{1, 2, 3}
values := lo.Values(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 2})
// []int{1, 2, 2}
```
[[play](https://go.dev/play/p/nnRTQkzQfF6)]
### UniqValues
Creates a slice of unique map values.
```go
values := lo.UniqValues(map[string]int{"foo": 1, "bar": 2})
// []int{1, 2}
values := lo.UniqValues(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
// []int{1, 2, 3}
values := lo.UniqValues(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 2})
// []int{1, 2}
```
[[play](https://go.dev/play/p/nf6bXMh7rM3)]
### ValueOr
Returns the value of the given key or the fallback value if the key is not present.
```go
value := lo.ValueOr(map[string]int{"foo": 1, "bar": 2}, "foo", 42)
// 1
value := lo.ValueOr(map[string]int{"foo": 1, "bar": 2}, "baz", 42)
// 42
```
[[play](https://go.dev/play/p/bAq9mHErB4V)]
### PickBy
Returns same map type filtered by given predicate.
```go
m := lo.PickBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) bool {
return value%2 == 1
})
// map[string]int{"foo": 1, "baz": 3}
```
```go
// Use PickByErr when the predicate can return an error
m, err := lo.PickByErr(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) (bool, error) {
if key == "bar" {
return false, fmt.Errorf("bar not allowed")
}
return value%2 == 1, nil
})
// map[string]int(nil), error("bar not allowed")
```
[[play](https://go.dev/play/p/kdg8GR_QMmf)]
### PickByKeys
Returns same map type filtered by given keys.
```go
m := lo.PickByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"})
// map[string]int{"foo": 1, "baz": 3}
```
[[play](https://go.dev/play/p/R1imbuci9qU)]
### PickByValues
Returns same map type filtered by given values.
```go
m := lo.PickByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3})
// map[string]int{"foo": 1, "baz": 3}
```
[[play](https://go.dev/play/p/1zdzSvbfsJc)]
### OmitBy
Returns same map type filtered by given predicate.
```go
m := lo.OmitBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) bool {
return value%2 == 1
})
// map[string]int{"bar": 2}
```
```go
// Use OmitByErr when the predicate can return an error
m, err := lo.OmitByErr(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) (bool, error) {
if key == "bar" {
return false, fmt.Errorf("bar not allowed")
}
return value%2 == 1, nil
})
// map[string]int(nil), error("bar not allowed")
```
[[play](https://go.dev/play/p/EtBsR43bdsd)]
### OmitByKeys
Returns same map type filtered by given keys.
```go
m := lo.OmitByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"})
// map[string]int{"bar": 2}
```
[[play](https://go.dev/play/p/t1QjCrs-ysk)]
### OmitByValues
Returns same map type filtered by given values.
```go
m := lo.OmitByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3})
// map[string]int{"bar": 2}
```
[[play](https://go.dev/play/p/9UYZi-hrs8j)]
### Entries (alias: ToPairs)
Transforms a map into a slice of key/value pairs.
```go
entries := lo.Entries(map[string]int{"foo": 1, "bar": 2})
// []lo.Entry[string, int]{
// {
// Key: "foo",
// Value: 1,
// },
// {
// Key: "bar",
// Value: 2,
// },
// }
```
[[play](https://go.dev/play/p/_t4Xe34-Nl5)]
### FromEntries (alias: FromPairs)
Transforms a slice of key/value pairs into a map.
```go
m := lo.FromEntries([]lo.Entry[string, int]{
{
Key: "foo",
Value: 1,
},
{
Key: "bar",
Value: 2,
},
})
// map[string]int{"foo": 1, "bar": 2}
```
[[play](https://go.dev/play/p/oIr5KHFGCEN)]
### Invert
Creates a map composed of the inverted keys and values. If map contains duplicate values, subsequent values overwrite property assignments of previous values.
```go
m1 := lo.Invert(map[string]int{"a": 1, "b": 2})
// map[int]string{1: "a", 2: "b"}
m2 := lo.Invert(map[string]int{"a": 1, "b": 2, "c": 1})
// map[int]string{1: "c", 2: "b"}
```
[[play](https://go.dev/play/p/rFQ4rak6iA1)]
### Assign
Merges multiple maps from left to right.
```go
mergedMaps := lo.Assign(
map[string]int{"a": 1, "b": 2},
map[string]int{"b": 3, "c": 4},
)
// map[string]int{"a": 1, "b": 3, "c": 4}
```
[[play](https://go.dev/play/p/VhwfJOyxf5o)]
### ChunkEntries
Splits a map into a slice of elements in groups of length equal to its size. If the map cannot be split evenly, the final chunk will contain the remaining elements.
```go
maps := lo.ChunkEntries(
map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
},
3,
)
// []map[string]int{
// {"a": 1, "b": 2, "c": 3},
// {"d": 4, "e": 5},
// }
```
[[play](https://go.dev/play/p/X_YQL6mmoD-)]
### MapKeys
Manipulates map keys and transforms it to a map of another type.
```go
m2 := lo.MapKeys(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(_ int, v int) string {
return strconv.FormatInt(int64(v), 10)
})
// map[string]int{"1": 1, "2": 2, "3": 3, "4": 4}
```
```go
// Use MapKeysErr when the iteratee can return an error
m2, err := lo.MapKeysErr(map[int]int{1: 1, 2: 2, 3: 3}, func(_ int, v int) (string, error) {
if v == 2 {
return "", fmt.Errorf("even number not allowed")
}
return strconv.FormatInt(int64(v), 10), nil
})
// map[string]int(nil), error("even number not allowed")
```
[[play](https://go.dev/play/p/9_4WPIqOetJ)]
### MapValues
Manipulates map values and transforms it to a map of another type.
```go
m1 := map[int]int64{1: 1, 2: 2, 3: 3}
m2 := lo.MapValues(m1, func(x int64, _ int) string {
return strconv.FormatInt(x, 10)
})
// map[int]string{1: "1", 2: "2", 3: "3"}
```
```go
// Use MapValuesErr when the iteratee can return an error
m1 := map[int]int64{1: 1, 2: 2, 3: 3}
m2, err := lo.MapValuesErr(m1, func(x int64, _ int) (string, error) {
if x == 2 {
return "", fmt.Errorf("even number not allowed")
}
return strconv.FormatInt(x, 10), nil
})
// map[int]string(nil), error("even number not allowed")
```
[[play](https://go.dev/play/p/T_8xAfvcf0W)]
### MapEntries
Manipulates map entries and transforms it to a map of another type.
```go
in := map[string]int{"foo": 1, "bar": 2}
out := lo.MapEntries(in, func(k string, v int) (int, string) {
return v,k
})
// map[int]string{1: "foo", 2: "bar"}
```
```go
// Use MapEntriesErr when the iteratee can return an error
in := map[string]int{"foo": 1, "bar": 2, "baz": 3}
out, err := lo.MapEntriesErr(in, func(k string, v int) (int, string, error) {
if k == "bar" {
return 0, "", fmt.Errorf("bar not allowed")
}
return v, k, nil
})
// map[int]string(nil), error("bar not allowed")
```
[[play](https://go.dev/play/p/VuvNQzxKimT)]
### MapToSlice
Transforms a map into a slice based on specified iteratee.
```go
m := map[int]int64{1: 4, 2: 5, 3: 6}
s := lo.MapToSlice(m, func(k int, v int64) string {
return fmt.Sprintf("%d_%d", k, v)
})
// []string{"1_4", "2_5", "3_6"}
```
```go
// Use MapToSliceErr when the iteratee can return an error
m := map[int]int64{1: 4, 2: 5, 3: 6}
s, err := lo.MapToSliceErr(m, func(k int, v int64) (string, error) {
if k == 2 {
return "", fmt.Errorf("key 2 not allowed")
}
return fmt.Sprintf("%d_%d", k, v), nil
})
// []string(nil), error("key 2 not allowed")
```
[[play](https://go.dev/play/p/ZuiCZpDt6LD)]
### FilterMapToSlice
Transforms a map into a slice based on specified iteratee. The iteratee returns a value and a boolean. If the boolean is true, the value is added to the result slice.
If the boolean is false, the value is not added to the result slice. The order of the keys in the input map is not specified and the order of the keys in the output slice is not guaranteed.
```go
kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
result := lo.FilterMapToSlice(kv, func(k int, v int64) (string, bool) {
return fmt.Sprintf("%d_%d", k, v), k%2 == 0
})
// []{"2_2", "4_4"}
```
```go
kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
result, err := lo.FilterMapToSliceErr(kv, func(k int, v int64) (string, bool, error) {
if k == 3 {
return "", false, fmt.Errorf("key 3 not allowed")
}
return fmt.Sprintf("%d_%d", k, v), k%2 == 0, nil
})
// []string(nil), error("key 3 not allowed")
```
### FilterKeys
Transforms a map into a slice based on predicate returns true for specific elements. It is a mix of `lo.Filter()` and `lo.Keys()`.
```go
kv := map[int]string{1: "foo", 2: "bar", 3: "baz"}
result := FilterKeys(kv, func(k int, v string) bool {
return v == "foo"
})
// [1]
```
```go
// Use FilterKeysErr when the predicate can return an error
result, err := lo.FilterKeysErr(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) (bool, error) {
if k == 3 {
return false, fmt.Errorf("key 3 not allowed")
}
return v == "foo", nil
})
// []int(nil), error("key 3 not allowed")
```
[[play](https://go.dev/play/p/OFlKXlPrBAe)]
### FilterValues
Transforms a map into a slice based on predicate returns true for specific elements. It is a mix of `lo.Filter()` and `lo.Values()`.
```go
kv := map[int]string{1: "foo", 2: "bar", 3: "baz"}
result := FilterValues(kv, func(k int, v string) bool {
return v == "foo"
})
// ["foo"]
```
```go
// Use FilterValuesErr when the predicate can return an error
result, err := lo.FilterValuesErr(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) (bool, error) {
if k == 3 {
return false, fmt.Errorf("key 3 not allowed")
}
return v == "foo", nil
})
// []string(nil), error("key 3 not allowed")
```
[[play](https://go.dev/play/p/YVD5r_h-LX-)]
### Range / RangeFrom / RangeWithSteps
Creates a slice of numbers (positive and/or negative) progressing from start up to, but not including end.
```go
result := lo.Range(4)
// [0, 1, 2, 3]
result := lo.Range(-4)
// [0, -1, -2, -3]
result := lo.RangeFrom(1, 5)
// [1, 2, 3, 4, 5]
result := lo.RangeFrom[float64](1.0, 5)
// [1.0, 2.0, 3.0, 4.0, 5.0]
result := lo.RangeWithSteps(0, 20, 5)
// [0, 5, 10, 15]
result := lo.RangeWithSteps[float32](-1.0, -4.0, -1.0)
// [-1.0, -2.0, -3.0]
result := lo.RangeWithSteps(1, 4, -1)
// []
result := lo.Range(0)
// []
```
[[play](https://go.dev/play/p/0r6VimXAi9H)]
### Clamp
Clamps number within the inclusive lower and upper bounds.
```go
r1 := lo.Clamp(0, -10, 10)
// 0
r2 := lo.Clamp(-42, -10, 10)
// -10
r3 := lo.Clamp(42, -10, 10)
// 10
```
[[play](https://go.dev/play/p/RU4lJNC2hlI)]
### Sum
Sums the values in a collection.
If collection is empty 0 is returned.
```go
list := []int{1, 2, 3, 4, 5}
sum := lo.Sum(list)
// 15
```
[[play](https://go.dev/play/p/upfeJVqs4Bt)]
### SumBy
Summarizes the values in a collection using the given return value from the iteration function.
If collection is empty 0 is returned.
```go
strings := []string{"foo", "bar"}
sum := lo.SumBy(strings, func(item string) int {
return len(item)
})
// 6
```
With error handling:
```go
strings := []string{"foo", "bar", "baz"}
sum, err := lo.SumByErr(strings, func(item string) (int, error) {
if item == "bar" {
return 0, fmt.Errorf("invalid item: %s", item)
}
return len(item), nil
})
// sum: 3, err: invalid item: bar
```
### Product
Calculates the product of the values in a collection.
If collection is empty 0 is returned.
```go
list := []int{1, 2, 3, 4, 5}
product := lo.Product(list)
// 120
```
[[play](https://go.dev/play/p/2_kjM_smtAH)]
### ProductBy
Calculates the product of the values in a collection using the given return value from the iteration function.
If collection is empty 0 is returned.
```go
strings := []string{"foo", "bar"}
product := lo.ProductBy(strings, func(item string) int {
return len(item)
})
// 9
```
```go
// Use ProductByErr when the transform function can return an error
strings := []string{"foo", "bar", "baz"}
product, err := lo.ProductByErr(strings, func(item string) (int, error) {
if item == "bar" {
return 0, fmt.Errorf("bar is not allowed")
}
return len(item), nil
})
// 3, error("bar is not allowed")
```
[[play](https://go.dev/play/p/wadzrWr9Aer)]
### Mean
Calculates the mean of a collection of numbers.
If collection is empty 0 is returned.
```go
mean := lo.Mean([]int{2, 3, 4, 5})
// 3
mean := lo.Mean([]float64{2, 3, 4, 5})
// 3.5
mean := lo.Mean([]float64{})
// 0
```
### MeanBy
Calculates the mean of a collection of numbers using the given return value from the iteration function.
If collection is empty 0 is returned.
```go
list := []string{"aa", "bbb", "cccc", "ddddd"}
mapper := func(item string) float64 {
return float64(len(item))
}
mean := lo.MeanBy(list, mapper)
// 3.5
mean := lo.MeanBy([]float64{}, mapper)
// 0
```
```go
// Use MeanByErr when the transform function can return an error
list := []string{"aa", "bbb", "cccc", "ddddd"}
mean, err := lo.MeanByErr(list, func(item string) (float64, error) {
if item == "cccc" {
return 0, fmt.Errorf("cccc is not allowed")
}
return float64(len(item)), nil
})
// 0, error("cccc is not allowed")
```
[[play](https://go.dev/play/p/j7TsVwBOZ7P)]
### Mode
Calculates the mode (most frequent value) of a collection of numbers.
If multiple values have the same highest frequency, then multiple values are returned.
If the collection is empty, the zero value of `T[]` is returned.
```go
mode := lo.Mode([]int{2, 2, 3, 4})
// [2]
mode := lo.Mode([]float64{2, 2, 3, 3})
// [2, 3]
mode := lo.Mode([]float64{})
// []
mode := lo.Mode([]int{1, 2, 3, 4, 5, 6, 7, 8, 9})
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
```
### RandomString
Returns a random string of the specified length and made of the specified charset.
```go
str := lo.RandomString(5, lo.LettersCharset)
// example: "eIGbt"
```
[[play](https://go.dev/play/p/rRseOQVVum4)]
### Substring
Return part of a string.
```go
sub := lo.Substring("hello", 2, 3)
// "llo"
sub := lo.Substring("hello", -4, 3)
// "ell"
sub := lo.Substring("hello", -2, math.MaxUint)
// "lo"
```
[[play](https://go.dev/play/p/TQlxQi82Lu1)]
### ChunkString
Returns a slice of strings split into groups of length size. If the string can't be split evenly, the final chunk will be the remaining characters.
```go
lo.ChunkString("123456", 2)
// []string{"12", "34", "56"}
lo.ChunkString("1234567", 2)
// []string{"12", "34", "56", "7"}
lo.ChunkString("", 2)
// []string{""}
lo.ChunkString("1", 2)
// []string{"1"}
```
Note: `lo.ChunkString` and `lo.Chunk` functions behave inconsistently for empty input: `lo.ChunkString("", n)` returns `[""]` instead of `[]`. See [#788](https://github.com/samber/lo/issues/788).
[[play](https://go.dev/play/p/__FLTuJVz54)]
### RuneLength
An alias to utf8.RuneCountInString which returns the number of runes in string.
```go
sub := lo.RuneLength("hellô")
// 5
sub := len("hellô")
// 6
```
[[play](https://go.dev/play/p/tuhgW_lWY8l)]
### PascalCase
Converts string to pascal case.
```go
str := lo.PascalCase("hello_world")
// HelloWorld
```
[[play](https://go.dev/play/p/Dy_V_6DUYhe)]
### CamelCase
Converts string to camel case.
```go
str := lo.CamelCase("hello_world")
// helloWorld
```
[[play](https://go.dev/play/p/Go6aKwUiq59)]
### KebabCase
Converts string to kebab case.
```go
str := lo.KebabCase("helloWorld")
// hello-world
```
[[play](https://go.dev/play/p/96gT_WZnTVP)]
### SnakeCase
Converts string to snake case.
```go
str := lo.SnakeCase("HelloWorld")
// hello_world
```
[[play](https://go.dev/play/p/ziB0V89IeVH)]
### Words
Splits string into a slice of its words.
```go
str := lo.Words("helloWorld")
// []string{"hello", "world"}
```
[[play](https://go.dev/play/p/-f3VIQqiaVw)]
### Capitalize
Converts the first character of string to upper case and the remaining to lower case.
```go
str := lo.Capitalize("heLLO")
// Hello
```
[[play](https://go.dev/play/p/uLTZZQXqnsa)]
### Ellipsis
Trims and truncates a string to a specified length in runes (Unicode code points) and appends an ellipsis if truncated. Multi-byte characters such as emoji or CJK ideographs are never split in the middle.
```go
str := lo.Ellipsis(" Lorem Ipsum ", 5)
// Lo...
str := lo.Ellipsis("Lorem Ipsum", 100)
// Lorem Ipsum
str := lo.Ellipsis("Lorem Ipsum", 3)
// ...
str := lo.Ellipsis("hello 世界! 你好", 8)
// hello...
str := lo.Ellipsis("🏠🐶🐱🌟", 4)
// 🏠🐶🐱🌟
```
[[play](https://go.dev/play/p/qE93rgqe1TW)]
### T2 -> T9
Creates a tuple from a list of values.
```go
tuple1 := lo.T2("x", 1)
// Tuple2[string, int]{A: "x", B: 1}
func example() (string, int) { return "y", 2 }
tuple2 := lo.T2(example())
// Tuple2[string, int]{A: "y", B: 2}
```
[[play](https://go.dev/play/p/IllL3ZO4BQm)]
### Unpack2 -> Unpack9
Returns values contained in a tuple.
```go
r1, r2 := lo.Unpack2(lo.Tuple2[string, int]{"a", 1})
// "a", 1
```
Unpack is also available as a method of TupleX.
```go
tuple2 := lo.T2("a", 1)
a, b := tuple2.Unpack()
// "a", 1
```
[[play](https://go.dev/play/p/xVP_k0kJ96W)]
### Zip2 -> Zip9
Zip creates a slice of grouped elements, the first of which contains the first elements of the given slices, the second of which contains the second elements of the given slices, and so on.
When collections are different sizes, the Tuple attributes are filled with zero value.
```go
tuples := lo.Zip2([]string{"a", "b"}, []int{1, 2})
// []Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}}
```
[[play](https://go.dev/play/p/jujaA6GaJTp)]
### ZipBy2 -> ZipBy9
ZipBy creates a slice of transformed elements, the first of which contains the first elements of the given slices, the second of which contains the second elements of the given slices, and so on.
When collections are different sizes, the Tuple attributes are filled with zero value.
```go
items := lo.ZipBy2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) string {
return fmt.Sprintf("%s-%d", a, b)
})
// []string{"a-1", "b-2"}
```
With error handling:
```go
items, err := lo.ZipByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
if b == 2 {
return "", fmt.Errorf("number 2 is not allowed")
}
return fmt.Sprintf("%s-%d", a, b), nil
})
// []string(nil), error("number 2 is not allowed")
```
### Unzip2 -> Unzip9
Unzip accepts a slice of grouped elements and creates a slice regrouping the elements to their pre-zip configuration.
```go
a, b := lo.Unzip2([]Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}})
// []string{"a", "b"}
// []int{1, 2}
```
[[play](https://go.dev/play/p/ciHugugvaAW)]
### UnzipBy2 -> UnzipBy9
UnzipBy2 iterates over a collection and creates a slice regrouping the elements to their pre-zip configuration.
```go
a, b := lo.UnzipBy2([]string{"hello", "john", "doe"}, func(str string) (string, int) {
return str, len(str)
})
// []string{"hello", "john", "doe"}
// []int{5, 4, 3}
```
```go
a, b, err := lo.UnzipByErr2([]string{"hello", "error", "world"}, func(str string) (string, int, error) {
if str == "error" {
return "", 0, fmt.Errorf("error string not allowed")
}
return str, len(str), nil
})
// []string{}
// []int{}
// error string not allowed
```
### CrossJoin2 -> CrossJoin9
Combines every item from one list with every item from others. It is the cartesian product of lists received as arguments. Returns an empty list if a list is empty.
```go
result := lo.CrossJoin2([]string{"hello", "john", "doe"}, []int{1, 2})
// lo.Tuple2{"hello", 1}
// lo.Tuple2{"hello", 2}
// lo.Tuple2{"john", 1}
// lo.Tuple2{"john", 2}
// lo.Tuple2{"doe", 1}
// lo.Tuple2{"doe", 2}
```
### CrossJoinBy2 -> CrossJoinBy9
Combines every item from one list with every item from others. It is the cartesian product of lists received as arguments. The transform function is used to create the output values. Returns an empty list if a list is empty.
```go
result := lo.CrossJoinBy2([]string{"hello", "john", "doe"}, []int{1, 2}, func(a A, b B) string {
return fmt.Sprintf("%s - %d", a, b)
})
// "hello - 1"
// "hello - 2"
// "john - 1"
// "john - 2"
// "doe - 1"
// "doe - 2"
```
With error handling:
```go
result, err := lo.CrossJoinByErr2([]string{"hello", "john"}, []int{1, 2}, func(a string, b int) (string, error) {
if a == "john" {
return "", fmt.Errorf("john not allowed")
}
return fmt.Sprintf("%s - %d", a, b), nil
})
// []string(nil), error("john not allowed")
```
### Duration
Returns the time taken to execute a function.
```go
duration := lo.Duration(func() {
// very long job
})
// 3s
```
[[play](https://go.dev/play/p/HQfbBbAXaFP)]
### Duration0 -> Duration10
Returns the time taken to execute a function.
```go
duration := lo.Duration0(func() {
// very long job
})
// 3s
err, duration := lo.Duration1(func() error {
// very long job
return errors.New("an error")
})
// an error
// 3s
str, nbr, err, duration := lo.Duration3(func() (string, int, error) {
// very long job
return "hello", 42, nil
})
// hello
// 42
// nil
// 3s
```
### ChannelDispatcher
Distributes messages from input channels into N child channels. Close events are propagated to children.
Underlying channels can have a fixed buffer capacity or be unbuffered when cap is 0.
```go
ch := make(chan int, 42)
for i := 0; i <= 10; i++ {
ch <- i
}
children := lo.ChannelDispatcher(ch, 5, 10, DispatchingStrategyRoundRobin[int])
// []<-chan int{...}
consumer := func(c <-chan int) {
for {
msg, ok := <-c
if !ok {
println("closed")
break
}
println(msg)
}
}
for i := range children {
go consumer(children[i])
}
```
[[play](https://go.dev/play/p/UZGu2wVg3J2)]
Many distributions strategies are available:
- [lo.DispatchingStrategyRoundRobin](./channel.go): Distributes messages in a rotating sequential manner.
- [lo.DispatchingStrategyRandom](./channel.go): Distributes messages in a random manner.
- [lo.DispatchingStrategyWeightedRandom](./channel.go): Distributes messages in a weighted manner.
- [lo.DispatchingStrategyFirst](./channel.go): Distributes messages in the first non-full channel.
- [lo.DispatchingStrategyLeast](./channel.go): Distributes messages in the emptiest channel.
- [lo.DispatchingStrategyMost](./channel.go): Distributes to the fullest channel.
Some strategies bring fallback, in order to favor non-blocking behaviors. See implementations.
For custom strategies, just implement the `lo.DispatchingStrategy` prototype:
```go
type DispatchingStrategy[T any] func(message T, messageIndex uint64, channels []<-chan T) int
```
Eg:
```go
type Message struct {
TenantID uuid.UUID
}
func hash(id uuid.UUID) int {
h := fnv.New32a()
h.Write([]byte(id.String()))
return int(h.Sum32())
}
// Routes messages per TenantID.
customStrategy := func(message string, messageIndex uint64, channels []<-chan string) int {
destination := hash(message) % len(channels)
// check if channel is full
if len(channels[destination]) < cap(channels[destination]) {
return destination
}
// fallback when child channel is full
return utils.DispatchingStrategyRoundRobin(message, uint64(destination), channels)
}
children := lo.ChannelDispatcher(ch, 5, 10, customStrategy)
...
```
### SliceToChannel
Returns a read-only channel of collection elements. Channel is closed after last element. Channel capacity can be customized.
```go
list := []int{1, 2, 3, 4, 5}
for v := range lo.SliceToChannel(2, list) {
println(v)
}
// prints 1, then 2, then 3, then 4, then 5
```
[[play](https://go.dev/play/p/lIbSY3QmiEg)]
### ChannelToSlice
Returns a slice built from channel items. Blocks until channel closes.
```go
list := []int{1, 2, 3, 4, 5}
ch := lo.SliceToChannel(2, list)
items := ChannelToSlice(ch)
// []int{1, 2, 3, 4, 5}
```
### Generator
Implements the generator design pattern. Channel is closed after last element. Channel capacity can be customized.
```go
generator := func(yield func(int)) {
yield(1)
yield(2)
yield(3)
}
for v := range lo.Generator(2, generator) {
println(v)
}
// prints 1, then 2, then 3
```
### Buffer
Creates a slice of n elements from a channel. Returns the slice, the slice length, the read time and the channel status (opened/closed).
```go
ch := lo.SliceToChannel(2, []int{1, 2, 3, 4, 5})
items1, length1, duration1, ok1 := lo.Buffer(ch, 3)
// []int{1, 2, 3}, 3, 0s, true
items2, length2, duration2, ok2 := lo.Buffer(ch, 3)
// []int{4, 5}, 2, 0s, false
```
Example: RabbitMQ consumer 👇
```go
ch := readFromQueue()
for {
// read 1k items
items, length, _, ok := lo.Buffer(ch, 1000)
// do batching stuff
if !ok {
break
}
}
```
### BufferWithContext
Creates a slice of n elements from a channel, with timeout. Returns the slice, the slice length, the read time and the channel status (opened/closed).
```go
ctx, cancel := context.WithCancel(context.TODO())
go func() {
ch <- 0
time.Sleep(10*time.Millisecond)
ch <- 1
time.Sleep(10*time.Millisecond)
ch <- 2
time.Sleep(10*time.Millisecond)
ch <- 3
time.Sleep(10*time.Millisecond)
ch <- 4
time.Sleep(10*time.Millisecond)
cancel()
}()
items1, length1, duration1, ok1 := lo.BufferWithContext(ctx, ch, 3)
// []int{0, 1, 2}, 3, 20ms, true
items2, length2, duration2, ok2 := lo.BufferWithContext(ctx, ch, 3)
// []int{3, 4}, 2, 30ms, false
```
### BufferWithTimeout
Creates a slice of n elements from a channel, with timeout. Returns the slice, the slice length, the read time and the channel status (opened/closed).
```go
generator := func(yield func(int)) {
for i := 0; i < 5; i++ {
yield(i)
time.Sleep(35*time.Millisecond)
}
}
ch := lo.Generator(0, generator)
items1, length1, duration1, ok1 := lo.BufferWithTimeout(ch, 3, 100*time.Millisecond)
// []int{1, 2}, 2, 100ms, true
items2, length2, duration2, ok2 := lo.BufferWithTimeout(ch, 3, 100*time.Millisecond)
// []int{3, 4, 5}, 3, 75ms, true
items3, length3, duration2, ok3 := lo.BufferWithTimeout(ch, 3, 100*time.Millisecond)
// []int{}, 0, 10ms, false
```
Example: RabbitMQ consumer 👇
```go
ch := readFromQueue()
for {
// read 1k items
// wait up to 1 second
items, length, _, ok := lo.BufferWithTimeout(ch, 1000, 1*time.Second)
// do batching stuff
if !ok {
break
}
}
```
Example: Multithreaded RabbitMQ consumer 👇
```go
ch := readFromQueue()
// 5 workers
// prefetch 1k messages per worker
children := lo.ChannelDispatcher(ch, 5, 1000, lo.DispatchingStrategyFirst[int])
consumer := func(c <-chan int) {
for {
// read 1k items
// wait up to 1 second
items, length, _, ok := lo.BufferWithTimeout(ch, 1000, 1*time.Second)
// do batching stuff
if !ok {
break
}
}
}
for i := range children {
go consumer(children[i])
}
```
### FanIn
Merge messages from multiple input channels into a single buffered channel. Output messages have no priority. When all upstream channels reach EOF, downstream channel closes.
```go
stream1 := make(chan int, 42)
stream2 := make(chan int, 42)
stream3 := make(chan int, 42)
all := lo.FanIn(100, stream1, stream2, stream3)
// <-chan int
```
### FanOut
Broadcasts all the upstream messages to multiple downstream channels. When upstream channel reaches EOF, downstream channels close. If any downstream channels is full, broadcasting is paused.
```go
stream := make(chan int, 42)
all := lo.FanOut(5, 100, stream)
// [5]<-chan int
```
### Contains
Returns true if an element is present in a collection.
```go
present := lo.Contains([]int{0, 1, 2, 3, 4, 5}, 5)
// true
```
[[play](https://go.dev/play/p/W1EvyqY6t9j)]
### ContainsBy
Returns true if the predicate function returns `true`.
```go
present := lo.ContainsBy([]int{0, 1, 2, 3, 4, 5}, func(x int) bool {
return x == 3
})
// true
```
### Every
Returns true if all elements of a subset are contained in a collection or if the subset is empty.
```go
ok := lo.Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// true
ok := lo.Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
// false
```
### EveryBy
Returns true if the predicate returns true for all elements in the collection or if the collection is empty.
```go
b := EveryBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 5
})
// true
```
[[play](https://go.dev/play/p/dn1-vhHsq9x)]
### Some
Returns true if at least 1 element of a subset is contained in a collection.
If the subset is empty Some returns false.
```go
ok := lo.Some([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
// true
```
[[play](https://go.dev/play/p/Lj4ceFkeT9V)]
ok := lo.Some([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// false
```
### SomeBy
Returns true if the predicate returns true for any of the elements in the collection.
If the collection is empty SomeBy returns false.
```go
b := SomeBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 3
})
// true
```
### None
Returns true if no element of a subset is contained in a collection or if the subset is empty.
```go
b := None([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// false
b := None([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// true
```
[[play](https://go.dev/play/p/fye7JsmxzPV)]
### NoneBy
Returns true if the predicate returns true for none of the elements in the collection or if the collection is empty.
```go
b := NoneBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 0
})
// true
```
[[play](https://go.dev/play/p/O64WZ32H58S)]
### Intersect
Returns the intersection between collections.
```go
result1 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// []int{0, 2}
result2 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
// []int{0}
result3 := lo.Intersect([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// []int{}
result4 := lo.Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
// []int{3}
```
### IntersectBy
Returns the intersection between two collections using a custom key selector function.
```go
transform := func(v int) string {
return strconv.Itoa(v)
}
result1 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// []int{0, 2}
result2 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{0, 6})
// []int{0}
result3 := lo.IntersectBy(transform, []int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// []int{}
result4 := lo.IntersectBy(transform, []int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
// []int{3}
```
### Difference
Returns the difference between two collections.
- The first value is the collection of elements absent from list2.
- The second value is the collection of elements absent from list1.
```go
left, right := lo.Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6})
// []int{1, 3, 4, 5}, []int{6}
left, right := lo.Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5})
// []int{}, []int{}
```
[[play](https://go.dev/play/p/pKE-JgzqRpz)]
### Union
Returns all distinct elements from given collections. Result will not change the order of elements relatively.
```go
union := lo.Union([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}, []int{0, 10})
// []int{0, 1, 2, 3, 4, 5, 10}
```
### Without
Returns a slice excluding all given values.
```go
subset := lo.Without([]int{0, 2, 10}, 2)
// []int{0, 10}
subset := lo.Without([]int{0, 2, 10}, 0, 1, 2, 3, 4, 5)
// []int{10}
```
### WithoutBy
Filters a slice by excluding elements whose extracted keys match any in the exclude list.
Returns a new slice containing only the elements whose keys are not in the exclude list.
```go
type User struct {
ID int
Name string
}
// original users
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}
// extract function to get the user ID
getID := func(user User) int {
return user.ID
}
// exclude users with IDs 2 and 3
excludedIDs := []int{2, 3}
// filtering users
filteredUsers := lo.WithoutBy(users, getID, excludedIDs...)
// []User[{ID: 1, Name: "Alice"}]
```
```go
// Use WithoutByErr when the iteratee can return an error
type struct User {
ID int
Name string
}
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}
getID := func(user User) (int, error) {
if user.ID == 2 {
return 0, fmt.Errorf("Bob not allowed")
}
return user.ID, nil
}
filteredUsers, err := lo.WithoutByErr(users, getID, 2, 3)
// []User(nil), error("Bob not allowed")
```
### WithoutEmpty
Returns a slice excluding zero values.
```go
subset := lo.WithoutEmpty([]int{0, 2, 10})
// []int{2, 10}
```
### WithoutNth
Returns a slice excluding the nth value.
```go
subset := lo.WithoutNth([]int{-2, -1, 0, 1, 2}, 3, -42, 1)
// []int{-2, 0, 2}
```
### ElementsMatch
Returns true if lists contain the same set of elements (including empty set).
If there are duplicate elements, the number of occurrences in each list should match.
The order of elements is not checked.
```go
b := lo.ElementsMatch([]int{1, 1, 2}, []int{2, 1, 1})
// true
```
### ElementsMatchBy
Returns true if lists contain the same set of elements' keys (including empty set).
If there are duplicate keys, the number of occurrences in each list should match.
The order of elements is not checked.
```go
b := lo.ElementsMatchBy(
[]someType{a, b},
[]someType{b, a},
func(item someType) string { return item.ID() },
)
// true
```
### IndexOf
Returns the index at which the first occurrence of a value is found in a slice or -1 if the value cannot be found.
```go
found := lo.IndexOf([]int{0, 1, 2, 1, 2, 3}, 2)
// 2
notFound := lo.IndexOf([]int{0, 1, 2, 1, 2, 3}, 6)
// -1
```
[[play](https://go.dev/play/p/Eo7W0lvKTky)]
### LastIndexOf
Returns the index at which the last occurrence of a value is found in a slice or -1 if the value cannot be found.
```go
found := lo.LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 2)
// 4
notFound := lo.LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 6)
// -1
```
### HasPrefix
Returns true if the collection has the prefix.
```go
ok := lo.HasPrefix([]int{1, 2, 3, 4}, []int{42})
// false
ok := lo.HasPrefix([]int{1, 2, 3, 4}, []int{1, 2})
// true
```
[[play](https://go.dev/play/p/SrljzVDpMQM)]
### HasSuffix
Returns true if the collection has the suffix.
```go
ok := lo.HasSuffix([]int{1, 2, 3, 4}, []int{42})
// false
ok := lo.HasSuffix([]int{1, 2, 3, 4}, []int{3, 4})
// true
```
[[play](https://go.dev/play/p/bJeLetQNAON)]
### Find
Searches for an element in a slice based on a predicate. Returns element and true if element was found.
```go
str, ok := lo.Find([]string{"a", "b", "c", "d"}, func(i string) bool {
return i == "b"
})
// "b", true
str, ok := lo.Find([]string{"foobar"}, func(i string) bool {
return i == "b"
})
// "", false
```
```go
// Use FindErr when the predicate can return an error
str, err := lo.FindErr([]string{"a", "b", "c", "d"}, func(i string) (bool, error) {
if i == "c" {
return false, fmt.Errorf("c is not allowed")
}
return i == "b", nil
})
// "b", nil
str, err = lo.FindErr([]string{"a", "b", "c"}, func(i string) (bool, error) {
if i == "b" {
return false, fmt.Errorf("b is not allowed")
}
return i == "b", nil
})
// "", error("b is not allowed")
```
[[play](https://go.dev/play/p/Eo7W0lvKTky)]
### FindIndexOf
FindIndexOf searches for an element in a slice based on a predicate and returns the index and true. Returns -1 and false if the element is not found.
```go
str, index, ok := lo.FindIndexOf([]string{"a", "b", "a", "b"}, func(i string) bool {
return i == "b"
})
// "b", 1, true
str, index, ok := lo.FindIndexOf([]string{"foobar"}, func(i string) bool {
return i == "b"
})
// "", -1, false
```
[[play](https://go.dev/play/p/XWSEM4Ic_t0)]
### FindLastIndexOf
FindLastIndexOf searches for the last element in a slice based on a predicate and returns the index and true. Returns -1 and false if the element is not found.
```go
str, index, ok := lo.FindLastIndexOf([]string{"a", "b", "a", "b"}, func(i string) bool {
return i == "b"
})
// "b", 4, true
str, index, ok := lo.FindLastIndexOf([]string{"foobar"}, func(i string) bool {
return i == "b"
})
// "", -1, false
```
[[play](https://go.dev/play/p/dPiMRtJ6cUx)]
### FindOrElse
Searches for an element in a slice based on a predicate. Returns the element if found or a given fallback value otherwise.
```go
str := lo.FindOrElse([]string{"a", "b", "c", "d"}, "x", func(i string) bool {
return i == "b"
})
// "b"
str := lo.FindOrElse([]string{"foobar"}, "x", func(i string) bool {
return i == "b"
})
// "x"
```
### FindKey
Returns the key of the first value matching.
```go
result1, ok1 := lo.FindKey(map[string]int{"foo": 1, "bar": 2, "baz": 3}, 2)
// "bar", true
result2, ok2 := lo.FindKey(map[string]int{"foo": 1, "bar": 2, "baz": 3}, 42)
// "", false
type test struct {
foobar string
}
result3, ok3 := lo.FindKey(map[string]test{"foo": test{"foo"}, "bar": test{"bar"}, "baz": test{"baz"}}, test{"foo"})
// "foo", true
```
### FindKeyBy
Returns the key of the first element predicate returns true for.
```go
result1, ok1 := lo.FindKeyBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool {
return k == "foo"
})
// "foo", true
result2, ok2 := lo.FindKeyBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool {
return false
})
// "", false
```
### FindUniques
Returns a slice with all the elements that appear in the collection only once. The order of result values is determined by the order they occur in the slice.
```go
uniqueValues := lo.FindUniques([]int{1, 2, 2, 1, 2, 3})
// []int{3}
```
### FindUniquesBy
Returns a slice with all the elements that appear in the collection only once. The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is invoked for each element in the slice to generate the criterion by which uniqueness is computed.
```go
uniqueValues := lo.FindUniquesBy([]int{3, 4, 5, 6, 7}, func(i int) int {
return i%3
})
// []int{5}
```
### FindDuplicates
Returns a slice with the first occurrence of each duplicated element in the collection. The order of result values is determined by the order they occur in the slice.
```go
duplicatedValues := lo.FindDuplicates([]int{1, 2, 2, 1, 2, 3})
// []int{1, 2}
```
### FindDuplicatesBy
Returns a slice with the first occurrence of each duplicated element in the collection. The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is invoked for each element in the slice to generate the criterion by which uniqueness is computed.
```go
duplicatedValues := lo.FindDuplicatesBy([]int{3, 4, 5, 6, 7}, func(i int) int {
return i%3
})
// []int{3, 4}
```
With error handling:
```go
duplicatedValues, err := lo.FindDuplicatesByErr([]int{3, 4, 5, 6, 7}, func(i int) (int, error) {
if i == 5 {
return 0, fmt.Errorf("number 5 is not allowed")
}
return i % 3, nil
})
// []int(nil), error("number 5 is not allowed")
```
### Min
Search the minimum value of a collection.
Returns zero value when the collection is empty.
```go
min := lo.Min([]int{1, 2, 3})
// 1
min := lo.Min([]int{})
// 0
min := lo.Min([]time.Duration{time.Second, time.Hour})
// 1s
```
[[play](https://go.dev/play/p/r6e-Z8JozS8)]
### MinIndex
Search the minimum value of a collection and the index of the minimum value.
Returns (zero value, -1) when the collection is empty.
```go
min, index := lo.MinIndex([]int{1, 2, 3})
// 1, 0
min, index := lo.MinIndex([]int{})
// 0, -1
min, index := lo.MinIndex([]time.Duration{time.Second, time.Hour})
// 1s, 0
```
### MinBy
Search the minimum value of a collection using the given comparison function.
If several values of the collection are equal to the smallest value, returns the first such value.
Returns zero value when the collection is empty.
```go
min := lo.MinBy([]string{"s1", "string2", "s3"}, func(item string, min string) bool {
return len(item) < len(min)
})
// "s1"
min := lo.MinBy([]string{}, func(item string, min string) bool {
return len(item) < len(min)
})
// ""
```
```go
// Use MinByErr when the comparison function can return an error
min, err := lo.MinByErr([]string{"s1", "string2", "s3"}, func(item string, min string) (bool, error) {
if item == "string2" {
return false, fmt.Errorf("string2 is not allowed")
}
return len(item) < len(min), nil
})
// "s1", error("string2 is not allowed")
```
### MinIndexBy
Search the minimum value of a collection using the given comparison function and the index of the minimum value.
If several values of the collection are equal to the smallest value, returns the first such value.
Returns (zero value, -1) when the collection is empty.
```go
min, index := lo.MinIndexBy([]string{"s1", "string2", "s3"}, func(item string, min string) bool {
return len(item) < len(min)
})
// "s1", 0
min, index := lo.MinIndexBy([]string{}, func(item string, min string) bool {
return len(item) < len(min)
})
// "", -1
```
```go
min, index, err := lo.MinIndexByErr([]string{"s1", "string2", "s3"}, func(item string, min string) (bool, error) {
if item == "s2" {
return false, fmt.Errorf("s2 is not allowed")
}
return len(item) < len(min), nil
})
// "s1", 0, error("s2 is not allowed")
```
### Earliest
Search the minimum time.Time of a collection.
Returns zero value when the collection is empty.
```go
earliest := lo.Earliest(time.Now(), time.Time{})
// 0001-01-01 00:00:00 +0000 UTC
```
### EarliestBy
Search the minimum time.Time of a collection using the given iteratee function.
Returns zero value when the collection is empty.
```go
type foo struct {
bar time.Time
}
earliest := lo.EarliestBy([]foo{{time.Now()}, {}}, func(i foo) time.Time {
return i.bar
})
// {bar:{2023-04-01 01:02:03 +0000 UTC}}
```
```go
// Use EarliestByErr when the iteratee function can return an error
earliest, err := lo.EarliestByErr([]foo{{time.Now()}, {}}, func(i foo) (time.Time, error) {
if i.bar.IsZero() {
return time.Time{}, fmt.Errorf("zero time not allowed")
}
return i.bar, nil
})
// {bar:{...}}, error("zero time not allowed")
```
### Max
Search the maximum value of a collection.
Returns zero value when the collection is empty.
```go
max := lo.Max([]int{1, 2, 3})
// 3
max := lo.Max([]int{})
// 0
max := lo.Max([]time.Duration{time.Second, time.Hour})
// 1h
```
### MaxIndex
Search the maximum value of a collection and the index of the maximum value.
Returns (zero value, -1) when the collection is empty.
```go
max, index := lo.MaxIndex([]int{1, 2, 3})
// 3, 2
max, index := lo.MaxIndex([]int{})
// 0, -1
max, index := lo.MaxIndex([]time.Duration{time.Second, time.Hour})
// 1h, 1
```
### MaxBy
Search the maximum value of a collection using the given comparison function.
If several values of the collection are equal to the greatest value, returns the first such value.
Returns zero value when the collection is empty.
```go
max := lo.MaxBy([]string{"string1", "s2", "string3"}, func(item string, max string) bool {
return len(item) > len(max)
})
// "string1"
max := lo.MaxBy([]string{}, func(item string, max string) bool {
return len(item) > len(max)
})
// ""
```
```go
// Use MaxByErr when the comparison function can return an error
max, err := lo.MaxByErr([]string{"string1", "s2", "string3"}, func(item string, max string) (bool, error) {
if item == "s2" {
return false, fmt.Errorf("s2 is not allowed")
}
return len(item) > len(max), nil
})
// "string1", error("s2 is not allowed")
```
[[play](https://go.dev/play/p/JW1qu-ECwF7)]
### MaxIndexBy
Search the maximum value of a collection using the given comparison function and the index of the maximum value.
If several values of the collection are equal to the greatest value, returns the first such value.
Returns (zero value, -1) when the collection is empty.
```go
max, index := lo.MaxIndexBy([]string{"string1", "s2", "string3"}, func(item string, max string) bool {
return len(item) > len(max)
})
// "string1", 0
max, index := lo.MaxIndexBy([]string{}, func(item string, max string) bool {
return len(item) > len(max)
})
// "", -1
```
```go
// Use MaxIndexByErr when the comparison function can return an error
max, index, err := lo.MaxIndexByErr([]string{"string1", "s2", "string3"}, func(item string, max string) (bool, error) {
if item == "s2" {
return false, fmt.Errorf("s2 is not allowed")
}
return len(item) > len(max), nil
})
// "string1", 0, error("s2 is not allowed")
```
[[play](https://go.dev/play/p/uaUszc-c9QK)]
### Latest
Search the maximum time.Time of a collection.
Returns zero value when the collection is empty.
```go
latest := lo.Latest(time.Now(), time.Time{})
// 2023-04-01 01:02:03 +0000 UTC
```
### LatestBy
Search the maximum time.Time of a collection using the given iteratee function.
Returns zero value when the collection is empty.
```go
type foo struct {
bar time.Time
}
latest := lo.LatestBy([]foo{{time.Now()}, {}}, func(i foo) time.Time {
return i.bar
})
// {bar:{2023-04-01 01:02:03 +0000 UTC}}
```
```go
// Use LatestByErr when the iteratee function can return an error
result, err := lo.LatestByErr([]foo{{time.Now()}, {}}, func(i foo) (time.Time, error) {
if i.bar.IsZero() {
return time.Time{}, fmt.Errorf("zero time not allowed")
}
return i.bar, nil
})
// foo{}, error("zero time not allowed")
```
### First
Returns the first element of a collection and check for availability of the first element.
```go
first, ok := lo.First([]int{1, 2, 3})
// 1, true
first, ok := lo.First([]int{})
// 0, false
```
### FirstOrEmpty
Returns the first element of a collection or zero value if empty.
```go
first := lo.FirstOrEmpty([]int{1, 2, 3})
// 1
first := lo.FirstOrEmpty([]int{})
// 0
```
### FirstOr
Returns the first element of a collection or the fallback value if empty.
```go
first := lo.FirstOr([]int{1, 2, 3}, 245)
// 1
first := lo.FirstOr([]int{}, 31)
// 31
```
### Last
Returns the last element of a collection or error if empty.
```go
last, ok := lo.Last([]int{1, 2, 3})
// 3
// true
last, ok := lo.Last([]int{})
// 0
// false
```
### LastOrEmpty
Returns the last element of a collection or zero value if empty.
```go
last := lo.LastOrEmpty([]int{1, 2, 3})
// 3
last := lo.LastOrEmpty([]int{})
// 0
```
### LastOr
Returns the last element of a collection or the fallback value if empty.
```go
last := lo.LastOr([]int{1, 2, 3}, 245)
// 3
last := lo.LastOr([]int{}, 31)
// 31
```
### Nth
Returns the element at index `nth` of collection. If `nth` is negative, the nth element from the end is returned. An error is returned when nth is out of slice bounds.
```go
nth, err := lo.Nth([]int{0, 1, 2, 3}, 2)
// 2
nth, err := lo.Nth([]int{0, 1, 2, 3}, -2)
// 2
```
### NthOr
Returns the element at index `nth` of the collection. If `nth` is negative, it returns the `nth` element from the end. If `nth` is out of slice bounds, it returns the provided fallback value
```go
nth := lo.NthOr([]int{10, 20, 30, 40, 50}, 2, -1)
// 30
nth := lo.NthOr([]int{10, 20, 30, 40, 50}, -1, -1)
// 50
nth := lo.NthOr([]int{10, 20, 30, 40, 50}, 5, -1)
// -1 (fallback value)
```
### NthOrEmpty
Returns the element at index `nth` of the collection. If `nth` is negative, it returns the `nth` element from the end. If `nth` is out of slice bounds, it returns the zero value for the element type (e.g., 0 for integers, "" for strings, etc).
``` go
nth := lo.NthOrEmpty([]int{10, 20, 30, 40, 50}, 2)
// 30
nth := lo.NthOrEmpty([]int{10, 20, 30, 40, 50}, -1)
// 50
nth := lo.NthOrEmpty([]int{10, 20, 30, 40, 50}, 5)
// 0 (zero value for int)
nth := lo.NthOrEmpty([]string{"apple", "banana", "cherry"}, 2)
// "cherry"
nth := lo.NthOrEmpty([]string{"apple", "banana", "cherry"}, 5)
// "" (zero value for string)
```
### Sample
Returns a random item from collection.
```go
lo.Sample([]string{"a", "b", "c"})
// a random string from []string{"a", "b", "c"}
lo.Sample([]string{})
// ""
```
[[play](https://go.dev/play/p/FYA45LcpfM2)]
### SampleBy
Returns a random item from collection, using a given random integer generator.
```go
import "math/rand"
r := rand.New(rand.NewSource(42))
lo.SampleBy([]string{"a", "b", "c"}, r.Intn)
// a random string from []string{"a", "b", "c"}, using a seeded random generator
lo.SampleBy([]string{}, r.Intn)
// ""
```
### Samples
Returns N random unique items from collection.
```go
lo.Samples([]string{"a", "b", "c"}, 3)
// []string{"a", "b", "c"} in random order
```
### SamplesBy
Returns N random unique items from collection, using a given random integer generator.
```go
r := rand.New(rand.NewSource(42))
lo.SamplesBy([]string{"a", "b", "c"}, 3, r.Intn)
// []string{"a", "b", "c"} in random order, using a seeded random generator
```
### Ternary
A single line if/else statement.
```go
result := lo.Ternary(true, "a", "b")
// "a"
result := lo.Ternary(false, "a", "b")
// "b"
```
Take care to avoid dereferencing potentially nil pointers in your A/B expressions, because they are both evaluated. See TernaryF to avoid this problem.
[[play](https://go.dev/play/p/t-D7WBL44h2)]
### TernaryF
A single line if/else statement whose options are functions.
```go
result := lo.TernaryF(true, func() string { return "a" }, func() string { return "b" })
// "a"
result := lo.TernaryF(false, func() string { return "a" }, func() string { return "b" })
// "b"
```
Useful to avoid nil-pointer dereferencing in initializations, or avoid running unnecessary code
```go
var s *string
someStr := TernaryF(s == nil, func() string { return uuid.New().String() }, func() string { return *s })
// ef782193-c30c-4e2e-a7ae-f8ab5e125e02
```
[[play](https://go.dev/play/p/AO4VW20JoqM)]
### If / ElseIf / Else
```go
result := lo.If(true, 1).
ElseIf(false, 2).
Else(3)
// 1
result := lo.If(false, 1).
ElseIf(true, 2).
Else(3)
// 2
result := lo.If(false, 1).
ElseIf(false, 2).
Else(3)
// 3
```
Using callbacks:
```go
result := lo.IfF(true, func () int {
return 1
}).
ElseIfF(false, func () int {
return 2
}).
ElseF(func () int {
return 3
})
// 1
```
Mixed:
```go
result := lo.IfF(true, func () int {
return 1
}).
Else(42)
// 1
```
[[play](https://go.dev/play/p/WSw3ApMxhyW)]
### Switch / Case / Default
```go
result := lo.Switch(1).
Case(1, "1").
Case(2, "2").
Default("3")
// "1"
result := lo.Switch(2).
Case(1, "1").
Case(2, "2").
Default("3")
// "2"
result := lo.Switch(42).
Case(1, "1").
Case(2, "2").
Default("3")
// "3"
```
Using callbacks:
```go
result := lo.Switch(1).
CaseF(1, func() string {
return "1"
}).
CaseF(2, func() string {
return "2"
}).
DefaultF(func() string {
return "3"
})
// "1"
```
Mixed:
```go
result := lo.Switch(1).
CaseF(1, func() string {
return "1"
}).
Default("42")
// "1"
```
[[play](https://go.dev/play/p/TGbKUMAeRUd)]
### IsNil
Checks if a value is nil or if it's a reference type with a nil underlying value.
```go
var x int
lo.IsNil(x)
// false
var k struct{}
lo.IsNil(k)
// false
var i *int
lo.IsNil(i)
// true
var ifaceWithNilValue any = (*string)(nil)
lo.IsNil(ifaceWithNilValue)
// true
ifaceWithNilValue == nil
// false
```
### IsNotNil
Checks if a value is not nil or if it's not a reference type with a nil underlying value.
```go
var x int
lo.IsNotNil(x)
// true
var k struct{}
lo.IsNotNil(k)
// true
var i *int
lo.IsNotNil(i)
// false
var ifaceWithNilValue any = (*string)(nil)
lo.IsNotNil(ifaceWithNilValue)
// false
ifaceWithNilValue == nil
// true
```
### ToPtr
Returns a pointer copy of the value.
```go
ptr := lo.ToPtr("hello world")
// *string{"hello world"}
```
[[play](https://go.dev/play/p/P2sD0PMXw4F)]
### Nil
Returns a nil pointer of type.
```go
ptr := lo.Nil[float64]()
// nil
```
### EmptyableToPtr
Returns a pointer copy of value if it's nonzero.
Otherwise, returns nil pointer.
```go
ptr := lo.EmptyableToPtr(nil)
// nil
ptr := lo.EmptyableToPtr("")
// nil
ptr := lo.EmptyableToPtr([]int{})
// *[]int{}
ptr := lo.EmptyableToPtr("hello world")
// *string{"hello world"}
```
### FromPtr
Returns the pointer value or empty.
```go
str := "hello world"
value := lo.FromPtr(&str)
// "hello world"
value := lo.FromPtr(nil)
// ""
```
### FromPtrOr
Returns the pointer value or the fallback value.
```go
str := "hello world"
value := lo.FromPtrOr(&str, "empty")
// "hello world"
value := lo.FromPtrOr(nil, "empty")
// "empty"
```
### ToSlicePtr
Returns a slice of pointers to each value.
```go
ptr := lo.ToSlicePtr([]string{"hello", "world"})
// []*string{"hello", "world"}
```
### FromSlicePtr
Returns a slice with the pointer values.
Returns a zero value in case of a nil pointer element.
```go
str1 := "hello"
str2 := "world"
ptr := lo.FromSlicePtr[string]([]*string{&str1, &str2, nil})
// []string{"hello", "world", ""}
ptr := lo.Compact(
lo.FromSlicePtr[string]([]*string{&str1, &str2, nil}),
)
// []string{"hello", "world"}
```
### FromSlicePtrOr
Returns a slice with the pointer values or the fallback value.
```go
str1 := "hello"
str2 := "world"
ptr := lo.FromSlicePtrOr([]*string{&str1, nil, &str2}, "fallback value")
// []string{"hello", "fallback value", "world"}
```
[[play](https://go.dev/play/p/CuXGVzo9G65)]
### ToAnySlice
Returns a slice with all elements mapped to `any` type.
```go
elements := lo.ToAnySlice([]int{1, 5, 1})
// []any{1, 5, 1}
```
### FromAnySlice
Returns a slice with all elements mapped to a type. Returns false in case of type conversion failure.
```go
elements, ok := lo.FromAnySlice([]any{"foobar", 42})
// []string{}, false
elements, ok := lo.FromAnySlice([]any{"foobar", "42"})
// []string{"foobar", "42"}, true
```
### Empty
Returns the [zero value](https://go.dev/ref/spec#The_zero_value).
```go
lo.Empty[int]()
// 0
lo.Empty[string]()
// ""
lo.Empty[bool]()
// false
```
### IsEmpty
Returns true if argument is a zero value.
```go
lo.IsEmpty(0)
// true
lo.IsEmpty(42)
// false
lo.IsEmpty("")
// true
lo.IsEmpty("foobar")
// false
type test struct {
foobar string
}
lo.IsEmpty(test{foobar: ""})
// true
lo.IsEmpty(test{foobar: "foobar"})
// false
```
### IsNotEmpty
Returns true if argument is a zero value.
```go
lo.IsNotEmpty(0)
// false
lo.IsNotEmpty(42)
// true
lo.IsNotEmpty("")
// false
lo.IsNotEmpty("foobar")
// true
type test struct {
foobar string
}
lo.IsNotEmpty(test{foobar: ""})
// false
lo.IsNotEmpty(test{foobar: "foobar"})
// true
```
### Coalesce
Returns the first non-empty arguments. Arguments must be comparable.
```go
result, ok := lo.Coalesce(0, 1, 2, 3)
// 1 true
result, ok := lo.Coalesce("")
// "" false
var nilStr *string
str := "foobar"
result, ok := lo.Coalesce(nil, nilStr, &str)
// &"foobar" true
```
### CoalesceOrEmpty
Returns the first non-empty arguments. Arguments must be comparable.
```go
result := lo.CoalesceOrEmpty(0, 1, 2, 3)
// 1
result := lo.CoalesceOrEmpty("")
// ""
var nilStr *string
str := "foobar"
result := lo.CoalesceOrEmpty(nil, nilStr, &str)
// &"foobar"
```
### CoalesceSlice
Returns the first non-zero slice.
```go
result, ok := lo.CoalesceSlice([]int{1, 2, 3}, []int{4, 5, 6})
// [1, 2, 3]
// true
result, ok := lo.CoalesceSlice(nil, []int{})
// []
// true
result, ok := lo.CoalesceSlice([]int(nil))
// []
// false
```
### CoalesceSliceOrEmpty
Returns the first non-zero slice.
```go
result := lo.CoalesceSliceOrEmpty([]int{1, 2, 3}, []int{4, 5, 6})
// [1, 2, 3]
result := lo.CoalesceSliceOrEmpty(nil, []int{})
// []
```
### CoalesceMap
Returns the first non-zero map.
```go
result, ok := lo.CoalesceMap(map[string]int{"1": 1, "2": 2, "3": 3}, map[string]int{"4": 4, "5": 5, "6": 6})
// {"1": 1, "2": 2, "3": 3}
// true
result, ok := lo.CoalesceMap(nil, map[string]int{})
// {}
// true
result, ok := lo.CoalesceMap(map[string]int(nil))
// {}
// false
```
### CoalesceMapOrEmpty
Returns the first non-zero map.
```go
result := lo.CoalesceMapOrEmpty(map[string]int{"1": 1, "2": 2, "3": 3}, map[string]int{"4": 4, "5": 5, "6": 6})
// {"1": 1, "2": 2, "3": 3}
result := lo.CoalesceMapOrEmpty(nil, map[string]int{})
// {}
```
### Partial
Returns new function that, when called, has its first argument set to the provided value.
```go
add := func(x, y int) int { return x + y }
f := lo.Partial(add, 5)
f(10)
// 15
f(42)
// 47
```
[[play](https://go.dev/play/p/Sy1gAQiQZ3v)]
### Partial2 -> Partial5
Returns new function that, when called, has its first argument set to the provided value.
```go
add := func(x, y, z int) int { return x + y + z }
f := lo.Partial2(add, 42)
f(10, 5)
// 57
f(42, -4)
// 80
```
[[play](https://go.dev/play/p/-xiPjy4JChJ)]
### Attempt
Invokes a function N times until it returns valid output. Returns either the caught error or nil.
When the first argument is less than `1`, the function runs until a successful response is returned.
```go
iter, err := lo.Attempt(42, func(i int) error {
if i == 5 {
return nil
}
return errors.New("failed")
})
// 6
// nil
iter, err := lo.Attempt(2, func(i int) error {
if i == 5 {
return nil
}
return errors.New("failed")
})
// 2
// error "failed"
iter, err := lo.Attempt(0, func(i int) error {
if i < 42 {
return errors.New("failed")
}
return nil
})
// 43
// nil
```
For more advanced retry strategies (delay, exponential backoff...), please take a look at [cenkalti/backoff](https://github.com/cenkalti/backoff).
[[play](https://go.dev/play/p/3ggJZ2ZKcMj)]
### AttemptWithDelay
Invokes a function N times until it returns valid output, with a pause between each call. Returns either the caught error or nil.
When the first argument is less than `1`, the function runs until a successful response is returned.
```go
iter, duration, err := lo.AttemptWithDelay(5, 2*time.Second, func(i int, duration time.Duration) error {
if i == 2 {
return nil
}
return errors.New("failed")
})
// 3
// ~ 4 seconds
// nil
```
For more advanced retry strategies (delay, exponential backoff...), please take a look at [cenkalti/backoff](https://github.com/cenkalti/backoff).
[[play](https://go.dev/play/p/tVs6CygC7m1)]
### AttemptWhile
Invokes a function N times until it returns valid output. Returns either the caught error or nil, along with a bool value to determine whether the function should be invoked again. It will terminate the invoke immediately if the second return value is false.
When the first argument is less than `1`, the function runs until a successful response is returned.
```go
count1, err1 := lo.AttemptWhile(5, func(i int) (error, bool) {
err := doMockedHTTPRequest(i)
if err != nil {
if errors.Is(err, ErrBadRequest) { // let's assume ErrBadRequest is a critical error that needs to terminate the invoke
return err, false // flag the second return value as false to terminate the invoke
}
return err, true
}
return nil, false
})
```
For more advanced retry strategies (delay, exponential backoff...), please take a look at [cenkalti/backoff](https://github.com/cenkalti/backoff).
[[play](https://go.dev/play/p/1VS7HxlYMOG)]
### AttemptWhileWithDelay
Invokes a function N times until it returns valid output, with a pause between each call. Returns either the caught error or nil, along with a bool value to determine whether the function should be invoked again. It will terminate the invoke immediately if the second return value is false.
When the first argument is less than `1`, the function runs until a successful response is returned.
```go
count1, time1, err1 := lo.AttemptWhileWithDelay(5, time.Millisecond, func(i int, d time.Duration) (error, bool) {
err := doMockedHTTPRequest(i)
if err != nil {
if errors.Is(err, ErrBadRequest) { // let's assume ErrBadRequest is a critical error that needs to terminate the invoke
return err, false // flag the second return value as false to terminate the invoke
}
return err, true
}
return nil, false
})
```
For more advanced retry strategies (delay, exponential backoff...), please take a look at [cenkalti/backoff](https://github.com/cenkalti/backoff).
[[play](https://go.dev/play/p/mhufUjJfLEF)]
### Debounce
`NewDebounce` creates a debounced instance that delays invoking functions given until after wait milliseconds have elapsed, until `cancel` is called.
```go
f := func() {
println("Called once after 100ms when debounce stopped invoking!")
}
debounce, cancel := lo.NewDebounce(100 * time.Millisecond, f)
for j := 0; j < 10; j++ {
debounce()
}
time.Sleep(1 * time.Second)
cancel()
```
[[play](https://go.dev/play/p/mz32VMK2nqe)]
### DebounceBy
`NewDebounceBy` creates a debounced instance for each distinct key, that delays invoking functions given until after wait milliseconds have elapsed, until `cancel` is called.
```go
f := func(key string, count int) {
println(key + ": Called once after 100ms when debounce stopped invoking!")
}
debounce, cancel := lo.NewDebounceBy(100 * time.Millisecond, f)
for j := 0; j < 10; j++ {
debounce("first key")
debounce("second key")
}
time.Sleep(1 * time.Second)
cancel("first key")
cancel("second key")
```
[[play](https://go.dev/play/p/d3Vpt6pxhY8)]
### Throttle
Creates a throttled instance that invokes given functions only once in every interval.
This returns 2 functions, First one is throttled function and Second one is a function to reset interval.
```go
f := func() {
println("Called once in every 100ms")
}
throttle, reset := lo.NewThrottle(100 * time.Millisecond, f)
for j := 0; j < 10; j++ {
throttle()
time.Sleep(30 * time.Millisecond)
}
reset()
throttle()
```
`NewThrottleWithCount` is NewThrottle with count limit, throttled function will be invoked count times in every interval.
```go
f := func() {
println("Called three times in every 100ms")
}
throttle, reset := lo.NewThrottleWithCount(100 * time.Millisecond, f)
for j := 0; j < 10; j++ {
throttle()
time.Sleep(30 * time.Millisecond)
}
reset()
throttle()
```
`NewThrottleBy` and `NewThrottleByWithCount` are NewThrottle with sharding key, throttled function will be invoked count times in every interval.
```go
f := func(key string) {
println(key, "Called three times in every 100ms")
}
throttle, reset := lo.NewThrottleByWithCount(100 * time.Millisecond, f)
for j := 0; j < 10; j++ {
throttle("foo")
time.Sleep(30 * time.Millisecond)
}
reset()
throttle()
```
### Synchronize
Wraps the underlying callback in a mutex. It receives an optional mutex.
```go
s := lo.Synchronize()
for i := 0; i < 10; i++ {
go s.Do(func () {
println("will be called sequentially")
})
}
```
It is equivalent to:
```go
mu := sync.Mutex{}
func foobar() {
mu.Lock()
defer mu.Unlock()
// ...
}
```
### Async
Executes a function in a goroutine and returns the result in a channel.
```go
ch := lo.Async(func() error { time.Sleep(10 * time.Second); return nil })
// chan error (nil)
```
### Async{0->6}
Executes a function in a goroutine and returns the result in a channel.
For functions with multiple return values, the results will be returned as a tuple inside the channel.
For functions without return, struct{} will be returned in the channel.
```go
ch := lo.Async0(func() { time.Sleep(10 * time.Second) })
// chan struct{}
ch := lo.Async1(func() int {
time.Sleep(10 * time.Second);
return 42
})
// chan int (42)
ch := lo.Async2(func() (int, string) {
time.Sleep(10 * time.Second);
return 42, "Hello"
})
// chan lo.Tuple2[int, string] ({42, "Hello"})
```
### Transaction
Implements a Saga pattern.
```go
transaction := NewTransaction().
Then(
func(state int) (int, error) {
fmt.Println("step 1")
return state + 10, nil
},
func(state int) int {
fmt.Println("rollback 1")
return state - 10
},
).
Then(
func(state int) (int, error) {
fmt.Println("step 2")
return state + 15, nil
},
func(state int) int {
fmt.Println("rollback 2")
return state - 15
},
).
Then(
func(state int) (int, error) {
fmt.Println("step 3")
if true {
return state, errors.New("error")
}
return state + 42, nil
},
func(state int) int {
fmt.Println("rollback 3")
return state - 42
},
)
_, _ = transaction.Process(-5)
// Output:
// step 1
// step 2
// step 3
// rollback 2
// rollback 1
```
### WaitFor
Runs periodically until a condition is validated.
```go
alwaysTrue := func(i int) bool { return true }
alwaysFalse := func(i int) bool { return false }
laterTrue := func(i int) bool {
return i > 5
}
iterations, duration, ok := lo.WaitFor(alwaysTrue, 10*time.Millisecond, 2 * time.Millisecond)
// 1
// 1ms
// true
iterations, duration, ok := lo.WaitFor(alwaysFalse, 10*time.Millisecond, time.Millisecond)
// 10
// 10ms
// false
iterations, duration, ok := lo.WaitFor(laterTrue, 10*time.Millisecond, time.Millisecond)
// 7
// 7ms
// true
iterations, duration, ok := lo.WaitFor(laterTrue, 10*time.Millisecond, 5*time.Millisecond)
// 2
// 10ms
// false
```
[[play](https://go.dev/play/p/t_wTDmubbK3)]
### WaitForWithContext
Runs periodically until a condition is validated or context is invalid.
The condition receives also the context, so it can invalidate the process in the condition checker
```go
ctx := context.Background()
alwaysTrue := func(_ context.Context, i int) bool { return true }
alwaysFalse := func(_ context.Context, i int) bool { return false }
laterTrue := func(_ context.Context, i int) bool {
return i >= 5
}
iterations, duration, ok := lo.WaitForWithContext(ctx, alwaysTrue, 10*time.Millisecond, 2 * time.Millisecond)
// 1
// 1ms
// true
iterations, duration, ok := lo.WaitForWithContext(ctx, alwaysFalse, 10*time.Millisecond, time.Millisecond)
// 10
// 10ms
// false
iterations, duration, ok := lo.WaitForWithContext(ctx, laterTrue, 10*time.Millisecond, time.Millisecond)
// 5
// 5ms
// true
iterations, duration, ok := lo.WaitForWithContext(ctx, laterTrue, 10*time.Millisecond, 5*time.Millisecond)
// 2
// 10ms
// false
expiringCtx, cancel := context.WithTimeout(ctx, 5*time.Millisecond)
iterations, duration, ok := lo.WaitForWithContext(expiringCtx, alwaysFalse, 100*time.Millisecond, time.Millisecond)
// 5
// 5.1ms
// false
```
[[play](https://go.dev/play/p/t_wTDmubbK3)]
### Validate
Helper function that creates an error when a condition is not met.
```go
slice := []string{"a"}
val := lo.Validate(len(slice) == 0, "Slice should be empty but contains %v", slice)
// error("Slice should be empty but contains [a]")
slice := []string{}
val := lo.Validate(len(slice) == 0, "Slice should be empty but contains %v", slice)
// nil
```
[[play](https://go.dev/play/p/vPyh51XpCBt)]
### Must
Wraps a function call and panics if second argument is `error` or `false`, returns the value otherwise.
```go
val := lo.Must(time.Parse("2006-01-02", "2022-01-15"))
// 2022-01-15
val := lo.Must(time.Parse("2006-01-02", "bad-value"))
// panics
```
[[play](https://go.dev/play/p/TMoWrRp3DyC)]
### Must{0->6}
Must\* has the same behavior as Must but returns multiple values.
```go
func example0() (error)
func example1() (int, error)
func example2() (int, string, error)
func example3() (int, string, time.Date, error)
func example4() (int, string, time.Date, bool, error)
func example5() (int, string, time.Date, bool, float64, error)
func example6() (int, string, time.Date, bool, float64, byte, error)
lo.Must0(example0())
val1 := lo.Must1(example1()) // alias to Must
val1, val2 := lo.Must2(example2())
val1, val2, val3 := lo.Must3(example3())
val1, val2, val3, val4 := lo.Must4(example4())
val1, val2, val3, val4, val5 := lo.Must5(example5())
val1, val2, val3, val4, val5, val6 := lo.Must6(example6())
```
You can wrap functions like `func (...) (..., ok bool)`.
```go
// math.Signbit(float64) bool
lo.Must0(math.Signbit(v))
// bytes.Cut([]byte,[]byte) ([]byte, []byte, bool)
before, after := lo.Must2(bytes.Cut(s, sep))
```
You can give context to the panic message by adding some printf-like arguments.
```go
val, ok := lo.Find(myString, func(i string) bool {
return i == requiredChar
})
lo.Must0(ok, "'%s' must always contain '%s'", myString, requiredChar)
list := []int{0, 1, 2}
item := 5
lo.Must0(lo.Contains(list, item), "'%s' must always contain '%s'", list, item)
...
```
[[play](https://go.dev/play/p/TMoWrRp3DyC)]
### Try
Calls the function and returns false in case of error and panic.
```go
ok := lo.Try(func() error {
panic("error")
return nil
})
// false
ok := lo.Try(func() error {
return nil
})
// true
ok := lo.Try(func() error {
return errors.New("error")
})
// false
```
[[play](https://go.dev/play/p/mTyyWUvn9u4)]
### Try{0->6}
The same behavior as `Try`, but the callback returns 2 variables.
```go
ok := lo.Try2(func() (string, error) {
panic("error")
return "", nil
})
// false
```
[[play](https://go.dev/play/p/mTyyWUvn9u4)]
### TryOr
Calls the function and return a default value in case of error and on panic.
```go
str, ok := lo.TryOr(func() (string, error) {
panic("error")
return "hello", nil
}, "world")
// world
// false
str, ok := lo.TryOr(func() error {
return "hello", nil
}, "world")
// hello
// true
str, ok := lo.TryOr(func() error {
return "hello", errors.New("error")
}, "world")
// world
// false
```
[[play](https://go.dev/play/p/B4F7Wg2Zh9X)]
### TryOr{0->6}
The same behavior as `TryOr`, but the callback returns `X` variables.
```go
str, nbr, ok := lo.TryOr2(func() (string, int, error) {
panic("error")
return "hello", 42, nil
}, "world", 21)
// world
// 21
// false
```
[[play](https://go.dev/play/p/B4F7Wg2Zh9X)]
### TryWithErrorValue
The same behavior as `Try`, but also returns the value passed to panic.
```go
err, ok := lo.TryWithErrorValue(func() error {
panic("error")
return nil
})
// "error", false
```
[[play](https://go.dev/play/p/Kc7afQIT2Fs)]
### TryCatch
The same behavior as `Try`, but calls the catch function in case of error.
```go
caught := false
ok := lo.TryCatch(func() error {
panic("error")
return nil
}, func() {
caught = true
})
// false
// caught == true
```
[[play](https://go.dev/play/p/PnOON-EqBiU)]
### TryCatchWithErrorValue
The same behavior as `TryWithErrorValue`, but calls the catch function in case of error.
```go
caught := false
ok := lo.TryCatchWithErrorValue(func() error {
panic("error")
return nil
}, func(val any) {
caught = val == "error"
})
// false
// caught == true
```
[[play](https://go.dev/play/p/8Pc9gwX_GZO)]
### ErrorsAs
A shortcut for:
```go
err := doSomething()
var rateLimitErr *RateLimitError
if ok := errors.As(err, &rateLimitErr); ok {
// retry later
}
```
single line `lo` helper:
```go
err := doSomething()
if rateLimitErr, ok := lo.ErrorsAs[*RateLimitError](err); ok {
// retry later
}
```
[[play](https://go.dev/play/p/8wk5rH8UfrE)]
### Assert
Does nothing when the condition is `true`, otherwise it panics with an optional message.
Think twice before using it, given that [Go intentionally omits assertions from its standard library](https://go.dev/doc/faq#assertions).
```go
age := getUserAge()
lo.Assert(age >= 15)
```
```go
age := getUserAge()
lo.Assert(age >= 15, "user age must be >= 15")
```
[[play](https://go.dev/play/p/Xv8LLKBMNwI)]
### Assertf
Like `Assert`, but with `fmt.Printf`-like formatting.
Think twice before using it, given that [Go intentionally omits assertions from its standard library](https://go.dev/doc/faq#assertions).
```go
age := getUserAge()
lo.Assertf(age >= 15, "user age must be >= 15, got %d", age)
```
[[play](https://go.dev/play/p/TVPEmVcyrdY)]
## 🛩 Benchmark
We executed a simple benchmark with a dead-simple `lo.Map` loop:
See the full implementation [here](./map_benchmark_test.go).
```go
_ = lo.Map[int64](arr, func(x int64, i int) string {
return strconv.FormatInt(x, 10)
})
```
**Result:**
Here is a comparison between `lo.Map`, `lop.Map`, `go-funk` library and a simple Go `for` loop.
```shell
$ go test -benchmem -bench ./...
goos: linux
goarch: amd64
pkg: github.com/samber/lo
cpu: Intel(R) Core(TM) i5-7267U CPU @ 3.10GHz
cpu: Intel(R) Core(TM) i7 CPU 920 @ 2.67GHz
BenchmarkMap/lo.Map-8 8 132728237 ns/op 39998945 B/op 1000002 allocs/op
BenchmarkMap/lop.Map-8 2 503947830 ns/op 119999956 B/op 3000007 allocs/op
BenchmarkMap/reflect-8 2 826400560 ns/op 170326512 B/op 4000042 allocs/op
BenchmarkMap/for-8 9 126252954 ns/op 39998674 B/op 1000001 allocs/op
PASS
ok github.com/samber/lo 6.657s
```
- `lo.Map` is way faster (x7) than `go-funk`, a reflection-based Map implementation.
- `lo.Map` has the same allocation profile as `for`.
- `lo.Map` is 4% slower than `for`.
- `lop.Map` is slower than `lo.Map` because it implies more memory allocation and locks. `lop.Map` is useful for long-running callbacks, such as i/o bound processing.
- `for` beats other implementations for memory and CPU.
## 🤝 Contributing
- Ping me on Twitter [@samuelberthe](https://twitter.com/samuelberthe) (DMs, mentions, whatever :))
- Fork the [project](https://github.com/samber/lo)
- Fix [open issues](https://github.com/samber/lo/issues) or request new features
Don't hesitate ;)
Helper naming: helpers must be self-explanatory and respect standards (other languages, libraries...). Feel free to suggest many names in your contributions.
```bash
# Install some dev dependencies
make tools
# Run tests
make test
# or
make watch-test
```
## 👤 Contributors

## 💫 Show your support
Give a ⭐️ if this project helped you!
[](https://github.com/sponsors/samber)
## 📝 License
Copyright © 2022 [Samuel Berthe](https://github.com/samber).
This project is under [MIT](./LICENSE) license.
================================================
FILE: benchmark/CLAUDE.md
================================================
# Benchmark Guidelines
## File Organization
Benchmark files follow the naming convention:
```
benchmark/{package}_{category}_bench_test.go
```
- **package**: `core`, `it`, `mutable`, `parallel`
- **category**: `slice`, `map`, `find`, `intersect`, `math`, `string`, `type_manipulation`, `condition`, `tuples`
Shared data generators live in `helpers_test.go` (and `it_helpers_test.go` for `go1.23` iter helpers).
## Performance PRs
Every performance improvement PR **must** include a `benchstat` comparison in the PR description. Without before/after numbers, the PR will not be merged.
### How to produce a benchstat report
1. Check out `master` and run the "before" benchmarks:
```bash
git stash && git switch master
go test ./benchmark/... -bench=BenchmarkXxx -benchmem -count=6 -cpu=1 | tee /tmp/before.txt
```
2. Switch to your branch and run the "after" benchmarks:
```bash
git switch my-branch && git stash pop
go test ./benchmark/... -bench=BenchmarkXxx -benchmem -count=6 -cpu=1 | tee /tmp/after.txt
```
3. Compare with `benchstat`:
```bash
benchstat /tmp/before.txt /tmp/after.txt
```
4. Paste the full `benchstat` output in the PR description.
### What to include in the PR description
- The optimization technique (pre-allocation, direct indexing, value receivers, etc.)
- The `benchstat` table showing time/op, allocs/op, and bytes/op deltas
- An explanation of **why** the change is faster, not just **what** changed
### When NOT to submit a performance PR
- If `benchstat` shows no statistically significant improvement (p >= 0.05)
- If the improvement is < 5% and adds code complexity
- If the change regresses other benchmarks — always run the full suite, not just the targeted benchmark
## Adding New Benchmarks
When adding a new helper function to the library, add a corresponding benchmark in the appropriate `{package}_{category}_bench_test.go` file. Use the standard parametric pattern:
```go
func BenchmarkMyFunc(b *testing.B) {
for _, n := range lengths {
data := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.MyFunc(data, ...)
}
})
}
}
```
Use shared generators from `helpers_test.go` — do not create local generator functions.
================================================
FILE: benchmark/core_condition_bench_test.go
================================================
package benchmark
import (
"testing"
"github.com/samber/lo"
)
func BenchmarkIf(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.If(i%2 == 0, "even").Else("odd")
}
}
func BenchmarkIfElseIf(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.If(i%3 == 0, "fizz").
ElseIf(i%3 == 1, "buzz").
Else("none")
}
}
func BenchmarkIfElseIfChain(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.If(i%5 == 0, "a").
ElseIf(i%5 == 1, "b").
ElseIf(i%5 == 2, "c").
ElseIf(i%5 == 3, "d").
Else("e")
}
}
func BenchmarkSwitch(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Switch[int, string](i%3).
Case(0, "zero").
Case(1, "one").
Default("other")
}
}
func BenchmarkSwitchChain(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Switch[int, string](i%5).
Case(0, "zero").
Case(1, "one").
Case(2, "two").
Case(3, "three").
Default("other")
}
}
================================================
FILE: benchmark/core_find_bench_test.go
================================================
package benchmark
import (
"strconv"
"testing"
"github.com/samber/lo"
)
func BenchmarkIndexOf(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
target := ints[n-1] // worst case: last element
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.IndexOf(ints, target)
}
})
}
}
func BenchmarkLastIndexOf(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
target := ints[0]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.LastIndexOf(ints, target)
}
})
}
}
func BenchmarkHasPrefix(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
prefix := ints[:n/10+1]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.HasPrefix(ints, prefix)
}
})
}
}
func BenchmarkHasSuffix(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
suffix := ints[n-n/10-1:]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.HasSuffix(ints, suffix)
}
})
}
}
func BenchmarkFind(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
target := ints[n-1]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.Find(ints, func(v int) bool { return v == target })
}
})
}
}
func BenchmarkFindIndexOf(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
target := ints[n-1]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _, _ = lo.FindIndexOf(ints, func(v int) bool { return v == target })
}
})
}
}
func BenchmarkFindLastIndexOf(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
target := ints[0]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _, _ = lo.FindLastIndexOf(ints, func(v int) bool { return v == target })
}
})
}
}
func BenchmarkFindOrElse(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FindOrElse(ints, -1, func(v int) bool { return v == -999 })
}
})
}
}
func BenchmarkFindKey(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.FindKey(m, n/2)
}
})
}
}
func BenchmarkFindKeyBy(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.FindKeyBy(m, func(_ string, v int) bool { return v == n/2 })
}
})
}
}
func BenchmarkFindUniques(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FindUniques(ints)
}
})
}
}
func BenchmarkFindUniquesBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FindUniquesBy(ints, func(v int) int { return v % 50 })
}
})
}
}
func BenchmarkFindDuplicates(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FindDuplicates(ints)
}
})
}
}
func BenchmarkFindDuplicatesBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FindDuplicatesBy(ints, func(v int) int { return v % 50 })
}
})
}
}
func BenchmarkMin(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Min(ints)
}
})
}
}
func BenchmarkMinIndex(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.MinIndex(ints)
}
})
}
}
func BenchmarkMinBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.MinBy(ints, func(a, b int) bool { return a < b })
}
})
}
}
func BenchmarkMinIndexBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.MinIndexBy(ints, func(a, b int) bool { return a < b })
}
})
}
}
func BenchmarkMax(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Max(ints)
}
})
}
}
func BenchmarkMaxIndex(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.MaxIndex(ints)
}
})
}
}
func BenchmarkMaxBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.MaxBy(ints, func(a, b int) bool { return a > b })
}
})
}
}
func BenchmarkMaxIndexBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.MaxIndexBy(ints, func(a, b int) bool { return a > b })
}
})
}
}
func BenchmarkFirst(b *testing.B) {
ints := genSliceInt(100)
for i := 0; i < b.N; i++ {
_, _ = lo.First(ints)
}
}
func BenchmarkFirstOrEmpty(b *testing.B) {
ints := genSliceInt(100)
for i := 0; i < b.N; i++ {
_ = lo.FirstOrEmpty(ints)
}
}
func BenchmarkLast(b *testing.B) {
ints := genSliceInt(100)
for i := 0; i < b.N; i++ {
_, _ = lo.Last(ints)
}
}
func BenchmarkLastOrEmpty(b *testing.B) {
ints := genSliceInt(100)
for i := 0; i < b.N; i++ {
_ = lo.LastOrEmpty(ints)
}
}
func BenchmarkNth(b *testing.B) {
ints := genSliceInt(100)
for i := 0; i < b.N; i++ {
_, _ = lo.Nth(ints, 50)
}
}
func BenchmarkSample(b *testing.B) {
ints := genSliceInt(100)
for i := 0; i < b.N; i++ {
_ = lo.Sample(ints)
}
}
func BenchmarkSamples(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Samples(ints, n/4)
}
})
}
}
================================================
FILE: benchmark/core_intersect_bench_test.go
================================================
package benchmark
import (
"strconv"
"testing"
"github.com/samber/lo"
)
func BenchmarkContains(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
target := ints[n-1]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Contains(ints, target)
}
})
}
}
func BenchmarkContainsBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
target := ints[n-1]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ContainsBy(ints, func(v int) bool { return v == target })
}
})
}
}
func BenchmarkEvery(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
subset := ints[:n/2]
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Every(ints, subset)
}
})
}
}
func BenchmarkEveryBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.EveryBy(ints, func(v int) bool { return v >= 0 })
}
})
}
}
func BenchmarkSome(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
subset := []int{ints[n-1]}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Some(ints, subset)
}
})
}
}
func BenchmarkSomeBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.SomeBy(ints, func(v int) bool { return v < 0 })
}
})
}
}
func BenchmarkNone(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
subset := []int{-1, -2, -3}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.None(ints, subset)
}
})
}
}
func BenchmarkNoneBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.NoneBy(ints, func(v int) bool { return v < 0 })
}
})
}
}
func BenchmarkIntersect(b *testing.B) {
for _, n := range lengths {
a := genSliceInt(n)
c := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Intersect(a, c)
}
})
}
}
func BenchmarkIntersectBy(b *testing.B) {
for _, n := range lengths {
a := genSliceInt(n)
c := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.IntersectBy(func(v int) int { return v }, a, c)
}
})
}
}
func BenchmarkUnion(b *testing.B) {
for _, n := range lengths {
a := genSliceInt(n)
c := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Union(a, c)
}
})
}
}
func BenchmarkWithout(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Without(ints, 1, 2, 3, 4, 5)
}
})
}
}
func BenchmarkWithoutBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.WithoutBy(ints, func(v int) int { return v % 100 }, 1, 2, 3, 4, 5)
}
})
}
}
func BenchmarkWithoutEmpty(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
// sprinkle some zeroes
for j := 0; j < n/10; j++ {
ints[j*10] = 0
}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.WithoutEmpty(ints) //nolint:staticcheck
}
})
}
}
func BenchmarkWithoutNth(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.WithoutNth(ints, 0, n/2, n-1)
}
})
}
}
func BenchmarkElementsMatch(b *testing.B) {
for _, n := range lengths {
a := genSliceInt(n)
c := make([]int, n)
copy(c, a)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ElementsMatch(a, c)
}
})
}
}
================================================
FILE: benchmark/core_map_bench_test.go
================================================
package benchmark
import (
"strconv"
"testing"
"github.com/samber/lo"
lop "github.com/samber/lo/parallel"
"github.com/thoas/go-funk"
)
func BenchmarkKeys(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Keys(m)
}
})
}
}
func BenchmarkUniqKeys(b *testing.B) {
for _, n := range lengths {
m1 := genMap(n)
m2 := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.UniqKeys(m1, m2)
}
})
}
}
func BenchmarkHasKey(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
key := strconv.Itoa(n / 2)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.HasKey(m, key)
}
})
}
}
func BenchmarkValues(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Values(m)
}
})
}
}
func BenchmarkUniqValues(b *testing.B) {
m := []map[int64]int64{
mapGenerator(1000),
mapGenerator(1000),
mapGenerator(1000),
}
b.Run("lo.UniqValues", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = lo.UniqValues(m...)
}
})
}
func BenchmarkValueOr(b *testing.B) {
m := genMap(100)
b.Run("hit", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ValueOr(m, "50", -1)
}
})
b.Run("miss", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ValueOr(m, "missing", -1)
}
})
}
func BenchmarkPickBy(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.PickBy(m, func(_ string, v int) bool { return v%2 == 0 })
}
})
}
}
func BenchmarkPickByKeys(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
keys := make([]string, n/2)
for i := range keys {
keys[i] = strconv.Itoa(i * 2)
}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.PickByKeys(m, keys)
}
})
}
}
func BenchmarkPickByValues(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
vals := make([]int, n/2)
for i := range vals {
vals[i] = i * 2
}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.PickByValues(m, vals)
}
})
}
}
func BenchmarkOmitBy(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.OmitBy(m, func(_ string, v int) bool { return v%2 == 0 })
}
})
}
}
func BenchmarkOmitByKeys(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
keys := make([]string, n/4)
for i := range keys {
keys[i] = strconv.Itoa(i)
}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.OmitByKeys(m, keys)
}
})
}
}
func BenchmarkOmitByValues(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
vals := make([]int, n/4)
for i := range vals {
vals[i] = i
}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.OmitByValues(m, vals)
}
})
}
}
func BenchmarkEntries(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Entries(m)
}
})
}
}
func BenchmarkFromEntries(b *testing.B) {
for _, n := range lengths {
entries := make([]lo.Entry[string, int], n)
for i := 0; i < n; i++ {
entries[i] = lo.Entry[string, int]{Key: strconv.Itoa(i), Value: i}
}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FromEntries(entries)
}
})
}
}
func BenchmarkInvert(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Invert(m)
}
})
}
}
func BenchmarkAssign(b *testing.B) {
for _, n := range lengths {
m1 := genMap(n)
m2 := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Assign(m1, m2)
}
})
}
}
func BenchmarkChunkEntries(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ChunkEntries(m, 5)
}
})
}
}
func BenchmarkMapKeys(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.MapKeys(m, func(_ int, k string) string { return k + "_x" })
}
})
}
}
func BenchmarkMapValues(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.MapValues(m, func(v int, _ string) int { return v * 2 })
}
})
}
}
func BenchmarkMapEntries(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.MapEntries(m, func(k string, v int) (string, int) { return k, v * 2 })
}
})
}
}
func BenchmarkMapToSlice(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.MapToSlice(m, func(k string, v int) string { return k + "=" + strconv.Itoa(v) })
}
})
}
}
func BenchmarkFilterMapToSlice(b *testing.B) {
for _, n := range lengths {
m := genMap(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FilterMapToSlice(m, func(k string, v int) (string, bool) { return k, v%2 == 0 })
}
})
}
}
func BenchmarkFilterKeys(b *testing.B) {
m := mapGenerator(1000)
b.Run("lo.FilterKeys", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = lo.FilterKeys(m, func(k, v int64) bool { return k%2 == 0 })
}
})
}
func BenchmarkFilterValues(b *testing.B) {
m := mapGenerator(1000)
b.Run("lo.FilterValues", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = lo.FilterValues(m, func(k, v int64) bool { return v%2 == 0 })
}
})
}
// ---------------------------------------------------------------------------
// Comparison benchmarks (lo vs lop vs go-funk vs manual loop)
// ---------------------------------------------------------------------------
func BenchmarkMapComparison(b *testing.B) {
arr := sliceGenerator(1000000)
b.Run("lo.Map", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = lo.Map(arr, func(x int64, i int) string {
return strconv.FormatInt(x, 10)
})
}
})
b.Run("lop.Map", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = lop.Map(arr, func(x int64, i int) string {
return strconv.FormatInt(x, 10)
})
}
})
b.Run("reflect", func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = funk.Map(arr, func(x int64) string {
return strconv.FormatInt(x, 10)
})
}
})
b.Run("for", func(b *testing.B) {
for n := 0; n < b.N; n++ {
results := make([]string, len(arr))
for i, item := range arr {
result := strconv.FormatInt(item, 10)
results[i] = result
}
}
})
}
================================================
FILE: benchmark/core_math_bench_test.go
================================================
package benchmark
import (
"strconv"
"testing"
"github.com/samber/lo"
)
func BenchmarkRange(b *testing.B) {
for _, n := range lengths {
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Range(n)
}
})
}
}
func BenchmarkRangeFrom(b *testing.B) {
for _, n := range lengths {
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.RangeFrom(0, n)
}
})
}
}
func BenchmarkRangeWithSteps(b *testing.B) {
for _, n := range lengths {
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.RangeWithSteps(0, n, 1)
}
})
}
}
func BenchmarkClamp(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Clamp(15, 0, 10)
}
}
func BenchmarkSum(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Sum(ints)
}
})
}
}
func BenchmarkSumBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.SumBy(ints, func(v int) int { return v })
}
})
}
}
func BenchmarkProduct(b *testing.B) {
for _, n := range lengths {
floats := make([]float64, n)
for j := range floats {
floats[j] = 1.0001
}
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Product(floats)
}
})
}
}
func BenchmarkProductBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ProductBy(ints, func(v int) float64 { return float64(v) * 0.001 })
}
})
}
}
func BenchmarkMean(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Mean(ints)
}
})
}
}
func BenchmarkMeanBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.MeanBy(ints, func(v int) int { return v })
}
})
}
}
func BenchmarkMode(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Mode(ints)
}
})
}
}
================================================
FILE: benchmark/core_slice_bench_test.go
================================================
package benchmark
import (
"fmt"
"sort"
"strconv"
"testing"
"github.com/samber/lo"
)
func BenchmarkChunk(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Chunk(strs, 5)
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Chunk(ints, 5)
}
})
}
}
func BenchmarkFlatten(b *testing.B) {
for _, n := range lengths {
ints := make([][]int, 0, n)
for i := 0; i < n; i++ {
ints = append(ints, genSliceInt(n))
}
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Flatten(ints)
}
})
}
for _, n := range lengths {
strs := make([][]string, 0, n)
for i := 0; i < n; i++ {
strs = append(strs, genSliceString(n))
}
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Flatten(strs)
}
})
}
}
func BenchmarkDrop(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Drop(strs, n/4)
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Drop(ints, n/4)
}
})
}
}
func BenchmarkDropRight(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropRight(strs, n/4)
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropRight(ints, n/4)
}
})
}
}
func BenchmarkDropWhile(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropWhile(strs, func(v string) bool { return len(v) < 4 })
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropWhile(ints, func(v int) bool { return i < 10_000 })
}
})
}
}
func BenchmarkDropRightWhile(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropRightWhile(strs, func(v string) bool { return len(v) < 4 })
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropRightWhile(ints, func(v int) bool { return i < 10_000 })
}
})
}
}
func BenchmarkDropByIndex(b *testing.B) {
for _, n := range lengths {
for _, indexes := range [][]int{
{0},
{0, n / 2, n / 4, n - 1},
lo.Range(n),
} {
name := fmt.Sprintf("size_%d/indexes_%d/", n, len(indexes))
strs := genSliceString(n)
b.Run(name+"strings", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropByIndex(strs, indexes...)
}
})
ints := genSliceInt(n)
b.Run(name+"ints", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropByIndex(ints, indexes...)
}
})
heavy := genSliceHeavy(n)
b.Run(name+"heavy", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.DropByIndex(heavy, indexes...)
}
})
}
}
}
func BenchmarkReplace(b *testing.B) {
lengths := []int{1_000, 10_000, 100_000}
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Replace(strs, strs[n/4], "123123", 10)
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Replace(ints, ints[n/4], 123123, 10)
}
})
}
}
func BenchmarkReject(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Reject(strs, func(v string, _ int) bool { return len(v) < 3 })
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Reject(ints, func(v, _ int) bool { return v < 50000 })
}
})
}
}
func BenchmarkRejectErr(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.RejectErr(strs, func(v string, _ int) (bool, error) { return len(v) < 3, nil })
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.RejectErr(ints, func(v, _ int) (bool, error) { return v < 50000, nil })
}
})
}
}
func BenchmarkRejectMap(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.RejectMap(strs, func(v string, _ int) (string, bool) { return v, len(v) < 3 })
}
})
}
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.RejectMap(ints, func(v, _ int) (int, bool) { return v, v < 50000 })
}
})
}
}
func BenchmarkUniqMap(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.UniqMap(ints, func(v, _ int) int { return v % 50 })
}
})
}
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.UniqMap(strs, func(v string, _ int) string { return v })
}
})
}
}
func BenchmarkRepeatBy(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.RepeatBy(n, func(index int) int { return index * 2 })
}
})
}
for _, n := range lengths {
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.RepeatBy(n, strconv.Itoa)
}
})
}
}
func BenchmarkFill(b *testing.B) {
for _, n := range lengths {
collection := make([]clonableString, n)
for i := range collection {
collection[i] = clonableString{strconv.Itoa(i)}
}
b.Run(fmt.Sprintf("size_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Fill(collection, clonableString{"hello"})
}
})
}
}
func BenchmarkRepeat(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("size_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Repeat(n, clonableString{"hello"})
}
})
}
}
func BenchmarkFilter(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Filter(ints, func(v, _ int) bool { return v%2 == 0 })
}
})
}
}
func BenchmarkFilterErr(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.FilterErr(ints, func(v, _ int) (bool, error) { return v%2 == 0, nil })
}
})
}
}
func BenchmarkSliceMap(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Map(ints, func(v, _ int) int { return v * 2 })
}
})
}
}
func BenchmarkMapErr(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.MapErr(ints, func(v, _ int) (int, error) { return v * 2, nil })
}
})
}
}
func BenchmarkFilterMap(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FilterMap(ints, func(v, _ int) (int, bool) { return v * 2, v%2 == 0 })
}
})
}
}
func BenchmarkFlatMap(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FlatMap(ints, func(v, _ int) []int { return []int{v, v + 1} })
}
})
}
}
func BenchmarkReduce(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Reduce(ints, func(agg, item, _ int) int { return agg + item }, 0)
}
})
}
}
func BenchmarkReduceRight(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ReduceRight(ints, func(agg, item, _ int) int { return agg + item }, 0)
}
})
}
}
func BenchmarkForEach(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
lo.ForEach(ints, func(_, _ int) {})
}
})
}
}
func BenchmarkForEachWhile(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
lo.ForEachWhile(ints, func(_, _ int) bool { return true })
}
})
}
}
func BenchmarkTimes(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Times(n, func(i int) int { return i })
}
})
}
}
func BenchmarkUniq(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Uniq(ints)
}
})
}
}
func BenchmarkUniqBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.UniqBy(ints, func(v int) int { return v % 100 })
}
})
}
}
func BenchmarkGroupBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.GroupBy(ints, func(v int) int { return v % 10 })
}
})
}
}
func BenchmarkGroupByMap(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.GroupByMap(ints, func(v int) (int, string) { return v % 10, strconv.Itoa(v) })
}
})
}
}
func BenchmarkPartitionBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.PartitionBy(ints, func(v int) int { return v % 5 })
}
})
}
}
func BenchmarkConcat(b *testing.B) {
for _, n := range lengths {
a := genSliceInt(n)
c := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Concat(a, c)
}
})
}
}
func BenchmarkWindow(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Window(ints, 5)
}
})
}
}
func BenchmarkSliding(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Sliding(ints, 5, 2)
}
})
}
}
func BenchmarkInterleave(b *testing.B) {
for _, n := range lengths {
a := genSliceInt(n)
c := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Interleave(a, c)
}
})
}
}
func BenchmarkShuffle(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Shuffle(ints) //nolint:staticcheck
}
})
}
}
func BenchmarkReverse(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Reverse(ints) //nolint:staticcheck
}
})
}
}
func BenchmarkRepeatByErr(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.RepeatByErr(n, func(index int) (int, error) { return index * 2, nil })
}
})
}
}
func BenchmarkKeyBy(b *testing.B) {
for _, n := range lengths {
strs := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.KeyBy(strs, func(v string) string { return v })
}
})
}
}
func BenchmarkAssociate(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Associate(ints, func(v int) (int, string) { return v, strconv.Itoa(v) })
}
})
}
}
func BenchmarkSliceToMap(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.SliceToMap(ints, func(v int) (int, string) { return v, strconv.Itoa(v) })
}
})
}
}
func BenchmarkFilterSliceToMap(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FilterSliceToMap(ints, func(v int) (int, string, bool) { return v, strconv.Itoa(v), v%2 == 0 })
}
})
}
}
func BenchmarkKeyify(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Keyify(ints)
}
})
}
}
func BenchmarkTake(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Take(ints, n/4)
}
})
}
}
func BenchmarkTakeWhile(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.TakeWhile(ints, func(v int) bool { return v < 50000 })
}
})
}
}
func BenchmarkTakeFilter(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.TakeFilter(ints, 5, func(v, _ int) bool { return v%2 == 0 })
}
})
}
}
func BenchmarkFilterReject(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.FilterReject(ints, func(v, _ int) bool { return v%2 == 0 })
}
})
}
}
func BenchmarkCount(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Count(ints, 42)
}
})
}
}
func BenchmarkCountBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.CountBy(ints, func(v int) bool { return v%2 == 0 })
}
})
}
}
func BenchmarkCountValues(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.CountValues(ints)
}
})
}
}
func BenchmarkCountValuesBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.CountValuesBy(ints, func(v int) int { return v % 100 })
}
})
}
}
func BenchmarkSubset(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Subset(ints, n/4, uint(n/2))
}
})
}
}
func BenchmarkSlice(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Slice(ints, n/4, n*3/4)
}
})
}
}
func BenchmarkReplaceAll(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ReplaceAll(ints, ints[n/4], 123123)
}
})
}
}
func BenchmarkClone(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Clone(ints)
}
})
}
}
func BenchmarkCompact(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Compact(ints)
}
})
}
}
func BenchmarkIsSorted(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.IsSorted(ints)
}
})
}
}
func BenchmarkIsSortedBy(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.IsSortedBy(ints, func(v int) int { return v })
}
})
}
}
func BenchmarkIsSortedBySorted(b *testing.B) {
for _, n := range lengths {
data := genSliceInt(n)
sort.Ints(data)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
lo.IsSortedBy(data, func(v int) int { return v })
}
})
}
}
func BenchmarkSplice(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
extra := []int{1, 2, 3}
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Splice(ints, n/2, extra...)
}
})
}
}
func BenchmarkCut(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
sep := ints[n/4 : n/4+3]
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _, _ = lo.Cut(ints, sep)
}
})
}
}
func BenchmarkCutPrefix(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
prefix := ints[:3]
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.CutPrefix(ints, prefix)
}
})
}
}
func BenchmarkCutSuffix(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
suffix := ints[n-3:]
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.CutSuffix(ints, suffix)
}
})
}
}
func BenchmarkTrim(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
cutset := ints[:3]
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Trim(ints, cutset)
}
})
}
}
func BenchmarkTrimLeft(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
cutset := ints[:3]
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.TrimLeft(ints, cutset)
}
})
}
}
func BenchmarkTrimRight(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
cutset := ints[n-3:]
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.TrimRight(ints, cutset)
}
})
}
}
func BenchmarkTrimPrefix(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
prefix := ints[:3]
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.TrimPrefix(ints, prefix)
}
})
}
}
func BenchmarkTrimSuffix(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
suffix := ints[n-3:]
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.TrimSuffix(ints, suffix)
}
})
}
}
func BenchmarkFilterTakeVsFilterAndTake(b *testing.B) {
n := 1000
ints := genSliceInt(n)
b.Run("lo.TakeFilter", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.TakeFilter(ints, 5, func(v, _ int) bool {
return v%2 == 0
})
}
})
b.Run("lo.Filter+lo.Take", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Take(lo.Filter(ints, func(v, _ int) bool { return v%2 == 0 }), 5)
}
})
b.Run("lo.Filter+native_slice", func(b *testing.B) {
for i := 0; i < b.N; i++ {
filtered := lo.Filter(ints, func(v, _ int) bool { return v%2 == 0 })
takeN := 5
if takeN > len(filtered) {
_ = filtered
} else {
_ = filtered[:takeN]
}
}
})
b.Run("manual_loop", func(b *testing.B) {
for i := 0; i < b.N; i++ {
result := make([]int, 0, 5)
count := 0
for _, v := range ints {
if v%2 == 0 {
result = append(result, v)
count++
if count >= 5 {
break
}
}
}
_ = result
}
})
}
func BenchmarkDifference(b *testing.B) {
for _, n := range lengths {
ints1 := genSliceInt(n)
ints2 := genSliceInt(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.Difference(ints1, ints2)
}
})
}
for _, n := range lengths {
strs1 := genSliceString(n)
strs2 := genSliceString(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.Difference(strs1, strs2)
}
})
}
}
func BenchmarkFromSlicePtr(b *testing.B) {
for _, n := range lengths {
ptrs := lo.ToSlicePtr(genSliceInt(n))
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FromSlicePtr(ptrs)
}
})
}
for _, n := range lengths {
ptrs := lo.ToSlicePtr(genSliceString(n))
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FromSlicePtr(ptrs)
}
})
}
}
func BenchmarkFromSlicePtrOr(b *testing.B) {
for _, n := range lengths {
ptrs := lo.ToSlicePtr(genSliceInt(n))
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FromSlicePtrOr(ptrs, -1)
}
})
}
for _, n := range lengths {
ptrs := lo.ToSlicePtr(genSliceString(n))
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.FromSlicePtrOr(ptrs, "default")
}
})
}
}
================================================
FILE: benchmark/core_string_bench_test.go
================================================
package benchmark
import (
"testing"
"github.com/samber/lo"
)
func BenchmarkRandomString(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.RandomString(64, lo.AlphanumericCharset)
}
}
func BenchmarkSubstring(b *testing.B) {
s := lo.RandomString(1000, lo.LettersCharset)
for i := 0; i < b.N; i++ {
_ = lo.Substring(s, 100, 200)
}
}
func BenchmarkChunkString(b *testing.B) {
s := lo.RandomString(1000, lo.LettersCharset)
for i := 0; i < b.N; i++ {
_ = lo.ChunkString(s, 10)
}
}
func BenchmarkRuneLength(b *testing.B) {
s := lo.RandomString(1000, lo.LettersCharset)
for i := 0; i < b.N; i++ {
_ = lo.RuneLength(s)
}
}
func BenchmarkPascalCase(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.PascalCase("some_long_variable_name")
}
}
func BenchmarkCamelCase(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.CamelCase("some_long_variable_name")
}
}
func BenchmarkKebabCase(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.KebabCase("someLongVariableName")
}
}
func BenchmarkSnakeCase(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.SnakeCase("someLongVariableName")
}
}
func BenchmarkWords(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Words("someLongVariableName")
}
}
func BenchmarkCapitalize(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.Capitalize("hello world")
}
}
func BenchmarkEllipsis(b *testing.B) {
s := lo.RandomString(200, lo.LettersCharset)
for i := 0; i < b.N; i++ {
_ = lo.Ellipsis(s, 50)
}
}
================================================
FILE: benchmark/core_tuples_bench_test.go
================================================
package benchmark
import (
"fmt"
"testing"
"github.com/samber/lo"
)
func BenchmarkZip2_Equal(b *testing.B) {
for _, n := range lengths {
a := genSliceInt(n)
s := genSliceString(n)
b.Run(fmt.Sprintf("n_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
lo.Zip2(a, s)
}
})
}
}
func BenchmarkZip2_Unequal(b *testing.B) {
for _, n := range lengths {
a := genSliceInt(n)
s := genSliceString(n / 2)
b.Run(fmt.Sprintf("n_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
lo.Zip2(a, s)
}
})
}
}
func BenchmarkUnzip2(b *testing.B) {
for _, n := range lengths {
tuples := make([]lo.Tuple2[int, string], n)
for i := range tuples {
tuples[i] = lo.Tuple2[int, string]{A: i, B: "x"}
}
b.Run(fmt.Sprintf("n_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
lo.Unzip2(tuples)
}
})
}
}
================================================
FILE: benchmark/core_type_manipulation_bench_test.go
================================================
package benchmark
import (
"strconv"
"testing"
"github.com/samber/lo"
)
func BenchmarkToPtr(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ToPtr(42)
}
}
func BenchmarkFromPtr(b *testing.B) {
p := lo.ToPtr(42)
for i := 0; i < b.N; i++ {
_ = lo.FromPtr(p)
}
}
func BenchmarkFromPtrOr(b *testing.B) {
var p *int
for i := 0; i < b.N; i++ {
_ = lo.FromPtrOr(p, 99)
}
}
func BenchmarkToSlicePtr(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ToSlicePtr(ints)
}
})
}
}
func BenchmarkToAnySlice(b *testing.B) {
for _, n := range lengths {
ints := genSliceInt(n)
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.ToAnySlice(ints)
}
})
}
}
func BenchmarkFromAnySlice(b *testing.B) {
for _, n := range lengths {
anys := lo.ToAnySlice(genSliceInt(n))
b.Run(strconv.Itoa(n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.FromAnySlice[int](anys)
}
})
}
}
func BenchmarkIsEmpty(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.IsEmpty(0)
}
}
func BenchmarkIsNotEmpty(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lo.IsNotEmpty(42)
}
}
func BenchmarkCoalesce(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = lo.Coalesce(0, 0, 0, 42, 99)
}
}
================================================
FILE: benchmark/helpers_test.go
================================================
package benchmark
import (
"math/rand"
"strconv"
"time"
)
var lengths = []int{10, 100, 1000}
func genSliceString(n int) []string {
res := make([]string, 0, n)
for i := 0; i < n; i++ {
res = append(res, strconv.Itoa(rand.Intn(100_000)))
}
return res
}
func genSliceInt(n int) []int {
res := make([]int, 0, n)
for i := 0; i < n; i++ {
res = append(res, rand.Intn(100_000))
}
return res
}
type heavy = [100]int
func genSliceHeavy(n int) []heavy {
result := make([]heavy, n)
for i := range result {
for j := range result[i] {
result[i][j] = i + j
}
}
return result
}
func genMap(n int) map[string]int {
m := make(map[string]int, n)
for i := 0; i < n; i++ {
m[strconv.Itoa(i)] = i
}
return m
}
type clonableString struct {
val string
}
func (c clonableString) Clone() clonableString {
return clonableString{c.val}
}
// sliceGenerator creates a random int64 slice (used by comparison benchmarks).
func sliceGenerator(size uint) []int64 {
r := rand.New(rand.NewSource(time.Now().Unix()))
result := make([]int64, size)
for i := uint(0); i < size; i++ {
result[i] = r.Int63()
}
return result
}
// mapGenerator creates a random int64 map (used by comparison benchmarks).
func mapGenerator(size uint) map[int64]int64 {
r := rand.New(rand.NewSource(time.Now().Unix()))
result := make(map[int64]int64, size)
for i := uint(0); i < size; i++ {
result[int64(i)] = r.Int63()
}
return result
}
================================================
FILE: benchmark/it_find_bench_test.go
================================================
//go:build go1.23
package benchmark
import (
"fmt"
"math/rand/v2"
"testing"
"github.com/samber/lo/it"
)
func BenchmarkItFind(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_, _ = it.Find(ints, func(x int) bool { return x == 0 })
}
})
}
}
func BenchmarkItContains(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Contains(ints, 42)
}
})
}
}
func BenchmarkItContainsBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
target := rand.IntN(100_000)
for range b.N {
_ = it.ContainsBy(ints, func(x int) bool { return x == target })
}
})
}
}
func BenchmarkItEvery(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Every(ints, 1, 2, 3)
}
})
}
}
func BenchmarkItEveryBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.EveryBy(ints, func(x int) bool { return x >= 0 })
}
})
}
}
func BenchmarkItSome(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Some(ints, 1, 2, 3)
}
})
}
}
func BenchmarkItSomeBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
target := rand.IntN(100_000)
for range b.N {
_ = it.SomeBy(ints, func(x int) bool { return x == target })
}
})
}
}
func BenchmarkItNone(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.None(ints, -1, -2, -3)
}
})
}
}
func BenchmarkItNoneBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
target := rand.IntN(100_000)
for range b.N {
_ = it.NoneBy(ints, func(x int) bool { return x == target })
}
})
}
}
func BenchmarkItIntersect(b *testing.B) {
for _, n := range itLengths {
a := genInts(n)
c := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Intersect(a, c) { //nolint:revive
}
}
})
}
}
func BenchmarkItUnion(b *testing.B) {
for _, n := range itLengths {
a := genInts(n)
c := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Union(a, c) { //nolint:revive
}
}
})
}
}
func BenchmarkItWithout(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Without(ints, 1, 2, 3, 4, 5) { //nolint:revive
}
}
})
}
}
func BenchmarkItWithoutNth(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.WithoutNth(ints, 0, n/2, n-1) { //nolint:revive
}
}
})
}
}
func BenchmarkItIndexOf(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.IndexOf(ints, -1)
}
})
}
}
func BenchmarkItLastIndexOf(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.LastIndexOf(ints, -1)
}
})
}
}
func BenchmarkItHasPrefix(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.HasPrefix(ints, -1, -2, -3)
}
})
}
}
func BenchmarkItHasSuffix(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.HasSuffix(ints, -1, -2, -3)
}
})
}
}
func BenchmarkItFindIndexOf(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_, _, _ = it.FindIndexOf(ints, func(x int) bool { return x == -1 })
}
})
}
}
func BenchmarkItFindOrElse(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.FindOrElse(ints, -1, func(x int) bool { return x == -1 })
}
})
}
}
func BenchmarkItFindUniques(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.FindUniques(ints) { //nolint:revive
}
}
})
}
}
func BenchmarkItFindDuplicates(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.FindDuplicates(ints) { //nolint:revive
}
}
})
}
}
func BenchmarkItMin(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Min(ints)
}
})
}
}
func BenchmarkItMax(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Max(ints)
}
})
}
}
func BenchmarkItMinBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.MinBy(ints, func(a, b int) bool { return a < b })
}
})
}
}
func BenchmarkItMaxBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.MaxBy(ints, func(a, b int) bool { return a > b })
}
})
}
}
func BenchmarkItFirst(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_, _ = it.First(ints)
}
})
}
}
func BenchmarkItLast(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_, _ = it.Last(ints)
}
})
}
}
func BenchmarkItNth(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_, _ = it.Nth(ints, n/2)
}
})
}
}
func BenchmarkItSample(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Sample(ints)
}
})
}
}
func BenchmarkItSamples(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Samples(ints, 5) { //nolint:revive
}
}
})
}
}
================================================
FILE: benchmark/it_helpers_test.go
================================================
//go:build go1.23
package benchmark
import (
"iter"
"math/rand/v2"
"strconv"
)
var itLengths = []int{10, 100, 1000}
func genStrings(n int) iter.Seq[string] {
return func(yield func(string) bool) {
for range n {
if !yield(strconv.Itoa(rand.IntN(100_000))) {
break
}
}
}
}
func genInts(n int) iter.Seq[int] {
return func(yield func(int) bool) {
for range n {
if !yield(rand.IntN(100_000)) {
break
}
}
}
}
func genIntPtrSeq(n int) iter.Seq[*int] {
return func(yield func(*int) bool) {
for range n {
v := rand.IntN(100_000)
if !yield(&v) {
break
}
}
}
}
func genMapStringInt(n int) map[string]int {
m := make(map[string]int, n)
for i := range n {
m[strconv.Itoa(i)] = rand.IntN(100_000)
}
return m
}
func genMapSeq(n int) iter.Seq[map[string]int] {
return func(yield func(map[string]int) bool) {
for range n {
m := map[string]int{strconv.Itoa(rand.IntN(100_000)): rand.IntN(100_000)}
if !yield(m) {
break
}
}
}
}
================================================
FILE: benchmark/it_map_bench_test.go
================================================
//go:build go1.23
package benchmark
import (
"fmt"
"strings"
"testing"
"github.com/samber/lo/it"
)
func BenchmarkItKeys(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
for range it.Keys(m) { //nolint:revive
}
}
})
}
}
func BenchmarkItUniqKeys(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
for range it.UniqKeys(m) { //nolint:revive
}
}
})
}
}
func BenchmarkItValues(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
for range it.Values(m) { //nolint:revive
}
}
})
}
}
func BenchmarkItUniqValues(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
for range it.UniqValues(m) { //nolint:revive
}
}
})
}
}
func BenchmarkItEntries(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
for range it.Entries(m) { //nolint:revive
}
}
})
}
}
func BenchmarkItFromEntries(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
entries := it.Entries(m)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
_ = it.FromEntries(entries)
}
})
}
}
func BenchmarkItInvert(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
entries := it.Entries(m)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
for range it.Invert(entries) { //nolint:revive
}
}
})
}
}
func BenchmarkItAssign(b *testing.B) {
for _, n := range itLengths {
seq := genMapSeq(n)
b.Run(fmt.Sprintf("maps_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Assign(seq)
}
})
}
}
func BenchmarkItFilterKeys(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
for range it.FilterKeys(m, func(_ string, v int) bool { return v%2 == 0 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItFilterValues(b *testing.B) {
for _, n := range itLengths {
m := genMapStringInt(n)
b.Run(fmt.Sprintf("map_%d", n), func(b *testing.B) {
for range b.N {
for range it.FilterValues(m, func(_ string, v int) bool { return v%2 == 0 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItChunkString(b *testing.B) {
for _, n := range itLengths {
var sb strings.Builder
for range n {
sb.WriteString("a")
}
s := sb.String()
b.Run(fmt.Sprintf("len_%d", n), func(b *testing.B) {
for range b.N {
for range it.ChunkString(s, 5) { //nolint:revive
}
}
})
}
}
================================================
FILE: benchmark/it_math_bench_test.go
================================================
//go:build go1.23
package benchmark
import (
"fmt"
"testing"
"github.com/samber/lo/it"
)
func BenchmarkItSum(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Sum(ints)
}
})
}
}
func BenchmarkItSumBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.SumBy(ints, func(x int) int { return x })
}
})
}
}
func BenchmarkItProduct(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Product(ints)
}
})
}
}
func BenchmarkItMean(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Mean(ints)
}
})
}
}
func BenchmarkItMode(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Mode(ints)
}
})
}
}
func BenchmarkItRange(b *testing.B) {
for _, n := range itLengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Range(n) { //nolint:revive
}
}
})
}
}
func BenchmarkItLength(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Length(ints)
}
})
}
}
================================================
FILE: benchmark/it_slice_bench_test.go
================================================
//go:build go1.23
package benchmark
import (
"fmt"
"iter"
"testing"
"github.com/samber/lo/it"
)
func BenchmarkItChunk(b *testing.B) {
for _, n := range itLengths {
strs := genStrings(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.Chunk(strs, 5) { //nolint:revive
}
}
})
}
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for range b.N {
for range it.Chunk(ints, 5) { //nolint:revive
}
}
})
}
}
func BenchmarkItFlatten(b *testing.B) {
for _, n := range itLengths {
ints := make([]iter.Seq[int], 0, n)
for range n {
ints = append(ints, genInts(n))
}
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Flatten(ints) { //nolint:revive
}
}
})
}
for _, n := range itLengths {
strs := make([]iter.Seq[string], 0, n)
for range n {
strs = append(strs, genStrings(n))
}
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.Flatten(strs) { //nolint:revive
}
}
})
}
}
func BenchmarkItDrop(b *testing.B) {
for _, n := range itLengths {
strs := genStrings(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.Drop(strs, n/4) { //nolint:revive
}
}
})
}
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for range b.N {
for range it.Drop(ints, n/4) { //nolint:revive
}
}
})
}
}
func BenchmarkItDropWhile(b *testing.B) {
for _, n := range itLengths {
strs := genStrings(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.DropWhile(strs, func(v string) bool { return len(v) < 4 }) { //nolint:revive
}
}
})
}
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for i := range b.N {
for range it.DropWhile(ints, func(v int) bool { return i < 10_000 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItDropLastWhile(b *testing.B) {
for _, n := range itLengths {
strs := genStrings(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.DropLastWhile(strs, func(v string) bool { return len(v) < 4 }) { //nolint:revive
}
}
})
}
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for range b.N {
for range it.DropLastWhile(ints, func(v int) bool { return v < 10_000 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItDropByIndex(b *testing.B) {
for _, n := range itLengths {
strs := genStrings(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.DropByIndex(strs, n/4) { //nolint:revive
}
}
})
}
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for range b.N {
for range it.DropByIndex(ints, n/4) { //nolint:revive
}
}
})
}
}
func BenchmarkItReplace(b *testing.B) {
lengths := []int{1_000, 10_000, 100_000}
for _, n := range lengths {
strs := genStrings(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.Replace(strs, "321321", "123123", 10) { //nolint:revive
}
}
})
}
for _, n := range lengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for range b.N {
for range it.Replace(ints, 321321, 123123, 10) { //nolint:revive
}
}
})
}
}
func BenchmarkItTrim(b *testing.B) {
for _, n := range itLengths {
strs := genStrings(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.Trim(strs, "123", "456") { //nolint:revive
}
}
})
}
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Trim(ints, 123, 456) { //nolint:revive
}
}
})
}
}
func BenchmarkItTrimSuffix(b *testing.B) {
for _, n := range itLengths {
strs := genStrings(n)
b.Run(fmt.Sprintf("strings_%d", n), func(b *testing.B) {
for range b.N {
for range it.TrimSuffix(strs, []string{""}) { //nolint:revive
}
}
})
}
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints%d", n), func(b *testing.B) {
for range b.N {
for range it.TrimSuffix(ints, []int{0}) { //nolint:revive
}
}
})
}
}
func BenchmarkItFilter(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Filter(ints, func(x int) bool { return x%2 == 0 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItMap(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Map(ints, func(x int) int { return x * 2 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItUniqMap(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.UniqMap(ints, func(x int) int { return x % 50 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItFilterMap(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.FilterMap(ints, func(x int) (int, bool) { return x * 2, x%2 == 0 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItFlatMap(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.FlatMap(ints, func(x int) iter.Seq[int] { //nolint:revive
return func(yield func(int) bool) {
yield(x)
}
}) {
}
}
})
}
}
func BenchmarkItReduce(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Reduce(ints, func(agg, item int) int { return agg + item }, 0)
}
})
}
}
func BenchmarkItForEach(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
it.ForEach(ints, func(_ int) {})
}
})
}
}
func BenchmarkItForEachWhile(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
it.ForEachWhile(ints, func(_ int) bool { return true })
}
})
}
}
func BenchmarkItTimes(b *testing.B) {
for _, n := range itLengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Times(n, func(i int) int { return i * 2 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItUniq(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Uniq(ints) { //nolint:revive
}
}
})
}
}
func BenchmarkItUniqBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.UniqBy(ints, func(x int) int { return x % 50 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItGroupBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.GroupBy(ints, func(x int) int { return x % 10 })
}
})
}
}
func BenchmarkItPartitionBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.PartitionBy(ints, func(x int) int { return x % 10 })
}
})
}
}
func BenchmarkItConcat(b *testing.B) {
for _, n := range itLengths {
a := genInts(n)
c := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Concat(a, c) { //nolint:revive
}
}
})
}
}
func BenchmarkItInterleave(b *testing.B) {
for _, n := range itLengths {
a := genInts(n)
c := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Interleave(a, c) { //nolint:revive
}
}
})
}
}
func BenchmarkItShuffle(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Shuffle(ints) { //nolint:revive
}
}
})
}
}
func BenchmarkItReverse(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Reverse(ints) { //nolint:revive
}
}
})
}
}
func BenchmarkItRepeatBy(b *testing.B) {
for _, n := range itLengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.RepeatBy(n, func(i int) int { return i * 2 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItKeyBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.KeyBy(ints, func(x int) int { return x })
}
})
}
}
func BenchmarkItAssociate(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Associate(ints, func(x int) (int, int) { return x, x * 2 })
}
})
}
}
func BenchmarkItTake(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Take(ints, n/2) { //nolint:revive
}
}
})
}
}
func BenchmarkItTakeWhile(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.TakeWhile(ints, func(x int) bool { return x < 90_000 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItTakeFilter(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.TakeFilter(ints, 5, func(x int) bool { return x%2 == 0 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItReject(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Reject(ints, func(x int) bool { return x%2 == 0 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItRejectMap(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.RejectMap(ints, func(x int) (int, bool) { return x * 2, x%2 == 0 }) { //nolint:revive
}
}
})
}
}
func BenchmarkItCount(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.Count(ints, 42)
}
})
}
}
func BenchmarkItCountBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.CountBy(ints, func(x int) bool { return x%2 == 0 })
}
})
}
}
func BenchmarkItCountValues(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.CountValues(ints)
}
})
}
}
func BenchmarkItCountValuesBy(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.CountValuesBy(ints, func(x int) int { return x % 10 })
}
})
}
}
func BenchmarkItSubset(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Subset(ints, n/4, n/2) { //nolint:revive
}
}
})
}
}
func BenchmarkItSlice(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Slice(ints, n/4, n*3/4) { //nolint:revive
}
}
})
}
}
func BenchmarkItReplaceAll(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.ReplaceAll(ints, 42, 99) { //nolint:revive
}
}
})
}
}
func BenchmarkItCompact(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Compact(ints) { //nolint:revive
}
}
})
}
}
func BenchmarkItIsSorted(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
_ = it.IsSorted(ints)
}
})
}
}
func BenchmarkItSplice(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Splice(ints, n/2, 1, 2, 3) { //nolint:revive
}
}
})
}
}
func BenchmarkItCutPrefix(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
after, _ := it.CutPrefix(ints, []int{-1, -2})
for range after { //nolint:revive
}
}
})
}
}
func BenchmarkItBuffer(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.Buffer(ints, 5) { //nolint:revive
}
}
})
}
}
================================================
FILE: benchmark/it_type_manipulation_bench_test.go
================================================
//go:build go1.23
package benchmark
import (
"fmt"
"testing"
"github.com/samber/lo/it"
)
func BenchmarkItToSeqPtr(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.ToSeqPtr(ints) { //nolint:revive
}
}
})
}
}
func BenchmarkItFromSeqPtr(b *testing.B) {
for _, n := range itLengths {
ptrs := genIntPtrSeq(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.FromSeqPtr(ptrs) { //nolint:revive
}
}
})
}
}
func BenchmarkItToAnySeq(b *testing.B) {
for _, n := range itLengths {
ints := genInts(n)
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for range b.N {
for range it.ToAnySeq(ints) { //nolint:revive
}
}
})
}
}
================================================
FILE: benchmark/mutable_slice_bench_test.go
================================================
package benchmark
import (
"fmt"
"testing"
"github.com/samber/lo/mutable"
)
func BenchmarkMutableFilter(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
cp := make([]int, len(src))
copy(cp, src)
_ = mutable.Filter(cp, func(x int) bool { return x%2 == 0 })
}
})
}
}
func BenchmarkMutableFilterI(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
cp := make([]int, len(src))
copy(cp, src)
_ = mutable.FilterI(cp, func(x, _ int) bool { return x%2 == 0 })
}
})
}
}
func BenchmarkMutableMap(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
cp := make([]int, len(src))
copy(cp, src)
mutable.Map(cp, func(x int) int { return x * 2 })
}
})
}
}
func BenchmarkMutableMapI(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
cp := make([]int, len(src))
copy(cp, src)
mutable.MapI(cp, func(x, i int) int { return x * i })
}
})
}
}
func BenchmarkMutableShuffle(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
cp := make([]int, len(src))
copy(cp, src)
mutable.Shuffle(cp)
}
})
}
}
func BenchmarkMutableReverse(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
cp := make([]int, len(src))
copy(cp, src)
mutable.Reverse(cp)
}
})
}
}
================================================
FILE: benchmark/parallel_slice_bench_test.go
================================================
package benchmark
import (
"fmt"
"testing"
lop "github.com/samber/lo/parallel"
)
func BenchmarkParallelMap(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
_ = lop.Map(src, func(x, _ int) int { return x * 2 })
}
})
}
}
func BenchmarkParallelForEach(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
lop.ForEach(src, func(_, _ int) {})
}
})
}
}
func BenchmarkParallelTimes(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = lop.Times(n, func(i int) int { return i * 2 })
}
})
}
}
func BenchmarkParallelGroupBy(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
_ = lop.GroupBy(src, func(x int) int { return x % 10 })
}
})
}
}
func BenchmarkParallelPartitionBy(b *testing.B) {
for _, n := range lengths {
b.Run(fmt.Sprintf("ints_%d", n), func(b *testing.B) {
src := genSliceInt(n)
for i := 0; i < b.N; i++ {
_ = lop.PartitionBy(src, func(x int) int { return x % 10 })
}
})
}
}
================================================
FILE: channel.go
================================================
package lo
import (
"context"
"sync"
"time"
"github.com/samber/lo/internal/xrand"
)
// DispatchingStrategy is a function that distributes messages to channels.
type DispatchingStrategy[T any] func(msg T, index uint64, channels []<-chan T) int
// ChannelDispatcher distributes messages from input channels into N child channels.
// Close events are propagated to children.
// Underlying channels can have a fixed buffer capacity or be unbuffered when cap is 0.
// Play: https://go.dev/play/p/UZGu2wVg3J2
func ChannelDispatcher[T any](stream <-chan T, count, channelBufferCap int, strategy DispatchingStrategy[T]) []<-chan T {
children := createChannels[T](count, channelBufferCap)
roChildren := channelsToReadOnly(children)
go func() {
// propagate channel closing to children
defer closeChannels(children)
var i uint64
for msg := range stream {
destination := strategy(msg, i, roChildren) % count
children[destination] <- msg
i++
}
}()
return roChildren
}
func createChannels[T any](count, channelBufferCap int) []chan T {
children := make([]chan T, 0, count)
for i := 0; i < count; i++ {
children = append(children, make(chan T, channelBufferCap))
}
return children
}
func channelsToReadOnly[T any](children []chan T) []<-chan T {
roChildren := make([]<-chan T, 0, len(children))
for i := range children {
roChildren = append(roChildren, children[i])
}
return roChildren
}
func closeChannels[T any](children []chan T) {
for i := 0; i < len(children); i++ {
close(children[i])
}
}
func channelIsNotFull[T any](ch <-chan T) bool {
return cap(ch) == 0 || len(ch) < cap(ch)
}
// DispatchingStrategyRoundRobin distributes messages in a rotating sequential manner.
// If the channel capacity is exceeded, the next channel will be selected and so on.
// Play: https://go.dev/play/p/UZGu2wVg3J2
func DispatchingStrategyRoundRobin[T any](msg T, index uint64, channels []<-chan T) int {
for {
i := int(index % uint64(len(channels)))
if channelIsNotFull(channels[i]) {
return i
}
index++
time.Sleep(10 * time.Microsecond) // prevent CPU from burning 🔥
}
}
// DispatchingStrategyRandom distributes messages in a random manner.
// If the channel capacity is exceeded, another random channel will be selected and so on.
// Play: https://go.dev/play/p/GEyGn3TdGk4
func DispatchingStrategyRandom[T any](msg T, index uint64, channels []<-chan T) int {
for {
i := xrand.IntN(len(channels))
if channelIsNotFull(channels[i]) {
return i
}
time.Sleep(10 * time.Microsecond) // prevent CPU from burning 🔥
}
}
// DispatchingStrategyWeightedRandom distributes messages in a weighted manner.
// If the channel capacity is exceeded, another random channel will be selected and so on.
// Play: https://go.dev/play/p/v0eMh8NZG2L
func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy[T] {
seq := []int{}
for i, weight := range weights {
for j := 0; j < weight; j++ {
seq = append(seq, i)
}
}
return func(msg T, index uint64, channels []<-chan T) int {
for {
i := seq[xrand.IntN(len(seq))]
if channelIsNotFull(channels[i]) {
return i
}
time.Sleep(10 * time.Microsecond) // prevent CPU from burning 🔥
}
}
}
// DispatchingStrategyFirst distributes messages in the first non-full channel.
// If the capacity of the first channel is exceeded, the second channel will be selected and so on.
// Play: https://go.dev/play/p/OrJCvOmk42f
func DispatchingStrategyFirst[T any](msg T, index uint64, channels []<-chan T) int {
for {
for i := range channels {
if channelIsNotFull(channels[i]) {
return i
}
}
time.Sleep(10 * time.Microsecond) // prevent CPU from burning 🔥
}
}
// DispatchingStrategyLeast distributes messages in the emptiest channel.
// Play: https://go.dev/play/p/ypy0jrRcEe7
func DispatchingStrategyLeast[T any](msg T, index uint64, channels []<-chan T) int {
_, i := MinIndexBy(channels, func(a, b <-chan T) bool {
return len(a) < len(b)
})
return i
}
// DispatchingStrategyMost distributes messages in the fullest channel.
// If the channel capacity is exceeded, the next channel will be selected and so on.
// Play: https://go.dev/play/p/erHHone7rF9
func DispatchingStrategyMost[T any](msg T, index uint64, channels []<-chan T) int {
_, i := MaxIndexBy(channels, func(a, b <-chan T) bool {
return len(a) > len(b) && channelIsNotFull(a)
})
return i
}
// SliceToChannel returns a read-only channel of collection elements.
// Play: https://go.dev/play/p/lIbSY3QmiEg
func SliceToChannel[T any](bufferSize int, collection []T) <-chan T {
ch := make(chan T, bufferSize)
go func() {
for i := range collection {
ch <- collection[i]
}
close(ch)
}()
return ch
}
// ChannelToSlice returns a slice built from channel items. Blocks until channel closes.
// Play: https://go.dev/play/p/lIbSY3QmiEg
func ChannelToSlice[T any](ch <-chan T) []T {
collection := []T{}
for item := range ch {
collection = append(collection, item)
}
return collection
}
// Generator implements the generator design pattern.
// Play: https://go.dev/play/p/lIbSY3QmiEg
//
// Deprecated: use "iter" package instead (Go >= 1.23).
func Generator[T any](bufferSize int, generator func(yield func(T))) <-chan T {
ch := make(chan T, bufferSize)
go func() {
// WARNING: infinite loop
generator(func(t T) {
ch <- t
})
close(ch)
}()
return ch
}
// Buffer creates a slice of n elements from a channel. Returns the slice and the slice length.
// @TODO: we should probably provide a helper that reuses the same buffer.
// Play: https://go.dev/play/p/gPQ-6xmcKQI
func Buffer[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) {
buffer := make([]T, 0, size)
now := time.Now()
for index := 0; index < size; index++ {
item, ok := <-ch
if !ok {
return buffer, index, time.Since(now), false
}
buffer = append(buffer, item)
}
return buffer, size, time.Since(now), true
}
// BufferWithContext creates a slice of n elements from a channel, with context. Returns the slice and the slice length.
// @TODO: we should probably provide a helper that reuses the same buffer.
// Play: https://go.dev/play/p/oRfOyJWK9YF
func BufferWithContext[T any](ctx context.Context, ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool) {
buffer := make([]T, 0, size)
now := time.Now()
for index := 0; index < size; index++ {
select {
case item, ok := <-ch:
if !ok {
return buffer, index, time.Since(now), false
}
buffer = append(buffer, item)
case <-ctx.Done():
return buffer, index, time.Since(now), true
}
}
return buffer, size, time.Since(now), true
}
// BufferWithTimeout creates a slice of n elements from a channel, with timeout. Returns the slice and the slice length.
// Play: https://go.dev/play/p/sxyEM3koo4n
func BufferWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (collection []T, length int, readTime time.Duration, ok bool) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return BufferWithContext(ctx, ch, size)
}
// FanIn collects messages from multiple input channels into a single buffered channel.
// Output messages have no priority. When all upstream channels reach EOF, downstream channel closes.
// Play: https://go.dev/play/p/FH8Wq-T04Jb
func FanIn[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T {
out := make(chan T, channelBufferCap)
var wg sync.WaitGroup
// Start an output goroutine for each input channel in upstreams.
wg.Add(len(upstreams))
for i := range upstreams {
go func(index int) {
for n := range upstreams[index] {
out <- n
}
wg.Done()
}(i)
}
// Start a goroutine to close out once all the output goroutines are done.
go func() {
wg.Wait()
close(out)
}()
return out
}
// FanOut broadcasts all the upstream messages to multiple downstream channels.
// When upstream channel reaches EOF, downstream channels close. If any downstream
// channels is full, broadcasting is paused.
// Play: https://go.dev/play/p/2LHxcjKX23L
func FanOut[T any](count, channelsBufferCap int, upstream <-chan T) []<-chan T {
downstreams := createChannels[T](count, channelsBufferCap)
go func() {
for msg := range upstream {
for i := range downstreams {
downstreams[i] <- msg
}
}
// Close out once all the output goroutines are done.
for i := range downstreams {
close(downstreams[i])
}
}()
return channelsToReadOnly(downstreams)
}
================================================
FILE: channel_test.go
================================================
package lo
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestChannelDispatcher(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
ch := make(chan int, 10)
ch <- 0
ch <- 1
ch <- 2
ch <- 3
is.Len(ch, 4)
children := ChannelDispatcher(ch, 5, 10, DispatchingStrategyRoundRobin[int])
time.Sleep(10 * time.Millisecond)
// check channels allocation
is.Len(children, 5)
is.Equal(10, cap(children[0]))
is.Equal(10, cap(children[1]))
is.Equal(10, cap(children[2]))
is.Equal(10, cap(children[3]))
is.Equal(10, cap(children[4]))
is.Len(children[0], 1)
is.Len(children[1], 1)
is.Len(children[2], 1)
is.Len(children[3], 1)
is.Empty(children[4])
// check channels content
is.Empty(ch)
msg0, ok0 := <-children[0]
is.True(ok0)
is.Zero(msg0)
msg1, ok1 := <-children[1]
is.True(ok1)
is.Equal(1, msg1)
msg2, ok2 := <-children[2]
is.True(ok2)
is.Equal(2, msg2)
msg3, ok3 := <-children[3]
is.True(ok3)
is.Equal(3, msg3)
// msg4, ok4 := <-children[4]
// is.False(ok4)
// is.Zero(msg4)
// is.Nil(children[4])
// check it is closed
close(ch)
time.Sleep(10 * time.Millisecond)
is.Panics(func() {
ch <- 42
})
msg0, ok0 = <-children[0]
is.False(ok0)
is.Zero(msg0)
msg1, ok1 = <-children[1]
is.False(ok1)
is.Zero(msg1)
msg2, ok2 = <-children[2]
is.False(ok2)
is.Zero(msg2)
msg3, ok3 = <-children[3]
is.False(ok3)
is.Zero(msg3)
msg4, ok4 := <-children[4]
is.False(ok4)
is.Zero(msg4)
// unbuffered channels
children = ChannelDispatcher(ch, 5, 0, DispatchingStrategyRoundRobin[int])
is.Zero(cap(children[0]))
}
func TestDispatchingStrategyRoundRobin(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
children := createChannels[int](3, 2)
rochildren := channelsToReadOnly(children)
defer closeChannels(children)
is.Zero(DispatchingStrategyRoundRobin(42, 0, rochildren))
is.Equal(1, DispatchingStrategyRoundRobin(42, 1, rochildren))
is.Equal(2, DispatchingStrategyRoundRobin(42, 2, rochildren))
is.Zero(DispatchingStrategyRoundRobin(42, 3, rochildren))
}
func TestDispatchingStrategyRandom(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
children := createChannels[int](2, 2)
rochildren := channelsToReadOnly(children)
defer closeChannels(children)
for i := 0; i < 2; i++ {
children[1] <- i
}
is.Zero(DispatchingStrategyRandom(42, 0, rochildren))
}
func TestDispatchingStrategyWeightedRandom(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
children := createChannels[int](2, 2)
rochildren := channelsToReadOnly(children)
defer closeChannels(children)
dispatcher := DispatchingStrategyWeightedRandom[int]([]int{0, 42})
is.Equal(1, dispatcher(42, 0, rochildren))
children[0] <- 0
is.Equal(1, dispatcher(42, 0, rochildren))
children[1] <- 1
is.Equal(1, dispatcher(42, 0, rochildren))
}
func TestDispatchingStrategyFirst(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
children := createChannels[int](2, 2)
rochildren := channelsToReadOnly(children)
defer closeChannels(children)
is.Zero(DispatchingStrategyFirst(42, 0, rochildren))
children[0] <- 0
is.Zero(DispatchingStrategyFirst(42, 0, rochildren))
children[0] <- 1
is.Equal(1, DispatchingStrategyFirst(42, 0, rochildren))
}
func TestDispatchingStrategyLeast(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
children := createChannels[int](2, 2)
rochildren := channelsToReadOnly(children)
defer closeChannels(children)
is.Zero(DispatchingStrategyLeast(42, 0, rochildren))
children[0] <- 0
is.Equal(1, DispatchingStrategyLeast(42, 0, rochildren))
children[1] <- 0
is.Zero(DispatchingStrategyLeast(42, 0, rochildren))
children[0] <- 1
is.Equal(1, DispatchingStrategyLeast(42, 0, rochildren))
children[1] <- 1
is.Zero(DispatchingStrategyLeast(42, 0, rochildren))
}
func TestDispatchingStrategyMost(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
children := createChannels[int](2, 2)
rochildren := channelsToReadOnly(children)
defer closeChannels(children)
is.Zero(DispatchingStrategyMost(42, 0, rochildren))
children[0] <- 0
is.Zero(DispatchingStrategyMost(42, 0, rochildren))
children[1] <- 0
is.Zero(DispatchingStrategyMost(42, 0, rochildren))
children[0] <- 1
is.Zero(DispatchingStrategyMost(42, 0, rochildren))
children[1] <- 1
is.Zero(DispatchingStrategyMost(42, 0, rochildren))
}
func TestSliceToChannel(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
ch := SliceToChannel(2, []int{1, 2, 3})
r1, ok1 := <-ch
r2, ok2 := <-ch
r3, ok3 := <-ch
is.True(ok1)
is.Equal(1, r1)
is.True(ok2)
is.Equal(2, r2)
is.True(ok3)
is.Equal(3, r3)
_, ok4 := <-ch
is.False(ok4)
}
func TestChannelToSlice(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
ch := SliceToChannel(2, []int{1, 2, 3})
items := ChannelToSlice(ch)
is.Equal([]int{1, 2, 3}, items)
}
func TestGenerate(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
generator := func(yield func(int)) {
yield(0)
yield(1)
yield(2)
yield(3)
}
i := 0
for v := range Generator(2, generator) {
is.Equal(i, v)
i++
}
is.Equal(4, i)
}
func TestBuffer(t *testing.T) {
t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
ch := SliceToChannel(2, []int{1, 2, 3})
items1, length1, _, ok1 := Buffer(ch, 2)
items2, length2, _, ok2 := Buffer(ch, 2)
items3, length3, _, ok3 := Buffer(ch, 2)
is.Equal([]int{1, 2}, items1)
is.Equal(2, length1)
is.True(ok1)
is.Equal([]int{3}, items2)
is.Equal(1, length2)
is.False(ok2)
is.Empty(items3)
is.Zero(length3)
is.False(ok3)
}
func TestBufferWithContext(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 200*time.Millisecond)
is := assert.New(t)
ch1 := make(chan int, 10)
ctx, cancel := context.WithCancel(context.Background())
go func() {
ch1 <- 0
ch1 <- 1
ch1 <- 2
time.Sleep(5 * time.Millisecond)
cancel()
ch1 <- 3
ch1 <- 4
ch1 <- 5
close(ch1)
}()
items1, length1, _, ok1 := BufferWithContext(ctx, ch1, 20)
is.Equal([]int{0, 1, 2}, items1)
is.Equal(3, length1)
is.True(ok1)
ch2 := make(chan int, 10)
ctx, cancel = context.WithCancel(context.Background())
defer cancel()
defer close(ch2)
for i := 0; i < 10; i++ {
ch2 <- i
}
items2, length2, _, ok2 := BufferWithContext(ctx, ch2, 5)
is.Equal([]int{0, 1, 2, 3, 4}, items2)
is.Equal(5, length2)
is.True(ok2)
}
func TestBufferWithTimeout(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 2000*time.Millisecond)
is := assert.New(t)
generator := func(n ...int) func(yield func(int)) {
return func(yield func(int)) {
for i := 0; i < len(n); i++ {
yield(n[i])
time.Sleep(100 * time.Millisecond)
}
}
}
ch := Generator(0, generator(0, 1, 2, 3, 4))
items1, length1, duration1, ok1 := BufferWithTimeout(ch, 20, 150*time.Millisecond)
is.Equal([]int{0, 1}, items1)
is.Equal(2, length1)
is.InDelta(150*time.Millisecond, duration1, float64(20*time.Millisecond))
is.True(ok1)
items2, length2, duration2, ok2 := BufferWithTimeout(ch, 20, 10*time.Millisecond)
is.Empty(items2)
is.Zero(length2)
is.InDelta(10*time.Millisecond, duration2, float64(10*time.Millisecond))
is.True(ok2)
items3, length3, duration3, ok3 := BufferWithTimeout(ch, 1, 300*time.Millisecond)
is.Equal([]int{2}, items3)
is.Equal(1, length3)
is.InDelta(50*time.Millisecond, duration3, float64(20*time.Millisecond))
is.True(ok3)
items4, length4, duration4, ok4 := BufferWithTimeout(ch, 2, 250*time.Millisecond)
is.Equal([]int{3, 4}, items4)
is.Equal(2, length4)
is.InDelta(200*time.Millisecond, duration4, float64(50*time.Millisecond))
is.True(ok4)
items5, length5, duration5, ok5 := BufferWithTimeout(ch, 3, 250*time.Millisecond)
is.Empty(items5)
is.Zero(length5)
is.InDelta(100*time.Millisecond, duration5, float64(50*time.Millisecond))
is.False(ok5)
items6, length6, duration6, ok6 := BufferWithTimeout(ch, 3, 250*time.Millisecond)
is.Empty(items6)
is.Zero(length6)
is.InDelta(1*time.Millisecond, duration6, float64(10*time.Millisecond))
is.False(ok6)
}
func TestFanIn(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
upstreams := createChannels[int](3, 10)
roupstreams := channelsToReadOnly(upstreams)
for i := range roupstreams {
go func(i int) {
upstreams[i] <- 1
upstreams[i] <- 1
close(upstreams[i])
}(i)
}
out := FanIn(10, roupstreams...)
time.Sleep(10 * time.Millisecond)
// check input channels
is.Empty(roupstreams[0])
is.Empty(roupstreams[1])
is.Empty(roupstreams[2])
// check channels allocation
is.Len(out, 6)
is.Equal(10, cap(out))
// check channels content
for i := 0; i < 6; i++ {
msg0, ok0 := <-out
is.True(ok0)
is.Equal(1, msg0)
}
// check it is closed
time.Sleep(10 * time.Millisecond)
msg0, ok0 := <-out
is.False(ok0)
is.Zero(msg0)
}
func TestFanOut(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
upstream := SliceToChannel(10, []int{0, 1, 2, 3, 4, 5})
rodownstreams := FanOut(3, 10, upstream)
time.Sleep(10 * time.Millisecond)
// check output channels
is.Len(rodownstreams, 3)
// check channels allocation
for i := range rodownstreams {
is.Len(rodownstreams[i], 6)
is.Equal(10, cap(rodownstreams[i]))
is.Equal([]int{0, 1, 2, 3, 4, 5}, ChannelToSlice(rodownstreams[i]))
}
// check it is closed
time.Sleep(10 * time.Millisecond)
// check channels allocation
for i := range rodownstreams {
msg, ok := <-rodownstreams[i]
is.False(ok)
is.Zero(msg)
}
}
================================================
FILE: concurrency.go
================================================
package lo
import (
"context"
"sync"
"time"
)
type synchronize struct {
locker sync.Locker
}
func (s *synchronize) Do(callback func()) {
s.locker.Lock()
Try0(callback)
s.locker.Unlock()
}
// Synchronize wraps the underlying callback in a mutex. It receives an optional mutex.
// Play: https://go.dev/play/p/X3cqROSpQmu
func Synchronize(opt ...sync.Locker) *synchronize { //nolint:revive
if len(opt) > 1 {
panic("lo.Synchronize: unexpected arguments")
} else if len(opt) == 0 {
opt = append(opt, &sync.Mutex{})
}
return &synchronize{
locker: opt[0],
}
}
// Async executes a function in a goroutine and returns the result in a channel.
// Play: https://go.dev/play/p/uo35gosuTLw
func Async[A any](f func() A) <-chan A {
ch := make(chan A, 1)
go func() {
ch <- f()
}()
return ch
}
// Async0 executes a function in a goroutine and returns a channel set once the function finishes.
// Play: https://go.dev/play/p/tNqf1cClG_o
func Async0(f func()) <-chan struct{} {
ch := make(chan struct{}, 1)
go func() {
f()
ch <- struct{}{}
}()
return ch
}
// Async1 is an alias to Async.
// Play: https://go.dev/play/p/RBQWtIn4PsF
func Async1[A any](f func() A) <-chan A {
return Async(f)
}
// Async2 has the same behavior as Async, but returns the 2 results as a tuple inside the channel.
// Play: https://go.dev/play/p/5SzzDjssXOH
func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B] {
ch := make(chan Tuple2[A, B], 1)
go func() {
ch <- T2(f())
}()
return ch
}
// Async3 has the same behavior as Async, but returns the 3 results as a tuple inside the channel.
// Play: https://go.dev/play/p/cZpZsDXNmlx
func Async3[A, B, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C] {
ch := make(chan Tuple3[A, B, C], 1)
go func() {
ch <- T3(f())
}()
return ch
}
// Async4 has the same behavior as Async, but returns the 4 results as a tuple inside the channel.
// Play: https://go.dev/play/p/9X5O2VrLzkR
func Async4[A, B, C, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D] {
ch := make(chan Tuple4[A, B, C, D], 1)
go func() {
ch <- T4(f())
}()
return ch
}
// Async5 has the same behavior as Async, but returns the 5 results as a tuple inside the channel.
// Play: https://go.dev/play/p/MqnUJpkmopA
func Async5[A, B, C, D, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C, D, E] {
ch := make(chan Tuple5[A, B, C, D, E], 1)
go func() {
ch <- T5(f())
}()
return ch
}
// Async6 has the same behavior as Async, but returns the 6 results as a tuple inside the channel.
// Play: https://go.dev/play/p/kM1X67JPdSP
func Async6[A, B, C, D, E, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A, B, C, D, E, F] {
ch := make(chan Tuple6[A, B, C, D, E, F], 1)
go func() {
ch <- T6(f())
}()
return ch
}
// WaitFor runs periodically until a condition is validated.
// Play: https://go.dev/play/p/t_wTDmubbK3
func WaitFor(condition func(i int) bool, timeout, heartbeatDelay time.Duration) (totalIterations int, elapsed time.Duration, conditionFound bool) {
conditionWithContext := func(_ context.Context, currentIteration int) bool {
return condition(currentIteration)
}
return WaitForWithContext(context.Background(), conditionWithContext, timeout, heartbeatDelay)
}
// WaitForWithContext runs periodically until a condition is validated or context is canceled.
// Play: https://go.dev/play/p/t_wTDmubbK3
func WaitForWithContext(ctx context.Context, condition func(ctx context.Context, currentIteration int) bool, timeout, heartbeatDelay time.Duration) (totalIterations int, elapsed time.Duration, conditionFound bool) {
start := time.Now()
if ctx.Err() != nil {
return totalIterations, time.Since(start), false
}
ctx, cleanCtx := context.WithTimeout(ctx, timeout)
ticker := time.NewTicker(heartbeatDelay)
defer func() {
cleanCtx()
ticker.Stop()
}()
for {
select {
case <-ctx.Done():
return totalIterations, time.Since(start), false
case <-ticker.C:
totalIterations++
if condition(ctx, totalIterations-1) {
return totalIterations, time.Since(start), true
}
}
}
}
================================================
FILE: concurrency_test.go
================================================
package lo
import (
"context"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestSynchronize(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 1000*time.Millisecond)
is := assert.New(t)
// check that callbacks are not executed concurrently
{
start := time.Now()
wg := sync.WaitGroup{}
wg.Add(10)
s := Synchronize()
for i := 0; i < 10; i++ {
go s.Do(func() {
time.Sleep(50 * time.Millisecond)
wg.Done()
})
}
wg.Wait()
duration := time.Since(start)
is.InDelta(500*time.Millisecond, duration, float64(40*time.Millisecond))
}
// check locker is locked
{
mu := &sync.Mutex{}
s := Synchronize(mu)
s.Do(func() {
is.False(mu.TryLock())
})
is.True(mu.TryLock())
Try0(func() {
mu.Unlock()
})
}
// check we don't accept multiple arguments
{
is.PanicsWithValue("lo.Synchronize: unexpected arguments", func() {
mu := &sync.Mutex{}
Synchronize(mu, mu, mu)
})
}
}
func TestAsync(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
sync := make(chan struct{})
ch := Async(func() int {
<-sync
return 10
})
sync <- struct{}{}
select {
case result := <-ch:
is.Equal(10, result)
case <-time.After(time.Millisecond):
is.Fail("Async should not block")
}
}
func TestAsyncX(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
{
sync := make(chan struct{})
ch := Async0(func() {
<-sync
})
sync <- struct{}{}
select {
case <-ch:
case <-time.After(time.Millisecond):
is.Fail("Async0 should not block")
}
}
{
sync := make(chan struct{})
ch := Async1(func() int {
<-sync
return 10
})
sync <- struct{}{}
select {
case result := <-ch:
is.Equal(10, result)
case <-time.After(time.Millisecond):
is.Fail("Async1 should not block")
}
}
{
sync := make(chan struct{})
ch := Async2(func() (int, string) {
<-sync
return 10, "Hello"
})
sync <- struct{}{}
select {
case result := <-ch:
is.Equal(Tuple2[int, string]{10, "Hello"}, result)
case <-time.After(time.Millisecond):
is.Fail("Async2 should not block")
}
}
{
sync := make(chan struct{})
ch := Async3(func() (int, string, bool) {
<-sync
return 10, "Hello", true
})
sync <- struct{}{}
select {
case result := <-ch:
is.Equal(Tuple3[int, string, bool]{10, "Hello", true}, result)
case <-time.After(time.Millisecond):
is.Fail("Async3 should not block")
}
}
{
sync := make(chan struct{})
ch := Async4(func() (int, string, bool, float64) {
<-sync
return 10, "Hello", true, 3.14
})
sync <- struct{}{}
select {
case result := <-ch:
is.Equal(Tuple4[int, string, bool, float64]{10, "Hello", true, 3.14}, result)
case <-time.After(time.Millisecond):
is.Fail("Async4 should not block")
}
}
{
sync := make(chan struct{})
ch := Async5(func() (int, string, bool, float64, string) {
<-sync
return 10, "Hello", true, 3.14, "World"
})
sync <- struct{}{}
select {
case result := <-ch:
is.Equal(Tuple5[int, string, bool, float64, string]{10, "Hello", true, 3.14, "World"}, result)
case <-time.After(time.Millisecond):
is.Fail("Async5 should not block")
}
}
{
sync := make(chan struct{})
ch := Async6(func() (int, string, bool, float64, string, int) {
<-sync
return 10, "Hello", true, 3.14, "World", 100
})
sync <- struct{}{}
select {
case result := <-ch:
is.Equal(Tuple6[int, string, bool, float64, string, int]{10, "Hello", true, 3.14, "World", 100}, result)
case <-time.After(time.Millisecond):
is.Fail("Async6 should not block")
}
}
}
func TestWaitFor(t *testing.T) { //nolint:paralleltest
// t.Parallel()
t.Run("exist condition works", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 300*time.Millisecond)
is := assert.New(t)
laterTrue := func(i int) bool {
return i >= 5
}
iter, duration, ok := WaitFor(laterTrue, 200*time.Millisecond, 10*time.Millisecond)
is.Equal(6, iter, "unexpected iteration count")
is.InDelta(60*time.Millisecond, duration, float64(5*time.Millisecond))
is.True(ok)
})
t.Run("counter is incremented", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
counter := 0
alwaysFalse := func(i int) bool {
is.Equal(counter, i)
counter++
return false
}
iter, duration, ok := WaitFor(alwaysFalse, 40*time.Millisecond, 10*time.Millisecond)
is.Equal(counter, iter, "unexpected iteration count")
is.InDelta(40*time.Millisecond, duration, float64(5*time.Millisecond))
is.False(ok)
})
alwaysTrue := func(_ int) bool { return true }
alwaysFalse := func(_ int) bool { return false }
t.Run("timeout works", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 200*time.Millisecond)
is := assert.New(t)
iter, duration, ok := WaitFor(alwaysFalse, 50*time.Millisecond, 100*time.Millisecond)
is.Zero(iter, "unexpected iteration count")
is.InDelta(50*time.Millisecond, duration, float64(10*time.Millisecond))
is.False(ok)
})
t.Run("exist on first condition", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 200*time.Millisecond)
is := assert.New(t)
iter, duration, ok := WaitFor(alwaysTrue, 100*time.Millisecond, 30*time.Millisecond)
is.Equal(1, iter, "unexpected iteration count")
is.InDelta(30*time.Millisecond, duration, float64(10*time.Millisecond))
is.True(ok)
})
}
func TestWaitForWithContext(t *testing.T) { //nolint:paralleltest
// t.Parallel()
t.Run("exist condition works", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 200*time.Millisecond)
is := assert.New(t)
laterTrue := func(_ context.Context, i int) bool {
return i >= 5
}
iter, duration, ok := WaitForWithContext(context.Background(), laterTrue, 200*time.Millisecond, 10*time.Millisecond)
is.Equal(6, iter, "unexpected iteration count")
is.InDelta(60*time.Millisecond, duration, float64(5*time.Millisecond))
is.True(ok)
})
t.Run("counter is incremented", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 150*time.Millisecond)
is := assert.New(t)
counter := 0
alwaysFalse := func(_ context.Context, i int) bool {
is.Equal(counter, i)
counter++
return false
}
iter, duration, ok := WaitForWithContext(context.Background(), alwaysFalse, 80*time.Millisecond, 20*time.Millisecond)
is.Equal(counter, iter, "unexpected iteration count")
is.InDelta(80*time.Millisecond, duration, float64(10*time.Millisecond))
is.False(ok)
})
alwaysTrue := func(_ context.Context, _ int) bool { return true }
alwaysFalse := func(_ context.Context, _ int) bool { return false }
t.Run("timeout works", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 200*time.Millisecond)
is := assert.New(t)
iter, duration, ok := WaitForWithContext(context.Background(), alwaysFalse, 50*time.Millisecond, 100*time.Millisecond)
is.Zero(iter, "unexpected iteration count")
is.InDelta(50*time.Millisecond, duration, float64(10*time.Millisecond))
is.False(ok)
})
t.Run("exist on first condition", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 200*time.Millisecond)
is := assert.New(t)
iter, duration, ok := WaitForWithContext(context.Background(), alwaysTrue, 100*time.Millisecond, 10*time.Millisecond)
is.Equal(1, iter, "unexpected iteration count")
is.InDelta(10*time.Millisecond, duration, float64(5*time.Millisecond))
is.True(ok)
})
t.Run("context cancellation stops everything", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 100*time.Millisecond)
is := assert.New(t)
expiringCtx, clean := context.WithTimeout(context.Background(), 45*time.Millisecond)
t.Cleanup(func() {
clean()
})
iter, duration, ok := WaitForWithContext(expiringCtx, alwaysFalse, 100*time.Millisecond, 30*time.Millisecond)
is.Equal(1, iter, "unexpected iteration count")
is.InDelta(45*time.Millisecond, duration, float64(10*time.Millisecond))
is.False(ok)
})
t.Run("canceled context stops everything", func(t *testing.T) { //nolint:paralleltest
// t.Parallel()
testWithTimeout(t, 200*time.Millisecond)
is := assert.New(t)
canceledCtx, cancel := context.WithCancel(context.Background())
cancel()
iter, duration, ok := WaitForWithContext(canceledCtx, alwaysFalse, 100*time.Millisecond, 30*time.Millisecond)
is.Zero(iter, "unexpected iteration count")
is.InDelta(1*time.Millisecond, duration, float64(1*time.Millisecond))
is.False(ok)
})
}
================================================
FILE: condition.go
================================================
package lo
// Ternary is a single line if/else statement.
// Take care to avoid dereferencing potentially nil pointers in your A/B expressions, because they are both evaluated. See TernaryF to avoid this problem.
// Play: https://go.dev/play/p/t-D7WBL44h2
func Ternary[T any](condition bool, ifOutput, elseOutput T) T {
if condition {
return ifOutput
}
return elseOutput
}
// TernaryF is a single line if/else statement whose options are functions.
// Play: https://go.dev/play/p/AO4VW20JoqM
func TernaryF[T any](condition bool, ifFunc, elseFunc func() T) T {
if condition {
return ifFunc()
}
return elseFunc()
}
// Perf: value receivers (not pointer) allow the compiler to fully inline the entire
// If().ElseIf().Else() chain, eliminating all function call overhead.
type ifElse[T any] struct {
result T
done bool
}
// If is a single line if/else statement.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func If[T any](condition bool, result T) ifElse[T] { //nolint:revive
if condition {
return ifElse[T]{result, true}
}
var t T
return ifElse[T]{t, false}
}
// IfF is a single line if/else statement whose options are functions.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func IfF[T any](condition bool, resultF func() T) ifElse[T] { //nolint:revive
if condition {
return ifElse[T]{resultF(), true}
}
var t T
return ifElse[T]{t, false}
}
// ElseIf.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func (i ifElse[T]) ElseIf(condition bool, result T) ifElse[T] {
if !i.done && condition {
i.result = result
i.done = true
}
return i
}
// ElseIfF.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func (i ifElse[T]) ElseIfF(condition bool, resultF func() T) ifElse[T] {
if !i.done && condition {
i.result = resultF()
i.done = true
}
return i
}
// Else.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func (i ifElse[T]) Else(result T) T {
if i.done {
return i.result
}
return result
}
// ElseF.
// Play: https://go.dev/play/p/WSw3ApMxhyW
func (i ifElse[T]) ElseF(resultF func() T) T {
if i.done {
return i.result
}
return resultF()
}
// Perf: value receivers (not pointer) allow the compiler to fully inline the entire
// Switch().Case().Default() chain, eliminating all function call overhead.
type switchCase[T comparable, R any] struct {
predicate T
result R
done bool
}
// Switch is a pure functional switch/case/default statement.
// Play: https://go.dev/play/p/TGbKUMAeRUd
func Switch[T comparable, R any](predicate T) switchCase[T, R] { //nolint:revive
var result R
return switchCase[T, R]{
predicate,
result,
false,
}
}
// Case.
// Play: https://go.dev/play/p/TGbKUMAeRUd
func (s switchCase[T, R]) Case(val T, result R) switchCase[T, R] {
if !s.done && s.predicate == val {
s.result = result
s.done = true
}
return s
}
// CaseF.
// Play: https://go.dev/play/p/TGbKUMAeRUd
func (s switchCase[T, R]) CaseF(val T, callback func() R) switchCase[T, R] {
if !s.done && s.predicate == val {
s.result = callback()
s.done = true
}
return s
}
// Default.
// Play: https://go.dev/play/p/TGbKUMAeRUd
func (s switchCase[T, R]) Default(result R) R {
if !s.done {
s.result = result
}
return s.result
}
// DefaultF.
// Play: https://go.dev/play/p/TGbKUMAeRUd
func (s switchCase[T, R]) DefaultF(callback func() R) R {
if !s.done {
s.result = callback()
}
return s.result
}
================================================
FILE: condition_test.go
================================================
package lo
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTernary(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Ternary(true, "a", "b")
result2 := Ternary(false, "a", "b")
is.Equal("a", result1)
is.Equal("b", result2)
}
func TestTernaryF(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := TernaryF(true, func() string { return "a" }, func() string { return "b" })
result2 := TernaryF(false, func() string { return "a" }, func() string { return "b" })
is.Equal("a", result1)
is.Equal("b", result2)
}
func TestIfElse(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := If(true, 1).ElseIf(false, 2).Else(3)
result2 := If(true, 1).ElseIf(true, 2).Else(3)
result3 := If(false, 1).ElseIf(true, 2).Else(3)
result4 := If(false, 1).ElseIf(false, 2).Else(3)
is.Equal(1, result1)
is.Equal(1, result2)
is.Equal(2, result3)
is.Equal(3, result4)
}
func TestIfFElseF(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := IfF(true, func() int { return 1 }).ElseIfF(false, func() int { return 2 }).ElseF(func() int { return 3 })
result2 := IfF(true, func() int { return 1 }).ElseIfF(true, func() int { return 2 }).ElseF(func() int { return 3 })
result3 := IfF(false, func() int { return 1 }).ElseIfF(true, func() int { return 2 }).ElseF(func() int { return 3 })
result4 := IfF(false, func() int { return 1 }).ElseIfF(false, func() int { return 2 }).ElseF(func() int { return 3 })
is.Equal(1, result1)
is.Equal(1, result2)
is.Equal(2, result3)
is.Equal(3, result4)
}
func TestSwitchCase(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Switch[int, int](42).Case(42, 1).Case(1, 2).Default(3)
result2 := Switch[int, int](42).Case(42, 1).Case(42, 2).Default(3)
result3 := Switch[int, int](42).Case(1, 1).Case(42, 2).Default(3)
result4 := Switch[int, int](42).Case(1, 1).Case(1, 2).Default(3)
is.Equal(1, result1)
is.Equal(1, result2)
is.Equal(2, result3)
is.Equal(3, result4)
}
func TestSwitchCaseF(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Switch[int, int](42).CaseF(42, func() int { return 1 }).CaseF(1, func() int { return 2 }).DefaultF(func() int { return 3 })
result2 := Switch[int, int](42).CaseF(42, func() int { return 1 }).CaseF(42, func() int { return 2 }).DefaultF(func() int { return 3 })
result3 := Switch[int, int](42).CaseF(1, func() int { return 1 }).CaseF(42, func() int { return 2 }).DefaultF(func() int { return 3 })
result4 := Switch[int, int](42).CaseF(1, func() int { return 1 }).CaseF(1, func() int { return 2 }).DefaultF(func() int { return 3 })
is.Equal(1, result1)
is.Equal(1, result2)
is.Equal(2, result3)
is.Equal(3, result4)
}
================================================
FILE: constraints.go
================================================
package lo
// Clonable defines a constraint of types having Clone() T method.
type Clonable[T any] interface {
Clone() T
}
================================================
FILE: docs/.gitignore
================================================
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: docs/CLAUDE.md
================================================
# Adding New Helper Documentation
When adding a new helper to the `lo` library, follow these guidelines to create or update documentation files in the `docs/data/` directory.
## File Structure
Documentation files follow the naming pattern `core-{helper-name}.md` for core helpers.
### Frontmatter Format
Each documentation file must start with a frontmatter section:
```yaml
---
name: HelperName
slug: helpername
sourceRef: file.go#L123
category: core
subCategory: slice
signatures:
- "func HelperName(params) returnType"
- "func (receiver *Type) MethodName(params) returnType"
- "func HelperNameI(params) returnType"
- "func HelperNameWithContext(params) returnType"
playUrl: https://go.dev/play/p/EXAMPLE
variantHelpers:
- core#slice#helpername
- core#slice#helpername2
- core#slice#helpername3
- core#slice#helpername4
- core#slice#helpername5
- core#slice#helpernamei
- core#slice#helpernamewithcontext
similarHelpers:
- mutable#slice#helpername
- core#slice#filterhelpername
- core#slice#zipx
position: 0
---
```
### Frontmatter Fields
- **name**: The display name of the helper (PascalCase)
- **slug**: URL-friendly name (kebab-case, matches filename without `core-` prefix)
- **sourceRef**: Source file reference with line number (format: `file.go#L123`)
- **category**: `core`, `mutable`, `parallel`, `it`... The category must match the file name.
- **subCategory**: The functional category (e.g., `condition`, `map`, `find`, `slice`...)
- **signatures**: Array of function signatures as strings. Do not list signatures from other sub-packages/category.
- **playUrl**: Go Playground URL with working example
- **variantHelpers**: Array of variant helper names. Must contain at least the default helper named above. This field is for:
- Variations of the same helper with different signatures (e.g., `Map`, `MapI`, `MapWithContext`, `MapIWithContext`)
- Helper variants that add functionality like indexes, context, or different parameter types
- All variants must be in the same category and subcategory as the main helper
- Examples: `core#slice#map`, `core#slice#mapi`, `core#slice#mapwithcontext`
- **similarHelpers**: Array of related helper names (leave empty if none). This field is for:
- Equivalent helpers in other packages/categories (e.g., `parallel#slice#Map`, `mutable#slice#Filter`)
- Helper compositions or related functionality (e.g., `FilterMap` is similar to both `Map` and `Filter`)
- Helpers with different names but similar purposes (e.g., `FindBy` variants vs base `Find`)
- Cross-references to helpers that users might want to consider as alternatives
- **position**: Position in the list (0, 10, 20, 30...). Order must follow the order in source code. Helpers are grouped by category+sub-category and displayed on a page. Position number is reset for each page.
## Content Structure
After the frontmatter, include:
1. **Brief description**: One sentence explaining what the helper does
2. **Code example**: Working Go code demonstrating usage
3. **Expected output**: Comment showing the result
```markdown
Brief description of what this helper does. Be concise and not too long.
```go
result := lo.HelperName(example)
// expected result
```
```
Multiple examples can be used for demonstration the helper, such as edge cases. If multiple signatures are grouped under this documentation, it could be useful to describe some (all?) of them.
## Understanding variantHelpers vs similarHelpers
### variantHelpers
Use `variantHelpers` for different versions of the **same helper function**:
**Example**: Map helper variants (all in `core#slice#map`):
```yaml
variantHelpers:
- core#slice#map # func Map[T, R]([]T, func(T, int) R) []R
- core#slice#maperr # func MapErr[T, R]([]T, func(T, int) (R, error)) ([]R, error)
- core#slice#mapi # func MapI[T, R]([]T, func(T, int) R) []R (with index)
- core#slice#mapwithcontext # func MapWithContext[T, R]([]T, func(T, int, context.Context) R, context.Context) []R
```
### similarHelpers
Use `similarHelpers` for **related but different helpers**:
**Example**: FilterMap combines Map and Filter functionality:
```yaml
similarHelpers:
- core#slice#map # Related transformation helper
- core#slice#filter # Related filtering helper
```
**Example**: Cross-package equivalents:
```yaml
similarHelpers:
- parallel#slice#map # Parallel version in different package
- mutable#slice#map # Mutable version in different package
- mutable#slice#zipx # Mutable version in different package
```
**Example**: Related helpers with similar functionality:
```yaml
similarHelpers:
- core#slice#find # Single result search
- core#slice#filter # Multiple result filtering
- core#slice#findby # Key-based search
- core#slice#findorelse # Search with default value
```
**Example**: Helper families with similar patterns:
```yaml
similarHelpers:
- core#slice#min # Find minimum value
- core#slice#max # Find maximum value
- core#slice#minby # Find minimum by key function
- core#slice#maxby # Find maximum by key function
- core#slice#minindex # Find minimum value index
- core#slice#maxindex # Find maximum value index
```
**Example**: Composition helpers that combine multiple operations:
```yaml
similarHelpers:
- core#slice#filtermap # Filter + Map combination
- core#slice#mapkeys # Map to extract keys
- core#slice#mapvalues # Map to extract values
```
When you add similarHelpers to a new helper, please update the linked similar helpers documentation, and add the one you're adding.
Don't link helpers having numeric declensions (eg: use core#slice#zipx instead of core#slice#zip2).
### Key Differences
- **variantHelpers**: Same helper, different signatures/parameters (same package)
- **similarHelpers**: Different helpers, related functionality (can be cross-package)
## Grouping Related Helpers
When multiple helpers operate on the same struct or serve similar purposes, consolidate them into a single file:
**Example**: Map helpers:
- `Map()` base helper
- `MapI()` add index to predicate callback
- `MapWithContext()` add context to predicate callback
- `MapIWithContext()` add index and context to predicate callback
**Example**: Switch helpers all operate on `switchCase[T, R]`:
- `Switch()` - constructor
- `Case()` - method for adding cases
- `CaseF()` - method for adding cases with functions
- `Default()` - method for default values
- `DefaultF()` - method for default values with functions
In such cases:
1. Use the primary helper name in the filename (e.g., `core-switch.md`)
2. Include all related signatures in the `signatures` array
3. List all related helpers in `variantHelpers` array
4. Document each helper in its own section with `### HelperName` headers
## Naming Conventions
### Categories
Use these established subCategories:
- `condition` - conditional logic (if/else, switch)
- `map` - transformation functions
- `find` - search and lookup functions
- `slice` - array manipulation
- `math` - mathematical operations
- `string` - string operations
- `type` - type utilities
- `error-handling` - error management
- `retry` - retry mechanisms
- `time` - time operations
- `function` - function utilities
- `channel` - channel operations
- `tuple` - tuple operations
- `intersect` - set intersections
### Helper Names
- Follow Go naming conventions (PascalCase for exported)
- Use descriptive names that clearly indicate purpose
- For function variants, use consistent suffixes:
- `F` suffix for function-based versions (lazy evaluation)
- `I` suffix for variants having `index int` argument in predicate callback
- `Err` suffix for variants returning an error in predicate callback
- `WithContext` suffix when context.Context is provided
- `X` suffix for helpers with varying arguments (eg: MustX: Must2, Must3, Must4...)
## Go Playground Examples
Every helper must have a working Go Playground example linked in two places:
1. **Source code**: `// Play: ` comment on the last line of the doc block, right before the `func` keyword
2. **Doc file**: `playUrl: ` field in the YAML frontmatter of `docs/data/-.md`
### Creating a New Playground Example
#### Step 1: Write the Example Code
Write a minimal, self-contained `main.go` that demonstrates the helper. Guidelines:
- Use realistic but simple data
- Print the result with `fmt.Println` so the output is visible
- Include edge cases when useful (e.g., empty input, error case)
- For `Err` variants, show both a success and an error case
- For time-based helpers, use `time.Date()` for deterministic output
- For random helpers (`SampleBy`, `SamplesBy`), use `rand.New(rand.NewSource(42))` for reproducible output
#### Step 2: Import Conventions
Use the correct import path depending on the package:
```go
// Core helpers
import "github.com/samber/lo"
// Usage: lo.Map(...)
// Iterator helpers (it/ package, requires Go 1.23+)
import (
"slices"
"github.com/samber/lo/it"
)
// Usage: slices.Collect(it.Map(...))
// Convert slices to iterators: slices.Values([]int{1, 2, 3})
// Parallel helpers
import lop "github.com/samber/lo/parallel"
// Usage: lop.Map(...)
```
#### Step 3: Run and Share via Go Playground
Use the `go-playground` MCP tool to execute the example and get a shareable URL:
```
mcp__go-playground__run_and_share_go_code(code: "")
```
This compiles the code on go.dev/play, runs it, and returns:
- The program output (to verify correctness)
- A shareable URL like `https://go.dev/play/p/XXXXXXX`
If the output doesn't match expectations, fix the code and re-run until it produces the correct result.
#### Step 4: Add the URL to Source Code
Add a `// Play:` comment as the **last line** of the function's doc comment block:
```go
// Map manipulates a slice and transforms it to a slice of another type.
// Play: https://go.dev/play/p/refNB9ZTIGo
func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R {
```
#### Step 5: Add the URL to Documentation
Set the `playUrl` field in the corresponding `docs/data/*.md` file:
```yaml
---
name: Map
slug: map
playUrl: https://go.dev/play/p/refNB9ZTIGo
...
---
```
### Troubleshooting
**First-run timeouts**: The Go Playground may timeout on the first execution if `github.com/samber/lo` hasn't been cached yet. Simply retry — subsequent runs succeed because the module is cached.
**New helpers not yet released**: If documentation is created at the same time as the helper source code, the Go Playground cannot compile it because the module version hasn't been published yet. In that case, skip the playground example and leave `playUrl` empty. Create the example after the next release.
**SIMD helpers**: Helpers in `exp/simd/` require `go1.26+goexperiment.simd+amd64` build tags, which the Go Playground does not support. These helpers cannot have playground examples.
### Bulk Verification
To verify all playground URLs compile, you can use `mcp__go-playground__execute_go_playground_url` to re-run an existing URL and check the output. To read the source code of an existing playground, use `mcp__go-playground__read_go_playground_url`.
## Example: Complete File
```yaml
---
name: Map
slug: map
sourceRef: map.go#L123
category: core
subCategory: map
signatures:
- "func Map[T any, R any](collection []T, transform func(item T, index int) R) []R"
playUrl: https://go.dev/play/p/EXAMPLE
similarHelpers: []
position: 0
---
Applies a function to each element of a collection and returns a new collection with the results.
```go
result := lo.Map([]int{1, 2, 3}, func(item int, index int) string {
return fmt.Sprintf("%d", item)
})
// []string{"1", "2", "3"}
```
```
## Checklist
Before submitting:
- [ ] Frontmatter is complete and correctly formatted
- [ ] Filename matches slug (with `core-` or `mutable-` or `parallel-` or `it-` prefix)
- [ ] Source reference points to correct line number
- [ ] Category and subCategory are appropriate
- [ ] All signatures are included and properly formatted
- [ ] Go Playground example works and demonstrates usage
- [ ] Expected output is shown as a comment
- [ ] Similar helpers are listed if applicable
- [ ] Related helpers are consolidated into single file when appropriate
- [ ] All validation scripts pass without errors
- [ ] Helper is added to llms.txt
================================================
FILE: docs/README.md
================================================
# Website
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
## Installation
```bash
yarn
```
## Local Development
```bash
yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
## Build
```bash
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
## Deployment
Using SSH:
```bash
USE_SSH=true yarn deploy
```
Not using SSH:
```bash
GIT_USER= yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
================================================
FILE: docs/data/core-assert.md
================================================
---
name: Assert
slug: assert
sourceRef: errors.go#L359
category: core
subCategory: error-handling
playUrl: https://go.dev/play/p/Xv8LLKBMNwI
variantHelpers:
- core#error-handling#assert
- core#error-handling#assertf
similarHelpers:
- core#error-handling#validate
- core#error-handling#mustx
- core#error-handling#tryx
- core#error-handling#tryorx
- core#error-handling#trycatch
- core#error-handling#trywitherrorvalue
- core#error-handling#errorsas
position: 80
signatures:
- "func Assert(condition bool, message ...string)"
- "func Assertf(condition bool, format string, args ...any)"
---
Does nothing when condition is true; otherwise panics.
```go
// Base variant with optional message
age := 12
lo.Assert(age >= 15, "user age must be >= 15")
// panics: "user age must be >= 15"
// Without message - panics with default message
x := -1
lo.Assert(x > 0)
// panics: "assertion failed: condition is not true"
// Formatted variant with custom message
age = 12
lo.Assertf(age >= 15, "user age must be >= 15, got %d", age)
// panics: "user age must be >= 15, got 12"
// When condition is true - no panic
age = 20
lo.Assert(age >= 15, "user age must be >= 15")
// continues normally
```
## Custom handler
Replace `lo.Assert` and `lo.Assertf` with your own statement:
```go
lo.Assertf = func(condition bool, format string, args ...any) {
if !condition {
panic(fmt.Errorf("%s: %s", "customErr", fmt.Sprintf(format, args...)))
}
}
```
================================================
FILE: docs/data/core-assign.md
================================================
---
name: Assign
slug: assign
sourceRef: map.go#L234
category: core
subCategory: map
playUrl: https://go.dev/play/p/VhwfJOyxf5o
variantHelpers:
- core#map#assign
similarHelpers:
- core#map#entries
- core#map#keys
- core#map#values
position: 160
signatures:
- "func Assign[K comparable, V any, Map ~map[K]V](maps ...Map) Map"
---
Merges multiple maps from left to right. Later maps overwrite earlier keys.
```go
merged := lo.Assign(
map[string]int{"a": 1, "b": 2},
map[string]int{"b": 3, "c": 4},
)
// map[string]int{"a": 1, "b": 3, "c": 4}
```
================================================
FILE: docs/data/core-associate.md
================================================
---
name: Associate
slug: associate
sourceRef: slice.go#L389
category: core
subCategory: slice
playUrl: https://go.dev/play/p/WHa2CfMO3Lr
variantHelpers:
- core#slice#associate
- core#slice#associatei
- core#slice#slicetomap
similarHelpers:
- core#slice#keyby
- core#slice#groupby
- core#slice#filterslicetomap
- core#slice#slicetomap
position: 240
signatures:
- "func Associate[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V"
- "func AssociateI[T any, K comparable, V any](collection []T, transform func(item T, index int) (K, V)) map[K]V"
- "func SliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V"
---
Builds a map from a slice using a transform function that yields key/value pairs for each item. Perfect for converting collections to lookup maps.
### Associate
Transforms each element into a key-value pair. Later items with the same key will overwrite earlier ones.
```go
type foo struct {
baz string
bar int
}
in := []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}
m := lo.Associate(in, func(f *foo) (string, int) {
return f.baz, f.bar
})
// m: map[string]int{"apple": 1, "banana": 2}
```
### AssociateI
Variant that includes the element index in the transform function, useful when you need the position in the original slice.
```go
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
}
result := lo.AssociateI(users, func(user User, index int) (string, int) {
return fmt.Sprintf("%s-%d", user.Name, index), user.Age
})
// result: map[string]int{"Alice-0": 25, "Bob-1": 30}
```
### SliceToMap
Alias for Associate - provides the same functionality with a more explicit name.
```go
products := []string{"apple", "banana", "cherry"}
result := lo.SliceToMap(products, func(product string) (string, int) {
return product, len(product)
})
// result: map[string]int{"apple": 5, "banana": 6, "cherry": 6}
```
================================================
FILE: docs/data/core-asyncx.md
================================================
---
name: AsyncX
slug: asyncx
sourceRef: concurrency.go#L35
category: core
subCategory: concurrency
signatures:
- "func Async[A any](f func() A) <-chan A"
- "func Async0(f func()) <-chan struct{}"
- "func Async1[A any](f func() A) <-chan A"
- "func Async2[A, B any](f func() (A, B)) <-chan Tuple2[A, B]"
- "func Async3[A, B, C any](f func() (A, B, C)) <-chan Tuple3[A, B, C]"
- "func Async4[A, B, C, D any](f func() (A, B, C, D)) <-chan Tuple4[A, B, C, D]"
- "func Async5[A, B, C, D, E any](f func() (A, B, C, D, E)) <-chan Tuple5[A, B, C, D, E]"
- "func Async6[A, B, C, D, E, F any](f func() (A, B, C, D, E, F)) <-chan Tuple6[A, B, C, D, E, F]"
playUrl: https://go.dev/play/p/uo35gosuTLw
variantHelpers:
- core#concurrency#async
- core#concurrency#asyncx
similarHelpers:
- core#concurrency#synchronize
- core#concurrency#waitfor
- core#retry#newtransaction
- core#channel#channelseq
position: 10
---
Runs a function asynchronously and returns results via channels. Variants support 0 to 6 return values, using tuple types for multi-value results.
Variants: `Async`, `Async0..Async6`
```go
ch := lo.Async(func() int {
time.Sleep(10 * time.Millisecond)
return 42
})
v := <-ch
done := lo.Async0(func() {
time.Sleep(5 * time.Millisecond)
})
<-done
```
================================================
FILE: docs/data/core-attempt.md
================================================
---
name: Attempt
slug: attempt
sourceRef: retry.go#L153
category: core
subCategory: retry
playUrl: https://go.dev/play/p/3ggJZ2ZKcMj
variantHelpers:
- core#retry#attempt
similarHelpers:
- core#retry#attemptwithdelay
- core#retry#attemptwhile
- core#retry#attemptwhilewithdelay
position: 0
signatures:
- "func Attempt(maxIteration int, f func(index int) error) (int, error)"
---
Invokes a function up to N times until it returns nil. Returns the number of iterations attempted (1-based) and the last error. Useful for retrying operations that might fail temporarily.
```go
// Success after 3 attempts
iter, err := lo.Attempt(5, func(i int) error {
if i == 2 {
return nil // succeeds on 3rd attempt (index 2)
}
return errors.New("failed")
})
// iter: 3, err: nil
```
```go
// All attempts fail - returns last error
iter, err = lo.Attempt(3, func(i int) error {
return fmt.Errorf("attempt %d failed", i)
})
// iter: 3, err: "attempt 2 failed" (last error from index 2)
```
```go
// Immediate success on first attempt
iter, err = lo.Attempt(5, func(i int) error {
return nil // succeeds immediately
})
// iter: 1, err: nil
```
```go
// Zero attempts - returns error without calling function
iter, err = lo.Attempt(0, func(i int) error {
return errors.New("should not be called")
})
// iter: 0, err: "maxIteration must be greater than 0"
```
================================================
FILE: docs/data/core-attemptwhile.md
================================================
---
name: AttemptWhile
slug: attemptwhile
sourceRef: retry.go#L198
category: core
subCategory: retry
playUrl: https://go.dev/play/p/1VS7HxlYMOG
variantHelpers:
- core#retry#attemptwhile
similarHelpers:
- core#retry#attempt
- core#retry#attemptwithdelay
- core#retry#attemptwhilewithdelay
position: 20
signatures:
- "func AttemptWhile(maxIteration int, f func(int) (error, bool)) (int, error)"
---
Invokes a function up to N times until it returns nil. The second return value controls whether to continue attempting.
```go
count, err := lo.AttemptWhile(5, func(i int) (error, bool) {
if i == 2 {
return nil, false
}
return errors.New("fail"), true
})
// count == 3, err == nil
```
================================================
FILE: docs/data/core-attemptwhilewithdelay.md
================================================
---
name: AttemptWhileWithDelay
slug: attemptwhilewithdelay
sourceRef: retry.go#L223
category: core
subCategory: retry
playUrl: https://go.dev/play/p/mhufUjJfLEF
variantHelpers:
- core#retry#attemptwhilewithdelay
similarHelpers:
- core#retry#attempt
- core#retry#attemptwithdelay
- core#retry#attemptwhile
position: 30
signatures:
- "func AttemptWhileWithDelay(maxIteration int, delay time.Duration, f func(int, time.Duration) (error, bool)) (int, time.Duration, error)"
---
Like AttemptWhile, but pauses between attempts and returns elapsed time.
```go
count, dur, err := lo.AttemptWhileWithDelay(
5,
time.Millisecond,
func(i int, d time.Duration) (error, bool) {
return errors.New("x"), i < 1
},
)
```
================================================
FILE: docs/data/core-attemptwithdelay.md
================================================
---
name: AttemptWithDelay
slug: attemptwithdelay
sourceRef: retry.go#L172
category: core
subCategory: retry
playUrl: https://go.dev/play/p/tVs6CygC7m1
variantHelpers:
- core#retry#attemptwithdelay
similarHelpers:
- core#retry#attempt
- core#retry#attemptwhile
- core#retry#attemptwhilewithdelay
position: 10
signatures:
- "func AttemptWithDelay(maxIteration int, delay time.Duration, f func(index int, duration time.Duration) error) (int, time.Duration, error)"
---
Invokes a function up to N times with a pause between calls until it returns nil. Returns iterations, elapsed, and error.
```go
iter, dur, err := lo.AttemptWithDelay(
3,
100*time.Millisecond,
func(i int, d time.Duration) error {
if i == 1 {
return nil
}
return errors.New("x")
},
)
```
================================================
FILE: docs/data/core-buffer.md
================================================
---
name: Buffer
slug: buffer
sourceRef: channel.go#L214
category: core
subCategory: channel
signatures:
- "func Buffer[T any](ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool)"
- "func BufferWithContext[T any](ctx context.Context, ch <-chan T, size int) (collection []T, length int, readTime time.Duration, ok bool)"
variantHelpers:
- core#channel#buffer
- core#channel#bufferwithcontext
similarHelpers:
- core#channel#slicetochannel
- core#channel#channeltoslice
position: 260
---
Buffer reads up to size items from a channel and returns them as a slice.
```go
ch := make(chan int, 10)
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
items, length, readTime, ok := lo.Buffer(ch, 3)
// items: []int{1, 2, 3}
// length: 3
// readTime: ~0s (immediate read from buffered channel)
// ok: true (channel was closed)
```
### BufferWithContext
BufferWithContext reads up to size items from a channel with context cancellation.
```go
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
ch := make(chan int)
go func() {
time.Sleep(50 * time.Millisecond)
ch <- 1
time.Sleep(100 * time.Millisecond)
ch <- 2
}()
items, length, readTime, ok := lo.BufferWithContext(ctx, ch, 5)
// items: []int{1} (only first item received before timeout)
// length: 1
// readTime: ~100ms (context timeout)
// ok: false (context cancelled)
```
================================================
FILE: docs/data/core-bufferwithtimeout.md
================================================
---
name: BufferWithTimeout
slug: bufferwithtimeout
sourceRef: channel.go#L214
category: core
subCategory: channel
signatures:
- "func BufferWithTimeout[T any](ch <-chan T, size int, timeout time.Duration) (collection []T, length int, readTime time.Duration, ok bool)"
variantHelpers:
- core#channel#bufferwithtimeout
similarHelpers:
- core#channel#buffer
- core#channel#bufferwithcontext
- core#channel#slicetochannel
- core#channel#channeltoslice
position: 263
---
BufferWithTimeout reads up to size items from a channel with timeout.
```go
ch := make(chan int)
go func() {
time.Sleep(200 * time.Millisecond)
ch <- 1
}()
items, length, readTime, ok := lo.BufferWithTimeout(ch, 5, 100*time.Millisecond)
// Returns empty slice due to timeout
```
================================================
FILE: docs/data/core-camelcase.md
================================================
---
name: CamelCase
slug: camelcase
sourceRef: string.go#L176
category: core
subCategory: string
playUrl: https://go.dev/play/p/4JNDzaMwXkm
variantHelpers:
- core#string#camelcase
similarHelpers:
- core#string#pascalcase
- core#string#snakecase
- core#string#kebabcase
- core#string#capitalize
- core#string#words
position: 50
signatures:
- "func CamelCase(str string) string"
---
Converts a string to camelCase.
```go
lo.CamelCase("hello_world")
// "helloWorld"
```
================================================
FILE: docs/data/core-capitalize.md
================================================
---
name: Capitalize
slug: capitalize
sourceRef: string.go#L227
category: core
subCategory: string
playUrl: https://go.dev/play/p/uLTZZQXqnsa
variantHelpers:
- core#string#capitalize
similarHelpers:
- core#string#pascalcase
- core#string#camelcase
- core#string#snakecase
- core#string#kebabcase
- core#string#words
- core#string#runelength
position: 90
signatures:
- "func Capitalize(str string) string"
---
Converts the first character to uppercase and the remaining to lowercase.
```go
lo.Capitalize("heLLO")
// "Hello"
```
================================================
FILE: docs/data/core-channeldispatcher.md
================================================
---
name: ChannelDispatcher
slug: channeldispatcher
sourceRef: channel.go#L18
category: core
subCategory: channel
signatures:
- "func ChannelDispatcher[T any](stream <-chan T, count, channelBufferCap int, strategy DispatchingStrategy[T]) []<-chan T"
similarHelpers:
- core#channel#fanin
- core#channel#fanout
- it#channel#seqtochannel
- it#channel#channeltoseq
position: 250
---
ChannelDispatcher distributes messages from a stream to multiple channels based on a strategy.
```go
stream := make(chan int, 100)
for i := 0; i < 100; i++ {
stream <- i
}
close(stream)
channels := lo.ChannelDispatcher(stream, 3, 10, lo.DispatchingStrategyRoundRobin[int])
// Returns 3 channels with round-robin distribution
```
================================================
FILE: docs/data/core-channeltoslice.md
================================================
---
name: ChannelToSlice
slug: channeltoslice
sourceRef: channel.go#L18
category: core
subCategory: channel
signatures:
- "func ChannelToSlice[T any](ch <-chan T) []T"
similarHelpers:
- core#channel#slicetochannel
- core#channel#buffer
- it#channel#seqtochannel
- it#channel#channeltoseq
position: 252
---
ChannelToSlice converts a channel to a slice.
```go
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
slice := lo.ChannelToSlice(ch)
// slice contains [1, 2, 3]
```
================================================
FILE: docs/data/core-chunk.md
================================================
---
name: Chunk
slug: chunk
sourceRef: slice.go#L209
category: core
subCategory: slice
playUrl: https://go.dev/play/p/kEMkFbdu85g
variantHelpers:
- core#slice#chunk
similarHelpers:
- core#slice#slice
- core#slice#partitionby
- core#slice#drop
- core#slice#flatten
- core#slice#window
- core#slice#sliding
- core#map#chunkentries
position: 140
signatures:
- "func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice"
---
Splits a slice into chunks of the given size. The final chunk may be smaller.
```go
lo.Chunk([]int{0, 1, 2, 3, 4, 5}, 2)
// [][]int{{0, 1}, {2, 3}, {4, 5}}
lo.Chunk([]int{0, 1, 2, 3, 4, 5, 6}, 2)
// [][]int{{0, 1}, {2, 3}, {4, 5}, {6}}
```
## Note
`lo.ChunkString` and `lo.Chunk` functions behave inconsistently for empty input: `lo.ChunkString("", n)` returns `[""]` instead of `[]`.
See https://github.com/samber/lo/issues/788
================================================
FILE: docs/data/core-chunkentries.md
================================================
---
name: ChunkEntries
slug: chunkentries
sourceRef: map.go#L253
category: core
subCategory: map
playUrl: https://go.dev/play/p/X_YQL6mmoD-
variantHelpers:
- core#map#chunkentries
similarHelpers:
- core#slice#chunk
- core#map#mapentries
- core#map#keyby
- core#map#groupby
- core#map#values
position: 170
signatures:
- "func ChunkEntries[K comparable, V any](m map[K]V, size int) []map[K]V"
---
Splits a map into maps of at most the given size. The last chunk may be smaller.
```go
chunks := lo.ChunkEntries(map[string]int{"a":1, "b":2, "c":3, "d":4, "e":5}, 3)
// []map[string]int{ {"a":1, "b":2, "c":3}, {"d":4, "e":5} }
```
================================================
FILE: docs/data/core-chunkstring.md
================================================
---
name: ChunkString
slug: chunkstring
sourceRef: string.go#L130
category: core
subCategory: string
playUrl: https://go.dev/play/p/__FLTuJVz54
variantHelpers:
- core#string#chunkstring
similarHelpers:
- core#slice#chunk
- core#string#substring
- core#string#words
- core#string#runelength
- core#string#split
position: 20
signatures:
- "func ChunkString[T ~string](str T, size int) []T"
---
Splits a string into chunks of the given size. The last chunk may be shorter. Returns [""] for empty input.
```go
lo.ChunkString("1234567", 2)
// []string{"12", "34", "56", "7"}
```
================================================
FILE: docs/data/core-clamp.md
================================================
---
name: Clamp
slug: clamp
sourceRef: math.go#L59
category: core
subCategory: math
playUrl: https://go.dev/play/p/RU4lJNC2hlI
variantHelpers:
- core#math#clamp
similarHelpers:
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
- core#math#mean
- core#math#sum
- core#math#product
position: 30
signatures:
- "func Clamp[T constraints.Ordered](value T, mIn T, mAx T) T"
---
Clamps a number within inclusive lower and upper bounds.
```go
lo.Clamp(42, -10, 10)
// 10
```
================================================
FILE: docs/data/core-clone.md
================================================
---
name: Clone
slug: clone
sourceRef: slice.go#L741
category: core
subCategory: slice
playUrl: https://go.dev/play/p/66LJ2wAF0rN
variantHelpers:
- core#slice#clone
similarHelpers:
- core#slice#repeat
- core#slice#fill
position: 160
signatures:
- "func Clone[T any, Slice ~[]T](collection Slice) Slice"
---
Returns a shallow copy of the collection.
```go
in := []int{1, 2, 3, 4, 5}
cloned := lo.Clone(in)
// Verify it's a different slice by checking that modifying one doesn't affect the other
in[0] = 99
// cloned is []int{1, 2, 3, 4, 5}
```
================================================
FILE: docs/data/core-coalesce.md
================================================
---
name: Coalesce
slug: coalesce
sourceRef: type_manipulation.go#L153
category: core
subCategory: type
signatures:
- "func Coalesce[T comparable](values ...T) (T, bool)"
playUrl: https://go.dev/play/p/Gyo9otyvFHH
variantHelpers:
- core#type#coalesce
similarHelpers:
- core#type#coalesceorempty
- core#type#coalesceslice
- core#type#coalescesliceorempty
- core#type#coalescemap
- core#type#coalescemaporempty
- core#type#valueor
- core#type#empty
- core#type#fromptror
position: 130
---
Returns the first non-zero value from the provided comparable arguments, with a boolean indicating if a non-zero value was found.
```go
// With strings - returns first non-empty string
result, ok := lo.Coalesce("", "foo", "bar")
// result: "foo", ok: true
// All zero values - returns zero value with false
result, ok = lo.Coalesce("", "")
// result: "", ok: false
// With integers - zero is considered zero value
result, ok = lo.Coalesce(0, 42, 100)
// result: 42, ok: true
// With floats - zero is considered zero value
result, ok = lo.Coalesce(0.0, 3.14, 2.71)
// result: 3.14, ok: true
// With pointers - nil is zero value for pointer types
var s *string
str := "hello"
result, ok = lo.Coalesce(nil, &str)
// result: &str, ok: true
// All nil pointers
result, ok = lo.Coalesce[*string](nil, nil, nil)
// result: nil, ok: false
```
================================================
FILE: docs/data/core-coalescemap.md
================================================
---
name: CoalesceMap
slug: coalescemap
sourceRef: type_manipulation.go#L196
category: core
subCategory: type
signatures:
- "func CoalesceMap[K comparable, V any](v ...map[K]V) (map[K]V, bool)"
playUrl: https://go.dev/play/p/Gyo9otyvFHH
variantHelpers:
- core#type#coalescemap
similarHelpers:
- core#type#coalesce
- core#type#coalesceorempty
- core#type#coalesceslice
- core#type#coalescesliceorempty
- core#type#coalescemaporempty
- core#type#empty
position: 150
---
Returns the first non-empty map from the provided arguments, with a boolean indicating if a non-empty map was found.
```go
result, ok := lo.CoalesceMap(map[string]int{}, map[string]int{"a": 1}, map[string]int{"b": 2})
// map[string]int{"a": 1}, true
result, ok = lo.CoalesceMap(map[string]int{}, map[string]int{})
// map[string]int{}, false
result, ok = lo.CoalesceMap(map[int]string{}, map[int]string{1: "one"}, map[int]string{2: "two"})
// map[int]string{1: "one"}, true
```
================================================
FILE: docs/data/core-coalescemaporempty.md
================================================
---
name: CoalesceMapOrEmpty
slug: coalescemaporempty
sourceRef: type_manipulation.go#L207
category: core
subCategory: type
signatures:
- "func CoalesceMapOrEmpty[K comparable, V any](v ...map[K]V) map[K]V"
playUrl: https://go.dev/play/p/Gyo9otyvFHH
variantHelpers:
- core#type#coalescemaporempty
similarHelpers:
- core#type#coalesce
- core#type#coalesceorempty
- core#type#coalesceslice
- core#type#coalescesliceorempty
- core#type#coalescemap
- core#type#empty
position: 155
---
Returns the first non-empty map from the provided arguments, or an empty map if all arguments are empty.
```go
result := lo.CoalesceMapOrEmpty(map[string]int{}, map[string]int{"a": 1}, map[string]int{"b": 2})
// map[string]int{"a": 1}
result = lo.CoalesceMapOrEmpty(map[string]int{}, map[string]int{})
// map[string]int{}
result = lo.CoalesceMapOrEmpty(map[int]string{}, map[int]string{1: "one"}, map[int]string{2: "two"})
// map[int]string{1: "one"}
```
================================================
FILE: docs/data/core-coalesceorempty.md
================================================
---
name: CoalesceOrEmpty
slug: coalesceorempty
sourceRef: type_manipulation.go#L167
category: core
subCategory: type
signatures:
- "func CoalesceOrEmpty[T comparable](v ...T) T"
playUrl: https://go.dev/play/p/Gyo9otyvFHH
variantHelpers:
- core#type#coalesceorempty
similarHelpers:
- core#type#coalesce
- core#type#coalesceslice
- core#type#coalescesliceorempty
- core#type#coalescemap
- core#type#coalescemaporempty
- core#type#valueor
- core#type#empty
position: 135
---
Returns the first non-zero value from the provided comparable arguments, or the zero value if all arguments are zero.
```go
result := lo.CoalesceOrEmpty("", "foo", "bar")
// "foo"
result = lo.CoalesceOrEmpty("", "")
// ""
result = lo.CoalesceOrEmpty(0, 42, 100)
// 42
result = lo.CoalesceOrEmpty(0.0, 0.0, 0.0)
// 0.0
```
================================================
FILE: docs/data/core-coalesceslice.md
================================================
---
name: CoalesceSlice
slug: coalesceslice
sourceRef: type_manipulation.go#L174
category: core
subCategory: type
signatures:
- "func CoalesceSlice[T any](v ...[]T) ([]T, bool)"
playUrl: https://go.dev/play/p/Gyo9otyvFHH
variantHelpers:
- core#type#coalesceslice
similarHelpers:
- core#type#coalesce
- core#type#coalesceorempty
- core#type#coalescesliceorempty
- core#type#coalescemap
- core#type#coalescemaporempty
- core#type#empty
position: 140
---
Returns the first non-empty slice from the provided arguments, with a boolean indicating if a non-empty slice was found.
```go
result, ok := lo.CoalesceSlice([]int{}, []int{1, 2, 3}, []int{4, 5})
// []int{1, 2, 3}, true
result, ok = lo.CoalesceSlice([]int{}, []int{})
// []int{}, false
result, ok = lo.CoalesceSlice([]string{}, []string{"a", "b"}, []string{"c"})
// []string{"a", "b"}, true
```
================================================
FILE: docs/data/core-coalescesliceorempty.md
================================================
---
name: CoalesceSliceOrEmpty
slug: coalescesliceorempty
sourceRef: type_manipulation.go#L185
category: core
subCategory: type
signatures:
- "func CoalesceSliceOrEmpty[T any](v ...[]T) []T"
playUrl: https://go.dev/play/p/Gyo9otyvFHH
variantHelpers:
- core#type#coalescesliceorempty
similarHelpers:
- core#type#coalesce
- core#type#coalesceorempty
- core#type#coalesceslice
- core#type#coalescemap
- core#type#coalescemaporempty
- core#type#empty
position: 145
---
Returns the first non-empty slice from the provided arguments, or an empty slice if all arguments are empty.
```go
result := lo.CoalesceSliceOrEmpty([]int{}, []int{1, 2, 3}, []int{4, 5})
// []int{1, 2, 3}
result = lo.CoalesceSliceOrEmpty([]int{}, []int{})
// []int{}
result = lo.CoalesceSliceOrEmpty([]string{}, []string{"a", "b"}, []string{"c"})
// []string{"a", "b"}
```
================================================
FILE: docs/data/core-compact.md
================================================
---
name: Compact
slug: compact
sourceRef: slice.go#L706
category: core
subCategory: slice
playUrl: https://go.dev/play/p/tXiy-iK6PAc
variantHelpers:
- core#slice#compact
similarHelpers:
- core#slice#filter
- core#slice#reject
- core#slice#without
- core#slice#withoutempty
position: 270
signatures:
- "func Compact[T comparable, Slice ~[]T](collection Slice) Slice"
---
Returns a slice of all non-zero elements.
```go
lo.Compact([]string{"", "foo", "", "bar", ""})
// []string{"foo", "bar"}
```
================================================
FILE: docs/data/core-concat.md
================================================
---
name: Concat
slug: concat
sourceRef: slice.go#L282
category: core
subCategory: slice
playUrl: https://go.dev/play/p/Ux2UuR2xpRK
variantHelpers:
- core#slice#concat
similarHelpers:
- it#sequence#concat
- core#slice#flatten
- core#intersection#union
position: 160
signatures:
- "func Concat[T any, Slice ~[]T](collections ...Slice) Slice"
---
Returns a new slice containing all the elements in collections. Concat conserves the order of the elements.
```go
list1 := []int{0, 1}
list2 := []int{2, 3, 4, 5}
flat := lo.Flatten(list1, list2)
// []int{0, 1, 2, 3, 4, 5}
```
================================================
FILE: docs/data/core-contains.md
================================================
---
name: Contains
slug: contains
sourceRef: intersect.go#L5
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/W1EvyqY6t9j
variantHelpers:
- core#intersect#contains
similarHelpers:
- core#intersect#containsby
- core#intersect#every
- core#intersect#some
- core#intersect#none
- core#slice#find
position: 0
signatures:
- "func Contains[T comparable](collection []T, element T) bool"
---
Returns true if an element is present in a collection.
```go
present := lo.Contains([]int{0, 1, 2, 3, 4, 5}, 5)
// true
```
================================================
FILE: docs/data/core-containsby.md
================================================
---
name: ContainsBy
slug: containsby
sourceRef: intersect.go#L17
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/W1EvyqY6t9j
variantHelpers:
- core#intersect#containsby
similarHelpers:
- core#intersect#contains
- core#intersect#some
- core#intersect#every
- core#intersect#none
- core#slice#some
- core#slice#every
- core#slice#findby
- core#slice#filter
position: 10
signatures:
- "func ContainsBy[T any](collection []T, predicate func(item T) bool) bool"
---
Returns true if the predicate returns true for any element in the collection.
```go
exists := lo.ContainsBy(
[]int{0, 1, 2, 3},
func(x int) bool {
return x == 3
},
)
// true
```
================================================
FILE: docs/data/core-count.md
================================================
---
name: Count
slug: count
sourceRef: slice.go#L582
category: core
subCategory: slice
playUrl: https://go.dev/play/p/Y3FlK54yveC
variantHelpers:
- core#slice#count
similarHelpers:
- core#slice#countby
- core#slice#countvalues
- core#slice#every
- core#slice#some
position: 0
signatures:
- "func Count[T comparable](collection []T, value T) int"
---
Counts the number of elements in the collection that equal a given value.
```go
lo.Count([]int{1, 5, 1}, 1)
// 2
```
================================================
FILE: docs/data/core-countby.md
================================================
---
name: CountBy
slug: countby
sourceRef: slice.go#L849
category: core
subCategory: slice
playUrl: https://go.dev/play/p/5GMQP5vNT4q
variantHelpers:
- core#slice#countby
similarHelpers:
- core#slice#count
- core#slice#every
- core#slice#some
- core#slice#filter
- core#slice#find
- core#slice#countbyerr
position: 0
signatures:
- "func CountBy[T any](collection []T, predicate func(item T) bool) int"
---
Counts the number of elements for which the predicate is true.
```go
lo.CountBy([]int{1, 5, 1}, func(i int) bool {
return i < 4
})
// 2
```
================================================
FILE: docs/data/core-countbyerr.md
================================================
---
name: CountByErr
slug: countbyerr
sourceRef: slice.go#L863
category: core
subCategory: slice
signatures:
- "func CountByErr[T any](collection []T, predicate func(item T) (bool, error)) (int, error)"
variantHelpers:
- core#slice#countbyerr
playUrl: https://go.dev/play/p/7BnyPhpG6lW
similarHelpers:
- core#slice#countby
- core#slice#count
- core#slice#everybyerr
- core#slice#somebyerr
position: 5
---
Counts the number of elements for which the predicate is true. Returns an error if the predicate function fails, stopping iteration immediately.
```go
count, err := lo.CountByErr([]int{1, 5, 1}, func(i int) (bool, error) {
if i == 5 {
return false, fmt.Errorf("5 not allowed")
}
return i < 4, nil
})
// 0, error("5 not allowed")
```
```go
count, err := lo.CountByErr([]int{1, 5, 1}, func(i int) (bool, error) {
return i < 4, nil
})
// 2, nil
```
================================================
FILE: docs/data/core-countvalues.md
================================================
---
name: CountValues
slug: countvalues
sourceRef: slice.go#L610
category: core
subCategory: slice
playUrl: https://go.dev/play/p/-p-PyLT4dfy
variantHelpers:
- core#slice#countvalues
similarHelpers:
- core#slice#count
- core#slice#countby
- core#slice#countvaluesby
- core#slice#groupby
- core#slice#uniq
position: 0
signatures:
- "func CountValues[T comparable](collection []T) map[T]int"
---
Counts the number of occurrences of each element in the collection.
```go
lo.CountValues([]int{1, 2, 2})
// map[int]int{1: 1, 2: 2}
```
================================================
FILE: docs/data/core-countvaluesby.md
================================================
---
name: CountValuesBy
slug: countvaluesby
sourceRef: slice.go#L623
category: core
subCategory: slice
playUrl: https://go.dev/play/p/2U0dG1SnOmS
variantHelpers:
- core#slice#countvaluesby
similarHelpers:
- core#slice#countvalues
- core#slice#groupby
- core#slice#map
- core#slice#mapvalues
position: 0
signatures:
- "func CountValuesBy[T any, U comparable](collection []T, transform func(item T) U) map[U]int"
---
Counts the number of each transformed value (equivalent to Map followed by CountValues).
```go
isEven := func(v int) bool {
return v%2 == 0
}
lo.CountValuesBy([]int{1, 2, 2}, isEven)
// map[bool]int{false: 1, true: 2}
```
================================================
FILE: docs/data/core-crossjoinbyerrx.md
================================================
---
name: CrossJoinByErrX
slug: crossjoinbyerrx
sourceRef: tuples.go#L1320
category: core
subCategory: tuple
signatures:
- "func CrossJoinByErr2[A any, B any, Out any](listA []A, listB []B, transform func(a A, b B) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr3[A any, B any, C any, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr4[A any, B any, C any, D any, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr5[A any, B any, C any, D any, E any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr6[A any, B any, C any, D any, E any, F any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr7[A any, B any, C any, D any, E any, F any, G any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr8[A any, B any, C any, D any, E any, F any, G any, H any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error)"
- "func CrossJoinByErr9[A any, B any, C any, D any, E any, F any, G any, H any, I any, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error)"
variantHelpers:
- core#tuple#crossjoinbyerrx
similarHelpers:
- core#tuple#crossjoinbyx
position: 61
---
Computes a cartesian product and projects each combination through a function that can return an error. Stops iteration immediately when an error is encountered and returns the zero value (nil for slices).
Variants: `CrossJoinByErr2..CrossJoinByErr9`
```go
result, err := lo.CrossJoinByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
if a == "b" {
return "", fmt.Errorf("b not allowed")
}
return fmt.Sprintf("%s-%d", a, b), nil
})
// []string(nil), error("b not allowed")
```
```go
result, err := lo.CrossJoinByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
return fmt.Sprintf("%s-%d", a, b), nil
})
// []string{"a-1", "a-2", "b-1", "b-2"}, nil
```
Returns an empty list if any input list is empty.
================================================
FILE: docs/data/core-crossjoinbyx.md
================================================
---
name: CrossJoinByX
slug: crossjoinbyx
sourceRef: tuples.go#L956
category: core
subCategory: tuple
signatures:
- "func CrossJoinBy2[A, B, Out any](listA []A, listB []B, transform func(a A, b B) Out) []Out"
- "func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) Out) []Out"
- "func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) Out) []Out"
- "func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) Out) []Out"
- "func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) Out) []Out"
- "func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) Out) []Out"
- "func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out"
- "func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out"
playUrl: https://go.dev/play/p/8Y7btpvuA-C
variantHelpers:
- core#tuple#crossjoinbyx
- core#tuple#crossjoinbyerrx
similarHelpers:
- core#tuple#zipx
- core#tuple#unzipx
- core#tuple#zipbyx
- core#tuple#unzipbyx
- core#slice#product
- core#slice#productby
position: 62
---
Computes a cartesian product and projects each combination through a function. Variants support 2 up to 9 input slices.
Variants: `CrossJoinBy2..CrossJoinBy9`
```go
a := []int{1,2}
b := []string{"x","y"}
out := lo.CrossJoinBy2(a, b, func(x int, y string) string {
return fmt.Sprintf("%d-%s", x, y)
})
```
================================================
FILE: docs/data/core-crossjoinx.md
================================================
---
name: CrossJoinX
slug: crossjoinx
sourceRef: tuples.go#L891
category: core
subCategory: tuple
signatures:
- "func CrossJoin2[A, B any](listA []A, listB []B) []Tuple2[A, B]"
- "func CrossJoin3[A, B, C any](listA []A, listB []B, listC []C) []Tuple3[A, B, C]"
- "func CrossJoin4[A, B, C, D any](listA []A, listB []B, listC []C, listD []D) []Tuple4[A, B, C, D]"
- "func CrossJoin5[A, B, C, D, E any](listA []A, listB []B, listC []C, listD []D, listE []E) []Tuple5[A, B, C, D, E]"
- "func CrossJoin6[A, B, C, D, E, F any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F) []Tuple6[A, B, C, D, E, F]"
- "func CrossJoin7[A, B, C, D, E, F, G any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G) []Tuple7[A, B, C, D, E, F, G]"
- "func CrossJoin8[A, B, C, D, E, F, G, H any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H) []Tuple8[A, B, C, D, E, F, G, H]"
- "func CrossJoin9[A, B, C, D, E, F, G, H, I any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I) []Tuple9[A, B, C, D, E, F, G, H, I]"
playUrl: https://go.dev/play/p/3VFppyL9FDU
variantHelpers:
- core#tuple#crossjoinx
similarHelpers:
- core#tuple#tuplex
- core#intersect#product
- core#intersect#productby
- core#map#entries
position: 50
---
Computes the cartesian product of input slices, returning tuples of all combinations. Variants support 2 up to 9 input slices.
Variants: `CrossJoin2..CrossJoin9`
```go
a := []int{1,2}
b := []string{"x","y"}
pairs := lo.CrossJoin2(a, b)
```
================================================
FILE: docs/data/core-cut.md
================================================
---
name: Cut
slug: cut
sourceRef: slice.go#L774
category: core
subCategory: slice
playUrl: https://go.dev/play/p/GiL3qhpIP3f
variantHelpers:
- core#slice#cut
similarHelpers:
- core#slice#cutprefix
- core#slice#cutsuffix
- core#slice#slice
- core#slice#trim
- core#slice#trimprefix
- core#slice#trimsuffix
- core#slice#partitionby
position: 0
signatures:
- "func Cut[T comparable, Slice ~[]T](collection Slice, separator Slice) (before Slice, after Slice, found bool)"
---
Slices the collection around the first instance of the separator, returning before, after, and whether it was found.
```go
left, right, found := lo.Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b", "c", "d"})
// left: []string{"a"}
// right: []string{"e", "f", "g"}
// found: true
left, right, found = lo.Cut([]string{"a", "b", "c"}, []string{"z"})
// left: []string{"a", "b", "c"}
// right: []string{}
// found: false
```
================================================
FILE: docs/data/core-cutprefix.md
================================================
---
name: CutPrefix
slug: cutprefix
sourceRef: slice.go#L800
category: core
subCategory: slice
playUrl: https://go.dev/play/p/P1scQj53aFa
variantHelpers:
- core#slice#cutprefix
similarHelpers:
- core#slice#cutsuffix
- core#slice#cut
- core#slice#trimprefix
- core#slice#trimleft
- core#slice#slice
- core#slice#drop
- core#slice#hasprefix
position: 0
signatures:
- "func CutPrefix[T comparable, Slice ~[]T](collection Slice, separator Slice) (after Slice, found bool)"
---
Returns the collection without the provided leading prefix and a boolean indicating whether it was present.
```go
right, found := lo.CutPrefix([]string{"a", "b", "c", "d"}, []string{"a", "b", "c"})
// right: []string{"d"}
// found: true
right, found = lo.CutPrefix([]string{"a", "b", "c"}, []string{"b"})
// right: []string{"a", "b", "c"}
// found: false
```
================================================
FILE: docs/data/core-cutsuffix.md
================================================
---
name: CutSuffix
slug: cutsuffix
sourceRef: slice.go#L821
category: core
subCategory: slice
playUrl: https://go.dev/play/p/7FKfBFvPTaT
variantHelpers:
- core#slice#cutsuffix
similarHelpers:
- core#slice#cutprefix
- core#slice#cut
- core#slice#trimsuffix
- core#slice#trimright
- core#slice#slice
- core#slice#dropright
- core#slice#hassuffix
position: 0
signatures:
- "func CutSuffix[T comparable, Slice ~[]T](collection Slice, separator Slice) (before Slice, found bool)"
---
Returns the collection without the provided trailing suffix and a boolean indicating whether it was present.
```go
left, found := lo.CutSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"f", "g"})
// left: []string{"a", "b", "c", "d", "e"}
// found: true
left, found = lo.CutSuffix([]string{"a", "b", "c"}, []string{"b"})
// left: []string{"a", "b", "c"}
// found: false
```
================================================
FILE: docs/data/core-difference.md
================================================
---
name: Difference
slug: difference
sourceRef: intersect.go#L124
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/pKE-JgzqRpz
variantHelpers:
- core#intersect#difference
similarHelpers:
- core#intersect#intersect
- core#intersect#union
- core#intersect#without
- core#intersect#withoutby
- core#slice#uniq
- core#slice#uniqby
position: 90
signatures:
- "func Difference[T comparable, Slice ~[]T](list1 Slice, list2 Slice) (Slice, Slice)"
---
Returns the difference between two collections. The first slice contains elements absent from list2; the second contains elements absent from list1.
```go
left, right := lo.Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6})
// []int{1, 3, 4, 5}, []int{6}
```
================================================
FILE: docs/data/core-dispatchingstrategy.md
================================================
---
name: DispatchingStrategy
slug: dispatchingstrategy
sourceRef: channel.go#L78
category: core
subCategory: channel
signatures:
- "func DispatchingStrategyRoundRobin[T any](msg T, index uint64, channels []<-chan T) int"
- "func DispatchingStrategyRandom[T any](msg T, index uint64, channels []<-chan T) int"
- "func DispatchingStrategyWeightedRandom[T any](weights []int) DispatchingStrategy[T]"
- "func DispatchingStrategyFirst[T any](msg T, index uint64, channels []<-chan T) int"
- "func DispatchingStrategyLeast[T any](msg T, index uint64, channels []<-chan T) int"
- "func DispatchingStrategyMost[T any](msg T, index uint64, channels []<-chan T) int"
variantHelpers:
- core#channel#dispatchingstrategyroundrobin
- core#channel#dispatchingstrategyrandom
- core#channel#dispatchingstrategyweightedrandom
- core#channel#dispatchingstrategyfirst
- core#channel#dispatchingstrategyleast
- core#channel#dispatchingstrategymost
similarHelpers:
- core#channel#channeldispatcher
position: 270
---
DispatchingStrategyRoundRobin distributes messages to channels in round-robin order.
```go
strategy := lo.DispatchingStrategyRoundRobin[int]
index := strategy(42, 0, []chan int{ch1, ch2, ch3})
// Returns 0, then 1, then 2, then 0, cycling through channels
```
DispatchingStrategyRandom distributes messages to a random channel.
```go
strategy := lo.DispatchingStrategyRandom[int]
index := strategy(42, 0, []chan int{ch1, ch2, ch3})
// Returns a random channel index 0, 1, or 2
```
DispatchingStrategyWeightedRandom distributes messages to channels based on weights.
```go
weights := []int{1, 3, 6} // Channel 0: 10%, Channel 1: 30%, Channel 2: 60%
strategy := lo.DispatchingStrategyWeightedRandom[int](weights)
index := strategy(42, 0, []chan int{ch1, ch2, ch3})
// Returns 2 most often, 1 sometimes, 0 rarely
```
DispatchingStrategyFirst distributes messages to the first non-full channel.
```go
strategy := lo.DispatchingStrategyFirst[int]
index := strategy(42, 0, []chan int{ch1, ch2, ch3})
// Always returns 0 if ch1 is not full
```
DispatchingStrategyLeast distributes messages to the channel with the fewest items.
```go
strategy := lo.DispatchingStrategyLeast[int]
index := strategy(42, 0, []chan int{ch1, ch2, ch3})
// Returns the index of the channel with the smallest buffer size
```
DispatchingStrategyMost distributes messages to the channel with the most items.
```go
strategy := lo.DispatchingStrategyMost[int]
index := strategy(42, 0, []chan int{ch1, ch2, ch3})
// Returns the index of the channel with the largest buffer size
```
================================================
FILE: docs/data/core-drop.md
================================================
---
name: Drop
slug: drop
sourceRef: slice.go#L441
category: core
subCategory: slice
playUrl: https://go.dev/play/p/JswS7vXRJP2
variantHelpers:
- core#slice#drop
similarHelpers:
- core#slice#dropright
- core#slice#dropwhile
- core#slice#dropbyindex
- core#slice#slice
- core#slice#droprightwhile
- core#slice#cutprefix
- core#slice#take
- core#slice#takewhile
position: 170
signatures:
- "func Drop[T any, Slice ~[]T](collection Slice, n int) Slice"
---
Drops n elements from the beginning of a slice.
```go
lo.Drop([]int{0, 1, 2, 3, 4, 5}, 2)
// []int{2, 3, 4, 5}
```
================================================
FILE: docs/data/core-dropbyindex.md
================================================
---
name: DropByIndex
slug: dropbyindex
sourceRef: slice.go#L501
category: core
subCategory: slice
playUrl: https://go.dev/play/p/bPIH4npZRxS
variantHelpers:
- core#slice#dropbyindex
similarHelpers:
- core#slice#drop
- core#slice#dropright
- core#slice#dropwhile
- core#slice#droprightwhile
- core#slice#slice
- core#slice#withoutnth
- core#slice#splice
position: 210
signatures:
- "func DropByIndex[T any, Slice ~[]T](collection Slice, indexes ...int) Slice"
---
Drops elements from a slice by index. Negative indexes count from the end.
```go
lo.DropByIndex([]int{0, 1, 2, 3, 4, 5}, 2, 4, -1)
// []int{0, 1, 3}
```
================================================
FILE: docs/data/core-dropright.md
================================================
---
name: DropRight
slug: dropright
sourceRef: slice.go#L457
category: core
subCategory: slice
playUrl: https://go.dev/play/p/GG0nXkSJJa3
variantHelpers:
- core#slice#dropright
similarHelpers:
- core#slice#drop
- core#slice#dropwhile
- core#slice#droprightwhile
- core#slice#dropbyindex
- core#slice#slice
- core#slice#cutsuffix
- core#slice#trimright
position: 180
signatures:
- "func DropRight[T any, Slice ~[]T](collection Slice, n int) Slice"
---
Drops n elements from the end of a slice.
```go
lo.DropRight([]int{0, 1, 2, 3, 4, 5}, 2)
// []int{0, 1, 2, 3}
```
================================================
FILE: docs/data/core-droprightwhile.md
================================================
---
name: DropRightWhile
slug: droprightwhile
sourceRef: slice.go#L486
category: core
subCategory: slice
playUrl: https://go.dev/play/p/HBh8pHl-ZZz
variantHelpers:
- core#slice#droprightwhile
similarHelpers:
- core#slice#dropwhile
- core#slice#drop
- core#slice#dropright
- core#slice#dropbyindex
- core#slice#filterreject
- core#slice#partitionby
position: 200
signatures:
- "func DropRightWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice"
---
Drops elements from the end while the predicate returns true.
```go
lo.DropRightWhile([]string{"a", "aa", "aaa", "aa", "aa"}, func(val string) bool {
return len(val) <= 2
})
// []string{"a", "aa", "aaa"}
```
================================================
FILE: docs/data/core-dropwhile.md
================================================
---
name: DropWhile
slug: dropwhile
sourceRef: slice.go#L472
category: core
subCategory: slice
playUrl: https://go.dev/play/p/b_PYomVQLGy
variantHelpers:
- core#slice#dropwhile
similarHelpers:
- core#slice#droprightwhile
- core#slice#drop
- core#slice#dropright
- core#slice#dropbyindex
- core#slice#filterreject
- core#slice#partitionby
- core#slice#takewhile
position: 190
signatures:
- "func DropWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice"
---
Drops elements from the beginning while the predicate returns true.
```go
lo.DropWhile([]string{"a", "aa", "aaa", "aa", "aa"}, func(val string) bool {
return len(val) <= 2
})
// []string{"aaa", "aa", "aa"}
```
================================================
FILE: docs/data/core-durationx.md
================================================
---
name: DurationX
slug: durationx
sourceRef: time.go#L7
category: core
subCategory: time
signatures:
- "func Duration(callback func()) time.Duration"
- "func Duration0(callback func()) time.Duration"
- "func Duration1[T any](callback func() T) (T, time.Duration)"
- "func Duration2[T, U any](callback func() (T, U)) (T, U, time.Duration)"
- "func Duration3[T, U, V any](callback func() (T, U, V)) (T, U, V, time.Duration)"
- "func Duration4[T, U, V, W any](callback func() (T, U, V, W)) (T, U, V, W, time.Duration)"
- "func Duration5[T, U, V, W, X any](callback func() (T, U, V, W, X)) (T, U, V, W, X, time.Duration)"
- "func Duration6[T, U, V, W, X, Y any](callback func() (T, U, V, W, X, Y)) (T, U, V, W, X, Y, time.Duration)"
- "func Duration7[T, U, V, W, X, Y, Z any](callback func() (T, U, V, W, X, Y, Z)) (T, U, V, W, X, Y, Z, time.Duration)"
- "func Duration8[T, U, V, W, X, Y, Z, A any](callback func() (T, U, V, W, X, Y, Z, A)) (T, U, V, W, X, Y, Z, A, time.Duration)"
- "func Duration9[T, U, V, W, X, Y, Z, A, B any](callback func() (T, U, V, W, X, Y, Z, A, B)) (T, U, V, W, X, Y, Z, A, B, time.Duration)"
- "func Duration10[T, U, V, W, X, Y, Z, A, B, C any](callback func() (T, U, V, W, X, Y, Z, A, B, C)) (T, U, V, W, X, Y, Z, A, B, C, time.Duration)"
playUrl: https://go.dev/play/p/LFhKq2vY9Ty
variantHelpers:
- core#time#duration
- core#time#durationx
similarHelpers:
- core#concurrency#waitfor
- core#retry#attemptwithdelay
- core#retry#attemptwhilewithdelay
- core#retry#newdebounce
- core#retry#newdebounceby
- core#retry#newthrottle
- core#retry#newthrottleby
- core#retry#newthrottlebywithcount
position: 0
---
Measures execution time of a function. Variants return the elapsed duration alongside 0 to 10 returned values from the function.
```go
// Base variant (no return values): Duration
elapsedOnly := lo.Duration(func() {
time.Sleep(3 * time.Millisecond)
})
_ = elapsedOnly
// Zero-return variant: Duration0
elapsed := lo.Duration0(func() {
time.Sleep(10 * time.Millisecond)
})
_ = elapsed
// One-return variant: Duration1
v, dur := lo.Duration1(func() int {
time.Sleep(5 * time.Millisecond)
return 123
})
_ = v
_ = dur
// Two-return variant: Duration2
a, b, elapsed2 := lo.Duration2(func() (int, string) {
time.Sleep(2 * time.Millisecond)
return 7, "x"
})
_ = a
_ = b
_ = elapsed2
```
================================================
FILE: docs/data/core-earliest.md
================================================
---
name: Earliest
slug: earliest
sourceRef: find.go#L363
category: core
subCategory: find
playUrl: https://go.dev/play/p/pRyy0c6hsBs
variantHelpers:
- core#find#earliest
similarHelpers:
- core#find#latest
- core#find#earliestby
- core#find#latestby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
- core#find#minindex
- core#find#maxindex
- core#find#minindexby
- core#find#maxindexby
position: 180
signatures:
- "func Earliest(times ...time.Time) time.Time"
---
Searches the minimum time.Time in the provided arguments. Returns zero value when the input is empty.
```go
t1 := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
t2 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
min := lo.Earliest(t2, t1)
// 2023-01-01 00:00:00 +0000 UTC
```
================================================
FILE: docs/data/core-earliestby.md
================================================
---
name: EarliestBy
slug: earliestby
sourceRef: find.go#L462
category: core
subCategory: find
playUrl: https://go.dev/play/p/0XvCF6vuLXC
variantHelpers:
- core#find#earliestby
similarHelpers:
- core#find#earliestbyerr
- core#find#latestby
- core#find#earliest
- core#find#latest
- core#find#minby
- core#find#maxby
- core#find#minindexby
- core#find#maxindexby
- core#find#findby
- core#find#findkeyby
- core#find#findduplicatesby
- core#find#finduniquesby
position: 190
signatures:
- "func EarliestBy[T any](collection []T, iteratee func(item T) time.Time) T"
---
Searches a collection for the element with the minimum time extracted by the predicate. Returns zero value when the collection is empty.
```go
type Event struct{ At time.Time }
events := []Event{{At: time.Now().Add(2 * time.Hour)}, {At: time.Now()}}
first := lo.EarliestBy(events, func(e Event) time.Time {
return e.At
})
```
================================================
FILE: docs/data/core-earliestbyerr.md
================================================
---
name: EarliestByErr
slug: earliestbyerr
sourceRef: find.go#L484
category: core
subCategory: find
playUrl: https://go.dev/play/p/zJUBUj7ANvq
variantHelpers:
- core#find#earliestbyerr
similarHelpers:
- core#find#earliestby
- core#find#latestby
- core#find#earliest
- core#find#latest
- core#find#minby
- core#find#maxby
position: 191
signatures:
- "func EarliestByErr[T any](collection []T, iteratee func(item T) (time.Time, error)) (T, error)"
---
Searches a collection for the element with the minimum time extracted by the predicate. Returns zero value and nil error when the collection is empty.
If the iteratee returns an error, iteration stops and the error is returned.
```go
type Event struct{ At time.Time }
events := []Event{{At: time.Now().Add(2 * time.Hour)}, {At: time.Now()}}
first, err := lo.EarliestByErr(events, func(e Event) (time.Time, error) {
return e.At, nil
})
// Event,
```
Example with error:
```go
type Event struct{ At time.Time }
events := []Event{{At: time.Now()}, {At: time.Now().Add(time.Hour)}}
first, err := lo.EarliestByErr(events, func(e Event) (time.Time, error) {
if e.At.After(time.Now().Add(30 * time.Minute)) {
return time.Time{}, fmt.Errorf("event too far in the future")
}
return e.At, nil
})
// Event, error("event too far in the future")
```
================================================
FILE: docs/data/core-elementsmatch.md
================================================
---
name: ElementsMatch
slug: elementsmatch
sourceRef: intersect.go#L247
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/XWSEM4Ic_t0
variantHelpers:
- core#intersect#elementsmatch
similarHelpers:
- core#intersect#elementsmatchby
- core#intersect#contains
- core#intersect#containsby
- core#intersect#intersect
- core#intersect#difference
- core#intersect#union
- core#intersect#every
- core#intersect#everyby
- core#intersect#some
- core#intersect#someby
- core#intersect#none
- core#intersect#noneby
position: 150
signatures:
- "func ElementsMatch[T comparable, Slice ~[]T](list1 Slice, list2 Slice) bool"
---
Returns true if lists contain the same set of elements (including empty set). If there are duplicate elements, occurrences must match. Order is not checked.
```go
lo.ElementsMatch([]int{1, 1, 2}, []int{2, 1, 1})
// true
```
================================================
FILE: docs/data/core-elementsmatchby.md
================================================
---
name: ElementsMatchBy
slug: elementsmatchby
sourceRef: intersect.go#L255
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/XWSEM4Ic_t0
variantHelpers:
- core#intersect#elementsmatchby
similarHelpers:
- core#intersect#elementsmatch
- core#intersect#containsby
- core#intersect#contains
- core#intersect#intersect
- core#intersect#difference
- core#intersect#union
- core#intersect#everyby
- core#intersect#every
- core#intersect#someby
- core#intersect#some
- core#intersect#noneby
- core#intersect#none
position: 160
signatures:
- "func ElementsMatchBy[T any, K comparable](list1 []T, list2 []T, iteratee func(item T) K) bool"
---
Returns true if lists contain the same set of keys computed by the predicate, with matching multiplicities. Order is not checked.
```go
type Item struct{
ID string
}
lo.ElementsMatchBy(
[]Item{{"a"}, {"b"}},
[]Item{{"b"}, {"a"}},
func(i Item) string {
return i.ID
},
)
// true
```
================================================
FILE: docs/data/core-ellipsis.md
================================================
---
name: Ellipsis
slug: ellipsis
sourceRef: string.go#L235
category: core
subCategory: string
playUrl: https://go.dev/play/p/qE93rgqe1TW
variantHelpers:
- core#string#ellipsis
similarHelpers:
- core#string#substring
- core#string#runelength
- core#string#trim
- core#string#capitalize
position: 100
signatures:
- "func Ellipsis(str string, length int) string"
---
Trims and truncates a string to the specified length in runes (Unicode code points) and appends an ellipsis if truncated. Multi-byte characters such as emoji or CJK ideographs are never split in the middle.
```go
lo.Ellipsis(" Lorem Ipsum ", 5)
// "Lo..."
str = lo.Ellipsis("Lorem Ipsum", 100)
// "Lorem Ipsum"
str = lo.Ellipsis("Lorem Ipsum", 3)
// "..."
str = lo.Ellipsis("hello 世界! 你好", 8)
// "hello..."
str = lo.Ellipsis("🏠🐶🐱🌟", 4)
// "🏠🐶🐱🌟"
```
================================================
FILE: docs/data/core-empty.md
================================================
---
name: Empty
slug: empty
sourceRef: type_manipulation.go#L132
category: core
subCategory: type
signatures:
- "func Empty[T any]() T"
variantHelpers:
- core#type#empty
similarHelpers:
- core#type#isempty
- core#type#isnotempty
- core#type#coalesceorempty
- core#type#coalescesliceorempty
- core#type#coalescemaporempty
position: 120
---
Returns the zero value for the specified type. This is useful when you need an empty value of a specific type.
```go
result := lo.Empty[string]()
// "" (zero value for string)
result = lo.Empty[int]()
// 0 (zero value for int)
result = lo.Empty[[]int]()
// []int{} (zero value for slice)
result = lo.Empty[map[string]int]()
// map[string]int{} (zero value for map)
result = lo.Empty[*int]()
// nil (zero value for pointer)
```
================================================
FILE: docs/data/core-emptyabletoptr.md
================================================
---
name: EmptyableToPtr
slug: emptyabletoptr
sourceRef: type_manipulation.go#L41
category: core
subCategory: type
signatures:
- "func EmptyableToPtr[T any](x T) *T"
variantHelpers:
- core#type#emptyabletoptr
similarHelpers:
- core#type#toptr
- core#type#fromptr
- core#type#fromptror
- core#type#tosliceptr
- core#type#fromsliceptr
position: 105
---
Returns a pointer to the provided value, or nil if the value is empty (zero value). This is useful for avoiding pointers to empty values.
```go
ptr := lo.EmptyableToPtr("")
// nil (because empty string is zero value)
ptr = lo.EmptyableToPtr("hello")
// *string pointing to "hello"
ptr = lo.EmptyableToPtr(0)
// nil (because 0 is zero value for int)
ptr = lo.EmptyableToPtr(42)
// *int pointing to 42
```
================================================
FILE: docs/data/core-entries.md
================================================
---
name: Entries
slug: entries
sourceRef: map.go#L179
category: core
subCategory: map
playUrl: https://go.dev/play/p/_t4Xe34-Nl5
variantHelpers:
- core#map#entries
similarHelpers:
- core#map#fromentries
- core#map#keys
- core#map#values
- core#map#toentries
position: 120
signatures:
- "func Entries[K comparable, V any](in map[K]V) []Entry[K, V]"
---
Transforms a map into a slice of key/value pairs.
```go
entries := lo.Entries(map[string]int{"foo": 1, "bar": 2})
// []lo.Entry[string, int]{ {Key: "foo", Value: 1}, {Key: "bar", Value: 2} }
```
================================================
FILE: docs/data/core-errorsas.md
================================================
---
name: ErrorsAs
slug: errorsas
sourceRef: errors.go#L351
category: core
subCategory: error-handling
playUrl: https://go.dev/play/p/8wk5rH8UfrE
variantHelpers:
- core#error-handling#erroras
similarHelpers:
- core#error-handling#validate
- core#error-handling#mustx
- core#error-handling#tryx
- core#error-handling#tryorx
- core#error-handling#trycatch
- core#error-handling#trywitherrorvalue
- core#error-handling#trycatchwitherrorvalue
- core#error-handling#assert
position: 70
signatures:
- "func ErrorsAs[T error](err error) (T, bool)"
---
A generic wrapper around `errors.As` that returns the typed error and a boolean indicating success.
```go
if rateLimitErr, ok := lo.ErrorsAs[*RateLimitError](err); ok {
// handle
}
```
================================================
FILE: docs/data/core-every.md
================================================
---
name: Every
slug: every
sourceRef: intersect.go#L29
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/W1EvyqY6t9j
variantHelpers:
- core#intersect#every
similarHelpers:
- core#intersect#everyby
- core#intersect#some
- core#intersect#someby
- core#intersect#none
- core#intersect#noneby
- core#intersect#contains
- core#intersect#containsby
- core#intersect#elementsmatch
- core#intersect#elementsmatchby
position: 20
signatures:
- "func Every[T comparable](collection []T, subset []T) bool"
---
Returns true if all elements of a subset are contained in a collection, or if the subset is empty.
```go
ok := lo.Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// true
```
================================================
FILE: docs/data/core-everyby.md
================================================
---
name: EveryBy
slug: everyby
sourceRef: intersect.go#L41
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/dn1-vhHsq9x
variantHelpers:
- core#intersect#everyby
similarHelpers:
- core#intersect#every
- core#intersect#someby
- core#intersect#some
- core#intersect#noneby
- core#intersect#none
- core#intersect#containsby
- core#intersect#contains
- core#intersect#elementsmatchby
- core#intersect#elementsmatch
position: 30
signatures:
- "func EveryBy[T any](collection []T, predicate func(item T) bool) bool"
---
Returns true if the predicate returns true for all elements in the collection, or if the collection is empty.
```go
ok := lo.EveryBy(
[]int{1, 2, 3, 4},
func(x int) bool {
return x < 5
},
)
// true
```
================================================
FILE: docs/data/core-fanin.md
================================================
---
name: FanIn
slug: fanin
sourceRef: channel.go#L18
category: core
subCategory: channel
signatures:
- "func FanIn[T any](channelBufferCap int, upstreams ...<-chan T) <-chan T"
similarHelpers:
- core#channel#fanout
- core#channel#channeldispatcher
position: 254
---
FanIn merges multiple upstream channels into a single downstream channel, reading from all input channels and forwarding their values to one output channel. The channelBufferCap parameter sets the buffer size of the output channel.
```go
ch1 := make(chan int, 2)
ch2 := make(chan int, 2)
ch1 <- 1
ch1 <- 2
ch2 <- 3
ch2 <- 4
close(ch1)
close(ch2)
merged := lo.FanIn(10, ch1, ch2)
var result []int
for item := range merged {
result = append(result, item)
}
// result: []int{1, 2, 3, 4} (order may vary as goroutine scheduling differs)
```
================================================
FILE: docs/data/core-fanout.md
================================================
---
name: FanOut
slug: fanout
sourceRef: channel.go#L18
category: core
subCategory: channel
signatures:
- "func FanOut[T any](count, channelsBufferCap int, upstream <-chan T) []<-chan T"
similarHelpers:
- core#channel#fanin
- core#channel#channeldispatcher
- it#channel#channelseq
position: 256
---
FanOut splits a single channel into multiple channels.
```go
upstream := make(chan int, 6)
for i := 0; i < 6; i++ {
upstream <- i
}
close(upstream)
downstreams := lo.FanOut(3, 10, upstream)
// Returns 3 channels, each receiving 2 items
```
================================================
FILE: docs/data/core-fill.md
================================================
---
name: Fill
slug: fill
sourceRef: slice.go#L338
category: core
subCategory: slice
playUrl: https://go.dev/play/p/VwR34GzqEub
variantHelpers:
- core#slice#fill
similarHelpers:
- core#slice#repeat
- core#slice#slice
- core#slice#flatten
- core#slice#chunk
- core#slice#chunkentries
- core#slice#interleave
- core#slice#reverse
- core#slice#shuffle
- core#slice#sample
- core#slice#drop
- core#slice#dropwhile
- core#slice#dropright
- core#slice#droprightwhile
position: 200
signatures:
- "func Fill[T Clonable[T], Slice ~[]T](collection Slice, initial T) Slice"
---
Fills a slice with clones of the provided initial value.
```go
type foo struct{ bar string }
func (f foo) Clone() foo {
return foo{f.bar}
}
lo.Fill([]foo{{"a"}, {"a"}}, foo{"b"})
// []foo{{"b"}, {"b"}}
```
================================================
FILE: docs/data/core-filter.md
================================================
---
name: Filter
slug: filter
sourceRef: slice.go#L12
category: core
subCategory: slice
playUrl: https://go.dev/play/p/Apjg3WeSi7K
similarHelpers:
- core#slice#reject
- core#slice#filtererr
- core#slice#filtermap
- core#slice#filterreject
- core#slice#rejectmap
- core#slice#takefilter
- parallel#slice#filter
- mutable#slice#filter
variantHelpers:
- core#slice#filter
position: 0
signatures:
- "func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice"
---
Iterates over a collection and returns a slice of all the elements the predicate function returns `true` for.
```go
even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool {
return x%2 == 0
})
// []int{2, 4}
```
================================================
FILE: docs/data/core-filtererr.md
================================================
---
name: FilterErr
slug: filtererr
sourceRef: slice.go#L27
category: core
subCategory: slice
signatures:
- "func FilterErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error)"
playUrl: https://go.dev/play/p/Apjg3WeSi7K
variantHelpers:
- core#slice#filtererr
similarHelpers:
- core#slice#filter
- core#slice#reject
- core#slice#filtermap
- core#slice#filterreject
position: 5
---
Iterates over a collection and returns a slice of all the elements the predicate function returns `true` for. If the predicate returns an error, iteration stops immediately and returns the error.
```go
even, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, index int) (bool, error) {
if x == 3 {
return false, errors.New("number 3 is not allowed")
}
return x%2 == 0, nil
})
// []int(nil), error("number 3 is not allowed")
```
```go
even, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, index int) (bool, error) {
return x%2 == 0, nil
})
// []int{2, 4}, nil
```
================================================
FILE: docs/data/core-filterkeys.md
================================================
---
name: FilterKeys
slug: filterkeys
sourceRef: map.go#L350
category: core
subCategory: map
playUrl: https://go.dev/play/p/OFlKXlPrBAe
variantHelpers:
- core#map#filterkeys
similarHelpers:
- core#map#filtervalues
- core#map#filterkeyserr
- core#map#pickbykeys
- core#map#omitbykeys
- core#map#pickbyvalues
- core#map#omitbyvalues
position: 230
signatures:
- "func FilterKeys[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []K"
---
Returns a slice of keys for which the predicate is true.
```go
kv := map[int]string{1:"foo", 2:"bar", 3:"baz"}
result := lo.FilterKeys(kv, func(k int, v string) bool {
return v == "foo"
})
// []int{1}
```
================================================
FILE: docs/data/core-filterkeyserr.md
================================================
---
name: FilterKeysErr
slug: filterkeyserr
sourceRef: map.go#L498
category: core
subCategory: map
signatures:
- "func FilterKeysErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]K, error)"
playUrl: https://go.dev/play/p/j2gUQzCTu4t
variantHelpers:
- core#map#filterkeyserr
similarHelpers:
- core#map#filterkeys
- core#map#filtervalueserr
- core#slice#filter
position: 235
---
Transforms a map into a slice of keys based on a predicate that can return an error. It is a mix of Filter() and Keys() with error handling. If the predicate returns true, the key is included. If the predicate returns an error, iteration stops immediately and returns the error.
```go
kv := map[int]string{1:"foo", 2:"bar", 3:"baz"}
result, err := lo.FilterKeysErr(kv, func(k int, v string) (bool, error) {
if k == 3 {
return false, errors.New("key 3 not allowed")
}
return v == "foo", nil
})
// []int(nil), error("key 3 not allowed")
```
```go
kv := map[int]string{1:"foo", 2:"bar", 3:"baz"}
result, err := lo.FilterKeysErr(kv, func(k int, v string) (bool, error) {
return v == "bar", nil
})
// []int{2}, nil
```
================================================
FILE: docs/data/core-filtermap.md
================================================
---
name: FilterMap
slug: filtermap
sourceRef: slice.go#L58
category: core
subCategory: slice
playUrl: https://go.dev/play/p/CgHYNUpOd1I
variantHelpers:
- core#slice#filtermap
similarHelpers:
- core#slice#map
- core#slice#filter
- core#slice#uniqmap
- core#slice#rejectmap
- core#slice#filtermaptoslice
- core#slice#takefilter
- parallel#slice#filtermap
position: 30
signatures:
- "func FilterMap[T any, R any](collection []T, callback func(item T, index int) (R, bool)) []R"
---
Returns a slice obtained after both filtering and mapping using the given callback function.
The callback function should return two values: the result of the mapping operation and whether the result element should be included or not.
```go
matching := lo.FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", true
}
return "", false
})
// []string{"xpu", "xpu"}
```
================================================
FILE: docs/data/core-filtermaptoslice.md
================================================
---
name: FilterMapToSlice
slug: filtermaptoslice
sourceRef: map.go#L430
category: core
subCategory: map
playUrl: https://go.dev/play/p/jgsD_Kil9pV
variantHelpers:
- core#map#filtermaptoslice
similarHelpers:
- core#map#maptoslice
- core#slice#filtermap
- core#slice#filterreject
- core#map#filterkeys
- core#map#filtervalues
- core#map#filtermaptosliceerr
position: 220
signatures:
- "func FilterMapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(key K, value V) (R, bool)) []R"
---
Transforms a map into a slice using an predicate that returns a value and a boolean to include it.
```go
kv := map[int]int64{1:1, 2:2, 3:3, 4:4}
result := lo.FilterMapToSlice(kv, func(k int, v int64) (string, bool) {
return fmt.Sprintf("%d_%d", k, v), k%2 == 0
})
// []string{"2_2", "4_4"}
```
================================================
FILE: docs/data/core-filtermaptosliceerr.md
================================================
---
name: FilterMapToSliceErr
slug: filtermaptosliceerr
sourceRef: map.go#L443
category: core
subCategory: map
signatures:
- "func FilterMapToSliceErr[K comparable, V any, R any](in map[K]V, iteratee func(key K, value V) (R, bool, error)) ([]R, error)"
playUrl: https://go.dev/play/p/YjFEORLBWvk
variantHelpers:
- core#map#filtermaptosliceerr
similarHelpers:
- core#map#filtermaptoslice
- core#map#maptosliceerr
- core#slice#filtermap
- core#slice#filtermaperr
position: 225
---
Transforms a map into a slice using a predicate that returns a value, a boolean to include it, and an error. Stops iteration immediately on error.
```go
kv := map[int]int64{1:1, 2:2, 3:3, 4:4}
result, err := lo.FilterMapToSliceErr(kv, func(k int, v int64) (string, bool, error) {
if k == 3 {
return "", false, fmt.Errorf("key 3 not allowed")
}
return fmt.Sprintf("%d_%d", k, v), k%2 == 0, nil
})
// []string(nil), error("key 3 not allowed")
```
```go
kv := map[int]int64{1:1, 2:2, 3:3, 4:4}
result, err := lo.FilterMapToSliceErr(kv, func(k int, v int64) (string, bool, error) {
return fmt.Sprintf("%d_%d", k, v), k%2 == 0, nil
})
// []string{"2_2", "4_4"}, nil
```
================================================
FILE: docs/data/core-filterreject.md
================================================
---
name: FilterReject
slug: filterreject
sourceRef: slice.go#L565
category: core
subCategory: slice
playUrl: https://go.dev/play/p/lHSEGSznJjB
variantHelpers:
- core#slice#filterreject
similarHelpers:
- core#slice#filter
- core#slice#reject
- core#slice#partitionby
- mutable#slice#filterreject
position: 280
signatures:
- "func FilterReject[T any, Slice ~[]T](collection Slice, predicate func(T, int) bool) (kept Slice, rejected Slice)"
---
Returns two slices: elements kept (predicate true) and elements rejected (predicate false).
```go
kept, rejected := lo.FilterReject(
[]int{1, 2, 3, 4},
func(x int, _ int) bool {
return x%2 == 0
},
)
// kept: []int{2, 4}
// rejected: []int{1, 3}
```
================================================
FILE: docs/data/core-filterslicetomap.md
================================================
---
name: FilterSliceToMap
slug: filterslicetomap
sourceRef: slice.go#L414
category: core
subCategory: slice
playUrl: https://go.dev/play/p/eurMiQEqey2
variantHelpers:
- core#slice#filterslicetomap
similarHelpers:
- core#slice#filter
- core#slice#map
- core#slice#groupby
- core#slice#keyby
position: 260
signatures:
- "func FilterSliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V, bool)) map[K]V"
---
Transforms elements to key/value pairs and includes them in the result only when the transform's boolean is true.
```go
list := []string{"a", "aa", "aaa"}
m := lo.FilterSliceToMap(list, func(str string) (string, int, bool) {
return str, len(str), len(str) > 1
})
// map[string]int{"aa": 2, "aaa": 3}
```
================================================
FILE: docs/data/core-filtervalues.md
================================================
---
name: FilterValues
slug: filtervalues
sourceRef: map.go#L365
category: core
subCategory: map
playUrl: https://go.dev/play/p/YVD5r_h-LX-
variantHelpers:
- core#map#filtervalues
similarHelpers:
- core#map#filterkeys
- core#map#filtervalueserr
- core#map#pickbyvalues
- core#map#omitbyvalues
- core#map#pickbykeys
- core#map#omitbykeys
position: 240
signatures:
- "func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []V"
---
Returns a slice of values for which the predicate is true.
```go
kv := map[int]string{1:"foo", 2:"bar", 3:"baz"}
result := lo.FilterValues(kv, func(k int, v string) bool {
return v == "foo"
})
// []string{"foo"}
```
================================================
FILE: docs/data/core-filtervalueserr.md
================================================
---
name: FilterValuesErr
slug: filtervalueserr
sourceRef: map.go#L519
category: core
subCategory: map
signatures:
- "func FilterValuesErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]V, error)"
playUrl: https://go.dev/play/p/hKvHlqLzbdE
variantHelpers:
- core#map#filtervalueserr
similarHelpers:
- core#map#filtervalues
- core#map#filterkeyserr
- core#slice#filter
position: 245
---
Transforms a map into a slice of values based on a predicate that can return an error. It is a mix of Filter() and Values() with error handling. If the predicate returns true, the value is included. If the predicate returns an error, iteration stops immediately and returns the error.
```go
kv := map[int]string{1:"foo", 2:"bar", 3:"baz"}
result, err := lo.FilterValuesErr(kv, func(k int, v string) (bool, error) {
if k == 3 {
return false, errors.New("key 3 not allowed")
}
return v == "foo", nil
})
// []string(nil), error("key 3 not allowed")
```
```go
kv := map[int]string{1:"foo", 2:"bar", 3:"baz"}
result, err := lo.FilterValuesErr(kv, func(k int, v string) (bool, error) {
return v == "bar", nil
})
// []string{"bar"}, nil
```
================================================
FILE: docs/data/core-find.md
================================================
---
name: Find
slug: find
sourceRef: find.go#L72
category: core
subCategory: find
playUrl: https://go.dev/play/p/Eo7W0lvKTky
variantHelpers:
- core#find#find
similarHelpers:
- core#find#findorelse
- core#find#findkey
- core#find#findindexof
- core#find#finderr
- core#slice#filter
- core#slice#first
- core#slice#last
position: 40
signatures:
- "func Find[T any](collection []T, predicate func(item T) bool) (T, bool)"
---
Searches for an element in a slice based on a predicate and returns the element with a boolean indicating success.
```go
value, ok := lo.Find([]string{"a", "b", "c", "d"}, func(i string) bool {
return i == "b"
})
// "b", true
value, ok = lo.Find([]string{"foobar"}, func(i string) bool {
return i == "b"
})
// "", false
```
================================================
FILE: docs/data/core-findduplicates.md
================================================
---
name: FindDuplicates
slug: findduplicates
sourceRef: find.go#L207
category: core
subCategory: find
playUrl: https://go.dev/play/p/muFgL_XBwoP
variantHelpers:
- core#find#findduplicates
similarHelpers:
- core#find#findduplicatesby
- core#slice#uniq
- core#slice#finduniques
position: 120
signatures:
- "func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice"
---
Returns a slice with the first occurrence of each duplicated element in the collection, preserving order.
```go
lo.FindDuplicates([]int{1, 2, 2, 1, 2, 3})
// []int{1, 2}
```
================================================
FILE: docs/data/core-findduplicatesby.md
================================================
---
name: FindDuplicatesBy
slug: findduplicatesby
sourceRef: find.go#L234
category: core
subCategory: find
playUrl: https://go.dev/play/p/LKdYdNHuGJG
variantHelpers:
- core#find#findduplicatesby
- core#find#findduplicatesbyerr
similarHelpers:
- core#find#findduplicates
- core#find#finduniques
- core#find#finduniquesby
position: 130
signatures:
- "func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice"
---
Returns a slice with the first occurrence of each duplicated element by key, preserving order.
```go
lo.FindDuplicatesBy([]int{3, 4, 5, 6, 7}, func(i int) int {
return i % 3
})
// []int{3, 4}
```
================================================
FILE: docs/data/core-findduplicatesbyerr.md
================================================
---
name: FindDuplicatesByErr
slug: findduplicatesbyerr
sourceRef: find.go#L296
category: core
subCategory: find
playUrl: https://go.dev/play/p/HiVILQqdFP0
signatures:
- "func FindDuplicatesByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error)"
variantHelpers:
- core#find#findduplicatesbyerr
similarHelpers:
- core#find#findduplicatesby
- core#find#findduplicates
- core#find#finduniques
- core#find#finduniquesby
position: 135
---
Returns a slice with the first occurrence of each duplicated element by key, preserving order. The iteratee can return an error to stop iteration immediately.
```go
result, err := lo.FindDuplicatesByErr([]int{3, 4, 5, 6, 7}, func(i int) (int, error) {
return i % 3, nil
})
// []int{3, 4},
```
Example with error:
```go
result, err := lo.FindDuplicatesByErr([]int{3, 4, 5, 6, 7}, func(i int) (int, error) {
if i == 5 {
return 0, fmt.Errorf("number 5 is not allowed")
}
return i % 3, nil
})
// []int(nil), error("number 5 is not allowed")
```
================================================
FILE: docs/data/core-finderr.md
================================================
---
name: FindErr
slug: finderr
sourceRef: find.go#L83
category: core
subCategory: find
signatures:
- "func FindErr[T any](collection []T, predicate func(item T) (bool, error)) (T, error)"
playUrl: https://go.dev/play/p/XK-qtpQWXJ9
variantHelpers: []
similarHelpers:
- core#find#find
- core#find#findorelse
- core#find#findkey
- core#find#findindexof
- core#slice#filter
position: 41
---
Searches for an element in a slice based on a predicate that can return an error. Returns the element and nil error if found. Returns zero value and nil error if not found. If the predicate returns an error, iteration stops immediately and returns zero value and the error.
```go
result, err := lo.FindErr([]string{"a", "b", "c", "d"}, func(i string) (bool, error) {
return i == "b", nil
})
// "b", nil
result, err = lo.FindErr([]string{"foobar"}, func(i string) (bool, error) {
return i == "b", nil
})
// "", nil
result, err = lo.FindErr([]string{"a", "b", "c"}, func(i string) (bool, error) {
if i == "b" {
return false, fmt.Errorf("b is not allowed")
}
return i == "b", nil
})
// "", error("b is not allowed")
```
================================================
FILE: docs/data/core-findindexof.md
================================================
---
name: FindIndexOf
slug: findindexof
sourceRef: find.go#L87
category: core
subCategory: find
playUrl: https://go.dev/play/p/XWSEM4Ic_t0
variantHelpers:
- core#find#findindexof
similarHelpers:
- core#find#find
- core#slice#indexof
- core#find#findlastindexof
position: 50
signatures:
- "func FindIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bool)"
---
Searches for an element based on a predicate and returns the element, its index, and a boolean indicating success.
```go
val, idx, ok := lo.FindIndexOf([]string{"a", "b", "a", "b"}, func(i string) bool {
return i == "b"
})
// "b", 1, true
```
================================================
FILE: docs/data/core-findkey.md
================================================
---
name: FindKey
slug: findkey
sourceRef: find.go#L128
category: core
subCategory: find
playUrl: https://go.dev/play/p/Bg0w1VDPYXx
variantHelpers:
- core#find#findkey
similarHelpers:
- core#find#findkeyby
- core#find#find
- core#find#findby
position: 80
signatures:
- "func FindKey[K comparable, V comparable](object map[K]V, value V) (K, bool)"
---
Returns the first key whose value equals the provided value.
```go
k, ok := lo.FindKey(map[string]int{"foo":1, "bar":2, "baz":3}, 2)
// "bar", true
```
================================================
FILE: docs/data/core-findkeyby.md
================================================
---
name: FindKeyBy
slug: findkeyby
sourceRef: find.go#L140
category: core
subCategory: find
playUrl: https://go.dev/play/p/9IbiPElcyo8
variantHelpers:
- core#find#findkeyby
similarHelpers:
- core#find#findkey
- core#find#findby
- core#find#findorelse
position: 90
signatures:
- "func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value V) bool) (K, bool)"
---
Returns the first key in the map for which the predicate returns true.
```go
k, ok := lo.FindKeyBy(map[string]int{"foo":1, "bar":2, "baz":3}, func(k string, v int) bool {
return k == "foo"
})
// "foo", true
```
================================================
FILE: docs/data/core-findlastindexof.md
================================================
---
name: FindLastIndexOf
slug: findlastindexof
sourceRef: find.go#L101
category: core
subCategory: find
playUrl: https://go.dev/play/p/2VhPMiQvX-D
variantHelpers:
- core#find#findlastindexof
similarHelpers:
- core#find#findindexof
- core#find#find
- core#find#lastindexof
- core#find#findby
position: 60
signatures:
- "func FindLastIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bool)"
---
Searches for the last element matching the predicate and returns the element, its index, and a boolean indicating success.
```go
val, idx, ok := lo.FindLastIndexOf([]string{"a", "b", "a", "b"}, func(i string) bool {
return i == "b"
})
// "b", 3, true
```
================================================
FILE: docs/data/core-findorelse.md
================================================
---
name: FindOrElse
slug: findorelse
sourceRef: find.go#L116
category: core
subCategory: find
playUrl: https://go.dev/play/p/Eo7W0lvKTky
variantHelpers:
- core#find#findorelse
similarHelpers:
- core#find#find
- core#slice#firstor
- core#slice#lastor
- core#slice#nthor
position: 70
signatures:
- "func FindOrElse[T any](collection []T, fallback T, predicate func(item T) bool) T"
---
Searches for an element based on a predicate and returns it if found, otherwise returns the fallback.
```go
value := lo.FindOrElse([]string{"a", "b", "c", "d"}, "x", func(i string) bool {
return i == "b"
})
// "b"
value = lo.FindOrElse([]string{"foobar"}, "x", func(i string) bool {
return i == "b"
})
// "x"
```
================================================
FILE: docs/data/core-finduniques.md
================================================
---
name: FindUniques
slug: finduniques
sourceRef: find.go#L152
category: core
subCategory: find
playUrl: https://go.dev/play/p/NV5vMK_2Z_n
variantHelpers:
- core#find#finduniques
similarHelpers:
- core#find#finduniquesby
- core#find#findduplicates
- core#find#findduplicatesby
position: 100
signatures:
- "func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice"
---
Returns a slice with elements that appear only once in the collection, preserving original order.
```go
lo.FindUniques([]int{1, 2, 2, 1, 2, 3})
// []int{3}
```
================================================
FILE: docs/data/core-finduniquesby.md
================================================
---
name: FindUniquesBy
slug: finduniquesby
sourceRef: find.go#L178
category: core
subCategory: find
playUrl: https://go.dev/play/p/2vmxCs4kW_m
variantHelpers:
- core#find#finduniquesby
similarHelpers:
- core#find#finduniques
- core#find#findduplicatesby
- core#find#uniq
- core#find#uniqby
position: 110
signatures:
- "func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice"
---
Returns a slice of elements that are unique by a computed key, preserving original order.
```go
lo.FindUniquesBy([]int{3, 4, 5, 6, 7}, func(i int) int {
return i % 3
})
// []int{5}
```
================================================
FILE: docs/data/core-first.md
================================================
---
name: First
slug: first
sourceRef: find.go#L554
category: core
subCategory: find
playUrl: https://go.dev/play/p/94lu5X6_cbf
variantHelpers:
- core#find#first
similarHelpers:
- core#slice#take
- core#slice#takewhile
- core#find#firstor
- core#find#firstorempty
- core#find#last
- core#find#lastor
- core#find#lastorempty
- core#find#nth
position: 260
signatures:
- "func First[T any](collection []T) (T, bool)"
---
Returns the first element of a collection and whether it exists.
```go
v, ok := lo.First([]int{1, 2, 3})
// v == 1, ok == true
```
================================================
FILE: docs/data/core-firstor.md
================================================
---
name: FirstOr
slug: firstor
sourceRef: find.go#L574
category: core
subCategory: find
playUrl: https://go.dev/play/p/x9CxQyRFXeZ
variantHelpers:
- core#find#firstor
similarHelpers:
- core#find#first
- core#find#firstorempty
- core#find#lastor
- core#find#nthor
position: 280
signatures:
- "func FirstOr[T any](collection []T, fallback T) T"
---
Returns the first element of a collection or the fallback value if empty.
```go
v := lo.FirstOr([]int{}, -1)
// v == -1
```
================================================
FILE: docs/data/core-firstorempty.md
================================================
---
name: FirstOrEmpty
slug: firstorempty
sourceRef: find.go#L567
category: core
subCategory: find
playUrl: https://go.dev/play/p/i200n9wgrDA
variantHelpers:
- core#find#firstorempty
similarHelpers:
- core#find#first
- core#find#firstor
- core#find#lastorempty
- core#find#nthorempty
position: 270
signatures:
- "func FirstOrEmpty[T any](collection []T) T"
---
Returns the first element of a collection or the zero value if empty.
```go
v := lo.FirstOrEmpty([]int{})
// v == 0
```
================================================
FILE: docs/data/core-flatmap.md
================================================
---
name: FlatMap
slug: flatmap
sourceRef: slice.go#L74
category: core
subCategory: slice
playUrl: https://go.dev/play/p/pFCF5WVB225
variantHelpers:
- core#slice#flatmap
similarHelpers:
- core#slice#flatmaperr
- core#slice#map
- core#slice#maperr
- parallel#slice#map
- mutable#slice#map
- core#slice#filtermap
position: 40
signatures:
- "func FlatMap[T any, R any](collection []T, transform func(item T, index int) []R) []R"
---
Manipulates a slice and transforms and flattens it to a slice of another type. The transform function can either return a slice or a `nil`, and in the `nil` case no value is added to the final slice.
```go
out := lo.FlatMap([]int64{0, 1, 2}, func(x int64, _ int) []string {
return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}
})
// []string{"0", "0", "1", "1", "2", "2"}
```
================================================
FILE: docs/data/core-flatmaperr.md
================================================
---
name: FlatMapErr
slug: flatmaperr
sourceRef: slice.go#L99
category: core
subCategory: slice
signatures:
- "func FlatMapErr[T any, R any](collection []T, transform func(item T, index int) ([]R, error)) ([]R, error)"
variantHelpers:
- core#slice#flatmaperr
similarHelpers:
- core#slice#flatmap
- core#slice#maperr
- core#slice#map
- core#slice#filtermap
position: 41
---
Manipulates a slice and transforms and flattens it to a slice of another type using a function that can return an error. Stops iteration immediately when an error is encountered.
```go
// Error case - stops on first error
result, err := lo.FlatMapErr([]int64{0, 1, 2, 3}, func(x int64, _ int) ([]string, error) {
if x == 2 {
return nil, fmt.Errorf("number 2 is not allowed")
}
return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}, nil
})
// []string(nil), error("number 2 is not allowed")
```
```go
// Success case
result, err := lo.FlatMapErr([]int64{0, 1, 2}, func(x int64, _ int) ([]string, error) {
return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}, nil
})
// []string{"0", "0", "1", "1", "2", "2"}, nil
```
================================================
FILE: docs/data/core-flatten.md
================================================
---
name: Flatten
slug: flatten
sourceRef: slice.go#L266
category: core
subCategory: slice
playUrl: https://go.dev/play/p/rbp9ORaMpjw
variantHelpers:
- core#slice#flatten
similarHelpers:
- core#slice#chunk
- core#slice#interleave
- core#slice#slice
position: 160
signatures:
- "func Flatten[T any, Slice ~[]T](collection []Slice) Slice"
---
Flattens a slice of slices by one level.
```go
flat := lo.Flatten([][]int{{0, 1}, {2, 3, 4, 5}})
// []int{0, 1, 2, 3, 4, 5}
```
================================================
FILE: docs/data/core-foreach.md
================================================
---
name: ForEach
slug: foreach
sourceRef: slice.go#L107
category: core
subCategory: slice
playUrl: https://go.dev/play/p/oofyiUPRf8t
variantHelpers:
- core#slice#foreach
similarHelpers:
- core#slice#times
- core#slice#map
- core#slice#foreachwhile
- parallel#slice#foreach
position: 70
signatures:
- "func ForEach[T any](collection []T, callback func(item T, index int))"
---
Iterates over elements of a collection and invokes the callback for each element.
```go
lo.ForEach([]string{"hello", "world"}, func(x string, _ int) {
println(x)
})
// prints "hello\nworld\n"
```
================================================
FILE: docs/data/core-foreachwhile.md
================================================
---
name: ForEachWhile
slug: foreachwhile
sourceRef: slice.go#L116
category: core
subCategory: slice
signatures:
- "func ForEachWhile[T any](collection []T, predicate func(item T, index int) bool)"
playUrl: https://go.dev/play/p/dG7h9H4nJQf
variantHelpers:
- core#slice#foreachwhile
similarHelpers:
- core#slice#foreach
- core#slice#filter
- core#slice#some
- core#slice#every
- core#slice#droprightwhile
- core#slice#dropwhile
- parallel#slice#foreach
position: 80
---
Iterates over elements of a collection and invokes the predicate for each element until false is returned.
```go
numbers := []int64{1, 2, -9223372036854775808, 4}
lo.ForEachWhile(numbers, func(x int64, _ int) bool {
if x < 0 {
return false
}
fmt.Println(x)
return true
})
// Output:
// 1
// 2
```
================================================
FILE: docs/data/core-fromanyslice.md
================================================
---
name: FromAnySlice
slug: fromanyslice
sourceRef: type_manipulation.go#L118
category: core
subCategory: type
signatures:
- "func FromAnySlice[T any](in []any) ([]T, bool)"
variantHelpers:
- core#type#fromanyslice
similarHelpers:
- core#type#toanyslice
- core#type#tosliceptr
- core#type#fromsliceptr
position: 128
---
Converts a slice of empty interface values back to a typed slice. Returns the converted slice and a boolean indicating success. All elements must be of the target type.
```go
data := []any{1, 2, 3}
result, ok := lo.FromAnySlice[int](data)
// []int{1, 2, 3}, true
data = []any{"a", "b", "c"}
result, ok = lo.FromAnySlice[string](data)
// []string{"a", "b", "c"}, true
data = []any{1, "b", 3} // mixed types
result, ok = lo.FromAnySlice[int](data)
// []int{}, false (conversion failed due to string element)
data = []any{} // empty slice
result, ok = lo.FromAnySlice[int](data)
// []int{}, true (empty slice always succeeds)
```
================================================
FILE: docs/data/core-fromentries.md
================================================
---
name: FromEntries
slug: fromentries
sourceRef: map.go#L201
category: core
subCategory: map
playUrl: https://go.dev/play/p/oIr5KHFGCEN
variantHelpers:
- core#map#fromentries
similarHelpers:
- core#map#frompairs
- core#map#entries
- core#map#topairs
- core#map#keys
- core#map#values
position: 130
signatures:
- "func FromEntries[K comparable, V any](entries []Entry[K, V]) map[K]V"
---
Transforms a slice of key/value pairs into a map.
```go
m := lo.FromEntries([]lo.Entry[string, int]{
{Key: "foo", Value: 1},
{Key: "bar", Value: 2},
})
// map[string]int{"foo": 1, "bar": 2}
```
================================================
FILE: docs/data/core-frompairs.md
================================================
---
name: FromPairs
slug: frompairs
sourceRef: map.go#L214
category: core
subCategory: map
playUrl: https://go.dev/play/p/oIr5KHFGCEN
variantHelpers:
- core#map#frompairs
similarHelpers:
- core#map#fromentries
- core#map#entries
- core#map#topairs
- core#map#keys
- core#map#values
position: 140
signatures:
- "func FromPairs[K comparable, V any](entries []Entry[K, V]) map[K]V"
---
Transforms a slice of key/value pairs into a map. Alias of `FromEntries`.
```go
m := lo.FromPairs([]lo.Entry[string, int]{{Key: "foo", Value: 1}})
// map[string]int{"foo": 1}
```
================================================
FILE: docs/data/core-fromptr.md
================================================
---
name: FromPtr
slug: fromptr
sourceRef: type_manipulation.go#L53
category: core
subCategory: type
signatures:
- "func FromPtr[T any](x *T) T"
variantHelpers:
- core#type#fromptr
similarHelpers:
- core#type#toptr
- core#type#fromptror
- core#type#emptyabletoptr
- core#type#tosliceptr
- core#type#fromsliceptr
position: 95
---
Dereferences a pointer and returns the underlying value. If the pointer is nil, returns the zero value for the type. This is a safe way to extract values from optional pointers without risking panics.
```go
ptr := lo.ToPtr(42)
value := lo.FromPtr(ptr)
// value: 42
value = lo.FromPtr[string](nil)
// value: "" (zero value for string)
value = lo.FromPtr[int](nil)
// value: 0 (zero value for int)
// Working with structs
type Person struct {
Name string
Age int
}
var personPtr *Person
person := lo.FromPtr(personPtr)
// person: Person{Name: "", Age: 0} (zero value for Person)
```
================================================
FILE: docs/data/core-fromptror.md
================================================
---
name: FromPtrOr
slug: fromptror
sourceRef: type_manipulation.go#L63
category: core
subCategory: type
signatures:
- "func FromPtrOr[T any](x *T, fallback T) T"
variantHelpers:
- core#type#fromptror
similarHelpers:
- core#type#toptr
- core#type#fromptr
- core#type#emptyabletoptr
- core#type#tosliceptr
- core#type#fromsliceptr
- core#type#valueor
position: 100
---
Returns the value pointed to by the pointer, or the fallback value if the pointer is nil.
```go
ptr := lo.ToPtr(42)
value := lo.FromPtrOr(ptr, 0)
// 42
value = lo.FromPtrOr[string](nil, "default")
// "default"
value = lo.FromPtrOr[int](nil, -1)
// -1
ptr = nil
value = lo.FromPtrOr(ptr, 999)
// 999
```
================================================
FILE: docs/data/core-fromsliceptr.md
================================================
---
name: FromSlicePtr
slug: fromsliceptr
sourceRef: type_manipulation.go#L85
category: core
subCategory: type
signatures:
- "func FromSlicePtr[T any](collection []*T) []T"
variantHelpers:
- core#type#fromsliceptr
similarHelpers:
- core#type#toptr
- core#type#fromptr
- core#type#fromptror
- core#type#emptyabletoptr
- core#type#tosliceptr
position: 115
---
Converts a slice of pointers to a slice of values. Nil pointers are converted to zero values.
```go
a, b, c := 1, 2, 3
ptrs := []*int{&a, &b, &c}
slice := lo.FromSlicePtr(ptrs)
// []int{1, 2, 3}
a, b = "hello", "world"
ptrs = []*string{&a, nil, &b}
slice = lo.FromSlicePtr(ptrs)
// []string{"hello", "", "world"} (nil pointer becomes zero value)
ptrs = []*int{}
slice = lo.FromSlicePtr(ptrs)
// []int{}
```
================================================
FILE: docs/data/core-generator.md
================================================
---
name: Generator
slug: generator
sourceRef: channel.go#L18
category: core
subCategory: channel
signatures:
- "func Generator[T any](bufferSize int, generator func(yield func(T))) <-chan T"
similarHelpers:
- core#channel#slicetochannel
- core#channel#fanin
- it#channel#seqtochannel
position: 253
---
Generator creates a channel from a generator function.
```go
gen := lo.Generator(10, func(yield func(int)) {
for i := 0; i < 10; i++ {
yield(i * 2)
}
})
for item := range gen {
fmt.Println(item)
}
// Prints even numbers 0, 2, 4, 6, 8, 10, 12, 14, 16, 18
```
================================================
FILE: docs/data/core-groupby.md
================================================
---
name: GroupBy
slug: groupby
sourceRef: slice.go#L180
category: core
subCategory: slice
playUrl: https://go.dev/play/p/XnQBd_v6brd
variantHelpers:
- core#slice#groupby
similarHelpers:
- core#slice#groupbyerr
- core#slice#groupbymap
- core#slice#partitionby
- core#slice#keyby
- parallel#slice#groupby
position: 120
signatures:
- "func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) map[U]Slice"
---
Groups elements by a key computed from each element. The result is a map keyed by the group key with slices of original elements.
```go
groups := lo.GroupBy(
[]int{0, 1, 2, 3, 4, 5},
func(i int) int {
return i % 3
},
)
// map[int][]int{0: {0, 3}, 1: {1, 4}, 2: {2, 5}}
```
================================================
FILE: docs/data/core-groupbyerr.md
================================================
---
name: GroupByErr
slug: groupbyerr
sourceRef: slice.go#L279
category: core
subCategory: slice
playUrl: https://go.dev/play/p/BzKPcY3AdX2
signatures:
- "func GroupByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (map[U]Slice, error)"
variantHelpers:
- core#slice#groupbyerr
similarHelpers:
- core#slice#groupby
- core#slice#groupbymap
- core#slice#partitionby
- core#slice#keyby
- parallel#slice#groupby
position: 121
---
Groups elements by a key computed from each element using an iteratee that can return an error. Stops iteration immediately when an error is encountered. The result is a map keyed by the group key with slices of original elements.
```go
// Error case - stops on first error
result, err := lo.GroupByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
if i == 3 {
return 0, fmt.Errorf("number 3 is not allowed")
}
return i % 3, nil
})
// map[int][]int(nil), error("number 3 is not allowed")
```
```go
// Success case
result, err := lo.GroupByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
return i % 3, nil
})
// map[int][]int{0: {0, 3}, 1: {1, 4}, 2: {2, 5}}, nil
```
================================================
FILE: docs/data/core-groupbymap.md
================================================
---
name: GroupByMap
slug: groupbymap
sourceRef: slice.go#L194
category: core
subCategory: slice
playUrl: https://go.dev/play/p/iMeruQ3_W80
variantHelpers:
- core#slice#groupbymap
similarHelpers:
- core#slice#groupbymaperr
- core#slice#groupby
- core#slice#groupbyerr
- core#slice#partitionby
- core#slice#keyby
- core#map#associate
- parallel#slice#groupby
position: 130
signatures:
- "func GroupByMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K][]V"
---
Groups items by a key computed from each element and maps each element to a value.
```go
groups := lo.GroupByMap(
[]int{0, 1, 2, 3, 4, 5},
func(i int) (int, int) {
return i % 3, i * 2
},
)
// map[int][]int{0:{0,6}, 1:{2,8}, 2:{4,10}}
```
================================================
FILE: docs/data/core-groupbymaperr.md
================================================
---
name: GroupByMapErr
slug: groupbymaperr
sourceRef: slice.go#L311
category: core
subCategory: slice
signatures:
- "func GroupByMapErr[T any, K comparable, V any](collection []T, transform func(item T) (K, V, error)) (map[K][]V, error)"
variantHelpers:
- core#slice#groupbymaperr
similarHelpers:
- core#slice#groupbymap
- core#slice#groupby
- core#slice#groupbyerr
- core#slice#partitionby
- core#slice#keyby
- core#map#associate
- parallel#slice#groupby
position: 131
---
Groups items by a key computed from each element and maps each element to a value using a transform function that can return an error. Stops iteration immediately when an error is encountered.
```go
// Error case - stops on first error
result, err := lo.GroupByMapErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, int, error) {
if i == 3 {
return 0, 0, fmt.Errorf("number 3 is not allowed")
}
return i % 3, i * 2, nil
})
// map[int][]int(nil), error("number 3 is not allowed")
```
```go
// Success case
result, err := lo.GroupByMapErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, int, error) {
return i % 3, i * 2, nil
})
// map[int][]int{0: {0, 6}, 1: {2, 8}, 2: {4, 10}}, nil
```
================================================
FILE: docs/data/core-haskey.md
================================================
---
name: HasKey
slug: haskey
sourceRef: map.go#L47
category: core
subCategory: map
playUrl: https://go.dev/play/p/aVwubIvECqS
variantHelpers:
- core#map#haskey
similarHelpers:
- core#map#valueor
- core#map#keys
- core#map#values
position: 20
signatures:
- "func HasKey[K comparable, V any](in map[K]V, key K) bool"
---
Returns whether the given key exists in the map.
```go
exists := lo.HasKey(map[string]int{"foo": 1, "bar": 2}, "foo")
// true
exists = lo.HasKey(map[string]int{"foo": 1, "bar": 2}, "baz")
// false
```
================================================
FILE: docs/data/core-hasprefix.md
================================================
---
name: HasPrefix
slug: hasprefix
sourceRef: find.go#L41
category: core
subCategory: find
playUrl: https://go.dev/play/p/SrljzVDpMQM
variantHelpers:
- core#find#hasprefix
similarHelpers:
- core#find#hassuffix
- core#find#contains
- core#slice#trimleft
- core#slice#trimprefix
position: 20
signatures:
- "func HasPrefix[T comparable](collection []T, prefix []T) bool"
---
Returns true if a collection starts with the given prefix slice.
```go
lo.HasPrefix([]int{1, 2, 3, 4}, []int{1, 2})
// true
```
================================================
FILE: docs/data/core-hassuffix.md
================================================
---
name: HasSuffix
slug: hassuffix
sourceRef: find.go#L57
category: core
subCategory: find
playUrl: https://go.dev/play/p/bJeLetQNAON
variantHelpers:
- core#find#hassuffix
similarHelpers:
- core#find#hasprefix
- core#find#contains
- core#slice#trimright
- core#slice#trimsuffix
position: 30
signatures:
- "func HasSuffix[T comparable](collection []T, suffix []T) bool"
---
Returns true if a collection ends with the given suffix slice.
```go
lo.HasSuffix([]int{1, 2, 3, 4}, []int{3, 4})
// true
```
================================================
FILE: docs/data/core-if.md
================================================
---
name: If/Else
slug: if-else
sourceRef: condition.go#L31
category: core
subCategory: condition
playUrl: https://go.dev/play/p/WSw3ApMxhyW
variantHelpers:
- core#condition#if
- core#condition#iff
similarHelpers:
- core#condition#switch
- core#condition#ternary
- core#condition#validate
position: 10
signatures:
- "func If[T any](condition bool, result T) *ifElse[T]"
- "func IfF[T any](condition bool, resultF func() T) *ifElse[T]"
- "func (i *ifElse[T]) ElseIf(condition bool, result T) *ifElse[T]"
- "func (i *ifElse[T]) ElseIfF(condition bool, resultF func() T) *ifElse[T]"
- "func (i *ifElse[T]) Else(result T) T"
- "func (i *ifElse[T]) ElseF(resultF func() T) T"
---
A fluent conditional builder that allows chaining If/ElseIf/Else conditions.
### If
Starts a fluent If/ElseIf/Else chain. Returns a builder that can be completed with `ElseIf`, `Else`, etc.
```go
result := lo.If(true, 1).Else(3)
// 1
```
### IfF
Function form of If. Lazily computes the initial result when the condition is true.
```go
result := lo.IfF(true, func() int {
return 1
}).Else(3)
// 1
```
### ElseIf
Adds an ElseIf branch to an If/Else chain.
```go
result := lo.If(false, 1).ElseIf(true, 2).Else(3)
// 2
```
### ElseIfF
Function form of ElseIf. Lazily computes the branch result when the condition is true.
```go
result := lo.If(false, 1).ElseIfF(true, func() int {
return 2
}).Else(3)
// 2
```
### Else
Completes the If/Else chain by returning the chosen result or the default provided here.
```go
result := lo.If(false, 1).ElseIf(false, 2).Else(3)
// 3
```
### ElseF
Function form of Else. Lazily computes the default result if no previous branch matched.
```go
result := lo.If(false, 1).ElseIf(false, 2).ElseF(func() int {
return 3
})
// 3
```
================================================
FILE: docs/data/core-indexof.md
================================================
---
name: IndexOf
slug: indexof
sourceRef: find.go#L14
category: core
subCategory: find
playUrl: https://go.dev/play/p/Eo7W0lvKTky
variantHelpers:
- core#find#indexof
similarHelpers:
- core#find#findindexof
- core#find#findlastindexof
- core#find#lastindexof
- core#find#find
position: 0
signatures:
- "func IndexOf[T comparable](collection []T, element T) int"
---
Returns the index of the first occurrence of a value in a slice, or -1 if not found.
```go
idx := lo.IndexOf([]int{0, 1, 2, 1, 2, 3}, 2)
// 2
idx = lo.IndexOf([]int{0, 1, 2, 1, 2, 3}, 6)
// -1
```
================================================
FILE: docs/data/core-interleave.md
================================================
---
name: Interleave
slug: interleave
sourceRef: slice.go#L282
category: core
subCategory: slice
playUrl: https://go.dev/play/p/KOVtGUt-tdI
variantHelpers:
- core#slice#interleave
similarHelpers:
- core#slice#flatten
- core#slice#chunk
- core#slice#slice
- core#slice#shuffle
position: 170
signatures:
- "func Interleave[T any, Slice ~[]T](collections ...Slice) Slice"
---
Round-robins input slices by index, appending values sequentially into the result.
```go
lo.Interleave([]int{1, 4, 7}, []int{2, 5, 8}, []int{3, 6, 9})
// []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
lo.Interleave([]int{1}, []int{2, 5, 8}, []int{3, 6}, []int{4, 7, 9, 10})
// []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
```
================================================
FILE: docs/data/core-intersect.md
================================================
---
name: Intersect
slug: intersect
sourceRef: intersect.go#L103
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/uuElL9X9e58
variantHelpers:
- core#intersect#intersect
similarHelpers:
- core#intersect#intersectby
- it#intersect#intersect
- it#intersect#intersectby
- core#intersect#difference
- core#intersect#union
- core#intersect#without
- core#slice#uniq
position: 80
signatures:
- "func Intersect[T comparable, Slice ~[]T](lists ...Slice) Slice"
---
Returns the intersection between collections.
```go
lo.Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
// []int{3}
```
================================================
FILE: docs/data/core-intersectby.md
================================================
---
name: IntersectBy
slug: intersectby
sourceRef: intersect.go#L174
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/uWF8y2-zmtf
variantHelpers:
- core#intersect#intersectby
similarHelpers:
- core#intersect#intersect
- it#intersect#intersect
- it#intersect#intersectby
- core#intersect#difference
- core#intersect#union
- core#intersect#without
- core#slice#uniq
position: 80
signatures:
- "func IntersectBy[T any, K comparable, Slice ~[]T](transform func(T) K, lists ...Slice) Slice"
---
Returns the intersection between two collections using a custom key selector function.
```go
transform := func(v int) string {
return strconv.Itoa(v)
}
lo.IntersectBy(transform, []int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
// []int{3}
```
================================================
FILE: docs/data/core-invert.md
================================================
---
name: Invert
slug: invert
sourceRef: map.go#L222
category: core
subCategory: map
playUrl: https://go.dev/play/p/rFQ4rak6iA1
variantHelpers:
- core#map#invert
similarHelpers:
- core#map#entries
- core#map#topairs
- core#map#frompairs
position: 150
signatures:
- "func Invert[K comparable, V comparable](in map[K]V) map[V]K"
---
Creates a map with keys and values swapped. If values are duplicated, later keys overwrite earlier ones.
```go
lo.Invert(map[string]int{"a": 1, "b": 2})
// map[int]string{1: "a", 2: "b"}
```
================================================
FILE: docs/data/core-isempty.md
================================================
---
name: IsEmpty
slug: isempty
sourceRef: type_manipulation.go#L139
category: core
subCategory: type
signatures:
- "func IsEmpty[T comparable](v T) bool"
variantHelpers:
- core#type#isempty
similarHelpers:
- core#type#isnotempty
- core#type#empty
- core#type#isnil
- core#type#isnotnil
position: 122
---
Returns true if the value is empty (zero value) for comparable types. This works with strings, numbers, slices, maps, pointers, etc.
```go
result := lo.IsEmpty("")
// true (empty string)
result = lo.IsEmpty("hello")
// false
result = lo.IsEmpty(0)
// true (zero value for int)
result = lo.IsEmpty(42)
// false
result = lo.IsEmpty([]int{})
// true (empty slice)
result = lo.IsEmpty([]int{1, 2, 3})
// false
result = lo.IsEmpty(map[string]int{})
// true (empty map)
var ptr *int
result = lo.IsEmpty(ptr)
// true (nil pointer)
```
================================================
FILE: docs/data/core-isnil.md
================================================
---
name: IsNil
slug: isnil
sourceRef: type_manipulation.go#L7
category: core
subCategory: type
signatures:
- "func IsNil(x any) bool"
variantHelpers:
- core#type#isnil
similarHelpers:
- core#type#isnotnil
- core#type#empty
- core#type#isempty
- core#type#isnotempty
position: 85
---
Returns true if the input is nil or points to a nil value. This works with pointers, interfaces, maps, slices, channels, and functions.
```go
result := lo.IsNil(nil)
// true
var ptr *int
result = lo.IsNil(ptr)
// true
result = lo.IsNil(42)
// false
result = lo.IsNil("hello")
// false
var iface interface{}
result = lo.IsNil(iface)
// true
iface = 42
result = lo.IsNil(iface)
// false
```
================================================
FILE: docs/data/core-isnotempty.md
================================================
---
name: IsNotEmpty
slug: isnotempty
sourceRef: type_manipulation.go#L146
category: core
subCategory: type
signatures:
- "func IsNotEmpty[T comparable](v T) bool"
variantHelpers:
- core#type#isnotempty
similarHelpers:
- core#type#isempty
- core#type#empty
- core#type#isnil
- core#type#isnotnil
position: 124
---
Returns true if the value is not empty (not zero value) for comparable types. This is the opposite of IsEmpty.
```go
result := lo.IsNotEmpty("")
// false (empty string)
result = lo.IsNotEmpty("hello")
// true
result = lo.IsNotEmpty(0)
// false (zero value for int)
result = lo.IsNotEmpty(42)
// true
result = lo.IsNotEmpty([]int{})
// false (empty slice)
result = lo.IsNotEmpty([]int{1, 2, 3})
// true
result = lo.IsNotEmpty(map[string]int{})
// false (empty map)
var ptr *int
result = lo.IsNotEmpty(ptr)
// false (nil pointer)
```
================================================
FILE: docs/data/core-isnotnil.md
================================================
---
name: IsNotNil
slug: isnil
sourceRef: type_manipulation.go#L22
category: core
subCategory: type
signatures:
- "func IsNotNil(x any) bool"
variantHelpers:
- core#type#isnotnil
similarHelpers:
- core#type#isnil
- core#type#empty
- core#type#isempty
- core#type#isnotempty
position: 87
---
Returns true if the input is not nil and does not point to a nil value. This works with pointers, interfaces, maps, slices, channels, and functions.
```go
result := lo.IsNotNil(nil)
// false
var ptr *int
result = lo.IsNotNil(ptr)
// false
result = lo.IsNotNil(42)
// true
result = lo.IsNotNil("hello")
// true
var iface interface{}
result = lo.IsNotNil(iface)
// false
iface = 42
result = lo.IsNotNil(iface)
// true
```
================================================
FILE: docs/data/core-issorted.md
================================================
---
name: IsSorted
slug: issorted
sourceRef: slice.go#L722
category: core
subCategory: slice
playUrl: https://go.dev/play/p/mc3qR-t4mcx
variantHelpers:
- core#slice#issorted
similarHelpers:
- core#slice#issortedby
- core#slice#min
- core#slice#max
- core#slice#reverse
position: 0
signatures:
- "func IsSorted[T constraints.Ordered](collection []T) bool"
---
Checks if a slice is sorted in ascending order.
```go
lo.IsSorted([]int{0, 1, 2, 3, 4, 5})
// true
```
================================================
FILE: docs/data/core-issortedby.md
================================================
---
name: IsSortedBy
slug: issortedby
sourceRef: slice.go#L733
category: core
subCategory: slice
playUrl: https://go.dev/play/p/wiG6XyBBu49
variantHelpers:
- core#slice#issortedby
similarHelpers:
- core#slice#issorted
- core#slice#minby
- core#slice#maxby
- core#slice#reverse
position: 0
signatures:
- "func IsSortedBy[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool"
---
Checks if a slice is sorted based on a key computed for each element.
```go
ok := lo.IsSortedBy([]string{"a", "bb", "ccc"}, func(s string) int {
return len(s)
})
// true
```
================================================
FILE: docs/data/core-kebabcase.md
================================================
---
name: KebabCase
slug: kebabcase
sourceRef: string.go#L190
category: core
subCategory: string
playUrl: https://go.dev/play/p/ZBeMB4-pq45
variantHelpers:
- core#string#kebabcase
similarHelpers:
- core#string#pascalcase
- core#string#camelcase
- core#string#snakecase
- core#string#capitalize
- core#string#words
position: 60
signatures:
- "func KebabCase(str string) string"
---
Converts a string to kebab-case.
```go
lo.KebabCase("helloWorld")
// "hello-world"
```
================================================
FILE: docs/data/core-keyby.md
================================================
---
name: KeyBy
slug: keyby
sourceRef: slice.go#L374
category: core
subCategory: slice
playUrl: https://go.dev/play/p/ccUiUL_Lnel
variantHelpers:
- core#slice#keyby
similarHelpers:
- core#slice#keybyerr
- core#slice#groupby
- core#slice#groupbyerr
- core#slice#partitionby
- core#map#associate
- core#slice#keyify
position: 230
signatures:
- "func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V"
---
Transforms a slice to a map using a pivot callback to compute keys.
```go
m := lo.KeyBy(
[]string{"a", "aa", "aaa"},
func(str string) int {
return len(str)
},
)
// map[int]string{1: "a", 2: "aa", 3: "aaa"}
```
================================================
FILE: docs/data/core-keybyerr.md
================================================
---
name: KeyByErr
slug: keybyerr
sourceRef: slice.go#L576
category: core
subCategory: slice
signatures:
- "func KeyByErr[K comparable, V any](collection []V, iteratee func(item V) (K, error)) (map[K]V, error)"
variantHelpers:
- core#slice#keybyerr
similarHelpers:
- core#slice#keyby
- core#slice#groupby
- core#slice#groupbyerr
- core#slice#partitionby
- core#map#associate
- core#slice#keyify
position: 231
---
Transforms a slice to a map using a pivot callback to compute keys. Stops iteration immediately when an error is encountered.
```go
// Error case - stops on first error
result, err := lo.KeyByErr([]string{"a", "aa", "aaa", ""}, func(str string) (int, error) {
if str == "" {
return 0, fmt.Errorf("empty string not allowed")
}
return len(str), nil
})
// map[int]string(nil), error("empty string not allowed")
```
```go
// Success case
result, err := lo.KeyByErr([]string{"a", "aa", "aaa"}, func(str string) (int, error) {
if str == "" {
return 0, fmt.Errorf("empty string not allowed")
}
return len(str), nil
})
// map[int]string{1: "a", 2: "aa", 3: "aaa"}, nil
```
================================================
FILE: docs/data/core-keyify.md
================================================
---
name: Keyify
slug: keyify
sourceRef: slice.go#L429
category: core
subCategory: slice
playUrl: https://go.dev/play/p/_d5lXdzfw32
variantHelpers:
- core#slice#keyify
similarHelpers:
- core#slice#keyby
- core#slice#uniq
- core#slice#uniqby
- core#slice#groupby
position: 270
signatures:
- "func Keyify[T comparable, Slice ~[]T](collection Slice) map[T]struct{}"
---
Returns a set-like map where each unique element of the slice is a key.
```go
set := lo.Keyify([]int{1, 1, 2, 3, 4})
// map[int]struct{}{1: {}, 2: {}, 3: {}, 4: {}}
```
================================================
FILE: docs/data/core-keys.md
================================================
---
name: Keys
slug: keys
sourceRef: map.go#L5
category: core
subCategory: map
playUrl: https://go.dev/play/p/Uu11fHASqrU
variantHelpers:
- core#map#keys
similarHelpers:
- core#map#values
- core#map#uniqkeys
- core#map#entries
- core#map#topairs
- core#map#frompairs
- core#map#filterkeys
position: 0
signatures:
- "func Keys[K comparable, V any](in ...map[K]V) []K"
---
Creates a slice of the map keys.
Use the UniqKeys variant to deduplicate common keys.
```go
keys := lo.Keys(map[string]int{"foo": 1, "bar": 2})
// []string{"foo", "bar"}
keys := lo.Keys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
// []string{"foo", "bar", "baz"}
keys := lo.Keys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 3})
// []string{"foo", "bar", "bar"}
```
================================================
FILE: docs/data/core-last.md
================================================
---
name: Last
slug: last
sourceRef: find.go#L585
category: core
subCategory: find
playUrl: https://go.dev/play/p/ul45Z0y2EFO
variantHelpers:
- core#find#last
similarHelpers:
- core#find#first
- core#find#nth
- core#find#lastor
- core#find#lastorempty
position: 290
signatures:
- "func Last[T any](collection []T) (T, bool)"
---
Returns the last element of a collection and whether it exists.
```go
v, ok := lo.Last([]int{1, 2, 3})
// v == 3, ok == true
```
================================================
FILE: docs/data/core-lastindexof.md
================================================
---
name: LastIndexOf
slug: lastindexof
sourceRef: find.go#L27
category: core
subCategory: find
playUrl: https://go.dev/play/p/Eo7W0lvKTky
variantHelpers:
- core#find#lastindexof
similarHelpers:
- core#find#indexof
- core#find#findkey
- core#find#findlastindexof
position: 10
signatures:
- "func LastIndexOf[T comparable](collection []T, element T) int"
---
Returns the index of the last occurrence of a value in a slice, or -1 if not found.
```go
idx := lo.LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 2)
// 4
idx = lo.LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 6)
// -1
```
================================================
FILE: docs/data/core-lastor.md
================================================
---
name: LastOr
slug: lastor
sourceRef: find.go#L605
category: core
subCategory: find
playUrl: https://go.dev/play/p/ul45Z0y2EFO
variantHelpers:
- core#find#lastor
similarHelpers:
- core#find#firstor
- core#find#last
- core#find#lastorempty
- core#find#nthor
position: 310
signatures:
- "func LastOr[T any](collection []T, fallback T) T"
---
Returns the last element of a collection or the fallback value if empty.
```go
v := lo.LastOr([]int{}, -1)
// v == -1
```
================================================
FILE: docs/data/core-lastorempty.md
================================================
---
name: LastOrEmpty
slug: lastorempty
sourceRef: find.go#L598
category: core
subCategory: find
playUrl: https://go.dev/play/p/ul45Z0y2EFO
variantHelpers:
- core#find#lastorempty
similarHelpers:
- core#find#firstorempty
- core#find#last
- core#find#lastor
- core#find#nthorempty
position: 300
signatures:
- "func LastOrEmpty[T any](collection []T) T"
---
Returns the last element of a collection or the zero value if empty.
```go
v := lo.LastOrEmpty([]int{})
// v == 0
```
================================================
FILE: docs/data/core-latest.md
================================================
---
name: Latest
slug: latest
sourceRef: find.go#L508
category: core
subCategory: find
playUrl: https://go.dev/play/p/dBfdf5s8s-Y
variantHelpers:
- core#find#latest
similarHelpers:
- core#find#earliest
- core#find#latestby
- core#find#earliestby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
- core#find#minindex
- core#find#maxindex
- core#find#minindexby
- core#find#maxindexby
position: 240
signatures:
- "func Latest(times ...time.Time) time.Time"
---
Searches the maximum time.Time in the provided arguments. Returns zero value when the input is empty.
```go
t1 := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
t2 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
max := lo.Latest(t1, t2)
// 2024-01-01 00:00:00 +0000 UTC
```
================================================
FILE: docs/data/core-latestby.md
================================================
---
name: LatestBy
slug: latestby
sourceRef: find.go#L530
category: core
subCategory: find
playUrl: https://go.dev/play/p/p1HA8XumaMU
variantHelpers:
- core#find#latestby
similarHelpers:
- core#find#latestbyerr
- core#find#latest
- core#find#earliestby
- core#find#earliestbyerr
- core#find#earliest
- core#find#maxby
- core#find#minby
- core#find#maxindexby
- core#find#minindexby
- core#find#findby
- core#find#findkeyby
- core#find#findduplicatesby
- core#find#finduniquesby
position: 250
signatures:
- "func LatestBy[T any](collection []T, iteratee func(item T) time.Time) T"
---
Searches a collection for the element with the maximum time extracted by the predicate. Returns zero value when the collection is empty.
```go
type Event struct{ At time.Time }
events := []Event{{At: time.Now()}, {At: time.Now().Add(2 * time.Hour)}}
last := lo.LatestBy(events, func(e Event) time.Time {
return e.At
})
```
================================================
FILE: docs/data/core-latestbyerr.md
================================================
---
name: LatestByErr
slug: latestbyerr
sourceRef: find.go#L737
category: core
subCategory: find
playUrl: https://go.dev/play/p/WpBUptwnxuG
signatures:
- "func LatestByErr[T any](collection []T, iteratee func(item T) (time.Time, error)) (T, error)"
variantHelpers:
- core#find#latestbyerr
similarHelpers:
- core#find#latestby
- core#find#latest
- core#find#earliestby
- core#find#earliestbyerr
- core#find#earliest
- core#find#maxby
- core#find#minby
- core#find#maxindexby
- core#find#minindexby
- core#find#findby
- core#find#findkeyby
- core#find#findduplicatesby
- core#find#finduniquesby
position: 251
---
Searches a collection for the element with the maximum time extracted by the predicate. Returns zero value when the collection is empty. Stops iteration immediately when an error is encountered.
```go
type Event struct{ At time.Time }
events := []Event{{At: time.Now()}, {At: time.Now().Add(2 * time.Hour)}}
last, err := lo.LatestByErr(events, func(e Event) (time.Time, error) {
return e.At, nil
})
// Event{At: ...}, nil
```
```go
// Error case - stops on first error
type Event struct{ At time.Time }
events := []Event{{At: time.Now()}, {At: time.Time{}}, {At: time.Now().Add(2 * time.Hour)}}
_, err := lo.LatestByErr(events, func(e Event) (time.Time, error) {
if e.At.IsZero() {
return time.Time{}, fmt.Errorf("zero time not allowed")
}
return e.At, nil
})
// error("zero time not allowed")
```
================================================
FILE: docs/data/core-map.md
================================================
---
name: Map
slug: map
sourceRef: slice.go#L26
category: core
subCategory: slice
playUrl: https://go.dev/play/p/OkPcYAhBo0D
similarHelpers:
- core#slice#maperr
- core#slice#filtermap
- core#slice#flatmap
- core#slice#uniqmap
- core#slice#rejectmap
- core#slice#mapkeys
- core#slice#mapvalues
- core#slice#mapentries
- core#slice#maptoslice
- core#slice#filtermaptoslice
- parallel#slice#map
- mutable#slice#map
variantHelpers:
- core#slice#map
position: 10
signatures:
- "func Map[T any, R any](collection []T, transform func(item T, index int) R) []R"
---
Transforms each element in a slice to a new type using a function. Takes both the element and its index, making it useful for transformations that need positional context.
```go
// Basic type transformation
transformed := lo.Map([]int64{1, 2, 3, 4}, func(x int64, index int) string {
return strconv.FormatInt(x, 10)
})
// transformed: []string{"1", "2", "3", "4"}
```
```go
// Transforming structs
type Person struct {
FirstName string
LastName string
Age int
}
people := []Person{
{FirstName: "John", LastName: "Doe", Age: 25},
{FirstName: "Jane", LastName: "Smith", Age: 30},
}
fullNames := lo.Map(people, func(p Person, index int) string {
return fmt.Sprintf("%s %s", p.FirstName, p.LastName)
})
// fullNames: []string{"John Doe", "Jane Smith"}
```
================================================
FILE: docs/data/core-mapentries.md
================================================
---
name: MapEntries
slug: mapentries
sourceRef: map.go#L336
category: core
subCategory: map
playUrl: https://go.dev/play/p/VuvNQzxKimT
variantHelpers:
- core#map#mapentries
similarHelpers:
- core#map#mapkeys
- core#map#mapvalues
- core#map#maptoslice
- core#slice#map
- core#map#mapentrieserr
position: 200
signatures:
- "func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2)) map[K2]V2"
---
Transforms both keys and values using an predicate function.
```go
in := map[string]int{"foo":1, "bar":2}
out := lo.MapEntries(in, func(k string, v int) (int, string) {
return v, k
})
// map[int]string{1:"foo", 2:"bar"}
```
================================================
FILE: docs/data/core-mapentrieserr.md
================================================
---
name: MapEntriesErr
slug: mapentrieserr
sourceRef: map.go#L351
category: core
subCategory: map
signatures:
- "func MapEntriesErr[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2, error)) (map[K2]V2, error)"
variantHelpers:
- core#map#mapentrieserr
similarHelpers:
- core#map#mapentries
- core#map#mapkeyserr
- core#map#mapvalueserr
position: 205
---
Transforms both keys and values using an predicate function. Returns an error if the iteratee function fails, stopping iteration immediately.
```go
in := map[string]int{"foo": 1, "bar": 2, "baz": 3}
out, err := lo.MapEntriesErr(in, func(k string, v int) (int, string, error) {
if k == "bar" {
return 0, "", fmt.Errorf("bar not allowed")
}
return v, k, nil
})
// map[int]string(nil), error("bar not allowed")
```
```go
in := map[string]int{"foo": 1, "bar": 2}
out, err := lo.MapEntriesErr(in, func(k string, v int) (int, string, error) {
return v, k, nil
})
// map[int]string{1:"foo", 2:"bar"}, nil
```
================================================
FILE: docs/data/core-maperr.md
================================================
---
name: MapErr
slug: maperr
sourceRef: slice.go#L36
category: core
subCategory: slice
signatures:
- "func MapErr[T any, R any](collection []T, transform func(item T, index int) (R, error)) ([]R, error)"
variantHelpers:
- core#slice#maperr
similarHelpers:
- core#slice#map
- core#slice#filtermap
- core#slice#flatmap
- core#slice#rejectmap
- parallel#slice#map
position: 11
---
Transforms each element in a slice to a new type using a function that can return an error. Stops iteration immediately when an error is encountered.
```go
// Error case - stops on first error
result, err := lo.MapErr([]int{1, 2, 3, 4}, func(x int, _ int) (string, error) {
if x == 3 {
return "", fmt.Errorf("number 3 is not allowed")
}
return strconv.Itoa(x), nil
})
// []string(nil), error("number 3 is not allowed")
```
```go
// Success case
result, err := lo.MapErr([]int{1, 2, 3, 4}, func(x int, _ int) (string, error) {
return strconv.Itoa(x), nil
})
// []string{"1", "2", "3", "4"}, nil
```
================================================
FILE: docs/data/core-mapkeys.md
================================================
---
name: MapKeys
slug: mapkeys
sourceRef: map.go#L283
category: core
subCategory: map
playUrl: https://go.dev/play/p/9_4WPIqOetJ
variantHelpers:
- core#map#mapkeys
similarHelpers:
- core#map#mapvalues
- core#map#mapentries
- core#map#keyby
- core#slice#map
- core#map#mapkeyserr
position: 180
signatures:
- "func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(value V, key K) R) map[R]V"
---
Transforms map keys using a predicate while keeping values.
```go
in := map[int]int{1:1, 2:2}
out := lo.MapKeys(in, func(v int, _ int) string {
return strconv.Itoa(v)
})
// map[string]int{"1":1, "2":2}
```
================================================
FILE: docs/data/core-mapkeyserr.md
================================================
---
name: MapKeysErr
slug: mapkeyserr
sourceRef: map.go#L293
category: core
subCategory: map
signatures:
- "func MapKeysErr[K comparable, V any, R comparable](in map[K]V, iteratee func(value V, key K) (R, error)) (map[R]V, error)"
variantHelpers:
- core#map#mapkeyserr
similarHelpers:
- core#map#mapkeys
- core#map#mapvalueserr
position: 185
---
Transforms map keys using a predicate while keeping values. Returns an error if the iteratee function fails, stopping iteration immediately.
```go
in := map[int]int{1: 1, 2: 2, 3: 3}
out, err := lo.MapKeysErr(in, func(v int, _ int) (string, error) {
if v == 2 {
return "", fmt.Errorf("even number not allowed")
}
return strconv.Itoa(v), nil
})
// map[string]int(nil), error("even number not allowed")
```
```go
in := map[int]int{1: 1, 2: 2, 3: 3}
out, err := lo.MapKeysErr(in, func(v int, _ int) (string, error) {
return strconv.Itoa(v), nil
})
// map[string]int{"1":1, "2":2, "3":3}, nil
```
================================================
FILE: docs/data/core-maptoslice.md
================================================
---
name: MapToSlice
slug: maptoslice
sourceRef: map.go#L367
category: core
subCategory: map
playUrl: https://go.dev/play/p/4f5hbHyMf5h
variantHelpers:
- core#map#maptoslice
similarHelpers:
- core#map#mapentries
- core#map#entries
- core#slice#map
- core#slice#mapentries
- core#map#maptosliceerr
position: 210
signatures:
- "func MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(key K, value V) R) []R"
---
Transforms a map into a slice by applying an predicate to each key/value pair.
```go
m := map[int]int64{1:4, 2:5, 3:6}
s := lo.MapToSlice(m, func(k int, v int64) string {
return fmt.Sprintf("%d_%d", k, v)
})
// []string{"1_4", "2_5", "3_6"}
```
================================================
FILE: docs/data/core-maptosliceerr.md
================================================
---
name: MapToSliceErr
slug: maptosliceerr
sourceRef: map.go#L379
category: core
subCategory: map
signatures:
- "func MapToSliceErr[K comparable, V any, R any](in map[K]V, iteratee func(key K, value V) (R, error)) ([]R, error)"
variantHelpers:
- core#map#maptosliceerr
similarHelpers:
- core#map#maptoslice
- core#map#mapentrieserr
- core#slice#maperr
position: 215
---
Transforms a map into a slice by applying an predicate to each key/value pair. Returns an error if the iteratee function fails, stopping iteration immediately.
```go
m := map[int]int64{1: 4, 2: 5, 3: 6}
s, err := lo.MapToSliceErr(m, func(k int, v int64) (string, error) {
if k == 2 {
return "", fmt.Errorf("key 2 not allowed")
}
return fmt.Sprintf("%d_%d", k, v), nil
})
// []string(nil), error("key 2 not allowed")
```
```go
m := map[int]int64{1:4, 2:5, 3:6}
s, err := lo.MapToSliceErr(m, func(k int, v int64) (string, error) {
return fmt.Sprintf("%d_%d", k, v), nil
})
// []string{"1_4", "2_5", "3_6"}, nil
```
================================================
FILE: docs/data/core-mapvalues.md
================================================
---
name: MapValues
slug: mapvalues
sourceRef: map.go#L310
category: core
subCategory: map
playUrl: https://go.dev/play/p/T_8xAfvcf0W
variantHelpers:
- core#map#mapvalues
similarHelpers:
- core#map#mapkeys
- core#map#mapentries
- core#map#groupby
- core#slice#map
- core#map#mapvalueserr
position: 190
signatures:
- "func MapValues[K comparable, V any, R any](in map[K]V, iteratee func(value V, key K) R) map[K]R"
---
Transforms map values using a predicate while keeping keys.
```go
in := map[int]int64{1:1, 2:2}
out := lo.MapValues(in, func(v int64, _ int) string {
return strconv.FormatInt(v, 10)
})
// map[int]string{1:"1", 2:"2"}
```
================================================
FILE: docs/data/core-mapvalueserr.md
================================================
---
name: MapValuesErr
slug: mapvalueserr
sourceRef: map.go#L322
category: core
subCategory: map
signatures:
- "func MapValuesErr[K comparable, V any, R any](in map[K]V, iteratee func(value V, key K) (R, error)) (map[K]R, error)"
variantHelpers:
- core#map#mapvalueserr
similarHelpers:
- core#map#mapvalues
- core#map#mapkeyserr
- core#map#mapentrieserr
position: 195
---
Transforms map values using a predicate while keeping keys. Returns an error if the iteratee function fails, stopping iteration immediately.
```go
in := map[int]int64{1: 1, 2: 2, 3: 3}
out, err := lo.MapValuesErr(in, func(v int64, _ int) (string, error) {
if v == 2 {
return "", fmt.Errorf("even number not allowed")
}
return strconv.FormatInt(v, 10), nil
})
// map[int]string(nil), error("even number not allowed")
```
```go
in := map[int]int64{1: 1, 2: 2, 3: 3}
out, err := lo.MapValuesErr(in, func(v int64, _ int) (string, error) {
return strconv.FormatInt(v, 10), nil
})
// map[int]string{1:"1", 2:"2", 3:"3"}, nil
```
================================================
FILE: docs/data/core-max.md
================================================
---
name: Max
slug: max
sourceRef: find.go#L410
category: core
subCategory: find
playUrl: https://go.dev/play/p/wYvG8gRRFw-
variantHelpers:
- core#find#max
similarHelpers:
- core#find#min
- core#find#maxby
- core#find#minby
- core#find#maxindex
- core#find#minindex
- core#find#maxindexby
- core#find#minindexby
- core#math#sum
- core#math#mean
- core#math#product
- core#math#mode
position: 200
signatures:
- "func Max[T constraints.Ordered](collection []T) T"
---
Searches the maximum value of a collection. Returns zero value when the collection is empty.
```go
max := lo.Max([]int{2, 5, 3})
// 5
```
================================================
FILE: docs/data/core-maxby.md
================================================
---
name: MaxBy
slug: maxby
sourceRef: find.go#L507
category: core
subCategory: find
playUrl: https://go.dev/play/p/PJCc-ThrwX1
variantHelpers:
- core#find#maxby
similarHelpers:
- core#find#max
- core#find#maxbyerr
- core#find#maxindex
- core#find#maxindexby
- core#find#min
- core#find#minby
- core#find#minindex
- core#find#minindexby
- core#math#sum
- core#math#mean
- core#math#product
- core#math#mode
position: 220
signatures:
- "func MaxBy[T any](collection []T, comparison func(a T, b T) bool) T"
---
Searches the maximum value of a collection using the given comparison function. Returns zero value when the collection is empty.
```go
type Point struct{ X int }
max := lo.MaxBy([]Point{{1}, {5}, {3}}, func(a, b Point) bool {
return a.X > b.X
})
// {5}
```
Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
See https://github.com/samber/lo/issues/129
================================================
FILE: docs/data/core-maxbyerr.md
================================================
---
name: MaxByErr
slug: maxbyerr
sourceRef: find.go#L528
category: core
subCategory: find
playUrl: https://go.dev/play/p/s-63-6_9zqM
variantHelpers:
- core#find#maxbyerr
similarHelpers:
- core#find#maxby
- core#find#max
- core#find#maxindex
- core#find#maxindexby
- core#find#min
- core#find#minby
- core#find#minindex
- core#find#minindexby
position: 221
signatures:
- "func MaxByErr[T any](collection []T, comparison func(a T, b T) (bool, error)) (T, error)"
---
Searches the maximum value of a collection using the given comparison function. Returns zero value and nil error when empty.
If the comparison function returns an error, iteration stops and the error is returned.
```go
type Point struct{ X int }
max, err := lo.MaxByErr([]Point{{1}, {5}, {3}}, func(a, b Point) (bool, error) {
return a.X > b.X, nil
})
// {5},
```
Example with error:
```go
type Point struct{ X int }
max, err := lo.MaxByErr([]Point{{1}, {5}, {3}}, func(a, b Point) (bool, error) {
if a.X == 5 {
return false, fmt.Errorf("cannot compare with 5")
}
return a.X > b.X, nil
})
// {1}, error("cannot compare with 5")
```
Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
See https://github.com/samber/lo/issues/129
================================================
FILE: docs/data/core-maxindex.md
================================================
---
name: MaxIndex
slug: maxindex
sourceRef: find.go#L432
category: core
subCategory: find
playUrl: https://go.dev/play/p/RFkB4Mzb1qt
variantHelpers:
- core#find#maxindex
similarHelpers:
- core#find#max
- core#find#maxby
- core#find#maxindexby
- core#find#min
- core#find#minby
- core#find#minindex
- core#find#minindexby
- core#math#sum
- core#math#mean
- core#math#product
- core#math#mode
position: 210
signatures:
- "func MaxIndex[T constraints.Ordered](collection []T) (T, int)"
---
Returns the maximum value and its index. Returns (zero value, -1) when the collection is empty.
```go
value, idx := lo.MaxIndex([]int{2, 5, 3})
// value == 5, idx == 1
```
================================================
FILE: docs/data/core-maxindexby.md
================================================
---
name: MaxIndexBy
slug: maxindexby
sourceRef: find.go#L566
category: core
subCategory: find
playUrl: https://go.dev/play/p/5yd4W7pe2QJ
variantHelpers:
- core#find#maxindexby
similarHelpers:
- core#find#max
- core#find#maxby
- core#find#maxbyerr
- core#find#maxindex
- core#find#maxindexbyerr
- core#find#min
- core#find#minby
- core#find#minindex
- core#find#minindexby
- core#math#sum
- core#math#mean
- core#math#product
- core#math#mode
position: 230
signatures:
- "func MaxIndexBy[T any](collection []T, comparison func(a T, b T) bool) (T, int)"
---
Returns the maximum value and its index using the given comparison function. Returns (zero value, -1) when the collection is empty.
```go
type Point struct{ X int }
value, idx := lo.MaxIndexBy([]Point{{1}, {5}, {3}}, func(a, b Point) bool {
return a.X > b.X
})
// value == {5}, idx == 1
```
Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
See https://github.com/samber/lo/issues/129
================================================
FILE: docs/data/core-maxindexbyerr.md
================================================
---
name: MaxIndexByErr
slug: maxindexbyerr
sourceRef: find.go#L591
category: core
subCategory: find
variantHelpers:
- core#find#maxindexbyerr
similarHelpers:
- core#find#maxindexby
- core#find#max
- core#find#maxby
- core#find#maxindex
- core#find#min
- core#find#minby
- core#find#minindex
- core#find#minindexby
position: 231
signatures:
- "func MaxIndexByErr[T any](collection []T, comparison func(a T, b T) (bool, error)) (T, int, error)"
---
Returns the maximum value and its index using the given comparison function. Returns (zero value, -1, nil) when empty.
If the comparison function returns an error, iteration stops and the error is returned.
```go
type Point struct{ X int }
value, idx, err := lo.MaxIndexByErr([]Point{{1}, {5}, {3}}, func(a, b Point) (bool, error) {
return a.X > b.X, nil
})
// value == {5}, idx == 1, err == nil
```
Example with error:
```go
type Point struct{ X int }
value, idx, err := lo.MaxIndexByErr([]Point{{1}, {5}, {3}}, func(a, b Point) (bool, error) {
if a.X == 5 {
return false, fmt.Errorf("cannot compare with 5")
}
return a.X > b.X, nil
})
// value == {1}, idx == 0, error("cannot compare with 5")
```
Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
See https://github.com/samber/lo/issues/129
================================================
FILE: docs/data/core-mean.md
================================================
---
name: Mean
slug: mean
sourceRef: math.go#L126
category: core
subCategory: math
playUrl: https://go.dev/play/p/tPURSuteUsP
variantHelpers:
- core#math#mean
similarHelpers:
- core#math#meanby
- core#math#mode
- core#math#sum
- core#math#sumby
- core#math#product
- core#math#productby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
position: 80
signatures:
- "func Mean[T constraints.Float | constraints.Integer](collection []T) T"
---
Calculates the arithmetic mean of a collection of numbers. Returns 0 for an empty collection.
```go
lo.Mean([]int{2, 3, 4, 5})
// 3
```
================================================
FILE: docs/data/core-meanby.md
================================================
---
name: MeanBy
slug: meanby
sourceRef: math.go#L161
category: core
subCategory: math
playUrl: https://go.dev/play/p/j7TsVwBOZ7P
variantHelpers:
- core#math#meanby
similarHelpers:
- core#math#mean
- core#math#meanbyerr
- core#math#mode
- core#math#sum
- core#math#sumby
- core#math#product
- core#math#productby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
position: 90
signatures:
- "func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) R) R"
---
Calculates the mean of values computed by a predicate. Returns 0 for an empty collection.
```go
list := []string{"aa", "bbb", "cccc", "ddddd"}
lo.MeanBy(list, func(item string) float64 {
return float64(len(item))
})
// 3.5
```
================================================
FILE: docs/data/core-meanbyerr.md
================================================
---
name: MeanByErr
slug: meanbyerr
sourceRef: math.go#L172
category: core
subCategory: math
variantHelpers:
- core#math#meanbyerr
similarHelpers:
- core#math#meanby
- core#math#mean
- core#math#mode
- core#math#sum
- core#math#sumbyerr
- core#math#product
- core#math#productby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
position: 91
signatures:
- "func MeanByErr[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) (R, error)) (R, error)"
---
Calculates the mean of values computed by a predicate. Returns 0 for an empty collection.
If the iteratee returns an error, iteration stops and the error is returned.
```go
list := []string{"aa", "bbb", "cccc", "ddddd"}
result, err := lo.MeanByErr(list, func(item string) (float64, error) {
return float64(len(item)), nil
})
// 3.5,
```
Example with error:
```go
list := []string{"aa", "bbb", "cccc", "ddddd"}
result, err := lo.MeanByErr(list, func(item string) (float64, error) {
if item == "cccc" {
return 0, fmt.Errorf("cccc is not allowed")
}
return float64(len(item)), nil
})
// 0, error("cccc is not allowed")
```
================================================
FILE: docs/data/core-min.md
================================================
---
name: Min
slug: min
sourceRef: find.go#L265
category: core
subCategory: find
playUrl: https://go.dev/play/p/fJFLwpY8eMN
variantHelpers:
- core#find#min
similarHelpers:
- core#find#minby
- core#find#minindex
- core#find#minindexby
- core#find#max
- core#find#maxby
- core#find#maxindex
- core#find#maxindexby
position: 140
signatures:
- "func Min[T constraints.Ordered](collection []T) T"
---
Returns the minimum value of a collection. Returns the zero value when the collection is empty.
```go
lo.Min([]int{1, 2, 3})
// 1
```
================================================
FILE: docs/data/core-minby.md
================================================
---
name: MinBy
slug: minby
sourceRef: find.go#L329
category: core
subCategory: find
playUrl: https://go.dev/play/p/-B1PsrHVnfx
variantHelpers:
- core#find#minby
similarHelpers:
- core#find#min
- core#find#minbyerr
- core#find#minindex
- core#find#minindexby
- core#find#max
- core#find#maxby
- core#find#maxindex
- core#find#maxindexby
- core#math#sum
- core#math#mean
- core#math#product
- core#math#mode
position: 160
signatures:
- "func MinBy[T any](collection []T, comparison func(a T, b T) bool) T"
---
Searches the minimum value of a collection using the given comparison function. Returns the first minimal value; zero value when empty.
```go
type Point struct{ X int }
min := lo.MinBy([]Point{{1}, {5}, {3}}, func(a, b Point) bool {
return a.X < b.X
})
// {1}
```
================================================
FILE: docs/data/core-minbyerr.md
================================================
---
name: MinByErr
slug: minbyerr
sourceRef: find.go#L349
category: core
subCategory: find
playUrl: https://go.dev/play/p/nvDYGS8q895
variantHelpers:
- core#find#minbyerr
similarHelpers:
- core#find#minby
- core#find#min
- core#find#minindex
- core#find#minindexby
- core#find#max
- core#find#maxby
- core#find#maxindex
- core#find#maxindexby
position: 161
signatures:
- "func MinByErr[T any](collection []T, comparison func(a T, b T) (bool, error)) (T, error)"
---
Searches the minimum value of a collection using the given comparison function. Returns the first minimal value; zero value and nil error when empty.
If the comparison function returns an error, iteration stops and the error is returned.
```go
type Point struct{ X int }
min, err := lo.MinByErr([]Point{{1}, {5}, {3}}, func(a, b Point) (bool, error) {
return a.X < b.X, nil
})
// {1},
```
Example with error:
```go
type Point struct{ X int }
min, err := lo.MinByErr([]Point{{1}, {5}, {3}}, func(a, b Point) (bool, error) {
if a.X == 5 {
return false, fmt.Errorf("cannot compare with 5")
}
return a.X < b.X, nil
})
// {0}, error("cannot compare with 5")
```
================================================
FILE: docs/data/core-minindex.md
================================================
---
name: MinIndex
slug: minindex
sourceRef: find.go#L287
category: core
subCategory: find
playUrl: https://go.dev/play/p/RxAidik4p50
variantHelpers:
- core#find#minindex
similarHelpers:
- core#find#min
- core#find#minby
- core#find#minindexby
- core#find#max
- core#find#maxby
- core#find#maxindex
- core#find#maxindexby
- core#math#sum
- core#math#mean
- core#math#product
- core#math#mode
position: 150
signatures:
- "func MinIndex[T constraints.Ordered](collection []T) (T, int)"
---
Returns the minimum value and its index. Returns (zero value, -1) when the collection is empty.
```go
value, idx := lo.MinIndex([]int{2, 5, 3})
// value == 2, idx == 0
```
================================================
FILE: docs/data/core-minindexby.md
================================================
---
name: MinIndexBy
slug: minindexby
sourceRef: find.go#L337
category: core
subCategory: find
playUrl: https://go.dev/play/p/zwwPRqWhnUY
variantHelpers:
- core#find#minindexby
similarHelpers:
- core#find#minindexbyerr
- core#find#min
- core#find#minby
- core#find#minindex
- core#find#max
- core#find#maxby
- core#find#maxindex
- core#find#maxindexby
- core#math#sum
- core#math#mean
- core#math#product
- core#math#mode
position: 170
signatures:
- "func MinIndexBy[T any](collection []T, comparison func(a T, b T) bool) (T, int)"
---
Searches the minimum value using a comparison function and returns the value and its index. Returns (zero value, -1) when empty.
```go
type Point struct{ X int }
value, idx := lo.MinIndexBy([]Point{{1}, {5}, {3}}, func(a, b Point) bool {
return a.X < b.X
})
// value == {1}, idx == 0
```
================================================
FILE: docs/data/core-minindexbyerr.md
================================================
---
name: MinIndexByErr
slug: minindexbyerr
sourceRef: find.go#L404
category: core
subCategory: find
playUrl: https://go.dev/play/p/MUqi_NvTKM1
signatures:
- "func MinIndexByErr[T any](collection []T, comparison func(a T, b T) (bool, error)) (T, int, error)"
variantHelpers:
- core#find#minindexbyerr
similarHelpers:
- core#find#minindexby
- core#find#min
- core#find#minby
- core#find#minbyerr
- core#find#minindex
- core#find#max
- core#find#maxby
- core#find#maxbyerr
- core#find#maxindex
- core#find#maxindexby
- core#find#maxindexbyerr
- core#math#sum
- core#math#sumbyerr
- core#math#mean
- core#math#meanbyerr
- core#math#product
- core#math#productbyerr
- core#math#mode
position: 171
---
Searches the minimum value using a comparison function and returns the value and its index. Returns (zero value, -1, nil) when empty. Stops iteration immediately when an error is encountered.
```go
type Point struct{ X int }
value, idx, err := lo.MinIndexByErr([]Point{{1}, {5}, {3}}, func(a, b Point) (bool, error) {
return a.X < b.X, nil
})
// value == {1}, idx == 0, err == nil
```
```go
// Error case - stops on first error
_, _, err := lo.MinIndexByErr([]Point{{1}, {5}, {0}}, func(a, b Point) (bool, error) {
if a.X == 0 || b.X == 0 {
return false, fmt.Errorf("zero value not allowed")
}
return a.X < b.X, nil
})
// error("zero value not allowed")
```
```go
// Error case on first comparison
_, _, err := lo.MinIndexByErr([]Point{{1}, {5}}, func(a, b Point) (bool, error) {
return false, fmt.Errorf("comparison error")
})
// error("comparison error")
```
================================================
FILE: docs/data/core-mode.md
================================================
---
name: Mode
slug: mode
sourceRef: math.go#L149
category: core
subCategory: math
playUrl: https://go.dev/play/p/PbiviqnV5zX
variantHelpers:
- core#math#mode
similarHelpers:
- core#math#mean
- core#math#meanby
- core#math#sum
- core#math#sumby
- core#math#product
- core#math#productby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
- core#math#countvalues
- core#math#countvaluesby
position: 100
signatures:
- "func Mode[T constraints.Integer | constraints.Float](collection []T) []T"
---
Returns the mode(s), i.e., the most frequent value(s) in a collection. If multiple values share the highest frequency, returns all. Empty input yields an empty slice.
```go
lo.Mode([]int{2, 2, 3, 3})
// []int{2, 3}
```
================================================
FILE: docs/data/core-mustx.md
================================================
---
name: MustX
slug: mustx
sourceRef: errors.go#L65
category: core
subCategory: error-handling
signatures:
- "func Must[T any](val T, err any, messageArgs ...any) T"
- "func Must0(err any, messageArgs ...any)"
- "func Must1[T any](val T, err any, messageArgs ...any) T"
- "func Must2[T1, T2 any](val1 T1, val2 T2, err any, messageArgs ...any) (T1, T2)"
- "func Must3[T1, T2, T3 any](val1 T1, val2 T2, val3 T3, err any, messageArgs ...any) (T1, T2, T3)"
- "func Must4[T1, T2, T3, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any, messageArgs ...any) (T1, T2, T3, T4)"
- "func Must5[T1, T2, T3, T4, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any, messageArgs ...any) (T1, T2, T3, T4, T5)"
- "func Must6[T1, T2, T3, T4, T5, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any, messageArgs ...any) (T1, T2, T3, T4, T5, T6)"
playUrl: https://go.dev/play/p/TMoWrRp3DyC
variantHelpers:
- core#error-handling#must
- core#error-handling#mustx
similarHelpers:
- core#error-handling#validate
- core#error-handling#tryx
- core#error-handling#tryorx
- core#error-handling#trycatch
- core#error-handling#trywitherrorvalue
- core#error-handling#errorsas
- core#error-handling#assert
position: 10
---
Panics if err is an error or false, returning successful values otherwise. Variants cover functions returning 0 to 6 values.
```go
// returns 10, panics if err is not nil
v := lo.Must(strconv.Atoi("10"))
// panics with custom message
lo.Must0(fmt.Errorf("boom"), "failed to parse")
// panics if myFunc returns an error
func myFunc() (int, string, float64, bool, error) { ... }
a, b, c, d := lo.Must4(myFunc())
```
================================================
FILE: docs/data/core-newdebounce.md
================================================
---
name: NewDebounce
slug: newdebounce
sourceRef: retry.go#L54
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/_IPY7ROzbMk
variantHelpers:
- core#concurrency#newdebounce
similarHelpers:
- core#concurrency#newdebounceby
- core#concurrency#newthrottle
- core#concurrency#newthrottleby
- core#concurrency#newtransaction
- core#concurrency#synchronize
position: 0
signatures:
- "func NewDebounce(duration time.Duration, f ...func()) (func(), func())"
---
Creates a debounced function that delays invoking the callbacks until after the wait duration has elapsed since the last call. Returns the debounced function and a cancel function.
```go
debounce, cancel := lo.NewDebounce(
100 * time.Millisecond,
func() {
println("Called once after debounce!")
},
)
for i := 0; i < 10; i++ {
debounce()
}
time.Sleep(200 * time.Millisecond)
cancel()
```
================================================
FILE: docs/data/core-newdebounceby.md
================================================
---
name: NewDebounceBy
slug: newdebounceby
sourceRef: retry.go#L137
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/Izk7GEzZm2Q
variantHelpers:
- core#concurrency#newdebounceby
similarHelpers:
- core#concurrency#newdebounce
- core#concurrency#newthrottle
- core#concurrency#newthrottleby
- core#concurrency#newdebounceby
position: 10
signatures:
- "func NewDebounceBy[T comparable](duration time.Duration, f ...func(key T, count int)) (func(key T), func(key T))"
---
Creates a debounced function per key that delays invoking callbacks until after the wait duration has elapsed for that key. Returns a per-key debounced function and a per-key cancel function.
```go
debounce, cancel := lo.NewDebounceBy[string](
100*time.Millisecond,
func(key string, count int) {
println(key, count)
},
)
for i := 0; i < 10; i++ {
debounce("first")
}
time.Sleep(200 * time.Millisecond)
cancel("first")
```
================================================
FILE: docs/data/core-newthrottle.md
================================================
---
name: NewThrottle
slug: newthrottle
sourceRef: retry.go#L349
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/qQn3fm8Z7jS
variantHelpers:
- core#concurrency#newthrottle
similarHelpers:
- core#concurrency#newthrottleby
- core#concurrency#newthrottlewithcount
- core#concurrency#newdebounce
- core#concurrency#newdebounceby
position: 70
signatures:
- "func NewThrottle(interval time.Duration, f ...func()) (throttle func(), reset func())"
---
Creates a throttled function that invokes callbacks at most once per interval. Returns the throttled function and a reset function.
```go
throttle, reset := lo.NewThrottle(
100*time.Millisecond,
func() {
println("tick")
},
)
for i := 0; i < 10; i++ {
throttle();
time.Sleep(30*time.Millisecond)
}
reset()
```
================================================
FILE: docs/data/core-newthrottleby.md
================================================
---
name: NewThrottleBy
slug: newthrottleby
sourceRef: retry.go#L371
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/0Wv6oX7dHdC
variantHelpers:
- core#concurrency#newthrottleby
similarHelpers:
- core#concurrency#newthrottle
- core#concurrency#newthrottlebywithcount
- core#concurrency#newdebounce
- core#concurrency#newdebounceby
position: 90
signatures:
- "func NewThrottleBy[T comparable](interval time.Duration, f ...func(key T)) (throttle func(key T), reset func())"
---
Creates a throttled function per key.
```go
throttle, reset := lo.NewThrottleBy[string](
100*time.Millisecond,
func(key string) {
println(key)
},
)
for i := 0; i < 10; i++ {
throttle("foo");
time.Sleep(30*time.Millisecond)
}
reset()
```
================================================
FILE: docs/data/core-newthrottlebywithcount.md
================================================
---
name: NewThrottleByWithCount
slug: newthrottlebywithcount
sourceRef: retry.go#L377
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/vQk3ECH7_EW
variantHelpers:
- core#concurrency#newthrottlebywithcount
similarHelpers:
- core#concurrency#newthrottle
- core#concurrency#newthrottleby
- core#concurrency#newthrottlewithcount
- core#concurrency#newdebounce
- core#concurrency#newdebounceby
position: 100
signatures:
- "func NewThrottleByWithCount[T comparable](interval time.Duration, count int, f ...func(key T)) (throttle func(key T), reset func())"
---
Creates a throttled function per key with a per-interval invocation limit.
```go
throttle, reset := lo.NewThrottleByWithCount[string](
100*time.Millisecond,
3,
func(key string) {
println(key)
},
)
for i := 0; i < 10; i++ {
throttle("foo")
}
reset()
```
================================================
FILE: docs/data/core-newthrottlewithcount.md
================================================
---
name: NewThrottleWithCount
slug: newthrottlewithcount
sourceRef: retry.go#L355
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/w5nc0MgWtjC
variantHelpers:
- core#concurrency#newthrottlewithcount
similarHelpers:
- core#concurrency#newthrottle
- core#concurrency#newthrottleby
- core#concurrency#newthrottlebywithcount
- core#concurrency#newdebounce
- core#concurrency#newdebounceby
position: 80
signatures:
- "func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throttle func(), reset func())"
---
Creates a throttled function with a per-interval invocation limit.
```go
throttle, reset := lo.NewThrottleWithCount(
100*time.Millisecond,
3,
func() {
println("tick")
},
)
for i := 0; i < 10; i++ {
throttle();
time.Sleep(30*time.Millisecond)
}
reset()
```
================================================
FILE: docs/data/core-newtransaction.md
================================================
---
name: NewTransaction
slug: newtransaction
sourceRef: retry.go#L253
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/7B2o52wEQbj
variantHelpers:
- core#concurrency#newtransaction
similarHelpers:
- core#concurrency#synchronize
- core#concurrency#asyncx
- core#concurrency#waitfor
position: 30
signatures:
- "func NewTransaction[T any]() *Transaction[T]"
---
Creates a new Saga transaction that chains steps with rollback functions.
Use Then to add steps with exec and rollback functions. Call Process with an initial state to execute the pipeline; if a step returns an error, previously executed steps are rolled back in reverse order using their rollback functions.
```go
type Acc struct{ Sum int }
tx := lo.NewTransaction[Acc]().
Then(
func(a Acc) (Acc, error) {
a.Sum += 10
return a, nil
},
func(a Acc) Acc {
a.Sum -= 10
return a
},
).
Then(
func(a Acc) (Acc, error) {
a.Sum *= 3
return a, nil
},
func(a Acc) Acc {
a.Sum /= 3
return a
},
)
res, err := tx.Process(Acc{Sum: 1})
// res.Sum == 33, err == nil
```
================================================
FILE: docs/data/core-nil.md
================================================
---
name: Nil
slug: nil
sourceRef: type_manipulation.go#L34
category: core
subCategory: type
playUrl: https://go.dev/play/p/P2sD0PMXw4F
variantHelpers:
- core#type#nil
similarHelpers:
- core#type#isnil
- core#type#isnotnil
- core#type#toptr
- core#type#fromptr
position: 1
signatures:
- "func Nil[T any]() *T"
---
Returns a nil pointer of type.
```go
lo.Nil[string]()
// (*string)(nil)
lo.Nil[int]()
// (*int)(nil)
```
Useful when you need a nil pointer of a specific type without declaring a variable first.
================================================
FILE: docs/data/core-none.md
================================================
---
name: None
slug: none
sourceRef: intersect.go#L79
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/fye7JsmxzPV
variantHelpers:
- core#intersect#none
similarHelpers:
- core#intersect#noneby
- core#intersect#contains
- core#intersect#every
- core#intersect#some
- core#intersect#containsby
position: 60
signatures:
- "func None[T comparable](collection []T, subset []T) bool"
---
Returns true if no element of a subset is contained in a collection, or if the subset is empty.
```go
ok := lo.None([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// true
```
================================================
FILE: docs/data/core-noneby.md
================================================
---
name: NoneBy
slug: noneby
sourceRef: intersect.go#L91
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/O64WZ32H58S
variantHelpers:
- core#intersect#noneby
similarHelpers:
- core#intersect#none
- core#intersect#everyby
- core#intersect#every
- core#intersect#someby
- core#intersect#some
- core#intersect#containsby
- core#intersect#contains
position: 70
signatures:
- "func NoneBy[T any](collection []T, predicate func(item T) bool) bool"
---
Returns true if the predicate returns true for none of the elements in the collection, or if the collection is empty.
```go
ok := lo.NoneBy(
[]int{1, 2, 3, 4},
func(x int) bool {
return x < 0
},
)
// true
```
================================================
FILE: docs/data/core-nth.md
================================================
---
name: Nth
slug: nth
sourceRef: find.go#L617
category: core
subCategory: find
playUrl: https://go.dev/play/p/mNFI9-kIZZ5
variantHelpers:
- core#find#nth
similarHelpers:
- core#find#first
- core#find#last
- core#find#nthor
- core#find#nthorempty
- core#slice#indexof
- core#slice#drop
position: 320
signatures:
- "func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error)"
---
Returns the element at index nth of collection. If nth is negative, returns the nth element from the end. Returns an error when nth is out of slice bounds.
```go
v, _ := lo.Nth([]int{10, 20, 30}, 1)
// v == 20
```
================================================
FILE: docs/data/core-nthor.md
================================================
---
name: NthOr
slug: nthor
sourceRef: find.go#L635
category: core
subCategory: find
playUrl: https://go.dev/play/p/njKcNhBBVsF
variantHelpers:
- core#find#nthor
similarHelpers:
- core#find#nthorempty
- core#find#nth
- core#find#findorelse
- core#find#firstor
position: 330
signatures:
- "func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T"
---
Returns the element at index nth of collection, or the fallback if out of bounds. If nth is negative, returns the nth element from the end.
```go
v := lo.NthOr([]int{10, 20, 30}, 10, -1)
// v == -1
```
================================================
FILE: docs/data/core-nthorempty.md
================================================
---
name: NthOrEmpty
slug: nthorempty
sourceRef: find.go#L647
category: core
subCategory: find
playUrl: https://go.dev/play/p/sHoh88KWt6B
variantHelpers:
- core#find#nthorempty
similarHelpers:
- core#find#nthor
- core#find#nth
- core#find#findorelse
- core#find#firstorempty
position: 340
signatures:
- "func NthOrEmpty[T any, N constraints.Integer](collection []T, nth N) T"
---
Returns the element at index nth of collection, or the zero value if out of bounds. If nth is negative, returns the nth element from the end.
```go
v := lo.NthOrEmpty([]int{10, 20, 30}, 10)
// v == 0
```
================================================
FILE: docs/data/core-omitby.md
================================================
---
name: OmitBy
slug: omitby
sourceRef: map.go#L159
category: core
subCategory: map
playUrl: https://go.dev/play/p/EtBsR43bdsd
variantHelpers:
- core#map#omitby
similarHelpers:
- core#map#pickby
- core#map#omitbykeys
- core#map#omitbyvalues
- core#map#pickbykeys
- core#map#pickbyvalues
- core#map#filterkeys
- core#map#filtervalues
- core#map#omitbyerr
position: 90
signatures:
- "func OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map"
---
Returns a map of the same type excluding entries that match the predicate.
```go
m := lo.OmitBy(
map[string]int{"foo": 1, "bar": 2, "baz": 3},
func(key string, value int) bool {
return value%2 == 1
},
)
// map[string]int{"bar": 2}
```
================================================
FILE: docs/data/core-omitbyerr.md
================================================
---
name: OmitByErr
slug: omitbyerr
sourceRef: map.go#L171
category: core
subCategory: map
signatures:
- "func OmitByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error)"
variantHelpers:
- core#map#omitbyerr
similarHelpers:
- core#map#omitby
- core#map#pickbyerr
- core#map#pickby
position: 95
---
Returns a map of the same type excluding entries that match the predicate. Returns an error if the predicate function fails, stopping iteration immediately.
```go
m, err := lo.OmitByErr(
map[string]int{"foo": 1, "bar": 2, "baz": 3},
func(key string, value int) (bool, error) {
if key == "bar" {
return false, fmt.Errorf("bar not allowed")
}
return value%2 == 1, nil
},
)
// map[string]int(nil), error("bar not allowed")
```
```go
m, err := lo.OmitByErr(
map[string]int{"foo": 1, "bar": 2, "baz": 3},
func(key string, value int) (bool, error) {
return value%2 == 1, nil
},
)
// map[string]int{"bar": 2}, nil
```
================================================
FILE: docs/data/core-omitbykeys.md
================================================
---
name: OmitByKeys
slug: omitbykeys
sourceRef: map.go#L154
category: core
subCategory: map
playUrl: https://go.dev/play/p/t1QjCrs-ysk
variantHelpers:
- core#map#omitbykeys
similarHelpers:
- core#map#omitby
- core#map#omitbyvalues
- core#map#pickby
- core#map#pickbykeys
- core#map#pickbyvalues
position: 100
signatures:
- "func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map"
---
Returns a map of the same type excluding the provided keys.
```go
m := lo.OmitByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"})
// map[string]int{"bar": 2}
```
================================================
FILE: docs/data/core-omitbyvalues.md
================================================
---
name: OmitByValues
slug: omitbyvalues
sourceRef: map.go#L167
category: core
subCategory: map
playUrl: https://go.dev/play/p/9UYZi-hrs8j
variantHelpers:
- core#map#omitbyvalues
similarHelpers:
- core#map#omitby
- core#map#omitbykeys
- core#map#pickby
- core#map#pickbykeys
- core#map#pickbyvalues
position: 110
signatures:
- "func OmitByValues[K comparable, V comparable, Map ~map[K]V](in Map, values []V) Map"
---
Returns a map of the same type excluding the provided values.
```go
m := lo.OmitByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3})
// map[string]int{"bar": 2}
```
================================================
FILE: docs/data/core-partialx.md
================================================
---
name: PartialX
slug: partialx
sourceRef: func.go#L5
category: core
subCategory: function
signatures:
- "func Partial[T1, T2, R any](f func(a T1, b T2) R, arg1 T1) func(T2) R"
- "func Partial1[T1, T2, R any](f func(T1, T2) R, arg1 T1) func(T2) R"
- "func Partial2[T1, T2, T3, R any](f func(T1, T2, T3) R, arg1 T1) func(T2, T3) R"
- "func Partial3[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) R, arg1 T1) func(T2, T3, T4) R"
- "func Partial4[T1, T2, T3, T4, T5, R any](f func(T1, T2, T3, T4, T5) R, arg1 T1) func(T2, T3, T4, T5) R"
- "func Partial5[T1, T2, T3, T4, T5, T6, R any](f func(T1, T2, T3, T4, T5, T6) R, arg1 T1) func(T2, T3, T4, T5, T6) R"
playUrl: https://go.dev/play/p/Sy1gAQiQZ3v
variantHelpers:
- core#function#partial
- core#function#partialx
similarHelpers:
- core#condition#if
- core#condition#ternary
- core#condition#switch
- core#retry#attempt
- core#retry#attemptwithdelay
position: 0
---
Pre-binds the first argument of a function. Variants support functions taking from 2 up to 6 input parameters.
```go
add := func(x, y int) int {
return x + y
}
add10 := lo.Partial(add, 10)
sum := add10(5)
// 15
```
================================================
FILE: docs/data/core-partitionby.md
================================================
---
name: PartitionBy
slug: partitionby
sourceRef: slice.go#L240
category: core
subCategory: slice
playUrl: https://go.dev/play/p/NfQ_nGjkgXW
variantHelpers:
- core#slice#partitionby
similarHelpers:
- core#slice#partitionbyerr
- core#slice#groupby
- core#slice#groupbyerr
- core#slice#groupbymap
- core#slice#chunk
- core#map#keyby
position: 150
signatures:
- "func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K) []Slice"
---
Partitions a slice into groups determined by a key computed from each element, preserving original order.
================================================
FILE: docs/data/core-partitionbyerr.md
================================================
---
name: PartitionByErr
slug: partitionbyerr
sourceRef: slice.go#L385
category: core
subCategory: slice
signatures:
- "func PartitionByErr[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) (K, error)) ([]Slice, error)"
variantHelpers:
- core#slice#partitionbyerr
similarHelpers:
- core#slice#partitionby
- core#slice#groupby
- core#slice#groupbyerr
- core#slice#groupbymap
- core#slice#chunk
- core#map#keyby
position: 151
---
Partitions a slice into groups determined by a key computed from each element using an iteratee that can return an error. Stops iteration immediately when an error is encountered. Preserves original order.
```go
// Error case - stops on first error
result, err := lo.PartitionByErr([]int{-2, -1, 0, 1, 2, 3}, func(x int) (string, error) {
if x == 0 {
return "", fmt.Errorf("zero is not allowed")
}
if x < 0 {
return "negative", nil
} else if x%2 == 0 {
return "even", nil
}
return "odd", nil
})
// [][]int(nil), error("zero is not allowed")
```
```go
// Success case
result, err := lo.PartitionByErr([]int{-2, -1, 0, 1, 2}, func(x int) (string, error) {
if x < 0 {
return "negative", nil
} else if x%2 == 0 {
return "even", nil
}
return "odd", nil
})
// [][]int{{-2, -1}, {0, 2}, {1}}, nil
```
================================================
FILE: docs/data/core-pascalcase.md
================================================
---
name: PascalCase
slug: pascalcase
sourceRef: string.go#L166
category: core
subCategory: string
playUrl: https://go.dev/play/p/uxER7XpRHLB
variantHelpers:
- core#string#pascalcase
similarHelpers:
- core#string#camelcase
- core#string#snakecase
- core#string#kebabcase
- core#string#capitalize
- core#string#words
position: 40
signatures:
- "func PascalCase(str string) string"
---
Converts a string to PascalCase.
```go
lo.PascalCase("hello_world")
// "HelloWorld"
```
================================================
FILE: docs/data/core-pickby.md
================================================
---
name: PickBy
slug: pickby
sourceRef: map.go#L105
category: core
subCategory: map
playUrl: https://go.dev/play/p/kdg8GR_QMmf
variantHelpers:
- core#map#pickby
similarHelpers:
- core#map#omitby
- core#map#omitbykeys
- core#map#omitbyvalues
- core#map#pickbykeys
- core#map#pickbyvalues
- core#map#filterkeys
- core#map#filtervalues
- core#map#pickbyerr
position: 60
signatures:
- "func PickBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map"
---
Returns a map of the same type filtered by a key/value predicate.
```go
m := lo.PickBy(
map[string]int{"foo": 1, "bar": 2, "baz": 3},
func(key string, value int) bool {
return value%2 == 1
},
)
// map[string]int{"foo": 1, "baz": 3}
```
================================================
FILE: docs/data/core-pickbyerr.md
================================================
---
name: PickByErr
slug: pickbyerr
sourceRef: map.go#L117
category: core
subCategory: map
signatures:
- "func PickByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error)"
variantHelpers:
- core#map#pickbyerr
similarHelpers:
- core#map#pickby
- core#map#omitby
- core#map#omitbyerr
position: 65
---
Returns a map of the same type filtered by a key/value predicate. Returns an error if the predicate function fails, stopping iteration immediately.
```go
m, err := lo.PickByErr(
map[string]int{"foo": 1, "bar": 2, "baz": 3},
func(key string, value int) (bool, error) {
if key == "bar" {
return false, fmt.Errorf("bar not allowed")
}
return value%2 == 1, nil
},
)
// map[string]int(nil), error("bar not allowed")
```
```go
m, err := lo.PickByErr(
map[string]int{"foo": 1, "bar": 2, "baz": 3},
func(key string, value int) (bool, error) {
return value%2 == 1, nil
},
)
// map[string]int{"foo": 1, "baz": 3}, nil
```
================================================
FILE: docs/data/core-pickbykeys.md
================================================
---
name: PickByKeys
slug: pickbykeys
sourceRef: map.go#L118
category: core
subCategory: map
playUrl: https://go.dev/play/p/R1imbuci9qU
variantHelpers:
- core#map#pickbykeys
similarHelpers:
- core#map#pickby
- core#map#pickbyvalues
- core#map#omitby
- core#map#omitbykeys
- core#map#omitbyvalues
position: 70
signatures:
- "func PickByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map"
---
Returns a map of the same type filtered by the provided keys.
```go
m := lo.PickByKeys(
map[string]int{"foo": 1, "bar": 2, "baz": 3},
[]string{"foo", "baz"},
)
// map[string]int{"foo": 1, "baz": 3}
```
================================================
FILE: docs/data/core-pickbyvalues.md
================================================
---
name: PickByValues
slug: pickbyvalues
sourceRef: map.go#L130
category: core
subCategory: map
playUrl: https://go.dev/play/p/-_PPkSbO1Kc
variantHelpers:
- core#map#pickbyvalues
similarHelpers:
- core#map#pickby
- core#map#pickbykeys
- core#map#omitby
- core#map#omitbykeys
- core#map#omitbyvalues
position: 80
signatures:
- "func PickByValues[K comparable, V comparable, Map ~map[K]V](in Map, values []V) Map"
---
Returns a map of the same type filtered by the provided values.
```go
m := lo.PickByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3})
// map[string]int{"foo": 1, "baz": 3}
```
================================================
FILE: docs/data/core-product.md
================================================
---
name: Product
slug: product
sourceRef: math.go#L90
category: core
subCategory: math
playUrl: https://go.dev/play/p/2_kjM_smtAH
variantHelpers:
- core#math#product
similarHelpers:
- core#math#sum
- core#math#mean
- core#math#productby
- core#math#sumby
- core#math#meanby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
- core#math#mode
position: 60
signatures:
- "func Product[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T"
---
Calculates the product of the values in a collection. Returns 1 for nil or empty collections.
```go
lo.Product([]int{1, 2, 3, 4, 5})
// 120
```
================================================
FILE: docs/data/core-productby.md
================================================
---
name: ProductBy
slug: productby
sourceRef: math.go#L125
category: core
subCategory: math
playUrl: https://go.dev/play/p/wadzrWr9Aer
variantHelpers:
- core#math#productby
similarHelpers:
- core#math#product
- core#math#productbyerr
- core#math#sumby
- core#math#meanby
- core#find#minby
- core#find#maxby
- core#find#minindexby
- core#find#maxindexby
- core#math#sum
- core#math#mean
position: 70
signatures:
- "func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R"
---
Calculates the product of values computed by a predicate. Returns 1 for nil or empty collections.
```go
strings := []string{"foo", "bar"}
lo.ProductBy(strings, func(item string) int {
return len(item)
})
// 9
```
================================================
FILE: docs/data/core-productbyerr.md
================================================
---
name: ProductByErr
slug: productbyerr
sourceRef: math.go#L134
category: core
subCategory: math
variantHelpers:
- core#math#productbyerr
similarHelpers:
- core#math#productby
- core#math#product
- core#math#sumbyerr
- core#math#meanby
- core#find#minby
- core#find#maxby
position: 71
signatures:
- "func ProductByErr[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) (R, error)) (R, error)"
---
Calculates the product of values computed by a predicate. Returns 1 for nil or empty collections.
If the iteratee returns an error, iteration stops and the error is returned.
```go
strings := []string{"foo", "bar"}
result, err := lo.ProductByErr(strings, func(item string) (int, error) {
return len(item), nil
})
// 9,
```
Example with error:
```go
strings := []string{"foo", "bar", "baz"}
result, err := lo.ProductByErr(strings, func(item string) (int, error) {
if item == "bar" {
return 0, fmt.Errorf("bar is not allowed")
}
return len(item), nil
})
// 3, error("bar is not allowed")
```
================================================
FILE: docs/data/core-randomstring.md
================================================
---
name: RandomString
slug: randomstring
sourceRef: string.go#L35
category: core
subCategory: string
playUrl: https://go.dev/play/p/rRseOQVVum4
variantHelpers:
- core#string#randomstring
similarHelpers:
- core#string#substring
- core#string#chunkstring
- core#string#words
- core#string#capitalize
- core#string#camelcase
- core#string#pascalcase
- core#string#kebabcase
- core#string#snakecase
position: 0
signatures:
- "func RandomString(size int, charset []rune) string"
---
Returns a random string of the specified length from the given charset.
```go
str := lo.RandomString(5, lo.LettersCharset)
// e.g., "eIGbt"
```
================================================
FILE: docs/data/core-range.md
================================================
---
name: Range
slug: range
sourceRef: math.go#L9
category: core
subCategory: math
playUrl: https://go.dev/play/p/rho00R0WuHs
variantHelpers:
- core#math#range
similarHelpers:
- core#math#rangefrom
- core#math#rangewithsteps
- core#math#times
- core#slice#repeat
- core#slice#repeatby
position: 0
signatures:
- "func Range(elementNum int) []int"
---
Creates a slice of integers of the given length starting at 0. Negative length produces a descending sequence.
```go
lo.Range(4)
// []int{0, 1, 2, 3}
lo.Range(-4)
// []int{0, -1, -2, -3}
```
================================================
FILE: docs/data/core-rangefrom.md
================================================
---
name: RangeFrom
slug: rangefrom
sourceRef: math.go#L21
category: core
subCategory: math
playUrl: https://go.dev/play/p/0r6VimXAi9H
variantHelpers:
- core#math#rangefrom
similarHelpers:
- core#math#range
- core#math#rangewithsteps
- core#math#times
- core#slice#repeat
- core#slice#repeatby
position: 10
signatures:
- "func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) []T"
---
Creates a slice starting at `start` with the specified length. Negative length yields a descending sequence.
```go
lo.RangeFrom(1, 5)
// []int{1, 2, 3, 4, 5}
```
================================================
FILE: docs/data/core-rangewithsteps.md
================================================
---
name: RangeWithSteps
slug: rangewithsteps
sourceRef: math.go#L34
category: core
subCategory: math
playUrl: https://go.dev/play/p/0r6VimXAi9H
variantHelpers:
- core#math#rangewithsteps
similarHelpers:
- core#math#range
- core#math#rangefrom
- core#math#times
- core#slice#repeat
- core#slice#repeatby
position: 20
signatures:
- "func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) []T"
---
Creates a slice progressing from `start` up to, but not including, `end` by `step`. Returns empty if `step` is 0 or direction mismatches.
```go
lo.RangeWithSteps(0, 20, 5)
// []int{0, 5, 10, 15}
```
================================================
FILE: docs/data/core-reduce.md
================================================
---
name: Reduce
slug: reduce
sourceRef: slice.go#L87
category: core
subCategory: slice
playUrl: https://go.dev/play/p/CgHYNUpOd1I
variantHelpers:
- core#slice#reduce
similarHelpers:
- core#slice#reduceerr
- core#slice#reduceright
- core#slice#sum
- core#slice#sumby
- core#slice#sumbyerr
- core#slice#product
- core#slice#productby
- core#slice#mean
- core#slice#meanby
position: 50
signatures:
- "func Reduce[T any, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R"
---
Reduces a collection to a single value by accumulating results of an accumulator function. Each call receives the previous result value.
```go
sum := lo.Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int {
return agg + item
}, 0)
// 10
```
================================================
FILE: docs/data/core-reduceerr.md
================================================
---
name: ReduceErr
slug: reduceerr
sourceRef: slice.go#L128
category: core
subCategory: slice
signatures:
- "func ReduceErr[T any, R any](collection []T, accumulator func(agg R, item T, index int) (R, error), initial R) (R, error)"
variantHelpers:
- core#slice#reduceerr
similarHelpers:
- core#slice#reduce
- core#slice#reduceright
- core#slice#sum
- core#slice#sumby
- core#slice#sumbyerr
position: 51
---
Reduces a collection to a single value by accumulating results of an accumulator function that can return an error. Stops iteration immediately when an error is encountered.
```go
// Error case - stops on first error
result, err := lo.ReduceErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
if item == 3 {
return 0, fmt.Errorf("number 3 is not allowed")
}
return agg + item, nil
}, 0)
// 0, error("number 3 is not allowed")
```
```go
// Success case
result, err := lo.ReduceErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
return agg + item, nil
}, 0)
// 10, nil
```
================================================
FILE: docs/data/core-reduceright.md
================================================
---
name: ReduceRight
slug: reduceright
sourceRef: slice.go#L97
category: core
subCategory: slice
playUrl: https://go.dev/play/p/Fq3W70l7wXF
variantHelpers:
- core#slice#reduceright
similarHelpers:
- core#slice#reducerighterr
- core#slice#reduce
- core#slice#reduceerr
- core#slice#sum
- core#slice#product
- core#slice#mean
- core#slice#max
- core#slice#min
position: 60
signatures:
- "func ReduceRight[T any, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R"
---
Like Reduce except it iterates from right to left and accumulates into a single value.
```go
result := lo.ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item []int, _ int) []int {
return append(agg, item...)
}, []int{})
// []int{4, 5, 2, 3, 0, 1}
```
================================================
FILE: docs/data/core-reducerighterr.md
================================================
---
name: ReduceRightErr
slug: reducerighterr
sourceRef: slice.go#L153
category: core
subCategory: slice
signatures:
- "func ReduceRightErr[T any, R any](collection []T, accumulator func(agg R, item T, index int) (R, error), initial R) (R, error)"
variantHelpers:
- core#slice#reducerighterr
similarHelpers:
- core#slice#reduceright
- core#slice#reduce
- core#slice#reduceerr
position: 61
---
Like Reduce but iterates from right to left and accumulates into a single value using an accumulator function that can return an error. Stops iteration immediately when an error is encountered.
```go
// Error case - stops on first error (from right to left)
result, err := lo.ReduceRightErr([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item []int, _ int) ([]int, error) {
if len(item) > 0 && item[0] == 4 {
return nil, fmt.Errorf("element starting with 4 is not allowed")
}
return append(agg, item...), nil
}, []int{})
// []int(nil), error("element starting with 4 is not allowed")
```
```go
// Success case
result, err := lo.ReduceRightErr([]int{1, 2, 3, 4}, func(agg int, item int, _ int) (int, error) {
return agg + item, nil
}, 0)
// 10, nil
```
================================================
FILE: docs/data/core-reject.md
================================================
---
name: Reject
slug: reject
sourceRef: slice.go#L532
category: core
subCategory: slice
playUrl: https://go.dev/play/p/pFCF5WVB225
variantHelpers:
- core#slice#reject
similarHelpers:
- core#slice#filter
- core#slice#rejecterr
- core#slice#filterreject
- core#slice#rejectmap
- core#slice#filterreject
- mutable#slice#reject
position: 260
signatures:
- "func Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice"
---
Returns the elements for which the predicate returns false (opposite of Filter).
```go
lo.Reject(
[]int{1, 2, 3, 4},
func(x int, _ int) bool {
return x%2 == 0
},
)
// []int{1, 3}
```
================================================
FILE: docs/data/core-rejecterr.md
================================================
---
name: RejectErr
slug: rejecterr
sourceRef: slice.go#L897
category: core
subCategory: slice
signatures:
- "func RejectErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error)"
playUrl: https://go.dev/play/p/pFCF5WVB225
variantHelpers:
- core#slice#rejecterr
similarHelpers:
- core#slice#reject
- core#slice#filtererr
- core#slice#rejectmap
- core#slice#filterreject
position: 265
---
The opposite of FilterErr. Returns the elements for which the predicate returns false. If the predicate returns an error, iteration stops immediately and returns the error.
```go
odd, err := lo.RejectErr([]int{1, 2, 3, 4}, func(x int, index int) (bool, error) {
if x == 3 {
return false, errors.New("number 3 is not allowed")
}
return x%2 == 0, nil
})
// []int(nil), error("number 3 is not allowed")
```
```go
odd, err := lo.RejectErr([]int{1, 2, 3, 4}, func(x int, index int) (bool, error) {
return x%2 == 0, nil
})
// []int{1, 3}, nil
```
================================================
FILE: docs/data/core-rejectmap.md
================================================
---
name: RejectMap
slug: rejectmap
sourceRef: slice.go#L550
category: core
subCategory: slice
playUrl: https://go.dev/play/p/bmXhtuM2OMq
variantHelpers:
- core#slice#rejectmap
similarHelpers:
- core#slice#filtermap
- core#slice#map
- core#slice#filter
- core#slice#reject
position: 270
signatures:
- "func RejectMap[T any, R any](collection []T, callback func(item T, index int) (R, bool)) []R"
---
Opposite of FilterMap: maps each item and includes results where the predicate returned false.
```go
items := lo.RejectMap([]int{1, 2, 3, 4}, func(x int, _ int) (int, bool) {
return x * 10, x%2 == 0
})
// []int{10, 30}
```
================================================
FILE: docs/data/core-repeat.md
================================================
---
name: Repeat
slug: repeat
sourceRef: slice.go#L362
category: core
subCategory: slice
playUrl: https://go.dev/play/p/g3uHXbmc3b6
variantHelpers:
- core#slice#repeat
similarHelpers:
- core#slice#times
- core#slice#repeatby
position: 220
signatures:
- "func Repeat[T any](count int, initial T) []T"
---
Builds a slice with N copies of initial value.
```go
lo.Repeat(5, "42")
// []string{"42", "42", "42", "42", "42"}
```
================================================
FILE: docs/data/core-repeatby.md
================================================
---
name: RepeatBy
slug: repeatby
sourceRef: slice.go#L362
category: core
subCategory: slice
playUrl: https://go.dev/play/p/ozZLCtX_hNU
variantHelpers:
- core#slice#repeatby
- core#slice#repeatbyerr
similarHelpers:
- core#slice#times
- core#slice#repeat
position: 220
signatures:
- "func RepeatBy[T any](count int, callback func(index int) T) []T"
---
Builds a slice by calling the callback N times with the current index.
```go
lo.RepeatBy(5, func(i int) string {
return strconv.Itoa(i * i)
})
// []string{"0", "1", "4", "9", "16"}
```
================================================
FILE: docs/data/core-repeatbyerr.md
================================================
---
name: RepeatByErr
slug: repeatbyerr
sourceRef: slice.go#L564
category: core
subCategory: slice
signatures:
- "func RepeatByErr[T any](count int, callback func(index int) (T, error)) ([]T, error)"
variantHelpers:
- core#slice#repeatbyerr
similarHelpers:
- core#slice#repeatby
- core#slice#times
- core#slice#repeat
position: 225
---
Builds a slice by calling the callback N times with the current index. The callback can return an error to stop iteration immediately.
```go
result, err := lo.RepeatByErr(5, func(i int) (int, error) {
return i * i, nil
})
// []int{0, 1, 4, 9, 16},
```
Example with error:
```go
result, err := lo.RepeatByErr(5, func(i int) (int, error) {
if i == 3 {
return 0, fmt.Errorf("number 3 is not allowed")
}
return i * i, nil
})
// []int(nil), error("number 3 is not allowed")
```
================================================
FILE: docs/data/core-replace.md
================================================
---
name: Replace
slug: replace
sourceRef: slice.go#L684
category: core
subCategory: slice
playUrl: https://go.dev/play/p/XfPzmf9gql6
variantHelpers:
- core#slice#replace
similarHelpers:
- core#string#replace
- core#string#replaceall
- core#slice#fill
- core#slice#splice
- core#slice#slice
position: 0
signatures:
- "func Replace[T comparable, Slice ~[]T](collection Slice, old T, nEw T, n int) Slice"
---
Returns a copy of the slice with the first n non-overlapping instances of old replaced by new.
```go
in := []int{0, 1, 0, 1, 2, 3, 0}
lo.Replace(in, 0, 42, 2)
// []int{42, 1, 42, 1, 2, 3, 0}
```
================================================
FILE: docs/data/core-replaceall.md
================================================
---
name: ReplaceAll
slug: replaceall
sourceRef: slice.go#L700
category: core
subCategory: slice
playUrl: https://go.dev/play/p/a9xZFUHfYcV
variantHelpers:
- core#slice#replaceall
similarHelpers:
- core#slice#replace
- core#slice#map
- core#slice#filtermap
position: 0
signatures:
- "func ReplaceAll[T comparable, Slice ~[]T](collection Slice, old T, nEw T) Slice"
---
Returns a copy of the slice with all non-overlapping instances of old replaced by new.
```go
in := []int{0, 1, 0, 1, 2, 3, 0}
lo.ReplaceAll(in, 0, 42)
// []int{42, 1, 42, 1, 2, 3, 42}
```
================================================
FILE: docs/data/core-reverse.md
================================================
---
name: Reverse
slug: reverse
sourceRef: slice.go#L331
category: core
subCategory: slice
playUrl: https://go.dev/play/p/iv2e9jslfBM
variantHelpers:
- core#slice#reverse
similarHelpers:
- mutable#slice#reverse
- core#slice#drop
- core#slice#dropright
- core#slice#slice
- core#slice#flatten
position: 190
signatures:
- "func Reverse[T any, Slice ~[]T](collection Slice) Slice"
---
Reverses a slice in place. Deprecated: use `mutable.Reverse`.
================================================
FILE: docs/data/core-runelength.md
================================================
---
name: RuneLength
slug: runelength
sourceRef: string.go#L160
category: core
subCategory: string
playUrl: https://go.dev/play/p/BXT52mBk0zO
variantHelpers:
- core#string#runelength
similarHelpers:
- core#string#substring
- core#string#chunkstring
- core#string#words
- core#string#capitalize
- core#string#ellipsis
position: 30
signatures:
- "func RuneLength(str string) int"
---
Returns the number of runes (Unicode code points) in a string.
```go
lo.RuneLength("hellô")
// 5
```
================================================
FILE: docs/data/core-sample.md
================================================
---
name: Sample
slug: sample
sourceRef: find.go#L662
category: core
subCategory: find
playUrl: https://go.dev/play/p/vCcSJbh5s6l
variantHelpers:
- core#find#sample
similarHelpers:
- core#find#samples
- core#find#samplesby
- core#find#shuffle
position: 350
signatures:
- "func Sample[T any](collection []T) T"
---
Returns a random item from a collection.
```go
v := lo.Sample(
[]int{10, 20, 30},
)
```
================================================
FILE: docs/data/core-sampleby.md
================================================
---
name: SampleBy
slug: sampleby
sourceRef: find.go#L669
category: core
subCategory: find
playUrl: https://go.dev/play/p/HDmKmMgq0XN
variantHelpers:
- core#find#sampleby
similarHelpers:
- core#find#sample
- core#find#samples
- core#find#samplesby
- core#find#shuffle
position: 360
signatures:
- "func SampleBy[T any](collection []T, randomIntGenerator randomIntGenerator) T"
---
Returns a random item from a collection, using the provided random index generator.
```go
v := lo.SampleBy([]int{10, 20, 30}, func(n int) int {
return 0
})
// v == 10
```
================================================
FILE: docs/data/core-samples.md
================================================
---
name: Samples
slug: samples
sourceRef: find.go#L679
category: core
subCategory: find
playUrl: https://go.dev/play/p/QYRD8aufD0C
variantHelpers:
- core#find#samples
similarHelpers:
- core#find#sample
- core#find#samplesby
- core#find#shuffle
position: 370
signatures:
- "func Samples[T any, Slice ~[]T](collection Slice, count int) Slice"
---
Returns N random unique items from a collection.
```go
v := lo.Samples(
[]int{10, 20, 30},
2,
)
```
================================================
FILE: docs/data/core-samplesby.md
================================================
---
name: SamplesBy
slug: samplesby
sourceRef: find.go#L686
category: core
subCategory: find
playUrl: https://go.dev/play/p/Dy9bGDhD_Gw
variantHelpers:
- core#find#samplesby
similarHelpers:
- core#find#sample
- core#find#samples
- core#find#shuffle
position: 380
signatures:
- "func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerator randomIntGenerator) Slice"
---
Returns N random unique items from a collection, using the provided random index generator.
```go
v := lo.SamplesBy(
[]int{10, 20, 30},
2,
func(n int) int {
return 0
},
)
```
================================================
FILE: docs/data/core-shuffle.md
================================================
---
name: Shuffle
slug: shuffle
sourceRef: slice.go#L322
category: core
subCategory: slice
playUrl: https://go.dev/play/p/whgrQWwOy-j
variantHelpers:
- core#slice#shuffle
similarHelpers:
- mutable#slice#shuffle
- core#slice#sample
- core#slice#samples
- core#slice#sampleby
- core#slice#samplesby
position: 180
signatures:
- "func Shuffle[T any, Slice ~[]T](collection Slice) Slice"
---
Returns a slice of shuffled values (Fisher–Yates). Deprecated: use `mutable.Shuffle`.
================================================
FILE: docs/data/core-slice.md
================================================
---
name: Slice
slug: slice
sourceRef: slice.go#L658
category: core
subCategory: slice
playUrl: https://go.dev/play/p/8XWYhfMMA1h
variantHelpers:
- core#slice#slice
similarHelpers:
- core#slice#subset
- core#slice#drop
- core#slice#dropright
- core#slice#splice
- core#slice#replace
position: 0
signatures:
- "func Slice[T any, Slice ~[]T](collection Slice, start int, end int) Slice"
---
Returns a copy of a slice from `start` up to, but not including, `end`. Like `slice[start:end]`, but does not panic on overflow.
```go
in := []int{0, 1, 2, 3, 4}
lo.Slice(in, 2, 6)
// []int{2, 3, 4}
```
================================================
FILE: docs/data/core-slicetochannel.md
================================================
---
name: SliceToChannel
slug: slicetochannel
sourceRef: channel.go#L18
category: core
subCategory: channel
signatures:
- "func SliceToChannel[T any](bufferSize int, collection []T) <-chan T"
similarHelpers:
- core#channel#channeltoslice
- core#channel#buffer
- it#channel#seqtochannel
- it#channel#channeltoseq
position: 251
---
SliceToChannel converts a slice to a channel with specified buffer size.
```go
items := []int{1, 2, 3, 4, 5}
ch := lo.SliceToChannel(10, items)
for item := range ch {
fmt.Println(item)
}
// Prints 1, 2, 3, 4, 5
```
================================================
FILE: docs/data/core-slicetomap.md
================================================
---
name: SliceToMap
slug: slicetomap
sourceRef: slice.go#L405
category: core
subCategory: slice
playUrl: https://go.dev/play/p/WHa2CfMO3Lr
variantHelpers:
- core#slice#slicetomap
- core#slice#associate
- core#slice#associatei
similarHelpers:
- core#slice#filterslicetomap
- core#slice#keyby
- core#slice#groupby
- core#slice#groupbymap
position: 250
signatures:
- "func SliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V"
- "func Associate[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V"
- "func AssociateI[T any, K comparable, V any](collection []T, transform func(item T, index int) (K, V)) map[K]V"
---
Alias of Associate: transforms a slice into a map using a key/value transform function.
```go
type foo struct {
baz string
bar int
}
in := []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}
m := lo.SliceToMap(in, func(f *foo) (string, int) {
return f.baz, f.bar
})
// map[string]int{"apple": 1, "banana": 2}
```
================================================
FILE: docs/data/core-sliding.md
================================================
---
name: Sliding
slug: sliding
sourceRef: slice.go#L313
category: core
subCategory: slice
variantHelpers:
- core#slice#sliding
playUrl: https://go.dev/play/p/aIIU6gWxl2T
similarHelpers:
- core#slice#window
- core#slice#chunk
- core#slice#partitionby
- core#slice#flatten
- it#sequence#sliding
position: 150
signatures:
- "func Sliding[T any, Slice ~[]T](collection Slice, size, step int) []Slice"
---
Creates a slice of sliding windows of a given size with a given step. If step is equal to size, windows don't overlap (similar to Chunk). If step is less than size, windows overlap.
```go
// Overlapping windows (step < size)
lo.Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 1)
// [][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}
// Non-overlapping windows (step == size, like Chunk)
lo.Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 3)
// [][]int{{1, 2, 3}, {4, 5, 6}}
// Step > size (skipping elements)
lo.Sliding([]int{1, 2, 3, 4, 5, 6, 7, 8}, 2, 3)
// [][]int{{1, 2}, {4, 5}, {7, 8}}
```
================================================
FILE: docs/data/core-snakecase.md
================================================
---
name: SnakeCase
slug: snakecase
sourceRef: string.go#L200
category: core
subCategory: string
playUrl: https://go.dev/play/p/ziB0V89IeVH
variantHelpers:
- core#string#snakecase
similarHelpers:
- core#string#pascalcase
- core#string#camelcase
- core#string#kebabcase
- core#string#capitalize
- core#string#words
position: 70
signatures:
- "func SnakeCase(str string) string"
---
Converts a string to snake_case.
```go
lo.SnakeCase("HelloWorld")
// "hello_world"
```
================================================
FILE: docs/data/core-some.md
================================================
---
name: Some
slug: some
sourceRef: intersect.go#L54
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/Lj4ceFkeT9V
variantHelpers:
- core#intersect#some
similarHelpers:
- core#intersect#someby
- core#intersect#every
- core#intersect#everyby
- core#intersect#none
- core#intersect#noneby
- core#intersect#contains
- core#intersect#containsby
position: 40
signatures:
- "func Some[T comparable](collection []T, subset []T) bool"
---
Returns true if at least one element of a subset is contained in a collection. Returns false for an empty subset.
```go
ok := lo.Some([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
// true
```
================================================
FILE: docs/data/core-someby.md
================================================
---
name: SomeBy
slug: someby
sourceRef: intersect.go#L67
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/DXF-TORBudx
variantHelpers:
- core#intersect#someby
similarHelpers:
- core#intersect#some
- core#intersect#everyby
- core#intersect#every
- core#intersect#noneby
- core#intersect#none
- core#intersect#containsby
- core#intersect#contains
position: 50
signatures:
- "func SomeBy[T any](collection []T, predicate func(item T) bool) bool"
---
Returns true if the predicate returns true for any element in the collection. Returns false for an empty collection.
```go
ok := lo.SomeBy(
[]int{1, 2, 3, 4},
func(x int) bool {
return x < 3
},
)
// true
```
================================================
FILE: docs/data/core-splice.md
================================================
---
name: Splice
slug: splice
sourceRef: slice.go#L748
category: core
subCategory: slice
playUrl: https://go.dev/play/p/G5_GhkeSUBA
variantHelpers:
- core#slice#splice
similarHelpers:
- core#slice#slice
- core#slice#drop
- core#slice#dropright
- core#slice#insert
position: 0
signatures:
- "func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice"
---
Inserts multiple elements at the specified index, with support for negative indices and automatic bounds handling. Negative indices count from the end (-1 means before last element), and indices beyond the slice length append to the end.
```go
// Basic insertion at position 1
result := lo.Splice([]string{"a", "b"}, 1, "1", "2")
// result: []string{"a", "1", "2", "b"}
// Negative index: -1 means before the last element
result = lo.Splice([]string{"a", "b"}, -1, "1", "2")
// result: []string{"a", "1", "2", "b"}
// Index overflow: when index > len(slice), elements are appended
result = lo.Splice([]string{"a", "b"}, 42, "1", "2")
// result: []string{"a", "b", "1", "2"}
// Insert at beginning (index 0)
result = lo.Splice([]int{3, 4, 5}, 0, 1, 2)
// result: []int{1, 2, 3, 4, 5}
// Insert before last element with negative index
result = lo.Splice([]int{1, 2, 3}, -2, 99)
// result: []int{1, 99, 2, 3}
// No elements to insert returns original slice
result = lo.Splice([]string{"a", "b"}, 1)
// result: []string{"a", "b"}
```
================================================
FILE: docs/data/core-subset.md
================================================
---
name: Subset
slug: subset
sourceRef: slice.go#L635
category: core
subCategory: slice
playUrl: https://go.dev/play/p/tOQu1GhFcog
variantHelpers:
- core#slice#subset
similarHelpers:
- core#slice#slice
- core#slice#chunk
- core#slice#drop
- core#slice#dropright
position: 0
signatures:
- "func Subset[T any, Slice ~[]T](collection Slice, offset int, length uint) Slice"
---
Returns a copy of a slice from `offset` up to `length` elements. Like `slice[start:start+length]`, but does not panic on overflow.
```go
in := []int{0, 1, 2, 3, 4}
lo.Subset(in, 2, 3)
// []int{2, 3, 4}
```
================================================
FILE: docs/data/core-substring.md
================================================
---
name: Substring
slug: substring
sourceRef: string.go#L105
category: core
subCategory: string
playUrl: https://go.dev/play/p/emzCC9zBjHu
variantHelpers:
- core#string#substring
similarHelpers:
- core#string#chunkstring
- core#string#runelength
- core#string#ellipsis
- core#string#words
- core#slice#replace
- core#slice#replaceall
position: 10
signatures:
- "func Substring[T ~string](str T, offset int, length uint) T"
---
Returns a substring starting at the given offset with the specified length. Supports negative offsets; out-of-bounds are clamped. Operates on Unicode runes (characters) and is optimized for zero allocations.
```go
// Basic usage
result := lo.Substring("hello", 2, 3)
// result: "llo"
// Negative offset - counts from end
result = lo.Substring("hello", -4, 3)
// result: "ell"
// Length longer than string - clamped to available characters
result = lo.Substring("hello", 1, 10)
// result: "ello" (only 4 characters available from position 1)
// Zero length - returns empty string
result = lo.Substring("hello", 1, 0)
// result: ""
// Offset beyond string length - returns empty string
result = lo.Substring("hello", 10, 3)
// result: ""
// With Unicode strings (rune-aware)
result = lo.Substring("héllo", 1, 3)
// result: "él"
// Negative offset with negative values clamped
result = lo.Substring("hello", -10, 3)
// result: "hel" (offset clamped to 0)
```
================================================
FILE: docs/data/core-sum.md
================================================
---
name: Sum
slug: sum
sourceRef: math.go#L70
category: core
subCategory: math
playUrl: https://go.dev/play/p/upfeJVqs4Bt
variantHelpers:
- core#math#sum
similarHelpers:
- core#math#product
- core#math#mean
- core#math#sumby
- core#math#productby
- core#math#meanby
- core#find#min
- core#find#max
- core#find#minby
- core#find#maxby
- core#math#mode
position: 40
signatures:
- "func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T"
---
Sums the values in a collection. Returns 0 for an empty collection.
```go
lo.Sum([]int{1, 2, 3, 4, 5})
// 15
```
================================================
FILE: docs/data/core-sumby.md
================================================
---
name: SumBy
slug: sumby
sourceRef: math.go#L80
category: core
subCategory: math
playUrl: https://go.dev/play/p/Dz_a_7jN_ca
variantHelpers:
- core#math#sumby
similarHelpers:
- core#math#sum
- core#math#sumbyerr
- core#math#productby
- core#math#meanby
- core#math#product
- core#math#mean
position: 50
signatures:
- "func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R"
---
Sums the values computed by a predicate across a collection. Returns 0 for an empty collection.
```go
strings := []string{"foo", "bar"}
lo.SumBy(strings, func(item string) int {
return len(item)
})
// 6
```
```go
// See also: SumByErr for error handling
strings := []string{"foo", "bar", "baz"}
sum, err := lo.SumByErr(strings, func(item string) (int, error) {
if item == "bar" {
return 0, fmt.Errorf("invalid item: %s", item)
}
return len(item), nil
})
// sum: 3, err: invalid item: bar
```
================================================
FILE: docs/data/core-sumbyerr.md
================================================
---
name: SumByErr
slug: sumbyerr
sourceRef: math.go#L102
category: core
subCategory: math
variantHelpers:
- core#math#sumbyerr
similarHelpers:
- core#math#sumby
- core#math#sum
- core#math#productby
- core#math#meanby
position: 50
signatures:
- "func SumByErr[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) (R, error)) (R, error)"
---
Sums the values computed by a predicate across a collection, stopping early and returning an error if the predicate returns one. Returns 0 for an empty collection.
```go
strings := []string{"foo", "bar", "baz"}
sum, err := lo.SumByErr(strings, func(item string) (int, error) {
if item == "bar" {
return 0, fmt.Errorf("invalid item: %s", item)
}
return len(item), nil
})
// sum: 3, err: invalid item: bar
```
```go
strings := []string{"foo", "bar"}
sum, err := lo.SumByErr(strings, func(item string) (int, error) {
return len(item), nil
})
// sum: 6, err: nil
```
```go
strings := []string{}
sum, err := lo.SumByErr(strings, func(item string) (int, error) {
return len(item), nil
})
// sum: 0, err: nil
```
================================================
FILE: docs/data/core-switch.md
================================================
---
name: Switch
slug: switch
sourceRef: condition.go#L101
category: core
subCategory: condition
playUrl: https://go.dev/play/p/TGbKUMAeRUd
variantHelpers:
- core#condition#switch
- core#condition#case
- core#condition#casef
- core#condition#default
- core#condition#defaultf
similarHelpers:
- core#condition#if
- core#condition#ternary
- core#condition#validate
position: 20
signatures:
- "func Switch[T comparable, R any](predicate T) *switchCase[T, R]"
- "func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R]"
- "func (s *switchCase[T, R]) CaseF(val T, callback func() R) *switchCase[T, R]"
- "func (s *switchCase[T, R]) Default(result R) R"
- "func (s *switchCase[T, R]) DefaultF(callback func() R) R"
---
Starts a functional switch/case/default chain using a predicate value.
```go
result := lo.Switch(2).Case(1, "1").Case(2, "2").Default("3")
// "2"
```
### Case
Adds a Case branch to a Switch chain returning a constant result.
```go
result := lo.Switch(1).Case(1, "1").Default("?")
// "1"
```
### CaseF
Adds a Case branch that lazily computes its result.
```go
result := lo.Switch(2).CaseF(2, func() string {
return "2"
}).Default("?")
// "2"
```
### Default
Completes the Switch chain by providing a default result when no Case matched.
```go
result := lo.Switch(42).Default("none")
// "none"
```
### DefaultF
Function form of Default. Lazily computes the default result when no Case matched.
```go
result := lo.Switch(0).Case(1, "1").DefaultF(func() string {
return "3"
})
// "3"
```
================================================
FILE: docs/data/core-synchronize.md
================================================
---
name: Synchronize
slug: synchronize
sourceRef: concurrency.go#L21
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/X3cqROSpQmu
variantHelpers:
- core#concurrency#synchronize
similarHelpers:
- core#retry#newtransaction
- core#concurrency#asyncx
- core#concurrency#waitfor
position: 0
signatures:
- "func Synchronize(opt ...sync.Locker) *synchronize"
---
Wraps a callback in a mutex to ensure sequential execution. Optionally accepts a custom locker.
```go
s := lo.Synchronize()
for i := 0; i < 10; i++ {
go s.Do(func() { println("sequential") })
}
```
================================================
FILE: docs/data/core-take.md
================================================
---
name: Take
slug: take
sourceRef: slice.go#L587
category: core
subCategory: slice
variantHelpers:
- core#slice#take
similarHelpers:
- core#slice#takewhile
- core#slice#drop
- core#slice#dropright
- core#slice#dropwhile
- core#slice#first
- core#slice#filtermap
- core#slice#takefilter
- it#sequence#take
position: 175
signatures:
- "func Take[T any, Slice ~[]T](collection Slice, n int) Slice"
---
Takes the first n elements from a slice.
```go
lo.Take([]int{0, 1, 2, 3, 4, 5}, 3)
// []int{0, 1, 2}
lo.Take([]int{0, 1, 2}, 5)
// []int{0, 1, 2}
```
================================================
FILE: docs/data/core-takefilter.md
================================================
---
name: TakeFilter
slug: takefilter
sourceRef: slice.go#L643
category: core
subCategory: slice
variantHelpers:
- core#slice#takefilter
playUrl: https://go.dev/play/p/l99lvN4gReF
similarHelpers:
- core#slice#filter
- core#slice#take
- core#slice#takewhile
- core#slice#filtermap
- core#slice#filterreject
- it#sequence#takefilter
position: 125
signatures:
- "func TakeFilter[T any, Slice ~[]T](collection Slice, n int, predicate func(item T, index int) bool) Slice"
---
Filters elements and takes the first n elements that match the predicate. Equivalent to calling Take(Filter(...)), but more efficient as it stops after finding n matches.
```go
lo.TakeFilter([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3, func(val int, index int) bool {
return val%2 == 0
})
// []int{2, 4, 6}
lo.TakeFilter([]string{"a", "aa", "aaa", "aaaa"}, 2, func(val string, index int) bool {
return len(val) > 1
})
// []string{"aa", "aaa"}
```
================================================
FILE: docs/data/core-takewhile.md
================================================
---
name: TakeWhile
slug: takewhile
sourceRef: slice.go#L605
category: core
subCategory: slice
variantHelpers:
- core#slice#takewhile
playUrl: https://go.dev/play/p/NJkLGvyRWm4
similarHelpers:
- core#slice#take
- core#slice#dropwhile
- core#slice#droprightwhile
- core#slice#filter
- core#slice#takefilter
- core#slice#first
- it#sequence#takewhile
position: 195
signatures:
- "func TakeWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice"
---
Takes elements from the beginning while the predicate returns true.
```go
lo.TakeWhile([]int{0, 1, 2, 3, 4, 5}, func(val int) bool {
return val < 3
})
// []int{0, 1, 2}
lo.TakeWhile([]string{"a", "aa", "aaa", "aa"}, func(val string) bool {
return len(val) <= 2
})
// []string{"a", "aa"}
```
================================================
FILE: docs/data/core-ternary.md
================================================
---
name: Ternary
slug: ternary
sourceRef: condition.go#L6
category: core
subCategory: condition
playUrl: https://go.dev/play/p/t-D7WBL44h2
variantHelpers:
- core#condition#ternary
- core#condition#ternaryf
similarHelpers:
- core#condition#if
- core#condition#switch
- core#condition#validate
- core#function#partialx
position: 0
signatures:
- "func Ternary[T any](condition bool, ifOutput T, elseOutput T) T"
- "func TernaryF[T any](condition bool, ifFunc func() T, elseFunc func() T) T"
---
A single-line if/else that returns `ifOutput` when condition is true, otherwise `elseOutput`.
```go
result := lo.Ternary(true, "a", "b")
// result: "a"
result = lo.Ternary(false, "a", "b")
// result: "b"
// With numbers
age := 25
status := lo.Ternary(age >= 18, "adult", "minor")
// status: "adult"
// With complex expressions
x, y := 10, 20
max := lo.Ternary(x > y, x, y)
// max: 20
```
### TernaryF - Function Form
Function form of Ternary. Lazily evaluates the selected branch to avoid unnecessary work or nil dereferences.
```go
var s *string
str := lo.TernaryF(s == nil, func() string {
return uuid.New().String()
}, func() string {
return *s
})
// str: newly generated UUID (since s is nil)
// Example with expensive operations
str = "hello"
result := lo.TernaryF(s != nil, func() string {
return *s // safe dereference only when s is not nil
}, func() string {
return "default value"
})
// result: "hello"
// Performance benefit - only executes needed branch
result = lo.TernaryF(false, func() string {
time.Sleep(1 * time.Second) // this won't execute
return "slow operation"
}, func() string {
return "fast operation" // only this executes
})
// result: "fast operation" (returns immediately)
```
================================================
FILE: docs/data/core-times.md
================================================
---
name: Times
slug: times
sourceRef: slice.go#L127
category: core
subCategory: slice
playUrl: https://go.dev/play/p/vgQj3Glr6lT
variantHelpers:
- core#slice#times
similarHelpers:
- core#slice#foreach
- core#slice#repeat
- core#slice#repeatby
- parallel#slice#times
position: 90
signatures:
- "func Times[T any](count int, iteratee func(index int) T) []T"
---
Invokes the predicate n times, returning a slice of results. The predicate receives the index on each call.
```go
lo.Times(3, func(i int) string {
return strconv.Itoa(i)
})
// []string{"0", "1", "2"}
```
================================================
FILE: docs/data/core-toanyslice.md
================================================
---
name: ToAnySlice
slug: toanyslice
sourceRef: type_manipulation.go#L107
category: core
subCategory: type
signatures:
- "func ToAnySlice[T any](collection []T) []any"
variantHelpers:
- core#type#toanyslice
similarHelpers:
- core#type#fromanyslice
- core#type#tosliceptr
- core#type#fromsliceptr
position: 126
---
Converts a typed slice to a slice of empty interface values (any). This is useful when working with heterogeneous data.
```go
ints := []int{1, 2, 3}
result := lo.ToAnySlice(ints)
// []any{1, 2, 3}
strings := []string{"a", "b", "c"}
result = lo.ToAnySlice(strings)
// []any{"a", "b", "c"}
custom := []struct{ Name string }{{Name: "Alice"}, {Name: "Bob"}}
result = lo.ToAnySlice(custom)
// []any{struct{ Name string }{Name: "Alice"}, struct{ Name string }{Name: "Bob"}}
```
================================================
FILE: docs/data/core-topairs.md
================================================
---
name: ToPairs
slug: topairs
sourceRef: map.go#L195
category: core
subCategory: map
playUrl: https://go.dev/play/p/3Dhgx46gawJ
variantHelpers:
- core#map#topairs
similarHelpers:
- core#map#entries
- core#map#fromentries
- core#map#frompairs
position: 130
signatures:
- "func ToPairs[K comparable, V any](in map[K]V) []Entry[K, V]"
---
Transforms a map into a slice of key/value pairs. Alias of `Entries`.
```go
pairs := lo.ToPairs(map[string]int{"foo": 1, "bar": 2})
// []lo.Entry[string, int]{...}
```
================================================
FILE: docs/data/core-toptr.md
================================================
---
name: ToPtr
slug: toptr
sourceRef: type_manipulation.go#L28
category: core
subCategory: type
signatures:
- "func ToPtr[T any](x T) *T"
variantHelpers:
- core#type#toptr
similarHelpers:
- core#type#fromptr
- core#type#fromptror
- core#type#emptyabletoptr
- core#type#tosliceptr
- core#type#fromsliceptr
position: 90
---
Returns a pointer to the provided value.
```go
ptr := lo.ToPtr(42)
// *int pointing to 42
ptr = lo.ToPtr("hello")
// *string pointing to "hello"
ptr = lo.ToPtr([]int{1, 2, 3})
// *[]int pointing to []int{1, 2, 3}
```
================================================
FILE: docs/data/core-tosliceptr.md
================================================
---
name: ToSlicePtr
slug: tosliceptr
sourceRef: type_manipulation.go#L73
category: core
subCategory: type
signatures:
- "func ToSlicePtr[T any](collection []T) []*T"
variantHelpers:
- core#type#tosliceptr
similarHelpers:
- core#type#toptr
- core#type#fromptr
- core#type#fromptror
- core#type#emptyabletoptr
- core#type#fromsliceptr
position: 110
---
Converts a slice of values to a slice of pointers to those values.
```go
slice := []int{1, 2, 3}
ptrs := lo.ToSlicePtr(slice)
// []*int{&1, &2, &3}
slice = []string{"a", "b", "c"}
ptrs = lo.ToSlicePtr(slice)
// []*string{&"a", &"b", &"c"}
slice = []int{}
ptrs = lo.ToSlicePtr(slice)
// []*int{}
```
================================================
FILE: docs/data/core-trim.md
================================================
---
name: Trim
slug: trim
sourceRef: slice.go#L842
category: core
subCategory: slice
playUrl: https://go.dev/play/p/LZLfLj5C8Lg
variantHelpers:
- core#slice#trim
similarHelpers:
- core#slice#trimleft
- core#slice#trimright
- core#slice#trimprefix
- core#slice#trimsuffix
position: 0
signatures:
- "func Trim[T comparable, Slice ~[]T](collection Slice, cutset Slice) Slice"
---
Removes all leading and trailing elements in the cutset from the collection.
```go
result := lo.Trim([]int{0, 1, 2, 0, 3, 0}, []int{1, 0})
// []int{2, 0, 3}
result = lo.Trim([]string{"hello", "world", " "}, []string{" ", ""})
// []string{"hello", "world"}
```
================================================
FILE: docs/data/core-trimleft.md
================================================
---
name: TrimLeft
slug: trimleft
sourceRef: slice.go#L847
category: core
subCategory: slice
playUrl: https://go.dev/play/p/fJK-AhROy9w
variantHelpers:
- core#slice#trimleft
similarHelpers:
- core#slice#trim
- core#slice#trimright
- core#slice#trimprefix
- core#slice#trimsuffix
position: 0
signatures:
- "func TrimLeft[T comparable, Slice ~[]T](collection Slice, cutset Slice) Slice"
---
Removes all leading elements found in the cutset from the collection.
```go
result := lo.TrimLeft([]int{0, 1, 2, 0, 3, 0}, []int{1, 0})
// []int{2, 0, 3, 0}
result = lo.TrimLeft([]string{"hello", "world", " "}, []string{" ", ""})
// []string{"hello", "world", " "}
```
================================================
FILE: docs/data/core-trimprefix.md
================================================
---
name: TrimPrefix
slug: trimprefix
sourceRef: slice.go#L858
category: core
subCategory: slice
playUrl: https://go.dev/play/p/8O2RoWYzi8J
variantHelpers:
- core#slice#trimprefix
similarHelpers:
- core#slice#trim
- core#slice#trimleft
- core#slice#trimright
- core#slice#trimsuffix
position: 0
signatures:
- "func TrimPrefix[T comparable, Slice ~[]T](collection Slice, prefix Slice) Slice"
---
Removes all leading occurrences of the given prefix from the collection.
```go
result := lo.TrimPrefix([]int{1, 2, 1, 2, 3, 1, 2, 4}, []int{1, 2})
// []int{3, 1, 2, 4}
result = lo.TrimPrefix([]string{"hello", "world", "hello", "test"}, []string{"hello"})
// []string{"world", "hello", "test"}
```
================================================
FILE: docs/data/core-trimright.md
================================================
---
name: TrimRight
slug: trimright
sourceRef: slice.go#L873
category: core
subCategory: slice
playUrl: https://go.dev/play/p/9V_N8sRyyiZ
variantHelpers:
- core#slice#trimright
similarHelpers:
- core#slice#trim
- core#slice#trimleft
- core#slice#trimprefix
- core#slice#trimsuffix
position: 0
signatures:
- "func TrimRight[T comparable, Slice ~[]T](collection Slice, cutset Slice) Slice"
---
Removes all trailing elements found in the cutset from the collection.
```go
result := lo.TrimRight([]int{0, 1, 2, 0, 3, 0}, []int{0, 3})
// []int{0, 1, 2}
result = lo.TrimRight([]string{"hello", "world", " "}, []string{" ", ""})
// []string{"hello", "world", ""}
```
================================================
FILE: docs/data/core-trimsuffix.md
================================================
---
name: TrimSuffix
slug: trimsuffix
sourceRef: slice.go#L884
category: core
subCategory: slice
playUrl: https://go.dev/play/p/IjEUrV0iofq
variantHelpers:
- core#slice#trimsuffix
similarHelpers:
- core#slice#trim
- core#slice#trimleft
- core#slice#trimright
- core#slice#trimprefix
position: 0
signatures:
- "func TrimSuffix[T comparable, Slice ~[]T](collection Slice, suffix Slice) Slice"
---
Removes all trailing occurrences of the given suffix from the collection.
```go
result := lo.TrimSuffix([]int{1, 2, 3, 1, 2, 4, 2, 4, 2, 4}, []int{2, 4})
// []int{1, 2, 3, 1}
result = lo.TrimSuffix([]string{"hello", "world", "hello", "test"}, []string{"test"})
// []string{"hello", "world", "hello"}
```
================================================
FILE: docs/data/core-trycatch.md
================================================
---
name: TryCatch
slug: trycatch
sourceRef: errors.go#L335
category: core
subCategory: error-handling
playUrl: https://go.dev/play/p/PnOON-EqBiU
variantHelpers:
- core#error-handling#trycatch
similarHelpers:
- core#error-handling#validate
- core#error-handling#mustx
- core#error-handling#tryx
- core#error-handling#tryorx
- core#error-handling#trywitherrorvalue
- core#error-handling#trycatchwitherrorvalue
- core#error-handling#errorsas
- core#error-handling#assert
position: 50
signatures:
- "func TryCatch(callback func() error, catch func())"
---
Calls the catch function when the callback errors or panics.
```go
caught := false
lo.TryCatch(func() error { panic("error") }, func() { caught = true })
// caught == true
```
================================================
FILE: docs/data/core-trycatchwitherrorvalue.md
================================================
---
name: TryCatchWithErrorValue
slug: trycatchwitherrorvalue
sourceRef: errors.go#L343
category: core
subCategory: error-handling
playUrl: https://go.dev/play/p/8Pc9gwX_GZO
variantHelpers:
- core#error-handling#trycatchwitherrorvalue
similarHelpers:
- core#error-handling#validate
- core#error-handling#mustx
- core#error-handling#tryx
- core#error-handling#tryorx
- core#error-handling#trywitherrorvalue
- core#error-handling#trycatch
- core#error-handling#errorsas
- core#error-handling#assert
position: 60
signatures:
- "func TryCatchWithErrorValue(callback func() error, catch func(any))"
---
Calls the catch function with the error value when the callback errors or panics.
```go
lo.TryCatchWithErrorValue(
func() error {
panic("error")
},
func(val any) {
fmt.Println(val)
},
)
```
================================================
FILE: docs/data/core-tryorx.md
================================================
---
name: TryOrX
slug: tryorx
sourceRef: errors.go#L197
category: core
subCategory: error-handling
signatures:
- "func TryOr[A any](callback func() (A, error), fallbackA A) (A, bool)"
- "func TryOr1[A any](callback func() (A, error), fallbackA A) (A, bool)"
- "func TryOr2[A, B any](callback func() (A, B, error), fallbackA A, fallbackB B) (A, B, bool)"
- "func TryOr3[A, B, C any](callback func() (A, B, C, error), fallbackA A, fallbackB B, fallbackC C) (A, B, C, bool)"
- "func TryOr4[A, B, C, D any](callback func() (A, B, C, D, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D) (A, B, C, D, bool)"
- "func TryOr5[A, B, C, D, E any](callback func() (A, B, C, D, E, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E) (A, B, C, D, E, bool)"
- "func TryOr6[A, B, C, D, E, F any](callback func() (A, B, C, D, E, F, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E, fallbackF F) (A, B, C, D, E, F, bool)"
playUrl: https://go.dev/play/p/B4F7Wg2Zh9X
variantHelpers:
- core#error-handling#tryor
- core#error-handling#tryorx
similarHelpers:
- core#error-handling#validate
- core#error-handling#mustx
- core#error-handling#tryx
- core#error-handling#trycatch
- core#error-handling#trywitherrorvalue
- core#error-handling#errorsas
- core#error-handling#assert
position: 30
---
Like Try, but returns provided fallback values in case of error; also returns a success flag. Variants cover callbacks returning 1 to 6 values.
```go
value, ok := lo.TryOr(func() (int, error) {
return 0, fmt.Errorf("boom")
}, 123)
// value == 123, ok == false
value, ok = lo.TryOr(func() (int, error) {
return 42, nil
}, 0)
// value == 42, ok == true
```
================================================
FILE: docs/data/core-trywitherrorvalue.md
================================================
---
name: TryWithErrorValue
slug: trywitherrorvalue
sourceRef: errors.go#L314
category: core
subCategory: error-handling
playUrl: https://go.dev/play/p/Kc7afQIT2Fs
variantHelpers:
- core#error-handling#trywitherrorvalue
similarHelpers:
- core#error-handling#validate
- core#error-handling#mustx
- core#error-handling#tryx
- core#error-handling#tryorx
- core#error-handling#trycatch
- core#error-handling#trycatchwitherrorvalue
- core#error-handling#errorsas
- core#error-handling#assert
position: 40
signatures:
- "func TryWithErrorValue(callback func() error) (errorValue any, ok bool)"
---
Runs a function and returns the error value (panic or error) along with a boolean indicating success.
```go
err, ok := lo.TryWithErrorValue(func() error { panic("error") })
// err == "error", ok == false
```
================================================
FILE: docs/data/core-tryx.md
================================================
---
name: TryX
slug: tryx
sourceRef: errors.go#L118
category: core
subCategory: error-handling
signatures:
- "func Try(callback func() error) bool"
- "func Try0(callback func()) bool"
- "func Try1(callback func() error) bool"
- "func Try2[T any](callback func() (T, error)) bool"
- "func Try3[T, R any](callback func() (T, R, error)) bool"
- "func Try4[T, R, S any](callback func() (T, R, S, error)) bool"
- "func Try5[T, R, S, Q any](callback func() (T, R, S, Q, error)) bool"
- "func Try6[T, R, S, Q, U any](callback func() (T, R, S, Q, U, error)) bool"
playUrl: https://go.dev/play/p/mTyyWUvn9u4
variantHelpers:
- core#error-handling#try
- core#error-handling#tryx
similarHelpers:
- core#error-handling#validate
- core#error-handling#mustx
- core#error-handling#tryorx
- core#error-handling#trycatch
- core#error-handling#trywitherrorvalue
- core#error-handling#errorsas
- core#error-handling#assert
position: 20
---
Calls a function and returns false in case of error or panic. Variants cover callbacks returning 0 to 6 values.
```go
ok := lo.Try(func() error {
// return an error to mark failure
return fmt.Errorf("boom")
})
// ok == false
ok = lo.Try0(func() {
// panics are caught and return false
panic("boom")
})
// ok == false
ok = lo.Try2(func() (int, error) {
return 42, nil
})
// ok == true
```
================================================
FILE: docs/data/core-tuplex.md
================================================
---
name: TupleX
slug: tuplex
sourceRef: tuples.go#L5
category: core
subCategory: tuple
signatures:
- "func T2[A, B any](a A, b B) lo.Tuple2[A, B]"
- "func T3[A, B, C any](a A, b B, c C) lo.Tuple3[A, B, C]"
- "func T4[A, B, C, D any](a A, b B, c C, d D) lo.Tuple4[A, B, C, D]"
- "func T5[A, B, C, D, E any](a A, b B, c C, d D, e E) lo.Tuple5[A, B, C, D, E]"
- "func T6[A, B, C, D, E, F any](a A, b B, c C, d D, e E, f F) lo.Tuple6[A, B, C, D, E, F]"
- "func T7[A, B, C, D, E, F, G any](a A, b B, c C, d D, e E, f F, g G) lo.Tuple7[A, B, C, D, E, F, G]"
- "func T8[A, B, C, D, E, F, G, H any](a A, b B, c C, d D, e E, f F, g G, h H) lo.Tuple8[A, B, C, D, E, F, G, H]"
- "func T9[A, B, C, D, E, F, G, H, I any](a A, b B, c C, d D, e E, f F, g G, h H, i I) lo.Tuple9[A, B, C, D, E, F, G, H, I]"
playUrl: https://go.dev/play/p/IllL3ZO4BQm
variantHelpers:
- core#tuple#tx
- core#tuple#tuplex
similarHelpers:
- core#tuple#unpackx
- core#tuple#zipx
- core#tuple#zipbyx
- core#tuple#unzipx
- core#tuple#unzipbyx
position: 0
---
Constructors for tuple values from 2 up to 9 elements.
Variants: `T2..T9`
```go
t := lo.T3(1, "a", true)
// lo.Tuple3[int, string, bool]{A:1, B:"a", C:true}
```
================================================
FILE: docs/data/core-union.md
================================================
---
name: Union
slug: union
sourceRef: intersect.go#L157
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/-hsqZNTH0ej
variantHelpers:
- core#intersect#union
similarHelpers:
- core#intersect#intersect
- core#intersect#difference
- core#intersect#without
- core#slice#uniq
- core#slice#uniqby
position: 100
signatures:
- "func Union[T comparable, Slice ~[]T](lists ...Slice) Slice"
---
Returns all distinct elements from given collections while preserving relative order.
```go
lo.Union([]int{0, 1, 2, 3, 4, 5}, []int{0, 2}, []int{0, 10})
// []int{0, 1, 2, 3, 4, 5, 10}
```
================================================
FILE: docs/data/core-uniq.md
================================================
---
name: Uniq
slug: uniq
sourceRef: slice.go#L140
category: core
subCategory: slice
playUrl: https://go.dev/play/p/DTzbeXZ6iEN
variantHelpers:
- core#slice#uniq
similarHelpers:
- core#slice#uniqby
- core#slice#uniqmap
- core#slice#uniqkeys
- core#slice#uniqvalues
position: 100
signatures:
- "func Uniq[T comparable, Slice ~[]T](collection Slice) Slice"
---
Returns a duplicate-free version of a slice, keeping only the first occurrence of each value. Order is preserved.
```go
lo.Uniq([]int{1, 2, 2, 1})
// []int{1, 2}
```
================================================
FILE: docs/data/core-uniqby.md
================================================
---
name: UniqBy
slug: uniqby
sourceRef: slice.go#L160
category: core
subCategory: slice
playUrl: https://go.dev/play/p/g42Z3QSb53u
variantHelpers:
- core#slice#uniqby
similarHelpers:
- core#slice#uniqbyerr
- core#slice#uniq
- core#slice#uniqmap
- core#slice#partitionby
position: 110
signatures:
- "func UniqBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice"
---
Returns a duplicate-free version of a slice based on a computed key. Keeps only the first element for each unique key.
```go
lo.UniqBy(
[]int{0, 1, 2, 3, 4, 5},
func(i int) int {
return i % 3
},
)
// []int{0, 1, 2}
```
================================================
FILE: docs/data/core-uniqbyerr.md
================================================
---
name: UniqByErr
slug: uniqbyerr
sourceRef: slice.go#L240
category: core
subCategory: slice
signatures:
- "func UniqByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error)"
variantHelpers:
- core#slice#uniqbyerr
similarHelpers:
- core#slice#uniqby
- core#slice#uniq
- core#slice#uniqmap
- core#slice#partitionby
position: 111
---
Returns a duplicate-free version of a slice based on a computed key using an iteratee that can return an error. Stops iteration immediately when an error is encountered. Keeps only the first element for each unique key.
```go
// Error case - stops on first error
result, err := lo.UniqByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
if i == 3 {
return 0, fmt.Errorf("number 3 is not allowed")
}
return i % 3, nil
})
// []int(nil), error("number 3 is not allowed")
```
```go
// Success case
result, err := lo.UniqByErr([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, error) {
return i % 3, nil
})
// []int{0, 1, 2}, nil
```
================================================
FILE: docs/data/core-uniqkeys.md
================================================
---
name: UniqKeys
slug: uniqkeys
sourceRef: map.go#L23
category: core
subCategory: map
playUrl: https://go.dev/play/p/TPKAb6ILdHk
variantHelpers:
- core#map#uniqkeys
similarHelpers:
- core#map#keys
- core#map#values
- core#map#uniqvalues
position: 10
signatures:
- "func UniqKeys[K comparable, V any](in ...map[K]V) []K"
---
Creates a slice of unique map keys across one or more maps.
```go
keys := lo.UniqKeys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 3})
// []string{"foo", "bar"}
```
================================================
FILE: docs/data/core-uniqmap.md
================================================
---
name: UniqMap
slug: uniqmap
sourceRef: slice.go#L38
category: core
subCategory: slice
playUrl: https://go.dev/play/p/fygzLBhvUdB
variantHelpers:
- core#slice#uniqmap
similarHelpers:
- core#slice#uniq
- core#slice#uniqby
- core#slice#map
- core#slice#filtermap
position: 20
signatures:
- "func UniqMap[T any, R comparable](collection []T, transform func(item T, index int) R) []R"
---
Manipulates a slice and transforms it to a slice of another type with unique values.
```go
type User struct {
Name string
Age int
}
users := []User{{Name: "Alex", Age: 10}, {Name: "Alex", Age: 12}, {Name: "Bob", Age: 11}, {Name: "Alice", Age: 20}}
names := lo.UniqMap(users, func(u User, index int) string {
return u.Name
})
// []string{"Alex", "Bob", "Alice"}
```
================================================
FILE: docs/data/core-uniqvalues.md
================================================
---
name: UniqValues
slug: uniqvalues
sourceRef: map.go#L72
category: core
subCategory: map
playUrl: https://go.dev/play/p/nf6bXMh7rM3
variantHelpers:
- core#map#uniqvalues
similarHelpers:
- core#map#values
- core#map#uniqkeys
- core#slice#uniq
position: 40
signatures:
- "func UniqValues[K comparable, V comparable](in ...map[K]V) []V"
---
Creates a slice of unique map values across one or more maps.
```go
values := lo.UniqValues(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 2})
// []int{1, 2}
```
================================================
FILE: docs/data/core-unpackx.md
================================================
---
name: UnpackX
slug: unpackx
sourceRef: tuples.go#L53
category: core
subCategory: tuple
signatures:
- "func Unpack2[A, B any](tuple Tuple2[A, B]) (A, B)"
playUrl: https://go.dev/play/p/xVP_k0kJ96W
variantHelpers:
- core#tuple#unpackx
similarHelpers:
- core#tuple#tuplex
- core#tuple#zipx
- core#tuple#zipbyx
- core#tuple#unzipx
- core#tuple#unzipbyx
position: 10
---
Extracts values from tuples. Variants support tuple sizes from 2 to 9.
Variants: `Unpack2..Unpack9`
```go
a, b, c := lo.Unpack3(lo.T3(1, "a", true))
```
================================================
FILE: docs/data/core-unzipbyerrx.md
================================================
---
name: UnzipByErrX
slug: unzipbyerrx
sourceRef: tuples.go#L1024
category: core
subCategory: tuple
signatures:
- "func UnzipByErr2[In any, A any, B any](items []In, predicate func(In) (a A, b B, err error)) ([]A, []B, error)"
- "func UnzipByErr3[In any, A any, B any, C any](items []In, predicate func(In) (a A, b B, c C, err error)) ([]A, []B, []C, error)"
- "func UnzipByErr4[In any, A any, B any, C any, D any](items []In, predicate func(In) (a A, b B, c C, d D, err error)) ([]A, []B, []C, []D, error)"
- "func UnzipByErr5[In any, A any, B any, C any, D any, E any](items []In, predicate func(In) (a A, b B, c C, d D, e E, err error)) ([]A, []B, []C, []D, []E, error)"
- "func UnzipByErr6[In any, A any, B any, C any, D any, E any, F any](items []In, predicate func(In) (a A, b B, c C, d D, e E, f F, err error)) ([]A, []B, []C, []D, []E, []F, error)"
- "func UnzipByErr7[In any, A any, B any, C any, D any, E any, F any, G any](items []In, predicate func(In) (a A, b B, c C, d D, e E, f F, g G, err error)) ([]A, []B, []C, []D, []E, []F, []G, error)"
- "func UnzipByErr8[In any, A any, B any, C any, D any, E any, F any, G any, H any](items []In, predicate func(In) (a A, b B, c C, d D, e E, f F, g G, h H, err error)) ([]A, []B, []C, []D, []E, []F, []G, []H, error)"
- "func UnzipByErr9[In any, A any, B any, C any, D any, E any, F any, G any, H any, I any](items []In, predicate func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I, err error)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I, error)"
playUrl: https://go.dev/play/p/G2pyXQa1SUD
variantHelpers:
- core#tuple#unzipbyerrx
similarHelpers:
- core#tuple#unzipbyx
- core#tuple#tuplex
- core#slice#map
position: 41
---
Transforms each input element into a tuple and splits results into parallel slices. The iteratee can return an error to stop iteration immediately. Variants support arities from 2 to 9.
Variants: `UnzipByErr2..UnzipByErr9`
```go
a, b, err := lo.UnzipByErr2([]string{"hello", "error", "world"}, func(str string) (string, int, error) {
if str == "error" {
return "", 0, fmt.Errorf("error string not allowed")
}
return str, len(str), nil
})
// []string{}
// []int{}
// error string not allowed
```
On error, all result slices are `nil` and iteration stops immediately.
================================================
FILE: docs/data/core-unzipbyx.md
================================================
---
name: UnzipByX
slug: unzipbyx
sourceRef: tuples.go#L698
category: core
subCategory: tuple
signatures:
- "func UnzipBy2[In any, A any, B any](items []In, predicate func(In) (a A, b B)) ([]A, []B)"
- "func UnzipBy3[In any, A any, B any, C any](items []In, predicate func(In) (a A, b B, c C)) ([]A, []B, []C)"
- "func UnzipBy4[In any, A any, B any, C any, D any](items []In, predicate func(In) (a A, b B, c C, d D)) ([]A, []B, []C, []D)"
- "func UnzipBy5[In any, A any, B any, C any, D any, E any](items []In, predicate func(In) (a A, b B, c C, d D, e E)) ([]A, []B, []C, []D, []E)"
- "func UnzipBy6[In any, A any, B any, C any, D any, E any, F any](items []In, predicate func(In) (a A, b B, c C, d D, e E, f F)) ([]A, []B, []C, []D, []E, []F)"
- "func UnzipBy7[In any, A any, B any, C any, D any, E any, F any, G any](items []In, predicate func(In) (a A, b B, c C, d D, e E, f F, g G)) ([]A, []B, []C, []D, []E, []F, []G)"
- "func UnzipBy8[In any, A any, B any, C any, D any, E any, F any, G any, H any](items []In, predicate func(In) (a A, b B, c C, d D, e E, f F, g G, h H)) ([]A, []B, []C, []D, []E, []F, []G, []H)"
- "func UnzipBy9[In any, A any, B any, C any, D any, E any, F any, G any, H any, I any](items []In, predicate func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I)"
playUrl: https://go.dev/play/p/tN8yqaRZz0r
variantHelpers:
- core#tuple#unzipbyx
similarHelpers:
- core#tuple#tuplex
- core#tuple#unpackx
- core#tuple#zipx
- core#tuple#zipbyx
- core#tuple#unzipx
- core#tuple#unzipbyerrx
- core#slice#map
- core#slice#filtermap
position: 40
---
Transforms each input element into a tuple and splits results into parallel slices. Variants support arities from 2 to 9.
Variants: `UnzipBy2..UnzipBy9`
```go
type User struct{ ID int; Name string }
ids, names := lo.UnzipBy2([]User{{1,"a"},{2,"b"}}, func(u User) (int, string) {
return u.ID, u.Name
})
```
================================================
FILE: docs/data/core-unzipx.md
================================================
---
name: UnzipX
slug: unzipx
sourceRef: tuples.go#L514
category: core
subCategory: tuple
signatures:
- "func Unzip2[A, B any](tuples []Tuple2[A, B]) ([]A, []B)"
- "func Unzip3[A, B, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C)"
- "func Unzip4[A, B, C, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, []C, []D)"
- "func Unzip5[A, B, C, D, E any](tuples []Tuple5[A, B, C, D, E]) ([]A, []B, []C, []D, []E)"
- "func Unzip6[A, B, C, D, E, F any](tuples []Tuple6[A, B, C, D, E, F]) ([]A, []B, []C, []D, []E, []F)"
- "func Unzip7[A, B, C, D, E, F, G any](tuples []Tuple7[A, B, C, D, E, F, G]) ([]A, []B, []C, []D, []E, []F, []G)"
- "func Unzip8[A, B, C, D, E, F, G, H any](tuples []Tuple8[A, B, C, D, E, F, G, H]) ([]A, []B, []C, []D, []E, []F, []G, []H)"
- "func Unzip9[A, B, C, D, E, F, G, H, I any](tuples []Tuple9[A, B, C, D, E, F, G, H, I]) ([]A, []B, []C, []D, []E, []F, []G, []H, []I)"
playUrl: https://go.dev/play/p/K-vG9tyD3Kf
variantHelpers:
- core#tuple#unzipx
similarHelpers:
- core#tuple#tuplex
- core#tuple#unpackx
- core#tuple#zipx
- core#tuple#zipbyx
- core#tuple#unzipbyx
- core#slice#mapkeys
- core#slice#mapvalues
position: 30
---
Splits a slice of tuples back into multiple parallel slices. Variants support tuple sizes from 2 to 9.
Variants: `Unzip2..Unzip9`
```go
pairs := []lo.Tuple2[int, string]{
lo.T2(1, "a"),
lo.T2(2, "b"),
}
xs, ys := lo.Unzip2(pairs)
```
================================================
FILE: docs/data/core-validate.md
================================================
---
name: Validate
slug: validate
sourceRef: errors.go#L13
category: core
subCategory: error-handling
playUrl: https://go.dev/play/p/vPyh51XpCBt
variantHelpers:
- core#error-handling#validate
similarHelpers:
- core#error-handling#mustx
- core#error-handling#tryx
- core#error-handling#tryorx
- core#error-handling#assert
position: 0
signatures:
- "func Validate(ok bool, format string, args ...any) error"
---
Creates an error when a condition is not met; returns nil when it is.
```go
slice := []string{"a"}
err := lo.Validate(len(slice) == 0, "Slice should be empty")
// error("Slice should be empty")
err := lo.Validate(len(slice) == 0, "Slice should be empty but contains %v", slice)
// error("Slice should be empty but contains [a]")
```
================================================
FILE: docs/data/core-valueor.md
================================================
---
name: ValueOr
slug: valueor
sourceRef: map.go#L97
category: core
subCategory: map
playUrl: https://go.dev/play/p/bAq9mHErB4V
variantHelpers:
- core#map#valueor
similarHelpers:
- core#map#haskey
- core#map#keys
- core#map#values
position: 50
signatures:
- "func ValueOr[K comparable, V any](in map[K]V, key K, fallback V) V"
---
Returns the value for a key or a fallback if the key is not present.
```go
value := lo.ValueOr(map[string]int{"foo": 1, "bar": 2}, "foo", 42)
// 1
value = lo.ValueOr(map[string]int{"foo": 1, "bar": 2}, "baz", 42)
// 42
```
================================================
FILE: docs/data/core-values.md
================================================
---
name: Values
slug: values
sourceRef: map.go#L54
category: core
subCategory: map
playUrl: https://go.dev/play/p/nnRTQkzQfF6
variantHelpers:
- core#map#values
similarHelpers:
- core#map#keys
- core#map#entries
- core#map#topairs
- core#map#frompairs
- core#map#uniqvalues
position: 30
signatures:
- "func Values[K comparable, V any](in ...map[K]V) []V"
---
Creates a slice of the map values across one or more maps.
```go
values := lo.Values(map[string]int{"foo": 1, "bar": 2})
// []int{1, 2}
values = lo.Values(map[string]int{"foo": 1}, map[string]int{"bar": 2})
// []int{1, 2}
```
================================================
FILE: docs/data/core-waitfor.md
================================================
---
name: WaitFor
slug: waitfor
sourceRef: concurrency.go#L112
category: core
subCategory: concurrency
playUrl: https://go.dev/play/p/t_wTDmubbK3
variantHelpers:
- core#concurrency#waitfor
- core#concurrency#waitforwithcontext
similarHelpers:
- core#retry#attemptwithdelay
- core#retry#attemptwhilewithdelay
- core#time#durationx
position: 20
signatures:
- "func WaitFor(condition func(i int) bool, timeout time.Duration, heartbeatDelay time.Duration) (totalIterations int, elapsed time.Duration, conditionFound bool)"
- "func WaitForWithContext(ctx context.Context, condition func(ctx context.Context, i int) bool, timeout time.Duration, heartbeatDelay time.Duration) (totalIterations int, elapsed time.Duration, conditionFound bool)"
---
Runs periodically until a condition is validated. Use WaitFor for simple predicates, or WaitForWithContext when you need context cancellation/timeout inside the predicate. Both return total iterations, elapsed time, and whether the condition became true.
```go
iterations, elapsed, ok := lo.WaitFor(
func(i int) bool {
return i > 5
},
10*time.Millisecond,
time.Millisecond,
)
```
With context:
```go
iterations, elapsed, ok := lo.WaitForWithContext(
context.Background(),
func(_ context.Context, i int) bool {
return i >= 5
},
10*time.Millisecond,
time.Millisecond,
)
```
================================================
FILE: docs/data/core-window.md
================================================
---
name: Window
slug: window
sourceRef: slice.go#L289
category: core
subCategory: slice
variantHelpers:
- core#slice#window
similarHelpers:
- core#slice#sliding
- core#slice#chunk
- core#slice#partitionby
- core#slice#flatten
- it#sequence#window
position: 145
signatures:
- "func Window[T any, Slice ~[]T](collection Slice, size int) []Slice"
---
Creates a slice of sliding windows of a given size. Each window overlaps with the previous one by size-1 elements. This is equivalent to `Sliding(collection, size, 1)`.
```go
lo.Window([]int{1, 2, 3, 4, 5}, 3)
// [][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}}
lo.Window([]float64{20, 22, 21, 23, 24}, 3)
// [][]float64{{20, 22, 21}, {22, 21, 23}, {21, 23, 24}}
```
================================================
FILE: docs/data/core-without.md
================================================
---
name: Without
slug: without
sourceRef: intersect.go#L181
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/PcAVtYJsEsS
variantHelpers:
- core#intersect#without
similarHelpers:
- core#intersect#withoutby
- core#intersect#intersect
- core#intersect#difference
- core#intersect#union
- core#slice#reject
position: 110
signatures:
- "func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice"
---
Returns a slice excluding all given values.
```go
lo.Without([]int{0, 2, 10}, 2)
// []int{0, 10}
```
================================================
FILE: docs/data/core-withoutby.md
================================================
---
name: WithoutBy
slug: withoutby
sourceRef: intersect.go#L273
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/VgWJOF01NbJ
variantHelpers:
- core#intersect#withoutby
similarHelpers:
- core#intersect#without
- core#intersect#difference
- core#slice#rejectby
- core#intersect#withoutbyerr
position: 120
signatures:
- "func WithoutBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K, exclude ...K) Slice"
---
Filters a slice by excluding elements whose extracted keys match any in the exclude list.
```go
type User struct {
ID int
Name string
}
users := []User{
{1, "Alice"},
{2, "Bob"},
{3, "Charlie"},
}
filtered := lo.WithoutBy(users, func(u User) int {
return u.ID
}, 2, 3)
// []User{{1, "Alice"}}
```
================================================
FILE: docs/data/core-withoutbyerr.md
================================================
---
name: WithoutByErr
slug: withoutbyerr
sourceRef: intersect.go#L287
category: core
subCategory: intersect
signatures:
- "func WithoutByErr[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) (K, error), exclude ...K) (Slice, error)"
variantHelpers:
- core#intersect#withoutbyerr
similarHelpers:
- core#intersect#withoutby
- core#intersect#without
- core#slice#rejectbyerr
position: 125
---
Filters a slice by excluding elements whose extracted keys match any in the exclude list. Returns an error if the iteratee function fails, stopping iteration immediately.
```go
type User struct {
ID int
Name string
}
users := []User{
{1, "Alice"},
{2, "Bob"},
{3, "Charlie"},
}
filtered, err := lo.WithoutByErr(users, func(u User) (int, error) {
if u.ID == 2 {
return 0, fmt.Errorf("Bob not allowed")
}
return u.ID, nil
}, 2, 3)
// []User(nil), error("Bob not allowed")
```
```go
type User struct {
ID int
Name string
}
users := []User{
{1, "Alice"},
{2, "Bob"},
{3, "Charlie"},
}
filtered, err := lo.WithoutByErr(users, func(u User) (int, error) {
return u.ID, nil
}, 2, 3)
// []User{{1, "Alice"}}, nil
```
================================================
FILE: docs/data/core-withoutempty.md
================================================
---
name: WithoutEmpty
slug: withoutempty
sourceRef: intersect.go#L218
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/iZvYJWuniJm
variantHelpers:
- core#intersect#withoutempty
similarHelpers:
- core#slice#compact
- core#intersect#without
- core#intersect#withoutby
position: 130
signatures:
- "func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice"
---
Returns a slice excluding zero values. Deprecated: use `Compact` instead.
```go
lo.WithoutEmpty([]int{0, 2, 10})
// []int{2, 10}
```
================================================
FILE: docs/data/core-withoutnth.md
================================================
---
name: WithoutNth
slug: withoutnth
sourceRef: intersect.go#L223
category: core
subCategory: intersect
playUrl: https://go.dev/play/p/AIro-_UtL9c
variantHelpers:
- core#intersect#withoutnth
similarHelpers:
- core#intersect#without
- core#intersect#withoutby
- core#slice#dropbyindex
position: 140
signatures:
- "func WithoutNth[T comparable, Slice ~[]T](collection Slice, nths ...int) Slice"
---
Returns a slice excluding the elements at the given indexes.
```go
lo.WithoutNth([]int{-2, -1, 0, 1, 2}, 3)
// []int{-2, -1, 0, 2}
```
================================================
FILE: docs/data/core-words.md
================================================
---
name: Words
slug: words
sourceRef: string.go#L210
category: core
subCategory: string
playUrl: https://go.dev/play/p/-f3VIQqiaVw
variantHelpers:
- core#string#words
similarHelpers:
- core#string#pascalcase
- core#string#camelcase
- core#string#kebabcase
- core#string#snakecase
- core#string#capitalize
- core#string#chunkstring
- core#string#substring
position: 80
signatures:
- "func Words(str string) []string"
---
Splits a string into a slice of its words, separating letters and digits and removing non-alphanumeric separators.
```go
lo.Words("helloWorld")
// []string{"hello", "world"}
```
================================================
FILE: docs/data/core-zipbyerrx.md
================================================
---
name: ZipByErrX
slug: zipbyerrx
sourceRef: tuples.go#L444
category: core
subCategory: tuple
signatures:
- "func ZipByErr2[A any, B any, Out any](a []A, b []B, iteratee func(a A, b B) (Out, error)) ([]Out, error)"
- "func ZipByErr3[A any, B any, C any, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) (Out, error)) ([]Out, error)"
- "func ZipByErr4[A any, B any, C any, D any, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) (Out, error)) ([]Out, error)"
- "func ZipByErr5[A any, B any, C any, D any, E any, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error)"
- "func ZipByErr6[A any, B any, C any, D any, E any, F any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error)"
- "func ZipByErr7[A any, B any, C any, D any, E any, F any, G any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error)"
- "func ZipByErr8[A any, B any, C any, D any, E any, F any, G any, H any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error)"
- "func ZipByErr9[A any, B any, C any, D any, E any, F any, G any, H any, I any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error)"
variantHelpers:
- core#tuple#zipbyerrx
similarHelpers:
- core#tuple#zipbyx
position: 30
---
Zips multiple slices and projects each grouped set through a function that can return an error. Stops iteration immediately when an error is encountered and returns the zero value (nil for slices).
Variants: `ZipByErr2..ZipByErr9`
```go
result, err := lo.ZipByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
if b == 2 {
return "", fmt.Errorf("number 2 is not allowed")
}
return fmt.Sprintf("%s-%d", a, b), nil
})
// []string(nil), error("number 2 is not allowed")
```
```go
result, err := lo.ZipByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
return fmt.Sprintf("%s-%d", a, b), nil
})
// []string{"a-1", "b-2"}, nil
```
When collections are different sizes, the missing attributes are filled with zero value before calling the iteratee.
================================================
FILE: docs/data/core-zipbyx.md
================================================
---
name: ZipByX
slug: zipbyx
sourceRef: tuples.go#L335
category: core
subCategory: tuple
signatures:
- "func ZipBy2[A any, B any, Out any](a []A, b []B, predicate func(a A, b B) Out) []Out"
- "func ZipBy3[A any, B any, C any, Out any](a []A, b []B, c []C, predicate func(a A, b B, c C) Out) []Out"
- "func ZipBy4[A any, B any, C any, D any, Out any](a []A, b []B, c []C, d []D, predicate func(a A, b B, c C, d D) Out) []Out"
- "func ZipBy5[A any, B any, C any, D any, E any, Out any](a []A, b []B, c []C, d []D, e []E, predicate func(a A, b B, c C, d D, e E) Out) []Out"
- "func ZipBy6[A any, B any, C any, D any, E any, F any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, predicate func(a A, b B, c C, d D, e E, f F) Out) []Out"
- "func ZipBy7[A any, B any, C any, D any, E any, F any, G any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, predicate func(a A, b B, c C, d D, e E, f F, g G) Out) []Out"
- "func ZipBy8[A any, B any, C any, D any, E any, F any, G any, H any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, predicate func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out"
- "func ZipBy9[A any, B any, C any, D any, E any, F any, G any, H any, I any, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, predicate func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out"
playUrl: https://go.dev/play/p/wlHur6yO8rR
variantHelpers:
- core#tuple#zipbyx
- core#tuple#zipbyerrx
similarHelpers:
- core#tuple#tuplex
- core#tuple#unpackx
- core#tuple#zipx
- core#tuple#unzipx
- core#tuple#unzipbyx
- core#slice#map
- core#slice#filtermap
position: 31
---
Zips multiple slices and projects each grouped set through a function. Variants support 2 up to 9 input slices.
Variants: `ZipBy2..ZipBy9`
```go
xs := []int{1,2}
ys := []string{"a","b"}
pairs := lo.ZipBy2(xs, ys, func(x int, y string) string {
return fmt.Sprintf("%d-%s", x, y)
})
```
================================================
FILE: docs/data/core-zipx.md
================================================
---
name: ZipX
slug: zipx
sourceRef: tuples.go#L103
category: core
subCategory: tuple
signatures:
- "func Zip2[A, B any](a []A, b []B) []Tuple2[A, B]"
playUrl: https://go.dev/play/p/jujaA6GaJTp
variantHelpers:
- core#tuple#zipx
similarHelpers:
- core#tuple#tuplex
- core#tuple#unpackx
- core#tuple#zipbyx
- core#tuple#unzipx
- core#tuple#unzipbyx
- core#slice#interleave
position: 20
---
Zips multiple slices into a slice of tuples. Variants support 2 up to 9 input slices.
Variants: `Zip2..Zip9`
```go
xs := []int{1,2}
ys := []string{"a","b"}
pairs := lo.Zip2(xs, ys)
```
================================================
FILE: docs/data/it-assign.md
================================================
---
name: Assign
slug: assign
sourceRef: it/map.go#L122
category: it
subCategory: map
signatures:
- "func Assign[K comparable, V any, Map ~map[K]V](maps ...iter.Seq[Map]) Map"
variantHelpers:
- it#map#assign
similarHelpers:
- core#map#assign
- it#map#fromentries
- it#map#invert
position: 50
---
Merges multiple map sequences into a single map. Later maps overwrite values from earlier maps when keys conflict.
```go
map1 := func(yield func(map[string]int) bool) {
yield(map[string]int{"a": 1, "b": 2})
}
map2 := func(yield func(map[string]int) bool) {
yield(map[string]int{"b": 3, "c": 4})
}
map3 := func(yield func(map[string]int) bool) {
yield(map[string]int{"d": 5, "e": 6})
}
result := it.Assign(map1, map2, map3)
// map[string]int{"a": 1, "b": 3, "c": 4, "d": 5, "e": 6}
// Note: "b" is 3 (overwritten from map2)
singleMap := func(yield func(map[int]string) bool) {
yield(map[int]string{1: "one", 2: "two"})
}
result = it.Assign(singleMap)
// map[int]string{1: "one", 2: "two"}
emptyMap1 := func(yield func(map[string]bool) bool) {
yield(map[string]bool{})
}
emptyMap2 := func(yield func(map[string]bool) bool) {
yield(map[string]bool{"active": true})
}
result = it.Assign(emptyMap1, emptyMap2)
// map[string]bool{"active": true}
```
================================================
FILE: docs/data/it-associate.md
================================================
---
name: Associate
slug: associate
sourceRef: it/seq.go#L412
category: it
subCategory: map
signatures:
- "func Associate[T any, K comparable, V any](collection iter.Seq[T], transform func(item T) (K, V)) map[K]V"
- "func AssociateI[T any, K comparable, V any](collection iter.Seq[T], transform func(item T, index int) (K, V)) map[K]V"
- "func SeqToMap[T any, K comparable, V any](collection iter.Seq[T], transform func(item T) (K, V)) map[K]V"
- "func SeqToMapI[T any, K comparable, V any](collection iter.Seq[T], transform func(item T, index int) (K, V)) map[K]V"
- "func FilterSeqToMap[T any, K comparable, V any](collection iter.Seq[T], transform func(item T) (K, V, bool)) map[K]V"
- "func FilterSeqToMapI[T any, K comparable, V any](collection iter.Seq[T], transform func(item T, index int) (K, V, bool)) map[K]V"
variantHelpers:
- it#sequence#associate
- it#sequence#associatei
- it#sequence#seqtomap
- it#sequence#seqtomapi
- it#sequence#filterseqtomap
- it#sequence#filterseqtomapi
similarHelpers:
- core#slice#associate
- core#map#keyby
- it#map#keyby
position: 140
---
Associate returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
If any of two pairs have the same key the last one gets added to the map.
```go
collection := func(yield func(string) bool) {
yield("apple")
yield("banana")
yield("cherry")
}
result := it.Associate(collection, func(s string) (string, int) {
return s, len(s)
})
// result contains {"apple": 5, "banana": 6, "cherry": 6}
```
AssociateI returns a map containing key-value pairs provided by transform function applied to elements of the given sequence, with index.
```go
collection := func(yield func(string) bool) {
yield("a")
yield("b")
yield("c")
}
result := it.AssociateI(collection, func(item string, index int) (int, string) {
return index, item
})
// result contains {0: "a", 1: "b", 2: "c"}
```
SeqToMap returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
Alias of Associate().
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
}
result := it.SeqToMap(collection, func(i int) (int, string) {
return i, fmt.Sprintf("item-%d", i)
})
// result contains {1: "item-1", 2: "item-2", 3: "item-3"}
```
FilterSeqToMap returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
The third return value of the transform function is a boolean that indicates whether the key-value pair should be included in the map.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
result := it.FilterSeqToMap(collection, func(i int) (int, string, bool) {
return i, fmt.Sprintf("item-%d", i), i%2 == 0
})
// result contains {2: "item-2", 4: "item-4"}
```
================================================
FILE: docs/data/it-buffer.md
================================================
---
name: Buffer
slug: buffer
sourceRef: it/seq.go#L1162
category: it
subCategory: sequence
signatures:
- "func Buffer[T any](seq iter.Seq[T], size int) iter.Seq[[]T]"
playUrl: https://go.dev/play/p/zDZdcCA20ut
variantHelpers:
- it#sequence#buffer
similarHelpers:
- it#sequence#chunk
- it#sequence#sliding
- it#sequence#window
position: 65
---
Returns a sequence of slices, each containing up to size items read from the sequence.
The last slice may be smaller if the sequence closes before filling the buffer.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(5)
_ = yield(6)
_ = yield(7)
}
buffers := it.Buffer(seq, 3)
var result [][]int
for buffer := range buffers {
result = append(result, buffer)
}
// result contains [[1 2 3] [4 5 6] [7]]
```
================================================
FILE: docs/data/it-channelseq.md
================================================
---
name: ChannelToSeq
slug: channeltoseq
sourceRef: it/channel.go#L39
category: it
subCategory: channel
signatures:
- "func ChannelToSeq[T any](ch <-chan T) iter.Seq[T]"
playUrl: "https://go.dev/play/p/IXqSs2Ooqpm"
variantHelpers:
- it#channel#channeltoseq
similarHelpers:
- it#channel#seqtochannel
position: 10
---
Builds an `iter.Seq` from a channel. The returned sequence yields each value received from the channel in order. Iteration blocks until the channel is closed.
Examples:
```go
ch := make(chan int, 3)
ch <- 1; ch <- 2; ch <- 3
close(ch)
seq := it.ChannelToSeq(ch)
var got []int
for v := range seq {
got = append(got, v)
}
// got == []int{1, 2, 3}
```
================================================
FILE: docs/data/it-channeltoseq.md
================================================
---
name: SeqToChannel2
slug: channeltoseq
sourceRef: it/channel.go#L26
category: it
subCategory: channel
signatures:
- "func SeqToChannel2[K, V any](bufferSize int, collection iter.Seq2[K, V]) <-chan lo.Tuple2[K, V]"
- "func ChannelToSeq[T any](ch <-chan T) iter.Seq[T]"
playUrl: "https://go.dev/play/p/IXqSs2Ooqpm"
variantHelpers:
- it#channel#seqtochannel
- it#channel#seqtochannel2
- it#channel#channeltoseq
similarHelpers:
- core#channel#channelseq
position: 10
---
SeqToChannel2 returns a read-only channel of key-value tuple elements from a sequence.
```go
collection := func(yield func(int, string) bool) {
yield(1, "a")
yield(2, "b")
}
ch := it.SeqToChannel2(10, collection)
for tuple := range ch {
fmt.Printf("%d: %s\n", tuple.A, tuple.B)
}
// 1: a
// 2: b
```
ChannelToSeq returns a sequence built from channel items. Blocks until channel closes.
```go
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
seq := it.ChannelToSeq(ch)
for item := range seq {
fmt.Println(item)
}
// 1
// 2
// 3
```
================================================
FILE: docs/data/it-chunk.md
================================================
---
name: Chunk
slug: chunk
sourceRef: it/seq.go#L264
category: it
subCategory: sequence
signatures:
- "func Chunk[T any](collection iter.Seq[T], size int) iter.Seq[[]T]"
playUrl: https://go.dev/play/p/qo8esZ_L60Q
variantHelpers:
- it#sequence#chunk
similarHelpers:
- core#slice#chunk
- it#sequence#partitionby
position: 60
---
Returns a sequence of elements split into groups of length size. The last chunk may be smaller than size.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(5)
}
chunks := it.Chunk(seq, 2)
var result [][]int
for chunk := range chunks {
result = append(result, chunk)
}
// result contains [1, 2], [3, 4], [5]
```
## Note
`it.ChunkString` and `it.Chunk` functions behave inconsistently for empty input: `it.ChunkString("", n)` returns `[""]` instead of `[]`.
See https://github.com/samber/lo/issues/788
================================================
FILE: docs/data/it-chunkentries.md
================================================
---
name: ChunkEntries
slug: chunkentries
sourceRef: it/map.go#L138
category: it
subCategory: map
signatures:
- "func ChunkEntries[K comparable, V any](m map[K]V, size int) iter.Seq[map[K]V]"
variantHelpers:
- it#map#chunkentries
similarHelpers:
- core#map#chunkentries
- it#sequence#chunk
- it#map#keys
- it#map#values
position: 60
---
Chunks a map into smaller maps of the specified size. Returns a sequence of maps, each containing up to the specified number of entries.
```go
originalMap := map[string]int{
"a": 1, "b": 2, "c": 3, "d": 4, "e": 5,
}
result := it.ChunkEntries(originalMap, 2)
// iter.Seq[map[string]int] yielding:
// map[string]int{"a": 1, "b": 2}
// map[string]int{"c": 3, "d": 4}
// map[string]int{"e": 5}
smallMap := map[int]string{1: "one", 2: "two"}
result = it.ChunkEntries(smallMap, 5)
// iter.Seq[map[int]string] yielding:
// map[int]string{1: "one", 2: "two"}
largeMap := make(map[int]bool)
for i := 0; i < 10; i++ {
largeMap[i] = true
}
result = it.ChunkEntries(largeMap, 3)
// iter.Seq[map[int]bool] yielding 4 maps with 3, 3, 3, and 1 entries respectively
```
================================================
FILE: docs/data/it-chunkstring.md
================================================
---
name: ChunkString
slug: chunkstring
sourceRef: it/string.go#L130
category: it
subCategory: string
playUrl: "https://go.dev/play/p/Bcc5ixTQQoQ"
variantHelpers:
- it#string#chunkstring
similarHelpers:
- core#string#chunkstring
position: 0
signatures:
- "func ChunkString[T ~string](str T, size int) iter.Seq[T]"
---
Returns a sequence of chunks of length `size` from the input string. If the string length is not a multiple of `size`, the final chunk contains the remaining characters. Panics if `size <= 0`.
Examples:
```go
// Even split
seq := it.ChunkString("123456", 2)
var out []string
for s := range seq { out = append(out, s) }
// out == []string{"12", "34", "56"}
```
```go
// Remainder chunk
seq := it.ChunkString("1234567", 2)
var out []string
for s := range seq { out = append(out, s) }
// out == []string{"12", "34", "56", "7"}
```
```go
// Empty and small inputs
seq1 := it.ChunkString("", 2)
seq2 := it.ChunkString("1", 2)
// seq1 yields ""
// seq2 yields "1"
```
================================================
FILE: docs/data/it-coalesceseq.md
================================================
---
name: CoalesceSeq
slug: coalesceseq
sourceRef: it/type_manipulation.go#L65
category: it
subCategory: type
signatures:
- "func CoalesceSeq[T any](v ...iter.Seq[T]) (iter.Seq[T], bool)"
variantHelpers:
- it#type#coalesceseq
similarHelpers:
- it#type#coalesceseqorempty
- core#type#coalesce
- core#type#coalesceslice
position: 100
---
Returns the first non-empty sequence from the provided arguments, with a boolean indicating if a non-empty sequence was found.
```go
emptySeq := func(yield func(int) bool) bool {
return false // empty sequence
}
nonEmptySeq := it.Range(3)
result, ok := it.CoalesceSeq(emptySeq, nonEmptySeq, emptySeq)
// iter.Seq[int] yielding 0, 1, 2, true
emptyStrSeq := func(yield func(string) bool) bool {
return false // empty sequence
}
strSeq := func(yield func(string) bool) bool {
yield("a")
yield("b")
return true
}
result, ok = it.CoalesceSeq(emptyStrSeq, strSeq)
// iter.Seq[string] yielding "a", "b", true
result, ok = it.CoalesceSeq(emptySeq, emptyStrSeq)
// nil sequence, false
```
================================================
FILE: docs/data/it-coalesceseqorempty.md
================================================
---
name: CoalesceSeqOrEmpty
slug: coalesceseqorempty
sourceRef: it/type_manipulation.go#L76
category: it
subCategory: type
signatures:
- "func CoalesceSeqOrEmpty[T any](v ...iter.Seq[T]) iter.Seq[T]"
variantHelpers:
- it#type#coalesceseqorempty
similarHelpers:
- it#type#coalesceseq
- core#type#coalesceorempty
- core#type#coalescesliceorempty
position: 102
---
Returns the first non-empty sequence from the provided arguments, or an empty sequence if all arguments are empty.
```go
emptySeq := func(yield func(int) bool) bool {
return false // empty sequence
}
nonEmptySeq := it.Range(3)
result := it.CoalesceSeqOrEmpty(emptySeq, nonEmptySeq, emptySeq)
// iter.Seq[int] yielding 0, 1, 2
emptyStrSeq := func(yield func(string) bool) bool {
return false // empty sequence
}
strSeq := func(yield func(string) bool) bool {
yield("a")
yield("b")
return true
}
result = it.CoalesceSeqOrEmpty(emptyStrSeq, strSeq)
// iter.Seq[string] yielding "a", "b"
result = it.CoalesceSeqOrEmpty(emptySeq, emptyStrSeq)
// empty sequence (yields nothing)
```
================================================
FILE: docs/data/it-compact.md
================================================
---
name: Compact
slug: compact
sourceRef: it/seq.go#L699
category: it
subCategory: slice
signatures:
- "func Compact[T comparable, I ~func(func(T) bool)](collection I) I"
variantHelpers:
- it#slice#compact
similarHelpers:
- core#slice#compact
position: 192
---
Compact returns a sequence of all non-zero elements.
```go
collection := func(yield func(int) bool) {
yield(0)
yield(1)
yield(0)
yield(2)
yield(3)
yield(0)
}
compacted := it.Compact(collection)
var result []int
for item := range compacted {
result = append(result, item)
}
// result contains [1, 2, 3]
```
================================================
FILE: docs/data/it-concat.md
================================================
---
name: Concat
slug: concat
sourceRef: it/seq.go#L358
category: it
subCategory: sequence
playUrl: https://go.dev/play/p/Fa0u7xT2JOR
variantHelpers:
- it#sequence#concat
similarHelpers:
- it#sequence#flatten
- core#slice#concat
position: 160
signatures:
- "func Concat[T any, I ~func(func(T) bool)](collection ...I) I"
---
Returns a sequence of all the elements in iterators. Concat conserves the order of the elements.
```go
list1 := slices.Values([]int{0, 1, 2})
list2 := slices.Values([]int{3, 4, 5})
list3 := slices.Values([]int{6, 7, 8})
result := it.Concat(list1, list2, list3)
// result: []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
```
================================================
FILE: docs/data/it-contains.md
================================================
---
name: Contains
slug: contains
sourceRef: it/intersect.go#L13
category: it
subCategory: intersect
signatures:
- "func Contains[T comparable](collection iter.Seq[T], element T) bool"
playUrl: "https://go.dev/play/p/1edj7hH3TS2"
variantHelpers:
- it#intersect#contains
similarHelpers:
- core#slice#contains
- it#intersect#containsby
- it#intersect#some
position: 0
---
Returns true if an element is present in a collection.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
}
has := it.Contains(seq, 20)
// has == true
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
has := it.Contains(seq, "orange")
// has == false
```
================================================
FILE: docs/data/it-containsby.md
================================================
---
name: ContainsBy
slug: containsby
sourceRef: it/intersect.go#L17
category: it
subCategory: intersect
signatures:
- "func ContainsBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool"
playUrl: https://go.dev/play/p/m86Cpsoyv8k
variantHelpers:
- it#intersect#containsby
similarHelpers:
- core#slice#containsby
position: 650
---
Returns true if predicate function returns true for any element in the collection.
Will iterate through the entire sequence if predicate never returns true.
Examples:
```go
// Check if collection contains an even number
numbers := it.Slice([]int{1, 3, 5, 7, 9})
hasEven := it.ContainsBy(numbers, func(n int) bool { return n%2 == 0 })
// hasEven: false
numbers = it.Slice([]int{1, 3, 5, 8, 9})
hasEven = it.ContainsBy(numbers, func(n int) bool { return n%2 == 0 })
// hasEven: true
// Check if collection contains a string with specific prefix
words := it.Slice([]string{"hello", "world", "go", "lang"})
hasPrefix := it.ContainsBy(words, func(s string) bool { return strings.HasPrefix(s, "go") })
// hasPrefix: true
// Check if collection contains a person with specific age
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
hasAge30 := it.ContainsBy(people, func(p Person) bool { return p.Age == 30 })
// hasAge30: true
hasAge40 := it.ContainsBy(people, func(p Person) bool { return p.Age == 40 })
// hasAge40: false
// Check if collection contains an element with specific property
strings := it.Slice([]string{"apple", "banana", "cherry"})
hasLongString := it.ContainsBy(strings, func(s string) bool { return len(s) > 5 })
// hasLongString: true
// Check if collection contains negative numbers
numbers = it.Slice([]int{1, -2, 3, 4, -5})
hasNegative := it.ContainsBy(numbers, func(n int) bool { return n < 0 })
// hasNegative: true
// Check if collection contains valid email
emails := it.Slice([]string{"user@example.com", "invalid-email", "test@domain.org"})
hasValidEmail := it.ContainsBy(emails, func(email string) bool {
return strings.Contains(email, "@") && strings.Contains(email, ".")
})
// hasValidEmail: true
// Check empty collection
empty := it.Slice([]int{})
hasAny := it.ContainsBy(empty, func(n int) bool { return n > 0 })
// hasAny: false
// Check for nil pointers (with pointer slice)
ptrs := it.Slice([]*int{ptr(5), nil, ptr(10)})
hasNil := it.ContainsBy(ptrs, func(p *int) bool { return p == nil })
// hasNil: true
```
================================================
FILE: docs/data/it-count.md
================================================
---
name: Count / CountBy
slug: count
sourceRef: it/seq.go#L630
category: it
subCategory: sequence
signatures:
- "func Count[T comparable](collection iter.Seq[T], value T) int"
- "func CountBy[T any](collection iter.Seq[T], predicate func(item T) bool) int"
playUrl: https://go.dev/play/p/UcJ-6cANwfY
variantHelpers:
- it#sequence#count
- it#sequence#countby
similarHelpers:
- core#slice#count
- it#sequence#countvalues
position: 110
---
Counts elements in a collection. Count counts elements equal to a value, CountBy counts elements matching a predicate.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(2)
_ = yield(3)
_ = yield(2)
}
cnt := it.Count(seq, 2)
// cnt == 3
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("apricot")
}
cnt := it.CountBy(seq, func(s string) bool {
return len(s) > 5
})
// cnt == 2 (banana, apricot)
```
================================================
FILE: docs/data/it-countby.md
================================================
---
name: CountBy
slug: countby
sourceRef: it/seq.go#L325
category: it
subCategory: find
signatures:
- "func CountBy[T any](collection iter.Seq[T], predicate func(item T) bool) int"
playUrl: https://go.dev/play/p/m6G0o3huCOG
variantHelpers:
- it#find#count
similarHelpers:
- core#slice#countby
- core#slice#count
position: 35
---
Counts the number of elements in the collection that satisfy the predicate.
```go
result := it.CountBy(it.Range(1, 11), func(item int) bool {
return item%2 == 0
})
// 5
```
================================================
FILE: docs/data/it-countvalues.md
================================================
---
name: CountValues
slug: countvalues
sourceRef: it/seq.go#L720
category: it
subCategory: slice
signatures:
- "func CountValues[T comparable](collection iter.Seq[T]) map[T]int"
playUrl: https://go.dev/play/p/PPBT4Fp-V3B
variantHelpers: []
similarHelpers:
- core#slice#countvalues
position: 203
---
CountValues counts the number of each element in the collection.
```go
collection := func(yield func(string) bool) {
yield("apple")
yield("banana")
yield("apple")
yield("cherry")
yield("banana")
yield("apple")
}
counts := it.CountValues(collection)
// counts contains {"apple": 3, "banana": 2, "cherry": 1}
```
================================================
FILE: docs/data/it-countvaluesby.md
================================================
---
name: CountValuesBy
slug: countvaluesby
sourceRef: it/seq.go#L720
category: it
subCategory: slice
signatures:
- "func CountValuesBy[T any, U comparable](collection iter.Seq[T], transform func(item T) U) map[U]int"
playUrl: https://go.dev/play/p/gnr_MPhYCHX
variantHelpers: []
similarHelpers:
- core#slice#countvaluesby
position: 204
---
CountValuesBy counts the number of each element returned from transform function.
Is equivalent to chaining Map and CountValues.
```go
type Person struct {
Name string
Age int
}
collection := func(yield func(Person) bool) {
yield(Person{"Alice", 25})
yield(Person{"Bob", 30})
yield(Person{"Charlie", 25})
yield(Person{"Diana", 30})
}
countsByAge := it.CountValuesBy(collection, func(p Person) int {
return p.Age
})
// countsByAge contains {25: 2, 30: 2}
```
================================================
FILE: docs/data/it-crossjoinbyx.md
================================================
---
name: CrossJoinByX
slug: crossjoinbyx
sourceRef: it/tuples.go#L439
category: it
subCategory: tuple
signatures:
- "func CrossJoinBy2[T1, T2, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], transform func(T1, T2) R) iter.Seq[R]"
- "func CrossJoinBy3[T1, T2, T3, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], transform func(T1, T2, T3) R) iter.Seq[R]"
- "func CrossJoinBy4[T1, T2, T3, T4, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], transform func(T1, T2, T3, T4) R) iter.Seq[R]"
- "func CrossJoinBy5[T1, T2, T3, T4, T5, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], transform func(T1, T2, T3, T4, T5) R) iter.Seq[R]"
- "func CrossJoinBy6[T1, T2, T3, T4, T5, T6, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], seq6 iter.Seq[T6], transform func(T1, T2, T3, T4, T5, T6) R) iter.Seq[R]"
- "func CrossJoinBy7[T1, T2, T3, T4, T5, T6, T7, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], seq6 iter.Seq[T6], seq7 iter.Seq[T7], transform func(T1, T2, T3, T4, T5, T6, T7) R) iter.Seq[R]"
- "func CrossJoinBy8[T1, T2, T3, T4, T5, T6, T7, T8, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], seq6 iter.Seq[T6], seq7 iter.Seq[T7], seq8 iter.Seq[T8], transform func(T1, T2, T3, T4, T5, T6, T7, T8) R) iter.Seq[R]"
- "func CrossJoinBy9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], seq6 iter.Seq[T6], seq7 iter.Seq[T7], seq8 iter.Seq[T8], seq9 iter.Seq[T9], transform func(T1, T2, T3, T4, T5, T6, T7, T8, T9) R) iter.Seq[R]"
playUrl: https://go.dev/play/p/6QGp3W-bQU1
variantHelpers:
- it#tuple#crossjoinbyx
similarHelpers:
- core#tuple#crossjoinbyx
- it#tuple#crossjoinx
- core#slice#map
position: 20
---
Combines every item from multiple lists (cartesian product) using a callback function to transform the results. Returns an empty sequence if any input sequence is empty.
Variants: `CrossJoinBy2..CrossJoinBy9`
```go
seq1 := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
}
seq2 := func(yield func(string) bool) {
_ = yield("a")
_ = yield("b")
}
result := it.CrossJoinBy2(seq1, seq2, func(i int, s string) string {
return fmt.Sprintf("%d-%s", i, s)
})
var output []string
for item := range result {
output = append(output, item)
}
// output contains ["1-a", "1-b", "2-a", "2-b"]
```
================================================
FILE: docs/data/it-crossjoinx.md
================================================
---
name: CrossJoinX
slug: crossjoinx
sourceRef: it/tuples.go#L370
category: it
subCategory: tuple
signatures:
- "func CrossJoin2[T1, T2 any](list1 iter.Seq[T1], list2 iter.Seq[T2]) iter.Seq[lo.Tuple2[T1, T2]]"
- "func CrossJoin3[T1, T2, T3 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3]) iter.Seq[lo.Tuple3[T1, T2, T3]]"
- "func CrossJoin4[T1, T2, T3, T4 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4]) iter.Seq[lo.Tuple4[T1, T2, T3, T4]]"
- "func CrossJoin5[T1, T2, T3, T4, T5 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5]) iter.Seq[lo.Tuple5[T1, T2, T3, T4, T5]]"
- "func CrossJoin6[T1, T2, T3, T4, T5, T6 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5], list6 iter.Seq[T6]) iter.Seq[lo.Tuple6[T1, T2, T3, T4, T5, T6]]"
- "func CrossJoin7[T1, T2, T3, T4, T5, T6, T7 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5], list6 iter.Seq[T6], list7 iter.Seq[T7]) iter.Seq[lo.Tuple7[T1, T2, T3, T4, T5, T6, T7]]"
- "func CrossJoin8[T1, T2, T3, T4, T5, T6, T7, T8 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5], list6 iter.Seq[T6], list7 iter.Seq[T7], list8 iter.Seq[T8]) iter.Seq[lo.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]"
- "func CrossJoin9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5], list6 iter.Seq[T6], list7 iter.Seq[T7], list8 iter.Seq[T8], list9 iter.Seq[T9]) iter.Seq[lo.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]"
playUrl: https://go.dev/play/p/OFe8xjZFjWU
variantHelpers:
- it#tuple#crossjoinx
similarHelpers:
- core#tuple#crossjoinx
- it#tuple#zipx
- it#tuple#crossjoinbyx
position: 10
---
Combines every item from multiple lists (cartesian product). The resulting sequence contains all possible combinations of elements from each input sequence.
The cartesian product means every element from the first sequence is paired with every element from the second sequence, then those pairs are combined with every element from the third sequence, and so on.
```go
seq1 := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
}
seq2 := func(yield func(string) bool) {
_ = yield("a")
_ = yield("b")
}
cross := it.CrossJoin2(seq1, seq2)
var result []string
for tuple := range cross {
result = append(result, fmt.Sprintf("%d%s", tuple.A, tuple.B))
}
// result contains ["1a", "1b", "2a", "2b"] (all 4 combinations in lexical order)
```
Example with 3 sequences:
```go
numbers := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
}
letters := func(yield func(string) bool) {
_ = yield("a")
_ = yield("b")
}
colors := func(yield func(string) bool) {
_ = yield("red")
_ = yield("blue")
}
cross := it.CrossJoin3(numbers, letters, colors)
var result []string
for tuple := range cross {
result = append(result, fmt.Sprintf("%d%s%s", tuple.A, tuple.B, tuple.C))
}
// result contains 8 combinations: ["1ared", "1ablue", "1bred", "1bblue", "2ared", "2ablue", "2bred", "2bblue"]
```
================================================
FILE: docs/data/it-cutprefix.md
================================================
---
name: CutPrefix
slug: cutprefix
sourceRef: it/seq.go#L778
category: it
subCategory: string
signatures:
- "func CutPrefix[T comparable, I ~func(func(T) bool)](collection I, separator []T) (after I, found bool)"
playUrl: https://go.dev/play/p/bPnV39zVnAV
variantHelpers: []
similarHelpers:
- core#string#cutprefix
position: 260
---
CutPrefix returns collection without the provided leading prefix and reports whether it found the prefix.
If collection doesn't start with prefix, CutPrefix returns collection, false.
If prefix is empty, CutPrefix returns collection, true.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
after, found := it.CutPrefix(collection, []int{1, 2})
var result []int
for item := range after {
result = append(result, item)
}
// result contains [3, 4], found is true
after2, found2 := it.CutPrefix(collection, []int{9, 10})
var result2 []int
for item := range after2 {
result2 = append(result2, item)
}
// result2 contains [1, 2, 3, 4], found2 is false
```
================================================
FILE: docs/data/it-cutsuffix.md
================================================
---
name: CutSuffix
slug: cutsuffix
sourceRef: it/seq.go#L778
category: it
subCategory: string
signatures:
- "func CutSuffix[T comparable, I ~func(func(T) bool)](collection I, separator []T) (before I, found bool)"
playUrl: https://go.dev/play/p/CTRh9m1UHrZ
variantHelpers: []
similarHelpers:
- core#string#cutsuffix
position: 261
---
CutSuffix returns collection without the provided ending suffix and reports whether it found the suffix.
If collection doesn't end with suffix, CutSuffix returns collection, false.
If suffix is empty, CutSuffix returns collection, true.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
before, found := it.CutSuffix(collection, []int{3, 4})
var result []int
for item := range before {
result = append(result, item)
}
// result contains [1, 2], found is true
```
================================================
FILE: docs/data/it-drain.md
================================================
---
name: Drain
slug: drain
sourceRef: it/seq.go#L26
category: it
subCategory: sequence
signatures:
- "func Drain[T any](collection iter.Seq[T])"
playUrl: https://go.dev/play/p/xU_GCG861r1
variantHelpers: []
similarHelpers: []
position: 170
---
Drain consumes an entire sequence.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
fmt.Println("yielding")
}
it.Drain(collection)
// prints "yielding" three times, sequence is consumed
```
================================================
FILE: docs/data/it-drop.md
================================================
---
name: Drop
slug: drop
sourceRef: it/seq.go#L498
category: it
subCategory: sequence
signatures:
- "func Drop[T any, I ~func(func(T) bool)](collection I, n int) I"
playUrl: https://go.dev/play/p/O1J1-uWc3z9
variantHelpers:
- it#sequence#drop
similarHelpers:
- core#slice#drop
- it#sequence#droplast
position: 100
---
Drops n elements from the beginning of a sequence.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(5)
}
dropped := it.Drop(seq, 2)
var result []int
for v := range dropped {
result = append(result, v)
}
// result contains 3, 4, 5
```
================================================
FILE: docs/data/it-dropbyindex.md
================================================
---
name: DropByIndex
slug: dropbyindex
sourceRef: it/seq.go#L581
category: it
subCategory: sequence
signatures:
- "func DropByIndex[T any, I ~func(func(T) bool)](collection I, indexes ...int) I"
playUrl: https://go.dev/play/p/vPbrZYgiU4q
variantHelpers:
- it#slice#drop
similarHelpers:
- core#slice#dropbyindex
- core#slice#withoutnth
position: 55
---
Removes elements from a collection at the specified indexes.
```go
result := it.DropByIndex(it.Range(1, 6), 1, 3)
// [1, 3, 5]
```
================================================
FILE: docs/data/it-droplast.md
================================================
---
name: DropLast
slug: droplast
sourceRef: it/seq.go#L512
category: it
subCategory: sequence
signatures:
- "func DropLast[T any, I ~func(func(T) bool)](collection I, n int) I"
variantHelpers:
- it#sequence#droplast
similarHelpers:
- it#sequence#drop
- it#sequence#dropwhile
- it#sequence#droplastwhile
- it#sequence#trim
- it#sequence#trimsuffix
position: 78
---
Drops the last n elements from a sequence. Returns a new sequence without the specified number of trailing elements.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
result := it.DropLast(seq, 2)
// iter.Seq[int] yielding 1, 2, 3
result = it.DropLast(seq, 0)
// iter.Seq[int] yielding 1, 2, 3, 4, 5 (unchanged)
result = it.DropLast(seq, 10)
// iter.Seq[int] yielding nothing (all elements dropped)
seq = func(yield func(string) bool) {
yield("a")
yield("b")
yield("c")
}
result = it.DropLast(seq, 1)
// iter.Seq[string] yielding "a", "b"
```
================================================
FILE: docs/data/it-droplastwhile.md
================================================
---
name: DropLastWhile
slug: droplastwhile
sourceRef: it/seq.go#L180
category: it
subCategory: sequence
signatures:
- "func DropLastWhile[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I"
variantHelpers: []
similarHelpers:
- core#slice#droplastwhile
position: 163
---
DropLastWhile drops elements from the end of a sequence while the predicate returns true.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
filtered := it.DropLastWhile(collection, func(x int) bool {
return x > 3
})
var result []int
for item := range filtered {
result = append(result, item)
}
// result contains [1, 2, 3]
```
================================================
FILE: docs/data/it-dropslice.md
================================================
---
name: DropLast
slug: dropslice
sourceRef: it/seq.go#L510
category: it
subCategory: slice
signatures:
- "func DropLast[T any, I ~func(func(T) bool)](collection I, n int) I"
- "func DropByIndex[T any, I ~func(func(T) bool)](collection I, indexes ...int) I"
- "func Subset[T any, I ~func(func(T) bool)](collection I, offset, length int) I"
- "func Slice[T any, I ~func(func(T) bool)](collection I, start, end int) I"
variantHelpers:
- it#slice#droplast
- it#slice#dropbyindex
- it#slice#subset
- it#slice#slice
similarHelpers:
- core#slice#drop
- core#slice#droplast
- core#slice#dropbyindex
- core#slice#subset
- core#slice#slice
position: 150
---
DropLast drops n elements from the end of a sequence.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
filtered := it.DropLast(collection, 2)
var result []int
for item := range filtered {
result = append(result, item)
}
// result contains [1, 2, 3]
```
DropByIndex drops elements from a sequence by the index.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
filtered := it.DropByIndex(collection, 1, 3)
var result []int
for item := range filtered {
result = append(result, item)
}
// result contains [1, 3, 5]
```
Subset returns a subset of a sequence from `offset` up to `length` elements.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
yield(6)
}
subset := it.Subset(collection, 2, 3)
var result []int
for item := range subset {
result = append(result, item)
}
// result contains [3, 4, 5]
```
Slice returns a subset of a sequence from `start` up to, but not including `end`.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
sliced := it.Slice(collection, 1, 4)
var result []int
for item := range sliced {
result = append(result, item)
}
// result contains [2, 3, 4]
```
================================================
FILE: docs/data/it-dropwhile.md
================================================
---
name: DropWhile
slug: dropwhile
sourceRef: it/seq.go#L180
category: it
subCategory: sequence
signatures:
- "func DropWhile[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I"
variantHelpers: []
similarHelpers:
- core#slice#dropwhile
- it#sequence#drop
position: 162
---
DropWhile drops elements from the beginning of a sequence while the predicate returns true.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
filtered := it.DropWhile(collection, func(x int) bool {
return x < 3
})
var result []int
for item := range filtered {
result = append(result, item)
}
// result contains [3, 4, 5]
```
================================================
FILE: docs/data/it-earliest.md
================================================
---
name: Earliest
slug: earliest
sourceRef: it/find.go#L278
category: it
subCategory: find
signatures:
- "func Earliest(times iter.Seq[time.Time]) time.Time"
playUrl: https://go.dev/play/p/fI6_S10H7Py
variantHelpers:
- it#find#earliest
similarHelpers:
- core#slice#earliest
position: 500
---
Searches for the earliest (minimum) time.Time in a collection.
Returns zero value when the collection is empty.
Will iterate through the entire sequence.
Examples:
```go
import "time"
// Find the earliest time from a collection
times := it.Slice([]time.Time{
time.Date(2023, 5, 15, 10, 0, 0, 0, time.UTC),
time.Date(2023, 3, 20, 14, 30, 0, 0, time.UTC),
time.Date(2023, 8, 1, 9, 15, 0, 0, time.UTC),
})
earliest := it.Earliest(times)
// earliest: 2023-03-20 14:30:00 +0000 UTC
// With empty collection
empty := it.Slice([]time.Time{})
earliest := it.Earliest(empty)
// earliest: 0001-01-01 00:00:00 +0000 UTC (zero value)
// Find earliest from parsed times
times := it.Slice([]time.Time{
time.Parse(time.RFC3339, "2023-01-01T12:00:00Z"),
time.Parse(time.RFC3339, "2023-01-01T10:00:00Z"),
time.Parse(time.RFC3339, "2023-01-01T14:00:00Z"),
})
earliest := it.Earliest(times)
// earliest: 2023-01-01 10:00:00 +0000 UTC
```
================================================
FILE: docs/data/it-earliestby.md
================================================
---
name: EarliestBy
slug: earliestby
sourceRef: it/find.go#L285
category: it
subCategory: find
signatures:
- "func EarliestBy[T any](collection iter.Seq[T], transform func(item T) time.Time) T"
playUrl: https://go.dev/play/p/y_Pf3Jmw-B4
variantHelpers:
- it#find#earliestby
similarHelpers:
- core#slice#earliestby
position: 510
---
Searches for the element with the earliest time using a transform function.
Returns zero value when the collection is empty.
Will iterate through the entire sequence.
Examples:
```go
import "time"
type Event struct {
Name string
Time time.Time
}
// Find the earliest event by time
events := it.Slice([]Event{
{"Meeting", time.Date(2023, 5, 15, 10, 0, 0, 0, time.UTC)},
{"Lunch", time.Date(2023, 5, 15, 12, 0, 0, 0, time.UTC)},
{"Breakfast", time.Date(2023, 5, 15, 8, 0, 0, 0, time.UTC)},
})
earliest := it.EarliestBy(events, func(e Event) time.Time {
return e.Time
})
// earliest: {Name: "Breakfast", Time: 2023-05-15 08:00:00 +0000 UTC}
// Find the earliest task by deadline
type Task struct {
ID int
Deadline time.Time
}
tasks := it.Slice([]Task{
{1, time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC)},
{2, time.Date(2023, 5, 15, 0, 0, 0, 0, time.UTC)},
{3, time.Date(2023, 7, 1, 0, 0, 0, 0, time.UTC)},
})
earliest := it.EarliestBy(tasks, func(t Task) time.Time {
return t.Deadline
})
// earliest: {ID: 2, Deadline: 2023-05-15 00:00:00 +0000 UTC}
```
================================================
FILE: docs/data/it-elementsmatch.md
================================================
---
name: ElementsMatch
slug: elementsmatch
sourceRef: it/intersect.go#L174
category: it
subCategory: intersect
signatures:
- "func ElementsMatch[T comparable](list1, list2 iter.Seq[T]) bool"
playUrl: "https://go.dev/play/p/24SGQm1yMRe"
variantHelpers:
- it#intersect#elementsmatch
similarHelpers:
- core#slice#elementsmatch
position: 720
---
Returns true if lists contain the same set of elements (including empty set).
If there are duplicate elements, the number of occurrences in each list should match.
The order of elements is not checked.
Will iterate through each sequence before returning and allocate a map large enough to hold all distinct elements.
Long heterogeneous input sequences can cause excessive memory usage.
Examples:
```go
// Lists with same elements in different order
list1 := it.Slice([]int{1, 2, 3, 4, 5})
list2 := it.Slice([]int{5, 4, 3, 2, 1})
match := it.ElementsMatch(list1, list2)
// match: true
// Lists with different elements
list1 = it.Slice([]int{1, 2, 3, 4, 5})
list2 = it.Slice([]int{1, 2, 3, 4, 6})
match = it.ElementsMatch(list1, list2)
// match: false (5 vs 6)
// Lists with duplicates
list1 = it.Slice([]int{1, 2, 2, 3, 4})
list2 = it.Slice([]int{4, 3, 2, 1, 2})
match = it.ElementsMatch(list1, list2)
// match: true (both have two 2's)
// Lists with different number of duplicates
list1 = it.Slice([]int{1, 2, 2, 3, 4})
list2 = it.Slice([]int{4, 3, 2, 1, 1})
match = it.ElementsMatch(list1, list2)
// match: false (list1 has two 2's, list2 has two 1's)
// Empty lists
empty1 := it.Slice([]int{})
empty2 := it.Slice([]int{})
match = it.ElementsMatch(empty1, empty2)
// match: true
// One empty, one not empty
empty := it.Slice([]int{})
nonEmpty := it.Slice([]int{1, 2, 3})
match = it.ElementsMatch(empty, nonEmpty)
// match: false
// String lists
words1 := it.Slice([]string{"hello", "world", "go"})
words2 := it.Slice([]string{"go", "hello", "world"})
match := it.ElementsMatch(words1, words2)
// match: true
words1 = it.Slice([]string{"hello", "world", "go"})
words2 = it.Slice([]string{"go", "hello", "golang"})
match = it.ElementsMatch(words1, words2)
// match: false
// Struct lists
type Person struct {
Name string
Age int
}
people1 := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
people2 := it.Slice([]Person{
{Name: "Charlie", Age: 35},
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
})
match := it.ElementsMatch(people1, people2)
// match: true
// Different lengths
list1 = it.Slice([]int{1, 2, 3})
list2 = it.Slice([]int{1, 2, 3, 4})
match = it.ElementsMatch(list1, list2)
// match: false
// Same elements but different counts
list1 = it.Slice([]int{1, 1, 2, 3})
list2 = it.Slice([]int{1, 2, 2, 3})
match = it.ElementsMatch(list1, list2)
// match: false (list1 has two 1's, list2 has two 2's)
// Boolean values
bools1 := it.Slice([]bool{true, false, true})
bools2 := it.Slice([]bool{true, true, false})
match := it.ElementsMatch(bools1, bools2)
// match: true
// Different boolean counts
bools1 = it.Slice([]bool{true, false, true})
bools2 = it.Slice([]bool{true, false, false})
match = it.ElementsMatch(bools1, bools2)
// match: false
// Lists with same single element
list1 = it.Slice([]int{42})
list2 = it.Slice([]int{42})
match = it.ElementsMatch(list1, list2)
// match: true
// Lists with different single elements
list1 = it.Slice([]int{42})
list2 = it.Slice([]int{43})
match = it.ElementsMatch(list1, list2)
// match: false
```
================================================
FILE: docs/data/it-elementsmatchby.md
================================================
---
name: ElementsMatchBy
slug: elementsmatchby
sourceRef: it/intersect.go#L183
category: it
subCategory: intersect
signatures:
- "func ElementsMatchBy[T any, K comparable](list1, list2 iter.Seq[T], transform func(item T) K) bool"
playUrl: "https://go.dev/play/p/I3vFrmQo43E"
variantHelpers:
- it#intersect#elementsmatchby
similarHelpers:
- core#slice#elementsmatchby
position: 730
---
Returns true if lists contain the same set of elements' keys (including empty set).
If there are duplicate keys, the number of occurrences in each list should match.
The order of elements is not checked.
Will iterate through each sequence before returning and allocate a map large enough to hold all distinct transformed elements.
Long heterogeneous input sequences can cause excessive memory usage.
Examples:
```go
// Match people by age (ignoring names)
type Person struct {
Name string
Age int
}
people1 := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
people2 := it.Slice([]Person{
{Name: "David", Age: 35},
{Name: "Eve", Age: 25},
{Name: "Frank", Age: 30},
})
match := it.ElementsMatchBy(people1, people2, func(p Person) int { return p.Age })
// match: true (both have ages 25, 30, 35)
// Match by string length
words1 := it.Slice([]string{"hello", "world", "go", "lang"})
words2 := it.Slice([]string{"short", "longer", "golang", "python"})
match = it.ElementsMatchBy(words1, words2, func(s string) int { return len(s) })
// match: false (lengths: 5,5,2,4 vs 6,6,6,6)
// Match by first character
items1 := it.Slice([]string{"apple", "apricot", "banana", "blueberry"})
items2 := it.Slice([]string{"ant", "anchor", "boat", "berry"})
match = it.ElementsMatchBy(items1, items2, func(s string) byte { return s[0] })
// match: true (both start with a, a, b, b)
// Match emails by domain
type Email struct {
Address string
}
emails1 := it.Slice([]Email{
{Address: "user1@example.com"},
{Address: "user2@gmail.com"},
{Address: "user3@example.com"},
})
emails2 := it.Slice([]Email{
{Address: "different@gmail.com"},
{Address: "another@example.com"},
{Address: "third@example.com"},
})
match = it.ElementsMatchBy(emails1, emails2, func(e Email) string {
parts := strings.Split(e.Address, "@")
if len(parts) > 1 {
return parts[1]
}
return ""
})
// match: true (both have domains: example.com, gmail.com, example.com)
// Match by modulo operation
numbers1 := it.Slice([]int{1, 2, 3, 4, 5, 6})
numbers2 := it.Slice([]int{7, 8, 9, 10, 11, 12})
match = it.ElementsMatchBy(numbers1, numbers2, func(n int) int { return n % 3 })
// match: true (remainders: 1,2,0,1,2,0 vs 1,2,0,1,2,0)
// Match by case-insensitive strings
strings1 := it.Slice([]string{"Hello", "World", "GO"})
strings2 := it.Slice([]string{"hello", "world", "go"})
match = it.ElementsMatchBy(strings1, strings2, func(s string) string { return strings.ToLower(s) })
// match: true
// Match orders by customer ID (ignoring order details)
type Order struct {
ID string
CustomerID string
ProductID string
}
orders1 := it.Slice([]Order{
{ID: "1", CustomerID: "A", ProductID: "X"},
{ID: "2", CustomerID: "B", ProductID: "Y"},
{ID: "3", CustomerID: "A", ProductID: "Z"},
})
orders2 := it.Slice([]Order{
{ID: "4", CustomerID: "B", ProductID: "W"},
{ID: "5", CustomerID: "A", ProductID: "V"},
{ID: "6", CustomerID: "A", ProductID: "U"},
})
match = it.ElementsMatchBy(orders1, orders2, func(o Order) string { return o.CustomerID })
// match: true (both have customer IDs: A, B, A)
// Match dates by year-month (ignoring day)
import "time"
dates1 := it.Slice([]time.Time{
time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
time.Date(2023, 2, 20, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 25, 0, 0, 0, 0, time.UTC),
})
dates2 := it.Slice([]time.Time{
time.Date(2023, 1, 5, 0, 0, 0, 0, time.UTC),
time.Date(2023, 2, 10, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 30, 0, 0, 0, 0, time.UTC),
})
match = it.ElementsMatchBy(dates1, dates2, func(t time.Time) string {
return fmt.Sprintf("%d-%02d", t.Year(), t.Month())
})
// match: true (both have: 2023-01, 2023-02, 2023-01)
// Match by category function
type Product struct {
Name string
Price float64
}
products1 := it.Slice([]Product{
{Name: "Book", Price: 19.99},
{Name: "Pen", Price: 1.99},
{Name: "Laptop", Price: 999.99},
})
products2 := it.Slice([]Product{
{Name: "Pencil", Price: 0.99},
{Name: "Phone", Price: 699.99},
{Name: "Desk", Price: 199.99},
})
match = it.ElementsMatchBy(products1, products2, func(p Product) string {
if p.Price < 10 {
return "cheap"
} else if p.Price < 100 {
return "medium"
}
return "expensive"
})
// match: true (both have: medium, cheap, expensive)
// Match by custom classification
type Student struct {
Name string
Age int
}
students1 := it.Slice([]Student{
{Name: "Alice", Age: 8},
{Name: "Bob", Age: 15},
{Name: "Charlie", Age: 20},
})
students2 := it.Slice([]Student{
{Name: "Diana", Age: 25},
{Name: "Eve", Age: 10},
{Name: "Frank", Age: 12},
})
match = it.ElementsMatchBy(students1, students2, func(s Student) string {
if s.Age < 12 {
return "child"
} else if s.Age < 18 {
return "teen"
}
return "adult"
})
// match: false (students1: child, teen, adult vs students2: adult, child, child)
```
================================================
FILE: docs/data/it-empty.md
================================================
---
name: Empty
slug: empty
sourceRef: it/type_manipulation.go#L44
category: it
subCategory: type
signatures:
- "func Empty[T any]() iter.Seq[T]"
playUrl: "https://go.dev/play/p/E5fF1hH8Bc3"
variantHelpers:
- it#type#empty
similarHelpers:
- it#type#isempty
- it#type#isnotempty
position: 0
---
Returns an empty sequence of the specified type.
Examples:
```go
emptySeq := it.Empty[int]()
count := 0
for range emptySeq {
count++
}
// count == 0
```
```go
emptySeq := it.Empty[string]()
var result []string
for v := range emptySeq {
result = append(result, v)
}
// result is empty slice
```
================================================
FILE: docs/data/it-entries.md
================================================
---
name: Entries
slug: entries
sourceRef: it/map.go#L77
category: it
subCategory: map
signatures:
- "func Entries[K comparable, V any](in ...map[K]V) iter.Seq2[K, V]"
playUrl: https://go.dev/play/p/ckLxqTE9KCz
variantHelpers:
- it#map#entries
similarHelpers:
- core#slice#entries
- it#map#fromentries
- it#map#topairs
position: 20
---
Transforms a map into a sequence of key/value pairs. Accepts multiple maps and concatenates their entries.
Examples:
```go
m := map[string]int{
"apple": 1,
"banana": 2,
"cherry": 3,
}
entriesSeq := it.Entries(m)
var keys []string
var values []int
for k, v := range entriesSeq {
keys = append(keys, k)
values = append(values, v)
}
// keys contains map keys, values contains corresponding values
```
================================================
FILE: docs/data/it-every.md
================================================
---
name: Every
slug: every
sourceRef: it/intersect.go#L25
category: it
subCategory: intersect
signatures:
- "func Every[T comparable](collection iter.Seq[T], subset ...T) bool"
playUrl: "https://go.dev/play/p/rwM9Y353aIC"
variantHelpers:
- it#intersect#every
similarHelpers:
- core#slice#every
- it#intersect#some
- it#intersect#none
position: 30
---
Returns true if all elements of a subset are contained in a collection.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(5)
}
hasAll := it.Every(seq, 2, 4)
// hasAll == true
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
hasAll := it.Every(seq, "apple", "orange")
// hasAll == false (orange is not in collection)
```
================================================
FILE: docs/data/it-everyby.md
================================================
---
name: EveryBy
slug: everyby
sourceRef: it/intersect.go#L43
category: it
subCategory: intersect
signatures:
- "func EveryBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool"
playUrl: https://go.dev/play/p/7OvV1BRWsER
variantHelpers:
- it#intersect#everyby
similarHelpers:
- core#slice#everyby
position: 660
---
Returns true if the predicate returns true for all elements in the collection or if the collection is empty.
Will iterate through the entire sequence if predicate never returns false.
Examples:
```go
// Check if all numbers are positive
numbers := it.Slice([]int{1, 3, 5, 7, 9})
allPositive := it.EveryBy(numbers, func(n int) bool { return n > 0 })
// allPositive: true
numbers = it.Slice([]int{1, -3, 5, 7, 9})
allPositive = it.EveryBy(numbers, func(n int) bool { return n > 0 })
// allPositive: false
// Check if all strings have minimum length
words := it.Slice([]string{"hello", "world", "go", "lang"})
allLongEnough := it.EveryBy(words, func(s string) bool { return len(s) >= 2 })
// allLongEnough: true
allVeryLong := it.EveryBy(words, func(s string) bool { return len(s) >= 5 })
// allVeryLong: false
// Check if all people are adults
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
allAdults := it.EveryBy(people, func(p Person) bool { return p.Age >= 18 })
// allAdults: true
minors := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 15}, // Not adult
{Name: "Charlie", Age: 35},
})
allAdults = it.EveryBy(minors, func(p Person) bool { return p.Age >= 18 })
// allAdults: false
// Check if all numbers are even
numbers = it.Slice([]int{2, 4, 6, 8, 10})
allEven := it.EveryBy(numbers, func(n int) bool { return n%2 == 0 })
// allEven: true
numbers = it.Slice([]int{2, 4, 6, 7, 10}) // 7 is odd
allEven = it.EveryBy(numbers, func(n int) bool { return n%2 == 0 })
// allEven: false
// Check if all strings are lowercase
strings := it.Slice([]string{"hello", "world", "go", "lang"})
allLowercase := it.EveryBy(strings, func(s string) bool { return s == strings.ToLower(s) })
// allLowercase: true
strings = it.Slice([]string{"hello", "World", "go", "lang"}) // "World" has uppercase
allLowercase = it.EveryBy(strings, func(s string) bool { return s == strings.ToLower(s) })
// allLowercase: false
// Empty collection returns true
empty := it.Slice([]int{})
allPositive := it.EveryBy(empty, func(n int) bool { return n > 0 })
// allPositive: true
// Check if all emails are valid
emails := it.Slice([]string{"user@example.com", "test@domain.org", "admin@site.net"})
allValid := it.EveryBy(emails, func(email string) bool {
return strings.Contains(email, "@") && strings.Contains(email, ".")
})
// allValid: true
emails = it.Slice([]string{"user@example.com", "invalid-email", "test@domain.org"})
allValid = it.EveryBy(emails, func(email string) bool {
return strings.Contains(email, "@") && strings.Contains(email, ".")
})
// allValid: false
```
================================================
FILE: docs/data/it-fill.md
================================================
---
name: Fill
slug: fill
sourceRef: it/seq.go#L26
category: it
subCategory: sequence
signatures:
- "func Fill[T lo.Clonable[T], I ~func(func(T) bool)](collection I, initial T) I"
variantHelpers: []
playUrl: https://go.dev/play/p/mHShWq5ezMc
similarHelpers:
- core#slice#fill
position: 174
---
Fill replaces elements of a sequence with `initial` value.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
}
filled := it.Fill(collection, 99)
var result []int
for item := range filled {
result = append(result, item)
}
// result contains [99, 99, 99]
```
================================================
FILE: docs/data/it-filter.md
================================================
---
name: Filter
slug: filter
sourceRef: it/seq.go#L33
category: it
subCategory: sequence
signatures:
- "func Filter[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I"
- "func FilterI[T any, I ~func(func(T) bool)](collection I, predicate func(item T, index int) bool) I"
playUrl: "https://go.dev/play/p/psenko2KKsX"
variantHelpers:
- it#sequence#filter
- it#sequence#filteri
similarHelpers:
- core#slice#filter
- core#slice#filteri
- it#sequence#reject
position: 10
---
Returns a sequence of all elements for which the predicate function returns true.
### Filter
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(5)
}
filtered := it.Filter(seq, func(x int) bool {
return x%2 == 0
})
var result []int
for v := range filtered {
result = append(result, v)
}
// result contains 2, 4 (even numbers)
```
### FilterI
FilterI iterates over elements of collection, returning a sequence of all elements predicate returns true for. The predicate function includes the index.
```go
result := it.FilterI(it.Range(1, 6), func(item int, index int) bool {
return item%2 == 0 && index > 1
})
var filtered []int
for v := range result {
filtered = append(filtered, v)
}
// filtered contains [4, 6]
```
================================================
FILE: docs/data/it-filterkeys.md
================================================
---
name: FilterKeys
slug: filterkeys
sourceRef: it/map.go#L238
category: it
subCategory: map
signatures:
- "func FilterKeys[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []K"
variantHelpers:
- it#map#filterkeys
similarHelpers:
- core#map#filterkeys
- it#map#filtervalues
- it#map#keys
- it#map#values
position: 56
---
Filters map keys based on a predicate function that takes both key and value. Returns a slice of keys that satisfy the predicate.
```go
m := map[string]int{
"apple": 3,
"banana": 5,
"cherry": 2,
"date": 0,
}
result := it.FilterKeys(m, func(key string, value int) bool {
return value > 2
})
// []string{"apple", "banana"} (keys with values > 2)
numberMap := map[int]string{1: "one", 2: "two", 3: "three", 4: "four"}
result = it.FilterKeys(numberMap, func(key int, value string) bool {
return len(value) == 3
})
// []int{1, 2, 3} (keys where value length is 3)
personMap := map[string]int{"alice": 25, "bob": 30, "charlie": 17}
result = it.FilterKeys(personMap, func(key string, age int) bool {
return strings.HasPrefix(key, "a") && age >= 20
})
// []string{"alice"} (keys starting with "a" and age >= 20)
emptyMap := map[string]int{}
result = it.FilterKeys(emptyMap, func(key string, value int) bool {
return true
})
// []string{} (empty map)
```
================================================
FILE: docs/data/it-filtermap.md
================================================
---
name: FilterMap
slug: filtermap
sourceRef: it/seq.go#L86
category: it
subCategory: sequence
signatures:
- "func FilterMap[T, R any](collection iter.Seq[T], callback func(item T) (R, bool)) iter.Seq[R]"
- "func FilterMapI[T, R any](collection iter.Seq[T], callback func(item T, index int) (R, bool)) iter.Seq[R]"
variantHelpers:
- it#sequence#filtermap
- it#sequence#filtermapi
similarHelpers:
- it#sequence#map
- it#sequence#filter
- it#sequence#filtermaptoslice
- it#sequence#rejectmap
position: 40
---
Maps elements of a sequence to new values and filters out elements where the callback returns false. Only elements where the second return value is true are included in the result.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
result := it.FilterMap(seq, func(x int) (string, bool) {
if x%2 == 0 {
return fmt.Sprintf("even-%d", x), true
}
return "", false
})
// iter.Seq[string] yielding "even-2", "even-4"
seq = func(yield func(string) bool) {
yield("a")
yield("")
yield("c")
yield("d")
}
result = it.FilterMap(seq, func(s string) (int, bool) {
if s != "" {
return len(s), true
}
return 0, false
})
// iter.Seq[int] yielding 1, 1, 1 (length of "a", "c", "d")
```
### FilterMapI
Maps elements of a sequence to new values and filters out elements where the callback returns false. The callback receives both the item and its index.
```go
seq := func(yield func(string) bool) {
yield("apple")
yield("banana")
yield("cherry")
}
result := it.FilterMapI(seq, func(s string, index int) (string, bool) {
if index%2 == 0 {
return fmt.Sprintf("%s-%d", s, index), true
}
return "", false
})
// iter.Seq[string] yielding "apple-0", "cherry-2"
```
================================================
FILE: docs/data/it-filtermaptoseq.md
================================================
---
name: FilterMapToSeq
slug: filtermaptoseq
sourceRef: it/map.go#L178
category: it
subCategory: map
signatures:
- "func FilterMapToSeq[K comparable, V, R any](in map[K]V, transform func(key K, value V) (R, bool)) iter.Seq[R]"
variantHelpers:
- it#map#filtermaptoseq
similarHelpers:
- it#map#maptoseq
- core#map#filtermaptoslice
- it#map#filterkeys
- it#map#filtervalues
position: 54
---
Transforms a map into a sequence by applying a transform function to each key-value pair, but only includes values where the transform function returns true as the second value.
```go
m := map[string]int{
"apple": 3,
"banana": 0,
"cherry": 2,
"date": 0,
}
result := it.FilterMapToSeq(m, func(key string, value int) (string, bool) {
if value > 0 {
return fmt.Sprintf("%s:%d", key, value), true
}
return "", false
})
// iter.Seq[string] yielding "apple:3", "cherry:2" (only entries with value > 0)
personMap := map[string]int{"alice": 25, "bob": 30, "charlie": 15}
type Person struct {
Name string
Age int
}
result = it.FilterMapToSeq(personMap, func(name string, age int) (Person, bool) {
person := Person{Name: name, Age: age}
return person, age >= 18
})
// iter.Seq[Person] yielding {Name: "alice", Age: 25}, {Name: "bob", Age: 30} (only adults)
dataMap := map[string]float64{"a": 1.5, "b": -2.0, "c": 3.14}
result = it.FilterMapToSeq(dataMap, func(key string, value float64) (int, bool) {
if value > 0 {
return int(value * 100), true
}
return 0, false
})
// iter.Seq[int] yielding 150, 314 (1.5*100, 3.14*100 rounded)
```
================================================
FILE: docs/data/it-filtervalues.md
================================================
---
name: FilterValues
slug: filtervalues
sourceRef: it/map.go#L253
category: it
subCategory: map
signatures:
- "func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []V"
variantHelpers:
- it#map#filtervalues
similarHelpers:
- core#map#filtervalues
- it#map#filterkeys
- it#map#keys
- it#map#values
position: 58
---
Filters map values based on a predicate function that takes both key and value. Returns a slice of values that satisfy the predicate.
```go
m := map[string]int{
"apple": 3,
"banana": 5,
"cherry": 2,
"date": 0,
}
result := it.FilterValues(m, func(key string, value int) bool {
return value > 2
})
// []int{3, 5} (values > 2, corresponds to "apple" and "banana")
numberMap := map[int]string{1: "one", 2: "two", 3: "three", 4: "four"}
result = it.FilterValues(numberMap, func(key int, value string) bool {
return len(value) == 3
})
// []string{"one", "two", "three"} (values with length 3)
personMap := map[string]int{"alice": 25, "bob": 30, "charlie": 17}
result = it.FilterValues(personMap, func(key string, age int) bool {
return strings.HasPrefix(key, "a") && age >= 20
})
// []int{25} (value for "alice" only)
emptyMap := map[string]int{}
result = it.FilterValues(emptyMap, func(key string, value int) bool {
return true
})
// []int{} (empty map)
dataMap := map[string]float64{"a": 1.5, "b": -2.0, "c": 0.0, "d": 3.14}
result = it.FilterValues(dataMap, func(key string, value float64) bool {
return value > 0
})
// []float64{1.5, 3.14} (positive values only)
```
================================================
FILE: docs/data/it-find.md
================================================
---
name: Find
slug: find
sourceRef: it/find.go#L105
category: it
subCategory: find
signatures:
- "func Find[T any](collection iter.Seq[T], predicate func(item T) bool) (T, bool)"
playUrl: https://go.dev/play/p/4w28pF_l58a
variantHelpers:
- it#find#find
similarHelpers:
- core#slice#find
- it#find#findindexof
- it#find#findorelse
position: 40
---
Searches for an element in a sequence based on a predicate function. Returns the element and true if found, zero value and false if not found.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
_ = yield(40)
}
found, ok := it.Find(seq, func(x int) bool {
return x > 25
})
// found == 30, ok == true
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
found, ok := it.Find(seq, func(s string) bool {
return len(s) > 10
})
// found == "", ok == false (no element longer than 10 chars)
```
================================================
FILE: docs/data/it-findduplicates.md
================================================
---
name: FindDuplicates
slug: findduplicates
sourceRef: it/find.go#L196
category: it
subCategory: find
signatures:
- "func FindDuplicates[T comparable, I ~func(func(T) bool)](collection I) I"
playUrl: https://go.dev/play/p/dw-VLQXKijT
variantHelpers:
- it#find#findduplicates
similarHelpers:
- core#slice#findduplicates
- it#find#finduniques
- it#find#findduplicatesby
position: 90
---
Returns the first occurrence of each duplicated element in the collection (elements that appear more than once).
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(4)
_ = yield(4)
}
dupSeq := it.FindDuplicates(seq)
var result []int
for v := range dupSeq {
result = append(result, v)
}
// result contains 2, 4 (first occurrence of each duplicated element)
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("apple")
_ = yield("cherry")
_ = yield("banana")
}
dupSeq := it.FindDuplicates(seq)
var result []string
for v := range dupSeq {
result = append(result, v)
}
// result contains "apple", "banana" (duplicated elements)
```
================================================
FILE: docs/data/it-findduplicatesby.md
================================================
---
name: FindDuplicatesBy
slug: findduplicatesby
sourceRef: it/find.go#L200
category: it
subCategory: find
signatures:
- "func FindDuplicatesBy[T any, U comparable, I ~func(func(T) bool)](collection I, transform func(item T) U) I"
playUrl: https://go.dev/play/p/tm1tZdC93OH
variantHelpers:
- it#find#findduplicatesby
similarHelpers:
- core#slice#findduplicatesby
position: 640
---
Returns a sequence with the first occurrence of each duplicated element in the collection, based on a transform function.
The order of result values is determined by the order duplicates occur in the sequence. A transform function is
invoked for each element in the sequence to generate the criterion by which uniqueness is computed.
Will allocate a map large enough to hold all distinct transformed elements.
Long heterogeneous input sequences can cause excessive memory usage.
Examples:
```go
// Find duplicate people by age
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 30}, // Same age as Alice - Alice is returned
{Name: "Diana", Age: 30}, // Same age as Alice - already marked as duplicate
{Name: "Eve", Age: 25}, // Same age as Bob - Bob is returned
})
duplicates := it.FindDuplicatesBy(people, func(p Person) int { return p.Age })
// duplicates: sequence with Alice (age 30) and Bob (age 25)
// Find duplicate strings by length
words := it.Slice([]string{"hello", "world", "hi", "go", "bye", "yes"})
duplicates := it.FindDuplicatesBy(words, func(s string) int { return len(s) })
// duplicates: sequence with "hello" (length 5, also "world" has length 5)
// and "hi" (length 2, also "go", "yes" have length 2)
// Find duplicate items by first letter
items := it.Slice([]string{"apple", "apricot", "banana", "blueberry", "cherry", "cranberry"})
duplicates := it.FindDuplicatesBy(items, func(s string) byte { return s[0] })
// duplicates: sequence with "apple" (starts with 'a', also "apricot"),
// "banana" (starts with 'b', also "blueberry"),
// "cherry" (starts with 'c', also "cranberry")
// Find duplicate numbers by modulo
numbers := it.Slice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12})
duplicates := it.FindDuplicatesBy(numbers, func(n int) int { return n % 3 })
// duplicates: sequence with 1, 2, 3 (remainders 1, 2, 0 appear multiple times)
// Find duplicate structs by composite key
type Order struct {
CustomerID string
ProductID string
}
orders := it.Slice([]Order{
{CustomerID: "A", ProductID: "1"},
{CustomerID: "A", ProductID: "2"},
{CustomerID: "B", ProductID: "1"}, // Same customer as first
{CustomerID: "C", ProductID: "3"},
{CustomerID: "A", ProductID: "3"}, // Same customer as first two
})
duplicates := it.FindDuplicatesBy(orders, func(o Order) string { return o.CustomerID })
// duplicates: sequence with first order {CustomerID: "A", ProductID: "1"}
// Find duplicate items by case-insensitive comparison
words := it.Slice([]string{"Hello", "hello", "WORLD", "world", "Go", "GO", "go"})
duplicates := it.FindDuplicatesBy(words, func(s string) string { return strings.ToLower(s) })
// duplicates: sequence with "Hello", "WORLD", "Go" (first occurrences of each case-insensitive duplicate)
// Find duplicate dates by year-month
import "time"
dates := it.Slice([]time.Time{
time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 20, 0, 0, 0, 0, time.UTC), // Same month as first
time.Date(2023, 2, 10, 0, 0, 0, 0, time.UTC),
time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC), // Different year
time.Date(2023, 2, 25, 0, 0, 0, 0, time.UTC), // Same month as third
})
duplicates := it.FindDuplicatesBy(dates, func(t time.Time) string {
return fmt.Sprintf("%d-%02d", t.Year(), t.Month())
})
// duplicates: sequence with first January date and first February date
// Find duplicate emails by domain
type Email struct {
Address string
}
emails := it.Slice([]Email{
{Address: "user1@example.com"},
{Address: "user2@example.com"}, // Same domain as first
{Address: "user3@gmail.com"},
{Address: "user4@example.com"}, // Same domain as first two
{Address: "user5@yahoo.com"},
})
duplicates := it.FindDuplicatesBy(emails, func(e Email) string {
parts := strings.Split(e.Address, "@")
if len(parts) > 1 {
return parts[1]
}
return ""
})
// duplicates: sequence with first email {Address: "user1@example.com"}
```
================================================
FILE: docs/data/it-findindexof.md
================================================
---
name: FindIndexOf
slug: findindexof
sourceRef: it/find.go#L112
category: it
subCategory: find
signatures:
- "func FindIndexOf[T any](collection iter.Seq[T], predicate func(item T) bool) (T, int, bool)"
playUrl: https://go.dev/play/p/ihchBAEkhXO
variantHelpers:
- it#find#findindexof
similarHelpers:
- core#slice#findindexof
- it#find#find
- it#find#findlastindexof
position: 50
---
Searches for an element based on a predicate and returns the element, its index, and true if found. Returns zero value, -1, and false if not found.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
_ = yield(40)
}
found, index, ok := it.FindIndexOf(seq, func(x int) bool {
return x > 25
})
// found == 30, index == 2, ok == true
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
found, index, ok := it.FindIndexOf(seq, func(s string) bool {
return s == "orange"
})
// found == "", index == -1, ok == false
```
================================================
FILE: docs/data/it-findlastindexof.md
================================================
---
name: FindLastIndexOf
slug: findlastindexof
sourceRef: it/find.go#L127
category: it
subCategory: find
signatures:
- "func FindLastIndexOf[T any](collection iter.Seq[T], predicate func(item T) bool) (T, int, bool)"
playUrl: https://go.dev/play/p/ezz6hXaC4Md
variantHelpers:
- it#find#findlastindexof
similarHelpers:
- core#slice#findlastindexof
- it#find#findindexof
- it#find#find
position: 60
---
Searches for the last element matching a predicate and returns the element, its index, and true if found. Returns zero value, -1, and false if not found.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
_ = yield(20)
_ = yield(40)
}
found, index, ok := it.FindLastIndexOf(seq, func(x int) bool {
return x == 20
})
// found == 20, index == 3, ok == true
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
found, index, ok := it.FindLastIndexOf(seq, func(s string) bool {
return len(s) > 10
})
// found == "", index == -1, ok == false
```
================================================
FILE: docs/data/it-findorelse.md
================================================
---
name: FindOrElse
slug: findorelse
sourceRef: it/find.go#L147
category: it
subCategory: find
signatures:
- "func FindOrElse[T any](collection iter.Seq[T], fallback T, predicate func(item T) bool) T"
playUrl: https://go.dev/play/p/1harvaiGMfI
variantHelpers:
- it#find#findorelse
similarHelpers:
- core#slice#findorelse
- it#find#find
position: 70
---
Searches for an element using a predicate or returns a fallback value if not found.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
_ = yield(40)
}
result := it.FindOrElse(seq, 99, func(x int) bool {
return x > 25
})
// result == 30
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
result := it.FindOrElse(seq, "unknown", func(s string) bool {
return len(s) > 10
})
// result == "unknown" (fallback value)
```
================================================
FILE: docs/data/it-finduniques.md
================================================
---
name: FindUniques
slug: finduniques
sourceRef: it/find.go#L159
category: it
subCategory: find
signatures:
- "func FindUniques[T comparable, I ~func(func(T) bool)](collection I) I"
playUrl: https://go.dev/play/p/O8dwXEbT56F
variantHelpers:
- it#find#finduniques
similarHelpers:
- core#slice#finduniques
- it#find#findduplicates
- it#find#finduniquesby
position: 80
---
Returns a sequence with elements that appear only once in the original collection (duplicates are removed).
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(4)
}
uniqueSeq := it.FindUniques(seq)
var result []int
for v := range uniqueSeq {
result = append(result, v)
}
// result contains 1, 3 (elements that appear only once)
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("apple")
_ = yield("cherry")
}
uniqueSeq := it.FindUniques(seq)
var result []string
for v := range uniqueSeq {
result = append(result, v)
}
// result contains "banana", "cherry" (unique elements)
```
================================================
FILE: docs/data/it-finduniquesby.md
================================================
---
name: FindUniquesBy
slug: finduniquesby
sourceRef: it/find.go#L163
category: it
subCategory: find
signatures:
- "func FindUniquesBy[T any, U comparable, I ~func(func(T) bool)](collection I, transform func(item T) U) I"
playUrl: https://go.dev/play/p/TiwGIzeDuML
variantHelpers:
- it#find#finduniquesby
similarHelpers:
- core#slice#finduniquesby
position: 630
---
Returns a sequence with all the elements that appear in the collection only once, based on a transform function.
The order of result values is determined by the order they occur in the collection. A transform function is
invoked for each element in the sequence to generate the criterion by which uniqueness is computed.
Will iterate through the entire sequence before yielding and allocate a map large enough to hold all distinct transformed elements.
Long heterogeneous input sequences can cause excessive memory usage.
Examples:
```go
// Find unique people by age
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 30}, // Same age as Alice, so Alice is not unique
{Name: "Diana", Age: 28},
})
uniques := it.FindUniquesBy(people, func(p Person) int { return p.Age })
// uniques: sequence with Bob (age 25) and Diana (age 28)
// Find unique strings by length
words := it.Slice([]string{"hello", "world", "hi", "go", "bye"})
uniques := it.FindUniquesBy(words, func(s string) int { return len(s) })
// uniques: sequence with words of unique lengths
// "hello" (5), "world" (5) - not unique due to same length
// "hi" (2), "go" (2), "bye" (3) - "bye" is unique (length 3)
// Find unique items by first letter
items := it.Slice([]string{"apple", "apricot", "banana", "blueberry", "cherry"})
uniques := it.FindUniquesBy(items, func(s string) byte { return s[0] })
// uniques: sequence with "cherry" (only word starting with 'c')
// Find unique numbers by modulo
numbers := it.Slice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
uniques := it.FindUniquesBy(numbers, func(n int) int { return n % 3 })
// uniques: sequence with numbers that have unique remainders when divided by 3
// Find unique structs by composite key
type Order struct {
CustomerID string
ProductID string
}
orders := it.Slice([]Order{
{CustomerID: "A", ProductID: "1"},
{CustomerID: "A", ProductID: "2"},
{CustomerID: "B", ProductID: "1"}, // Same customer as first orders
{CustomerID: "C", ProductID: "3"},
})
uniques := it.FindUniquesBy(orders, func(o Order) string { return o.CustomerID })
// uniques: sequence with Order{CustomerID: "C", ProductID: "3"} only
// Find unique items by case-insensitive comparison
words := it.Slice([]string{"Hello", "hello", "WORLD", "world", "Go"})
uniques := it.FindUniquesBy(words, func(s string) string { return strings.ToLower(s) })
// uniques: sequence with "Go" only (others have case-insensitive duplicates)
// Find unique dates by year-month
import "time"
dates := it.Slice([]time.Time{
time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 20, 0, 0, 0, 0, time.UTC), // Same month as first
time.Date(2023, 2, 10, 0, 0, 0, 0, time.UTC),
time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC), // Different year
})
uniques := it.FindUniquesBy(dates, func(t time.Time) string {
return fmt.Sprintf("%d-%02d", t.Year(), t.Month())
})
// uniques: sequence with dates from unique year-month combinations
```
================================================
FILE: docs/data/it-first.md
================================================
---
name: First
slug: first
sourceRef: it/find.go#L362
category: it
subCategory: find
signatures:
- "func First[T any](collection iter.Seq[T]) (T, bool)"
playUrl: https://go.dev/play/p/EhNyrc8jPfY
variantHelpers:
- it#find#first
similarHelpers:
- core#slice#first
- it#find#last
- it#find#firstor
position: 120
---
Returns the first element of a collection and a boolean indicating availability. Returns zero value and false if the collection is empty.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
}
first, ok := it.First(seq)
// first == 10, ok == true
```
```go
seq := func(yield func(string) bool) {
// empty sequence
}
first, ok := it.First(seq)
// first == "", ok == false (zero value for string)
```
================================================
FILE: docs/data/it-firstor.md
================================================
---
name: FirstOr
slug: firstor
sourceRef: it/find.go#L377
category: it
subCategory: find
signatures:
- "func FirstOr[T any](collection iter.Seq[T], fallback T) T"
playUrl: https://go.dev/play/p/wGFXI5NHkE2
variantHelpers:
- it#find#firstor
similarHelpers:
- core#slice#firstor
position: 550
---
Returns the first element of a collection or the fallback value if empty.
Will iterate at most once.
Examples:
```go
// Get the first element or fallback value
numbers := it.Slice([]int{5, 2, 8, 1, 9})
first := it.FirstOr(numbers, 42)
// first: 5
// With empty collection
empty := it.Slice([]int{})
first := it.FirstOr(empty, 42)
// first: 42 (fallback value)
// With strings
words := it.Slice([]string{"hello", "world", "go"})
first := it.FirstOr(words, "fallback")
// first: "hello"
emptyWords := it.Slice([]string{})
first := it.FirstOr(emptyWords, "fallback")
// first: "fallback"
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
})
first := it.FirstOr(people, Person{Name: "Default", Age: 0})
// first: {Name: "Alice", Age: 30}
emptyPeople := it.Slice([]Person{})
first := it.FirstOr(emptyPeople, Person{Name: "Default", Age: 0})
// first: {Name: "Default", Age: 0} (fallback value)
// Using with pointers
pointers := it.Slice([]*int{ptr(5), ptr(10), ptr(15)})
first := it.FirstOr(pointers, nil)
// first: pointer to 5
emptyPointers := it.Slice([]*int{})
first := it.FirstOr(emptyPointers, nil)
// first: nil (fallback value)
```
================================================
FILE: docs/data/it-firstorempty.md
================================================
---
name: FirstOrEmpty
slug: firstorempty
sourceRef: it/find.go#L370
category: it
subCategory: find
signatures:
- "func FirstOrEmpty[T any](collection iter.Seq[T]) T"
playUrl: https://go.dev/play/p/NTUTgPCfevx
variantHelpers:
- it#find#firstorempty
similarHelpers:
- core#slice#firstorempty
position: 540
---
Returns the first element of a collection or zero value if empty.
Will iterate at most once.
Examples:
```go
// Get the first element or zero value
numbers := it.Slice([]int{5, 2, 8, 1, 9})
first := it.FirstOrEmpty(numbers)
// first: 5
// With empty collection
empty := it.Slice([]int{})
first := it.FirstOrEmpty(empty)
// first: 0 (zero value for int)
// With strings
words := it.Slice([]string{"hello", "world", "go"})
first := it.FirstOrEmpty(words)
// first: "hello"
emptyWords := it.Slice([]string{})
first := it.FirstOrEmpty(emptyWords)
// first: "" (zero value for string)
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
})
first := it.FirstOrEmpty(people)
// first: {Name: "Alice", Age: 30}
emptyPeople := it.Slice([]Person{})
first := it.FirstOrEmpty(emptyPeople)
// first: {Name: "", Age: 0} (zero value for Person)
```
================================================
FILE: docs/data/it-flatmap.md
================================================
---
name: FlatMap
slug: flatmap
sourceRef: it/seq.go#L109
category: it
subCategory: sequence
signatures:
- "func FlatMap[T, R any](collection iter.Seq[T], transform func(item T) iter.Seq[R]) iter.Seq[R]"
- "func FlatMapI[T, R any](collection iter.Seq[T], transform func(item T, index int) iter.Seq[R]) iter.Seq[R]"
playUrl: https://go.dev/play/p/6toB9w2gpSy
variantHelpers:
- it#sequence#flatmap
- it#sequence#flatmapi
similarHelpers:
- core#slice#flatmap
- it#sequence#map
position: 120
---
Transforms and flattens a sequence to another type. Each element is transformed into a sequence, then all sequences are concatenated.
Examples:
```go
seq := func(yield func([]int) bool) {
_ = yield([]int{1, 2})
_ = yield([]int{3, 4})
_ = yield([]int{5})
}
flattened := it.FlatMap(seq, func(arr []int) iter.Seq[int] {
return func(yield func(int) bool) {
for _, v := range arr {
if !yield(v * 2) {
return
}
}
}
})
var result []int
for v := range flattened {
result = append(result, v)
}
// result contains 2, 4, 6, 8, 10
```
### FlatMapI
Transforms and flattens a sequence to another type. Each element is transformed into a sequence with access to the element's index, then all sequences are concatenated.
```go
seq := func(yield func(string) bool) {
_ = yield("a")
_ = yield("b")
_ = yield("c")
}
flattened := it.FlatMapI(seq, func(s string, index int) iter.Seq[string] {
return func(yield func(string) bool) {
for i := 0; i <= index; i++ {
if !yield(fmt.Sprintf("%s-%d", s, i)) {
return
}
}
}
})
var result []string
for v := range flattened {
result = append(result, v)
}
// result contains "a-0", "b-0", "b-1", "c-0", "c-1", "c-2"
```
================================================
FILE: docs/data/it-flatten.md
================================================
---
name: Flatten
slug: flatten
sourceRef: it/seq.go#L26
category: it
subCategory: sequence
signatures:
- "func Flatten[T any, I ~func(func(T) bool)](collection []I) I"
variantHelpers: []
playUrl: https://go.dev/play/p/CCklxuNk7Lm
similarHelpers:
- core#slice#flatten
position: 172
---
Flatten returns a sequence a single level deep.
```go
seq1 := func(yield func(int) bool) {
yield(1)
yield(2)
}
seq2 := func(yield func(int) bool) {
yield(3)
yield(4)
}
flattened := it.Flatten([]iter.Seq[int]{seq1, seq2})
var result []int
for item := range flattened {
result = append(result, item)
}
// result contains [1, 2, 3, 4]
```
================================================
FILE: docs/data/it-foreach.md
================================================
---
name: ForEach
slug: foreach
sourceRef: it/seq.go#L166
category: it
subCategory: sequence
signatures:
- "func ForEach[T any](collection iter.Seq[T], callback func(item T))"
playUrl: "https://go.dev/play/p/agIsKpG-S-P"
variantHelpers:
- it#sequence#foreach
- it#sequence#foreachi
similarHelpers:
- core#slice#foreach
- it#sequence#map
position: 40
---
Iterates over elements and invokes a callback function for each element.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
var result []int
it.ForEach(seq, func(item int) {
result = append(result, item*2)
})
// result contains 2, 4, 6
```
```go
seq := func(yield func(string) bool) {
_ = yield("hello")
_ = yield("world")
}
it.ForEach(seq, func(item string) {
fmt.Println("Item:", item)
})
// Prints: Item: hello
// Item: world
```
================================================
FILE: docs/data/it-foreachwhile.md
================================================
---
name: ForEachWhile
slug: foreachwhile
sourceRef: it/seq.go#L180
category: it
subCategory: sequence
signatures:
- "func ForEachWhile[T any](collection iter.Seq[T], predicate func(item T) bool)"
- "func ForEachWhileI[T any](collection iter.Seq[T], predicate func(item T, index int) bool)"
variantHelpers:
- it#sequence#foreachwhile
- it#sequence#foreachwhilei
similarHelpers:
- it#sequence#foreach
position: 160
---
ForEachWhile iterates over elements of collection and invokes predicate for each element.
The predicate return value decides to continue or break, like do while().
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
called := 0
it.ForEachWhile(collection, func(item int) bool {
called++
return item < 3
})
// called is 3 (elements 1, 2, 3 were processed)
```
ForEachWhileI iterates over elements of collection and invokes predicate for each element with index.
The predicate return value decides to continue or break, like do while().
```go
collection := func(yield func(string) bool) {
yield("a")
yield("b")
yield("c")
yield("d")
}
called := 0
it.ForEachWhileI(collection, func(item string, index int) bool {
called++
return index < 2
})
// called is 3 (elements at indices 0, 1, 2 were processed)
```
================================================
FILE: docs/data/it-fromanyseq.md
================================================
---
name: FromAnySeq
slug: fromanyseq
sourceRef: it/type_manipulation.go#L11
category: it
subCategory: type
signatures:
- "func FromAnySeq[T any](collection iter.Seq[any]) iter.Seq[T]"
variantHelpers:
- it#type#toanyseq
playUrl: "https://go.dev/play/p/wnOma1j5Uzu"
similarHelpers:
- core#type#fromany
position: 244
---
FromAnySeq returns a sequence with all elements mapped to a type.
Panics on type conversion failure.
```go
collection := func(yield func(any) bool) {
yield(1)
yield(2)
yield("three") // This will cause panic
}
intSeq := it.FromAnySeq[int](collection)
// This will panic when trying to convert "three" to int
```
================================================
FILE: docs/data/it-fromentries.md
================================================
---
name: FromEntries
slug: fromentries
sourceRef: it/map.go#L96
category: it
subCategory: map
signatures:
- "func FromEntries[K comparable, V any](entries ...iter.Seq2[K, V]) map[K]V"
playUrl: https://go.dev/play/p/MgEF1J5-tuK
variantHelpers:
- it#map#fromentries
- it#map#frompairs
similarHelpers:
- core#slice#fromentries
- it#map#entries
position: 30
---
Transforms a sequence of key/value pairs into a map. Accepts multiple sequences and merges them.
Examples:
```go
entries := func(yield func(string, int) bool) {
_ = yield("apple", 1)
_ = yield("banana", 2)
_ = yield("cherry", 3)
}
m := it.FromEntries(entries)
// m == map[string]int{"apple": 1, "banana": 2, "cherry": 3}
```
```go
entries1 := func(yield func(string, int) bool) {
_ = yield("a", 1)
_ = yield("b", 2)
}
entries2 := func(yield func(string, int) bool) {
_ = yield("c", 3)
_ = yield("d", 4)
}
m := it.FromEntries(entries1, entries2)
// m contains all entries from both sequences
```
================================================
FILE: docs/data/it-frompairs.md
================================================
---
name: FromPairs
slug: frompairs
sourceRef: it/map.go#L104
category: it
subCategory: map
signatures:
- "func FromPairs[K comparable, V any](entries ...iter.Seq2[K, V]) map[K]V"
playUrl: "https://go.dev/play/p/K3wL9j7TmXs"
variantHelpers:
- it#map#fromentries
similarHelpers:
- core#slice#frompairs
- core#slice#fromentries
position: 20
---
Transforms a sequence of key/value pairs into a map. Alias of FromEntries().
```go
pairs := it.Seq2(func(yield func(string, int) bool) {
yield("a", 1)
yield("b", 2)
yield("c", 3)
})
result := it.FromPairs(pairs)
// map[string]int{"a": 1, "b": 2, "c": 3}
```
================================================
FILE: docs/data/it-fromseqptr.md
================================================
---
name: FromSeqPtr
slug: fromseqptr
sourceRef: it/type_manipulation.go#L11
category: it
subCategory: type
signatures:
- "func FromSeqPtr[T any](collection iter.Seq[*T]) iter.Seq[T]"
variantHelpers:
- it#type#fromseqptror
playUrl: "https://go.dev/play/p/_eO6scpLcBF"
similarHelpers:
- core#type#fromptr
position: 241
---
FromSeqPtr returns a sequence with the pointer values.
Returns a zero value in case of a nil pointer element.
```go
one := 1
two := 2
var three *int = nil
collection := func(yield func(*int) bool) {
yield(&one)
yield(&two)
yield(three)
}
values := it.FromSeqPtr(collection)
var result []int
for val := range values {
result = append(result, val)
}
// result contains [1, 2, 0]
```
================================================
FILE: docs/data/it-fromseqptror.md
================================================
---
name: FromSeqPtrOr
slug: fromseqptror
sourceRef: it/type_manipulation.go#L11
category: it
subCategory: type
signatures:
- "func FromSeqPtrOr[T any](collection iter.Seq[*T], fallback T) iter.Seq[T]"
variantHelpers:
- it#type#fromseqptr
playUrl: "https://go.dev/play/p/LJ17S4pvOjo"
similarHelpers: []
position: 242
---
FromSeqPtrOr returns a sequence with the pointer values or the fallback value.
```go
one := 1
var two *int = nil
collection := func(yield func(*int) bool) {
yield(&one)
yield(two)
}
values := it.FromSeqPtrOr(collection, 99)
var result []int
for val := range values {
result = append(result, val)
}
// result contains [1, 99]
```
================================================
FILE: docs/data/it-groupby.md
================================================
---
name: GroupBy
slug: groupby
sourceRef: it/seq.go#L244
category: it
subCategory: sequence
signatures:
- "func GroupBy[T any, U comparable](collection iter.Seq[T], transform func(item T) U) map[U][]T"
playUrl: https://go.dev/play/p/oRIakS89OYy
variantHelpers:
- it#sequence#groupby
similarHelpers:
- core#slice#groupby
- it#sequence#partitionby
- it#sequence#groupbymap
position: 80
---
Returns an object composed of keys generated from running each element of collection through a transform function. The value of each key is an array of elements responsible for generating the key.
Examples:
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("apricot")
_ = yield("blueberry")
}
grouped := it.GroupBy(seq, func(s string) string {
return string(s[0]) // group by first letter
})
// grouped contains map with keys: "a": ["apple", "apricot"], "b": ["banana", "blueberry"]
```
================================================
FILE: docs/data/it-hasprefix.md
================================================
---
name: HasPrefix
slug: hasprefix
sourceRef: it/find.go#L49
category: it
subCategory: find
signatures:
- "func HasPrefix[T comparable](collection iter.Seq[T], prefix ...T) bool"
playUrl: https://go.dev/play/p/Fyj6uq-G5IH
variantHelpers:
- it#find#hasprefix
similarHelpers:
- core#slice#hasprefix
- it#find#hassuffix
position: 20
---
Returns true if the collection has the specified prefix. The prefix can be specified as multiple arguments.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
}
hasPrefix := it.HasPrefix(seq, 1, 2)
// hasPrefix == true
```
```go
seq := func(yield func(string) bool) {
_ = yield("hello")
_ = yield("world")
}
hasPrefix := it.HasPrefix(seq, "hello")
// hasPrefix == true
```
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
hasPrefix := it.HasPrefix(seq, 2, 3)
// hasPrefix == false
```
================================================
FILE: docs/data/it-hassuffix.md
================================================
---
name: HasSuffix
slug: hassuffix
sourceRef: it/find.go#L71
category: it
subCategory: find
signatures:
- "func HasSuffix[T comparable](collection iter.Seq[T], suffix ...T) bool"
playUrl: https://go.dev/play/p/r6bF9Rmq5S0
variantHelpers:
- it#find#hassuffix
similarHelpers:
- core#slice#hassuffix
- it#find#hasprefix
position: 30
---
Returns true if the collection has the specified suffix. The suffix can be specified as multiple arguments.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
}
hasSuffix := it.HasSuffix(seq, 3, 4)
// hasSuffix == true
```
```go
seq := func(yield func(string) bool) {
_ = yield("hello")
_ = yield("world")
}
hasSuffix := it.HasSuffix(seq, "world")
// hasSuffix == true
```
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
hasSuffix := it.HasSuffix(seq, 1, 2)
// hasSuffix == false
```
================================================
FILE: docs/data/it-indexof.md
================================================
---
name: IndexOf
slug: indexof
sourceRef: it/find.go#L19
category: it
subCategory: find
signatures:
- "func IndexOf[T comparable](collection iter.Seq[T], element T) int"
playUrl: "https://go.dev/play/p/1OZHU2yfb-m"
variantHelpers:
- it#find#indexof
similarHelpers:
- core#slice#indexof
- it#find#lastindexof
position: 0
---
Returns the index at which the first occurrence of a value is found in the sequence, or -1 if the value is not found. Scans the sequence from the beginning and returns the position of the first matching element.
```go
// Find existing element - returns first occurrence
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
_ = yield(20)
}
idx := it.IndexOf(seq, 20)
// idx: 1 (first occurrence of 20)
```
```go
// Element not found - returns -1
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
idx := it.IndexOf(seq, "orange")
// idx: -1 (orange not found in sequence)
```
```go
// Empty sequence - returns -1
emptySeq := func(yield func(string) bool) {
// no elements yielded
}
idx := it.IndexOf(emptySeq, "anything")
// idx: -1 (sequence is empty)
```
================================================
FILE: docs/data/it-interleave.md
================================================
---
name: Interleave
slug: interleave
sourceRef: it/seq.go#L26
category: it
subCategory: sequence
signatures:
- "func Interleave[T any](collections ...iter.Seq[T]) iter.Seq[T]"
variantHelpers: []
playUrl: https://go.dev/play/p/kNvnz4ClLgH
similarHelpers:
- core#slice#interleave
position: 173
---
Interleave round-robin alternating input sequences and sequentially appending value at index into result.
```go
seq1 := func(yield func(int) bool) {
yield(1)
yield(3)
}
seq2 := func(yield func(int) bool) {
yield(2)
yield(4)
}
seq3 := func(yield func(int) bool) {
yield(5)
yield(6)
}
interleaved := it.Interleave(seq1, seq2, seq3)
var result []int
for item := range interleaved {
result = append(result, item)
}
// result contains [1, 2, 5, 3, 4, 6]
```
================================================
FILE: docs/data/it-intersect.md
================================================
---
name: Intersect
slug: intersect
sourceRef: it/intersect.go#L78
category: it
subCategory: intersect
signatures:
- "func Intersect[T comparable, I ~func(func(T) bool)](lists ...I) I"
playUrl: "https://go.dev/play/p/kz3cGhGZZWF"
variantHelpers:
- it#intersect#intersect
similarHelpers:
- it#intersect#intersectby
- core#slice#intersect
- core#slice#intersectby
- it#intersect#union
position: 10
---
Returns the intersection between given collections (elements present in all collections).
Examples:
```go
seq1 := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
}
seq2 := func(yield func(int) bool) {
_ = yield(2)
_ = yield(3)
_ = yield(5)
}
seq3 := func(yield func(int) bool) {
_ = yield(3)
_ = yield(2)
_ = yield(6)
}
intersection := it.Intersect(seq1, seq2, seq3)
var result []int
for v := range intersection {
result = append(result, v)
}
// result contains 2, 3 (elements present in all sequences)
```
================================================
FILE: docs/data/it-intersectby.md
================================================
---
name: IntersectBy
slug: intersectby
sourceRef: it/intersect.go#L78
category: it
subCategory: intersect
signatures:
- "func IntersectBy[T any, K comparable, I ~func(func(T) bool)](func(T) K, lists ...I) I"
playUrl: https://go.dev/play/p/X2nEvHC-lE2
variantHelpers:
- it#intersect#intersectby
similarHelpers:
- it#intersect#intersect
- core#slice#intersect
- core#slice#intersectby
- it#intersect#union
position: 10
---
Returns the intersection between given collections using a custom key selector function.
Examples:
```go
seq1 := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
}
seq2 := func(yield func(int) bool) {
_ = yield(2)
_ = yield(3)
_ = yield(5)
}
seq3 := func(yield func(int) bool) {
_ = yield(3)
_ = yield(2)
_ = yield(6)
}
transform := func(v int) string {
return strconv.Itoa(v)
}
intersection := it.IntersectBy(transform, seq1, seq2, seq3)
var result []int
for v := range intersection {
result = append(result, v)
}
// result contains 2, 3 (elements present in all sequences)
```
================================================
FILE: docs/data/it-invert.md
================================================
---
name: Invert
slug: invert
sourceRef: it/map.go#L111
category: it
subCategory: map
signatures:
- "func Invert[K, V comparable](in iter.Seq2[K, V]) iter.Seq2[V, K]"
playUrl: https://go.dev/play/p/Iph19Lgcsx-
variantHelpers:
- it#map#invert
similarHelpers:
- core#slice#invert
- it#map#entries
position: 40
---
Creates a sequence composed of inverted keys and values from a sequence of key/value pairs.
Examples:
```go
entries := func(yield func(string, int) bool) {
_ = yield("apple", 1)
_ = yield("banana", 2)
_ = yield("cherry", 3)
}
inverted := it.Invert(entries)
var keys []int
var values []string
for k, v := range inverted {
keys = append(keys, k)
values = append(values, v)
}
// keys contains 1, 2, 3 and values contains "apple", "banana", "cherry"
```
================================================
FILE: docs/data/it-isempty.md
================================================
---
name: IsEmpty
slug: isempty
sourceRef: it/type_manipulation.go#L50
category: it
subCategory: type
signatures:
- "func IsEmpty[T any](collection iter.Seq[T]) bool"
playUrl: https://go.dev/play/p/krZ-laaVi2C
variantHelpers:
- it#type#isempty
similarHelpers:
- it#type#isnotempty
- it#type#empty
- it#sequence#length
position: 10
---
Returns true if the sequence is empty. Will consume the entire sequence to check.
Examples:
```go
seq := func(yield func(int) bool) {
// empty sequence
}
empty := it.IsEmpty(seq)
// empty == true
```
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
}
empty := it.IsEmpty(seq)
// empty == false
```
================================================
FILE: docs/data/it-isnotempty.md
================================================
---
name: IsNotEmpty
slug: isnotempty
sourceRef: it/type_manipulation.go#L59
category: it
subCategory: condition
signatures:
- "func IsNotEmpty[T any](collection iter.Seq[T]) bool"
playUrl: "https://go.dev/play/p/G7hH3jJ0De5"
variantHelpers:
- it#condition#isempty
similarHelpers:
- core#slice#isnotempty
- core#slice#isempty
position: 10
---
Returns true if the collection is not empty, false otherwise.
```go
result1 := it.IsNotEmpty(it.Range(1, 5))
// true
result2 := it.IsNotEmpty(it.Empty[int]())
// false
```
================================================
FILE: docs/data/it-issorted.md
================================================
---
name: IsSorted
slug: issorted
sourceRef: it/seq.go#L720
category: it
subCategory: slice
signatures:
- "func IsSorted[T constraints.Ordered](collection iter.Seq[T]) bool"
playUrl: https://go.dev/play/p/o-BD4UOn-0U
variantHelpers: []
similarHelpers:
- core#slice#issorted
position: 200
---
IsSorted checks if a sequence is sorted.
```go
sorted := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
unsorted := func(yield func(int) bool) {
yield(1)
yield(3)
yield(2)
yield(4)
}
fmt.Println(it.IsSorted(sorted)) // true
fmt.Println(it.IsSorted(unsorted)) // false
```
================================================
FILE: docs/data/it-issortedby.md
================================================
---
name: IsSortedBy
slug: issortedby
sourceRef: it/seq.go#L720
category: it
subCategory: slice
signatures:
- "func IsSortedBy[T any, K constraints.Ordered](collection iter.Seq[T], transform func(item T) K) bool"
playUrl: https://go.dev/play/p/AfYOiGWa78T
variantHelpers: []
similarHelpers:
- core#slice#issortedby
position: 201
---
IsSortedBy checks if a sequence is sorted by transform.
```go
collection := func(yield func(string) bool) {
yield("apple")
yield("banana")
yield("cherry")
}
sortedByLength := it.IsSortedBy(collection, func(s string) int {
return len(s)
})
// true (5, 6, 6 is sorted)
```
================================================
FILE: docs/data/it-keyby.md
================================================
---
name: KeyBy
slug: keyby
sourceRef: it/seq.go#L398
category: it
subCategory: sequence
signatures:
- "func KeyBy[K comparable, V any](collection iter.Seq[V], transform func(item V) K) map[K]V"
playUrl: https://go.dev/play/p/MMaHpzTqY0a
variantHelpers:
- it#map#associate
similarHelpers:
- core#slice#keyby
- core#slice#associate
position: 15
---
Transforms a sequence into a map using a transform function to generate keys.
```go
result := it.KeyBy(it.Range(1, 5), func(item int) string {
return fmt.Sprintf("key-%d", item)
})
// map[string]int{"key-1": 1, "key-2": 2, "key-3": 3, "key-4": 4}
```
================================================
FILE: docs/data/it-keyify.md
================================================
---
name: Keyify
slug: keyify
sourceRef: it/seq.go#L720
category: it
subCategory: slice
signatures:
- "func Keyify[T comparable](collection iter.Seq[T]) map[T]struct{}"
playUrl: https://go.dev/play/p/aHOD29_l-rF
variantHelpers: []
similarHelpers:
- core#slice#keyby
position: 202
---
Keyify returns a map with each unique element of the sequence as a key.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(1)
yield(3)
yield(2)
}
keyMap := it.Keyify(collection)
// keyMap contains {1: {}, 2: {}, 3: {}}
```
================================================
FILE: docs/data/it-keys.md
================================================
---
name: Keys
slug: keys
sourceRef: it/map.go#L11
category: it
subCategory: map
signatures:
- "func Keys[K comparable, V any](in ...map[K]V) iter.Seq[K]"
playUrl: "https://go.dev/play/p/Fu7h-eW18QM"
variantHelpers:
- it#map#keys
similarHelpers:
- core#slice#keys
- it#map#values
- it#map#uniqkeys
position: 0
---
Creates a sequence of the map keys. Accepts multiple maps and concatenates their keys.
Examples:
```go
m1 := map[string]int{
"apple": 1,
"banana": 2,
}
m2 := map[string]int{
"cherry": 3,
"date": 4,
}
keysSeq := it.Keys(m1, m2)
var result []string
for k := range keysSeq {
result = append(result, k)
}
// result contains keys from both maps
```
================================================
FILE: docs/data/it-last.md
================================================
---
name: Last
slug: last
sourceRef: it/find.go#L389
category: it
subCategory: find
signatures:
- "func Last[T any](collection iter.Seq[T]) (T, bool)"
playUrl: https://go.dev/play/p/eGZV-sSmn_Q
variantHelpers:
- it#find#last
similarHelpers:
- core#slice#last
- it#find#first
- it#find#lastor
position: 130
---
Returns the last element of a collection and a boolean indicating availability. Returns zero value and false if the collection is empty.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
}
last, ok := it.Last(seq)
// last == 30, ok == true
```
```go
seq := func(yield func(string) bool) {
// empty sequence
}
last, ok := it.Last(seq)
// last == "", ok == false (zero value for string)
```
================================================
FILE: docs/data/it-lastindexof.md
================================================
---
name: LastIndexOf
slug: lastindexof
sourceRef: it/find.go#L34
category: it
subCategory: find
signatures:
- "func LastIndexOf[T comparable](collection iter.Seq[T], element T) int"
playUrl: https://go.dev/play/p/QPATR3VC5wT
variantHelpers:
- it#find#lastindexof
similarHelpers:
- core#slice#lastindexof
- it#find#indexof
position: 10
---
Returns the index at which the last occurrence of a value is found in the sequence, or -1 if the value is not found.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
_ = yield(20)
}
idx := it.LastIndexOf(seq, 20)
// idx == 3
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
idx := it.LastIndexOf(seq, "orange")
// idx == -1
```
================================================
FILE: docs/data/it-lastor.md
================================================
---
name: LastOr
slug: lastor
sourceRef: it/find.go#L407
category: it
subCategory: find
signatures:
- "func LastOr[T any](collection iter.Seq[T], fallback T) T"
playUrl: https://go.dev/play/p/HNubjW2Mrxs
variantHelpers:
- it#find#lastor
similarHelpers:
- core#slice#lastor
position: 570
---
Returns the last element of a collection or the fallback value if empty.
Will iterate through the entire sequence.
Examples:
```go
// Get the last element or fallback value
numbers := it.Slice([]int{5, 2, 8, 1, 9})
last := it.LastOr(numbers, 42)
// last: 9
// With empty collection
empty := it.Slice([]int{})
last := it.LastOr(empty, 42)
// last: 42 (fallback value)
// With strings
words := it.Slice([]string{"hello", "world", "go"})
last := it.LastOr(words, "fallback")
// last: "go"
emptyWords := it.Slice([]string{})
last := it.LastOr(emptyWords, "fallback")
// last: "fallback"
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
})
last := it.LastOr(people, Person{Name: "Default", Age: 0})
// last: {Name: "Bob", Age: 25}
emptyPeople := it.Slice([]Person{})
last := it.LastOr(emptyPeople, Person{Name: "Default", Age: 0})
// last: {Name: "Default", Age: 0} (fallback value)
// With single element
single := it.Slice([]int{42})
last := it.LastOr(single, 99)
// last: 42
// Using with nil pointer fallback
values := it.Slice([]*string{ptr("hello"), ptr("world")})
last := it.LastOr(values, nil)
// last: pointer to "world"
emptyValues := it.Slice([]*string{})
last := it.LastOr(emptyValues, nil)
// last: nil (fallback value)
```
================================================
FILE: docs/data/it-lastorempty.md
================================================
---
name: LastOrEmpty
slug: lastorempty
sourceRef: it/find.go#L400
category: it
subCategory: find
signatures:
- "func LastOrEmpty[T any](collection iter.Seq[T]) T"
playUrl: https://go.dev/play/p/teODFK4YqM4
variantHelpers:
- it#find#lastorempty
similarHelpers:
- core#slice#lastorempty
position: 560
---
Returns the last element of a collection or zero value if empty.
Will iterate through the entire sequence.
Examples:
```go
// Get the last element or zero value
numbers := it.Slice([]int{5, 2, 8, 1, 9})
last := it.LastOrEmpty(numbers)
// last: 9
// With empty collection
empty := it.Slice([]int{})
last := it.LastOrEmpty(empty)
// last: 0 (zero value for int)
// With strings
words := it.Slice([]string{"hello", "world", "go"})
last := it.LastOrEmpty(words)
// last: "go"
emptyWords := it.Slice([]string{})
last := it.LastOrEmpty(emptyWords)
// last: "" (zero value for string)
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
})
last := it.LastOrEmpty(people)
// last: {Name: "Bob", Age: 25}
emptyPeople := it.Slice([]Person{})
last := it.LastOrEmpty(emptyPeople)
// last: {Name: "", Age: 0} (zero value for Person)
// With single element
single := it.Slice([]int{42})
last := it.LastOrEmpty(single)
// last: 42
```
================================================
FILE: docs/data/it-latest.md
================================================
---
name: Latest
slug: latest
sourceRef: it/find.go#L346
category: it
subCategory: find
signatures:
- "func Latest(times iter.Seq[time.Time]) time.Time"
playUrl: https://go.dev/play/p/r5Yq6ATSHoH
variantHelpers:
- it#find#latest
similarHelpers:
- core#slice#latest
position: 520
---
Searches for the latest (maximum) time.Time in a collection.
Returns zero value when the collection is empty.
Will iterate through the entire sequence.
Examples:
```go
import "time"
// Find the latest time from a collection
times := it.Slice([]time.Time{
time.Date(2023, 5, 15, 10, 0, 0, 0, time.UTC),
time.Date(2023, 3, 20, 14, 30, 0, 0, time.UTC),
time.Date(2023, 8, 1, 9, 15, 0, 0, time.UTC),
})
latest := it.Latest(times)
// latest: 2023-08-01 09:15:00 +0000 UTC
// With empty collection
empty := it.Slice([]time.Time{})
latest := it.Latest(empty)
// latest: 0001-01-01 00:00:00 +0000 UTC (zero value)
// Find latest from parsed times
times := it.Slice([]time.Time{
time.Parse(time.RFC3339, "2023-01-01T12:00:00Z"),
time.Parse(time.RFC3339, "2023-01-01T10:00:00Z"),
time.Parse(time.RFC3339, "2023-01-01T14:00:00Z"),
})
latest := it.Latest(times)
// latest: 2023-01-01 14:00:00 +0000 UTC
// Find latest log entry timestamp
logs := it.Slice([]time.Time{
time.Now().Add(-2 * time.Hour),
time.Now().Add(-1 * time.Hour),
time.Now(),
})
latest := it.Latest(logs)
// latest: current time
```
================================================
FILE: docs/data/it-latestby.md
================================================
---
name: LatestBy
slug: latestby
sourceRef: it/find.go#L353
category: it
subCategory: find
signatures:
- "func LatestBy[T any](collection iter.Seq[T], transform func(item T) time.Time) T"
playUrl: https://go.dev/play/p/o_daRzHrDUU
variantHelpers:
- it#find#latestby
similarHelpers:
- core#slice#latestby
position: 530
---
Searches for the element with the latest time using a transform function.
Returns zero value when the collection is empty.
Will iterate through the entire sequence.
Examples:
```go
import "time"
type Event struct {
Name string
Time time.Time
}
// Find the latest event by time
events := it.Slice([]Event{
{"Meeting", time.Date(2023, 5, 15, 10, 0, 0, 0, time.UTC)},
{"Lunch", time.Date(2023, 5, 15, 12, 0, 0, 0, time.UTC)},
{"Breakfast", time.Date(2023, 5, 15, 8, 0, 0, 0, time.UTC)},
})
latest := it.LatestBy(events, func(e Event) time.Time {
return e.Time
})
// latest: {Name: "Lunch", Time: 2023-05-15 12:00:00 +0000 UTC}
// Find the latest task by deadline
type Task struct {
ID int
Deadline time.Time
}
tasks := it.Slice([]Task{
{1, time.Date(2023, 6, 1, 0, 0, 0, 0, time.UTC)},
{2, time.Date(2023, 5, 15, 0, 0, 0, 0, time.UTC)},
{3, time.Date(2023, 7, 1, 0, 0, 0, 0, time.UTC)},
})
latest := it.LatestBy(tasks, func(t Task) time.Time {
return t.Deadline
})
// latest: {ID: 3, Deadline: 2023-07-01 00:00:00 +0000 UTC}
// Find the most recent activity
type Activity struct {
User string
Action string
Time time.Time
}
activities := it.Slice([]Activity{
{"alice", "login", time.Now().Add(-24 * time.Hour)},
{"bob", "logout", time.Now().Add(-12 * time.Hour)},
{"alice", "post", time.Now().Add(-1 * time.Hour)},
})
latest := it.LatestBy(activities, func(a Activity) time.Time {
return a.Time
})
// latest: {User: "alice", Action: "post", Time: 1 hour ago}
```
================================================
FILE: docs/data/it-length.md
================================================
---
name: Length
slug: length
sourceRef: it/seq.go#L16
category: it
subCategory: sequence
signatures:
- "func Length[T any](collection iter.Seq[T]) int"
playUrl: "https://go.dev/play/p/3dnbOjTbL-o"
variantHelpers:
- it#sequence#length
similarHelpers:
- core#slice#length
- it#sequence#isempty
- it#sequence#isnotempty
position: 0
---
Returns the length of a collection by iterating through the entire sequence.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
length := it.Length(seq)
// length == 3
```
```go
seq := func(yield func(string) bool) {
// empty sequence
}
length := it.Length(seq)
// length == 0
```
================================================
FILE: docs/data/it-map.md
================================================
---
name: Map
slug: map
sourceRef: it/seq.go#L51
category: it
subCategory: sequence
signatures:
- "func Map[T, R any](collection iter.Seq[T], transform func(item T) R) iter.Seq[R]"
- "func MapI[T, R any](collection iter.Seq[T], transform func(item T, index int) R) iter.Seq[R]"
playUrl: "https://go.dev/play/p/rWZiPB-RZOo"
variantHelpers:
- it#sequence#map
- it#sequence#mapi
similarHelpers:
- core#slice#map
- it#sequence#filtermap
- it#sequence#flatmap
position: 20
---
Transforms a sequence to another type by applying a transform function to each element.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
mapped := it.Map(seq, func(x int) string {
return fmt.Sprintf("item-%d", x)
})
var result []string
for v := range mapped {
result = append(result, v)
}
// result contains "item-1", "item-2", "item-3"
```
### MapI
Transforms a sequence to another type by applying a transform function to each element and its index.
```go
seq := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
}
mapped := it.MapI(seq, func(x int, index int) string {
return fmt.Sprintf("item-%d-%d", x, index)
})
var result []string
for v := range mapped {
result = append(result, v)
}
// result contains "item-10-0", "item-20-1", "item-30-2"
```
================================================
FILE: docs/data/it-maptoseq.md
================================================
---
name: MapToSeq
slug: maptoseq
sourceRef: it/map.go#L164
category: it
subCategory: map
signatures:
- "func MapToSeq[K comparable, V, R any](in map[K]V, transform func(key K, value V) R) iter.Seq[R]"
variantHelpers:
- it#map#maptoseq
similarHelpers:
- core#map#maptoslice
- it#map#values
- it#map#keys
- it#map#entries
- it#map#filtermaptoseq
position: 52
---
Transforms a map into a sequence by applying a transform function to each key-value pair. The transform function determines what values are yielded in the output sequence.
```go
m := map[string]int{
"apple": 3,
"banana": 5,
"cherry": 2,
}
result := it.MapToSeq(m, func(key string, value int) string {
return fmt.Sprintf("%s:%d", key, value)
})
// iter.Seq[string] yielding "apple:3", "banana:5", "cherry:2"
numberMap := map[int]string{1: "one", 2: "two", 3: "three"}
result = it.MapToSeq(numberMap, func(key int, value string) int {
return key * len(value)
})
// iter.Seq[int] yielding 3, 6, 15 (1*3, 2*3, 3*5)
personMap := map[string]int{"alice": 25, "bob": 30}
type Person struct {
Name string
Age int
}
result = it.MapToSeq(personMap, func(name string, age int) Person {
return Person{Name: name, Age: age}
})
// iter.Seq[Person] yielding {Name: "alice", Age: 25}, {Name: "bob", Age: 30}
```
================================================
FILE: docs/data/it-max.md
================================================
---
name: Max
slug: max
sourceRef: it/find.go#L295
category: it
subCategory: find
signatures:
- "func Max[T constraints.Ordered](collection iter.Seq[T]) T"
playUrl: https://go.dev/play/p/C2ZtW2bsBZ6
variantHelpers:
- it#find#max
similarHelpers:
- core#slice#max
- it#find#min
- it#find#maxby
position: 110
---
Searches the maximum value of a collection. Returns the largest element found.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(5)
_ = yield(2)
_ = yield(8)
_ = yield(1)
_ = yield(9)
}
max := it.Max(seq)
// max == 9
```
```go
seq := func(yield func(string) bool) {
_ = yield("zebra")
_ = yield("apple")
_ = yield("banana")
}
max := it.Max(seq)
// max == "zebra" (lexicographically largest)
```
================================================
FILE: docs/data/it-maxby.md
================================================
---
name: MaxBy
slug: maxby
sourceRef: it/find.go#L310
category: it
subCategory: find
signatures:
- "func MaxBy[T any](collection iter.Seq[T], comparison func(a, b T) bool) T"
playUrl: https://go.dev/play/p/yBhXFJb5oxC
variantHelpers:
- it#find#maxby
similarHelpers:
- core#slice#maxby
- it#find#minby
- it#find#max
position: 150
---
Searches maximum value using a custom comparison function. The comparison function should return true if the first argument is "greater than" the second.
Examples:
```go
type Person struct {
Name string
Age int
}
seq := func(yield func(Person) bool) {
_ = yield(Person{"Alice", 30})
_ = yield(Person{"Bob", 25})
_ = yield(Person{"Charlie", 35})
}
oldest := it.MaxBy(seq, func(a, b Person) bool {
return a.Age > b.Age
})
// oldest == Person{"Charlie", 35}
```
================================================
FILE: docs/data/it-maxindex.md
================================================
---
name: MaxIndex
slug: maxindex
sourceRef: it/find.go#L299
category: it
subCategory: find
signatures:
- "func MaxIndex[T constraints.Ordered](collection iter.Seq[T]) (T, int)"
playUrl: https://go.dev/play/p/zeu2wUvhl5e
variantHelpers:
- it#find#maxindex
similarHelpers:
- core#slice#maxindex
position: 480
---
Searches the maximum value of a collection and returns both the value and its index.
Returns (zero value, -1) when the collection is empty.
Will iterate through the entire sequence.
Examples:
```go
// Find the maximum value and its index
numbers := it.Slice([]int{5, 2, 8, 1, 9})
value, index := it.MaxIndex(numbers)
// value: 9, index: 4
// With empty collection
empty := it.Slice([]int{})
value, index := it.MaxIndex(empty)
// value: 0, index: -1
// Find the maximum string alphabetically and its index
words := it.Slice([]string{"apple", "zebra", "banana", "xylophone"})
value, index := it.MaxIndex(words)
// value: "zebra", index: 1
```
================================================
FILE: docs/data/it-maxindexby.md
================================================
---
name: MaxIndexBy
slug: maxindexby
sourceRef: it/find.go#L326
category: it
subCategory: find
signatures:
- "func MaxIndexBy[T any](collection iter.Seq[T], comparison func(a, b T) bool) (T, int)"
playUrl: https://go.dev/play/p/MXyE6BTILjx
variantHelpers:
- it#find#maxindexby
similarHelpers:
- core#slice#maxindexby
position: 490
---
Searches the maximum value of a collection using a comparison function and returns both the value and its index.
If several values are equal to the greatest value, returns the first such value.
Returns (zero value, -1) when the collection is empty.
Will iterate through the entire sequence.
Examples:
```go
// Find the maximum string by length and its index
words := it.Slice([]string{"apple", "hi", "banana", "xylophone"})
value, index := it.MaxIndexBy(words, func(a, b string) bool {
return len(a) > len(b)
})
// value: "xylophone", index: 3
// Find the maximum person by age and its index
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
value, index := it.MaxIndexBy(people, func(a, b Person) bool {
return a.Age > b.Age
})
// value: {Name: "Charlie", Age: 35}, index: 2
// Find the maximum number by absolute value and its index
numbers := it.Slice([]int{-5, 2, -8, 1})
value, index := it.MaxIndexBy(numbers, func(a, b int) bool {
return abs(a) > abs(b)
})
// value: -8, index: 2
```
================================================
FILE: docs/data/it-mean.md
================================================
---
name: Mean / MeanBy
slug: mean
sourceRef: it/math.go#L80
category: it
subCategory: math
signatures:
- "func Mean[T constraints.Float | constraints.Integer](collection iter.Seq[T]) T"
- "func MeanBy[T any, R constraints.Float | constraints.Integer](collection iter.Seq[T], iteratee func(item T) R) R"
playUrl: "https://go.dev/play/p/Lez0CsvVRl_l"
variantHelpers:
- it#math#mean
- it#math#meanby
similarHelpers:
- core#slice#mean
- core#slice#meanby
position: 30
---
Computes the arithmetic mean. `MeanBy` applies a transform before averaging. Returns 0 for empty sequences.
Examples:
```go
avg := it.Mean(iter.Seq[int](func(y func(int) bool){ _ = y(2); _ = y(3); _ = y(5) }))
// avg == 10/3 == 3 (int division)
```
```go
avg := it.MeanBy(iter.Seq[string](func(y func(string) bool){ _ = y("aa"); _ = y("bbb") }), func(s string) int { return len(s) })
// (2+3)/2 == 2
```
================================================
FILE: docs/data/it-meanby.md
================================================
---
name: MeanBy
slug: meanby
sourceRef: it/math.go#L106
category: it
subCategory: math
signatures:
- "func MeanBy[T any, R constraints.Float | constraints.Integer](collection iter.Seq[T], transform func(item T) R) R"
playUrl: https://go.dev/play/p/Ked4rpztH5Y
variantHelpers:
- it#math#mean
similarHelpers:
- core#slice#meanby
- core#slice#mean
position: 70
---
Returns the mean value of the collection using the given transform function.
```go
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35},
})
result := it.MeanBy(people, func(p Person) int {
return p.Age
})
// 30.0
```
================================================
FILE: docs/data/it-min.md
================================================
---
name: Min
slug: min
sourceRef: it/find.go#L227
category: it
subCategory: find
signatures:
- "func Min[T constraints.Ordered](collection iter.Seq[T]) T"
playUrl: https://go.dev/play/p/0VihyYEaM-M
variantHelpers:
- it#find#min
similarHelpers:
- core#slice#min
- it#find#max
- it#find#minby
position: 100
---
Searches the minimum value of a collection. Returns the smallest element found.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(5)
_ = yield(2)
_ = yield(8)
_ = yield(1)
_ = yield(9)
}
min := it.Min(seq)
// min == 1
```
```go
seq := func(yield func(string) bool) {
_ = yield("zebra")
_ = yield("apple")
_ = yield("banana")
}
min := it.Min(seq)
// min == "apple" (lexicographically smallest)
```
================================================
FILE: docs/data/it-minby.md
================================================
---
name: MinBy
slug: minby
sourceRef: it/find.go#L242
category: it
subCategory: find
signatures:
- "func MinBy[T any](collection iter.Seq[T], comparison func(a, b T) bool) T"
playUrl: https://go.dev/play/p/J5koo8khN-g
variantHelpers:
- it#find#minby
similarHelpers:
- core#slice#minby
- it#find#maxby
- it#find#min
position: 140
---
Searches minimum value using a custom comparison function. The comparison function should return true if the first argument is "less than" the second.
Examples:
```go
type Person struct {
Name string
Age int
}
seq := func(yield func(Person) bool) {
_ = yield(Person{"Alice", 30})
_ = yield(Person{"Bob", 25})
_ = yield(Person{"Charlie", 35})
}
youngest := it.MinBy(seq, func(a, b Person) bool {
return a.Age < b.Age
})
// youngest == Person{"Bob", 25}
```
================================================
FILE: docs/data/it-minindex.md
================================================
---
name: MinIndex
slug: minindex
sourceRef: it/find.go#L231
category: it
subCategory: find
signatures:
- "func MinIndex[T constraints.Ordered](collection iter.Seq[T]) (T, int)"
playUrl: https://go.dev/play/p/70ncPxECj6l
variantHelpers:
- it#find#minindex
similarHelpers:
- core#slice#minindex
position: 460
---
Searches the minimum value of a collection and returns both the value and its index.
Returns (zero value, -1) when the collection is empty.
Will iterate through the entire sequence.
Examples:
```go
// Find the minimum value and its index
numbers := it.Slice([]int{5, 2, 8, 1, 9})
value, index := it.MinIndex(numbers)
// value: 1, index: 3
// With empty collection
empty := it.Slice([]int{})
value, index := it.MinIndex(empty)
// value: 0, index: -1
```
================================================
FILE: docs/data/it-minindexby.md
================================================
---
name: MinIndexBy
slug: minindexby
sourceRef: it/find.go#L258
category: it
subCategory: find
signatures:
- "func MinIndexBy[T any](collection iter.Seq[T], comparison func(a, b T) bool) (T, int)"
playUrl: https://go.dev/play/p/blldzWJpqVa
variantHelpers:
- it#find#minindexby
similarHelpers:
- core#slice#minindexby
position: 470
---
Searches the minimum value of a collection using a comparison function and returns both the value and its index.
If several values are equal to the smallest value, returns the first such value.
Returns (zero value, -1) when the collection is empty.
Will iterate through the entire sequence.
Examples:
```go
// Find the minimum string by length and its index
words := it.Slice([]string{"apple", "hi", "banana", "ok"})
value, index := it.MinIndexBy(words, func(a, b string) bool {
return len(a) < len(b)
})
// value: "hi", index: 1
// Find the minimum person by age and its index
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
value, index := it.MinIndexBy(people, func(a, b Person) bool {
return a.Age < b.Age
})
// value: {Name: "Bob", Age: 25}, index: 1
```
================================================
FILE: docs/data/it-mode.md
================================================
---
name: Mode
slug: mode
sourceRef: it/math.go#L124
category: it
subCategory: math
signatures:
- "func Mode[T constraints.Integer | constraints.Float](collection iter.Seq[T]) []T"
playUrl: "https://go.dev/play/p/c_cmMMA5EhH"
variantHelpers:
- it#math#mode
similarHelpers:
- core#slice#mode
position: 40
---
Returns the mode (most frequent value) of a collection. If multiple values have the same highest frequency, then multiple values are returned. If the collection is empty, then the zero value of T is returned.
Will iterate through the entire sequence and allocate a map large enough to hold all distinct elements. Long heterogeneous input sequences can cause excessive memory usage.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(2)
_ = yield(3)
_ = yield(3)
_ = yield(3)
}
mode := it.Mode(seq)
// mode == []int{3}
```
```go
// Multiple modes
seq := func(yield func(string) bool) {
_ = yield("a")
_ = yield("b")
_ = yield("a")
_ = yield("b")
}
mode := it.Mode(seq)
// mode contains both "a" and "b" (order may vary)
```
================================================
FILE: docs/data/it-none.md
================================================
---
name: None
slug: none
sourceRef: it/intersect.go#L63
category: it
subCategory: intersect
signatures:
- "func None[T comparable](collection iter.Seq[T], subset ...T) bool"
playUrl: "https://go.dev/play/p/L7mm5S4a8Yo"
variantHelpers:
- it#intersect#none
similarHelpers:
- core#slice#none
position: 680
---
Returns true if no element of a subset is contained in a collection or if the subset is empty.
Will iterate through the entire sequence if subset elements never match.
Examples:
```go
// Check if collection contains none of the forbidden values
numbers := it.Slice([]int{1, 3, 5, 7, 9})
forbidden := []int{2, 4, 6, 8}
hasNone := it.None(numbers, forbidden...)
// hasNone: true
numbers = it.Slice([]int{1, 3, 5, 8, 9})
hasNone = it.None(numbers, forbidden...)
// hasNone: false (8 is in both collection and forbidden)
// Check if collection contains none of unwanted words
words := it.Slice([]string{"hello", "world", "go", "lang"})
unwanted := []string{"bad", "evil", "wrong"}
hasNone := it.None(words, unwanted...)
// hasNone: true
words = it.Slice([]string{"hello", "bad", "go", "lang"})
hasNone = it.None(words, unwanted...)
// hasNone: false ("bad" is in both)
// Check if collection contains none of specific IDs
ids := it.Slice([]int{101, 102, 103, 104})
restrictedIds := []int{201, 202, 203}
hasNone := it.None(ids, restrictedIds...)
// hasNone: true
ids = it.Slice([]int{101, 102, 203, 104})
hasNone = it.None(ids, restrictedIds...)
// hasNone: false (203 is restricted)
// Check with empty subset (always returns true)
numbers = it.Slice([]int{1, 3, 5, 7, 9})
hasNone = it.None(numbers)
// hasNone: true
// Check with strings containing specific characters
words := it.Slice([]string{"hello", "world", "go", "lang"})
forbiddenChars := []string{"@", "#", "$"}
hasNone := it.None(words, forbiddenChars...)
// hasNone: true
words = it.Slice([]string{"hello", "world", "go@"})
hasNone = it.None(words, forbiddenChars...)
// hasNone: false (contains "@")
// Check if collection has none of problematic status codes
statusCodes := it.Slice([]int{200, 201, 204})
errorCodes := []int{400, 401, 403, 404, 500}
hasNone := it.None(statusCodes, errorCodes...)
// hasNone: true
statusCodes = it.Slice([]int{200, 404, 204})
hasNone = it.None(statusCodes, errorCodes...)
// hasNone: false (contains 404)
// Check with empty collection (always returns true)
empty := it.Slice([]int{})
hasNone = it.None(empty, 1, 2, 3)
// hasNone: true
// Check for none of forbidden usernames
usernames := it.Slice([]string{"alice", "bob", "charlie"})
forbiddenUsers := []string{"admin", "root", "system"}
hasNone := it.None(usernames, forbiddenUsers...)
// hasNone: true
usernames = it.Slice([]string{"alice", "admin", "charlie"})
hasNone = it.None(usernames, forbiddenUsers...)
// hasNone: false ("admin" is forbidden)
```
================================================
FILE: docs/data/it-noneby.md
================================================
---
name: NoneBy
slug: noneby
sourceRef: it/intersect.go#L69
category: it
subCategory: intersect
signatures:
- "func NoneBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool"
playUrl: https://go.dev/play/p/PR7ddQ7Ckz5
variantHelpers:
- it#intersect#noneby
similarHelpers:
- core#slice#noneby
position: 690
---
Returns true if the predicate returns true for none of the elements in the collection or if the collection is empty.
Will iterate through the entire sequence if predicate never returns true.
Examples:
```go
// Check if collection has no even numbers
numbers := it.Slice([]int{1, 3, 5, 7, 9})
hasNoEvens := it.NoneBy(numbers, func(n int) bool { return n%2 == 0 })
// hasNoEvens: true
numbers = it.Slice([]int{1, 3, 5, 8, 9})
hasNoEvens = it.NoneBy(numbers, func(n int) bool { return n%2 == 0 })
// hasNoEvens: false (8 is even)
// Check if collection has no strings with specific prefix
words := it.Slice([]string{"hello", "world", "go", "lang"})
hasNoGoPrefix := it.NoneBy(words, func(s string) bool { return strings.HasPrefix(s, "go") })
// hasNoGoPrefix: false ("go" has go prefix)
hasNoPythonPrefix := it.NoneBy(words, func(s string) bool { return strings.HasPrefix(s, "python") })
// hasNoPythonPrefix: true
// Check if collection has no minors
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
hasNoMinors := it.NoneBy(people, func(p Person) bool { return p.Age < 18 })
// hasNoMinors: true
withMinor := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 16}, // Minor
{Name: "Charlie", Age: 35},
})
hasNoMinors = it.NoneBy(withMinor, func(p Person) bool { return p.Age < 18 })
// hasNoMinors: false
// Check if collection has no negative numbers
numbers = it.Slice([]int{1, 3, 5, 7, 9})
hasNoNegatives := it.NoneBy(numbers, func(n int) bool { return n < 0 })
// hasNoNegatives: true
numbers = it.Slice([]int{1, -3, 5, 7, 9})
hasNoNegatives = it.NoneBy(numbers, func(n int) bool { return n < 0 })
// hasNoNegatives: false (-3 is negative)
// Check if collection has no uppercase strings
strings := it.Slice([]string{"hello", "world", "go", "lang"})
hasNoUppercase := it.NoneBy(strings, func(s string) bool { return s != strings.ToLower(s) })
// hasNoUppercase: true
strings = it.Slice([]string{"hello", "World", "go", "lang"}) // "World" has uppercase
hasNoUppercase = it.NoneBy(strings, func(s string) bool { return s != strings.ToLower(s) })
// hasNoUppercase: false
// Empty collection returns true
empty := it.Slice([]int{})
hasNoEvens := it.NoneBy(empty, func(n int) bool { return n%2 == 0 })
// hasNoEvens: true
// Check if collection has no invalid emails
emails := it.Slice([]string{"user@example.com", "test@domain.org", "admin@site.net"})
hasNoInvalid := it.NoneBy(emails, func(email string) bool {
return !strings.Contains(email, "@") || !strings.Contains(email, ".")
})
// hasNoInvalid: true
emails = it.Slice([]string{"user@example.com", "invalid-email", "test@domain.org"})
hasNoInvalid = it.NoneBy(emails, func(email string) bool {
return !strings.Contains(email, "@") || !strings.Contains(email, ".")
})
// hasNoInvalid: false
// Check if collection has no numbers greater than 100
numbers = it.Slice([]int{1, 3, 5, 7, 9})
hasNoLargeNumbers := it.NoneBy(numbers, func(n int) bool { return n > 100 })
// hasNoLargeNumbers: true
numbers = it.Slice([]int{1, 3, 5, 150, 9})
hasNoLargeNumbers = it.NoneBy(numbers, func(n int) bool { return n > 100 })
// hasNoLargeNumbers: false (150 > 100)
// Check if collection has no strings shorter than 3 characters
words := it.Slice([]string{"hello", "world", "go", "lang"})
hasNoShortWords := it.NoneBy(words, func(s string) bool { return len(s) < 3 })
// hasNoShortWords: false ("go" has length 2)
```
================================================
FILE: docs/data/it-nth.md
================================================
---
name: Nth
slug: nth
sourceRef: it/find.go#L417
category: it
subCategory: find
signatures:
- "func Nth[T any, N constraints.Integer](collection iter.Seq[T], nth N) (T, error)"
playUrl: https://go.dev/play/p/FqgCobsKqva
variantHelpers:
- it#find#nth
similarHelpers:
- core#slice#nth
position: 580
---
Returns the element at index `nth` of collection. Returns an error when nth is out of bounds.
Will iterate n times through the sequence.
Examples:
```go
// Get element at specific index
numbers := it.Slice([]int{5, 2, 8, 1, 9})
element, err := it.Nth(numbers, 2)
// element: 8, err: nil
// Get first element (index 0)
first, err := it.Nth(numbers, 0)
// first: 5, err: nil
// Get last element
last, err := it.Nth(numbers, 4)
// last: 9, err: nil
// Out of bounds - negative
_, err := it.Nth(numbers, -1)
// err: nth: -1 out of bounds
// Out of bounds - too large
_, err := it.Nth(numbers, 10)
// err: nth: 10 out of bounds
// With strings
words := it.Slice([]string{"hello", "world", "go", "lang"})
element, err := it.Nth(words, 1)
// element: "world", err: nil
// With different integer types
numbers := it.Slice([]int{1, 2, 3, 4, 5})
element, err := it.Nth(numbers, int8(3))
// element: 4, err: nil
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
element, err := it.Nth(people, 1)
// element: {Name: "Bob", Age: 25}, err: nil
```
================================================
FILE: docs/data/it-nthor.md
================================================
---
name: NthOr
slug: nthor
sourceRef: it/find.go#L433
category: it
subCategory: find
signatures:
- "func NthOr[T any, N constraints.Integer](collection iter.Seq[T], nth N, fallback T) T"
playUrl: https://go.dev/play/p/MNweuhpy4Ym
variantHelpers:
- it#find#nthor
similarHelpers:
- core#slice#nthor
position: 590
---
Returns the element at index `nth` of collection. If `nth` is out of bounds, returns the fallback value instead of an error.
Will iterate n times through the sequence.
Examples:
```go
// Get element at specific index
numbers := it.Slice([]int{5, 2, 8, 1, 9})
element := it.NthOr(numbers, 2, 42)
// element: 8
// Get first element (index 0)
first := it.NthOr(numbers, 0, 42)
// first: 5
// Get last element
last := it.NthOr(numbers, 4, 42)
// last: 9
// Out of bounds - negative, returns fallback
element := it.NthOr(numbers, -1, 42)
// element: 42 (fallback)
// Out of bounds - too large, returns fallback
element := it.NthOr(numbers, 10, 42)
// element: 42 (fallback)
// With strings
words := it.Slice([]string{"hello", "world", "go", "lang"})
element := it.NthOr(words, 1, "fallback")
// element: "world"
// Out of bounds with string fallback
element := it.NthOr(words, 10, "fallback")
// element: "fallback"
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
})
fallback := Person{Name: "Default", Age: 0}
element := it.NthOr(people, 1, fallback)
// element: {Name: "Bob", Age: 25}
// Out of bounds with struct fallback
element := it.NthOr(people, 5, fallback)
// element: {Name: "Default", Age: 0}
// With different integer types
numbers := it.Slice([]int{1, 2, 3, 4, 5})
element := it.NthOr(numbers, int8(3), 99)
// element: 4
```
================================================
FILE: docs/data/it-nthorempty.md
================================================
---
name: NthOrEmpty
slug: nthorempty
sourceRef: it/find.go#L444
category: it
subCategory: find
signatures:
- "func NthOrEmpty[T any, N constraints.Integer](collection iter.Seq[T], nth N) T"
playUrl: https://go.dev/play/p/pC0Zhu3EUhe
variantHelpers:
- it#find#nthorempty
similarHelpers:
- core#slice#nthorempty
position: 600
---
Returns the element at index `nth` of collection. If `nth` is out of bounds, returns the zero value (empty value) for that type.
Will iterate n times through the sequence.
Examples:
```go
// Get element at specific index
numbers := it.Slice([]int{5, 2, 8, 1, 9})
element := it.NthOrEmpty(numbers, 2)
// element: 8
// Get first element (index 0)
first := it.NthOrEmpty(numbers, 0)
// first: 5
// Get last element
last := it.NthOrEmpty(numbers, 4)
// last: 9
// Out of bounds - negative, returns zero value
element := it.NthOrEmpty(numbers, -1)
// element: 0 (zero value for int)
// Out of bounds - too large, returns zero value
element := it.NthOrEmpty(numbers, 10)
// element: 0 (zero value for int)
// With strings
words := it.Slice([]string{"hello", "world", "go", "lang"})
element := it.NthOrEmpty(words, 1)
// element: "world"
// Out of bounds with string - returns empty string
element := it.NthOrEmpty(words, 10)
// element: "" (zero value for string)
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
})
element := it.NthOrEmpty(people, 1)
// element: {Name: "Bob", Age: 25}
// Out of bounds with struct - returns zero value
element := it.NthOrEmpty(people, 5)
// element: {Name: "", Age: 0} (zero value for Person)
// With pointers - returns nil when out of bounds
values := it.Slice([]*string{ptr("hello"), ptr("world")})
element := it.NthOrEmpty(values, 1)
// element: pointer to "world"
// Out of bounds with pointer - returns nil
element := it.NthOrEmpty(values, 5)
// element: nil (zero value for *string)
// With different integer types
numbers := it.Slice([]int{1, 2, 3, 4, 5})
element := it.NthOrEmpty(numbers, int8(3))
// element: 4
```
================================================
FILE: docs/data/it-partitionby.md
================================================
---
name: PartitionBy
slug: partitionby
sourceRef: it/seq.go#L26
category: it
subCategory: sequence
signatures:
- "func PartitionBy[T any, K comparable](collection iter.Seq[T], transform func(item T) K) [][]T"
variantHelpers: []
playUrl: https://go.dev/play/p/VxTx8mva28z
similarHelpers:
- core#slice#partitionby
position: 171
---
PartitionBy returns a sequence of elements split into groups. The order of grouped values is
determined by the order they occur in collection. The grouping is generated from the results
of running each element of collection through transform.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
yield(6)
}
result := it.PartitionBy(collection, func(x int) int {
return x % 3
})
// result contains [[1, 4], [2, 5], [3, 6]]
```
================================================
FILE: docs/data/it-product.md
================================================
---
name: Product / ProductBy
slug: product
sourceRef: it/math.go#L70
category: it
subCategory: math
signatures:
- "func Product[T constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T]) T"
- "func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T], iteratee func(item T) R) R"
playUrl: "https://go.dev/play/p/AOMCD1Yl5Bc"
variantHelpers:
- it#math#product
- it#math#productby
similarHelpers:
- core#slice#product
- core#slice#productby
position: 20
---
Multiplies values from a sequence. `ProductBy` applies a transform then multiplies. Returns 1 for empty sequences.
Examples:
```go
seq := it.RangeFrom(1, 4) // 1,2,3,4
p := it.Product(seq)
// p == 24
```
```go
nums := it.RangeFrom(2, 3) // 2,3,4
p := it.ProductBy(nums, func(n int) int { return n - 1 })
// (1*2*3) == 6
```
================================================
FILE: docs/data/it-productby.md
================================================
---
name: ProductBy
slug: productby
sourceRef: it/math.go#L90
category: it
subCategory: math
signatures:
- "func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T], transform func(item T) R) R"
playUrl: "https://go.dev/play/p/dgFCRJrlPHY"
variantHelpers:
- it#math#product
similarHelpers:
- core#slice#productby
- core#slice#product
position: 67
---
Returns the product of values in the collection using the given transform function.
```go
result := it.ProductBy(it.Range(1, 5), func(item int) int {
return item * 2
})
// 384 (2 * 4 * 6 * 8)
```
================================================
FILE: docs/data/it-range.md
================================================
---
name: Range
slug: range
sourceRef: it/math.go#L12
category: it
subCategory: math
signatures:
- "func Range(elementNum int) iter.Seq[int]"
playUrl: "https://go.dev/play/p/79QUZBa8Ukn"
variantHelpers:
- it#math#range
similarHelpers:
- core#slice#range
- it#math#rangefrom
- it#math#rangewithsteps
position: 0
---
Creates a sequence of integers starting from 0. Yields `elementNum` integers, stepping by ±1 depending on sign.
```go
seq := it.Range(4)
var out []int
for v := range seq { out = append(out, v) }
// out == []int{0, 1, 2, 3}
```
================================================
FILE: docs/data/it-rangefrom.md
================================================
---
name: RangeFrom
slug: rangefrom
sourceRef: it/math.go#L25
category: it
subCategory: math
signatures:
- "func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) iter.Seq[T]"
playUrl: "https://go.dev/play/p/WHP_NI5scj9"
variantHelpers:
- it#math#rangefrom
similarHelpers:
- core#slice#rangefrom
- it#math#range
- it#math#rangewithsteps
position: 10
---
Creates a sequence of numbers from start with specified length. Yields `elementNum` values starting from `start`, stepping by ±1 depending on sign.
```go
seq := it.RangeFrom(5, 4)
var result []int
for item := range seq {
result = append(result, item)
}
// result contains [5, 6, 7, 8]
seq2 := it.RangeFrom(10.5, 3)
var result2 []float64
for item := range seq2 {
result2 = append(result2, item)
}
// result2 contains [10.5, 11.5, 12.5]
```
================================================
FILE: docs/data/it-rangewithsteps.md
================================================
---
name: RangeWithSteps
slug: rangewithsteps
sourceRef: it/math.go#L35
category: it
subCategory: math
signatures:
- "func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) iter.Seq[T]"
playUrl: "https://go.dev/play/p/qxm2YNLG0vT"
variantHelpers:
- it#math#rangewithsteps
similarHelpers:
- core#slice#rangewithsteps
- it#math#range
- it#math#rangefrom
position: 20
---
Creates a sequence of numbers from start up to (excluding) end with a custom step. Step set to zero will return an empty sequence.
```go
seq := it.RangeWithSteps(0, 10, 3)
var result []int
for item := range seq {
result = append(result, item)
}
// result contains [0, 3, 6, 9]
seq2 := it.RangeWithSteps(10, 1, -3)
var result2 []int
for item := range seq2 {
result2 = append(result2, item)
}
// result2 contains [10, 7, 4, 1]
seq3 := it.RangeWithSteps(0, 5, 1.5)
var result3 []float64
for item := range seq3 {
result3 = append(result3, item)
}
// result3 contains [0, 1.5, 3, 4.5]
```
================================================
FILE: docs/data/it-reduce.md
================================================
---
name: Reduce
slug: reduce
sourceRef: it/seq.go#L133
category: it
subCategory: sequence
signatures:
- "func Reduce[T, R any](collection iter.Seq[T], accumulator func(agg R, item T) R, initial R) R"
- "func ReduceI[T, R any](collection iter.Seq[T], accumulator func(agg R, item T, index int) R, initial R) R"
playUrl: "https://go.dev/play/p/FmkVUf39ZP_Y"
variantHelpers:
- it#sequence#reduce
- it#sequence#reducei
similarHelpers:
- core#slice#reduce
- core#slice#reducei
- core#slice#reduceright
position: 30
---
Reduces a collection to a single accumulated value by applying an accumulator function to each element starting with an initial value.
### Reduce
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
}
sum := it.Reduce(seq, func(acc int, item int) int {
return acc + item
}, 0)
// sum == 10
```
```go
seq := func(yield func(string) bool) {
_ = yield("hello")
_ = yield("world")
}
concat := it.Reduce(seq, func(acc string, item string) string {
return acc + " " + item
}, "")
// concat == " hello world"
```
### ReduceI
Reduces a collection to a single value by iterating through elements and applying an accumulator function that includes the index.
```go
result := it.ReduceI(it.Range(1, 5), func(agg int, item int, index int) int {
return agg + item*index
}, 0)
// 20 (0*0 + 1*1 + 2*2 + 3*3)
```
================================================
FILE: docs/data/it-reducelast.md
================================================
---
name: ReduceLast
slug: reducelast
sourceRef: it/seq.go#L153
category: it
subCategory: sequence
signatures:
- "func ReduceLast[T, R any](collection iter.Seq[T], accumulator func(agg R, item T) R, initial R) R"
- "func ReduceLastI[T, R any](collection iter.Seq[T], accumulator func(agg R, item T, index int) R, initial R) R"
playUrl: https://go.dev/play/p/D2ZGZ2pN270
variantHelpers:
- it#sequence#reduce
- it#sequence#reducei
- it#sequence#reducelast
- it#sequence#reducelasti
similarHelpers:
- core#slice#reducelast
- core#slice#reducelasti
- core#slice#reduce
position: 54
---
Reduces a collection from right to left, returning a single value.
### ReduceLast
```go
result := it.ReduceLast(it.Range(1, 5), func(agg int, item int) int {
return agg - item
}, 0)
// -10 (0 - 4 - 3 - 2 - 1)
```
### ReduceLastI
Reduces a collection from right to left, returning a single value. The accumulator function includes the index.
```go
result := it.ReduceLastI(it.Range(1, 5), func(agg int, item int, index int) int {
return agg - item*index
}, 0)
// -20 (0 - 4*3 - 3*2 - 2*1 - 1*0)
```
================================================
FILE: docs/data/it-reject.md
================================================
---
name: Reject
slug: reject
sourceRef: it/seq.go#L586
category: it
subCategory: sequence
signatures:
- "func Reject[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I"
- "func RejectI[T any, I ~func(func(T) bool)](collection I, predicate func(item T, index int) bool) I"
- "func RejectMap[T, R any](collection iter.Seq[T], callback func(item T) (R, bool)) iter.Seq[R]"
- "func RejectMapI[T, R any](collection iter.Seq[T], callback func(item T, index int) (R, bool)) iter.Seq[R]"
variantHelpers:
- it#sequence#reject
- it#sequence#rejecti
- it#sequence#rejectmap
- it#sequence#rejectmapi
similarHelpers:
- core#slice#reject
- core#slice#filter
- it#sequence#filter
- it#sequence#filtermap
position: 180
---
Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return true for.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
filtered := it.Reject(collection, func(x int) bool {
return x%2 == 0
})
var result []int
for item := range filtered {
result = append(result, item)
}
// result contains [1, 3]
```
RejectI is the opposite of Filter, this method returns the elements of collection that predicate does not return true for, with index.
```go
collection := func(yield func(string) bool) {
yield("a")
yield("b")
yield("c")
}
filtered := it.RejectI(collection, func(item string, index int) bool {
return index == 1
})
var result []string
for item := range filtered {
result = append(result, item)
}
// result contains ["a", "c"]
```
RejectMap returns a sequence obtained after both filtering and mapping using the given callback function.
The callback function should return two values: the result of the mapping operation and whether the result element should be included or not.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
filtered := it.RejectMap(collection, func(x int) (string, bool) {
return fmt.Sprintf("item-%d", x), x%2 == 0
})
var result []string
for item := range filtered {
result = append(result, item)
}
// result contains ["item-1", "item-3"]
```
================================================
FILE: docs/data/it-rejectmap.md
================================================
---
name: RejectMap
slug: rejectmap
sourceRef: it/seq.go#L608
category: it
subCategory: sequence
signatures:
- "func RejectMap[T, R any](collection iter.Seq[T], callback func(item T) (R, bool)) iter.Seq[R]"
variantHelpers:
- it#sequence#rejectmap
similarHelpers:
- it#sequence#filtermap
- it#sequence#map
- it#sequence#filter
- it#sequence#reject
position: 42
---
Maps elements of a sequence to new values and rejects elements where the callback returns true. Only elements where the second return value is false are included in the result.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
result := it.RejectMap(seq, func(x int) (string, bool) {
if x%2 == 0 {
return fmt.Sprintf("even-%d", x), true // reject even numbers
}
return fmt.Sprintf("odd-%d", x), false
})
// iter.Seq[string] yielding "odd-1", "odd-3"
seq = func(yield func(string) bool) {
yield("a")
yield("")
yield("c")
yield("d")
}
result = it.RejectMap(seq, func(s string) (int, bool) {
if s == "" {
return 0, true // reject empty strings
}
return len(s), false
})
// iter.Seq[int] yielding 1, 1, 1 (length of "a", "c", "d")
```
================================================
FILE: docs/data/it-repeat.md
================================================
---
name: Repeat
slug: repeat
sourceRef: it/seq.go#L384
category: it
subCategory: sequence
signatures:
- "func Repeat[T lo.Clonable[T]](count int, initial T) iter.Seq[T]"
playUrl: https://go.dev/play/p/xs-aq0p_uDP
variantHelpers:
- it#slice#repeatby
similarHelpers:
- core#slice#repeat
- core#slice#repeatby
position: 75
---
Creates a sequence that repeats the initial value count times.
```go
result := it.Repeat(3, "hello")
// ["hello", "hello", "hello"]
```
================================================
FILE: docs/data/it-repeatby.md
================================================
---
name: RepeatBy
slug: repeatby
sourceRef: it/seq.go#L388
category: it
subCategory: sequence
signatures:
- "func RepeatBy[T any](count int, callback func(index int) T) iter.Seq[T]"
playUrl: https://go.dev/play/p/i7BuZQBcUzZ
variantHelpers:
- it#sequence#repeatby
similarHelpers:
- core#slice#repeat
- core#slice#times
- it#sequence#times
position: 130
---
Builds a sequence with values returned by N calls of callback.
```go
result := it.RepeatBy(3, func(index int) string {
return fmt.Sprintf("item-%d", index+1)
})
var output []string
for item := range result {
output = append(output, item)
}
// output contains ["item-1", "item-2", "item-3"]
result2 := it.RepeatBy(5, func(index int) int {
return index * 2
})
var output2 []int
for item := range result2 {
output2 = append(output2, item)
}
// output2 contains [0, 2, 4, 6, 8]
```
================================================
FILE: docs/data/it-replace.md
================================================
---
name: Replace
slug: replace
sourceRef: it/seq.go#L699
category: it
subCategory: slice
signatures:
- "func Replace[T comparable, I ~func(func(T) bool)](collection I, old, nEw T, n int) I"
variantHelpers:
- it#slice#replace
similarHelpers:
- core#slice#replace
position: 190
---
Replace returns a sequence with the first n non-overlapping instances of old replaced by new.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(2)
yield(3)
yield(2)
yield(4)
}
replaced := it.Replace(collection, 2, 99, 2)
var result []int
for item := range replaced {
result = append(result, item)
}
// result contains [1, 99, 99, 3, 2, 4]
```
================================================
FILE: docs/data/it-replaceall.md
================================================
---
name: ReplaceAll
slug: replaceall
sourceRef: it/seq.go#L699
category: it
subCategory: slice
signatures:
- "func ReplaceAll[T comparable, I ~func(func(T) bool)](collection I, old, nEw T) I"
variantHelpers:
- it#slice#replaceall
similarHelpers:
- core#slice#replaceall
position: 191
---
Returns a sequence with all non-overlapping instances of old replaced by new.
```go
// Basic replacement
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(2)
yield(3)
}
replaced := it.ReplaceAll(collection, 2, 99)
var result []int
for item := range replaced {
result = append(result, item)
}
// result: [1, 99, 99, 3]
// With strings - replacing multiple occurrences
strings := func(yield func(string) bool) {
yield("apple")
yield("banana")
yield("apple")
yield("cherry")
yield("apple")
}
replacedStrings := it.ReplaceAll(strings, "apple", "orange")
var fruitResult []string
for item := range replacedStrings {
fruitResult = append(fruitResult, item)
}
// fruitResult: ["orange", "banana", "orange", "cherry", "orange"]
// No matches found
noMatch := it.ReplaceAll(strings, "grape", "kiwi")
var noMatchResult []string
for item := range noMatch {
noMatchResult = append(noMatchResult, item)
}
// noMatchResult: ["apple", "banana", "apple", "cherry", "apple"] (unchanged)
// Empty collection
empty := func(yield func(int) bool) {
// no yields
}
emptyReplaced := it.ReplaceAll(empty, 1, 99)
var emptyResult []int
for item := range emptyReplaced {
emptyResult = append(emptyResult, item)
}
// emptyResult: [] (empty sequence)
```
================================================
FILE: docs/data/it-reverse.md
================================================
---
name: Reverse
slug: reverse
sourceRef: it/seq.go#L366
category: it
subCategory: sequence
signatures:
- "func Reverse[T any, I ~func(func(T) bool)](collection I) I"
playUrl: https://go.dev/play/p/R6-lR8yiNwa
variantHelpers:
- it#sequence#reverse
similarHelpers:
- core#slice#reverse
- it#sequence#shuffle
position: 90
---
Reverses a sequence so the first element becomes the last and the last element becomes the first.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
}
reversed := it.Reverse(seq)
var result []int
for v := range reversed {
result = append(result, v)
}
// result contains 4, 3, 2, 1
```
================================================
FILE: docs/data/it-sample.md
================================================
---
name: Sample
slug: sample
sourceRef: it/find.go#L455
category: it
subCategory: find
signatures:
- "func Sample[T any](collection iter.Seq[T]) T"
playUrl: https://go.dev/play/p/YDJVX0UXYDi
variantHelpers:
- it#find#sample
similarHelpers:
- core#slice#sample
- it#find#samples
- it#find#sampleBy
- it#find#samplesBy
position: 160
---
Returns a random item from collection.
Example:
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
item := it.Sample(seq)
// item is randomly one of: "apple", "banana", "cherry"
// Example with integers
numbers := func(yield func(int) bool) {
_ = yield(10)
_ = yield(20)
_ = yield(30)
_ = yield(40)
}
randomNum := it.Sample(numbers)
// randomNum is randomly one of: 10, 20, 30, 40
// Example with empty sequence - returns zero value
empty := func(yield func(string) bool) {
// no yields
}
emptyResult := it.Sample(empty)
// emptyResult: "" (zero value for string)
// Example with single item
single := func(yield func(int) bool) {
_ = yield(42)
}
singleResult := it.Sample(single)
// singleResult: 42 (always returns 42 since it's the only option)
```
================================================
FILE: docs/data/it-sampleby.md
================================================
---
name: SampleBy
slug: sampleby
sourceRef: it/find.go#L455
category: it
subCategory: find
signatures:
- "func SampleBy[T any](collection iter.Seq[T], randomIntGenerator func(int) int) T"
playUrl: https://go.dev/play/p/QQooySxORib
variantHelpers:
- it#find#sampleby
similarHelpers:
- core#slice#sample
- it#find#sample
- it#find#samples
- it#find#samplesby
position: 160
---
Returns a random item from collection, using a custom random index generator.
Example:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
// Use custom RNG for predictable results (returns first element)
item := it.SampleBy(seq, func(max int) int { return 0 })
// item == 1
```
================================================
FILE: docs/data/it-samples.md
================================================
---
name: Samples
slug: samples
sourceRef: it/find.go#L467
category: it
subCategory: find
signatures:
- "func Samples[T any, I ~func(func(T) bool)](collection I, count int) I"
playUrl: https://go.dev/play/p/GUTFx9LQ8pP
variantHelpers:
- it#find#samples
similarHelpers:
- core#slice#samples
- it#find#sample
- it#find#sampleby
- it#find#samplesby
position: 610
---
Returns N random unique items from collection.
Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
Long input sequences can cause excessive memory usage.
Examples:
```go
// Get 3 random unique items from collection
numbers := it.Slice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
samples := it.Samples(numbers, 3)
// samples: sequence of 3 random unique numbers from 1-10
// Get all items if count equals collection size
numbers := it.Slice([]int{1, 2, 3, 4, 5})
samples := it.Samples(numbers, 5)
// samples: sequence containing all 5 numbers in random order
// Get fewer items than collection size
numbers := it.Slice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
samples := it.Samples(numbers, 3)
// samples: sequence of 3 random unique numbers
// With strings
words := it.Slice([]string{"apple", "banana", "cherry", "date", "elderberry"})
samples := it.Samples(words, 2)
// samples: sequence of 2 random unique words
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
{Name: "Diana", Age: 28},
{Name: "Eve", Age: 32},
})
samples := it.Samples(people, 3)
// samples: sequence of 3 random unique people
// Count larger than collection size - returns all items in random order
numbers := it.Slice([]int{1, 2, 3})
samples := it.Samples(numbers, 10)
// samples: sequence of all 3 numbers in random order
// Zero count - returns empty sequence
numbers := it.Slice([]int{1, 2, 3, 4, 5})
samples := it.Samples(numbers, 0)
// samples: empty sequence
// Negative count - returns empty sequence
numbers := it.Slice([]int{1, 2, 3, 4, 5})
samples := it.Samples(numbers, -1)
// samples: empty sequence
```
================================================
FILE: docs/data/it-samplesby.md
================================================
---
name: SamplesBy
slug: samplesby
sourceRef: it/find.go#L474
category: it
subCategory: find
signatures:
- "func SamplesBy[T any, I ~func(func(T) bool)](collection I, count int, randomIntGenerator func(int) int) I"
playUrl: https://go.dev/play/p/fX2FEtixrVG
variantHelpers:
- it#find#samplesby
similarHelpers:
- core#slice#samplesby
- it#find#sample
- it#find#samples
- it#find#sampleby
position: 620
---
Returns N random unique items from collection, using randomIntGenerator as the random index generator.
Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
Long input sequences can cause excessive memory usage.
Examples:
```go
import (
"math/rand"
"time"
)
// Use default random generator with seed
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
// Get 3 random unique items with custom random generator
numbers := it.Slice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
samples := it.SamplesBy(numbers, 3, rng.Intn)
// samples: sequence of 3 random unique numbers
// Use deterministic random generator for testing
deterministicRng := rand.New(rand.NewSource(42))
samples := it.SamplesBy(numbers, 3, deterministicRng.Intn)
// samples: predictable sequence of 3 unique numbers
// With strings
words := it.Slice([]string{"apple", "banana", "cherry", "date", "elderberry"})
samples := it.SamplesBy(words, 2, rng.Intn)
// samples: sequence of 2 random unique words
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
{Name: "Diana", Age: 28},
{Name: "Eve", Age: 32},
})
samples := it.SamplesBy(people, 3, rng.Intn)
// samples: sequence of 3 random unique people
// Custom random function that biases towards certain items
biasedRandom := func(max int) int {
// Bias towards first half of collection
return rng.Intn(max/2 + 1)
}
samples := it.SamplesBy(numbers, 3, biasedRandom)
// samples: sequence of 3 random unique numbers, biased towards lower indices
// With zero-based modulo function (wraps around)
moduloRandom := func(max int) int {
return rng.Intn(max*3) % max
}
samples := it.SamplesBy(numbers, 3, moduloRandom)
// samples: sequence of 3 random unique numbers
// Test with deterministic function
deterministicFunc := func(max int) int {
return (max - 1) / 2 // Always return middle index
}
samples := it.SamplesBy(numbers, 1, deterministicFunc)
// samples: sequence with single element from middle
```
================================================
FILE: docs/data/it-seqtochannel.md
================================================
---
name: SeqToChannel
slug: seqtochannel
sourceRef: it/channel.go#L12
category: it
subCategory: channel
signatures:
- "func SeqToChannel[T any](bufferSize int, collection iter.Seq[T]) <-chan T"
- "func SeqToChannel2[K, V any](bufferSize int, collection iter.Seq2[K, V]) <-chan Tuple2[K, V]"
playUrl: "https://go.dev/play/p/id3jqJPffT6"
variantHelpers:
- it#channel#seqtochannel
- it#channel#seqtochannel2
similarHelpers:
- it#channel#channeltoseq
position: 0
---
Converts an `iter.Seq` (or `iter.Seq2`) into a read-only channel. Items are sent on a buffered channel and the channel is closed when the sequence ends.
Examples:
```go
// SeqToChannel: stream ints from a sequence
seq := it.Range(5) // 0..4
ch := it.SeqToChannel(2, seq)
var got []int
for v := range ch {
got = append(got, v)
}
// got == []int{0, 1, 2, 3, 4}
```
```go
// SeqToChannel2: stream key/value pairs as Tuple2
m := map[string]int{"a": 1, "b": 2}
kv := it.Entries(m)
ch := it.SeqToChannel2(1, kv)
for pair := range ch {
// pair.A is key, pair.B is value
}
```
================================================
FILE: docs/data/it-sequencestate.md
================================================
---
name: Empty
slug: sequencestate
sourceRef: it/type_manipulation.go#L43
category: it
subCategory: type
signatures:
- "func Empty[T any]() iter.Seq[T]"
- "func IsEmpty[T any](collection iter.Seq[T]) bool"
- "func IsNotEmpty[T any](collection iter.Seq[T]) bool"
- "func CoalesceSeq[T any](v ...iter.Seq[T]) (iter.Seq[T], bool)"
- "func CoalesceSeqOrEmpty[T any](v ...iter.Seq[T]) iter.Seq[T]"
variantHelpers:
- it#type#empty
- it#type#isempty
- it#type#isnotempty
- it#type#coalesceseq
- it#type#coalesceseqorempty
similarHelpers:
- core#type#isempty
- core#type#isnotempty
- core#condition#coalesce
position: 250
---
Empty returns an empty sequence.
```go
empty := it.Empty[int]()
var result []int
for item := range empty {
result = append(result, item)
}
// result is empty []
```
IsEmpty returns true if the sequence is empty.
```go
emptySeq := it.Empty[int]()
notEmptySeq := func(yield func(int) bool) {
yield(1)
}
fmt.Println(it.IsEmpty(emptySeq)) // true
fmt.Println(it.IsEmpty(notEmptySeq)) // false
```
IsNotEmpty returns true if the sequence is not empty.
```go
emptySeq := it.Empty[int]()
notEmptySeq := func(yield func(int) bool) {
yield(1)
}
fmt.Println(it.IsNotEmpty(emptySeq)) // false
fmt.Println(it.IsNotEmpty(notEmptySeq)) // true
```
CoalesceSeq returns the first non-empty sequence.
```go
empty1 := it.Empty[int]()
empty2 := it.Empty[int]()
notEmpty := func(yield func(int) bool) {
yield(1)
yield(2)
}
result, found := it.CoalesceSeq(empty1, empty2, notEmpty)
// found is true, result is the notEmpty sequence
result2, found2 := it.CoalesceSeq(empty1, empty2)
// found2 is false, result2 is an empty sequence
```
CoalesceSeqOrEmpty returns the first non-empty sequence.
```go
empty1 := it.Empty[int]()
empty2 := it.Empty[int]()
notEmpty := func(yield func(int) bool) {
yield(1)
yield(2)
}
result := it.CoalesceSeqOrEmpty(empty1, empty2, notEmpty)
// result is the notEmpty sequence
result2 := it.CoalesceSeqOrEmpty(empty1, empty2)
// result2 is an empty sequence
```
================================================
FILE: docs/data/it-shuffle.md
================================================
---
name: Shuffle
slug: shuffle
sourceRef: it/seq.go#L357
category: it
subCategory: sequence
signatures:
- "func Shuffle[T any, I ~func(func(T) bool)](collection I) I"
playUrl: https://go.dev/play/p/3WOx-ukGvKK
variantHelpers:
- it#sequence#shuffle
similarHelpers:
- core#slice#shuffle
- it#sequence#reverse
position: 130
---
Returns a sequence of shuffled values using Fisher-Yates algorithm. Note: this requires collecting all elements in memory.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(5)
}
shuffled := it.Shuffle(seq)
var result []int
for v := range shuffled {
result = append(result, v)
}
// result contains the same elements in random order
```
================================================
FILE: docs/data/it-slice.md
================================================
---
name: Slice
slug: slice
sourceRef: it/seq.go#L680
category: it
subCategory: sequence
signatures:
- "func Slice[T any, I ~func(func(T) bool)](collection I, start, end int) I"
playUrl: "https://go.dev/play/p/5WqJN9-zv"
variantHelpers:
- it#slice#drop
similarHelpers:
- core#slice#slice
- core#slice#chunk
position: 80
---
Returns a sub-sequence from start index to end index (exclusive).
```go
result := it.Slice(it.Range(1, 10), 2, 5)
// [3, 4, 5]
```
================================================
FILE: docs/data/it-sliding.md
================================================
---
name: Sliding
slug: sliding
sourceRef: it/seq.go#L329
category: it
subCategory: sequence
signatures:
- "func Sliding[T any](collection iter.Seq[T], size, step int) iter.Seq[[]T]"
playUrl: https://go.dev/play/p/mzhO4CZeiik
variantHelpers:
- it#sequence#sliding
similarHelpers:
- core#slice#sliding
position: 80
---
Creates a sequence of sliding windows of a given size with a given step. If step equals size, windows don't overlap.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
yield(6)
yield(7)
yield(8)
}
windows := it.Sliding(seq, 2, 3)
var result [][]int
for w := range windows {
result = append(result, w)
}
// result contains [1 2], [4 5], [7 8]
```
================================================
FILE: docs/data/it-some.md
================================================
---
name: Some
slug: some
sourceRef: it/intersect.go#L52
category: it
subCategory: intersect
signatures:
- "func Some[T comparable](collection iter.Seq[T], subset ...T) bool"
playUrl: "https://go.dev/play/p/KmX-fXictQl"
variantHelpers:
- it#intersect#some
similarHelpers:
- core#slice#some
- it#intersect#every
- it#intersect#none
position: 40
---
Returns true if at least one element of a subset is contained in a collection.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(5)
}
hasAny := it.Some(seq, 2, 6)
// hasAny == true (2 is in collection)
```
```go
seq := func(yield func(string) bool) {
_ = yield("apple")
_ = yield("banana")
_ = yield("cherry")
}
hasAny := it.Some(seq, "orange", "grape")
// hasAny == false (neither is in collection)
```
================================================
FILE: docs/data/it-someby.md
================================================
---
name: SomeBy
slug: someby
sourceRef: it/intersect.go#L56
category: it
subCategory: intersect
signatures:
- "func SomeBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool"
playUrl: https://go.dev/play/p/PDT6dWCl7Md
variantHelpers:
- it#intersect#someby
similarHelpers:
- core#slice#someby
position: 670
---
Returns true if the predicate returns true for any of the elements in the collection.
If the collection is empty SomeBy returns false.
Will iterate through the entire sequence if predicate never returns true.
Examples:
```go
// Check if any number is even
numbers := it.Slice([]int{1, 3, 5, 7, 9})
hasEven := it.SomeBy(numbers, func(n int) bool { return n%2 == 0 })
// hasEven: false
numbers = it.Slice([]int{1, 3, 5, 8, 9})
hasEven = it.SomeBy(numbers, func(n int) bool { return n%2 == 0 })
// hasEven: true
// Check if any string starts with specific prefix
words := it.Slice([]string{"hello", "world", "go", "lang"})
hasGoPrefix := it.SomeBy(words, func(s string) bool { return strings.HasPrefix(s, "go") })
// hasGoPrefix: true
hasPythonPrefix := it.SomeBy(words, func(s string) bool { return strings.HasPrefix(s, "python") })
// hasPythonPrefix: false
// Check if any person is a teenager
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
})
hasTeenager := it.SomeBy(people, func(p Person) bool { return p.Age >= 13 && p.Age <= 19 })
// hasTeenager: false
teenagers := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 16}, // Teenager
{Name: "Charlie", Age: 35},
})
hasTeenager = it.SomeBy(teenagers, func(p Person) bool { return p.Age >= 13 && p.Age <= 19 })
// hasTeenager: true
// Check if any number is greater than 100
numbers = it.Slice([]int{1, 3, 5, 7, 9})
hasLargeNumber := it.SomeBy(numbers, func(n int) bool { return n > 100 })
// hasLargeNumber: false
numbers = it.Slice([]int{1, 3, 5, 150, 9})
hasLargeNumber = it.SomeBy(numbers, func(n int) bool { return n > 100 })
// hasLargeNumber: true
// Check if any string contains a substring
strings := it.Slice([]string{"hello", "world", "go", "lang"})
hasWorld := it.SomeBy(strings, func(s string) bool { return strings.Contains(s, "world") })
// hasWorld: true
hasPython := it.SomeBy(strings, func(s string) bool { return strings.Contains(s, "python") })
// hasPython: false
// Empty collection returns false
empty := it.Slice([]int{})
hasAny := it.SomeBy(empty, func(n int) bool { return n > 0 })
// hasAny: false
// Check if any email is from specific domain
emails := it.Slice([]string{"user@example.com", "test@gmail.com", "admin@site.net"})
hasGmail := it.SomeBy(emails, func(email string) bool { return strings.HasSuffix(email, "@gmail.com") })
// hasGmail: true
hasYahoo := it.SomeBy(emails, func(email string) bool { return strings.HasSuffix(email, "@yahoo.com") })
// hasYahoo: false
// Check if any string is palindrome
words := it.Slice([]string{"level", "hello", "world", "radar"})
hasPalindrome := it.SomeBy(words, func(s string) bool {
return s == reverseString(s)
})
// hasPalindrome: true ("level" and "radar" are palindromes)
```
================================================
FILE: docs/data/it-splice.md
================================================
---
name: Splice
slug: splice
sourceRef: it/seq.go#L744
category: it
subCategory: sequence
signatures:
- "func Splice[T any, I ~func(func(T) bool)](collection I, index int, elements ...T) I"
variantHelpers:
- it#sequence#splice
similarHelpers:
- it#sequence#slice
- it#sequence#replace
- it#sequence#replaceall
- core#slice#splice
position: 122
---
Inserts elements into a sequence at the specified index. Returns a new sequence with the elements inserted.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(5)
}
result := it.Splice(seq, 2, 3, 4)
// iter.Seq[int] yielding 1, 2, 3, 4, 5
result = it.Splice(seq, 0, 0)
// iter.Seq[int] yielding 0, 1, 2, 5 (insert at beginning)
result = it.Splice(seq, 3, 6, 7)
// iter.Seq[int] yielding 1, 2, 5, 6, 7 (insert at end)
seq = func(yield func(string) bool) {
yield("a")
yield("c")
}
result = it.Splice(seq, 1, "b")
// iter.Seq[string] yielding "a", "b", "c"
result = it.Splice(seq, 1, "x", "y")
// iter.Seq[string] yielding "a", "x", "y", "c"
```
================================================
FILE: docs/data/it-subset.md
================================================
---
name: Subset
slug: subset
sourceRef: it/seq.go#L667
category: it
subCategory: sequence
signatures:
- "func Subset[T any, I ~func(func(T) bool)](collection I, offset, length int) I"
variantHelpers:
- it#sequence#subset
similarHelpers:
- it#sequence#slice
- it#sequence#drop
- it#sequence#dropright
- core#slice#slice
position: 120
---
Returns a subset of a sequence starting from the specified offset with the given length.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
result := it.Subset(seq, 1, 3)
// iter.Seq[int] yielding 2, 3, 4
result = it.Subset(seq, 0, 2)
// iter.Seq[int] yielding 1, 2
result = it.Subset(seq, 3, 10)
// iter.Seq[int] yielding 4, 5 (returns available elements)
result = it.Subset(seq, 10, 5)
// iter.Seq[int] yielding nothing (offset beyond sequence)
seq = func(yield func(string) bool) {
yield("a")
yield("b")
yield("c")
yield("d")
}
result = it.Subset(seq, 1, 2)
// iter.Seq[string] yielding "b", "c"
```
================================================
FILE: docs/data/it-sum.md
================================================
---
name: Sum / SumBy
slug: sum
sourceRef: it/math.go#L59
category: it
subCategory: math
signatures:
- "func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T]) T"
- "func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T], iteratee func(item T) R) R"
playUrl: "https://go.dev/play/p/nHbGFOEIeTa"
variantHelpers:
- it#math#sum
- it#math#sumby
similarHelpers:
- core#slice#sum
- core#slice#sumby
- core#slice#product
- core#slice#productby
position: 10
---
Sums values from a sequence. `SumBy` applies a transform and sums the results. Returns 0 for empty sequences.
Examples:
```go
sum := it.Sum(it.RangeFrom(1, 5))
// 1 + 2 + 3 + 4 + 5 == 15
```
```go
type User struct { Name string; Score int }
users := func(yield func(User) bool) {
_ = yield(User{"a", 3})
_ = yield(User{"b", 7})
}
total := it.SumBy(iter.Seq[User](users), func(u User) int { return u.Score })
// total == 10
```
================================================
FILE: docs/data/it-sumby.md
================================================
---
name: SumBy
slug: sumby
sourceRef: it/math.go#L74
category: it
subCategory: math
signatures:
- "func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T], transform func(item T) R) R"
playUrl: https://go.dev/play/p/ZNiqXNMu5QP
variantHelpers:
- it#math#sumby
similarHelpers:
- core#slice#sumby
- core#slice#sum
position: 65
---
Returns the sum of values in the collection using the given transform function.
```go
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35},
})
result := it.SumBy(people, func(p Person) int {
return p.Age
})
// 90
```
================================================
FILE: docs/data/it-take.md
================================================
---
name: Take
slug: take
sourceRef: it/seq.go#L682
category: it
subCategory: sequence
signatures:
- "func Take[T any, I ~func(func(T) bool)](collection I, n int) I"
variantHelpers:
- it#sequence#take
similarHelpers:
- core#slice#take
position: 110
---
Takes the first n elements from a sequence.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
result := it.Take(seq, 2)
var out []int
for v := range result {
out = append(out, v)
}
// out contains [1, 2]
```
================================================
FILE: docs/data/it-takefilter.md
================================================
---
name: TakeFilter
slug: takefilter
sourceRef: it/seq.go#L725
category: it
subCategory: sequence
signatures:
- "func TakeFilter[T any, I ~func(func(T) bool)](collection I, n int, predicate func(item T) bool) I"
- "func TakeFilterI[T any, I ~func(func(T) bool)](collection I, n int, predicate func(item T, index int) bool) I"
playUrl: https://go.dev/play/p/Db68Bhu4MCA
variantHelpers:
- it#sequence#takefilter
- it#sequence#takefilteri
similarHelpers:
- core#slice#takefilter
position: 130
---
Filters elements and takes the first n matches. Stops once n matches are found.
### TakeFilter
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
yield(6)
}
result := it.TakeFilter(seq, 2, func(x int) bool {
return x%2 == 0
})
var out []int
for v := range result {
out = append(out, v)
}
// out contains [2, 4]
```
### TakeFilterI
```go
result := it.TakeFilterI(seq, 2, func(x, index int) bool {
return x%2 == 0 && index < 4
})
// out contains [2, 4]
```
================================================
FILE: docs/data/it-takewhile.md
================================================
---
name: TakeWhile
slug: takewhile
sourceRef: it/seq.go#L706
category: it
subCategory: sequence
signatures:
- "func TakeWhile[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I"
variantHelpers:
- it#sequence#takewhile
similarHelpers:
- core#slice#takewhile
position: 120
---
Takes elements from the beginning of a sequence while the predicate returns true.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
}
result := it.TakeWhile(seq, func(x int) bool {
return x < 3
})
var out []int
for v := range result {
out = append(out, v)
}
// out contains [1, 2]
```
================================================
FILE: docs/data/it-times.md
================================================
---
name: Times
slug: times
sourceRef: it/seq.go#L202
category: it
subCategory: sequence
signatures:
- "func Times[T any](count int, callback func(index int) T) iter.Seq[T]"
playUrl: https://go.dev/play/p/0W4IRzQuCEc
variantHelpers:
- it#sequence#times
similarHelpers:
- core#slice#times
- it#math#range
position: 70
---
Invokes a callback function n times and returns a sequence of the results.
Examples:
```go
seq := it.Times(5, func(index int) int {
return index * 2
})
var result []int
for v := range seq {
result = append(result, v)
}
// result contains 0, 2, 4, 6, 8
```
```go
seq := it.Times(3, func(index int) string {
return fmt.Sprintf("item-%d", index+1)
})
var result []string
for v := range seq {
result = append(result, v)
}
// result contains "item-1", "item-2", "item-3"
```
================================================
FILE: docs/data/it-toanyseq.md
================================================
---
name: ToAnySeq
slug: toanyseq
sourceRef: it/type_manipulation.go#L11
category: it
subCategory: type
signatures:
- "func ToAnySeq[T any](collection iter.Seq[T]) iter.Seq[any]"
variantHelpers:
- it#type#fromanyseq
playUrl: "https://go.dev/play/p/ktE4IMXDMxv"
similarHelpers:
- core#type#toany
position: 243
---
ToAnySeq returns a sequence with all elements mapped to `any` type.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
}
anySeq := it.ToAnySeq(collection)
var result []any
for item := range anySeq {
result = append(result, item)
}
// result contains [1, 2, 3] as any type
```
================================================
FILE: docs/data/it-toseqptr.md
================================================
---
name: ToSeqPtr
slug: toseqptr
sourceRef: it/type_manipulation.go#L11
category: it
subCategory: type
signatures:
- "func ToSeqPtr[T any](collection iter.Seq[T]) iter.Seq[*T]"
playUrl: "https://go.dev/play/p/70BcKpDcOKm"
variantHelpers: []
similarHelpers:
- core#type#toptr
position: 240
---
ToSeqPtr returns a sequence of pointers to each value.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
}
ptrs := it.ToSeqPtr(collection)
var result []*int
for ptr := range ptrs {
result = append(result, ptr)
}
// result contains pointers to 1, 2, 3
```
================================================
FILE: docs/data/it-trim.md
================================================
---
name: Trim
slug: trim
sourceRef: it/seq.go#L778
category: it
subCategory: string
signatures:
- "func Trim[T comparable, I ~func(func(T) bool)](collection I, cutset ...T) I"
playUrl: https://go.dev/play/p/k0VCcilk4V1
variantHelpers:
- it#string#trimfirst
- it#string#trimlast
similarHelpers:
- core#string#trim
position: 262
---
Trim removes all the leading and trailing cutset from the collection.
```go
collection := func(yield func(int) bool) {
yield(0)
yield(0)
yield(1)
yield(2)
yield(3)
yield(0)
yield(0)
}
trimmed := it.Trim(collection, 0)
var result []int
for item := range trimmed {
result = append(result, item)
}
// result contains [1, 2, 3]
```
================================================
FILE: docs/data/it-trimfirst.md
================================================
---
name: TrimFirst
slug: trimfirst
sourceRef: it/seq.go#L778
category: it
subCategory: string
signatures:
- "func TrimFirst[T comparable, I ~func(func(T) bool)](collection I, cutset ...T) I"
playUrl: https://go.dev/play/p/4D4Ke5C5MwH
variantHelpers:
- it#string#trim
- it#string#trimlast
similarHelpers: []
position: 263
---
TrimFirst removes all the leading cutset from the collection.
```go
collection := func(yield func(int) bool) {
yield(0)
yield(0)
yield(1)
yield(2)
yield(3)
}
trimmed := it.TrimFirst(collection, 0)
var result []int
for item := range trimmed {
result = append(result, item)
}
// result contains [1, 2, 3]
```
================================================
FILE: docs/data/it-trimlast.md
================================================
---
name: TrimLast
slug: trimlast
sourceRef: it/seq.go#L778
category: it
subCategory: string
signatures:
- "func TrimLast[T comparable, I ~func(func(T) bool)](collection I, cutset ...T) I"
playUrl: https://go.dev/play/p/GQLhnaeW0gd
variantHelpers:
- it#string#trim
- it#string#trimfirst
similarHelpers: []
position: 264
---
TrimLast removes all the trailing cutset from the collection.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(0)
yield(0)
}
trimmed := it.TrimLast(collection, 0)
var result []int
for item := range trimmed {
result = append(result, item)
}
// result contains [1, 2, 3]
```
================================================
FILE: docs/data/it-trimprefix.md
================================================
---
name: TrimPrefix
slug: trimprefix
sourceRef: it/seq.go#L778
category: it
subCategory: string
signatures:
- "func TrimPrefix[T comparable, I ~func(func(T) bool)](collection I, prefix []T) I"
playUrl: https://go.dev/play/p/Pce4zSPnThY
variantHelpers: []
similarHelpers:
- core#string#trimprefix
position: 265
---
TrimPrefix removes all the leading prefix from the collection.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(1)
yield(2)
yield(3)
}
trimmed := it.TrimPrefix(collection, []int{1, 2})
var result []int
for item := range trimmed {
result = append(result, item)
}
// result contains [1, 2, 3]
```
================================================
FILE: docs/data/it-trimsuffix.md
================================================
---
name: TrimSuffix
slug: trimsuffix
sourceRef: it/seq.go#L778
category: it
subCategory: string
signatures:
- "func TrimSuffix[T comparable, I ~func(func(T) bool)](collection I, suffix []T) I"
playUrl: https://go.dev/play/p/s9nwy9helEi
variantHelpers: []
similarHelpers:
- core#string#trimsuffix
position: 266
---
TrimSuffix removes all the trailing suffix from the collection.
```go
collection := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(3)
yield(4)
}
trimmed := it.TrimSuffix(collection, []int{3, 4})
var result []int
for item := range trimmed {
result = append(result, item)
}
// result contains [1, 2]
```
================================================
FILE: docs/data/it-union.md
================================================
---
name: Union
slug: union
sourceRef: it/intersect.go#L136
category: it
subCategory: intersect
signatures:
- "func Union[T comparable, I ~func(func(T) bool)](lists ...I) I"
playUrl: "https://go.dev/play/p/ImIoFNpSUUB"
variantHelpers:
- it#intersect#union
similarHelpers:
- core#slice#union
- it#intersect#intersect
position: 20
---
Returns all distinct elements from given collections (union of all collections).
Examples:
```go
seq1 := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
seq2 := func(yield func(int) bool) {
_ = yield(2)
_ = yield(3)
_ = yield(4)
}
seq3 := func(yield func(int) bool) {
_ = yield(3)
_ = yield(5)
}
union := it.Union(seq1, seq2, seq3)
var result []int
for v := range union {
result = append(result, v)
}
// result contains 1, 2, 3, 4, 5 (all distinct elements)
```
================================================
FILE: docs/data/it-uniq.md
================================================
---
name: Uniq
slug: uniq
sourceRef: it/seq.go#L216
category: it
subCategory: sequence
signatures:
- "func Uniq[T comparable, I ~func(func(T) bool)](collection I) I"
playUrl: https://go.dev/play/p/D-SenTW-ipj
variantHelpers:
- it#sequence#uniq
similarHelpers:
- core#slice#uniq
- it#sequence#uniqby
position: 50
---
Returns a duplicate-free version of a sequence, removing consecutive duplicates.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(2)
_ = yield(3)
_ = yield(2)
_ = yield(2)
}
uniqueSeq := it.Uniq(seq)
var result []int
for v := range uniqueSeq {
result = append(result, v)
}
// result contains 1, 2, 3, 2 (consecutive duplicates removed)
```
================================================
FILE: docs/data/it-uniqby.md
================================================
---
name: UniqBy
slug: uniqby
sourceRef: it/seq.go#L225
category: it
subCategory: sequence
signatures:
- "func UniqBy[T any, U comparable, I ~func(func(T) bool)](collection I, transform func(item T) U) I"
playUrl: https://go.dev/play/p/HKrt3AvwMTR
variantHelpers:
- it#slice#uniq
similarHelpers:
- core#slice#uniqby
- core#slice#uniq
position: 45
---
Returns a sequence with duplicate elements removed based on a transform function.
```go
result := it.UniqBy(it.Range(1, 7), func(item int) int {
return item % 3
})
// [1, 2, 3]
```
================================================
FILE: docs/data/it-uniqkeys.md
================================================
---
name: UniqKeys
slug: uniqkeys
sourceRef: it/map.go#L26
category: it
subCategory: map
signatures:
- "func UniqKeys[K comparable, V any](in ...map[K]V) iter.Seq[K]"
playUrl: "https://go.dev/play/p/_NicwfgAHbO"
variantHelpers:
- it#map#uniqkeys
similarHelpers:
- core#slice#uniqkeys
position: 800
---
Creates a sequence of unique keys from multiple maps.
Will allocate a map large enough to hold all distinct input keys.
Long input sequences with heterogeneous keys can cause excessive memory usage.
Examples:
```go
// Single map
m1 := map[string]int{
"apple": 1,
"banana": 2,
"cherry": 3,
}
uniqueKeys := it.UniqKeys(m1)
// uniqueKeys: sequence with "apple", "banana", "cherry"
// Multiple maps with duplicate keys
m1 := map[string]int{
"apple": 1,
"banana": 2,
}
m2 := map[string]int{
"banana": 3,
"cherry": 4,
"apple": 5,
}
uniqueKeys = it.UniqKeys(m1, m2)
// uniqueKeys: sequence with "apple", "banana", "cherry" (no duplicates)
// Maps with integer keys
scores1 := map[int]string{
1: "Alice",
2: "Bob",
3: "Charlie",
}
scores2 := map[int]string{
3: "David",
4: "Eve",
1: "Frank",
}
uniqueKeys = it.UniqKeys(scores1, scores2)
// uniqueKeys: sequence with 1, 2, 3, 4
// Maps with struct keys
type Person struct {
Name string
Age int
}
people1 := map[Person]bool{
{Name: "Alice", Age: 30}: true,
{Name: "Bob", Age: 25}: true,
}
people2 := map[Person]bool{
{Name: "Bob", Age: 25}: false, // Same struct
{Name: "Charlie", Age: 35}: true,
}
uniqueKeys = it.UniqKeys(people1, people2)
// uniqueKeys: sequence with {Alice 30}, {Bob 25}, {Charlie 35}
// Empty maps
empty1 := map[string]int{}
empty2 := map[string]int{}
uniqueKeys = it.UniqKeys(empty1, empty2)
// uniqueKeys: empty sequence
// Mix of empty and non-empty maps
m1 := map[string]int{"a": 1}
empty := map[string]int{}
m2 := map[string]int{"b": 2}
uniqueKeys = it.UniqKeys(m1, empty, m2)
// uniqueKeys: sequence with "a", "b"
// Maps with same keys but different values
m1 := map[string]int{
"key1": 10,
"key2": 20,
}
m2 := map[string]int{
"key1": 100, // Same key, different value
"key3": 30,
}
uniqueKeys = it.UniqKeys(m1, m2)
// uniqueKeys: sequence with "key1", "key2", "key3" (key1 appears once)
// Large number of maps
maps := make([]map[int]string, 100)
for i := range maps {
maps[i] = map[int]string{i: fmt.Sprintf("value%d", i)}
}
uniqueKeys = it.UniqKeys(maps...)
// uniqueKeys: sequence with keys 0, 1, 2, ..., 99
```
================================================
FILE: docs/data/it-uniqvalues.md
================================================
---
name: UniqValues
slug: uniqvalues
sourceRef: it/map.go#L59
category: it
subCategory: map
signatures:
- "func UniqValues[K, V comparable](in ...map[K]V) iter.Seq[V]"
playUrl: https://go.dev/play/p/QQv4zGrk-fF
variantHelpers:
- it#map#uniqvalues
similarHelpers:
- core#slice#uniqvalues
position: 810
---
Creates a sequence of unique values from multiple maps.
Will allocate a map large enough to hold all distinct input values.
Long input sequences with heterogeneous values can cause excessive memory usage.
Examples:
```go
// Single map
m1 := map[string]int{
"apple": 1,
"banana": 2,
"cherry": 3,
}
uniqueValues := it.UniqValues(m1)
// uniqueValues: sequence with 1, 2, 3
// Multiple maps with duplicate values
m1 := map[string]int{
"apple": 1,
"banana": 2,
}
m2 := map[string]int{
"orange": 1, // Same value as "apple"
"grape": 3,
}
uniqueValues = it.UniqValues(m1, m2)
// uniqueValues: sequence with 1, 2, 3 (no duplicates)
// Maps with string values
scores1 := map[int]string{
1: "Alice",
2: "Bob",
3: "Charlie",
}
scores2 := map[int]string{
4: "Alice", // Same value
5: "David",
6: "Bob", // Same value
}
uniqueValues = it.UniqValues(scores1, scores2)
// uniqueValues: sequence with "Alice", "Bob", "Charlie", "David"
// Maps with boolean values
boolMaps := []map[string]bool{
{"enabled": true, "debug": false},
{"test": true, "prod": false}, // Same values
{"dev": true, "staging": false}, // Same values
}
uniqueValues = it.UniqValues(boolMaps...)
// uniqueValues: sequence with true, false
// Maps with float values
prices1 := map[string]float64{
"apple": 1.99,
"banana": 2.99,
}
prices2 := map[string]float64{
"orange": 1.99, // Same price as apple
"grape": 3.99,
}
uniqueValues = it.UniqValues(prices1, prices2)
// uniqueValues: sequence with 1.99, 2.99, 3.99
// Maps with struct values
type Product struct {
Name string
Price float64
}
products1 := map[int]Product{
1: {Name: "Book", Price: 19.99},
2: {Name: "Pen", Price: 1.99},
}
products2 := map[int]Product{
3: {Name: "Notebook", Price: 19.99}, // Same price as book
4: {Name: "Book", Price: 19.99}, // Same struct as products1[1]
}
uniqueValues = it.UniqValues(products1, products2)
// uniqueValues: sequence with {Book 19.99}, {Pen 1.99}, {Notebook 19.99}
// Maps with pointer values
type Person struct {
Name string
}
alice := &Person{Name: "Alice"}
bob := &Person{Name: "Bob"}
people1 := map[string]*Person{
"user1": alice,
"user2": bob,
}
people2 := map[string]*Person{
"user3": alice, // Same pointer
"user4": &Person{Name: "Charlie"},
}
uniqueValues = it.UniqValues(people1, people2)
// uniqueValues: sequence with pointers to Alice, Bob, Charlie
// Empty maps
empty1 := map[string]int{}
empty2 := map[string]int{}
uniqueValues = it.UniqValues(empty1, empty2)
// uniqueValues: empty sequence
// Mix of empty and non-empty maps
m1 := map[string]int{"a": 10}
empty := map[string]int{}
m2 := map[string]int{"b": 20, "c": 10} // 10 is duplicate
uniqueValues = it.UniqValues(m1, empty, m2)
// uniqueValues: sequence with 10, 20
// Maps with same values from different keys
m1 := map[string]int{
"key1": 100,
"key2": 200,
}
m2 := map[string]int{
"key3": 100, // Same value as key1
"key4": 200, // Same value as key2
"key5": 300,
}
uniqueValues = it.UniqValues(m1, m2)
// uniqueValues: sequence with 100, 200, 300
// Large number of duplicate values
maps := make([]map[int]string, 10)
for i := range maps {
maps[i] = map[int]string{
i: "common", // All maps have the same value
i + 100: fmt.Sprintf("unique_%d", i),
}
}
uniqueValues = it.UniqValues(maps...)
// uniqueValues: sequence with "common" and 10 unique values
```
================================================
FILE: docs/data/it-values.md
================================================
---
name: Values
slug: values
sourceRef: it/map.go#L44
category: it
subCategory: map
signatures:
- "func Values[K comparable, V any](in ...map[K]V) iter.Seq[V]"
playUrl: https://go.dev/play/p/-WehUfGtC6C
variantHelpers:
- it#map#values
similarHelpers:
- core#slice#values
- it#map#keys
- it#map#uniqvalues
position: 10
---
Creates a sequence of the map values. Accepts multiple maps and concatenates their values.
Examples:
```go
m1 := map[string]int{
"apple": 1,
"banana": 2,
}
m2 := map[string]int{
"cherry": 3,
"date": 4,
}
valuesSeq := it.Values(m1, m2)
var result []int
for v := range valuesSeq {
result = append(result, v)
}
// result contains values from both maps
```
================================================
FILE: docs/data/it-window.md
================================================
---
name: Window
slug: window
sourceRef: it/seq.go#L318
category: it
subCategory: sequence
signatures:
- "func Window[T any](collection iter.Seq[T], size int) iter.Seq[[]T]"
playUrl: https://go.dev/play/p/_1BzQYtKBhi
variantHelpers:
- it#sequence#window
similarHelpers:
- core#slice#window
position: 70
---
Creates a sequence of sliding windows of a given size. Each window overlaps with the previous one by size-1 elements.
```go
seq := func(yield func(int) bool) {
yield(1)
yield(2)
yield(3)
yield(4)
yield(5)
}
windows := it.Window(seq, 3)
var result [][]int
for w := range windows {
result = append(result, w)
}
// result contains [1 2 3], [2 3 4], [3 4 5]
```
================================================
FILE: docs/data/it-without.md
================================================
---
name: Without
slug: without
sourceRef: it/intersect.go#L155
category: it
subCategory: intersect
signatures:
- "func Without[T comparable, I ~func(func(T) bool)](collection I, exclude ...T) I"
playUrl: "https://go.dev/play/p/LbN55AVBZ7h"
variantHelpers:
- it#intersect#without
similarHelpers:
- core#slice#without
- it#intersect#intersect
position: 50
---
Returns a sequence excluding all given values.
Examples:
```go
seq := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
_ = yield(4)
_ = yield(5)
}
filtered := it.Without(seq, 2, 4)
var result []int
for v := range filtered {
result = append(result, v)
}
// result contains 1, 3, 5
```
================================================
FILE: docs/data/it-withoutby.md
================================================
---
name: WithoutBy
slug: withoutby
sourceRef: it/intersect.go#L159
category: it
subCategory: intersect
signatures:
- "func WithoutBy[T any, K comparable, I ~func(func(T) bool)](collection I, transform func(item T) K, exclude ...K) I"
playUrl: "https://go.dev/play/p/Hm734hnLnLI"
variantHelpers:
- it#intersect#withoutby
similarHelpers:
- core#slice#withoutby
position: 700
---
Filters a sequence by excluding elements whose extracted keys match any in the exclude list.
Returns a sequence containing only the elements whose keys are not in the exclude list.
Will allocate a map large enough to hold all distinct excludes.
Examples:
```go
// Exclude people by specific ages
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
{Name: "Diana", Age: 30}, // Same age as Alice
})
filtered := it.WithoutBy(people, func(p Person) int { return p.Age }, 30)
// filtered: sequence with Bob (age 25) and Charlie (age 35)
// Exclude strings by their length
words := it.Slice([]string{"hello", "world", "hi", "go", "bye"})
filtered := it.WithoutBy(words, func(s string) int { return len(s) }, 2)
// filtered: sequence with "hello" (5), "world" (5), "bye" (3)
// excludes "hi" and "go" (both length 2)
// Exclude items by first letter
items := it.Slice([]string{"apple", "apricot", "banana", "blueberry", "cherry"})
filtered := it.WithoutBy(items, func(s string) byte { return s[0] }, 'b')
// filtered: sequence with "apple", "apricot", "cherry"
// excludes "banana" and "blueberry" (both start with 'b')
// Exclude numbers by modulo
numbers := it.Slice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
filtered := it.WithoutBy(numbers, func(n int) int { return n % 3 }, 1)
// filtered: sequence with numbers where n % 3 != 1
// excludes 1, 4, 7, 10 (all have remainder 1)
// Exclude emails by domain
type Email struct {
Address string
}
emails := it.Slice([]Email{
{Address: "user1@example.com"},
{Address: "user2@gmail.com"},
{Address: "user3@example.com"},
{Address: "user4@yahoo.com"},
})
filtered := it.WithoutBy(emails, func(e Email) string {
parts := strings.Split(e.Address, "@")
if len(parts) > 1 {
return parts[1]
}
return ""
}, "example.com")
// filtered: sequence with gmail.com and yahoo.com emails
// Exclude orders by customer ID
type Order struct {
ID string
CustomerID string
ProductID string
}
orders := it.Slice([]Order{
{ID: "1", CustomerID: "A", ProductID: "X"},
{ID: "2", CustomerID: "B", ProductID: "Y"},
{ID: "3", CustomerID: "A", ProductID: "Z"},
{ID: "4", CustomerID: "C", ProductID: "W"},
})
filtered := it.WithoutBy(orders, func(o Order) string { return o.CustomerID }, "A")
// filtered: sequence with orders from customers B and C
// Exclude strings by case-insensitive value
words := it.Slice([]string{"Hello", "hello", "WORLD", "world", "Go"})
filtered := it.WithoutBy(words, func(s string) string { return strings.ToLower(s) }, "hello")
// filtered: sequence with "WORLD", "world", "Go"
// excludes both "Hello" and "hello" (both become "hello" when lowercased)
// Exclude dates by month
import "time"
dates := it.Slice([]time.Time{
time.Date(2023, 1, 15, 0, 0, 0, 0, time.UTC),
time.Date(2023, 2, 20, 0, 0, 0, 0, time.UTC),
time.Date(2023, 1, 25, 0, 0, 0, 0, time.UTC),
time.Date(2023, 3, 10, 0, 0, 0, 0, time.UTC),
})
filtered := it.WithoutBy(dates, func(t time.Time) time.Month { return t.Month() }, time.January)
// filtered: sequence with February and March dates
// Exclude multiple values
numbers := it.Slice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
filtered := it.WithoutBy(numbers, func(n int) int { return n % 2 }, 0, 1)
// filtered: empty sequence (all numbers are either even (0) or odd (1))
// Exclude by custom function result
type Product struct {
Name string
Price float64
}
products := it.Slice([]Product{
{Name: "Book", Price: 19.99},
{Name: "Pen", Price: 1.99},
{Name: "Laptop", Price: 999.99},
{Name: "Pencil", Price: 0.99},
})
// Exclude products with price < $10
filtered := it.WithoutBy(products, func(p Product) string {
if p.Price < 10 {
return "cheap"
}
return "expensive"
}, "cheap")
// filtered: sequence with "Book" and "Laptop"
```
================================================
FILE: docs/data/it-withoutnth.md
================================================
---
name: WithoutNth
slug: withoutnth
sourceRef: it/intersect.go#L167
category: it
subCategory: intersect
signatures:
- "func WithoutNth[T comparable, I ~func(func(T) bool)](collection I, nths ...int) I"
playUrl: "https://go.dev/play/p/KGE7Lpsk18P"
variantHelpers:
- it#intersect#withoutnth
similarHelpers:
- core#slice#withoutnth
position: 710
---
Returns a sequence excluding the elements at the specified indices.
Will allocate a map large enough to hold all distinct indices.
Examples:
```go
// Exclude elements at specific indices
numbers := it.Slice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
filtered := it.WithoutNth(numbers, 2, 5, 8)
// filtered: sequence with 1, 2, 4, 5, 7, 8, 10
// excludes elements at indices 2 (3), 5 (6), and 8 (9)
// Exclude single element
words := it.Slice([]string{"hello", "world", "go", "lang", "awesome"})
filtered := it.WithoutNth(words, 1)
// filtered: sequence with "hello", "go", "lang", "awesome"
// excludes "world" at index 1
// Exclude first element
numbers = it.Slice([]int{10, 20, 30, 40, 50})
filtered = it.WithoutNth(numbers, 0)
// filtered: sequence with 20, 30, 40, 50
// excludes 10 at index 0
// Exclude last element
numbers = it.Slice([]int{10, 20, 30, 40, 50})
filtered = it.WithoutNth(numbers, 4)
// filtered: sequence with 10, 20, 30, 40
// excludes 50 at index 4
// Exclude multiple elements including duplicates
words = it.Slice([]string{"a", "b", "c", "d", "e", "f", "g"})
filtered = it.WithoutNth(words, 1, 3, 1, 5)
// filtered: sequence with "a", "c", "e", "g"
// excludes elements at indices 1 (b), 3 (d), and 5 (f)
// index 1 appears twice but element at index 1 is only excluded once
// Exclude with negative indices (out of bounds, no effect)
numbers = it.Slice([]int{1, 2, 3, 4, 5})
filtered = it.WithoutNth(numbers, -1, 2)
// filtered: sequence with 1, 2, 4, 5
// excludes element at index 2 (3), ignores -1
// Exclude with indices larger than collection (out of bounds, no effect)
numbers = it.Slice([]int{1, 2, 3, 4, 5})
filtered = it.WithoutNth(numbers, 10, 2)
// filtered: sequence with 1, 2, 4, 5
// excludes element at index 2 (3), ignores 10
// Exclude all elements
numbers = it.Slice([]int{1, 2, 3, 4, 5})
filtered = it.WithoutNth(numbers, 0, 1, 2, 3, 4)
// filtered: empty sequence
// Exclude no indices (returns original)
numbers = it.Slice([]int{1, 2, 3, 4, 5})
filtered = it.WithoutNth(numbers)
// filtered: sequence with 1, 2, 3, 4, 5 (unchanged)
// With structs
type Person struct {
Name string
Age int
}
people := it.Slice([]Person{
{Name: "Alice", Age: 30},
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
{Name: "Diana", Age: 28},
{Name: "Eve", Age: 32},
})
filtered := it.WithoutNth(people, 1, 3)
// filtered: sequence with Alice, Charlie, Eve
// excludes Bob at index 1 and Diana at index 3
// With mixed valid and invalid indices
words = it.Slice([]string{"first", "second", "third", "fourth"})
filtered = it.WithoutNth(words, -1, 1, 10, 2)
// filtered: sequence with "first", "fourth"
// excludes "second" at index 1 and "third" at index 2
// ignores -1 and 10 as they are out of bounds
// Exclude from empty collection
empty := it.Slice([]int{})
filtered := it.WithoutNth(empty, 0, 1, 2)
// filtered: empty sequence
```
================================================
FILE: docs/data/it-zipbyx.md
================================================
---
name: ZipByX
slug: zipbyx
sourceRef: it/tuples.go#L295
category: it
subCategory: tuple
signatures:
- "func ZipBy2[T1, T2, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], transform func(T1, T2) R) iter.Seq[R]"
- "func ZipBy3[T1, T2, T3, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], transform func(T1, T2, T3) R) iter.Seq[R]"
- "func ZipBy4[T1, T2, T3, T4, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], transform func(T1, T2, T3, T4) R) iter.Seq[R]"
- "func ZipBy5[T1, T2, T3, T4, T5, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], transform func(T1, T2, T3, T4, T5) R) iter.Seq[R]"
- "func ZipBy6[T1, T2, T3, T4, T5, T6, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], seq6 iter.Seq[T6], transform func(T1, T2, T3, T4, T5, T6) R) iter.Seq[R]"
- "func ZipBy7[T1, T2, T3, T4, T5, T6, T7, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], seq6 iter.Seq[T6], seq7 iter.Seq[T7], transform func(T1, T2, T3, T4, T5, T6, T7) R) iter.Seq[R]"
- "func ZipBy8[T1, T2, T3, T4, T5, T6, T7, T8, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], seq6 iter.Seq[T6], seq7 iter.Seq[T7], seq8 iter.Seq[T8], transform func(T1, T2, T3, T4, T5, T6, T7, T8) R) iter.Seq[R]"
- "func ZipBy9[T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], seq3 iter.Seq[T3], seq4 iter.Seq[T4], seq5 iter.Seq[T5], seq6 iter.Seq[T6], seq7 iter.Seq[T7], seq8 iter.Seq[T8], seq9 iter.Seq[T9], transform func(T1, T2, T3, T4, T5, T6, T7, T8, T9) R) iter.Seq[R]"
playUrl: https://go.dev/play/p/y03uqMEAi1E
variantHelpers:
- it#tuple#zipbyx
similarHelpers:
- core#tuple#zipbyx
- it#tuple#zipx
- core#slice#map
position: 10
---
Creates a sequence of transformed elements from multiple sequences using a transform function. When sequences are different lengths, shorter sequences are padded with zero values before transformation.
Variants: `ZipBy2..ZipBy9`
```go
seq1 := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
seq2 := func(yield func(string) bool) {
_ = yield("a")
_ = yield("b")
}
zipped := it.ZipBy2(seq1, seq2, func(i int, s string) string {
return fmt.Sprintf("%d-%s", i, s)
})
var result []string
for item := range zipped {
result = append(result, item)
}
// result contains ["1-a", "2-b", "3-"]
```
================================================
FILE: docs/data/it-zipx.md
================================================
---
name: ZipX
slug: zipx
sourceRef: it/tuples.go#L14
category: it
subCategory: tuple
signatures:
- "func Zip2[T1, T2 any](list1 iter.Seq[T1], list2 iter.Seq[T2]) iter.Seq[lo.Tuple2[T1, T2]]"
- "func Zip3[T1, T2, T3 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3]) iter.Seq[lo.Tuple3[T1, T2, T3]]"
- "func Zip4[T1, T2, T3, T4 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4]) iter.Seq[lo.Tuple4[T1, T2, T3, T4]]"
- "func Zip5[T1, T2, T3, T4, T5 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5]) iter.Seq[lo.Tuple5[T1, T2, T3, T4, T5]]"
- "func Zip6[T1, T2, T3, T4, T5, T6 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5], list6 iter.Seq[T6]) iter.Seq[lo.Tuple6[T1, T2, T3, T4, T5, T6]]"
- "func Zip7[T1, T2, T3, T4, T5, T6, T7 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5], list6 iter.Seq[T6], list7 iter.Seq[T7]) iter.Seq[lo.Tuple7[T1, T2, T3, T4, T5, T6, T7]]"
- "func Zip8[T1, T2, T3, T4, T5, T6, T7, T8 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5], list6 iter.Seq[T6], list7 iter.Seq[T7], list8 iter.Seq[T8]) iter.Seq[lo.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]"
- "func Zip9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](list1 iter.Seq[T1], list2 iter.Seq[T2], list3 iter.Seq[T3], list4 iter.Seq[T4], list5 iter.Seq[T5], list6 iter.Seq[T6], list7 iter.Seq[T7], list8 iter.Seq[T8], list9 iter.Seq[T9]) iter.Seq[lo.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]"
playUrl: "https://go.dev/play/p/U5nBWvR8eUZ"
variantHelpers:
- it#tuple#zipx
similarHelpers:
- core#tuple#zipx
- it#tuple#crossjoinx
- it#tuple#zipbyx
position: 0
---
Creates a sequence of grouped elements from multiple sequences. The resulting sequence has length equal to the shortest input sequence.
Variants: `Zip2..Zip9`
```go
seq1 := func(yield func(int) bool) {
_ = yield(1)
_ = yield(2)
_ = yield(3)
}
seq2 := func(yield func(string) bool) {
_ = yield("a")
_ = yield("b")
_ = yield("c")
}
zipped := it.Zip2(seq1, seq2)
var result []string
for tuple := range zipped {
result = append(result, fmt.Sprintf("%d%s", tuple.A, tuple.B))
}
// result contains "1a", "2b", "3c"
```
================================================
FILE: docs/data/mutable-fill.md
================================================
---
name: Fill
slug: fill
sourceRef: mutable/slice_example_test.go#L72
category: mutable
subCategory: slice
signatures:
- "func Fill[T any, Slice ~[]T](collection Slice, initial T)"
playUrl: https://go.dev/play/p/VwR34GzqEub
variantHelpers:
- mutable#slice#fill
similarHelpers:
- core#slice#fill
- core#slice#repeat
- core#slice#times
position: 60
---
Fills all elements of a slice with the specified initial value. The operation modifies the slice in place.
```go
slice := make([]int, 5)
lo.Fill(slice, 42)
// []int{42, 42, 42, 42, 42}
slice = make([]string, 3)
lo.Fill(slice, "default")
// []string{"default", "default", "default"}
slice = make([]bool, 4)
lo.Fill(slice, true)
// []bool{true, true, true, true}
slice = []int{1, 2, 3, 4, 5}
lo.Fill(slice, 0)
// []int{0, 0, 0, 0, 0}
```
================================================
FILE: docs/data/mutable-filter.md
================================================
---
name: Filter
slug: filter
sourceRef: mutable/slice.go#L11
category: mutable
subCategory: slice
playUrl: https://go.dev/play/p/0jY3Z0B7O_5
similarHelpers:
- core#slice#filter
- core#slice#filterreject
- core#slice#filtermap
- parallel#slice#filter
- it#chunkstring
variantHelpers:
- "mutable#slice#filter"
- "mutable#slice#filteri"
position: 0
signatures:
- "func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice"
- "func FilterI[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice"
---
Modifies the input slice in place, keeping only the elements for which the predicate returns true. Order is preserved. Returns the shortened slice view backed by the original array.
Variants: `FilterI` accepts an index-aware predicate `(item T, index int) bool`.
```go
import lom "github.com/samber/lo/mutable"
list := []int{1, 2, 3, 4}
kept := lom.Filter(list, func(x int) bool {
return x%2 == 0
})
// kept -> []int{2, 4}
// list is modified in place (backed by same array)
```
Another example with a struct type:
```go
type User struct { Name string; Active bool }
users := []User{{"Alex", true}, {"Bob", false}, {"Carol", true}}
active := lom.Filter(users, func(u User) bool {
return u.Active
})
// active -> []User{{"Alex", true}, {"Carol", true}}
// users underlying storage is reused
```
Index-aware variant (FilterI):
```go
// keep even-indexed items whose length >= 2
list2 := []string{"a", "bb", "ccc", "dddd"}
kept2 := lom.FilterI(list2, func(s string, i int) bool {
return i%2 == 0 && len(s) >= 2
})
// kept2 -> []string{"ccc"}
nums := []int{10, 11, 12, 13, 14}
evenPos := lom.FilterI(nums, func(_ int, i int) bool {
return i%2 == 0
})
// evenPos -> []int{10, 12, 14}
```
================================================
FILE: docs/data/mutable-map.md
================================================
---
name: Map
slug: map
sourceRef: mutable/slice.go#L41
category: mutable
subCategory: slice
playUrl: https://go.dev/play/p/0jY3Z0B7O_5
variantHelpers:
- "mutable#slice#map"
- "mutable#slice#mapi"
similarHelpers:
- core#slice#map
- core#slice#mapkeys
- core#slice#mapvalues
- parallel#slice#map
position: 10
signatures:
- "func Map[T any, Slice ~[]T](collection Slice, transform func(item T) T)"
- "func MapI[T any, Slice ~[]T](collection Slice, transform func(item T, index int) T)"
---
Transforms each element in the slice by applying the transform function in place. The length remains unchanged; values are overwritten in the same backing array.
Variants: `MapI` accepts an index-aware mapper `(item T, index int) T`.
```go
import lom "github.com/samber/lo/mutable"
list := []int{1, 2, 3, 4}
lom.Map(list, func(x int) int {
return x * 2
})
// list -> []int{2, 4, 6, 8}
```
Mapping strings:
```go
words := []string{"go", "LoDash", "lo"}
lom.Map(words, func(s string) string {
return strings.ToUpper(s)
})
// words -> []string{"GO", "LODASH", "LO"}
```
Index-aware variant (MapI):
```go
nums := []int{10, 11, 12}
// add index to each number
lom.MapI(nums, func(x int, i int) int {
return x + i
})
// nums -> []int{10, 12, 14}
vals := []string{"a", "b", "c", "d"}
lom.MapI(vals, func(s string, i int) string {
if i%2 == 0 { return s + "!" }
return s
})
// vals -> []string{"a!", "b", "c!", "d"}
```
================================================
FILE: docs/data/mutable-reverse.md
================================================
---
name: Reverse
slug: reverse
sourceRef: mutable/slice.go#L65
category: mutable
subCategory: slice
playUrl: https://go.dev/play/p/O-M5pmCRgzV
variantHelpers:
- mutable#slice#reverse
similarHelpers:
- core#slice#reverse
position: 30
signatures:
- "func Reverse[T any, Slice ~[]T](collection Slice)"
---
Reverses the slice in place so the first element becomes the last, the second becomes the second-to-last, and so on.
```go
import lom "github.com/samber/lo/mutable"
list := []int{0, 1, 2, 3, 4, 5}
lom.Reverse(list)
// list -> []int{5, 4, 3, 2, 1, 0}
```
With custom types:
```go
type Point struct{ X, Y int }
pts := []Point{{0,0}, {1,1}, {2,2}}
lom.Reverse(pts)
// pts -> []Point{{2,2}, {1,1}, {0,0}}
```
================================================
FILE: docs/data/mutable-shuffle.md
================================================
---
name: Shuffle
slug: shuffle
sourceRef: mutable/slice.go#L57
category: mutable
subCategory: slice
playUrl: https://go.dev/play/p/2xb3WdLjeSJ
variantHelpers:
- "mutable#slice#shuffle"
similarHelpers:
- core#slice#shuffle
- core#slice#sample
- core#slice#samples
position: 20
signatures:
- "func Shuffle[T any, Slice ~[]T](collection Slice)"
---
Shuffles the slice in place using the Fisher–Yates algorithm. The operation mutates the original slice order.
```go
import lom "github.com/samber/lo/mutable"
list := []int{0, 1, 2, 3, 4, 5}
lom.Shuffle(list)
// list order is randomized, e.g., []int{1, 4, 0, 3, 5, 2}
```
With strings:
```go
names := []string{"alice", "bob", "carol"}
lom.Shuffle(names)
// names order is randomized
```
================================================
FILE: docs/data/parallel-foreach.md
================================================
---
name: ForEach
slug: foreach
sourceRef: parallel/slice.go#L32
category: parallel
subCategory: slice
playUrl: https://go.dev/play/p/sCJaB3quRMC
similarHelpers:
- core#slice#foreach
- parallel#slice#map
position: 10
signatures:
- "func ForEach[T any](collection []T, callback func(item T, index int))"
variantHelpers:
- parallel#slice#foreach
---
Iterates over elements of a collection and invokes the callback for each element in parallel.
```go
import (
"fmt"
lop "github.com/samber/lo/parallel"
)
lop.ForEach([]string{"hello", "world"}, func(x string, _ int) {
fmt.Println(x)
})
// prints lines in any order depending on scheduling
```
Useful for fire-and-forget work like publishing events or independent side effects:
```go
type Job struct{ ID int }
jobs := []Job{{1}, {2}, {3}}
lop.ForEach(jobs, func(j Job, _ int) {
// process each job concurrently
// send(j)
})
```
================================================
FILE: docs/data/parallel-groupby.md
================================================
---
name: GroupBy
slug: groupby
sourceRef: parallel/slice.go#L73
category: parallel
subCategory: slice
playUrl: "https://go.dev/play/p/EkyvA0gw4dj"
similarHelpers:
- core#slice#groupby
- core#slice#groupbymap
- parallel#slice#partitionby
position: 30
signatures:
- "func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) map[U]Slice"
variantHelpers:
- parallel#slice#groupby
---
Returns a map composed of keys generated from the results of running each element of the collection through the predicate. The predicate is called in parallel. Values keep the input order within each group.
```go
import (
lop "github.com/samber/lo/parallel"
)
groups := lop.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i % 3
})
// map[int][]int{0: {0, 3}, 1: {1, 4}, 2: {2, 5}}
```
Custom key types work as long as they are comparable:
```go
type Kind string
groups2 := lop.GroupBy([]string{"go", "rust", "java"}, func(s string) Kind {
if len(s) <= 2 { return "short" }
return "long"
})
// map[Kind][]string{"short": {"go"}, "long": {"rust", "java"}}
```
================================================
FILE: docs/data/parallel-map.md
================================================
---
name: Map
slug: map
sourceRef: parallel/slice.go#L8
category: parallel
subCategory: slice
playUrl: https://go.dev/play/p/sCJaB3quRMC
similarHelpers:
- core#slice#map
- mutable#slice#map
- parallel#slice#foreach
position: 0
signatures:
- "func Map[T any, R any](collection []T, transform func(item T, index int) R) []R"
variantHelpers:
- parallel#slice#map
---
Manipulates a slice and transforms it into a slice of another type. The transform is called in parallel and results are written back in the original order.
```go
import (
"strconv"
lop "github.com/samber/lo/parallel"
)
out := lop.Map([]int64{1, 2, 3, 4}, func(x int64, i int) string {
return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}
```
Parallel execution is useful when the predicate is slow or I/O-bound:
```go
import (
"net/http"
"io"
lop "github.com/samber/lo/parallel"
)
urls := []string{"https://example.com/a", "https://example.com/b"}
pages := lop.Map(urls, func(u string, _ int) string {
resp, _ := http.Get(u)
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
return string(b)
})
// pages keeps the same order as urls
```
================================================
FILE: docs/data/parallel-partitionby.md
================================================
---
name: PartitionBy
slug: partitionby
sourceRef: parallel/slice.go#L92
category: parallel
subCategory: slice
playUrl: "https://go.dev/play/p/GwBQdMgx2nC"
similarHelpers:
- core#slice#partitionby
- parallel#slice#groupby
position: 40
signatures:
- "func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K) []Slice"
variantHelpers:
- parallel#slice#partitionby
---
Returns a slice of groups where contiguous elements sharing the same key are batched together. Groups are created from the results of running each element of the collection through the predicate. The predicate is called in parallel and the order of groups follows their first appearance in the collection.
```go
import (
lop "github.com/samber/lo/parallel"
)
groups := lop.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string {
if x < 0 { return "negative" }
if x%2 == 0 { return "even" }
return "odd"
})
// [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}
```
Works with any comparable key type:
```go
type Bucket int
parts := lop.PartitionBy([]int{1,2,2,3,3,3}, func(x int) Bucket {
return Bucket(x)
})
// [][]int{{1}, {2,2}, {3,3,3}}
```
================================================
FILE: docs/data/parallel-times.md
================================================
---
name: Times
slug: times
sourceRef: parallel/slice.go#L49
category: parallel
subCategory: slice
playUrl: https://go.dev/play/p/ZNnWNcJ4Au-
similarHelpers:
- core#slice#times
position: 20
signatures:
- "func Times[T any](count int, iteratee func(index int) T) []T"
variantHelpers:
- parallel#slice#times
---
Invokes the predicate count times, returning a slice of the results of each invocation. The predicate is called in parallel with the index as argument.
```go
import (
"strconv"
lop "github.com/samber/lo/parallel"
)
nums := lop.Times(5, func(i int) string {
return strconv.Itoa(i)
})
// []string{"0", "1", "2", "3", "4"}
```
Great for generating data concurrently:
```go
ids := lop.Times(10, func(i int) string {
return fmt.Sprintf("item-%d", i)
})
```
================================================
FILE: docs/data/simd-clamp.md
================================================
---
name: Clamp
slug: clamp
sourceRef: exp/simd/math_avx.go#L453
category: exp
subCategory: simd
similarHelpers:
- exp#simd#clamp
position: 40
signatures:
- "func ClampInt8x16[T ~int8, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt8x32[T ~int8, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt8x64[T ~int8, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt16x8[T ~int16, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt16x16[T ~int16, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt16x32[T ~int16, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt32x4[T ~int32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt32x8[T ~int32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt32x16[T ~int32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt64x2[T ~int64, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt64x4[T ~int64, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampInt64x8[T ~int64, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint8x16[T ~uint8, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint8x32[T ~uint8, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint8x64[T ~uint8, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint16x8[T ~uint16, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint16x16[T ~uint16, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint16x32[T ~uint16, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint32x4[T ~uint32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint32x8[T ~uint32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint32x16[T ~uint32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint64x2[T ~uint64, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint64x4[T ~uint64, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampUint64x8[T ~uint64, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampFloat32x4[T ~float32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampFloat32x8[T ~float32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampFloat32x16[T ~float32, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampFloat64x2[T ~float64, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampFloat64x4[T ~float64, Slice ~[]T](collection Slice, min, max T) Slice"
- "func ClampFloat64x8[T ~float64, Slice ~[]T](collection Slice, min, max T) Slice"
---
Clamps each element in a collection between min and max values using SIMD instructions. The suffix (x2, x4, x8, x16, x32, x64) indicates the number of lanes processed simultaneously.
> **Note**: Choose the variant matching your CPU's capabilities. Higher lane counts provide better performance but require newer CPU support.
```go
// Using AVX2 variant (32 lanes at once) - Intel Haswell+ / AMD Excavator+
result := simd.ClampInt8x32([]int8{1, 5, 10, 15, 20}, 5, 15)
// []int8{5, 5, 10, 15, 15}
```
```go
// Using AVX-512 variant (16 lanes at once) - Intel Skylake-X+
result := simd.ClampFloat32x16([]float32{0.5, 1.5, 2.5, 3.5}, 1.0, 3.0)
// []float32{1.0, 1.5, 2.5, 3.0}
```
```go
// Using AVX variant (8 lanes at once) - works on all amd64
result := simd.ClampInt16x8([]int16{100, 150, 200, 250}, 120, 220)
// []int16{120, 150, 200, 220}
```
```go
// Empty collection returns empty collection
result := simd.ClampUint32x4([]uint32{}, 10, 100)
// []uint32{}
```
================================================
FILE: docs/data/simd-contains.md
================================================
---
name: Contains
slug: contains
sourceRef: exp/simd/intersect_avx512.go#L9
category: exp
subCategory: simd
similarHelpers:
- exp#simd#contains
position: 0
signatures:
- "func ContainsInt8x16[T ~int8](collection []T, target T) bool"
- "func ContainsInt8x32[T ~int8](collection []T, target T) bool"
- "func ContainsInt8x64[T ~int8](collection []T, target T) bool"
- "func ContainsInt16x8[T ~int16](collection []T, target T) bool"
- "func ContainsInt16x16[T ~int16](collection []T, target T) bool"
- "func ContainsInt16x32[T ~int16](collection []T, target T) bool"
- "func ContainsInt32x4[T ~int32](collection []T, target T) bool"
- "func ContainsInt32x8[T ~int32](collection []T, target T) bool"
- "func ContainsInt32x16[T ~int32](collection []T, target T) bool"
- "func ContainsInt64x2[T ~int64](collection []T, target T) bool"
- "func ContainsInt64x4[T ~int64](collection []T, target T) bool"
- "func ContainsInt64x8[T ~int64](collection []T, target T) bool"
- "func ContainsUint8x16[T ~uint8](collection []T, target T) bool"
- "func ContainsUint8x32[T ~uint8](collection []T, target T) bool"
- "func ContainsUint8x64[T ~uint8](collection []T, target T) bool"
- "func ContainsUint16x8[T ~uint16](collection []T, target T) bool"
- "func ContainsUint16x16[T ~uint16](collection []T, target T) bool"
- "func ContainsUint16x32[T ~uint16](collection []T, target T) bool"
- "func ContainsUint32x4[T ~uint32](collection []T, target T) bool"
- "func ContainsUint32x8[T ~uint32](collection []T, target T) bool"
- "func ContainsUint32x16[T ~uint32](collection []T, target T) bool"
- "func ContainsUint64x2[T ~uint64](collection []T, target T) bool"
- "func ContainsUint64x4[T ~uint64](collection []T, target T) bool"
- "func ContainsUint64x8[T ~uint64](collection []T, target T) bool"
- "func ContainsFloat32x4[T ~float32](collection []T, target T) bool"
- "func ContainsFloat32x8[T ~float32](collection []T, target T) bool"
- "func ContainsFloat32x16[T ~float32](collection []T, target T) bool"
- "func ContainsFloat64x2[T ~float64](collection []T, target T) bool"
- "func ContainsFloat64x4[T ~float64](collection []T, target T) bool"
- "func ContainsFloat64x8[T ~float64](collection []T, target T) bool"
---
Checks if a target value is present in a collection using SIMD instructions. The suffix (x4, x8, x16, x32, x64) indicates the number of lanes processed simultaneously.
> **Note**: Choose the variant matching your CPU's capabilities. Higher lane counts provide better performance but require newer CPU support.
```go
// Using AVX2 variant (32 lanes at once) - Intel Haswell+ / AMD Excavator+
found := simd.ContainsInt8x32([]int8{1, 2, 3, 4, 5}, 3)
// true
```
```go
// Using AVX variant (16 lanes at once) - works on all amd64
found := simd.ContainsInt64x2([]int64{1000000, 2000000, 3000000}, 2000000)
// true
```
```go
// Using AVX-512 variant (64 lanes at once) - Intel Skylake-X+
found := simd.ContainsUint8x64([]uint8{10, 20, 30, 40, 50}, 30)
// true
```
```go
// Float32 with AVX2 (8 lanes at once)
found := simd.ContainsFloat32x8([]float32{1.1, 2.2, 3.3, 4.4}, 3.3)
// true
```
```go
// Empty collection returns false
found := simd.ContainsInt16x16([]int16{}, 5)
// false
```
================================================
FILE: docs/data/simd-max.md
================================================
---
name: Max
slug: max
sourceRef: exp/simd/math_avx.go#L1279
category: exp
subCategory: simd
similarHelpers:
- exp#simd#max
position: 30
signatures:
- "func MaxInt8x16[T ~int8](collection []T) T"
- "func MaxInt8x32[T ~int8](collection []T) T"
- "func MaxInt8x64[T ~int8](collection []T) T"
- "func MaxInt16x8[T ~int16](collection []T) T"
- "func MaxInt16x16[T ~int16](collection []T) T"
- "func MaxInt16x32[T ~int16](collection []T) T"
- "func MaxInt32x4[T ~int32](collection []T) T"
- "func MaxInt32x8[T ~int32](collection []T) T"
- "func MaxInt32x16[T ~int32](collection []T) T"
- "func MaxInt64x2[T ~int64](collection []T) T"
- "func MaxInt64x4[T ~int64](collection []T) T"
- "func MaxInt64x8[T ~int64](collection []T) T"
- "func MaxUint8x16[T ~uint8](collection []T) T"
- "func MaxUint8x32[T ~uint8](collection []T) T"
- "func MaxUint8x64[T ~uint8](collection []T) T"
- "func MaxUint16x8[T ~uint16](collection []T) T"
- "func MaxUint16x16[T ~uint16](collection []T) T"
- "func MaxUint16x32[T ~uint16](collection []T) T"
- "func MaxUint32x4[T ~uint32](collection []T) T"
- "func MaxUint32x8[T ~uint32](collection []T) T"
- "func MaxUint32x16[T ~uint32](collection []T) T"
- "func MaxUint64x2[T ~uint64](collection []T) T"
- "func MaxUint64x4[T ~uint64](collection []T) T"
- "func MaxUint64x8[T ~uint64](collection []T) T"
- "func MaxFloat32x4[T ~float32](collection []T) T"
- "func MaxFloat32x8[T ~float32](collection []T) T"
- "func MaxFloat32x16[T ~float32](collection []T) T"
- "func MaxFloat64x2[T ~float64](collection []T) T"
- "func MaxFloat64x4[T ~float64](collection []T) T"
- "func MaxFloat64x8[T ~float64](collection []T) T"
---
Finds the maximum value in a collection using SIMD instructions. The suffix (x2, x4, x8, x16, x32, x64) indicates the number of lanes processed simultaneously.
> **Note**: Choose the variant matching your CPU's capabilities. Higher lane counts provide better performance but require newer CPU support.
```go
// Using AVX2 variant (32 lanes at once) - Intel Haswell+ / AMD Excavator+
max := simd.MaxInt8x32([]int8{5, 2, 8, 1, 9})
// 9
```
```go
// Using AVX-512 variant (16 lanes at once) - Intel Skylake-X+
max := simd.MaxFloat32x16([]float32{3.5, 1.2, 4.8, 2.1})
// 4.8
```
```go
// Using AVX variant (4 lanes at once) - works on all amd64
max := simd.MaxInt32x4([]int32{100, 50, 200, 75})
// 200
```
```go
// Empty collection returns 0
max := simd.MaxUint16x8([]uint16{})
// 0
```
================================================
FILE: docs/data/simd-mean.md
================================================
---
name: Mean
slug: mean
sourceRef: exp/simd/math_avx.go#L352
category: exp
subCategory: simd
similarHelpers:
- exp#simd#mean
- exp#simd#meanby
position: 10
signatures:
- "func MeanInt8x16[T ~int8](collection []T) T"
- "func MeanInt8x32[T ~int8](collection []T) T"
- "func MeanInt8x64[T ~int8](collection []T) T"
- "func MeanInt16x8[T ~int16](collection []T) T"
- "func MeanInt16x16[T ~int16](collection []T) T"
- "func MeanInt16x32[T ~int16](collection []T) T"
- "func MeanInt32x4[T ~int32](collection []T) T"
- "func MeanInt32x8[T ~int32](collection []T) T"
- "func MeanInt32x16[T ~int32](collection []T) T"
- "func MeanInt64x2[T ~int64](collection []T) T"
- "func MeanInt64x4[T ~int64](collection []T) T"
- "func MeanInt64x8[T ~int64](collection []T) T"
- "func MeanUint8x16[T ~uint8](collection []T) T"
- "func MeanUint8x32[T ~uint8](collection []T) T"
- "func MeanUint8x64[T ~uint8](collection []T) T"
- "func MeanUint16x8[T ~uint16](collection []T) T"
- "func MeanUint16x16[T ~uint16](collection []T) T"
- "func MeanUint16x32[T ~uint16](collection []T) T"
- "func MeanUint32x4[T ~uint32](collection []T) T"
- "func MeanUint32x8[T ~uint32](collection []T) T"
- "func MeanUint32x16[T ~uint32](collection []T) T"
- "func MeanUint64x2[T ~uint64](collection []T) T"
- "func MeanUint64x4[T ~uint64](collection []T) T"
- "func MeanUint64x8[T ~uint64](collection []T) T"
- "func MeanFloat32x4[T ~float32](collection []T) T"
- "func MeanFloat32x8[T ~float32](collection []T) T"
- "func MeanFloat32x16[T ~float32](collection []T) T"
- "func MeanFloat64x2[T ~float64](collection []T) T"
- "func MeanFloat64x4[T ~float64](collection []T) T"
- "func MeanFloat64x8[T ~float64](collection []T) T"
---
Calculates the arithmetic mean of a collection using SIMD instructions. The suffix (x2, x4, x8, x16, x32, x64) indicates the number of lanes processed simultaneously.
> **Note**: Choose the variant matching your CPU's capabilities. Higher lane counts provide better performance but require newer CPU support.
```go
// Using AVX2 variant (32 lanes at once) - Intel Haswell+ / AMD Excavator+
mean := simd.MeanInt8x32([]int8{1, 2, 3, 4, 5})
// 3
```
```go
// Using AVX-512 variant (16 lanes at once) - Intel Skylake-X+
mean := simd.MeanFloat32x16([]float32{1.0, 2.0, 3.0, 4.0})
// 2.5
```
```go
// Using AVX variant (8 lanes at once) - works on all amd64
mean := simd.MeanInt16x8([]int16{10, 20, 30, 40})
// 25
```
```go
// Empty collection returns 0
mean := simd.MeanUint32x4([]uint32{})
// 0
```
================================================
FILE: docs/data/simd-meanby.md
================================================
---
name: MeanBy
slug: meanby
sourceRef: exp/simd/math.go#L1006
category: exp
subCategory: simd
similarHelpers:
- exp#simd#mean
- exp#simd#sumby
position: 30
signatures:
- "func MeanByInt8[T any, R ~int8](collection []T, iteratee func(item T) R) R"
- "func MeanByInt16[T any, R ~int16](collection []T, iteratee func(item T) R) R"
- "func MeanByInt32[T any, R ~int32](collection []T, iteratee func(item T) R) R"
- "func MeanByInt64[T any, R ~int64](collection []T, iteratee func(item T) R) R"
- "func MeanByUint8[T any, R ~uint8](collection []T, iteratee func(item T) R) R"
- "func MeanByUint16[T any, R ~uint16](collection []T, iteratee func(item T) R) R"
- "func MeanByUint32[T any, R ~uint32](collection []T, iteratee func(item T) R) R"
- "func MeanByUint64[T any, R ~uint64](collection []T, iteratee func(item T) R) R"
- "func MeanByFloat32[T any, R ~float32](collection []T, iteratee func(item T) R) R"
- "func MeanByFloat64[T any, R ~float64](collection []T, iteratee func(item T) R) R"
- "func MeanByInt8x16[T any, R ~int8](collection []T, iteratee func(item T) R) R"
- "func MeanByInt8x32[T any, R ~int8](collection []T, iteratee func(item T) R) R"
- "func MeanByInt8x64[T any, R ~int8](collection []T, iteratee func(item T) R) R"
- "func MeanByInt16x8[T any, R ~int16](collection []T, iteratee func(item T) R) R"
- "func MeanByInt16x16[T any, R ~int16](collection []T, iteratee func(item T) R) R"
- "func MeanByInt16x32[T any, R ~int16](collection []T, iteratee func(item T) R) R"
- "func MeanByInt32x4[T any, R ~int32](collection []T, iteratee func(item T) R) R"
- "func MeanByInt32x8[T any, R ~int32](collection []T, iteratee func(item T) R) R"
- "func MeanByInt32x16[T any, R ~int32](collection []T, iteratee func(item T) R) R"
- "func MeanByInt64x2[T any, R ~int64](collection []T, iteratee func(item T) R) R"
- "func MeanByInt64x4[T any, R ~int64](collection []T, iteratee func(item T) R) R"
- "func MeanByInt64x8[T any, R ~int64](collection []T, iteratee func(item T) R) R"
- "func MeanByUint8x16[T any, R ~uint8](collection []T, iteratee func(item T) R) R"
- "func MeanByUint8x32[T any, R ~uint8](collection []T, iteratee func(item T) R) R"
- "func MeanByUint8x64[T any, R ~uint8](collection []T, iteratee func(item T) R) R"
- "func MeanByUint16x8[T any, R ~uint16](collection []T, iteratee func(item T) R) R"
- "func MeanByUint16x16[T any, R ~uint16](collection []T, iteratee func(item T) R) R"
- "func MeanByUint16x32[T any, R ~uint16](collection []T, iteratee func(item T) R) R"
- "func MeanByUint32x4[T any, R ~uint32](collection []T, iteratee func(item T) R) R"
- "func MeanByUint32x8[T any, R ~uint32](collection []T, iteratee func(item T) R) R"
- "func MeanByUint32x16[T any, R ~uint32](collection []T, iteratee func(item T) R) R"
- "func MeanByUint64x2[T any, R ~uint64](collection []T, iteratee func(item T) R) R"
- "func MeanByUint64x4[T any, R ~uint64](collection []T, iteratee func(item T) R) R"
- "func MeanByUint64x8[T any, R ~uint64](collection []T, iteratee func(item T) R) R"
- "func MeanByFloat32x4[T any, R ~float32](collection []T, iteratee func(item T) R) R"
- "func MeanByFloat32x8[T any, R ~float32](collection []T, iteratee func(item T) R) R"
- "func MeanByFloat32x16[T any, R ~float32](collection []T, iteratee func(item T) R) R"
- "func MeanByFloat64x2[T any, R ~float64](collection []T, iteratee func(item T) R) R"
- "func MeanByFloat64x4[T any, R ~float64](collection []T, iteratee func(item T) R) R"
- "func MeanByFloat64x8[T any, R ~float64](collection []T, iteratee func(item T) R) R"
---
MeanBy transforms a collection using an iteratee function and calculates the arithmetic mean of the result using SIMD instructions. The automatic dispatch functions (e.g., `MeanByInt8`) will select the best SIMD variant based on CPU capabilities. The specific variants (e.g., `MeanByInt8x32`) use a fixed SIMD instruction set regardless of CPU capabilities.
> **Note**: The automatic dispatch functions (e.g., `MeanByInt8`) will use the best available SIMD variant for the current CPU. Use specific variants (e.g., `MeanByInt8x32`) only if you know your target CPU supports that instruction set.
```go
type Person struct {
Name string
Age int8
}
people := []Person{
{Name: "Alice", Age: 20},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 40},
}
// Automatic dispatch - uses best available SIMD
mean := simd.MeanByInt8(people, func(p Person) int8 {
return p.Age
})
// 30
```
```go
type Product struct {
Name string
Price float32
}
products := []Product{
{Name: "Widget", Price: 10.50},
{Name: "Gadget", Price: 20.00},
{Name: "Tool", Price: 15.75},
}
// Mean price using specific AVX2 variant
mean := simd.MeanByFloat32x8(products, func(p Product) float32 {
return p.Price
})
// 15.4167
```
```go
type Metric struct {
Value uint16
}
metrics := []Metric{
{Value: 100},
{Value: 200},
{Value: 300},
{Value: 400},
}
// Using AVX variant - works on all amd64
mean := simd.MeanByUint16x8(metrics, func(m Metric) uint16 {
return m.Value
})
// 250
```
```go
// Empty collection returns 0
type Item struct {
Count int64
}
mean := simd.MeanByInt64([]Item{}, func(i Item) int64 {
return i.Count
})
// 0
```
================================================
FILE: docs/data/simd-min.md
================================================
---
name: Min
slug: min
sourceRef: exp/simd/math_avx.go#L833
category: exp
subCategory: simd
similarHelpers:
- exp#simd#min
position: 20
signatures:
- "func MinInt8x16[T ~int8](collection []T) T"
- "func MinInt8x32[T ~int8](collection []T) T"
- "func MinInt8x64[T ~int8](collection []T) T"
- "func MinInt16x8[T ~int16](collection []T) T"
- "func MinInt16x16[T ~int16](collection []T) T"
- "func MinInt16x32[T ~int16](collection []T) T"
- "func MinInt32x4[T ~int32](collection []T) T"
- "func MinInt32x8[T ~int32](collection []T) T"
- "func MinInt32x16[T ~int32](collection []T) T"
- "func MinInt64x2[T ~int64](collection []T) T"
- "func MinInt64x4[T ~int64](collection []T) T"
- "func MinInt64x8[T ~int64](collection []T) T"
- "func MinUint8x16[T ~uint8](collection []T) T"
- "func MinUint8x32[T ~uint8](collection []T) T"
- "func MinUint8x64[T ~uint8](collection []T) T"
- "func MinUint16x8[T ~uint16](collection []T) T"
- "func MinUint16x16[T ~uint16](collection []T) T"
- "func MinUint16x32[T ~uint16](collection []T) T"
- "func MinUint32x4[T ~uint32](collection []T) T"
- "func MinUint32x8[T ~uint32](collection []T) T"
- "func MinUint32x16[T ~uint32](collection []T) T"
- "func MinUint64x2[T ~uint64](collection []T) T"
- "func MinUint64x4[T ~uint64](collection []T) T"
- "func MinUint64x8[T ~uint64](collection []T) T"
- "func MinFloat32x4[T ~float32](collection []T) T"
- "func MinFloat32x8[T ~float32](collection []T) T"
- "func MinFloat32x16[T ~float32](collection []T) T"
- "func MinFloat64x2[T ~float64](collection []T) T"
- "func MinFloat64x4[T ~float64](collection []T) T"
- "func MinFloat64x8[T ~float64](collection []T) T"
---
Finds the minimum value in a collection using SIMD instructions. The suffix (x2, x4, x8, x16, x32, x64) indicates the number of lanes processed simultaneously.
> **Note**: Choose the variant matching your CPU's capabilities. Higher lane counts provide better performance but require newer CPU support.
```go
// Using AVX2 variant (32 lanes at once) - Intel Haswell+ / AMD Excavator+
min := simd.MinInt8x32([]int8{5, 2, 8, 1, 9})
// 1
```
```go
// Using AVX-512 variant (16 lanes at once) - Intel Skylake-X+
min := simd.MinFloat32x16([]float32{3.5, 1.2, 4.8, 2.1})
// 1.2
```
```go
// Using AVX variant (4 lanes at once) - works on all amd64
min := simd.MinInt32x4([]int32{100, 50, 200, 75})
// 50
```
```go
// Empty collection returns 0
min := simd.MinUint16x8([]uint16{})
// 0
```
================================================
FILE: docs/data/simd-sum.md
================================================
---
name: Sum
slug: sum
sourceRef: exp/simd/math_avx.go#L14
category: exp
subCategory: simd
similarHelpers:
- exp#simd#sum
- exp#simd#sumby
position: 0
signatures:
- "func SumInt8x16[T ~int8](collection []T) T"
- "func SumInt8x32[T ~int8](collection []T) T"
- "func SumInt8x64[T ~int8](collection []T) T"
- "func SumInt16x8[T ~int16](collection []T) T"
- "func SumInt16x16[T ~int16](collection []T) T"
- "func SumInt16x32[T ~int16](collection []T) T"
- "func SumInt32x4[T ~int32](collection []T) T"
- "func SumInt32x8[T ~int32](collection []T) T"
- "func SumInt32x16[T ~int32](collection []T) T"
- "func SumInt64x2[T ~int64](collection []T) T"
- "func SumInt64x4[T ~int64](collection []T) T"
- "func SumInt64x8[T ~int64](collection []T) T"
- "func SumUint8x16[T ~uint8](collection []T) T"
- "func SumUint8x32[T ~uint8](collection []T) T"
- "func SumUint8x64[T ~uint8](collection []T) T"
- "func SumUint16x8[T ~uint16](collection []T) T"
- "func SumUint16x16[T ~uint16](collection []T) T"
- "func SumUint16x32[T ~uint16](collection []T) T"
- "func SumUint32x4[T ~uint32](collection []T) T"
- "func SumUint32x8[T ~uint32](collection []T) T"
- "func SumUint32x16[T ~uint32](collection []T) T"
- "func SumUint64x2[T ~uint64](collection []T) T"
- "func SumUint64x4[T ~uint64](collection []T) T"
- "func SumUint64x8[T ~uint64](collection []T) T"
- "func SumFloat32x4[T ~float32](collection []T) T"
- "func SumFloat32x8[T ~float32](collection []T) T"
- "func SumFloat32x16[T ~float32](collection []T) T"
- "func SumFloat64x2[T ~float64](collection []T) T"
- "func SumFloat64x4[T ~float64](collection []T) T"
- "func SumFloat64x8[T ~float64](collection []T) T"
---
Sums the values in a collection using SIMD instructions. The suffix (x2, x4, x8, x16, x32, x64) indicates the number of lanes processed simultaneously.
> **Note**: Choose the variant matching your CPU's capabilities. Higher lane counts provide better performance but require newer CPU support.
```go
// Using AVX2 variant (32 lanes at once) - Intel Haswell+ / AMD Excavator+
sum := simd.SumInt8x32([]int8{1, 2, 3, 4, 5})
// 15
```
```go
// Using AVX-512 variant (16 lanes at once) - Intel Skylake-X+
sum := simd.SumFloat32x16([]float32{1.1, 2.2, 3.3, 4.4})
// 11
```
```go
// Using AVX variant (4 lanes at once) - works on all amd64
sum := simd.SumInt32x4([]int32{1000000, 2000000, 3000000})
// 6000000
```
```go
// Empty collection returns 0
sum := simd.SumUint16x16([]uint16{})
// 0
```
================================================
FILE: docs/data/simd-sumby.md
================================================
---
name: SumBy
slug: sumby
sourceRef: exp/simd/math.go#L841
category: exp
subCategory: simd
similarHelpers:
- exp#simd#sum
- exp#simd#meanby
position: 20
signatures:
- "func SumByInt8[T any, R ~int8](collection []T, iteratee func(item T) R) R"
- "func SumByInt16[T any, R ~int16](collection []T, iteratee func(item T) R) R"
- "func SumByInt32[T any, R ~int32](collection []T, iteratee func(item T) R) R"
- "func SumByInt64[T any, R ~int64](collection []T, iteratee func(item T) R) R"
- "func SumByUint8[T any, R ~uint8](collection []T, iteratee func(item T) R) R"
- "func SumByUint16[T any, R ~uint16](collection []T, iteratee func(item T) R) R"
- "func SumByUint32[T any, R ~uint32](collection []T, iteratee func(item T) R) R"
- "func SumByUint64[T any, R ~uint64](collection []T, iteratee func(item T) R) R"
- "func SumByFloat32[T any, R ~float32](collection []T, iteratee func(item T) R) R"
- "func SumByFloat64[T any, R ~float64](collection []T, iteratee func(item T) R) R"
- "func SumByInt8x16[T any, R ~int8](collection []T, iteratee func(item T) R) R"
- "func SumByInt8x32[T any, R ~int8](collection []T, iteratee func(item T) R) R"
- "func SumByInt8x64[T any, R ~int8](collection []T, iteratee func(item T) R) R"
- "func SumByInt16x8[T any, R ~int16](collection []T, iteratee func(item T) R) R"
- "func SumByInt16x16[T any, R ~int16](collection []T, iteratee func(item T) R) R"
- "func SumByInt16x32[T any, R ~int16](collection []T, iteratee func(item T) R) R"
- "func SumByInt32x4[T any, R ~int32](collection []T, iteratee func(item T) R) R"
- "func SumByInt32x8[T any, R ~int32](collection []T, iteratee func(item T) R) R"
- "func SumByInt32x16[T any, R ~int32](collection []T, iteratee func(item T) R) R"
- "func SumByInt64x2[T any, R ~int64](collection []T, iteratee func(item T) R) R"
- "func SumByInt64x4[T any, R ~int64](collection []T, iteratee func(item T) R) R"
- "func SumByInt64x8[T any, R ~int64](collection []T, iteratee func(item T) R) R"
- "func SumByUint8x16[T any, R ~uint8](collection []T, iteratee func(item T) R) R"
- "func SumByUint8x32[T any, R ~uint8](collection []T, iteratee func(item T) R) R"
- "func SumByUint8x64[T any, R ~uint8](collection []T, iteratee func(item T) R) R"
- "func SumByUint16x8[T any, R ~uint16](collection []T, iteratee func(item T) R) R"
- "func SumByUint16x16[T any, R ~uint16](collection []T, iteratee func(item T) R) R"
- "func SumByUint16x32[T any, R ~uint16](collection []T, iteratee func(item T) R) R"
- "func SumByUint32x4[T any, R ~uint32](collection []T, iteratee func(item T) R) R"
- "func SumByUint32x8[T any, R ~uint32](collection []T, iteratee func(item T) R) R"
- "func SumByUint32x16[T any, R ~uint32](collection []T, iteratee func(item T) R) R"
- "func SumByUint64x2[T any, R ~uint64](collection []T, iteratee func(item T) R) R"
- "func SumByUint64x4[T any, R ~uint64](collection []T, iteratee func(item T) R) R"
- "func SumByUint64x8[T any, R ~uint64](collection []T, iteratee func(item T) R) R"
- "func SumByFloat32x4[T any, R ~float32](collection []T, iteratee func(item T) R) R"
- "func SumByFloat32x8[T any, R ~float32](collection []T, iteratee func(item T) R) R"
- "func SumByFloat32x16[T any, R ~float32](collection []T, iteratee func(item T) R) R"
- "func SumByFloat64x2[T any, R ~float64](collection []T, iteratee func(item T) R) R"
- "func SumByFloat64x4[T any, R ~float64](collection []T, iteratee func(item T) R) R"
- "func SumByFloat64x8[T any, R ~float64](collection []T, iteratee func(item T) R) R"
---
SumBy transforms a collection using an iteratee function and sums the result using SIMD instructions. The automatic dispatch functions (e.g., `SumByInt8`) will select the best SIMD variant based on CPU capabilities. The specific variants (e.g., `SumByInt8x32`) use a fixed SIMD instruction set regardless of CPU capabilities.
> **Note**: The automatic dispatch functions (e.g., `SumByInt8`) will use the best available SIMD variant for the current CPU. Use specific variants (e.g., `SumByInt8x32`) only if you know your target CPU supports that instruction set.
```go
type Person struct {
Name string
Age int8
}
people := []Person{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
// Automatic dispatch - uses best available SIMD
sum := simd.SumByInt8(people, func(p Person) int8 {
return p.Age
})
// 90
```
```go
type Product struct {
Name string
Price float32
Stock int32
}
products := []Product{
{Name: "Widget", Price: 10.50, Stock: 5},
{Name: "Gadget", Price: 20.00, Stock: 3},
{Name: "Tool", Price: 15.75, Stock: 2},
}
// Sum stock value using specific AVX2 variant
sum := simd.SumByFloat32x8(products, func(p Product) float32 {
return p.Price * float32(p.Stock)
})
// 152.5
```
```go
type Metric struct {
Value uint16
}
metrics := []Metric{
{Value: 100},
{Value: 200},
{Value: 300},
{Value: 400},
}
// Using AVX variant - works on all amd64
sum := simd.SumByUint16x8(metrics, func(m Metric) uint16 {
return m.Value
})
// 1000
```
```go
// Empty collection returns 0
type Item struct {
Count int64
}
sum := simd.SumByInt64([]Item{}, func(i Item) int64 {
return i.Count
})
// 0
```
================================================
FILE: docs/docs/_category_.json
================================================
{
"label": "Documentation",
"position": 1,
"link": {
"type": "generated-index",
"description": "Learn how to use samber/lo - the reusable utility library for Go."
}
}
================================================
FILE: docs/docs/about.md
================================================
---
title: ✌️ About
description: Discover "lo", the utility library for Go
sidebar_position: 0
---
# ✌️ About
**samber/lo** is a Lodash-style utility library for Go that brings the power and convenience of functional programming helpers to the Go ecosystem. Born from the need for more expressive and concise data manipulation, `lo` fills the gaps between Go's standard library and the complex data transformations that modern applications require.

**Why `lo` Exists**
Go's standard library is excellent for many use cases, but it lacks the higher-level abstractions that developers coming from JavaScript, Python, or other languages often miss. While Go 1.18 introduced generics, the standard library's `slices` and `maps` packages only cover about 5-10 basic helpers. `lo` provides hundreds of additional utilities that make everyday programming tasks more enjoyable and less error-prone.
**The name "lo"**
I wanted a short and memorable name, similar to "Lodash". It's easy to type and no existing Go package was using this name, making it unique in the ecosystem.
## 🚀 Install
```go
go get -u github.com/samber/lo@v1
```
This library is v1 and follows SemVer strictly.
No breaking changes will be made to exported APIs before v2.0.0.
This library has no dependencies outside the Go standard library.
## 💡 Usage
You can import `lo` using:
```go
import (
"github.com/samber/lo"
lop "github.com/samber/lo/parallel"
lom "github.com/samber/lo/mutable"
loi "github.com/samber/lo/it"
)
```
Then use one of the helpers below:
```go
names := lo.Uniq([]string{"Samuel", "John", "Samuel"})
// []string{"Samuel", "John"}
```
### Tips for lazy developers
I cannot recommend it, but in case you are too lazy for repeating `lo.` everywhere, you can import the entire library into the namespace. 😅 🤮
```go
import (
. "github.com/samber/lo"
)
```
I take no responsibility for this junk. 😁 💩
## 🙋♂️ Community and Evolution
`lo` embraces Go's evolution:
- **Go 1.18**: Leveraged generics for type safety
- **Go 1.23**: Added iterator support with the `it` package
- **Future**: Will continue to adapt to Go's language improvements
The library is actively maintained and welcomes contributions. It follows semantic versioning strictly, ensuring stability for production applications.
## 📍 When to Use `lo`
Use `lo` when you need to:
- Transform complex data structures concisely
- Reduce boilerplate code for common operations
- Write more declarative and readable code
- Leverage functional programming patterns in Go
- Process data in parallel or with lazy evaluation
For simple operations, Go's standard library may suffice. But when you find yourself writing nested loops or complex data manipulation logic, `lo` provides the abstractions you need.
## 🏛️ Design Philosophy
### 1. **Type Safety Through Generics**
Every function in `lo` is built on Go 1.18+ generics, ensuring compile-time type safety without sacrificing flexibility. This eliminates runtime type assertions and reduces bugs.
### 2. **Immutable by Default**
The main `lo` package follows functional programming principles by returning new collections rather than modifying existing ones. This predictability makes code easier to reason about and test.
### 3. **Performance When Needed**
For performance-critical scenarios, `lo` offers specialized packages:
- `lo/mutable`: In-place operations that modify collections directly
- `lo/parallel`: Concurrent processing with built-in worker pools
- `lo/it`: Lazy evaluation using Go 1.23+ iterators
### 4. **Minimal Dependencies**
`lo` has zero dependencies outside the Go standard library. This choice ensures reliability, security, and avoids dependency hell.
================================================
FILE: docs/docs/contributing.md
================================================
---
title: 🤝 Contributing
description: Join the community of contributors.
sidebar_position: 110
---
# 🤝 Contributing
Hey! We are happy to have you as a new contributor. ✌️
For your contribution please follow some guidelines:
## Function Naming
Helpers must be self-explanatory and respect standards (other languages, libraries...). Feel free to suggest many names in your contributions or the related issue.
We hate breaking changes, so better think twice ;)
## Variadic functions
Many functions accept variadic parameters (like `lo.Keys(...map[K]V)` accepting multiple maps), providing flexibility while maintaining type safety.
## Slice type Parameters
Functions use `~[]T` constraints to accept any slice type, including named slice types, not just `[]T`. This design choice makes the library more flexible in real-world usage.
## Variants
When applicable, some functions can be added to sub-package as well: `mutable`, `it` and `parallel`. Add a documentation for each helper.
For function variants, use consistent suffixes:
- `F` suffix for function-based versions (lazy evaluation)
- `I` suffix for variants having `index int` argument in predicate callback
- `Err` suffix for variants returning an error in predicate callback
- `WithContext` suffix when context.Context is provided
- `X` suffix for helpers with varying arguments (eg: MustX: Must2, Must3, Must4...)
## Testing
We try to maintain code coverage above 90%.
## Benchmark and performance
Write performant helpers and limit extra memory consumption. Build an helper for general purpose and don't optimize for a particular use-case.
Feel free to write benchmarks.
Iterators can be unbounded and run for a very long time. If you expect a big memory footprint, please warn developers in the function comment.
## Documentation
Functions must be properly commented, with a Go Playground link. New helpers must be created with a markdown documentation in `docs/data/`. In markdown header, please link to similar helpers (and update other markdowns accordingly).
Add your helper to `docs/static/llms.txt`.
## Examples
Every function includes a "Play" link to the Go Playground, allowing developers to quickly experiment and understand behavior without setting up a local environment.
Please add an example of your helper in the file named `xxxx_example_test.go`. It will be available from Godoc website: https://pkg.go.dev/github.com/samber/lo
## Other conventions
### Naming
1- If a callback returns a single bool then it should probably be called "predicate".
2- If a callback is used to change a collection element into something else then it should probably be called "transform".
3- If a callback returns nothing (void) then it should probably be called "callback".
### Types
1- Generic functions must preserve the underlying type of collections so that the returned values maintain the same type as the input. See [#365](https://github.com/samber/lo/pull/365/files).
================================================
FILE: docs/docs/core/_category_.json
================================================
{
"label": "🧢 Core",
"position": 1,
"link": {
"type": "generated-index",
"description": "Stop reinventing the wheel"
}
}
================================================
FILE: docs/docs/core/channel.md
================================================
---
title: "Channel"
description: Loop over channel and perform transformations
sidebar_position: 20
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Channel helpers
This page lists all operations on channels, available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/concurrency.md
================================================
---
title: "Concurrency"
description: Operate on multiple gorountines
sidebar_position: 80
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Concurrency helpers
This page lists all concurrency operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/condition.md
================================================
---
title: "Condition"
description: Perform transformations on types
sidebar_position: 45
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Conditional helpers
This page lists all conditional operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/error-handling.md
================================================
---
title: "Error handling"
description: Error handling
sidebar_position: 110
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Error handling helpers
This page lists all error handling operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/find.md
================================================
---
title: "Find"
description: Loop over a collection and find element(s)
sidebar_position: 40
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Find helpers
This page lists all search helpers, available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/function.md
================================================
---
title: "Function"
description: Functions helpers
sidebar_position: 30
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Function helpers
This page lists all operations on functions, available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/intersect.md
================================================
---
title: "Intersect"
description: Loop over a collection and find similar items
sidebar_position: 50
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Intersection helpers
This page lists all intersection helpers, available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/map.md
================================================
---
title: "Map"
description: Loop over maps and perform transformations
sidebar_position: 10
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Map helpers
This page lists all operations on maps, available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/math.md
================================================
---
title: "Math"
description: Perform math operations on collections
sidebar_position: 60
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Math helpers
This page lists all math operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/retry.md
================================================
---
title: "Retry"
description: Retry processing on error
sidebar_position: 70
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Retry helpers
This page lists all retry operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/slice.md
================================================
---
title: Slice
description: Loop over slices and perform transformations
sidebar_position: 0
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Slice helpers
This page lists all operations on slices, available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/string.md
================================================
---
title: "String"
description: Perform transformations on strings
sidebar_position: 35
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - String helpers
This page lists all string operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/time.md
================================================
---
title: "Time"
description: Time manipulation
sidebar_position: 100
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Time helpers
This page lists all time operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/tuple.md
================================================
---
title: "Tuple"
description: Manipulate multiple items in a single variable
sidebar_position: 90
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Tuple helpers
This page lists all tuple operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/core/type.md
================================================
---
title: "Type manipulation"
description: Perform transformations on types
sidebar_position: 120
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Core - Type manipulation helpers
This page lists all type operations available in the core package of lo.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/experimental/_category_.json
================================================
{
"label": "🧪 Experimental",
"position": 5,
"link": {
"type": "generated-index",
"description": "Experimental features"
}
}
================================================
FILE: docs/docs/experimental/simd.md
================================================
---
title: SIMD
description: High-performance slice operations using AVX, AVX2 and AVX512 SIMD when built with Go 1.26+ and GOEXPERIMENT=simd on amd64.
sidebar_position: 0
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## SIMD helpers
This page lists all operations on slices, available in the `exp/simd` sub-package. These helpers use **AVX** (128-bit), **AVX2** (256-bit) or **AVX512** (512-bit) SIMD when built with Go 1.26+, the `GOEXPERIMENT=simd` flag, and on amd64.
:::warning Unstable API
SIMD helpers are experimental. The API may break in the future.
:::
## Performance
Benchmarks show that running SIMD operators on small datasets is slower:
```txt
BenchmarkSumInt8/small/Fallback-lo-4 203616572 5.875 ns/op
BenchmarkSumInt8/small/AVX-x16-4 100000000 12.04 ns/op
BenchmarkSumInt8/small/AVX2-x32-4 64041816 17.93 ns/op
BenchmarkSumInt8/small/AVX512-x64-4 26947528 44.75 ns/op
```
But much much faster on big datasets:
```txt
BenchmarkSumInt8/xlarge/Fallback-lo-4 247677 4860 ns/op
BenchmarkSumInt8/xlarge/AVX-x16-4 3851040 311.4 ns/op
BenchmarkSumInt8/xlarge/AVX2-x32-4 7100002 169.2 ns/op
BenchmarkSumInt8/xlarge/AVX512-x64-4 10107534 118.1 ns/op
```
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/getting-started.md
================================================
---
title: 🚀 Getting started
description: Let's discover samber/lo in less than 5 minutes.
sidebar_position: 1
---
# Getting started
**samber/lo** is a Lodash-style utility library for Go 1.18+ that leverages generics to provide type-safe helper functions. The library is organized into several packages, each serving different use cases.
## 🚀 Install
```bash
go get -u github.com/samber/lo@v1
```
## 🧢 Core Package (`lo`)
The main package provides immutable utility functions for slices, maps, strings, math operations, and more. It's the core of the library with over 300+ functions.
```go
import "github.com/samber/lo"
// Example: Map a slice of numbers to their squares
numbers := []int{1, 2, 3, 4, 5}
squared := lo.Map(numbers, func(x int, _ int) int {
return x * x
})
// Result: [1, 4, 9, 16, 25]
```
## 🔄 Iter Package (`lo/it`)
The `it` package provides Go 1.23+ sequence helpers with lazy evaluation, offering over 100 functions for efficient iteration without buffering.
```go
// Future usage (Go 1.23+)
import (
"iter"
loi "github.com/samber/lo/it"
)
seqIn := iter.Range(0, 1000)
// Lazy iteration without buffering
seqOut := loi.Filter(seqIn, func(x int) bool {
return x%2 == 0
})
```
## 👣 Mutable Package (`lo/mutable`)
The mutable package provides in-place operations that modify collections directly, useful for performance-critical scenarios.
```go
import lom "github.com/samber/lo/mutable"
// Filter in-place (modifies the original slice)
numbers := []int{1, 2, 3, 4, 5}
lom.Filter(&numbers, func(x int) bool {
return x%2 == 0
})
// Result: [2, 4]
```
## 🏎️ Parallel Package (`lo/parallel`)
The parallel package enables concurrent processing of collections with built-in worker pools, perfect for CPU-intensive operations.
```go
import lop "github.com/samber/lo/parallel"
// Process items concurrently (4 workers by default)
results := lop.Map(numbers, 4, func(x int) int {
// Some expensive operation
return expensiveOperation(x)
})
```
## ✅ Key Benefits
- **Type-safe** with generics
- **Immutable** by default (main package)
- **Performance** optimized with parallel and mutable variants
- **Comprehensive** with 500+ utility functions
- **Lazy evaluation** with `iter` std package (Go >= 1.23)
- **Minimal dependencies** zero dependencies outside the Go standard library
## 👀 Next Steps
- Check the [Go documentation](https://pkg.go.dev/github.com/samber/lo) for complete API reference
- Explore examples in the repository
- Choose the right sub-package for your use case
================================================
FILE: docs/docs/glossary.md
================================================
---
id: glossary
title: 📚 Glossary
description: Comprehensive glossary of samber/lo library terms and concepts
sidebar_position: 100
---
# Glossary
## Generics
Go 1.18+ feature that allows writing functions and types that work with any type while maintaining type safety. The `lo` library extensively uses generics to provide type-safe utility functions.
## Reflection
Language reflection is a programming technique where a program can examine and manipulate its own structure and behavior at runtime.
## Type Safety
The principle of ensuring that operations are performed on the correct data types, preventing runtime errors. `lo` leverages Go's type system and generics to provide type-safe operations and type-safe APIs.
## Immutability
The concept of data that cannot be changed after creation. Most `lo` functions follow immutable patterns, returning new collections rather than modifying existing ones.
## Predicate Function
A function that returns a boolean value, typically used for filtering or testing conditions. In `lo`, predicates are commonly used with `Filter`, `Find`, `Contains`, etc.
## Transformer Function
A function that transforms one value into another. Used with `Map`, `MapValues`, and other transformation operations.
## Reducer Function
A function that combines two values into one, used with `Reduce` and similar operations.
## Comparator Function
A function that compares two values and returns their relative order. Used with sorting operations.
## Higher-Order Functions
Functions that take other functions as parameters or return functions as results. Most `lo` utilities are higher-order functions.
## Mutable Operations
Functions that modify collections in-place rather than creating new ones. Mutable operations can be more memory efficient but less safe in concurrent scenarios and when data sharing is involved.
## Lazy Evaluation
Some `lo` operations implement lazy evaluation patterns for improved performance with large datasets.
## Memory Efficiency
Considerations for memory usage when choosing between immutable and mutable operations. Mutable operations can be more memory efficient but less safe in concurrent scenarios and when data sharing is involved.
## Concurrency Safety
How different operations behave in concurrent contexts and thread safety guarantees.
================================================
FILE: docs/docs/iter/_category_.json
================================================
{
"label": "🔄 Iterator",
"position": 2,
"link": {
"type": "generated-index",
"description": "Manipulate iterators over slices, maps, and channels"
}
}
================================================
FILE: docs/docs/iter/channel.md
================================================
---
title: Channel
description: Iterate over channel and perform transformations
sidebar_position: 30
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - Channel helpers
This page lists all operations on channels, available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/iter/find.md
================================================
---
title: Find
description: Iterate over a collection and find element(s)
sidebar_position: 50
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - Find helpers
This page lists all search helpers, available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/iter/intersect.md
================================================
---
title: Intersect
description: Iterate over a collection and find similar items
sidebar_position: 60
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - Intersection helpers
This page lists all intersection helpers, available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/iter/map.md
================================================
---
title: Map
description: Iterate over maps and perform transformations
sidebar_position: 10
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - Map helpers
This page lists all operations on maps, available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/iter/sequence.md
================================================
---
title: Sequence
description: Iterate over sequences and perform transformations
sidebar_position: 20
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - Sequence helpers
This page lists all operations on sequences, available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/iter/slice.md
================================================
---
title: Slice
description: Iterate over slices and perform transformations
sidebar_position: 0
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - Map helpers
This page lists all operations on slice, available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/iter/string.md
================================================
---
title: String
description: Perform transformations on strings
sidebar_position: 40
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - String helpers
This page lists all operations on strings, available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/iter/tuple.md
================================================
---
title: Tuple
description: Manipulate multiple items in a single variable
sidebar_position: 70
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - Tuple helpers
This page lists all tuple operations, available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/iter/type.md
================================================
---
title: Type manipulation
description: Perform transformations on types
sidebar_position: 80
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Iterator - Type manipulation helpers
This page lists all type operations available in the `it` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/mutable/_category_.json
================================================
{
"label": "👣 Mutable",
"position": 3,
"link": {
"type": "generated-index",
"description": "Mutable operations on slices, maps, and channels"
}
}
================================================
FILE: docs/docs/mutable/slice.md
================================================
---
title: Slice
description: Update slices in place
sidebar_position: 0
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Mutable - Slice helpers
This page lists all operations on slices, available in the `mutable` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docs/parallel/_category_.json
================================================
{
"label": "🏎️ Parallel",
"position": 4,
"link": {
"type": "generated-index",
"description": "Parallel processing of slices, maps, and channels"
}
}
================================================
FILE: docs/docs/parallel/slice.md
================================================
---
title: Slice
description: Loop over slices and perform transformations in parallel
sidebar_position: 0
hide_table_of_contents: true
---
:::warning Help improve this documentation
This documentation is still new and evolving. If you spot any mistakes, unclear explanations, or missing details, please [open an issue](https://github.com/samber/lo/issues).
Your feedback helps us improve!
:::
#
## Parallel - Slice helpers
This page lists all operations on slices, available in the `parallel` lo sub-package.
import HelperList from '@site/plugins/helpers-pages/components/HelperList';
================================================
FILE: docs/docusaurus.config.ts
================================================
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
const config: Config = {
title: 'lo',
tagline: 'A reusable utility library for Go: slices, maps, and more',
favicon: 'img/favicon.ico',
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
future: {
v4: {
removeLegacyPostBuildHeadAttribute: true,
useCssCascadeLayers: true,
},
experimental_faster: {
swcJsLoader: true,
swcJsMinimizer: true,
swcHtmlMinimizer: true,
lightningCssMinimizer: true,
rspackBundler: true,
rspackPersistentCache: true,
ssgWorkerThreads: true,
mdxCrossCompilerCache: true,
},
experimental_storage: {
type: 'localStorage',
namespace: true,
},
},
// Set the production url of your site here
url: 'https://ro.samber.dev',
// Set the // pathname under which your site is served
// For GitHub pages deployment, it is often '//'
baseUrl: '/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'samber', // Usually your GitHub org/user name.
projectName: 'lo', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'throw',
onBrokenAnchors: 'throw',
markdown: {
anchors: {
maintainCase: true,
},
mermaid: true,
},
// Storage configuration for better performance
staticDirectories: ['static'],
// Optional: Enable hash router for offline support (experimental)
// Uncomment if you need offline browsing capability
// router: 'hash',
// Future-proofing configurations
clientModules: [
require.resolve('./src/theme/prism-include-languages.js'),
],
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
headTags: [
// DNS prefetch for better performance
{
tagName: 'link',
attributes: {
rel: 'dns-prefetch',
href: '//fonts.googleapis.com',
},
},
{
tagName: 'link',
attributes: {
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossorigin: 'anonymous',
},
},
{
tagName: 'meta',
attributes: {
name: 'keywords',
content: 'go, golang, lo, slices, maps, strings, channels, functions, helpers, concurrency, error handling, reusable, utility, framework, library, samber',
},
},
{
tagName: 'meta',
attributes: {
property: 'og:image',
content: 'https://lo.samber.dev/img/cover.png',
},
},
{
tagName: 'meta',
attributes: {
name: 'twitter:card',
content: 'summary_large_image',
},
},
{
tagName: 'meta',
attributes: {
name: 'twitter:image',
content: 'https://lo.samber.dev/img/cover.png',
},
},
{
tagName: 'meta',
attributes: {
name: 'twitter:creator',
content: '@samuelberthe',
},
},
{
tagName: 'link',
attributes: {
rel: 'canonical',
href: 'https://lo.samber.dev',
},
},
],
customFields: {
sponsors: [
{
name: 'DBOS',
url: 'https://www.dbos.dev/?utm_campaign=gh-smbr',
title: 'DBOS - Durable workflow orchestration library for Go',
logo_light: '/img/sponsors/dbos-black.png',
logo_dark: '/img/sponsors/dbos-white.png',
},
],
},
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/samber/lo/tree/master/docs/',
showLastUpdateAuthor: true,
showLastUpdateTime: true,
// Enhanced docs features from 3.8+
breadcrumbs: true,
sidebarCollapsed: false,
numberPrefixParser: false,
// Enable admonitions
admonitions: {
keywords: ['note', 'tip', 'info', 'danger', 'warning'],
extendDefaults: true,
},
// Enhanced markdown features
remarkPlugins: [],
rehypePlugins: [],
},
sitemap: {
lastmod: 'date',
changefreq: 'weekly',
priority: 0.7,
ignorePatterns: ['/tags/**'],
filename: 'sitemap.xml',
// Enhanced sitemap features from 3.8+
createSitemapItems: async (params) => {
const {defaultCreateSitemapItems, ...rest} = params ;
const items = await defaultCreateSitemapItems(rest);
// Add custom priority for specific pages
return items.map((item) => {
if (item.url.includes('/docs/getting-started')) {
return {...item, priority: 1.0};
}
if (item.url.includes('/docs/')) {
return {...item, priority: 0.8};
}
return item;
});
},
},
theme: {
customCss: './src/css/custom.css',
},
gtag: {
trackingID: 'G-VVXXV8747F',
anonymizeIP: false,
},
} satisfies Preset.Options,
],
],
themeConfig: {
// Replace with your project's social card
image: 'img/cover.png',
colorMode: {
defaultMode: 'light',
disableSwitch: false,
respectPrefersColorScheme: true,
},
// Mermaid configuration
mermaid: {
theme: {light: 'neutral', dark: 'dark'},
options: {
maxTextSize: 50000,
},
},
// Enhanced metadata
metadata: [
{name: 'og:type', content: 'website'},
],
navbar: {
title: '🏎️ samber/lo',
logo: {
alt: 'lo - Reusable utility library for Go',
src: 'img/icon.png',
},
items: [
{
type: 'docSidebar',
sidebarId: 'docSidebar',
position: 'left',
label: 'Doc',
},
{
to: 'https://pkg.go.dev/github.com/samber/lo',
label: 'GoDoc',
position: 'left',
},
{
to: 'community',
label: 'Community',
position: 'left',
},
{
to: 'https://github.com/samber/lo/releases',
label: 'Changelog',
position: 'right',
},
{
to: 'https://github.com/sponsors/samber',
label: '💖 Sponsor',
position: 'right',
},
{
href: 'https://github.com/samber/lo',
// label: 'GitHub',
position: 'right',
className: 'header-github-link',
'aria-label': 'GitHub repository',
},
{
type: 'search',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Project',
items: [
{
label: 'Documentation',
to: '/docs/getting-started',
},
{
label: 'Changelog',
to: 'https://github.com/samber/lo/releases',
},
{
label: 'Godoc',
to: 'https://pkg.go.dev/github.com/samber/lo',
},
{
label: 'License',
to: 'https://github.com/samber/lo/blob/master/LICENSE',
},
{
label: '💖 Sponsor',
to: 'https://github.com/sponsors/samber',
},
],
},
{
title: 'Community',
items: [
{
label: 'New issue',
to: 'https://github.com/samber/lo/issues',
},
{
label: 'GitHub',
to: 'https://github.com/samber/lo',
},
{
label: 'Stack Overflow',
to: 'https://stackoverflow.com/search?q=samber+lo',
},
{
label: 'Twitter',
to: 'https://twitter.com/samuelberthe',
},
{
label: 'Substack',
to: 'https://samuelberthe.substack.com',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} lo.`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
defaultLanguage: 'go',
additionalLanguages: ['bash', 'diff', 'json', 'yaml', 'go'],
magicComments: [
{
className: 'theme-code-block-highlighted-line',
line: 'highlight-next-line',
block: {start: 'highlight-start', end: 'highlight-end'},
},
{
className: 'code-block-error-line',
line: 'error-next-line',
block: {start: 'error-start', end: 'error-end'},
},
],
},
algolia: {
appId: 'XKJFLJJWS2',
// bearer:disable javascript_lang_hardcoded_secret
apiKey: 'a1c30d9a943ef77d4cd26ac3aca68be7',
externalUrlRegex: 'lo\\.samber\\.dev',
indexName: 'lo.samber.dev',
contextualSearch: true,
searchParameters: {
// facetFilters: ['type:lvl1'],
},
searchPagePath: 'search',
// Enhanced search features from 3.8+
insights: true,
},
} satisfies Preset.ThemeConfig,
themes: ['@docusaurus/theme-mermaid'],
plugins: [
// Add ideal image plugin for better image optimization
[
'@docusaurus/plugin-ideal-image',
{
quality: 70,
max: 1030,
min: 640,
steps: 2,
disableInDev: false,
},
],
[
'vercel-analytics',
{
debug: true,
mode: 'auto',
},
],
// Custom plugin to generate helper category pages from data markdown
[
require.resolve('./plugins/helpers-pages'),
{},
],
],
};
export default config;
================================================
FILE: docs/package.json
================================================
{
"name": "lo",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc",
"sync-function-signatures": "node ./scripts/sync-function-signatures.js",
"check-function-signatures": "node ./scripts/sync-function-signatures.js --check",
"check-cross-references": "node ./scripts/check-cross-references.js",
"check-duplicates-in-category": "node ./scripts/check-duplicates-in-category.js",
"check-filename-matches-frontmatter": "node ./scripts/check-filename-matches-frontmatter.js",
"check-similar-exists": "node ./scripts/check-similar-exists.js",
"check-similar-keys-exist-in-directory": "node ./scripts/check-similar-keys-exist-in-directory.js"
},
"dependencies": {
"@docusaurus/core": "3.9.1",
"@docusaurus/faster": "^3.9.1",
"@docusaurus/plugin-css-cascade-layers": "^3.9.1",
"@docusaurus/plugin-ideal-image": "^3.9.1",
"@docusaurus/plugin-svgr": "^3.9.1",
"@docusaurus/plugin-vercel-analytics": "^3.9.1",
"@docusaurus/preset-classic": "3.9.1",
"@docusaurus/theme-mermaid": "^3.9.1",
"@mdx-js/react": "^3.0.0",
"@mermaid-js/layout-elk": "^0.1.9",
"classnames": "^2.3.2",
"clsx": "^2.0.0",
"marked": "^17.0.1",
"prism-react-renderer": "^2.3.0",
"prismjs": "^1.29.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.9.1",
"@docusaurus/tsconfig": "3.9.1",
"@docusaurus/types": "3.9.1",
"typescript": "~5.9.3"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=20.0"
}
}
================================================
FILE: docs/plugins/helpers-pages/components/HelperCard.tsx
================================================
import React, { useMemo, useCallback, useState, useEffect, useRef } from 'react';
import { ensurePrototypeGenericsHighlighted } from './highlightPrototypeGenerics';
import { marked } from 'marked';
import type { HelperDefinition } from '../index';
import Heading from '@theme/Heading';
import CodeBlock from '@theme/CodeBlock';
import '../../../src/prism-include-languages.js';
interface HelperCardProps {
helper: HelperDefinition;
}
export default function HelperCard({
helper,
}: HelperCardProps) {
// Extract function name from signature for godoc link
const functionName = useMemo(() => {
if (!helper.signatures) return '';
// Extract function name from signature like "func Map[T any, R any](collection []T, transform func(T, int) R) []R"
const match = helper.signatures.find(signature => signature.match(/func\s+(\w+)/))?.match(/func\s+(\w+)/);
return match ? match[1] : '';
}, [helper.signatures]);
const godocUrl = useMemo(() => {
let baseUrl = 'https://pkg.go.dev/github.com/samber/lo';
switch (helper.category) {
case 'core':
baseUrl = 'https://pkg.go.dev/github.com/samber/lo';
break;
case 'mutable':
baseUrl = 'https://pkg.go.dev/github.com/samber/lo/mutable';
break;
case 'parallel':
baseUrl = 'https://pkg.go.dev/github.com/samber/lo/parallel';
break;
// case 'it':
// baseUrl = 'https://pkg.go.dev/github.com/samber/lo/it';
// break;
}
if (!functionName) return '';
return `${baseUrl}#${functionName}`;
}, [functionName]);
const sourceRef = useMemo(() => {
return `https://github.com/samber/lo/blob/master/${helper.sourceRef}`;
}, [helper.sourceRef]);
const renderedNodes = useMemo(() => {
marked.setOptions({
gfm: true,
breaks: true,
});
const tokens = marked.lexer(helper.content || '');
const elements: React.ReactNode[] = [];
let pendingTokens: any[] = [];
const flushPending = () => {
if (pendingTokens.length === 0) return;
const html = (marked as any).parser(pendingTokens);
elements.push(
React.createElement('div', {
className: 'helper-card__markdown-chunk',
dangerouslySetInnerHTML: { __html: html },
key: `md-${elements.length}`,
})
);
pendingTokens = [];
};
for (const token of tokens as any[]) {
if (token.type === 'code') {
flushPending();
const lang = (token.lang || '').trim() || undefined;
elements.push(
React.createElement(CodeBlock as any, {
language: lang,
key: `code-${elements.length}`,
children: token.text,
})
);
} else {
pendingTokens.push(token);
}
}
flushPending();
return elements;
}, [helper.content]);
const signatures = useMemo(() => {
return helper.signatures.join('\n');
}, [helper.signatures]);
// Post-process prototype code to colorize generic type parameters locally
const prototypeRowRef = useRef(null);
useEffect(() => {
// // Ensure highlighting even if Prism updates asynchronously
// const raf = requestAnimationFrame(() => {
// ensurePrototypeGenericsHighlighted(prototypeRowRef.current as unknown as HTMLElement);
// });
// return () => cancelAnimationFrame(raf);
}, [helper.signatures]);
return (
{/* Heading registered in MDX ToC */}
{helper.name}
{/*
*/}
Prototype{helper.signatures.length > 1 ? 's' : ''}:
);
}
type SimilarHelpersProps = {
title: string;
similarHelpers: string[];
currentType: string;
currentCategory: string;
currentName: string;
};
function SimilarHelpers({
title,
similarHelpers,
currentType,
currentCategory,
currentName,
}: SimilarHelpersProps) {
const currentHelperLower = `${currentType}#${currentCategory}#${currentName}`.toLowerCase();
similarHelpers = similarHelpers.filter((helper) => helper != currentHelperLower);
if (similarHelpers.length === 0) {
return null;
}
return (
{title}:
{similarHelpers
.map((originalLabel) => {
const parts = String(originalLabel).split('#');
const typeRaw = parts[0];
const categoryRaw = parts[1];
const nameRaw = parts[2];
const fallbackType = currentType || '';
const fallbackCategory = currentCategory || '';
const type = (typeRaw || fallbackType).toLowerCase();
const category = (categoryRaw || fallbackCategory).toLowerCase();
// Fallback for legacy 2-part labels: type#name
const legacyName = parts.length === 2 ? parts[1] : undefined;
const name = (nameRaw || legacyName || '').toLowerCase();
return { originalLabel, type, category, name, nameRaw };
})
.sort((a, b) => {
const currentTypeLower = (currentType || '').toLowerCase();
const aSame = a.type === currentTypeLower ? 0 : 1;
const bSame = b.type === currentTypeLower ? 0 : 1;
if (aSame !== bSame) return aSame - bSame; // same type first
return a.name.localeCompare(b.name);
})
.map(({ originalLabel, type, category, name, nameRaw }, index) => {
const currentTypeLower = (currentType || '').toLowerCase();
const href = `/docs/${type}/${category}#${name}`;
const displayName = nameRaw || name;
const isSameSection = type === currentTypeLower; // compare only type for label
return (
{isSameSection ? (
displayName
) : (
<>
{type}› {' '}
{displayName}
>
)}
);
})}
);
}
================================================
FILE: docs/plugins/helpers-pages/components/HelperList.tsx
================================================
import React from 'react';
import HelperCard from './HelperCard';
import {usePluginData} from '@docusaurus/useGlobalData';
import type {HelperDefinition} from '../index';
import HelperTOC from './HelperTOC';
import './helper-components.css';
interface HelperListProps {
category: string;
subCategory: string;
}
export default function HelperList({
category,
subCategory,
}: HelperListProps) {
const gridStyle = {
marginTop: '2rem'
};
const data = usePluginData('helpers-pages') as {helpers: HelperDefinition[]};
const helpers = (data?.helpers ?? [])
.filter((h) => h.category === category && h.subCategory === subCategory)
.slice()
.sort((a, b) => a.position - b.position);
return (
{helpers.map((helper) => (
))}
);
}
================================================
FILE: docs/plugins/helpers-pages/components/HelperTOC.tsx
================================================
import React from 'react';
import {usePluginData} from '@docusaurus/useGlobalData';
import type {HelperDefinition} from '../index';
import './helper-components.css';
interface HelperTOCProps {
category: string;
subCategory: string;
title?: string;
}
export default function HelperTOC({
category,
subCategory,
title = 'On this page',
}: HelperTOCProps) {
const data = usePluginData('helpers-pages') as {helpers: HelperDefinition[]};
const helpers = (data?.helpers ?? [])
.filter((h) => h.category === category && h.subCategory === subCategory)
.slice()
.sort((a, b) => a.position - b.position);
if (helpers.length === 0) {
return null;
}
return (
{title}
);
}
================================================
FILE: docs/plugins/helpers-pages/components/helper-components.css
================================================
/* Helper Card Styles */
/* Theme tokens */
:root {
--helper-bg: #ffffff;
--helper-surface: #f6f8fa;
--helper-border: #e1e4e8;
--helper-border-strong: #c8d1dc;
--helper-text: #24292e;
--helper-text-muted: #586069;
--helper-primary: #0366d6;
--helper-primary-weak: #f1f8ff;
--helper-success: #28a745;
--helper-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
--helper-shadow-hover: 0 8px 24px rgba(140, 149, 159, 0.2);
/* Prototype color tokens (Solarized Light, extra contrast) */
--proto-fn: #6f5700;
/* deeper yellow */
--proto-type: #4a51a3;
/* deeper violet */
--proto-param: #157a80;
/* deeper cyan */
--proto-key: #1b5fa5;
/* deeper blue */
--proto-op: #a11d4f;
/* deeper magenta */
--proto-str: #556b00;
/* deeper green */
--proto-num: #973e12;
/* deeper orange */
--proto-punc: #36454f;
/* deeper base01 */
}
html[data-theme='dark'] {
--helper-bg: #0d1117;
--helper-surface: #161b22;
--helper-border: #30363d;
--helper-border-strong: #3d444d;
--helper-text: #c9d1d9;
--helper-text-muted: #8b949e;
--helper-primary: #2f81f7;
--helper-primary-weak: #0b1f3a;
--helper-success: #2ea043;
--helper-shadow: 0 1px 3px rgba(1, 4, 9, 0.4);
--helper-shadow-hover: 0 8px 24px rgba(1, 4, 9, 0.6);
/* Prototype color tokens (Solarized Dark) */
--proto-fn: #b58900;
/* yellow */
--proto-type: #6c71c4;
/* violet */
--proto-param: #2aa198;
/* cyan */
--proto-key: #268bd2;
/* blue */
--proto-op: #d33682;
/* magenta */
--proto-str: #859900;
/* green */
--proto-num: #cb4b16;
/* orange */
--proto-punc: #93a1a1;
/* base1 */
}
ul.helper-list {
list-style: none;
padding: 0;
margin: 0;
}
ul.helper-list>li {
list-style: none;
margin: 0;
}
.helper-card {
background: var(--helper-bg);
border: 1px solid var(--helper-border);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
transition: border-color 150ms ease, box-shadow 200ms ease, transform 200ms ease;
box-shadow: var(--helper-shadow);
position: relative;
overflow: hidden;
}
.helper-card:hover {
border-color: var(--helper-primary);
box-shadow: var(--helper-shadow-hover);
transform: translateY(-2px);
}
.helper-card::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(1200px 200px at -20% -10%, rgba(3, 102, 214, 0.06), transparent 40%),
radial-gradient(800px 200px at 120% 110%, rgba(3, 102, 214, 0.04), transparent 40%);
pointer-events: none;
}
.helper-card__header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.helper-card__title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
.helper-card__title a {
color: var(--helper-text);
text-decoration: none;
}
.helper-card__title a:hover {
color: var(--helper-primary);
}
.helper-card__actions {
display: flex;
align-items: center;
gap: 0.75rem;
}
.helper-card__badges {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.helper-card__badge {
padding: 0.35rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
line-height: 1;
}
.helper-card__badge--category {
background: var(--helper-primary-weak);
color: var(--helper-primary);
border: 1px solid var(--helper-border-strong);
}
.helper-card__badge--subcategory {
background: var(--helper-surface);
color: var(--helper-text-muted);
border: 1px solid var(--helper-border);
}
.helper-card__content {
margin-bottom: 1rem;
}
.helper-card__markdown {
color: var(--helper-text);
line-height: 1.6;
}
.helper-card__markdown p {
margin: 0 0 0.75rem 0;
}
.helper-card__markdown a {
color: var(--helper-primary);
text-decoration: underline;
}
.helper-card__markdown code {
background: var(--helper-surface);
border: 1px solid var(--helper-border);
border-radius: 4px;
padding: 0.1rem 0.3rem;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 0.9em;
}
.helper-card__markdown pre {
background: var(--helper-surface);
border: 1px solid var(--helper-border);
border-radius: 4px;
padding: 0.75rem;
overflow-x: auto;
}
.helper-card__markdown pre code {
border: none;
background: transparent;
padding: 0;
}
.helper-card__description {
color: var(--helper-text-muted);
margin-bottom: 1rem;
line-height: 1.5;
}
.helper-card__signature {
margin-bottom: 1rem;
}
.helper-card__signature h4 {
margin: 0 0 0.5rem 0;
font-size: 0.875rem;
font-weight: 600;
color: #24292e;
}
.helper-card__code {
background: var(--helper-surface);
border: 1px solid var(--helper-border);
border-radius: 4px;
padding: 0.75rem;
margin: 0;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 0.875rem;
line-height: 1.4;
overflow-x: auto;
}
.helper-card__code::-webkit-scrollbar {
height: 8px;
}
.helper-card__code::-webkit-scrollbar-thumb {
background: var(--helper-border-strong);
border-radius: 4px;
}
.helper-card__examples {
margin-bottom: 1rem;
}
.helper-card__examples h4 {
margin: 0 0 0.5rem 0;
font-size: 0.875rem;
font-weight: 600;
color: #24292e;
}
.helper-card__example {
margin-bottom: 0.75rem;
}
.helper-card__example h5 {
margin: 0 0 0.25rem 0;
font-size: 0.75rem;
font-weight: 500;
color: #586069;
}
.helper-card__similar {
margin-bottom: 1rem;
}
.helper-card__similar-row {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.helper-card__section-head {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 1rem;
margin-bottom: 0.5rem;
}
.helper-card__prototype-actions {
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.helper-card__prototype-label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--helper-text-muted);
flex: 0 0 auto;
margin-top: 0;
}
.helper-card__similar-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
/* Right-side sticky TOC */
.helper-toc {
position: sticky;
top: calc(var(--ifm-navbar-height) + var(--ifm-toc-padding-vertical, 1rem));
max-height: calc(100vh - 2rem);
overflow: auto;
padding-left: 1rem;
border-left: 1px solid var(--ifm-toc-border-color, var(--ifm-color-emphasis-200));
}
.helper-toc__title {
font-weight: 600;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: var(--ifm-color-emphasis-700);
}
.helper-toc__list {
list-style: none;
margin: 0;
padding: 0;
}
.helper-toc__item {
margin: 0.25rem 0;
}
.helper-toc__link {
font-size: 0.9rem;
text-decoration: none;
}
.helper-card__similar-link {
padding: 0.25rem 0.5rem;
background: var(--helper-surface);
border: 1px solid var(--helper-border);
border-radius: 4px;
color: var(--helper-primary);
text-decoration: none;
font-size: 0.75rem;
transition: all 0.2s ease;
}
.helper-card__similar-link:hover {
background: var(--helper-primary-weak);
border-color: var(--helper-primary);
}
.helper-card__similar-prefix {
color: var(--helper-text-muted);
}
.helper-card__footer {
border-top: 1px solid #e1e4e8;
padding-top: 1rem;
text-align: center;
}
/* Prototype block (compact code block placed after similar helpers) */
.helper-card__prototype {
margin-top: 0.25rem;
border-top: 1px dashed var(--helper-border);
padding-top: 0.75rem;
}
.helper-card__prototype-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.helper-card__prototype-code {
font-size: 0.80rem;
padding: 0.15rem 0.4rem;
border-radius: 6px;
margin: 0;
flex: 1 1 auto;
width: 100%;
}
/* Token colors inside prototype code only */
.helper-card__prototype-code .token.keyword {
color: var(--proto-key) !important;
}
.helper-card__prototype-code .token.function {
color: var(--proto-fn) !important;
font-weight: 600;
}
.helper-card__prototype-code .token.punctuation {
color: var(--proto-punc) !important;
}
.helper-card__prototype-code .token.operator {
color: var(--proto-op) !important;
}
.helper-card__prototype-code .token.constant,
.helper-card__prototype-code .token.number {
color: var(--proto-num) !important;
}
.helper-card__prototype-code .token.parameter,
.helper-card__prototype-code .token.variable {
color: var(--proto-param) !important;
}
.helper-card__prototype-code .token.class-name,
.helper-card__prototype-code .token.builtin,
.helper-card__prototype-code .token.type,
.helper-card__prototype-code .token.generic,
.helper-card__prototype-code .token.type-parameter,
.helper-card__prototype-code .token.template,
.helper-card__prototype-code .token.namespace {
color: var(--proto-type) !important;
font-weight: 600;
}
/* Force violet for all generic type parameters in prototype block */
.helper-card__prototype-code .token.type-parameter {
color: var(--proto-type) !important;
font-weight: 600;
}
/* Also target any spans we create for generics */
.helper-card__prototype-code span.token.type.type-parameter {
color: var(--proto-type) !important;
font-weight: 600;
}
/* Strings mapping */
.helper-card__prototype-code .token.string {
color: var(--proto-str) !important;
}
.helper-card__prototype-code code {
padding: 0.4rem;
}
/* Copy button */
.helper-card__copy {
padding: 0.125rem 0.4rem;
border-radius: 4px;
font-size: 0.7rem;
line-height: 1;
border: 1px solid var(--helper-border);
background: var(--helper-surface);
color: var(--helper-text);
cursor: pointer;
}
.helper-card__copy:hover {
border-color: var(--helper-primary);
}
.helper-card__copy.is-copied {
background: #2ea043;
color: #ffffff;
border-color: #2ea043;
}
/* Reduce Prism copy button size inside prototype code blocks */
.helper-card__prototype-code .clean-btn,
.helper-card__prototype-code .theme-code-block-copy-button {
display: none !important;
}
.helper-card__godoc {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0.5rem;
background: var(--helper-primary);
color: #ffffff;
text-decoration: none;
border-radius: 4px;
font-size: 0.75rem;
line-height: 1;
font-weight: 500;
transition: all 0.2s ease;
white-space: nowrap;
border: 1px solid transparent;
}
.helper-card__source {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0.5rem;
background: rgba(168, 85, 247, 0.12);
/* soft purple */
color: #6d28d9;
/* purple-700 */
text-decoration: none;
border-radius: 4px;
font-size: 0.75rem;
line-height: 1;
font-weight: 500;
transition: all 0.2s ease;
white-space: nowrap;
border: 1px solid rgba(167, 139, 250, 0.6);
}
.helper-card__source:hover {
background: #c084fc;
/* purple-400 - much lighter */
color: #ffffff;
text-decoration: none;
}
html[data-theme='dark'] .helper-card__source {
background: rgba(168, 85, 247, 0.22);
color: #c4b5fd;
/* purple-300 */
border-color: rgba(167, 139, 250, 0.75);
}
html[data-theme='dark'] .helper-card__source:hover {
background: #c084fc;
color: #ffffff;
}
.helper-card__godoc:hover {
background: #0256cc;
color: #ffffff;
text-decoration: none;
}
.helper-card__playground {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0.5rem;
background: var(--helper-success);
color: #ffffff;
text-decoration: none;
border-radius: 4px;
font-size: 0.75rem;
line-height: 1;
font-weight: 500;
transition: all 0.2s ease;
white-space: nowrap;
border: 1px solid transparent;
}
.helper-card__playground:hover {
background: #22863a;
color: #ffffff;
text-decoration: none;
}
/* Focus visibility */
.helper-card a:focus-visible,
.helper-card__similar-link:focus-visible,
.helper-card__source:focus-visible,
.helper-card__godoc:focus-visible,
.helper-card__playground:focus-visible {
outline: 2px solid var(--helper-primary);
outline-offset: 2px;
border-radius: 6px;
}
/* Motion reduction */
@media (prefers-reduced-motion: reduce) {
.helper-card,
.helper-card * {
transition: none !important;
}
}
================================================
FILE: docs/plugins/helpers-pages/components/highlightPrototypeGenerics.ts
================================================
function getGenericTypeNamesFromText(text: string): string[] {
// Extract generic type names from the declaration brackets
const bracketMatch = text.match(/\[([^\]]+)\]/);
if (!bracketMatch) return [];
const content = bracketMatch[1];
// Split by commas and extract type names (before constraints like "any", "~[]T", etc.)
const names = content
.split(',')
.map(s => s.trim())
.map(s => (s.match(/^([A-Z][A-Za-z0-9_]*)\b/) || [])[1])
.filter(Boolean) as string[];
// Also look for any other capitalized identifiers that might be generic types
// This catches cases where generics are used as types throughout the signature
const allCapsIdentifiers = text.match(/\b([A-Z][A-Za-z0-9_]*)\b/g) || [];
const additionalNames = allCapsIdentifiers.filter(name =>
!['func', 'any', 'bool', 'int', 'string', 'float', 'byte', 'rune'].includes(name) &&
!name.match(/^[A-Z][a-z]+$/) // Exclude function names
);
return Array.from(new Set([...names, ...additionalNames]));
}
export function highlightPrototypeGenerics(container: HTMLElement | null): boolean {
if (!container) return false;
const codeEl = container.querySelector('.helper-card__prototype-code code');
if (!codeEl) return false;
// Avoid double-processing
if ((codeEl as HTMLElement).dataset.__genericsEnhanced === '1') return true;
const textContent = codeEl.textContent || '';
const genericNames = getGenericTypeNamesFromText(textContent);
// Built-in types to color like other types
const builtinTypes = [
'bool','string','byte','rune','error',
'int','int8','int16','int32','int64',
'uint','uint8','uint16','uint32','uint64','uintptr',
'float32','float64','complex64','complex128'
];
const names = Array.from(new Set([...genericNames, ...builtinTypes]));
if (names.length === 0) return false;
// Simple approach: replace the entire content with highlighted version
let newHtml = textContent;
const pattern = new RegExp(`\\b(${names.join('|')})\\b`, 'g');
newHtml = newHtml.replace(pattern, '$1 ');
if (newHtml !== textContent) {
codeEl.innerHTML = newHtml;
(codeEl as HTMLElement).dataset.__genericsEnhanced = '1';
return true;
}
return false;
}
export function ensurePrototypeGenericsHighlighted(container: HTMLElement | null) {
if (!container) return;
// Try immediately
if (highlightPrototypeGenerics(container)) return;
// Try on next animation frame
requestAnimationFrame(() => {
if (highlightPrototypeGenerics(container)) return;
// Try with a small delay
setTimeout(() => highlightPrototypeGenerics(container), 50);
});
}
================================================
FILE: docs/plugins/helpers-pages/index.ts
================================================
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import type {LoadContext, Plugin} from '@docusaurus/types';
type HelperFrontMatter = {
name: string;
slug: string;
sourceRef: string;
category: 'core' | 'mutable' | 'parallel';
subCategory: 'slice'
| 'map'
| 'channel'
| 'string'
| 'function'
| 'find'
| 'condition'
| 'intersect'
| 'type'
| 'tuple'
| 'math'
| 'retry'
| 'error-handling'
| 'concurrency'
| 'time';
signatures: string[];
playUrl?: string;
variantHelpers: string[];
similarHelpers: string[];
position?: number;
};
export type HelperDefinition = HelperFrontMatter & {
content: string;
filePath: string;
};
function readAllHelperMarkdownFiles(dataDir: string): HelperDefinition[] {
if (!fs.existsSync(dataDir)) {
return [];
}
const entries = fs.readdirSync(dataDir, {withFileTypes: true});
const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith('.md')).map((e) => path.join(dataDir, e.name));
const items: HelperDefinition[] = [];
for (const filePath of mdFiles) {
const raw = fs.readFileSync(filePath, 'utf8');
const {data, content} = matter(raw);
const fm = data as Partial;
if (!fm.name || !fm.slug || !fm.category || !fm.subCategory) {
continue;
}
items.push({
name: fm.name,
slug: fm.slug,
sourceRef: fm.sourceRef,
category: fm.category,
subCategory: fm.subCategory,
signatures: fm.signatures,
playUrl: fm.playUrl,
variantHelpers: Array.isArray(fm.variantHelpers) ? (fm.variantHelpers as string[]) : [],
similarHelpers: Array.isArray(fm.similarHelpers) ? (fm.similarHelpers as string[]) : [],
position: typeof fm.position === 'number' ? fm.position : 9999,
content,
filePath,
} as HelperDefinition);
}
// stable sort within category/subcategory
items.sort((a, b) => {
if (a.category !== b.category) return a.category.localeCompare(b.category);
if (a.subCategory !== b.subCategory) return a.subCategory.localeCompare(b.subCategory);
if ((a.position ?? 9999) !== (b.position ?? 9999)) return (a.position ?? 9999) - (b.position ?? 9999);
return a.name.localeCompare(b.name);
});
return items;
}
export default function pluginHelpersPages(context: LoadContext): Plugin {
let helpersCache: HelperDefinition[] = [];
return {
name: 'helpers-pages',
async loadContent() {
const dataDir = path.resolve(context.siteDir, 'data');
helpersCache = readAllHelperMarkdownFiles(dataDir);
},
async contentLoaded({content, actions}) {
const {setGlobalData} = actions as any;
setGlobalData({helpers: helpersCache});
},
};
}
================================================
FILE: docs/scripts/check-cross-references.js
================================================
#!/usr/bin/env node
const path = require('path');
const { loadHelpers } = require('./utils');
const dataDir = process.argv[2] || path.join(__dirname, '..', 'data');
const { helpers, byFullKey } = loadHelpers(dataDir);
let hasError = false;
helpers.forEach((h) => {
const thisKey = `${h.category}#${h.subCategory}#${h.name}`;
(h.similarHelpers || []).forEach((ref) => {
const other = byFullKey.get(ref);
if (!other) return; // Existence is checked by another script
const otherHasBackRef = (other.similarHelpers || []).includes(thisKey);
if (!otherHasBackRef) {
hasError = true;
console.error(`Cross-ref missing: ${h.fileName} -> ${ref} but not reciprocated.`);
}
});
});
if (hasError) process.exit(1);
console.log('OK: all similarHelpers are reciprocal.');
================================================
FILE: docs/scripts/check-duplicates-in-category.js
================================================
#!/usr/bin/env node
const path = require('path');
const { loadHelpers } = require('./utils');
const dataDir = process.argv[2] || path.join(__dirname, '..', 'data');
const { byCategoryName } = loadHelpers(dataDir);
let hasError = false;
for (const [key, list] of byCategoryName.entries()) {
if (list.length > 1) {
hasError = true;
const files = list.map((h) => h.fileName).join(', ');
console.error(`Duplicate helper in category detected for ${key}: ${files}`);
}
}
if (hasError) process.exit(1);
console.log('OK: no duplicate helpers within categories.');
================================================
FILE: docs/scripts/check-filename-matches-frontmatter.js
================================================
#!/usr/bin/env node
const path = require('path');
const { loadHelpers, expectedFileName } = require('./utils');
const dataDir = process.argv[2] || path.join(__dirname, '..', 'data');
const { helpers } = loadHelpers(dataDir);
let hasError = false;
helpers.forEach((h) => {
const expected = expectedFileName(h);
if (!expected) {
hasError = true;
console.error(`Invalid or missing frontmatter (category/slug) in ${h.fileName}`);
return;
}
if (h.fileName !== expected) {
hasError = true;
console.error(`Filename mismatch for ${h.fileName}, expected ${expected}`);
}
});
if (hasError) process.exit(1);
console.log('OK: all filenames match category and slug.');
================================================
FILE: docs/scripts/check-function-signatures.js
================================================
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const { listMarkdownFiles, parseFrontmatter } = require('./utils');
const repoRoot = path.resolve(__dirname, '..', '..');
const dataDir = path.resolve(__dirname, '..', 'data');
function readFile(filePath) {
return fs.readFileSync(filePath, 'utf8');
}
function* walkGoFiles(dir, excludeDirs = new Set()) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.name.startsWith('.')) continue;
const abs = path.join(dir, entry.name);
const rel = path.relative(repoRoot, abs);
if (entry.isDirectory()) {
if (excludeDirs.has(entry.name)) continue;
yield* walkGoFiles(abs, excludeDirs);
} else if (entry.isFile() && entry.name.endsWith('.go')) {
// skip tests
if (entry.name.endsWith('_test.go')) continue;
// skip docs/ directory
if (rel.split(path.sep)[0] === 'docs') continue;
yield abs;
}
}
}
function buildFunctionRegex(name) {
// Matches: func Name[...]( or func Name(
const escaped = name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
return new RegExp('^func\\s+' + escaped + '(?:\\(|\\[)', '');
}
async function findFunctionDeclarations(name, preferredPathHint) {
const fnRegex = buildFunctionRegex(name);
const hits = [];
// Prefer hinted file if provided
if (preferredPathHint) {
const hintedAbs = path.resolve(repoRoot, preferredPathHint);
if (fs.existsSync(hintedAbs)) {
const hit = await scanFileForSignature(hintedAbs, fnRegex);
if (hit) hits.push(hit);
}
}
for (const abs of walkGoFiles(repoRoot)) {
const hit = await scanFileForSignature(abs, fnRegex);
if (hit) hits.push(hit);
}
return hits;
}
function stripBOM(s) {
return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s;
}
async function scanFileForSignature(absPath, fnRegex) {
const rl = readline.createInterface({
input: fs.createReadStream(absPath, { encoding: 'utf8' }),
crlfDelay: Infinity,
});
let lineNo = 0;
for await (const rawLine of rl) {
lineNo++;
const line = stripBOM(rawLine);
if (fnRegex.test(line)) {
// Normalize multiple spaces and tabs minimally: keep original line
let signature = line.trim();
// Remove end-of-line comments before processing trailing bracket
signature = signature
// remove line comments
.replace(/\/\/.*$/, '')
// remove trailing block comment
.replace(/\/\*.*?\*\/\s*$/, '')
.trimEnd();
// Remove spaces before and after trailing opening brace, then drop it
signature = signature.replace(/\s*\{\s*$/, '');
const rel = path.relative(repoRoot, absPath).replace(/\\/g, '/');
return { file: rel, line: lineNo, signature };
}
}
return null;
}
function parseSourceRefFile(sourceRef) {
if (!sourceRef) return null;
const idx = sourceRef.indexOf('#');
if (idx === -1) return sourceRef;
return sourceRef.slice(0, idx);
}
function normalizeSignature(signature) {
// Collapse all whitespace and remove spaces before '(' or '['
return signature
.replace(/\s+/g, ' ')
.replace(/\s+(\(|\[)/g, '$1')
.trim();
}
async function main() {
const args = new Set(process.argv.slice(2));
const files = listMarkdownFiles(dataDir);
let issues = 0;
for (const absPath of files) {
const content = readFile(absPath);
const fm = parseFrontmatter(content) || {};
const name = fm.name;
if (!name) continue;
const hintFile = parseSourceRefFile(fm.sourceRef);
const hits = await findFunctionDeclarations(name, hintFile);
const relMd = path.relative(repoRoot, absPath).replace(/\\/g, '/');
if (!hits || hits.length === 0) {
// eslint-disable-next-line no-console
console.warn(`[missing-helper] ${relMd} -> name="${name}"`);
issues++;
continue;
}
// Sort hits by file path to ensure consistent order
hits.sort((a, b) => a.file.localeCompare(b.file));
// Deduplicate signatures from hits (preserve first encountered formatting)
const seenFromHits = new Set();
const uniqueHitSignatures = [];
for (const hit of hits) {
const norm = normalizeSignature(hit.signature);
if (!seenFromHits.has(norm)) {
seenFromHits.add(norm);
uniqueHitSignatures.push(hit.signature);
}
}
// Existing frontmatter signatures
const existing = Array.isArray(fm.signatures) ? fm.signatures : [];
const existingNorm = existing.map(normalizeSignature);
// Report duplicate signatures within frontmatter (second and further occurrences)
const seenExisting = new Set();
for (let i = 0; i < existing.length; i++) {
const norm = existingNorm[i];
if (seenExisting.has(norm)) {
// eslint-disable-next-line no-console
console.warn(`[duplicate-signature] ${relMd} -> "${existing[i]}"`);
issues++;
} else {
seenExisting.add(norm);
}
}
// Unknown signatures (exist in frontmatter but not in code)
const hitsNormalized = new Set(uniqueHitSignatures.map(normalizeSignature));
for (const sig of existing) {
const norm = normalizeSignature(sig);
if (!hitsNormalized.has(norm)) {
// eslint-disable-next-line no-console
console.warn(`[unknown-signature] ${relMd} -> "${sig}"`);
issues++;
}
}
// Missing signatures (found in code but not listed in frontmatter)
const existingNormalizedSet = new Set(existingNorm);
for (const sig of uniqueHitSignatures) {
const norm = normalizeSignature(sig);
if (!existingNormalizedSet.has(norm)) {
// eslint-disable-next-line no-console
console.warn(`[missing-signature] ${relMd} -> "${sig}"`);
issues++;
}
}
// SourceRef verification
const expectedSourceRef = `${hits[0].file}#L${hits[0].line}`;
if (fm.sourceRef !== expectedSourceRef) {
// eslint-disable-next-line no-console
console.warn(`[sourceRef-outdated] ${relMd} -> expected=${expectedSourceRef} actual=${fm.sourceRef || '""'}`);
issues++;
}
}
if (args.has('--check') && issues > 0) {
process.exitCode = 1;
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
================================================
FILE: docs/scripts/check-helpers-visible-in-pages.js
================================================
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Read all markdown files in docs/data directory
const dataDir = path.join(__dirname, '../data');
const files = fs.readdirSync(dataDir).filter(f => f.endsWith('.md'));
const combinations = new Set();
const coreCategories = new Set();
const parallelCategories = new Set();
const itCategories = new Set();
const mutableCategories = new Set();
// Extract type+category combinations from each file
files.forEach(file => {
const filePath = path.join(dataDir, file);
const content = fs.readFileSync(filePath, 'utf8');
const typeMatch = content.match(/^category:\s*(.+)$/m);
const categoryMatch = content.match(/^subCategory:\s*(.+)$/m);
if (typeMatch && categoryMatch) {
const type = typeMatch[1].trim();
const category = categoryMatch[1].trim();
const combination = `${type}|${category}`;
combinations.add(combination);
if (type === 'core') {
coreCategories.add(category);
} else if (type === 'it') {
itCategories.add(category);
} else if (type === 'mutable') {
mutableCategories.add(category);
} else if (type === 'parallel') {
parallelCategories.add(category);
} else {
throw new Error(`Unknown type: ${type}`);
}
}
});
console.log('=== TYPE+CATEGORY COMBINATIONS FOUND ===');
Array.from(combinations).sort().forEach(comb => console.log(comb));
console.log('\n=== CORE CATEGORIES ===');
Array.from(coreCategories).sort().forEach(cat => console.log(cat));
console.log('\n=== IT CATEGORIES ===');
Array.from(itCategories).sort().forEach(cat => console.log(cat));
console.log('\n=== MUTABLE CATEGORIES ===');
Array.from(mutableCategories).sort().forEach(cat => console.log(cat));
console.log('\n=== PARALLEL CATEGORIES ===');
Array.from(parallelCategories).sort().forEach(cat => console.log(cat));
// Check existing pages
const corePagesDir = path.join(__dirname, '../docs/core');
const itPagesDir = path.join(__dirname, '../docs/it');
const mutablePagesDir = path.join(__dirname, '../docs/mutable');
const parallelPagesDir = path.join(__dirname, '../docs/parallel');
const existingCorePages = new Set();
const existingItPages = new Set();
const existingMutablePages = new Set();
const existingParallelPages = new Set();
if (fs.existsSync(corePagesDir)) {
fs.readdirSync(corePagesDir)
.filter(f => f.endsWith('.md'))
.forEach(f => existingCorePages.add(f.replace('.md', '')));
}
if (fs.existsSync(itPagesDir)) {
fs.readdirSync(itPagesDir)
.filter(f => f.endsWith('.md'))
.forEach(f => existingItPages.add(f.replace('.md', '')));
}
if (fs.existsSync(mutablePagesDir)) {
fs.readdirSync(mutablePagesDir)
.filter(f => f.endsWith('.md'))
.forEach(f => existingMutablePages.add(f.replace('.md', '')));
}
if (fs.existsSync(parallelPagesDir)) {
fs.readdirSync(parallelPagesDir)
.filter(f => f.endsWith('.md'))
.forEach(f => existingParallelPages.add(f.replace('.md', '')));
}
console.log('\n=== EXISTING CORE PAGES ===');
Array.from(existingCorePages).sort().forEach(page => console.log(page));
console.log('\n=== EXISTING IT PAGES ===');
Array.from(existingItPages).sort().forEach(page => console.log(page));
console.log('\n=== EXISTING MUTABLE PAGES ===');
Array.from(existingMutablePages).sort().forEach(page => console.log(page));
console.log('\n=== EXISTING PARALLEL PAGES ===');
Array.from(existingParallelPages).sort().forEach(page => console.log(page));
// Find missing pages
console.log('\n=== MISSING CORE PAGES ===');
Array.from(coreCategories).sort().forEach(category => {
if (!existingCorePages.has(category)) {
console.log(`MISSING: core/${category}.md`);
}
});
console.log('\n=== MISSING IT PAGES ===');
Array.from(itCategories).sort().forEach(category => {
if (!existingItPages.has(category)) {
console.log(`MISSING: it/${category}.md`);
}
});
console.log('\n=== MISSING MUTABLE PAGES ===');
Array.from(mutableCategories).sort().forEach(category => {
if (!existingMutablePages.has(category)) {
console.log(`MISSING: mutable/${category}.md`);
}
});
console.log('\n=== MISSING PARALLEL PAGES ===');
Array.from(parallelCategories).sort().forEach(category => {
if (!existingParallelPages.has(category)) {
console.log(`MISSING: parallel/${category}.md`);
}
});
// Check for duplicates
console.log('\n=== VALIDATION RESULTS ===');
let hasErrors = false;
Array.from(coreCategories).sort().forEach(category => {
if (!existingCorePages.has(category)) {
console.log(`❌ ERROR: Missing core page for category: ${category}`);
hasErrors = true;
}
});
Array.from(itCategories).sort().forEach(category => {
if (!existingItPages.has(category)) {
console.log(`❌ ERROR: Missing it page for category: ${category}`);
hasErrors = true;
}
});
Array.from(mutableCategories).sort().forEach(category => {
if (!existingMutablePages.has(category)) {
console.log(`❌ ERROR: Missing mutable page for category: ${category}`);
hasErrors = true;
}
});
Array.from(parallelCategories).sort().forEach(category => {
if (!existingParallelPages.has(category)) {
console.log(`❌ ERROR: Missing parallel page for category: ${category}`);
hasErrors = true;
}
});
if (!hasErrors) {
console.log('✅ All helper categories have corresponding pages!');
} else {
console.log('\n❌ Found missing pages. Please create them as shown above.');
process.exit(1);
}
================================================
FILE: docs/scripts/check-similar-exists.js
================================================
#!/usr/bin/env node
const path = require('path');
const { loadHelpers, toFullKey } = require('./utils');
const dataDir = process.argv[2] || path.join(__dirname, '..', 'data');
const { helpers, byFullKey } = loadHelpers(dataDir);
let hasError = false;
helpers.forEach((h) => {
(h.similarHelpers || []).forEach((ref) => {
const key = toFullKey(ref);
if (!byFullKey.has(key)) {
hasError = true;
console.error(`Missing similar helper reference from ${h.fileName} -> ${key}`);
}
});
});
if (hasError) process.exit(1);
console.log('OK: all similarHelpers references point to existing helpers.');
================================================
FILE: docs/scripts/check-similar-keys-exist-in-directory.js
================================================
#!/usr/bin/env node
const path = require('path');
const { loadHelpers, toFullKey } = require('./utils');
const dataDir = process.argv[2] || path.join(__dirname, '..', 'data');
const { helpers, byFullKey } = loadHelpers(dataDir);
let hasError = false;
helpers.forEach((h) => {
(h.similarHelpers || []).forEach((ref) => {
const key = toFullKey(ref);
if (!byFullKey.has(key)) {
hasError = true;
console.error(`Reference not found in directory for ${h.fileName} -> ${key}`);
}
});
});
if (hasError) process.exit(1);
console.log('OK: every similarHelpers reference exists in the directory.');
================================================
FILE: docs/scripts/utils.js
================================================
const fs = require('fs');
const path = require('path');
function readFile(filePath) {
return fs.readFileSync(filePath, 'utf8');
}
function listMarkdownFiles(dirPath) {
return fs
.readdirSync(dirPath)
.filter((f) => f.endsWith('.md'))
.map((f) => path.join(dirPath, f));
}
function parseFrontmatter(content) {
// Very small frontmatter parser tailored to our files
// Expects YAML-like block between leading --- lines
const fmMatch = content.match(/^---[\r\n]+([\s\S]*?)[\r\n]+---/);
if (!fmMatch) return null;
const fm = fmMatch[1];
const data = {};
// Capture simple key: value pairs and array values like: key: [a, b]
fm.split(/\r?\n/).forEach((line) => {
const m = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/);
if (!m) return;
const key = m[1];
const raw = m[2].trim();
if (raw.startsWith('[') && raw.endsWith(']')) {
const inner = raw.slice(1, -1).trim();
if (inner.length === 0) {
data[key] = [];
} else {
data[key] = inner
.split(',')
.map((s) => s.trim())
.map((s) => (s.startsWith('"') && s.endsWith('"') ? s.slice(1, -1) : s));
}
} else if (raw.startsWith('"') && raw.endsWith('"')) {
data[key] = raw.slice(1, -1);
} else if (raw === '[]') {
data[key] = [];
} else if (raw === 'null') {
data[key] = null;
} else {
data[key] = raw;
}
});
return data;
}
function loadHelpers(dataDir) {
const files = listMarkdownFiles(dataDir);
const helpers = [];
files.forEach((absPath) => {
const filename = path.basename(absPath);
const content = readFile(absPath);
const fm = parseFrontmatter(content) || {};
const helper = {
filePath: absPath,
fileName: filename,
name: fm.name || null,
slug: fm.slug || null,
category: fm.category || null,
subCategory: fm.subCategory || null,
similarHelpers: Array.isArray(fm.similarHelpers) ? fm.similarHelpers : [],
};
helpers.push(helper);
});
// Build index by keys for quick lookup
const byCategoryName = new Map(); // key: `${category}#${name}` -> helper
const byFullKey = new Map(); // key: `${category}#${subCategory}#${name}` -> helper
const byPath = new Map(); // key: filename -> helper
helpers.forEach((h) => {
if (h.category && h.name) {
const k = `${h.category}#${h.name}`;
if (!byCategoryName.has(k)) byCategoryName.set(k, []);
byCategoryName.get(k).push(h);
}
if (h.category && h.subCategory && h.name) {
const k2 = `${h.category}#${h.subCategory}#${h.name}`;
byFullKey.set(k2, h);
}
byPath.set(h.fileName, h);
});
return { helpers, byCategoryName, byFullKey, byPath };
}
function expectedFileName(helper) {
if (!helper || !helper.category || !helper.slug) return null;
return `${helper.category}-${helper.slug}.md`;
}
function toFullKey(ref) {
// ref format: category#subcategory#Name
return ref.trim();
}
module.exports = {
readFile,
listMarkdownFiles,
parseFrontmatter,
loadHelpers,
expectedFileName,
toFullKey,
};
================================================
FILE: docs/sidebars.ts
================================================
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
const sidebars: SidebarsConfig = {
// Enhanced sidebar with better organization and features from 3.8+
docSidebar: [
'about',
'getting-started',
{
type: 'category',
label: '🧢 Core',
collapsible: true,
collapsed: true,
items: [
{type: 'autogenerated', dirName: 'core'},
],
},
{
type: 'category',
label: '🔄 Iterator',
collapsible: true,
collapsed: true,
items: [
{type: 'autogenerated', dirName: 'iter'},
],
},
{
type: 'category',
label: '👣 Mutable',
collapsible: true,
collapsed: true,
items: [
{type: 'autogenerated', dirName: 'mutable'},
],
},
{
type: 'category',
label: '🏎️ Parallel',
collapsible: true,
collapsed: true,
items: [
{type: 'autogenerated', dirName: 'parallel'},
],
},
{
type: 'category',
label: '🧪 Experimental',
collapsible: true,
collapsed: true,
items: [
{type: 'autogenerated', dirName: 'experimental'},
],
},
'glossary',
'contributing',
],
};
export default sidebars;
================================================
FILE: docs/src/css/custom.css
================================================
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* Modern CSS cascade layers for better style organization */
@layer base, components, utilities;
/* You can override the default Infima variables here. */
/* https://www.colors.tools/lighten-and-darken/ */
@layer base {
:root {
--ifm-color-primary: #007D9C;
--ifm-color-primary-dark: #002b36;
--ifm-color-primary-darker: #000;
--ifm-color-primary-darkest: #000;
--ifm-color-primary-light: #03cdff;
--ifm-color-primary-lighter: #69e1ff;
--ifm-color-primary-lightest: #cff5ff;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
--ifm-navbar-background-color: #f9f9f9;
--ifm-background-surface-color: #f9f9f9;
--ifm-background-surface-color-secondary: #f3f3f3;
--ifm-background-color: #ffffff;
/* --ifm-menu-link-padding-horizontal: 1rem; */
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #007D9C;
--ifm-color-primary-dark: #002b36;
--ifm-color-primary-darker: #000;
--ifm-color-primary-darkest: #000;
--ifm-color-primary-light: #03cdff;
--ifm-color-primary-lighter: #69e1ff;
--ifm-color-primary-lightest: #cff5ff;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
--ifm-background-color: #1f201c;
--ifm-background-surface-color: #272822;
--ifm-background-surface-color-secondary: #2a2b25;
--ifm-navbar-background-color: #272822;
}
}
@layer components {
.header-github-link:hover {
opacity: 0.6;
}
.header-github-link::before {
content: '';
width: 24px;
height: 24px;
display: flex;
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat;
}
[data-theme='dark'] .header-github-link::before {
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat;
}
}
@layer utilities {
.theme-doc-sidebar-item-link-level-2 {
/* because emojis in the level-1 menu require to push right level-2 menu entries */
padding-left: 1.0rem;
}
.docSidebarWithSpnsors {
display: flex;
flex-direction: column;
height: 100%;
}
.sidebar-spnsors {
margin-top: auto;
padding: 0.75rem 1rem 1.25rem;
border-top: 1px solid var(--ifm-toc-border-color);
background-color: var(--ifm-background-surface-color);
}
.sidebar-spnsors__title {
font-size: 0.9rem;
font-weight: 600;
/* text-transform: uppercase; */
margin-bottom: 0.5rem;
opacity: 0.7;
}
.sidebar-spnsors__logos {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.sidebar-spnsors__logo {
max-height: 40px;
max-width: 160px;
object-fit: contain;
}
.sidebar-spnsors__logo-wrapper {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.15rem;
}
.sidebar-spnsors__logo-title {
font-size: 0.8rem;
line-height: 1.3;
font-weight: 500;
opacity: 0.9;
}
}
/* CUSTOM */
.navbar__item .dropdown__menu {
min-width: 7rem;
}
.navbar__item .dropdown__menu svg {
display: none;
}
/* Fix anchor links being hidden behind fixed navbar */
html {
scroll-padding-top: calc(var(--ifm-navbar-height) + 1rem) !important;
}
/* Also apply scroll-margin-top to specific elements */
h1,
h2,
h3,
h4,
h5,
h6,
[id],
.anchor {
scroll-margin-top: calc(var(--ifm-navbar-height) + 1rem) !important;
}
/* Target any element that could be an anchor target */
*[id] {
scroll-margin-top: calc(var(--ifm-navbar-height) + 1rem) !important;
}
================================================
FILE: docs/src/pages/community.module.css
================================================
.icon {
color: var(--ifm-navbar-link-color);
font-weight: var(--ifm-font-weight-semibold);
padding: var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal);
display: inline-block;
}
.headerImg {
padding-top: 20px;
height: 180px;
}
.twitter:before {
content: '';
width: 48px;
height: 48px;
display: flex;
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z'/%3E%3C/svg%3E") no-repeat;
}
html[data-theme='dark'] .twitter:before {
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z'/%3E%3C/svg%3E") no-repeat;
}
.chat:before {
content: '';
width: 48px;
height: 48px;
display: flex;
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z'/%3E%3C/svg%3E") no-repeat;
}
html[data-theme='dark'] .chat:before {
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z'/%3E%3C/svg%3E") no-repeat;
}
.email:before {
content: '';
width: 48px;
height: 48px;
display: flex;
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z'/%3E%3Cpolyline points='22,6 12,13 2,6'/%3E%3C/svg%3E") no-repeat;
}
html[data-theme='dark'] .email:before {
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z'/%3E%3Cpolyline points='22,6 12,13 2,6'/%3E%3C/svg%3E") no-repeat;
}
================================================
FILE: docs/src/pages/community.tsx
================================================
import React from 'react';
import styles from './community.module.css';
import classnames from 'classnames';
import Layout from '@theme/Layout';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
function Community() {
const context = useDocusaurusContext();
return (
Report bugs or suggest improvements
Open new issue
Follow @samuelberthe on Twitter
Follow @SamuelBerthe
For sensitive or security-related queries, send us an email
contact@samuel-berthe.fr
);
}
export default Community;
================================================
FILE: docs/src/pages/index.module.css
================================================
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}
================================================
FILE: docs/src/pages/index.tsx
================================================
import type {ReactNode} from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import Heading from '@theme/Heading';
import styles from './index.module.css';
type FeatureItem = {
title: string;
Svg: React.ComponentType>;
description: ReactNode;
};
const FeatureList: FeatureItem[] = [
{
title: 'Type-safe Utilities for Go',
Svg: require('@site/static/img/oxygen-tube.svg').default,
description: (
<>
Generic utilities bringing type safety and convenience to daily Go programming.
>
),
},
{
title: 'Comprehensive API Coverage',
Svg: require('@site/static/img/compass.svg').default,
description: (
<>
A rich set of helpers for slices, maps, and more, ready for any data task in Go.
>
),
},
{
title: 'Built for Productivity',
Svg: require('@site/static/img/backpacks.svg').default,
description: (
<>
Minimal dependencies and intuitive APIs for seamless adoption and fast results. No breaking changes.
>
),
},
];
function Feature({title, Svg, description}: FeatureItem) {
return (
);
}
function HomepageFeatures(): ReactNode {
return (
{FeatureList.map((props, idx) => (
))}
);
}
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (
);
}
export default function Home(): JSX.Element {
const {siteConfig} = useDocusaurusContext();
return (
);
}
================================================
FILE: docs/src/prism-include-languages.js
================================================
import siteConfig from '@generated/docusaurus.config';
export default function prismIncludeLanguages(PrismObject) {
const {
themeConfig: {prism},
} = siteConfig;
const {additionalLanguages, defaultLanguage} = prism;
// Add Go language support
globalThis.Prism = PrismObject;
// eslint-disable-next-line import/no-dynamic-require, global-require
require(`prismjs/components/prism-${defaultLanguage}`);
additionalLanguages.forEach((lang) => {
// eslint-disable-next-line import/no-dynamic-require, global-require
require(`prismjs/components/prism-${lang}`);
});
delete globalThis.Prism;
}
================================================
FILE: docs/src/theme/DocSidebar/index.tsx
================================================
import React from 'react';
import OriginalDocSidebar from '@theme-original/DocSidebar';
import type DocSidebarProps from '@theme/DocSidebar';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import {useColorMode} from '@docusaurus/theme-common';
type Sponsor = {
name: string;
url: string;
title: string;
logo_light: string;
logo_dark: string;
};
export default function DocSidebarWrapper(props: DocSidebarProps) {
const {siteConfig} = useDocusaurusContext();
const sponsors = (siteConfig.customFields?.sponsors ?? []) as Sponsor[];
const {colorMode} = useColorMode();
return (
{sponsors.length > 0 && (
)}
);
}
================================================
FILE: docs/src/theme/NotFound/index.tsx
================================================
import React from 'react';
import Layout from '@theme/Layout';
import Translate, {translate} from '@docusaurus/Translate';
import {PageMetadata} from '@docusaurus/theme-common';
export default function NotFoundPage(): JSX.Element {
return (
<>
Page Not Found
We could not find what you were looking for.
Please contact the owner of the site that linked you to the
original URL and let them know their link is broken.
>
);
}
================================================
FILE: docs/src/theme/prism-include-languages.js
================================================
import siteConfig from '@generated/docusaurus.config';
export default function prismIncludeLanguages(PrismObject) {
const {
themeConfig: {prism},
} = siteConfig;
const {additionalLanguages, defaultLanguage} = prism;
// Add Go language support
globalThis.Prism = PrismObject;
// eslint-disable-next-line import/no-dynamic-require, global-require
require(`prismjs/components/prism-${defaultLanguage}`);
additionalLanguages.forEach((lang) => {
// eslint-disable-next-line import/no-dynamic-require, global-require
require(`prismjs/components/prism-${lang}`);
});
delete globalThis.Prism;
}
================================================
FILE: docs/static/.nojekyll
================================================
================================================
FILE: docs/static/img/README.md
================================================
Credit goes to https://gopherize.me/
================================================
FILE: docs/static/llms.txt
================================================
# Lo - A Go library for functional programming
Lo is a comprehensive, production-ready Lodash-style Go library built on Go 1.18+ generics that provides hundreds of utility functions for working with slices, maps, strings, channels, functions, and more. It aims to make Go code more expressive, readable, and productive by offering a rich collection of functional programming helpers inspired by libraries like Lodash in JavaScript.
## Origins and Purpose
Born from the need to fill gaps in Go's standard library after the introduction of generics in Go 1.18, Lo addresses common pain points that Go developers face daily. While Go's standard library gained some generic helpers in `slices` and `maps` packages, Lo goes far beyond with 300+ carefully crafted utilities that make functional programming patterns natural and idiomatic in Go.
The library emerged from real-world production needs and has been battle-tested in numerous high-traffic applications, microservices, and enterprise systems. It's not just a collection of utility functions, but a thoughtful toolkit that enables developers to write more declarative, maintainable, and bug-free code.
## Key Features
- **Generic-based**: Fully leverages Go 1.18+ generics for compile-time type safety
- **Zero dependencies**: No external dependencies outside the Go standard library
- **Comprehensive**: 300+ helpers covering slices, maps, strings, channels, functions, and more
- **Functional programming**: Brings functional programming paradigms to Go naturally
- **Production-ready**: Battle-tested in high-traffic applications worldwide
- **Well-tested**: 95%+ test coverage with comprehensive examples
- **SemVer compliant**: Follows strict semantic versioning (v1.x.x stable)
- **Performance-focused**: Minimal runtime overhead with optimized implementations
- **Memory efficient**: Many helpers avoid allocations and use zero-copy patterns
- **Composable**: Designed to work together in method chains and complex pipelines
## Sub-packages
The main package is complemented by several specialized sub-packages:
- **lo**: Core package with 300+ helpers for everyday operations
- **lo/parallel**: Parallel processing helpers for concurrent operations
- **lo/mutable**: In-place mutation helpers for memory-efficient operations
- **lo/it**: Iterator helpers for lazy evaluation and streaming
## Design Philosophy
Lo is built on a foundation of pragmatic engineering principles that balance power, safety, and performance:
1. **Type Safety First**: All helpers leverage Go's generics for compile-time type checking, eliminating runtime type assertions and reducing bugs
2. **Go Idiomatic**: While inspired by functional programming, helpers feel natural in Go code and follow established conventions
3. **Performance Conscious**: Every helper is benchmarked and optimized for minimal overhead, with many using zero-copy patterns
4. **Explicit Clarity**: Helper names clearly communicate their purpose and behavior
5. **Composable by Design**: Functions work together seamlessly, enabling elegant data transformation pipelines
6. **Memory Efficient**: Careful attention to allocation patterns, with many helpers avoiding unnecessary heap allocations
7. **Error Handling Respect**: Helpers don't hide errors; they integrate naturally with Go's error handling patterns
8. **Practical Over Pure**: Prioritizes solving real-world problems over theoretical purity
## Core Package Helpers
### Condition
- Ternary: Return value based on condition
- TernaryF: Return value from function based on condition
- If: Return value if condition is true
- IfF: Return value from function if condition is true
- ElseIf: Return value if previous condition was false and this condition is true
- ElseIfF: Return value from function if previous condition was false and this condition is true
- Else: Return value if all previous conditions were false
- ElseF: Return value from function if all previous conditions were false
- Switch: Return value based on first matching case
- Case: Define a case for switch statement
- CaseF: Define a case with function execution for switch statement
- Default: Define default case for switch statement
- DefaultF: Define default case with function execution for switch statement
### Concurrency
- Synchronize: Coordinate multiple goroutines with mutex
- Async: Execute function in goroutine and return channel
- Async0-Async6: Execute functions with 0-6 return values in goroutines
- WaitFor: Block until condition becomes true
- WaitForWithContext: Block until condition becomes true with context cancellation
### Error Handling
- Validate: Return error if condition is false
- Must: Panic if error is not nil, otherwise return value
- Must0-Must6: Panic if error is not nil, otherwise return 0-6 values
- Try: Execute function and panic on error
- Try0-Try6: Execute function with 0-6 return values and panic on error
- TryOr: Execute function and return fallback value on error
- TryOr1-TryOr6: Execute function with 1-6 return values and return fallback on error
- TryWithErrorValue: Execute function and return error value on panic
- TryCatch: Execute function with panic recovery
- TryCatchWithErrorValue: Execute function with panic recovery and error value
- ErrorsAs: Convert error to target type
- Assert: Panic if condition is false
- Assertf: Panic with formatted message if condition is false
### Find
- IndexOf: Get index of first matching element
- LastIndexOf: Get index of last matching element
- HasPrefix: Check if collection starts with elements
- HasSuffix: Check if collection ends with elements
- Find: Get first element matching predicate
- FindIndexOf: Get first element and its index matching predicate
- FindLastIndexOf: Get last element and its index matching predicate
- FindOrElse: Get first element matching predicate or fallback value
- FindKey: Get first key in map with matching value
- FindKeyBy: Get first key in map matching predicate
- FindUniques: Get slice of unique elements
- FindUniquesBy: Get slice of unique elements by transform
- FindDuplicates: Get slice of duplicate elements
- FindDuplicatesBy: Get slice of duplicate elements by transform
- Min: Get minimum value in collection
- MinIndex: Get index of minimum value
- MinBy: Get minimum value by comparison function
- MinIndexBy: Get index of minimum value by comparison function
- Earliest: Get earliest time value
- EarliestBy: Get earliest value by time comparison
- Max: Get maximum value in collection
- MaxIndex: Get index of maximum value
- MaxBy: Get maximum value by comparison function
- MaxIndexBy: Get index of maximum value by comparison function
- Latest: Get latest time value
- LatestBy: Get latest value by time comparison
- First: Get first element from collection
- FirstOrEmpty: Get first element or zero value
- FirstOr: Get first element or fallback value
- Last: Get last element from collection
- LastOrEmpty: Get last element or zero value
- LastOr: Get last element or fallback value
- Nth: Get element at index
- NthOr: Get element at index or fallback value
- NthOrEmpty: Get element at index or zero value
- Sample: Get random element from collection
- SampleBy: Get random element using custom generator
- Samples: Get N random unique elements
- SamplesBy: Get N random elements using custom generator
### Function
- Partial: Create function with some arguments pre-filled
- Partial1-Partial5: Create functions with 1-5 arguments pre-filled
### Intersect
- Contains: Check if collection contains element
- ContainsBy: Check if collection contains element matching predicate
- Every: Check if all elements in collection match subset
- EveryBy: Check if all elements match predicate
- Some: Check if any elements in collection match subset
- SomeBy: Check if any elements match predicate
- None: Check if no elements in collection match subset
- NoneBy: Check if no elements match predicate
- Intersect: Get elements common to all collections
- IntersectBy: Get elements common to all collections with key selector
- Difference: Get elements in first collection but not in others
- Union: Get all unique elements from collections
- Without: Get collection with specified elements removed
- WithoutBy: Get collection with elements removed by predicate
- WithoutNth: Get collection with element at index removed
- ElementsMatch: Check if collections contain same elements
- ElementsMatchBy: Check if collections match by transform function
### Map
- Keys: Get slice of map keys
- UniqKeys: Get slice of unique map keys
- HasKey: Check if map contains key
- Values: Get slice of map values
- UniqValues: Get slice of unique map values
- ValueOr: Get map value or fallback if key not found
- PickBy: Create map with entries matching predicate
- PickByKeys: Create map with specified keys
- PickByValues: Create map with specified values
- OmitBy: Create map without entries matching predicate
- OmitByKeys: Create map without specified keys
- OmitByValues: Create map without specified values
- Entries: Get slice of key-value pairs from map
- ToPairs: Alias for Entries
- FromEntries: Create map from key-value pairs
- FromPairs: Alias for FromEntries
- Invert: Create map with keys and values swapped
- Assign: Merge multiple maps into one
- ChunkEntries: Split map entries into chunks of specified size
- MapKeys: Transform map keys using function
- MapValues: Transform map values using function
- MapEntries: Transform map entries using function
- MapToSlice: Convert map to slice using function
- FilterMapToSlice: Filter and convert map to slice
- FilterKeys: Get slice of keys matching predicate
- FilterValues: Get slice of values matching predicate
### Math
- Range: Generate slice of numbers from 0 to n-1
- RangeFrom: Generate slice of numbers from start to end
- RangeWithSteps: Generate slice of numbers with custom step size
- Clamp: Constrain value between minimum and maximum
- Sum: Calculate sum of numbers
- SumBy: Calculate sum by applying function to each element
- Product: Calculate product of numbers
- ProductBy: Calculate product by applying function to each element
- Mean: Calculate arithmetic mean of numbers
- MeanBy: Calculate mean by applying function to each element
- Mode: Find most frequently occurring value
### Retry
- NewDebounce: Create function that delays execution until after calls stop
- NewDebounceBy: Create debounced function with key-based grouping
- Attempt: Execute function with specified number of retries
- AttemptWithDelay: Execute function with retries and delay between attempts
- AttemptWhile: Execute function while condition is true
- AttemptWhileWithDelay: Execute function while condition is true with delay
- NewTransaction: Create transaction with rollback capability
- NewThrottle: Create function that limits execution frequency
- NewThrottleWithCount: Create throttled function with execution count limit
- NewThrottleBy: Create throttled function with key-based grouping
- NewThrottleByWithCount: Create throttled function with key-based grouping and count limit
### Slice
- Filter: Get elements matching predicate
- Map: Transform each element using function
- UniqMap: Transform elements and remove duplicates
- FilterMap: Filter and transform elements in one operation
- FlatMap: Transform elements and flatten result
- Reduce: Combine elements into single value
- ReduceRight: Combine elements from right to left
- ForEach: Execute function for each element
- ForEachWhile: Execute function while predicate returns true
- Times: Execute function n times and collect results
- Uniq: Remove duplicate elements
- UniqBy: Remove duplicates by key function
- GroupBy: Group elements by key function
- GroupByMap: Group elements by key function into map
- Chunk: Split slice into chunks of specified size
- Window: Create sliding windows of specified size (overlapping)
- Sliding: Create sliding windows with specified size and step
- PartitionBy: Split elements into two groups by predicate
- Flatten: Flatten nested slices into single slice
- Concat: Combine multiple slices into one
- Interleave: Interleave elements from multiple slices
- Fill: Fill slice with specified value
- Repeat: Create slice with element repeated n times
- RepeatBy: Create slice by calling function n times
- KeyBy: Create map from slice using key function
- Associate: Create map from slice using key-value function
- AssociateI: Create map from slice with index-aware function
- SliceToMap: Convert slice to map using key function
- SliceToMapI: Convert slice to map with index-aware function
- FilterSliceToMap: Filter and convert slice to map
- FilterSliceToMapI: Filter and convert slice to map with index
- Keyify: Create set from slice elements
- Take: Get first n elements from slice
- TakeWhile: Get elements from start while predicate is true
- TakeFilter / TakeFilterI: Filter and take first n matching elements (efficient)
- Drop: Remove first n elements
- DropRight: Remove last n elements
- DropWhile: Remove elements from start while predicate is true
- DropRightWhile: Remove elements from end while predicate is true
- DropByIndex: Remove elements at specified indices
- Reject: Remove elements matching predicate
- RejectMap: Remove and transform elements matching predicate
- FilterReject: Split elements into matching and non-matching groups
- Count: Count occurrences of value
- CountBy: Count elements matching predicate
- CountValues: Count frequency of each value
- CountValuesBy: Count frequency by key function
- Subset: Get slice of elements from offset with length
- Slice: Get slice of elements from start to end
- Replace: Replace first n occurrences of value
- ReplaceAll: Replace all occurrences of value
- Clone: Perform a shallow copy of the collection
- Compact: Remove zero values from slice
- IsSorted: Check if slice is sorted in ascending order
- IsSortedBy: Check if slice is sorted by key function
- Splice: Insert elements at specified index
- Cut: Split string at first occurrence of separator
- CutPrefix: Remove prefix from string if present
- CutSuffix: Remove suffix from string if present
- Trim: Remove whitespace from both ends of string
- TrimLeft: Remove whitespace from start of string
- TrimPrefix: Remove prefix from string if present
- TrimRight: Remove whitespace from end of string
- TrimSuffix: Remove suffix from string if present
### String
- RandomString: Generate random string of specified length
- Substring: Extract substring from start to end
- ChunkString: Split string into chunks of specified size
- RuneLength: Get number of runes in string
- PascalCase: Convert string to PascalCase
- CamelCase: Convert string to camelCase
- KebabCase: Convert string to kebab-case
- SnakeCase: Convert string to snake_case
- Words: Split string into slice of words
- Capitalize: Capitalize first letter of string
- Ellipsis: Truncate string to length with ellipsis
### Time
- Duration: Measure execution time of function
- Duration0-Duration10: Measure execution time of function with 0-10 return values
- EarliestBy: Find earliest time value by comparison function
- Latest: Find latest time value
- LatestBy: Find latest time value by comparison function
### Tuple
- T2-T9: Create tuple with 2-9 elements
- Unpack2-Unpack9: Unpack tuple with 2-9 elements into variables
- Zip2-Zip9: Combine 2-9 collections into tuples
- ZipBy2-ZipBy9: Combine 2-9 collections into tuples using function
- CrossJoin2-CrossJoin9: Create cartesian product of 2-9 collections
- CrossJoinBy2-CrossJoinBy9: Create cartesian product of 2-9 collections using function
### Type
- IsNil: Check if value is nil
- IsNotNil: Check if value is not nil
- ToPtr: Convert value to pointer
- Nil: Get nil value of type
- EmptyableToPtr: Convert emptyable value to pointer
- FromPtr: Get value from pointer
- FromPtrOr: Get value from pointer or fallback if nil
- ToSlicePtr: Convert slice to slice of pointers
- FromSlicePtr: Convert slice of pointers to slice
- FromSlicePtrOr: Convert slice of pointers to slice or fallback
- ToAnySlice: Convert slice to []any
- FromAnySlice: Convert []any to typed slice
- Empty: Get zero value of type
- IsEmpty: Check if value is zero/empty
- IsNotEmpty: Check if value is not zero/empty
- Coalesce: Return first non-zero value
- CoalesceOrEmpty: Return first non-zero value or zero value
- CoalesceSlice: Return first non-empty slice
- CoalesceSliceOrEmpty: Return first non-empty slice or empty slice
- CoalesceMap: Return first non-empty map
- CoalesceMapOrEmpty: Return first non-empty map or empty map
### Channel
- ChannelDispatcher: Interface for dispatching values to channels
- DispatchingStrategyRoundRobin: Distribute values evenly across channels
- DispatchingStrategyRandom: Distribute values randomly across channels
- DispatchingStrategyWeightedRandom: Distribute values with weighted randomness
- DispatchingStrategyFirst: Send to first available channel
- DispatchingStrategyLeast: Send to least busy channel
- DispatchingStrategyMost: Send to most busy channel
- SliceToChannel: Convert slice to buffered channel
- ChannelToSlice: Collect all values from channel into slice
- Buffer: Buffer channel values with specified capacity
- BufferWithContext: Buffer channel values with context cancellation
- BufferWithTimeout: Buffer channel values with timeout
- FanIn: Combine multiple channels into single channel
- FanOut: Distribute single channel to multiple channels
## Mutable Package Helpers
The lo/mutable package provides in-place mutation helpers for memory-efficient operations on slices:
- Filter: Remove elements that don't match predicate in-place
- FilterI: Remove elements that don't match index predicate in-place
- Map: Transform collection elements in-place
- MapI: Transform collection elements with index in-place
- Shuffle: Randomly shuffle collection in-place using Fisher-Yates algorithm
- Reverse: Reverse order of collection elements in-place
## Parallel Package Helpers
The lo/parallel package provides parallel processing helpers for concurrent operations on slices:
- Map: Transform collection elements in parallel while maintaining order
- ForEach: Execute function on each element in parallel
- Times: Execute function n times in parallel and collect results
- GroupBy: Group collection elements by key using parallel processing
- PartitionBy: Split collection into two groups by predicate using parallel processing
## Iterator Package Helpers
The lo/it package provides iterator helpers for lazy evaluation and streaming operations on sequences:
### Channel Operations
- SeqToChannel: Convert sequence to buffered channel
- SeqToChannel2: Convert key-value sequence to buffered channel
- ChannelToSeq: Convert channel to sequence
### Find Operations
- IndexOf: Get index of first matching element in sequence
- LastIndexOf: Get index of last matching element in sequence
- Find: Get first element matching predicate
- FindIndexOf: Get first element and its index matching predicate
- FindLastIndexOf: Get last element and its index matching predicate
- FindOrElse: Get first element matching predicate or fallback value
- FindUniques: Get slice of unique elements from sequence
- FindUniquesBy: Get slice of unique elements by transform function
- FindDuplicates: Get slice of duplicate elements from sequence
- FindDuplicatesBy: Get slice of duplicate elements by transform function
- Min: Get minimum value from sequence
- MinBy: Get minimum value by comparison function
- MinIndex: Get minimum value and its index
- MinIndexBy: Get minimum value and index by comparison function
- Max: Get maximum value from sequence
- MaxBy: Get maximum value by comparison function
- MaxIndex: Get maximum value and its index
- MaxIndexBy: Get maximum value and index by comparison function
- Earliest: Get earliest time value from sequence
- EarliestBy: Get earliest value by time comparison function
- Latest: Get latest time value from sequence
- LatestBy: Get latest value by time comparison function
- First: Get first element from sequence
- FirstOrEmpty: Get first element or zero value
- FirstOr: Get first element or fallback value
- Last: Get last element from sequence
- LastOrEmpty: Get last element or zero value
- LastOr: Get last element or fallback value
- Nth: Get element at index from sequence
- NthOr: Get element at index or fallback value
- NthOrEmpty: Get element at index or zero value
- Sample: Get random element from sequence
- SampleBy: Get random element using custom generator
- Samples: Get N random unique elements from sequence
- SamplesBy: Get N random elements using custom generator
### Intersect Operations
- Contains: Check if sequence contains element
- ContainsBy: Check if sequence contains element matching predicate
- Every: Check if all elements in sequence match subset
- EveryBy: Check if all elements match predicate
- Some: Check if any elements in sequence match subset
- SomeBy: Check if any elements match predicate
- None: Check if no elements in sequence match subset
- NoneBy: Check if no elements match predicate
- Intersect: Get elements common to all sequences
- IntersectBy: Get elements common to all sequences with key selector
- Union: Get all unique elements from sequences
- Without: Get sequence with specified elements excluded
- WithoutBy: Get sequence with elements excluded by key transform
- WithoutNth: Get sequence with element at index excluded
- ElementsMatch: Check if sequences contain same elements
- ElementsMatchBy: Check if sequences match by transform function
### Map Operations
- Keys: Create sequence from map keys
- UniqKeys: Create sequence from unique map keys
- Values: Create sequence from map values
- UniqValues: Create sequence from unique map values
- Entries: Transform map to key-value sequence
- ToPairs: Alias for Entries
- FromEntries: Transform key-value sequence to map
- FromPairs: Alias for FromEntries
- Invert: Create sequence with keys and values swapped
- Assign: Merge multiple map sequences into one
- ChunkEntries: Split map entries into chunks of specified size
- MapToSeq: Transform map to sequence using function
- FilterMapToSeq: Filter and transform map to sequence
- FilterKeys: Get sequence of keys matching predicate
- FilterValues: Get sequence of values matching predicate
- SeqToSeq2: Convert sequence to indexed sequence
- Seq2KeyToSeq: Extract keys from key-value sequence
- Seq2ValueToSeq: Extract values from key-value sequence
### Math Operations
- Range: Generate sequence of numbers from 0 to n-1
- RangeFrom: Generate sequence of numbers from start to end
- RangeWithSteps: Generate sequence of numbers with custom step size
- Sum: Calculate sum of numbers in sequence
- SumBy: Calculate sum by applying function to each element
- Product: Calculate product of numbers in sequence
- ProductBy: Calculate product by applying function to each element
- Mean: Calculate arithmetic mean of numbers in sequence
- MeanBy: Calculate mean by applying function to each element
- Mode: Find most frequently occurring values in sequence
### Sequence Operations
- Length: Get number of elements in sequence
- Drain: Consume entire sequence and return slice
- Filter: Get elements matching predicate
- FilterI: Get elements matching index predicate
- Map: Transform each element using function
- MapI: Transform elements with index using function
- UniqMap: Transform elements and remove duplicates
- UniqMapI: Transform elements with index and remove duplicates
- FilterMap: Filter and transform elements in one operation
- FilterMapI: Filter and transform elements with index in one operation
- FlatMap: Transform elements and flatten result
- FlatMapI: Transform elements with index and flatten result
- Reduce: Combine elements into single value
- ReduceI: Combine elements with index awareness
- ReduceLast: Combine elements from right to left
- ReduceLastI: Combine elements from right with index
- ForEach: Execute function for each element
- ForEachI: Execute function with index for each element
- ForEachWhile: Execute function while predicate returns true
- ForEachWhileI: Execute function with index while predicate returns true
- Times: Execute function n times and collect results
- Uniq: Remove duplicate elements
- UniqBy: Remove duplicates by key function
- GroupBy: Group elements by key function
- GroupByMap: Group elements by key function into map
- Chunk: Split sequence into chunks of specified size
- Window: Create sliding windows of specified size (overlapping)
- Sliding: Create sliding windows with specified size and step
- PartitionBy: Split elements into two groups by predicate
- Flatten: Flatten nested sequences into single sequence
- Concat: Combine multiple sequences into one
- Interleave: Interleave elements from multiple sequences
- Shuffle: Randomly shuffle sequence elements
- Reverse: Reverse order of sequence elements
- Fill: Fill sequence with specified value
- Repeat: Create sequence with value repeated n times
- RepeatBy: Create sequence by calling function n times
- KeyBy: Create map from sequence using key function
- Associate: Create map from sequence using key-value function
- AssociateI: Create map from sequence with index-aware function
- SeqToMap: Convert sequence to map using key function
- SeqToMapI: Convert sequence to map with index-aware function
- FilterSeqToMap: Filter and convert sequence to map
- FilterSeqToMapI: Filter and convert sequence to map with index
- Keyify: Create set from sequence elements
- Drop: Remove first n elements
- DropLast: Remove last n elements
- DropWhile: Remove elements from start while predicate is true
- DropLastWhile: Remove elements from end while predicate is true
- Take: Get first n elements from sequence
- TakeWhile: Get elements from start while predicate is true
- DropByIndex: Remove elements at specified indices
- TakeFilter / TakeFilterI: Filter and take first n matching elements
- Reject: Remove elements matching predicate
- RejectI: Remove elements matching index predicate
- RejectMap: Remove and transform elements matching predicate
- RejectMapI: Remove and transform elements matching index predicate
- Count: Count occurrences of value in sequence
- CountBy: Count elements matching predicate
- CountValues: Count frequency of each value in sequence
- CountValuesBy: Count frequency by key function
- Subset: Get slice of elements from offset with length
- Slice: Get slice of elements from start to end
- Replace: Replace first n occurrences of value
- ReplaceAll: Replace all occurrences of value
- Compact: Remove zero values from sequence
- IsSorted: Check if sequence is sorted in ascending order
- IsSortedBy: Check if sequence is sorted by key function
- Splice: Insert elements at specified index
- CutPrefix: Remove prefix from sequence if present
- CutSuffix: Remove suffix from sequence if present
- Trim: Remove elements from both ends of sequence
- TrimFirst: Remove elements from start of sequence
- TrimPrefix: Remove prefix from sequence if present
- TrimLast: Remove elements from end of sequence
- TrimSuffix: Remove suffix from sequence if present
### String Operations
- ChunkString: Split string into chunks of specified size
### Tuple Operations
- Zip2-Zip9: Combine 2-9 sequences into tuples
- ZipBy2-ZipBy9: Combine 2-9 sequences into tuples using function
- CrossJoin2-CrossJoin9: Create cartesian product of 2-9 sequences
- CrossJoinBy2-CrossJoinBy9: Create cartesian product of 2-9 sequences using function
### Type Manipulation
- ToSeqPtr: Convert sequence to sequence of pointers
- FromSeqPtr: Convert sequence of pointers to sequence
- FromSeqPtrOr: Convert sequence of pointers to sequence or fallback
- ToAnySeq: Convert sequence to []any
- FromAnySeq: Convert []any to typed sequence
- Empty: Create empty sequence
- IsEmpty: Check if sequence is empty
- IsNotEmpty: Check if sequence is not empty
- CoalesceSeq: Return first non-empty sequence
- CoalesceSeqOrEmpty: Return first non-empty sequence or empty sequence
## Additional Resources
- **Documentation**: https://lo.samber.dev
- **GoDoc**: https://pkg.go.dev/github.com/samber/lo
- **GitHub**: https://github.com/samber/lo
- **Playground**: Every helper includes a Go Playground example for quick testing
- **Community**: Active community with contributions from hundreds of developers
## Usage Examples
### Basic Collection Operations
```go
import "github.com/samber/lo"
// Remove duplicates from a slice
names := lo.Uniq([]string{"Samuel", "John", "Samuel"})
// []string{"Samuel", "John"}
// Filter and transform in one operation
numbers := lo.FilterMap([]int{1, 2, 3, 4, 5}, func(item int, index int) (string, bool) {
if item%2 == 0 {
return fmt.Sprintf("even-%d", item), true
}
return "", false
})
// []string{"even-2", "even-4"}
```
### Advanced Data Processing
```go
// Complex data transformation pipeline
users := []User{{ID: 1, Name: "Alice", Active: true}, {ID: 2, Name: "Bob", Active: false}}
// Group active users by name length, then extract IDs
activeUserIDs := lo.Pipe(
users,
lo.Filter(func(u User) bool { return u.Active }),
lo.GroupBy(func(u User) int { return len(u.Name) }),
lo.MapValues(func(users []User) []int {
return lo.Map(users, func(u User) int { return u.ID })
}),
)
// map[int][]int{5: []int{1}}
// Safe nested data access
config := map[string]any{
"database": map[string]any{
"host": "localhost",
"port": 5432,
},
}
host := lo.ValueOr(lo.CoalesceMap(
lo.MapValues(config, func(v any) map[string]any {
if m, ok := v.(map[string]any); ok {
return m
}
return nil
}),
), "localhost")
```
### Concurrency and Async Operations
```go
// Parallel processing of large datasets
results := lop.Map(items, func(item Item) Result {
return processItem(item) // Executed in parallel
})
// Debounce API calls to avoid rate limiting
debouncedSearch := lo.NewDebounce(300*time.Millisecond, func() {
results := searchAPI(query)
updateUI(results)
})
// Safe error handling with fallbacks
result, ok := lo.TryOr(func() (int, error) {
return dangerousOperation()
}, 0)
```
### Functional Control Flow
```go
// Elegant conditional logic
message := lo.If(user != nil, "Welcome back!").
ElseIfF(isGuest, func() string { return "Welcome guest!" }).
Else("Please login")
// Retry logic with exponential backoff
result, err := lo.AttemptWithDelay(3, time.Second, func(i int, d time.Duration) error {
return callExternalAPI(context.Background())
})
// Transaction management
tx := lo.NewTransaction[Order]()
defer tx.Rollback()
order, err := tx.Run(func() (Order, error) {
inventory := checkInventory(items)
if !inventory.Available {
return Order{}, errors.New("insufficient inventory")
}
return createOrder(user, items)
})
```
## Performance Considerations
Lo is engineered for performance-critical applications with several optimization strategies:
### Memory Efficiency
- **Zero-copy patterns**: Many helpers avoid unnecessary allocations by working with existing data
- **Slice reuse**: Mutable variants (`lo/mutable`) enable in-place operations for memory-constrained environments
- **Lazy evaluation**: Iterator patterns in `lo/iter` support streaming of large datasets
- **Pre-allocation**: Collection sizes are pre-calculated when possible to minimize reallocations
### Performance Characteristics
- **Compile-time optimization**: Generic types enable compile-time specialization and optimization
- **Minimal overhead**: Most helpers add zero or single-digit nanosecond overhead
- **Cache-friendly**: Linear memory access patterns for better CPU cache utilization
- **Branch prediction**: Hot paths are optimized for common case execution
### Benchmarks and Optimization
Every helper includes comprehensive benchmarks covering:
- Different input sizes (small, medium, large)
- Various data types (int, string, structs)
- Comparison with standard library alternatives
- Memory allocation tracking
```go
// Example benchmark results for lo.Uniq vs manual implementation
BenchmarkLoUniq-8 1000000 1200 ns/op 800 B/op 4 allocs/op
BenchmarkManualUniq-8 5000000 240 ns/op 0 B/op 0 allocs/op (when in-place)
```
### Production Optimization Tips
1. **Use `lo/it`** for large datasets when memory is a concern
2. **Leverage `lo/parallel`** for CPU-bound operations on large collections
3. **Choose appropriate helpers** - some have specialized variants for specific use cases
4. **Profile your code** - use Go's built-in profiling tools to identify bottlenecks
5. **Consider streaming** - use iterators for very large datasets that don't fit in memory
## Ecosystem and Community
### Related Projects
Lo is part of a growing ecosystem of Go libraries that enhance developer productivity:
- **[samber/ro](https://github.com/samber/ro)**: Reactive Programming library for Go
- **[samber/do](https://github.com/samber/do)**: A dependency injection toolkit based on Go 1.18+ Generics
- **[samber/mo](https://github.com/samber/mo)**: Monads based on Go 1.18+ Generics (Option, Result, Either...)
### Community and Contributions
- **Active development**: Regular updates and improvements from maintainers
- **Community contributions**: Hundreds of contributors from around the world
- **Corporate adoption**: Used by major companies including startups, fintech, and enterprise organizations
- **Discussions**: Active GitHub discussions for feature requests and support
## Migration and Adoption
### From Standard Library
Migrating from standard library `slices` and `maps` packages is straightforward:
- Lo provides similar APIs with additional features
- Many helpers drop-in replace standard library functions
- Enhanced error handling and edge case coverage
### From Other Libraries
If you're coming from other utility libraries:
- Lo emphasizes Go idioms over direct ports from other languages
- Type safety is prioritized over dynamic patterns
- Performance is a key consideration in all implementations
## License
MIT License - see LICENSE file for details
## Acknowledgments
Lo stands on the shoulders of giants:
- Inspired by Lodash, Underscore.js, and functional programming concepts
- Built upon Go's excellent generics implementation
- Community-driven with contributions from hundreds of developers
- Production-tested across diverse use cases and industries
================================================
FILE: docs/tsconfig.json
================================================
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": "."
},
"exclude": [".docusaurus", "build"]
}
================================================
FILE: errors.go
================================================
package lo
import (
"errors"
"fmt"
"reflect"
)
const defaultAssertionFailureMessage = "assertion failed"
// Validate is a helper that creates an error when a condition is not met.
// Play: https://go.dev/play/p/vPyh51XpCBt
func Validate(ok bool, format string, args ...any) error {
if !ok {
return fmt.Errorf(format, args...)
}
return nil
}
func messageFromMsgAndArgs(msgAndArgs ...any) string {
if len(msgAndArgs) == 1 {
if msgAsStr, ok := msgAndArgs[0].(string); ok {
return msgAsStr
}
return fmt.Sprintf("%+v", msgAndArgs[0])
}
if len(msgAndArgs) > 1 {
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) //nolint:errcheck,forcetypeassert
}
return ""
}
// MustChecker panics if err is error or false.
var MustChecker = func(err any, messageArgs ...any) {
if err == nil {
return
}
switch e := err.(type) {
case bool:
if !e {
message := messageFromMsgAndArgs(messageArgs...)
if message == "" {
message = "not ok"
}
panic(message)
}
case error:
message := messageFromMsgAndArgs(messageArgs...)
if message != "" {
panic(message + ": " + e.Error())
}
panic(e.Error())
default:
panic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error")
}
}
// Must is a helper that wraps a call to a function returning a value and an error
// and panics if err is error or false.
// Play: https://go.dev/play/p/fOqtX5HudtN
func Must[T any](val T, err any, messageArgs ...any) T {
MustChecker(err, messageArgs...)
return val
}
// Must0 has the same behavior as Must, but callback returns no variable.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must0(err any, messageArgs ...any) {
MustChecker(err, messageArgs...)
}
// Must1 is an alias to Must.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must1[T any](val T, err any, messageArgs ...any) T {
return Must(val, err, messageArgs...)
}
// Must2 has the same behavior as Must, but callback returns 2 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must2[T1, T2 any](val1 T1, val2 T2, err any, messageArgs ...any) (T1, T2) {
MustChecker(err, messageArgs...)
return val1, val2
}
// Must3 has the same behavior as Must, but callback returns 3 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must3[T1, T2, T3 any](val1 T1, val2 T2, val3 T3, err any, messageArgs ...any) (T1, T2, T3) {
MustChecker(err, messageArgs...)
return val1, val2, val3
}
// Must4 has the same behavior as Must, but callback returns 4 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must4[T1, T2, T3, T4 any](val1 T1, val2 T2, val3 T3, val4 T4, err any, messageArgs ...any) (T1, T2, T3, T4) {
MustChecker(err, messageArgs...)
return val1, val2, val3, val4
}
// Must5 has the same behavior as Must, but callback returns 5 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must5[T1, T2, T3, T4, T5 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, err any, messageArgs ...any) (T1, T2, T3, T4, T5) {
MustChecker(err, messageArgs...)
return val1, val2, val3, val4, val5
}
// Must6 has the same behavior as Must, but callback returns 6 variables.
// Play: https://go.dev/play/p/TMoWrRp3DyC
func Must6[T1, T2, T3, T4, T5, T6 any](val1 T1, val2 T2, val3 T3, val4 T4, val5 T5, val6 T6, err any, messageArgs ...any) (T1, T2, T3, T4, T5, T6) {
MustChecker(err, messageArgs...)
return val1, val2, val3, val4, val5, val6
}
// Try calls the function and return false in case of error.
func Try(callback func() error) (ok bool) {
ok = true
defer func() {
if r := recover(); r != nil {
ok = false
}
}()
err := callback()
if err != nil {
ok = false
}
return ok
}
// Try0 has the same behavior as Try, but callback returns no variable.
// Play: https://go.dev/play/p/mTyyWUvn9u4
func Try0(callback func()) bool {
return Try(func() error {
callback()
return nil
})
}
// Try1 is an alias to Try.
// Play: https://go.dev/play/p/mTyyWUvn9u4
func Try1(callback func() error) bool {
return Try(callback)
}
// Try2 has the same behavior as Try, but callback returns 2 variables.
// Play: https://go.dev/play/p/mTyyWUvn9u4
func Try2[T any](callback func() (T, error)) bool {
return Try(func() error {
_, err := callback()
return err
})
}
// Try3 has the same behavior as Try, but callback returns 3 variables.
// Play: https://go.dev/play/p/mTyyWUvn9u4
func Try3[T, R any](callback func() (T, R, error)) bool {
return Try(func() error {
_, _, err := callback()
return err
})
}
// Try4 has the same behavior as Try, but callback returns 4 variables.
// Play: https://go.dev/play/p/mTyyWUvn9u4
func Try4[T, R, S any](callback func() (T, R, S, error)) bool {
return Try(func() error {
_, _, _, err := callback()
return err
})
}
// Try5 has the same behavior as Try, but callback returns 5 variables.
// Play: https://go.dev/play/p/mTyyWUvn9u4
func Try5[T, R, S, Q any](callback func() (T, R, S, Q, error)) bool {
return Try(func() error {
_, _, _, _, err := callback()
return err
})
}
// Try6 has the same behavior as Try, but callback returns 6 variables.
// Play: https://go.dev/play/p/mTyyWUvn9u4
func Try6[T, R, S, Q, U any](callback func() (T, R, S, Q, U, error)) bool {
return Try(func() error {
_, _, _, _, _, err := callback()
return err
})
}
// TryOr has the same behavior as Must, but returns a default value in case of error.
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
func TryOr[A any](callback func() (A, error), fallbackA A) (A, bool) {
return TryOr1(callback, fallbackA)
}
// TryOr1 has the same behavior as Must, but returns a default value in case of error.
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
func TryOr1[A any](callback func() (A, error), fallbackA A) (A, bool) {
ok := false
Try0(func() {
a, err := callback()
if err == nil {
fallbackA = a
ok = true
}
})
return fallbackA, ok
}
// TryOr2 has the same behavior as Must, but returns a default value in case of error.
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
func TryOr2[A, B any](callback func() (A, B, error), fallbackA A, fallbackB B) (A, B, bool) {
ok := false
Try0(func() {
a, b, err := callback()
if err == nil {
fallbackA = a
fallbackB = b
ok = true
}
})
return fallbackA, fallbackB, ok
}
// TryOr3 has the same behavior as Must, but returns a default value in case of error.
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
func TryOr3[A, B, C any](callback func() (A, B, C, error), fallbackA A, fallbackB B, fallbackC C) (A, B, C, bool) {
ok := false
Try0(func() {
a, b, c, err := callback()
if err == nil {
fallbackA = a
fallbackB = b
fallbackC = c
ok = true
}
})
return fallbackA, fallbackB, fallbackC, ok
}
// TryOr4 has the same behavior as Must, but returns a default value in case of error.
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
func TryOr4[A, B, C, D any](callback func() (A, B, C, D, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D) (A, B, C, D, bool) {
ok := false
Try0(func() {
a, b, c, d, err := callback()
if err == nil {
fallbackA = a
fallbackB = b
fallbackC = c
fallbackD = d
ok = true
}
})
return fallbackA, fallbackB, fallbackC, fallbackD, ok
}
// TryOr5 has the same behavior as Must, but returns a default value in case of error.
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
func TryOr5[A, B, C, D, E any](callback func() (A, B, C, D, E, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E) (A, B, C, D, E, bool) {
ok := false
Try0(func() {
a, b, c, d, e, err := callback()
if err == nil {
fallbackA = a
fallbackB = b
fallbackC = c
fallbackD = d
fallbackE = e
ok = true
}
})
return fallbackA, fallbackB, fallbackC, fallbackD, fallbackE, ok
}
// TryOr6 has the same behavior as Must, but returns a default value in case of error.
// Play: https://go.dev/play/p/B4F7Wg2Zh9X
func TryOr6[A, B, C, D, E, F any](callback func() (A, B, C, D, E, F, error), fallbackA A, fallbackB B, fallbackC C, fallbackD D, fallbackE E, fallbackF F) (A, B, C, D, E, F, bool) {
ok := false
Try0(func() {
a, b, c, d, e, f, err := callback()
if err == nil {
fallbackA = a
fallbackB = b
fallbackC = c
fallbackD = d
fallbackE = e
fallbackF = f
ok = true
}
})
return fallbackA, fallbackB, fallbackC, fallbackD, fallbackE, fallbackF, ok
}
// TryWithErrorValue has the same behavior as Try, but also returns value passed to panic.
// Play: https://go.dev/play/p/Kc7afQIT2Fs
func TryWithErrorValue(callback func() error) (errorValue any, ok bool) {
ok = true
defer func() {
if r := recover(); r != nil {
ok = false
errorValue = r
}
}()
err := callback()
if err != nil {
ok = false
errorValue = err
}
return errorValue, ok
}
// TryCatch has the same behavior as Try, but calls the catch function in case of error.
// Play: https://go.dev/play/p/PnOON-EqBiU
func TryCatch(callback func() error, catch func()) {
if !Try(callback) {
catch()
}
}
// TryCatchWithErrorValue has the same behavior as TryWithErrorValue, but calls the catch function in case of error.
// Play: https://go.dev/play/p/8Pc9gwX_GZO
func TryCatchWithErrorValue(callback func() error, catch func(any)) {
if err, ok := TryWithErrorValue(callback); !ok {
catch(err)
}
}
// ErrorsAs is a shortcut for errors.As(err, &&T).
// Play: https://go.dev/play/p/8wk5rH8UfrE
func ErrorsAs[T error](err error) (T, bool) {
var t T
ok := errors.As(err, &t)
return t, ok
}
// Assert does nothing when the condition is true, otherwise it panics with an optional message.
// Play: https://go.dev/play/p/Xv8LLKBMNwI
var Assert = func(condition bool, message ...string) {
if condition {
return
}
panicMessage := defaultAssertionFailureMessage
if len(message) > 0 {
panicMessage = fmt.Sprintf("%s: %s", defaultAssertionFailureMessage, message[0])
}
panic(panicMessage)
}
// Assertf does nothing when the condition is true, otherwise it panics with a formatted message.
// Play: https://go.dev/play/p/TVPEmVcyrdY
var Assertf = func(condition bool, format string, args ...any) {
if condition {
return
}
panicMessage := fmt.Sprintf("%s: %s", defaultAssertionFailureMessage, fmt.Sprintf(format, args...))
panic(panicMessage)
}
================================================
FILE: errors_test.go
================================================
package lo
import (
"errors"
"fmt"
"io"
"net/url"
"reflect"
"runtime/debug"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidate(t *testing.T) {
t.Parallel()
is := assert.New(t)
slice := []string{"a"}
result1 := Validate(len(slice) == 0, "Slice should be empty but contains %v", slice)
slice = []string{}
result2 := Validate(len(slice) == 0, "Slice should be empty but contains %v", slice)
is.Error(result1)
is.NoError(result2)
}
func TestMust(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
is.Equal("foo", Must("foo", nil))
is.PanicsWithValue("something went wrong", func() {
Must("", errors.New("something went wrong"))
})
is.PanicsWithValue("operation shouldn't fail: something went wrong", func() {
Must("", errors.New("something went wrong"), "operation shouldn't fail")
})
is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() {
Must("", errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
is.Equal(1, Must(1, true))
is.PanicsWithValue("not ok", func() {
Must(1, false)
})
is.PanicsWithValue("operation shouldn't fail", func() {
Must(1, false, "operation shouldn't fail")
})
is.PanicsWithValue("operation shouldn't fail with foo", func() {
Must(1, false, "operation shouldn't fail with %s", "foo")
})
cb := func() error {
return assert.AnError
}
is.PanicsWithValue("operation should fail: assert.AnError general error for testing", func() {
Must0(cb(), "operation should fail")
})
is.PanicsWithValue("must: invalid err type 'int', should either be a bool or an error", func() {
Must0(0)
})
is.PanicsWithValue("must: invalid err type 'string', should either be a bool or an error", func() {
Must0("error")
})
}
func TestMustX(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
{
is.PanicsWithValue("something went wrong", func() {
Must0(errors.New("something went wrong"))
})
is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() {
Must0(errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
is.NotPanics(func() {
Must0(nil)
})
}
{
val1 := Must1(1, nil)
is.Equal(1, val1)
is.PanicsWithValue("something went wrong", func() {
Must1(1, errors.New("something went wrong"))
})
is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() {
Must1(1, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2 := Must2(1, 2, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.PanicsWithValue("something went wrong", func() {
Must2(1, 2, errors.New("something went wrong"))
})
is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() {
Must2(1, 2, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2, val3 := Must3(1, 2, 3, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.PanicsWithValue("something went wrong", func() {
Must3(1, 2, 3, errors.New("something went wrong"))
})
is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() {
Must3(1, 2, 3, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2, val3, val4 := Must4(1, 2, 3, 4, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.PanicsWithValue("something went wrong", func() {
Must4(1, 2, 3, 4, errors.New("something went wrong"))
})
is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() {
Must4(1, 2, 3, 4, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2, val3, val4, val5 := Must5(1, 2, 3, 4, 5, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.Equal(5, val5)
is.PanicsWithValue("something went wrong", func() {
Must5(1, 2, 3, 4, 5, errors.New("something went wrong"))
})
is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() {
Must5(1, 2, 3, 4, 5, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2, val3, val4, val5, val6 := Must6(1, 2, 3, 4, 5, 6, nil)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.Equal(5, val5)
is.Equal(6, val6)
is.PanicsWithValue("something went wrong", func() {
Must6(1, 2, 3, 4, 5, 6, errors.New("something went wrong"))
})
is.PanicsWithValue("operation shouldn't fail with foo: something went wrong", func() {
Must6(1, 2, 3, 4, 5, 6, errors.New("something went wrong"), "operation shouldn't fail with %s", "foo")
})
}
{
is.PanicsWithValue("not ok", func() {
Must0(false)
})
is.PanicsWithValue("operation shouldn't fail with foo", func() {
Must0(false, "operation shouldn't fail with %s", "foo")
})
is.NotPanics(func() {
Must0(true)
})
}
{
val1 := Must1(1, true)
is.Equal(1, val1)
is.PanicsWithValue("not ok", func() {
Must1(1, false)
})
is.PanicsWithValue("operation shouldn't fail with foo", func() {
Must1(1, false, "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2 := Must2(1, 2, true)
is.Equal(1, val1)
is.Equal(2, val2)
is.PanicsWithValue("not ok", func() {
Must2(1, 2, false)
})
is.PanicsWithValue("operation shouldn't fail with foo", func() {
Must2(1, 2, false, "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2, val3 := Must3(1, 2, 3, true)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.PanicsWithValue("not ok", func() {
Must3(1, 2, 3, false)
})
is.PanicsWithValue("operation shouldn't fail with foo", func() {
Must3(1, 2, 3, false, "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2, val3, val4 := Must4(1, 2, 3, 4, true)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.PanicsWithValue("not ok", func() {
Must4(1, 2, 3, 4, false)
})
is.PanicsWithValue("operation shouldn't fail with foo", func() {
Must4(1, 2, 3, 4, false, "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2, val3, val4, val5 := Must5(1, 2, 3, 4, 5, true)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.Equal(5, val5)
is.PanicsWithValue("not ok", func() {
Must5(1, 2, 3, 4, 5, false)
})
is.PanicsWithValue("operation shouldn't fail with foo", func() {
Must5(1, 2, 3, 4, 5, false, "operation shouldn't fail with %s", "foo")
})
}
{
val1, val2, val3, val4, val5, val6 := Must6(1, 2, 3, 4, 5, 6, true)
is.Equal(1, val1)
is.Equal(2, val2)
is.Equal(3, val3)
is.Equal(4, val4)
is.Equal(5, val5)
is.Equal(6, val6)
is.PanicsWithValue("not ok", func() {
Must6(1, 2, 3, 4, 5, 6, false)
})
is.PanicsWithValue("operation shouldn't fail with foo", func() {
Must6(1, 2, 3, 4, 5, 6, false, "operation shouldn't fail with %s", "foo")
})
}
}
func mustCheckerWithStack(err any, messageArgs ...any) {
if err == nil {
return
}
switch e := err.(type) {
case bool:
if !e {
message := messageFromMsgAndArgs(messageArgs...)
if message == "" {
message = "not ok"
}
// panic(stackErrors.New(message))
panic(errorsJoin(errors.New(message), errors.New(string(debug.Stack()))))
}
case error:
message := messageFromMsgAndArgs(messageArgs...)
if message != "" {
// panic(stackErrors.Wrap(e, message))
panic(errorsJoin(e, errors.New(message), errors.New(string(debug.Stack()))))
}
// panic(stackErrors.WithStack(e))
panic(errorsJoin(e, errors.New(string(debug.Stack()))))
default:
// panic(stackErrors.New("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error"))
panic(errorsJoin(errors.New("must: invalid err type '"+reflect.TypeOf(err).Name()+"', should either be a bool or an error"),
errors.New(string(debug.Stack()))))
}
}
// errorsJoin: var errorsJoin = errors.Join // only go 1.20+, not in go 1.18 .
func errorsJoin(es ...error) joinErrors { return joinErrors(es) }
type joinErrors []error
func (es joinErrors) Is(target error) bool {
for _, e := range es {
if errors.Is(e, target) {
return true
}
}
return error(es) == target
}
func (es joinErrors) Error() string {
sb := strings.Builder{}
for _, e := range es {
sb.WriteString(e.Error())
sb.WriteRune('\n')
}
return sb.String()
}
func (es joinErrors) As(t any) bool {
for _, e := range es {
if errors.As(e, t) {
return true
}
}
return false
}
func TestMustUserCustomHandler(t *testing.T) { //nolint:paralleltest
oldMustChecker := MustChecker
MustChecker = mustCheckerWithStack
defer func() {
MustChecker = oldMustChecker
}()
t.Run("wrap stack", func(t *testing.T) { //nolint:paralleltest
err, ok := TryWithErrorValue(func() error {
Must("foo", errors.New("wrap callstack"))
return nil
})
assert.False(t, ok)
fullErrStr := fmt.Sprintf("%+v", err)
assert.Contains(t, fullErrStr, "/errors_test.go:", fullErrStr)
})
t.Run("wrap as", func(t *testing.T) { //nolint:paralleltest
e, ok := TryWithErrorValue(func() error {
Must("foo", errorsJoin(io.EOF, &url.Error{
Op: "test op",
URL: "test url",
Err: io.ErrUnexpectedEOF,
}))
return nil
})
assert.False(t, ok)
err, ok := e.(error)
assert.True(t, ok)
errURL, ok := ErrorsAs[*url.Error](err)
assert.True(t, ok)
assert.NotNil(t, errURL)
if errURL != nil {
assert.Equal(t, "test url", errURL.URL)
assert.Equal(t, "test op", errURL.Op)
assert.ErrorIs(t, err, io.EOF)
assert.ErrorIs(t, err, io.ErrUnexpectedEOF)
}
})
}
func TestTry(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.False(Try(func() error {
panic("error")
}))
is.True(Try(func() error {
return nil
}))
is.False(Try(func() error {
return errors.New("fail")
}))
}
func TestTryX(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(Try1(func() error {
return nil
}))
is.True(Try2(func() (string, error) {
return "", nil
}))
is.True(Try3(func() (string, string, error) {
return "", "", nil
}))
is.True(Try4(func() (string, string, string, error) {
return "", "", "", nil
}))
is.True(Try5(func() (string, string, string, string, error) {
return "", "", "", "", nil
}))
is.True(Try6(func() (string, string, string, string, string, error) {
return "", "", "", "", "", nil
}))
is.False(Try1(func() error {
panic("error")
}))
is.False(Try2(func() (string, error) {
panic("error")
}))
is.False(Try3(func() (string, string, error) {
panic("error")
}))
is.False(Try4(func() (string, string, string, error) {
panic("error")
}))
is.False(Try5(func() (string, string, string, string, error) {
panic("error")
}))
is.False(Try6(func() (string, string, string, string, string, error) {
panic("error")
}))
is.False(Try1(func() error {
return errors.New("foo")
}))
is.False(Try2(func() (string, error) {
return "", errors.New("foo")
}))
is.False(Try3(func() (string, string, error) {
return "", "", errors.New("foo")
}))
is.False(Try4(func() (string, string, string, error) {
return "", "", "", errors.New("foo")
}))
is.False(Try5(func() (string, string, string, string, error) {
return "", "", "", "", errors.New("foo")
}))
is.False(Try6(func() (string, string, string, string, string, error) {
return "", "", "", "", "", errors.New("foo")
}))
}
func TestTryOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
a1, ok1 := TryOr(func() (int, error) { panic("error") }, 42)
a2, ok2 := TryOr(func() (int, error) { return 21, assert.AnError }, 42)
a3, ok3 := TryOr(func() (int, error) { return 21, nil }, 42)
is.Equal(42, a1)
is.False(ok1)
is.Equal(42, a2)
is.False(ok2)
is.Equal(21, a3)
is.True(ok3)
}
func TestTryOrX(t *testing.T) {
t.Parallel()
is := assert.New(t)
{
a1, ok1 := TryOr1(func() (int, error) { panic("error") }, 42)
a2, ok2 := TryOr1(func() (int, error) { return 21, assert.AnError }, 42)
a3, ok3 := TryOr1(func() (int, error) { return 21, nil }, 42)
is.Equal(42, a1)
is.False(ok1)
is.Equal(42, a2)
is.False(ok2)
is.Equal(21, a3)
is.True(ok3)
}
{
a1, b1, ok1 := TryOr2(func() (int, string, error) { panic("error") }, 42, "hello")
a2, b2, ok2 := TryOr2(func() (int, string, error) { return 21, "world", assert.AnError }, 42, "hello")
a3, b3, ok3 := TryOr2(func() (int, string, error) { return 21, "world", nil }, 42, "hello")
is.Equal(42, a1)
is.Equal("hello", b1)
is.False(ok1)
is.Equal(42, a2)
is.Equal("hello", b2)
is.False(ok2)
is.Equal(21, a3)
is.Equal("world", b3)
is.True(ok3)
}
{
a1, b1, c1, ok1 := TryOr3(func() (int, string, bool, error) { panic("error") }, 42, "hello", false)
a2, b2, c2, ok2 := TryOr3(func() (int, string, bool, error) { return 21, "world", true, assert.AnError }, 42, "hello", false)
a3, b3, c3, ok3 := TryOr3(func() (int, string, bool, error) { return 21, "world", true, nil }, 42, "hello", false)
is.Equal(42, a1)
is.Equal("hello", b1)
is.False(c1)
is.False(ok1)
is.Equal(42, a2)
is.Equal("hello", b2)
is.False(c2)
is.False(ok2)
is.Equal(21, a3)
is.Equal("world", b3)
is.True(c3)
is.True(ok3)
}
{
a1, b1, c1, d1, ok1 := TryOr4(func() (int, string, bool, int, error) { panic("error") }, 42, "hello", false, 42)
a2, b2, c2, d2, ok2 := TryOr4(func() (int, string, bool, int, error) { return 21, "world", true, 21, assert.AnError }, 42, "hello", false, 42)
a3, b3, c3, d3, ok3 := TryOr4(func() (int, string, bool, int, error) { return 21, "world", true, 21, nil }, 42, "hello", false, 42)
is.Equal(42, a1)
is.Equal("hello", b1)
is.False(c1)
is.Equal(42, d1)
is.False(ok1)
is.Equal(42, a2)
is.Equal("hello", b2)
is.False(c2)
is.Equal(42, d2)
is.False(ok2)
is.Equal(21, a3)
is.Equal("world", b3)
is.True(c3)
is.Equal(21, d3)
is.True(ok3)
}
{
a1, b1, c1, d1, e1, ok1 := TryOr5(func() (int, string, bool, int, int, error) { panic("error") }, 42, "hello", false, 42, 42)
a2, b2, c2, d2, e2, ok2 := TryOr5(func() (int, string, bool, int, int, error) { return 21, "world", true, 21, 21, assert.AnError }, 42, "hello", false, 42, 42)
a3, b3, c3, d3, e3, ok3 := TryOr5(func() (int, string, bool, int, int, error) { return 21, "world", true, 21, 21, nil }, 42, "hello", false, 42, 42)
is.Equal(42, a1)
is.Equal("hello", b1)
is.False(c1)
is.Equal(42, d1)
is.Equal(42, e1)
is.False(ok1)
is.Equal(42, a2)
is.Equal("hello", b2)
is.False(c2)
is.Equal(42, d2)
is.Equal(42, e2)
is.False(ok2)
is.Equal(21, a3)
is.Equal("world", b3)
is.True(c3)
is.Equal(21, d3)
is.Equal(21, e3)
is.True(ok3)
}
{
a1, b1, c1, d1, e1, f1, ok1 := TryOr6(func() (int, string, bool, int, int, int, error) { panic("error") }, 42, "hello", false, 42, 42, 42)
a2, b2, c2, d2, e2, f2, ok2 := TryOr6(func() (int, string, bool, int, int, int, error) { return 21, "world", true, 21, 21, 21, assert.AnError }, 42, "hello", false, 42, 42, 42)
a3, b3, c3, d3, e3, f3, ok3 := TryOr6(func() (int, string, bool, int, int, int, error) { return 21, "world", true, 21, 21, 21, nil }, 42, "hello", false, 42, 42, 42)
is.Equal(42, a1)
is.Equal("hello", b1)
is.False(c1)
is.Equal(42, d1)
is.Equal(42, e1)
is.Equal(42, f1)
is.False(ok1)
is.Equal(42, a2)
is.Equal("hello", b2)
is.False(c2)
is.Equal(42, d2)
is.Equal(42, e2)
is.Equal(42, f2)
is.False(ok2)
is.Equal(21, a3)
is.Equal("world", b3)
is.True(c3)
is.Equal(21, d3)
is.Equal(21, e3)
is.Equal(21, f3)
is.True(ok3)
}
}
func TestTryWithErrorValue(t *testing.T) {
t.Parallel()
is := assert.New(t)
err, ok := TryWithErrorValue(func() error {
// getting error in case of panic, using recover function
panic("error")
})
is.False(ok)
is.Equal("error", err)
err, ok = TryWithErrorValue(func() error {
return errors.New("foo")
})
is.False(ok)
e, isError := err.(error)
is.True(isError)
is.EqualError(e, "foo")
err, ok = TryWithErrorValue(func() error {
return nil
})
is.True(ok)
is.Nil(err)
}
func TestTryCatch(t *testing.T) {
t.Parallel()
is := assert.New(t)
caught := false
TryCatch(func() error {
panic("error")
}, func() {
// error was caught
caught = true
})
is.True(caught)
caught = false
TryCatch(func() error {
return nil
}, func() {
// no error to be caught
caught = true
})
is.False(caught)
}
func TestTryCatchWithErrorValue(t *testing.T) {
t.Parallel()
is := assert.New(t)
caught := false
TryCatchWithErrorValue(func() error {
panic("error")
}, func(val any) {
// error was caught
caught = val == "error"
})
is.True(caught)
caught = false
TryCatchWithErrorValue(func() error {
return nil
}, func(val any) {
// no error to be caught
caught = true
})
is.False(caught)
}
type internalError struct {
foobar string
}
func (e *internalError) Error() string {
return "internal error"
}
func TestErrorsAs(t *testing.T) {
t.Parallel()
is := assert.New(t)
err, ok := ErrorsAs[*internalError](errors.New("hello world"))
is.False(ok)
is.Nil(err)
err, ok = ErrorsAs[*internalError](&internalError{foobar: "foobar"})
is.True(ok)
is.Equal(&internalError{foobar: "foobar"}, err)
err, ok = ErrorsAs[*internalError](nil)
is.False(ok)
is.Nil(err)
}
func TestAssert(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
is.NotPanics(func() {
Assert(true)
})
is.NotPanics(func() {
Assert(true, "user defined message")
})
is.PanicsWithValue("assertion failed", func() {
Assert(false)
})
is.PanicsWithValue("assertion failed: user defined message", func() {
Assert(false, "user defined message")
})
// checks that the examples in `README.md` compile
{
age := 20
is.NotPanics(func() {
Assert(age >= 15)
})
is.NotPanics(func() {
Assert(age >= 15, "user age must be >= 15")
})
}
}
func TestAssertf(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
is.NotPanics(func() {
Assertf(true, "user defined message")
})
is.NotPanics(func() {
Assertf(true, "user defined message %d %d", 1, 2)
})
is.PanicsWithValue("assertion failed: user defined message", func() {
Assertf(false, "user defined message")
})
is.PanicsWithValue("assertion failed: user defined message 1 2", func() {
Assertf(false, "user defined message %d %d", 1, 2)
})
// checks that the example in `README.md` compiles
{
age := 7
is.PanicsWithValue("assertion failed: user age must be >= 15, got 7", func() {
Assertf(age >= 15, "user age must be >= 15, got %d", age)
})
}
}
func TestAssertfWithCustom(t *testing.T) { //nolint:paralleltest
oldAssertf := Assertf
Assertf = func(condition bool, format string, args ...any) {
if !condition {
panic(fmt.Errorf("%s: %s", "customErr", fmt.Sprintf(format, args...)))
}
}
defer func() {
Assertf = oldAssertf
}()
e, ok := TryWithErrorValue(func() error {
Assertf(false, "user defined message")
return nil
})
assert.False(t, ok)
assert.NotNil(t, e)
err, ok := e.(error)
assert.True(t, ok)
assert.Equal(t, "customErr: user defined message", err.Error())
}
================================================
FILE: exp/simd/BENCHMARK.md
================================================
# Benchmark
## Summary
Benchmarks show that running SIMD operations on small datasets is slower:
```txt
BenchmarkSumInt8/small/Fallback-lo-2 248740710 5.218 ns/op
BenchmarkSumInt8/small/AVX-x16-2 126181464 9.485 ns/op
BenchmarkSumInt8/small/AVX2-x32-2 73059427 14.44 ns/op
BenchmarkSumInt8/small/AVX512-x64-2 49913169 24.41 ns/op
```
But SIMD is much faster on large datasets:
```txt
BenchmarkSumInt8/xlarge/Fallback-lo-2 273898 4383 ns/op
BenchmarkSumInt8/xlarge/AVX-x16-2 6928408 173.1 ns/op
BenchmarkSumInt8/xlarge/AVX2-x32-2 12639586 94.09 ns/op
BenchmarkSumInt8/xlarge/AVX512-x64-2 13509693 89.67 ns/op
```
## Run
```bash
export GOEXPERIMENT=simd
cd exp/simd/
go test -bench ./... -run=^Benchmark -benchmem -bench
```
```bash
# get instruction set
cat /proc/cpuinfo
```
## Result
```
archsimd.X86: AVX=true AVX2=true AVX512=true
goos: linux
goarch: amd64
pkg: github.com/samber/lo/exp/simd
cpu: AMD EPYC 9454P 48-Core Processor
...
PASS
ok github.com/samber/lo/exp/simd 596.213s
```
| Benchmark | Iterations | Time/op | Bytes/op | Allocs/op |
| ---------------------------------------------- | ---------- | ----------- | -------- | ----------- |
| BenchmarkContainsInt8/tiny/AVX512-x16-2 | 312359204 | 3.625 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/tiny/AVX512-x32-2 | 277194441 | 4.531 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/tiny/AVX512-x64-2 | 336853209 | 3.401 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/small/AVX512-x16-2 | 449132103 | 2.670 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/small/AVX512-x32-2 | 148648339 | 8.332 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/small/AVX512-x64-2 | 143124861 | 7.982 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/medium/AVX512-x16-2 | 276816714 | 4.302 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/medium/AVX512-x32-2 | 345774957 | 3.529 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/medium/AVX512-x64-2 | 449868722 | 2.669 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/large/AVX512-x16-2 | 100000000 | 10.68 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/large/AVX512-x32-2 | 172934200 | 6.941 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/large/AVX512-x64-2 | 280992625 | 4.384 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/xlarge/AVX512-x16-2 | 187189599 | 6.203 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/xlarge/AVX512-x32-2 | 274289563 | 4.042 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/xlarge/AVX512-x64-2 | 375048555 | 2.953 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/massive/AVX512-x16-2 | 86434948 | 14.02 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/massive/AVX512-x32-2 | 153742346 | 8.012 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8/massive/AVX512-x64-2 | 259404483 | 5.214 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/tiny/AVX512-x8-2 | 270309470 | 4.315 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/tiny/AVX512-x16-2 | 264874646 | 4.281 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/tiny/AVX512-x32-2 | 328810479 | 3.593 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/small/AVX512-x8-2 | 374742561 | 3.206 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/small/AVX512-x16-2 | 449838870 | 2.678 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/small/AVX512-x32-2 | 143845734 | 8.484 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/medium/AVX512-x8-2 | 185415590 | 6.448 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/medium/AVX512-x16-2 | 273780868 | 4.268 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/medium/AVX512-x32-2 | 350067484 | 3.431 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/large/AVX512-x8-2 | 61109778 | 19.66 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/large/AVX512-x16-2 | 100000000 | 10.74 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/large/AVX512-x32-2 | 182886646 | 6.575 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/xlarge/AVX512-x8-2 | 15220682 | 71.53 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/xlarge/AVX512-x16-2 | 31876572 | 37.57 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/xlarge/AVX512-x32-2 | 61992217 | 19.55 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/massive/AVX512-x8-2 | 4372000 | 262.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/massive/AVX512-x16-2 | 9019658 | 131.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt16/massive/AVX512-x32-2 | 16568430 | 74.25 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/tiny/AVX512-x4-2 | 499209442 | 2.406 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/tiny/AVX512-x8-2 | 350479609 | 3.433 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/tiny/AVX512-x16-2 | 280918554 | 4.309 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/small/AVX512-x4-2 | 299561596 | 4.028 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/small/AVX512-x8-2 | 374064310 | 3.205 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/small/AVX512-x16-2 | 499219765 | 2.418 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/medium/AVX512-x4-2 | 100000000 | 10.42 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/medium/AVX512-x8-2 | 187391635 | 6.403 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/medium/AVX512-x16-2 | 307955800 | 3.875 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/large/AVX512-x4-2 | 33256420 | 36.05 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/large/AVX512-x8-2 | 62421526 | 19.23 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/large/AVX512-x16-2 | 100000000 | 10.36 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/xlarge/AVX512-x4-2 | 8328856 | 144.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/xlarge/AVX512-x8-2 | 17039037 | 71.14 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/xlarge/AVX512-x16-2 | 28740241 | 41.77 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/massive/AVX512-x4-2 | 3525885 | 332.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/massive/AVX512-x8-2 | 7318027 | 164.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt32/massive/AVX512-x16-2 | 12181366 | 99.08 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/tiny/AVX512-x2-2 | 409014308 | 2.934 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/tiny/AVX512-x4-2 | 449210791 | 2.667 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/tiny/AVX512-x8-2 | 280998146 | 4.293 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/small/AVX512-x2-2 | 195631429 | 6.172 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/small/AVX512-x4-2 | 281272394 | 4.308 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/small/AVX512-x8-2 | 408933924 | 3.044 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/medium/AVX512-x2-2 | 63006909 | 18.94 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/medium/AVX512-x4-2 | 100000000 | 10.67 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/medium/AVX512-x8-2 | 197411126 | 6.016 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/large/AVX512-x2-2 | 17098578 | 70.57 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/large/AVX512-x4-2 | 32558013 | 37.07 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/large/AVX512-x8-2 | 57629485 | 20.94 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/xlarge/AVX512-x2-2 | 4286155 | 281.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/xlarge/AVX512-x4-2 | 8344772 | 143.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/xlarge/AVX512-x8-2 | 14428276 | 83.14 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/massive/AVX512-x2-2 | 1000000 | 1012 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/massive/AVX512-x4-2 | 2350525 | 510.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64/massive/AVX512-x8-2 | 3773523 | 318.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/tiny/AVX512-x16-2 | 338880315 | 3.332 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/tiny/AVX512-x32-2 | 320784217 | 3.559 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/tiny/AVX512-x64-2 | 341599854 | 3.331 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/small/AVX512-x16-2 | 449579424 | 2.670 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/small/AVX512-x32-2 | 140368142 | 8.648 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/small/AVX512-x64-2 | 146828888 | 8.182 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/medium/AVX512-x16-2 | 374443974 | 3.472 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/medium/AVX512-x32-2 | 449271607 | 2.672 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/medium/AVX512-x64-2 | 598525731 | 2.018 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/large/AVX512-x16-2 | 254828565 | 4.956 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/large/AVX512-x32-2 | 407777484 | 2.938 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/large/AVX512-x64-2 | 443472316 | 2.666 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/xlarge/AVX512-x16-2 | 162196827 | 7.867 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/xlarge/AVX512-x32-2 | 268324950 | 4.518 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/xlarge/AVX512-x64-2 | 400437789 | 2.952 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/massive/AVX512-x16-2 | 214548872 | 5.640 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/massive/AVX512-x32-2 | 348431553 | 3.391 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint8/massive/AVX512-x64-2 | 459781908 | 2.455 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/tiny/AVX512-x8-2 | 276271912 | 4.297 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/tiny/AVX512-x16-2 | 281145528 | 4.270 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/tiny/AVX512-x32-2 | 315343911 | 3.667 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/small/AVX512-x8-2 | 374632351 | 3.204 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/small/AVX512-x16-2 | 449355727 | 2.670 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/small/AVX512-x32-2 | 138088146 | 8.395 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/medium/AVX512-x8-2 | 187276191 | 6.582 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/medium/AVX512-x16-2 | 281107980 | 4.306 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/medium/AVX512-x32-2 | 358850328 | 3.516 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/large/AVX512-x8-2 | 59025931 | 19.98 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/large/AVX512-x16-2 | 100000000 | 10.68 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/large/AVX512-x32-2 | 179631354 | 6.569 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/xlarge/AVX512-x8-2 | 16576267 | 71.63 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/xlarge/AVX512-x16-2 | 32578981 | 36.96 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/xlarge/AVX512-x32-2 | 61464870 | 19.44 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/massive/AVX512-x8-2 | 2153736 | 557.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/massive/AVX512-x16-2 | 4225728 | 281.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint16/massive/AVX512-x32-2 | 7829936 | 145.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/tiny/AVX512-x4-2 | 499390296 | 2.403 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/tiny/AVX512-x8-2 | 362964080 | 3.342 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/tiny/AVX512-x16-2 | 281063364 | 4.268 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/small/AVX512-x4-2 | 293867554 | 4.004 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/small/AVX512-x8-2 | 374510434 | 3.203 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/small/AVX512-x16-2 | 499714206 | 2.402 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/medium/AVX512-x4-2 | 100000000 | 10.42 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/medium/AVX512-x8-2 | 187258657 | 6.405 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/medium/AVX512-x16-2 | 312999210 | 3.881 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/large/AVX512-x4-2 | 33298366 | 36.02 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/large/AVX512-x8-2 | 62409421 | 19.23 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/large/AVX512-x16-2 | 100000000 | 10.10 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/xlarge/AVX512-x4-2 | 7948898 | 143.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/xlarge/AVX512-x8-2 | 17021738 | 70.49 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/xlarge/AVX512-x16-2 | 28742320 | 41.77 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/massive/AVX512-x4-2 | 1595774 | 751.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/massive/AVX512-x8-2 | 3094242 | 381.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint32/massive/AVX512-x16-2 | 5080051 | 238.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/tiny/AVX512-x2-2 | 374760351 | 3.203 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/tiny/AVX512-x4-2 | 498763054 | 2.419 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/tiny/AVX512-x8-2 | 319635274 | 3.582 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/small/AVX512-x2-2 | 187032452 | 6.447 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/small/AVX512-x4-2 | 299546244 | 4.009 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/small/AVX512-x8-2 | 373937659 | 3.207 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/medium/AVX512-x2-2 | 62413118 | 19.23 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/medium/AVX512-x4-2 | 113978791 | 10.42 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/medium/AVX512-x8-2 | 186965330 | 6.484 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/large/AVX512-x2-2 | 17005768 | 70.57 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/large/AVX512-x4-2 | 33286495 | 36.69 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/large/AVX512-x8-2 | 61486065 | 19.93 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/xlarge/AVX512-x2-2 | 4154370 | 280.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/xlarge/AVX512-x4-2 | 8371358 | 148.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/xlarge/AVX512-x8-2 | 14193795 | 72.36 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/massive/AVX512-x2-2 | 1773937 | 676.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/massive/AVX512-x4-2 | 3500168 | 343.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsUint64/massive/AVX512-x8-2 | 7097266 | 249.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/tiny/AVX512-x4-2 | 410522160 | 2.675 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/tiny/AVX512-x8-2 | 308565882 | 3.814 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/tiny/AVX512-x16-2 | 315331897 | 3.755 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/small/AVX512-x4-2 | 278219434 | 4.642 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/small/AVX512-x8-2 | 362945481 | 3.287 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/small/AVX512-x16-2 | 408523153 | 2.941 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/medium/AVX512-x4-2 | 100000000 | 10.77 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/medium/AVX512-x8-2 | 186186376 | 6.409 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/medium/AVX512-x16-2 | 264255108 | 4.619 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/large/AVX512-x4-2 | 33028701 | 36.27 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/large/AVX512-x8-2 | 62465360 | 19.53 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/large/AVX512-x16-2 | 108213310 | 10.95 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/xlarge/AVX512-x4-2 | 8359381 | 143.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/xlarge/AVX512-x8-2 | 17042701 | 70.46 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/xlarge/AVX512-x16-2 | 31806921 | 37.13 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/massive/AVX512-x4-2 | 1000000 | 1100 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/massive/AVX512-x8-2 | 2164672 | 554.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat32/massive/AVX512-x16-2 | 4201453 | 293.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/tiny/AVX512-x2-2 | 362183925 | 3.223 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/tiny/AVX512-x4-2 | 449021466 | 2.687 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/tiny/AVX512-x8-2 | 320176149 | 3.820 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/small/AVX512-x2-2 | 187139116 | 6.415 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/small/AVX512-x4-2 | 280722585 | 4.300 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/small/AVX512-x8-2 | 335670502 | 3.472 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/medium/AVX512-x2-2 | 62343927 | 19.23 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/medium/AVX512-x4-2 | 112332902 | 10.69 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/medium/AVX512-x8-2 | 179610780 | 6.741 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/large/AVX512-x2-2 | 16996959 | 70.51 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/large/AVX512-x4-2 | 33017950 | 36.29 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/large/AVX512-x8-2 | 60322328 | 19.73 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/xlarge/AVX512-x2-2 | 4141281 | 282.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/xlarge/AVX512-x4-2 | 7856590 | 145.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/xlarge/AVX512-x8-2 | 16623739 | 72.06 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/massive/AVX512-x2-2 | 541202 | 2195 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/massive/AVX512-x4-2 | 1000000 | 1158 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsFloat64/massive/AVX512-x8-2 | 2115301 | 560.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsWorstCase/AVX512-x4-2 | 7651734 | 145.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsWorstCase/AVX512-x8-2 | 14921599 | 70.49 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsWorstCase/AVX512-x16-2 | 28708478 | 41.38 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsBestCase/AVX512-x4-2 | 534237578 | 2.136 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsBestCase/AVX512-x8-2 | 561252645 | 2.159 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsBestCase/AVX512-x16-2 | 560396454 | 2.137 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/tiny/AVX512-x4-2 | 499649139 | 2.401 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/tiny/AVX512-x8-2 | 329743240 | 3.421 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/tiny/AVX512-x16-2 | 280516392 | 4.276 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/small/AVX512-x4-2 | 299373171 | 4.006 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/small/AVX512-x8-2 | 374407988 | 3.267 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/small/AVX512-x16-2 | 486948346 | 2.424 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/medium/AVX512-x4-2 | 100000000 | 10.41 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/medium/AVX512-x8-2 | 182899621 | 6.412 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/medium/AVX512-x16-2 | 311969776 | 3.829 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/large/AVX512-x4-2 | 33309816 | 36.04 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/large/AVX512-x8-2 | 59912676 | 19.74 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/large/AVX512-x16-2 | 100000000 | 10.65 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/xlarge/AVX512-x4-2 | 8346818 | 143.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/xlarge/AVX512-x8-2 | 16980399 | 70.54 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/xlarge/AVX512-x16-2 | 28676455 | 42.94 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/massive/AVX512-x4-2 | 1000000 | 1151 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/massive/AVX512-x8-2 | 2161594 | 555.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsNegative/massive/AVX512-x16-2 | 3549094 | 350.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8ByWidth/AVX512-x16-2 | 331533141 | 3.222 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8ByWidth/AVX512-x32-2 | 408741681 | 3.193 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt8ByWidth/AVX512-x64-2 | 365382873 | 3.241 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64SteadyState/AVX512-x2-2 | 5722603 | 211.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64SteadyState/AVX512-x4-2 | 11711869 | 103.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkContainsInt64SteadyState/AVX512-x8-2 | 19671033 | 61.36 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/small/Fallback-lo-2 | 248740710 | 5.218 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/small/AVX-x16-2 | 126181464 | 9.485 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/small/AVX2-x32-2 | 73059427 | 14.44 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/small/AVX512-x64-2 | 49913169 | 24.41 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/medium/Fallback-lo-2 | 17278075 | 69.96 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/medium/AVX-x16-2 | 100000000 | 10.58 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/medium/AVX2-x32-2 | 91620999 | 13.10 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/medium/AVX512-x64-2 | 54082130 | 22.20 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/large/Fallback-lo-2 | 2006178 | 576.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/large/AVX-x16-2 | 41836690 | 27.82 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/large/AVX2-x32-2 | 51735399 | 23.04 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/large/AVX512-x64-2 | 40861586 | 29.40 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/xlarge/Fallback-lo-2 | 273898 | 4383 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/xlarge/AVX-x16-2 | 6928408 | 173.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/xlarge/AVX2-x32-2 | 12639586 | 94.09 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8/xlarge/AVX512-x64-2 | 13509693 | 89.67 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/small/Fallback-lo-2 | 249444103 | 5.012 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/small/AVX-x8-2 | 244927230 | 5.052 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/small/AVX2-x16-2 | 122088517 | 9.715 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/small/AVX512-x32-2 | 54098370 | 22.00 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/medium/Fallback-lo-2 | 15782683 | 72.54 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/medium/AVX-x8-2 | 100000000 | 10.51 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/medium/AVX2-x16-2 | 100000000 | 10.75 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/medium/AVX512-x32-2 | 56147455 | 21.38 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/large/Fallback-lo-2 | 2173214 | 598.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/large/AVX-x8-2 | 26319481 | 44.73 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/large/AVX2-x16-2 | 40459519 | 27.91 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/large/AVX512-x32-2 | 39359752 | 31.28 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/xlarge/Fallback-lo-2 | 273932 | 4382 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/xlarge/AVX-x8-2 | 3557265 | 331.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/xlarge/AVX2-x16-2 | 6930166 | 173.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt16/xlarge/AVX512-x32-2 | 12100244 | 97.01 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/small/Fallback-lo-2 | 249566539 | 4.808 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/small/AVX-x4-2 | 259250019 | 4.581 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/small/AVX2-x8-2 | 232858933 | 5.404 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/small/AVX512-x16-2 | 100000000 | 11.18 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/medium/Fallback-lo-2 | 17274441 | 72.28 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/medium/AVX-x4-2 | 58400258 | 20.56 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/medium/AVX2-x8-2 | 110851756 | 10.67 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/medium/AVX512-x16-2 | 106593603 | 11.25 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/large/Fallback-lo-2 | 2171817 | 551.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/large/AVX-x4-2 | 8270253 | 146.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/large/AVX2-x8-2 | 22234518 | 46.06 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/large/AVX512-x16-2 | 37448763 | 32.31 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/xlarge/Fallback-lo-2 | 273699 | 4559 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/xlarge/AVX-x4-2 | 1000000 | 1102 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/xlarge/AVX2-x8-2 | 3586887 | 332.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt32/xlarge/AVX512-x16-2 | 7214437 | 170.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/small/Fallback-lo-2 | 417473124 | 2.886 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/small/AVX-x2-2 | 287521756 | 4.169 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/small/AVX2-x4-2 | 277783513 | 4.311 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/small/AVX512-x8-2 | 172823103 | 6.993 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/medium/Fallback-lo-2 | 34022653 | 35.27 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/medium/AVX-x2-2 | 49241248 | 24.05 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/medium/AVX2-x4-2 | 78897342 | 14.58 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/medium/AVX512-x8-2 | 84361297 | 14.03 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/large/Fallback-lo-2 | 3680988 | 282.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/large/AVX-x2-2 | 6293607 | 170.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/large/AVX2-x4-2 | 12739849 | 91.28 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/large/AVX512-x8-2 | 25508130 | 46.30 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/xlarge/Fallback-lo-2 | 546321 | 2283 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/xlarge/AVX-x2-2 | 877434 | 1289 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/xlarge/AVX2-x4-2 | 1845892 | 650.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64/xlarge/AVX512-x8-2 | 2148355 | 550.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/small/Fallback-lo-2 | 411100770 | 2.951 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/small/AVX-x4-2 | 264013596 | 4.572 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/small/AVX2-x8-2 | 174478266 | 6.911 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/small/AVX512-x16-2 | 61182673 | 19.78 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/medium/Fallback-lo-2 | 33815070 | 35.68 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/medium/AVX-x4-2 | 58238188 | 20.66 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/medium/AVX2-x8-2 | 91316544 | 13.26 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/medium/AVX512-x16-2 | 80046624 | 15.08 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/large/Fallback-lo-2 | 4304168 | 278.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/large/AVX-x4-2 | 6198957 | 184.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/large/AVX2-x8-2 | 12260169 | 86.60 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/large/AVX512-x16-2 | 22147112 | 45.34 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/xlarge/Fallback-lo-2 | 546901 | 2193 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/xlarge/AVX-x4-2 | 736503 | 1622 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/xlarge/AVX2-x8-2 | 1493887 | 810.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat32/xlarge/AVX512-x16-2 | 2959298 | 393.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/small/Fallback-lo-2 | 410778070 | 3.043 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/small/AVX-x2-2 | 254156008 | 4.714 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/small/AVX2-x4-2 | 227604434 | 5.323 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/small/AVX512-x8-2 | 170099748 | 7.115 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/medium/Fallback-lo-2 | 33646345 | 35.78 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/medium/AVX-x2-2 | 32931152 | 34.92 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/medium/AVX2-x4-2 | 75389446 | 16.79 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/medium/AVX512-x8-2 | 89826181 | 13.33 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/large/Fallback-lo-2 | 4293837 | 302.8 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/large/AVX-x2-2 | 3146601 | 381.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/large/AVX2-x4-2 | 6373876 | 184.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/large/AVX512-x8-2 | 13464712 | 88.96 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/xlarge/Fallback-lo-2 | 545764 | 2193 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/xlarge/AVX-x2-2 | 368846 | 3390 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/xlarge/AVX2-x4-2 | 709940 | 1613 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumFloat64/xlarge/AVX512-x8-2 | 1480214 | 808.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/small/Fallback-lo-2 | 411529147 | 3.043 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/small/AVX-x4-2 | 204428401 | 5.872 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/small/AVX2-x8-2 | 187573928 | 6.214 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/small/AVX512-x16-2 | 98346700 | 12.12 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/medium/Fallback-lo-2 | 33481442 | 35.72 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/medium/AVX-x4-2 | 52042394 | 22.12 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/medium/AVX2-x8-2 | 96288541 | 13.44 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/medium/AVX512-x16-2 | 100995780 | 11.90 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/large/Fallback-lo-2 | 4296570 | 289.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/large/AVX-x4-2 | 7743022 | 146.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/large/AVX2-x8-2 | 24355988 | 46.26 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/large/AVX512-x16-2 | 37322655 | 32.89 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/xlarge/Fallback-lo-2 | 547008 | 2193 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/xlarge/AVX-x4-2 | 1087246 | 1112 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/xlarge/AVX2-x8-2 | 1386868 | 761.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanInt32/xlarge/AVX512-x16-2 | 7166142 | 170.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/small/Fallback-lo-2 | 349760005 | 3.449 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/small/AVX-x2-2 | 189674538 | 6.293 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/small/AVX2-x4-2 | 159228600 | 7.531 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/small/AVX512-x8-2 | 110196433 | 10.89 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/medium/Fallback-lo-2 | 32968618 | 36.17 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/medium/AVX-x2-2 | 30863817 | 37.69 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/medium/AVX2-x4-2 | 62428772 | 19.66 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/medium/AVX512-x8-2 | 77140984 | 15.54 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/large/Fallback-lo-2 | 4281057 | 280.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/large/AVX-x2-2 | 3057349 | 389.4 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/large/AVX2-x4-2 | 6509438 | 185.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/large/AVX512-x8-2 | 12668032 | 93.50 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/xlarge/Fallback-lo-2 | 545898 | 2288 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/xlarge/AVX-x2-2 | 367671 | 4048 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/xlarge/AVX2-x4-2 | 739941 | 1621 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMeanFloat64/xlarge/AVX512-x8-2 | 1434867 | 811.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/small/AVX-x4-2 | 312338268 | 3.860 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/small/AVX2-x8-2 | 238034872 | 5.042 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/small/AVX512-x16-2 | 152600943 | 6.661 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/medium/AVX-x4-2 | 61051266 | 19.73 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/medium/AVX2-x8-2 | 91792144 | 13.11 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/medium/AVX512-x16-2 | 99994540 | 12.18 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/large/AVX-x4-2 | 8604774 | 140.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/large/AVX2-x8-2 | 15581037 | 77.56 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/large/AVX512-x16-2 | 30512421 | 40.24 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/xlarge/AVX-x4-2 | 1000000 | 1110 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/xlarge/AVX2-x8-2 | 2158272 | 557.2 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinInt32/xlarge/AVX512-x16-2 | 4253668 | 282.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/small/AVX-x2-2 | 264129410 | 4.544 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/small/AVX2-x4-2 | 299587609 | 4.008 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/small/AVX512-x8-2 | 100000000 | 10.05 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/medium/AVX-x2-2 | 32778514 | 36.93 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/medium/AVX2-x4-2 | 53356347 | 20.30 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/medium/AVX512-x8-2 | 74832976 | 16.21 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/large/AVX-x2-2 | 3863326 | 300.0 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/large/AVX2-x4-2 | 7670576 | 146.5 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/large/AVX512-x8-2 | 14017984 | 78.21 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/xlarge/AVX-x2-2 | 492739 | 2195 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/xlarge/AVX2-x4-2 | 1000000 | 1103 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMinFloat64/xlarge/AVX512-x8-2 | 2145290 | 560.3 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/small/AVX-x4-2 | 306585705 | 3.860 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/small/AVX2-x8-2 | 237347997 | 5.086 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/small/AVX512-x16-2 | 201433966 | 6.130 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/medium/AVX-x4-2 | 60759631 | 19.92 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/medium/AVX2-x8-2 | 90934662 | 13.13 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/medium/AVX512-x16-2 | 98517944 | 12.18 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/large/AVX-x4-2 | 8590542 | 139.6 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/large/AVX2-x8-2 | 15770372 | 77.69 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/large/AVX512-x16-2 | 30197324 | 39.32 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/xlarge/AVX-x4-2 | 1000000 | 1104 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/xlarge/AVX2-x8-2 | 2152038 | 562.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxInt32/xlarge/AVX512-x16-2 | 3917990 | 296.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/small/AVX-x2-2 | 249617162 | 4.816 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/small/AVX2-x4-2 | 207017514 | 5.855 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/small/AVX512-x8-2 | 66520290 | 17.74 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/medium/AVX-x2-2 | 32307492 | 36.92 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/medium/AVX2-x4-2 | 57306838 | 20.77 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/medium/AVX512-x8-2 | 56911946 | 21.12 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/large/AVX-x2-2 | 4259366 | 287.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/large/AVX2-x4-2 | 7905420 | 148.9 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/large/AVX512-x8-2 | 14100686 | 83.43 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/xlarge/AVX-x2-2 | 545378 | 2243 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/xlarge/AVX2-x4-2 | 1000000 | 1113 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkMaxFloat64/xlarge/AVX512-x8-2 | 2119741 | 565.7 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8ByWidth/Fallback-lo-2 | 896775 | 1335 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8ByWidth/AVX-x16-2 | 12557700 | 94.52 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8ByWidth/AVX2-x32-2 | 18702537 | 55.03 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt8ByWidth/AVX512-x64-2 | 21342572 | 56.10 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64SteadyState/Fallback-lo-2 | 513738 | 2195 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64SteadyState/AVX-x2-2 | 928376 | 1296 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64SteadyState/AVX2-x4-2 | 1836968 | 888.1 ns/op | 0 B/op | 0 allocs/op |
| BenchmarkSumInt64SteadyState/AVX512-x8-2 | 2141715 | 551.3 ns/op | 0 B/op | 0 allocs/op |
================================================
FILE: exp/simd/README.md
================================================
# SIMD experiment (Go 1.26+)
This package requires **Go 1.26** with `GOEXPERIMENT=simd` and **amd64**.
See [benchmarks](./BENCHMARK.md).
## CPU compatibility (avoiding SIGILL)
If you see **SIGILL: illegal instruction** when running tests, the CPU or VM does not support the SIMD instructions used by that code.
### Check support on Linux
```bash
# List SIMD-related flags
grep -E 'avx' /proc/cpuinfo
# Or with lscpu
lscpu | grep -i avx
```
**Rough mapping:**
| Tests / code | Required flag(s) | Typical CPUs |
| ----------------- | -------------------------- | ----------------------------------------------------------------------- |
| AVX (128-bit) | `avx` (baseline on amd64) | All amd64 |
| AVX2 (256-bit) | `avx2` | Intel Haswell+, AMD Excavator+ |
| AVX-512 (512-bit) | `avx512f` | Intel Skylake-X+, some Xeons; many AMD/consumer CPUs do **not** have it |
### What the tests do
- **AVX tests** (128-bit) call `requireAVX(t)` and are **skipped** if the CPU does not support AVX.
- **AVX2 tests** call `requireAVX2(t)` and are **skipped** if the CPU does not support AVX2 (no SIGILL).
- **AVX-512 tests** (when enabled) should call `requireAVX512(t)` and skip when AVX-512 is not available.
So on a machine without AVX2, AVX2 tests will show as skipped instead of crashing.
### Run only AVX tests
If your environment does not support AVX2/AVX-512, you can still run the AVX (128-bit) tests:
```bash
GOEXPERIMENT=simd go test -run AVX ./...
```
================================================
FILE: exp/simd/cpu_amd64.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import "simd/archsimd"
const (
simdLanes2 = uint(2)
simdLanes4 = uint(4)
simdLanes8 = uint(8)
simdLanes16 = uint(16)
simdLanes32 = uint(32)
simdLanes64 = uint(64)
)
// simdFeature represents the highest available SIMD instruction set
type simdFeature int
const (
simdFeatureNone simdFeature = iota
simdFeatureAVX
simdFeatureAVX2
simdFeatureAVX512
)
// currentSimdFeature is cached at package init to avoid repeated CPU feature checks
var currentSimdFeature simdFeature
func init() {
if archsimd.X86.AVX512() {
currentSimdFeature = simdFeatureAVX512
} else if archsimd.X86.AVX2() {
currentSimdFeature = simdFeatureAVX2
} else if archsimd.X86.AVX() {
currentSimdFeature = simdFeatureAVX
}
}
================================================
FILE: exp/simd/cpu_amd64_test.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"fmt"
"os"
"testing"
"simd/archsimd"
)
// skipHelper is a small interface implemented by both *testing.T and *testing.B
// to allow unified CPU feature requirement checking for both tests and benchmarks.
type skipHelper interface {
Helper()
Skipf(format string, args ...any)
}
// How to check if your Linux CPU supports SIMD (avoids SIGILL):
//
// grep -E 'avx' /proc/cpuinfo
//
// Or: lscpu | grep -i avx
//
// You need:
// - AVX tests (128-bit): avx in flags (baseline on amd64)
// - AVX2 tests (256-bit): avx2 in flags
// - AVX-512 tests: avx512f (and often avx512bw, avx512vl)
//
// If your CPU lacks AVX or AVX2 or AVX-512, tests that use them will be skipped automatically.
// requireAVX skips the test/benchmark if the CPU does not support AVX (128-bit SIMD).
// Use at the start of each AVX test/benchmark to avoid SIGILL on older or non-x86 systems.
func requireAVX(t skipHelper) {
t.Helper()
if !archsimd.X86.AVX() {
t.Skipf("CPU does not support AVX; skipping. Check compatibility: grep avx /proc/cpuinfo")
}
}
// requireAVX2 skips the test/benchmark if the CPU does not support AVX2 (256-bit SIMD).
// Use at the start of each AVX2 test/benchmark to avoid SIGILL on older or non-x86 systems.
func requireAVX2(t skipHelper) {
t.Helper()
if !archsimd.X86.AVX2() {
t.Skipf("CPU does not support AVX2; skipping. Check compatibility: grep avx2 /proc/cpuinfo")
}
}
// requireAVX512 skips the test/benchmark if the CPU does not support AVX-512 Foundation.
// Use at the start of each AVX-512 test/benchmark to avoid SIGILL on CPUs without AVX-512.
func requireAVX512(t skipHelper) {
t.Helper()
if !archsimd.X86.AVX512() {
t.Skipf("CPU does not support AVX-512; skipping. Check compatibility: grep avx512 /proc/cpuinfo")
}
}
// PrintCPUFeatures prints detected x86 SIMD features (for debugging).
// Run: go test -run PrintCPUFeatures -v
func PrintCPUFeatures(t *testing.T) {
fmt.Fprintf(os.Stdout, "X86 HasAVX=%v HasAVX2=%v HasAVX512=%v\n",
archsimd.X86.AVX(), archsimd.X86.AVX2(), archsimd.X86.AVX512())
}
================================================
FILE: exp/simd/go.mod
================================================
module github.com/samber/lo/exp/simd
go 1.26.0
require github.com/samber/lo v0.0.0
require golang.org/x/text v0.22.0 // indirect
replace github.com/samber/lo => ../../
================================================
FILE: exp/simd/go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: exp/simd/intersect_avx512.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"simd/archsimd"
)
// ContainsInt8x16 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsInt8x16[T ~int8](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes16
targetVec := archsimd.BroadcastInt8x16(int8(target))
base := unsafeSliceInt8(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt8x16Slice(s)
// Compare for equality; Equal returns a mask, ToBits() its bitmask.
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
// Handle remaining elements
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt16x8 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsInt16x8[T ~int16](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes8
targetVec := archsimd.BroadcastInt16x8(int16(target))
base := unsafeSliceInt16(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt16x8Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt32x4 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsInt32x4[T ~int32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes4
targetVec := archsimd.BroadcastInt32x4(int32(target))
base := unsafeSliceInt32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt32x4Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt64x2 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsInt64x2[T ~int64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes2
targetVec := archsimd.BroadcastInt64x2(int64(target))
base := unsafeSliceInt64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt64x2Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint8x16 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsUint8x16[T ~uint8](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes16
targetVec := archsimd.BroadcastUint8x16(uint8(target))
base := unsafeSliceUint8(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint8x16Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint16x8 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsUint16x8[T ~uint16](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes8
targetVec := archsimd.BroadcastUint16x8(uint16(target))
base := unsafeSliceUint16(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint16x8Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint32x4 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsUint32x4[T ~uint32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes4
targetVec := archsimd.BroadcastUint32x4(uint32(target))
base := unsafeSliceUint32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint32x4Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint64x2 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsUint64x2[T ~uint64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes2
targetVec := archsimd.BroadcastUint64x2(uint64(target))
base := unsafeSliceUint64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint64x2Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsFloat32x4 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsFloat32x4[T ~float32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes4
targetVec := archsimd.BroadcastFloat32x4(float32(target))
base := unsafeSliceFloat32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadFloat32x4Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsFloat64x2 checks if collection contains target using AVX SIMD and AVX-512 SIMD
func ContainsFloat64x2[T ~float64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes2
targetVec := archsimd.BroadcastFloat64x2(float64(target))
base := unsafeSliceFloat64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadFloat64x2Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt8x32 checks if collection contains target using AVX2 SIMD
func ContainsInt8x32[T ~int8](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes32
targetVec := archsimd.BroadcastInt8x32(int8(target))
base := unsafeSliceInt8(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt8x32Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt16x16 checks if collection contains target using AVX2 SIMD
func ContainsInt16x16[T ~int16](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes16
targetVec := archsimd.BroadcastInt16x16(int16(target))
base := unsafeSliceInt16(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt16x16Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt32x8 checks if collection contains target using AVX2 SIMD
func ContainsInt32x8[T ~int32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes8
targetVec := archsimd.BroadcastInt32x8(int32(target))
base := unsafeSliceInt32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt32x8Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt64x4 checks if collection contains target using AVX2 SIMD
func ContainsInt64x4[T ~int64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes4
targetVec := archsimd.BroadcastInt64x4(int64(target))
base := unsafeSliceInt64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt64x4Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint8x32 checks if collection contains target using AVX2 SIMD
func ContainsUint8x32[T ~uint8](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes32
targetVec := archsimd.BroadcastUint8x32(uint8(target))
base := unsafeSliceUint8(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint8x32Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint16x16 checks if collection contains target using AVX2 SIMD
func ContainsUint16x16[T ~uint16](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes16
targetVec := archsimd.BroadcastUint16x16(uint16(target))
base := unsafeSliceUint16(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint16x16Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint32x8 checks if collection contains target using AVX2 SIMD
func ContainsUint32x8[T ~uint32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes8
targetVec := archsimd.BroadcastUint32x8(uint32(target))
base := unsafeSliceUint32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint32x8Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint64x4 checks if collection contains target using AVX2 SIMD
func ContainsUint64x4[T ~uint64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes4
targetVec := archsimd.BroadcastUint64x4(uint64(target))
base := unsafeSliceUint64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint64x4Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsFloat32x8 checks if collection contains target using AVX2 SIMD
func ContainsFloat32x8[T ~float32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes8
targetVec := archsimd.BroadcastFloat32x8(float32(target))
base := unsafeSliceFloat32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadFloat32x8Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsFloat64x4 checks if collection contains target using AVX2 SIMD
func ContainsFloat64x4[T ~float64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes4
targetVec := archsimd.BroadcastFloat64x4(float64(target))
base := unsafeSliceFloat64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadFloat64x4Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt8x64 checks if collection contains target using AVX-512 SIMD
func ContainsInt8x64[T ~int8](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes64
targetVec := archsimd.BroadcastInt8x64(int8(target))
base := unsafeSliceInt8(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt8x64Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt16x32 checks if collection contains target using AVX-512 SIMD
func ContainsInt16x32[T ~int16](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes32
targetVec := archsimd.BroadcastInt16x32(int16(target))
base := unsafeSliceInt16(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt16x32Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt32x16 checks if collection contains target using AVX-512 SIMD
func ContainsInt32x16[T ~int32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes16
targetVec := archsimd.BroadcastInt32x16(int32(target))
base := unsafeSliceInt32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt32x16Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsInt64x8 checks if collection contains target using AVX-512 SIMD
func ContainsInt64x8[T ~int64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes8
targetVec := archsimd.BroadcastInt64x8(int64(target))
base := unsafeSliceInt64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadInt64x8Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint8x64 checks if collection contains target using AVX-512 SIMD
func ContainsUint8x64[T ~uint8](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes64
targetVec := archsimd.BroadcastUint8x64(uint8(target))
base := unsafeSliceUint8(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint8x64Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint16x32 checks if collection contains target using AVX-512 SIMD
func ContainsUint16x32[T ~uint16](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes32
targetVec := archsimd.BroadcastUint16x32(uint16(target))
base := unsafeSliceUint16(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint16x32Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint32x16 checks if collection contains target using AVX-512 SIMD
func ContainsUint32x16[T ~uint32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes16
targetVec := archsimd.BroadcastUint32x16(uint32(target))
base := unsafeSliceUint32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint32x16Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsUint64x8 checks if collection contains target using AVX-512 SIMD
func ContainsUint64x8[T ~uint64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes8
targetVec := archsimd.BroadcastUint64x8(uint64(target))
base := unsafeSliceUint64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadUint64x8Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsFloat32x16 checks if collection contains target using AVX-512 SIMD
func ContainsFloat32x16[T ~float32](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes16
targetVec := archsimd.BroadcastFloat32x16(float32(target))
base := unsafeSliceFloat32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadFloat32x16Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
// ContainsFloat64x8 checks if collection contains target using AVX-512 SIMD
func ContainsFloat64x8[T ~float64](collection []T, target T) bool {
length := uint(len(collection))
if length == 0 {
return false
}
const lanes = simdLanes8
targetVec := archsimd.BroadcastFloat64x8(float64(target))
base := unsafeSliceFloat64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
s := base[i : i+lanes]
v := archsimd.LoadFloat64x8Slice(s)
cmp := v.Equal(targetVec)
if cmp.ToBits() != 0 {
return true
}
}
for ; i < length; i++ {
if collection[i] == target {
return true
}
}
return false
}
================================================
FILE: exp/simd/intersect_avx512_test.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"math/rand/v2"
"testing"
"github.com/samber/lo"
)
func TestAVX512ContainsInt8x16(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int8
target int8
expected bool
}{
{"empty", []int8{}, 42, false},
{"single found", []int8{42}, 42, true},
{"single not found", []int8{42}, 10, false},
{"small found at start", []int8{1, 2, 3, 4, 5}, 1, true},
{"small found at end", []int8{1, 2, 3, 4, 5}, 5, true},
{"small found middle", []int8{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int8{1, 2, 3, 4, 5}, 10, false},
{"exactly 16 found", []int8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 8, true},
{"exactly 16 not found", []int8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 100, false},
{"large found", make([]int8, 1000), 0, true},
{"large not found", make([]int8, 100), 127, false},
{"negative found", []int8{-1, -2, -3, 4, 5}, -2, true},
{"negative not found", []int8{-1, -2, -3, 4, 5}, -10, false},
{"min value", []int8{-128, 0, 127}, -128, true},
{"max value", []int8{-128, 0, 127}, 127, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Fill large collections with random data
if len(tc.collection) > 16 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[100] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = int8(rand.IntN(256) - 128)
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt8x16(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt8x16() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt16x8(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int16
target int16
expected bool
}{
{"empty", []int16{}, 42, false},
{"single found", []int16{42}, 42, true},
{"single not found", []int16{42}, 10, false},
{"small found", []int16{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int16{1, 2, 3, 4, 5}, 10, false},
{"exactly 8 found", []int16{1, 2, 3, 4, 5, 6, 7, 8}, 8, true},
{"exactly 8 not found", []int16{1, 2, 3, 4, 5, 6, 7, 8}, 100, false},
{"large found", make([]int16, 1000), 0, true},
{"large not found", make([]int16, 100), 1000, false},
{"negative found", []int16{-1, -2, -3, 4, 5}, -2, true},
{"min value", []int16{-32768, 0, 32767}, -32768, true},
{"max value", []int16{-32768, 0, 32767}, 32767, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 8 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = int16(rand.IntN(65536) - 32768)
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt16x8(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt16x8() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt32x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int32
target int32
expected bool
}{
{"empty", []int32{}, 42, false},
{"single found", []int32{42}, 42, true},
{"single not found", []int32{42}, 10, false},
{"small found", []int32{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int32{1, 2, 3, 4, 5}, 10, false},
{"exactly 4 found", []int32{1, 2, 3, 4}, 4, true},
{"exactly 4 not found", []int32{1, 2, 3, 4}, 100, false},
{"large found", make([]int32, 1000), 0, true},
{"large not found", make([]int32, 100), 100000, false},
{"negative found", []int32{-1, -2, -3, 4, 5}, -2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 4 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Int32()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt32x4(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt32x4() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt64x2(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int64
target int64
expected bool
}{
{"empty", []int64{}, 42, false},
{"single found", []int64{42}, 42, true},
{"single not found", []int64{42}, 10, false},
{"small found", []int64{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int64{1, 2, 3, 4, 5}, 10, false},
{"exactly 2 found", []int64{1, 2}, 2, true},
{"exactly 2 not found", []int64{1, 2}, 100, false},
{"large found", make([]int64, 1000), 0, true},
{"large not found", make([]int64, 100), 1000000, false},
{"negative found", []int64{-1, -2, -3, 4, 5}, -2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 2 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Int64()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt64x2(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt64x2() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint8x16(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint8
target uint8
expected bool
}{
{"empty", []uint8{}, 42, false},
{"single found", []uint8{42}, 42, true},
{"single not found", []uint8{42}, 10, false},
{"small found", []uint8{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint8{1, 2, 3, 4, 5}, 10, false},
{"exactly 16 found", []uint8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 16, true},
{"exactly 16 not found", []uint8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 100, false},
{"large found", make([]uint8, 1000), 0, true},
{"large not found", make([]uint8, 100), 255, false},
{"max value", []uint8{255, 100, 50}, 255, true},
{"zero found", []uint8{0, 1, 2, 3}, 0, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 16 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[100] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = uint8(rand.IntN(256))
if tc.collection[i] == tc.target {
tc.collection[i] = tc.collection[i] + 1
if tc.collection[i] == 0 { // wrapped around
tc.collection[i] = 1
}
}
}
}
}
got := ContainsUint8x16(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint8x16() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint16x8(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint16
target uint16
expected bool
}{
{"empty", []uint16{}, 42, false},
{"single found", []uint16{42}, 42, true},
{"single not found", []uint16{42}, 10, false},
{"small found", []uint16{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint16{1, 2, 3, 4, 5}, 10, false},
{"exactly 8 found", []uint16{1, 2, 3, 4, 5, 6, 7, 8}, 8, true},
{"exactly 8 not found", []uint16{1, 2, 3, 4, 5, 6, 7, 8}, 100, false},
{"large found", make([]uint16, 1000), 0, true},
{"large not found", make([]uint16, 100), 1000, false},
{"max value", []uint16{65535, 1000, 500}, 65535, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 8 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = uint16(rand.IntN(65536))
if tc.collection[i] == tc.target {
tc.collection[i] = uint16((int(tc.collection[i]) + 1) % 65536)
}
}
}
}
got := ContainsUint16x8(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint16x8() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint32x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint32
target uint32
expected bool
}{
{"empty", []uint32{}, 42, false},
{"single found", []uint32{42}, 42, true},
{"single not found", []uint32{42}, 10, false},
{"small found", []uint32{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint32{1, 2, 3, 4, 5}, 10, false},
{"exactly 4 found", []uint32{1, 2, 3, 4}, 4, true},
{"exactly 4 not found", []uint32{1, 2, 3, 4}, 100, false},
{"large found", make([]uint32, 1000), 0, true},
{"large not found", make([]uint32, 100), 100000, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 4 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Uint32()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsUint32x4(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint32x4() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint64x2(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint64
target uint64
expected bool
}{
{"empty", []uint64{}, 42, false},
{"single found", []uint64{42}, 42, true},
{"single not found", []uint64{42}, 10, false},
{"small found", []uint64{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint64{1, 2, 3, 4, 5}, 10, false},
{"exactly 2 found", []uint64{1, 2}, 2, true},
{"exactly 2 not found", []uint64{1, 2}, 100, false},
{"large found", make([]uint64, 1000), 0, true},
{"large not found", make([]uint64, 100), 1000000, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 2 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Uint64()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsUint64x2(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint64x2() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsFloat32x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []float32
target float32
expected bool
}{
{"empty", []float32{}, 42.5, false},
{"single found", []float32{42.5}, 42.5, true},
{"single not found", []float32{42.5}, 10.0, false},
{"small found", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 3.3, true},
{"small not found", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 10.0, false},
{"exactly 4 found", []float32{1.0, 2.0, 3.0, 4.0}, 4.0, true},
{"exactly 4 not found", []float32{1.0, 2.0, 3.0, 4.0}, 100.0, false},
{"large found", make([]float32, 1000), 0, true},
{"large not found", make([]float32, 100), 100000.0, false},
{"negative found", []float32{-1.1, -2.2, 3.3, 4.4}, -2.2, true},
{"zeros", []float32{0, 0, 0, 0}, 0, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 4 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Float32() * 10000
}
}
}
got := ContainsFloat32x4(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsFloat32x4() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsFloat64x2(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []float64
target float64
expected bool
}{
{"empty", []float64{}, 42.5, false},
{"single found", []float64{42.5}, 42.5, true},
{"single not found", []float64{42.5}, 10.0, false},
{"small found", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 3.3, true},
{"small not found", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 10.0, false},
{"exactly 2 found", []float64{1.0, 2.0}, 2.0, true},
{"exactly 2 not found", []float64{1.0, 2.0}, 100.0, false},
{"large found", make([]float64, 1000), 0, true},
{"large not found", make([]float64, 100), 100000.0, false},
{"negative found", []float64{-1.1, -2.2, 3.3, 4.4}, -2.2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 2 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Float64() * 10000
}
}
}
got := ContainsFloat64x2(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsFloat64x2() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt8x32(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int8
target int8
expected bool
}{
{"empty", []int8{}, 42, false},
{"single found", []int8{42}, 42, true},
{"single not found", []int8{42}, 10, false},
{"small found", []int8{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int8{1, 2, 3, 4, 5}, 10, false},
{"exactly 32 found", make([]int8, 32), 0, true},
{"exactly 32 not found", make([]int8, 32), 127, false},
{"large found", make([]int8, 1000), 0, true},
{"large not found", make([]int8, 100), 127, false},
{"negative found", []int8{-1, -2, -3, 4, 5}, -2, true},
{"min value", []int8{-128, 0, 127}, -128, true},
{"max value", []int8{-128, 0, 127}, 127, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 32 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[100] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = int8(rand.IntN(256) - 128)
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt8x32(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt8x32() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt16x16(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int16
target int16
expected bool
}{
{"empty", []int16{}, 42, false},
{"single found", []int16{42}, 42, true},
{"single not found", []int16{42}, 10, false},
{"small found", []int16{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int16{1, 2, 3, 4, 5}, 10, false},
{"exactly 16 found", make([]int16, 16), 0, true},
{"exactly 16 not found", make([]int16, 16), 1000, false},
{"large found", make([]int16, 1000), 0, true},
{"large not found", make([]int16, 100), 1000, false},
{"negative found", []int16{-1, -2, -3, 4, 5}, -2, true},
{"min value", []int16{-32768, 0, 32767}, -32768, true},
{"max value", []int16{-32768, 0, 32767}, 32767, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 16 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = int16(rand.IntN(65536) - 32768)
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt16x16(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt16x16() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt32x8(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int32
target int32
expected bool
}{
{"empty", []int32{}, 42, false},
{"single found", []int32{42}, 42, true},
{"single not found", []int32{42}, 10, false},
{"small found", []int32{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int32{1, 2, 3, 4, 5}, 10, false},
{"exactly 8 found", make([]int32, 8), 0, true},
{"exactly 8 not found", make([]int32, 8), 100, false},
{"large found", make([]int32, 1000), 0, true},
{"large not found", make([]int32, 100), 100000, false},
{"negative found", []int32{-1, -2, -3, 4, 5}, -2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 8 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Int32()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt32x8(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt32x8() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int64
target int64
expected bool
}{
{"empty", []int64{}, 42, false},
{"single found", []int64{42}, 42, true},
{"single not found", []int64{42}, 10, false},
{"small found", []int64{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int64{1, 2, 3, 4, 5}, 10, false},
{"exactly 4 found", make([]int64, 4), 0, true},
{"exactly 4 not found", make([]int64, 4), 100, false},
{"large found", make([]int64, 1000), 0, true},
{"large not found", make([]int64, 100), 1000000, false},
{"negative found", []int64{-1, -2, -3, 4, 5}, -2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 4 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Int64()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt64x4(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt64x4() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint8x32(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint8
target uint8
expected bool
}{
{"empty", []uint8{}, 42, false},
{"single found", []uint8{42}, 42, true},
{"single not found", []uint8{42}, 10, false},
{"small found", []uint8{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint8{1, 2, 3, 4, 5}, 10, false},
{"exactly 32 found", make([]uint8, 32), 0, true},
{"exactly 32 not found", make([]uint8, 32), 255, false},
{"large found", make([]uint8, 1000), 0, true},
{"large not found", make([]uint8, 100), 255, false},
{"max value", []uint8{255, 100, 50}, 255, true},
{"zero found", []uint8{0, 1, 2, 3}, 0, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 32 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[100] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = uint8(rand.IntN(256))
if tc.collection[i] == tc.target {
tc.collection[i] = tc.collection[i] + 1
if tc.collection[i] == 0 { // wrapped around
tc.collection[i] = 1
}
}
}
}
}
got := ContainsUint8x32(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint8x32() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint16x16(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint16
target uint16
expected bool
}{
{"empty", []uint16{}, 42, false},
{"single found", []uint16{42}, 42, true},
{"single not found", []uint16{42}, 10, false},
{"small found", []uint16{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint16{1, 2, 3, 4, 5}, 10, false},
{"exactly 16 found", make([]uint16, 16), 0, true},
{"exactly 16 not found", make([]uint16, 16), 1000, false},
{"large found", make([]uint16, 1000), 0, true},
{"large not found", make([]uint16, 100), 1000, false},
{"max value", []uint16{65535, 1000, 500}, 65535, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 16 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = uint16(rand.IntN(65536))
if tc.collection[i] == tc.target {
tc.collection[i] = tc.collection[i] + 1
if tc.collection[i] == 0 { // wrapped around
tc.collection[i] = 1
}
}
}
}
}
got := ContainsUint16x16(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint16x16() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint32x8(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint32
target uint32
expected bool
}{
{"empty", []uint32{}, 42, false},
{"single found", []uint32{42}, 42, true},
{"single not found", []uint32{42}, 10, false},
{"small found", []uint32{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint32{1, 2, 3, 4, 5}, 10, false},
{"exactly 8 found", make([]uint32, 8), 0, true},
{"exactly 8 not found", make([]uint32, 8), 100, false},
{"large found", make([]uint32, 1000), 0, true},
{"large not found", make([]uint32, 100), 100000, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 8 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Uint32()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsUint32x8(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint32x8() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint64
target uint64
expected bool
}{
{"empty", []uint64{}, 42, false},
{"single found", []uint64{42}, 42, true},
{"single not found", []uint64{42}, 10, false},
{"small found", []uint64{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint64{1, 2, 3, 4, 5}, 10, false},
{"exactly 4 found", make([]uint64, 4), 0, true},
{"exactly 4 not found", make([]uint64, 4), 100, false},
{"large found", make([]uint64, 1000), 0, true},
{"large not found", make([]uint64, 100), 1000000, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 4 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Uint64()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsUint64x4(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint64x4() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsFloat32x8(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []float32
target float32
expected bool
}{
{"empty", []float32{}, 42.5, false},
{"single found", []float32{42.5}, 42.5, true},
{"single not found", []float32{42.5}, 10.0, false},
{"small found", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 3.3, true},
{"small not found", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 10.0, false},
{"exactly 8 found", []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}, 8.0, true},
{"exactly 8 not found", []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}, 100.0, false},
{"large found", make([]float32, 1000), 0, true},
{"large not found", make([]float32, 100), 100000.0, false},
{"negative found", []float32{-1.1, -2.2, 3.3, 4.4}, -2.2, true},
{"zeros", []float32{0, 0, 0, 0}, 0, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 8 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Float32() * 10000
}
}
}
got := ContainsFloat32x8(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsFloat32x8() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsFloat64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []float64
target float64
expected bool
}{
{"empty", []float64{}, 42.5, false},
{"single found", []float64{42.5}, 42.5, true},
{"single not found", []float64{42.5}, 10.0, false},
{"small found", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 3.3, true},
{"small not found", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 10.0, false},
{"exactly 4 found", []float64{1.0, 2.0, 3.0, 4.0}, 4.0, true},
{"exactly 4 not found", []float64{1.0, 2.0, 3.0, 4.0}, 100.0, false},
{"large found", make([]float64, 1000), 0, true},
{"large not found", make([]float64, 100), 100000.0, false},
{"negative found", []float64{-1.1, -2.2, 3.3, 4.4}, -2.2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 4 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Float64() * 10000
}
}
}
got := ContainsFloat64x4(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsFloat64x4() = %v, want %v", got, tc.expected)
}
})
}
}
// Test consistency with lo.Contains
func TestAVX512ContainsConsistency(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
int8 []int8
int16 []int16
int32 []int32
int64 []int64
uint8 []uint8
uint16 []uint16
uint32 []uint32
uint64 []uint64
float32 []float32
float64 []float64
}{
{
name: "mixed",
int8: []int8{-1, 0, 1, 2, 3},
int16: []int16{-1, 0, 1, 2, 3},
int32: []int32{-1, 0, 1, 2, 3},
int64: []int64{-1, 0, 1, 2, 3},
uint8: []uint8{0, 1, 2, 3, 4},
uint16: []uint16{0, 1, 2, 3, 4},
uint32: []uint32{0, 1, 2, 3, 4},
uint64: []uint64{0, 1, 2, 3, 4},
float32: []float32{0.0, 1.1, 2.2, 3.3},
float64: []float64{0.0, 1.1, 2.2, 3.3},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.int8) > 0 {
for _, target := range tc.int8 {
got := ContainsInt8x16(tc.int8, target)
want := lo.Contains(tc.int8, target)
if got != want {
t.Errorf("ContainsInt8x16() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int16) > 0 {
for _, target := range tc.int16 {
got := ContainsInt16x8(tc.int16, target)
want := lo.Contains(tc.int16, target)
if got != want {
t.Errorf("ContainsInt16x8() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int32) > 0 {
for _, target := range tc.int32 {
got := ContainsInt32x4(tc.int32, target)
want := lo.Contains(tc.int32, target)
if got != want {
t.Errorf("ContainsInt32x4() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int64) > 0 {
for _, target := range tc.int64 {
got := ContainsInt64x2(tc.int64, target)
want := lo.Contains(tc.int64, target)
if got != want {
t.Errorf("ContainsInt64x2() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint8) > 0 {
for _, target := range tc.uint8 {
got := ContainsUint8x16(tc.uint8, target)
want := lo.Contains(tc.uint8, target)
if got != want {
t.Errorf("ContainsUint8x16() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint16) > 0 {
for _, target := range tc.uint16 {
got := ContainsUint16x8(tc.uint16, target)
want := lo.Contains(tc.uint16, target)
if got != want {
t.Errorf("ContainsUint16x8() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint32) > 0 {
for _, target := range tc.uint32 {
got := ContainsUint32x4(tc.uint32, target)
want := lo.Contains(tc.uint32, target)
if got != want {
t.Errorf("ContainsUint32x4() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint64) > 0 {
for _, target := range tc.uint64 {
got := ContainsUint64x2(tc.uint64, target)
want := lo.Contains(tc.uint64, target)
if got != want {
t.Errorf("ContainsUint64x2() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.float32) > 0 {
for _, target := range tc.float32 {
got := ContainsFloat32x4(tc.float32, target)
want := lo.Contains(tc.float32, target)
if got != want {
t.Errorf("ContainsFloat32x4() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.float64) > 0 {
for _, target := range tc.float64 {
got := ContainsFloat64x2(tc.float64, target)
want := lo.Contains(tc.float64, target)
if got != want {
t.Errorf("ContainsFloat64x2() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int8) > 0 {
for _, target := range tc.int8 {
got := ContainsInt8x64(tc.int8, target)
want := lo.Contains(tc.int8, target)
if got != want {
t.Errorf("ContainsInt8x64() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int16) > 0 {
for _, target := range tc.int16 {
got := ContainsInt16x32(tc.int16, target)
want := lo.Contains(tc.int16, target)
if got != want {
t.Errorf("ContainsInt16x32() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int32) > 0 {
for _, target := range tc.int32 {
got := ContainsInt32x16(tc.int32, target)
want := lo.Contains(tc.int32, target)
if got != want {
t.Errorf("ContainsInt32x16() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int64) > 0 {
for _, target := range tc.int64 {
got := ContainsInt64x8(tc.int64, target)
want := lo.Contains(tc.int64, target)
if got != want {
t.Errorf("ContainsInt64x8() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint8) > 0 {
for _, target := range tc.uint8 {
got := ContainsUint8x64(tc.uint8, target)
want := lo.Contains(tc.uint8, target)
if got != want {
t.Errorf("ContainsUint8x64() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint16) > 0 {
for _, target := range tc.uint16 {
got := ContainsUint16x32(tc.uint16, target)
want := lo.Contains(tc.uint16, target)
if got != want {
t.Errorf("ContainsUint16x32() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint32) > 0 {
for _, target := range tc.uint32 {
got := ContainsUint32x16(tc.uint32, target)
want := lo.Contains(tc.uint32, target)
if got != want {
t.Errorf("ContainsUint32x16() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint64) > 0 {
for _, target := range tc.uint64 {
got := ContainsUint64x8(tc.uint64, target)
want := lo.Contains(tc.uint64, target)
if got != want {
t.Errorf("ContainsUint64x8() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.float32) > 0 {
for _, target := range tc.float32 {
got := ContainsFloat32x16(tc.float32, target)
want := lo.Contains(tc.float32, target)
if got != want {
t.Errorf("ContainsFloat32x16() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.float64) > 0 {
for _, target := range tc.float64 {
got := ContainsFloat64x8(tc.float64, target)
want := lo.Contains(tc.float64, target)
if got != want {
t.Errorf("ContainsFloat64x8() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int8) > 0 {
for _, target := range tc.int8 {
got := ContainsInt8x32(tc.int8, target)
want := lo.Contains(tc.int8, target)
if got != want {
t.Errorf("ContainsInt8x32() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int16) > 0 {
for _, target := range tc.int16 {
got := ContainsInt16x16(tc.int16, target)
want := lo.Contains(tc.int16, target)
if got != want {
t.Errorf("ContainsInt16x16() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int32) > 0 {
for _, target := range tc.int32 {
got := ContainsInt32x8(tc.int32, target)
want := lo.Contains(tc.int32, target)
if got != want {
t.Errorf("ContainsInt32x8() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.int64) > 0 {
for _, target := range tc.int64 {
got := ContainsInt64x4(tc.int64, target)
want := lo.Contains(tc.int64, target)
if got != want {
t.Errorf("ContainsInt64x4() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint8) > 0 {
for _, target := range tc.uint8 {
got := ContainsUint8x32(tc.uint8, target)
want := lo.Contains(tc.uint8, target)
if got != want {
t.Errorf("ContainsUint8x32() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint16) > 0 {
for _, target := range tc.uint16 {
got := ContainsUint16x16(tc.uint16, target)
want := lo.Contains(tc.uint16, target)
if got != want {
t.Errorf("ContainsUint16x16() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint32) > 0 {
for _, target := range tc.uint32 {
got := ContainsUint32x8(tc.uint32, target)
want := lo.Contains(tc.uint32, target)
if got != want {
t.Errorf("ContainsUint32x8() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.uint64) > 0 {
for _, target := range tc.uint64 {
got := ContainsUint64x4(tc.uint64, target)
want := lo.Contains(tc.uint64, target)
if got != want {
t.Errorf("ContainsUint64x4() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.float32) > 0 {
for _, target := range tc.float32 {
got := ContainsFloat32x8(tc.float32, target)
want := lo.Contains(tc.float32, target)
if got != want {
t.Errorf("ContainsFloat32x8() not consistent with lo.Contains for target %v", target)
}
}
}
if len(tc.float64) > 0 {
for _, target := range tc.float64 {
got := ContainsFloat64x4(tc.float64, target)
want := lo.Contains(tc.float64, target)
if got != want {
t.Errorf("ContainsFloat64x4() not consistent with lo.Contains for target %v", target)
}
}
}
})
}
}
func TestAVX512ContainsInt8x64(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int8
target int8
expected bool
}{
{"empty", []int8{}, 42, false},
{"single found", []int8{42}, 42, true},
{"single not found", []int8{42}, 10, false},
{"small found", []int8{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int8{1, 2, 3, 4, 5}, 10, false},
{"exactly 64 found", make([]int8, 64), 0, true},
{"exactly 64 not found", make([]int8, 64), 127, false},
{"large found", make([]int8, 1000), 0, true},
{"large not found", make([]int8, 100), 127, false},
{"negative found", []int8{-1, -2, -3, 4, 5}, -2, true},
{"min value", []int8{-128, 0, 127}, -128, true},
{"max value", []int8{-128, 0, 127}, 127, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 64 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[100] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = int8(rand.IntN(256) - 128)
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt8x64(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt8x64() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt16x32(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int16
target int16
expected bool
}{
{"empty", []int16{}, 42, false},
{"single found", []int16{42}, 42, true},
{"single not found", []int16{42}, 10, false},
{"small found", []int16{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int16{1, 2, 3, 4, 5}, 10, false},
{"exactly 32 found", make([]int16, 32), 0, true},
{"exactly 32 not found", make([]int16, 32), 1000, false},
{"large found", make([]int16, 1000), 0, true},
{"large not found", make([]int16, 100), 1000, false},
{"negative found", []int16{-1, -2, -3, 4, 5}, -2, true},
{"min value", []int16{-32768, 0, 32767}, -32768, true},
{"max value", []int16{-32768, 0, 32767}, 32767, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 32 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = int16(rand.IntN(65536) - 32768)
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt16x32(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt16x32() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt32x16(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int32
target int32
expected bool
}{
{"empty", []int32{}, 42, false},
{"single found", []int32{42}, 42, true},
{"single not found", []int32{42}, 10, false},
{"small found", []int32{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int32{1, 2, 3, 4, 5}, 10, false},
{"exactly 16 found", make([]int32, 16), 0, true},
{"exactly 16 not found", make([]int32, 16), 100, false},
{"large found", make([]int32, 1000), 0, true},
{"large not found", make([]int32, 100), 100000, false},
{"negative found", []int32{-1, -2, -3, 4, 5}, -2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 16 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Int32()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt32x16(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt32x16() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsInt64x8(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []int64
target int64
expected bool
}{
{"empty", []int64{}, 42, false},
{"single found", []int64{42}, 42, true},
{"single not found", []int64{42}, 10, false},
{"small found", []int64{1, 2, 3, 4, 5}, 3, true},
{"small not found", []int64{1, 2, 3, 4, 5}, 10, false},
{"exactly 8 found", make([]int64, 8), 0, true},
{"exactly 8 not found", make([]int64, 8), 100, false},
{"large found", make([]int64, 1000), 0, true},
{"large not found", make([]int64, 100), 1000000, false},
{"negative found", []int64{-1, -2, -3, 4, 5}, -2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 8 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Int64()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsInt64x8(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsInt64x8() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint8x64(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint8
target uint8
expected bool
}{
{"empty", []uint8{}, 42, false},
{"single found", []uint8{42}, 42, true},
{"single not found", []uint8{42}, 10, false},
{"small found", []uint8{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint8{1, 2, 3, 4, 5}, 10, false},
{"exactly 64 found", make([]uint8, 64), 0, true},
{"exactly 64 not found", make([]uint8, 64), 255, false},
{"large found", make([]uint8, 1000), 0, true},
{"large not found", make([]uint8, 100), 255, false},
{"max value", []uint8{255, 100, 50}, 255, true},
{"zero found", []uint8{0, 1, 2, 3}, 0, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 64 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[100] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = uint8(rand.IntN(256))
if tc.collection[i] == tc.target {
tc.collection[i] = tc.collection[i] + 1
if tc.collection[i] == 0 { // wrapped around
tc.collection[i] = 1
}
}
}
}
}
got := ContainsUint8x64(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint8x64() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint16x32(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint16
target uint16
expected bool
}{
{"empty", []uint16{}, 42, false},
{"single found", []uint16{42}, 42, true},
{"single not found", []uint16{42}, 10, false},
{"small found", []uint16{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint16{1, 2, 3, 4, 5}, 10, false},
{"exactly 32 found", make([]uint16, 32), 0, true},
{"exactly 32 not found", make([]uint16, 32), 1000, false},
{"large found", make([]uint16, 1000), 0, true},
{"large not found", make([]uint16, 100), 1000, false},
{"max value", []uint16{65535, 1000, 500}, 65535, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 32 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = uint16(rand.IntN(65536))
if tc.collection[i] == tc.target {
tc.collection[i] = tc.collection[i] + 1
if tc.collection[i] == 0 { // wrapped around
tc.collection[i] = 1
}
}
}
}
}
got := ContainsUint16x32(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint16x32() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint32x16(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint32
target uint32
expected bool
}{
{"empty", []uint32{}, 42, false},
{"single found", []uint32{42}, 42, true},
{"single not found", []uint32{42}, 10, false},
{"small found", []uint32{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint32{1, 2, 3, 4, 5}, 10, false},
{"exactly 16 found", make([]uint32, 16), 0, true},
{"exactly 16 not found", make([]uint32, 16), 100, false},
{"large found", make([]uint32, 1000), 0, true},
{"large not found", make([]uint32, 100), 100000, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 16 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Uint32()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsUint32x16(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint32x16() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsUint64x8(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []uint64
target uint64
expected bool
}{
{"empty", []uint64{}, 42, false},
{"single found", []uint64{42}, 42, true},
{"single not found", []uint64{42}, 10, false},
{"small found", []uint64{1, 2, 3, 4, 5}, 3, true},
{"small not found", []uint64{1, 2, 3, 4, 5}, 10, false},
{"exactly 8 found", make([]uint64, 8), 0, true},
{"exactly 8 not found", make([]uint64, 8), 100, false},
{"large found", make([]uint64, 1000), 0, true},
{"large not found", make([]uint64, 100), 1000000, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 8 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Uint64()
if tc.collection[i] == tc.target {
tc.collection[i]++
}
}
}
}
got := ContainsUint64x8(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsUint64x8() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsFloat32x16(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []float32
target float32
expected bool
}{
{"empty", []float32{}, 42.5, false},
{"single found", []float32{42.5}, 42.5, true},
{"single not found", []float32{42.5}, 10.0, false},
{"small found", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 3.3, true},
{"small not found", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 10.0, false},
{"exactly 16 found", []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0}, 16.0, true},
{"exactly 16 not found", []float32{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0}, 100.0, false},
{"large found", make([]float32, 1000), 0, true},
{"large not found", make([]float32, 100), 100000.0, false},
{"negative found", []float32{-1.1, -2.2, 3.3, 4.4}, -2.2, true},
{"zeros", []float32{0, 0, 0, 0}, 0, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 16 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Float32() * 10000
}
}
}
got := ContainsFloat32x16(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsFloat32x16() = %v, want %v", got, tc.expected)
}
})
}
}
func TestAVX512ContainsFloat64x8(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
collection []float64
target float64
expected bool
}{
{"empty", []float64{}, 42.5, false},
{"single found", []float64{42.5}, 42.5, true},
{"single not found", []float64{42.5}, 10.0, false},
{"small found", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 3.3, true},
{"small not found", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 10.0, false},
{"exactly 8 found", []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}, 8.0, true},
{"exactly 8 not found", []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}, 100.0, false},
{"large found", make([]float64, 1000), 0, true},
{"large not found", make([]float64, 100), 100000.0, false},
{"negative found", []float64{-1.1, -2.2, 3.3, 4.4}, -2.2, true},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.collection) > 8 && tc.collection[0] == 0 {
if tc.expected {
tc.collection[50] = tc.target
} else {
for i := range tc.collection {
tc.collection[i] = rand.Float64() * 10000
}
}
}
got := ContainsFloat64x8(tc.collection, tc.target)
if got != tc.expected {
t.Errorf("ContainsFloat64x8() = %v, want %v", got, tc.expected)
}
})
}
}
// Test type aliases work correctly
func TestAVX512ContainsTypeAlias(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
input := []myInt8{1, 2, 3, 4, 5}
target := myInt8(3)
got := ContainsInt8x64(input, target)
if !got {
t.Errorf("ContainsInt8x64() with type alias = false, want true")
}
target = myInt8(10)
got = ContainsInt8x64(input, target)
if got {
t.Errorf("ContainsInt8x64() with type alias = true, want false")
}
target = myInt8(3)
got = ContainsInt8x16(input, target)
if !got {
t.Errorf("ContainsInt8x16() with type alias = false, want true")
}
target = myInt8(10)
got = ContainsInt8x16(input, target)
if got {
t.Errorf("ContainsInt8x16() with type alias = true, want false")
}
target = myInt8(3)
got = ContainsInt8x32(input, target)
if !got {
t.Errorf("ContainsInt8x32() with type alias = false, want true")
}
target = myInt8(10)
got = ContainsInt8x32(input, target)
if got {
t.Errorf("ContainsInt8x32() with type alias = true, want false")
}
}
================================================
FILE: exp/simd/intersect_bench_test.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"testing"
)
// Benchmark suite for SIMD Contains operations compared to core lo package fallbacks.
// These benchmarks measure the performance of element lookup operations
// across different SIMD implementations (AVX, AVX2, AVX512) and data sizes.
// Benchmark sizes for Contains operations
var containsBenchmarkSizes = []struct {
name string
size int
}{
{"tiny", 4}, // Smaller than AVX width (16 lanes for int8)
{"small", 16}, // Exactly AVX width for int8
{"medium", 64}, // Multiple of AVX, between AVX and AVX2 for int8
{"large", 256}, // Multiple of AVX2 (32 lanes for int8)
{"xlarge", 1024}, // Multiple of AVX512 (64 lanes for int8)
{"massive", 8192}, // Very large dataset
}
// ============================================================================
// CONTAINS INT8 BENCHMARKS
// ============================================================================
func BenchmarkContainsInt8(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt8(bs.size)
target := int8(42)
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b) // ContainsInt8x16 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt8x16(data, target)
}
})
b.Run("AVX512-x32", func(b *testing.B) {
requireAVX512(b) // ContainsInt8x32 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt8x32(data, target)
}
})
b.Run("AVX512-x64", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt8x64(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS INT16 BENCHMARKS
// ============================================================================
func BenchmarkContainsInt16(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt16(bs.size)
target := int16(42)
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsInt16x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt16x8(data, target)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b) // ContainsInt16x16 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt16x16(data, target)
}
})
b.Run("AVX512-x32", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt16x32(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS INT32 BENCHMARKS
// ============================================================================
func BenchmarkContainsInt32(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt32(bs.size)
target := int32(42)
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x8(data, target)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x16(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS INT64 BENCHMARKS
// ============================================================================
func BenchmarkContainsInt64(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt64(bs.size)
target := int64(42)
b.Run("AVX512-x2", func(b *testing.B) {
requireAVX512(b) // ContainsInt64x2 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt64x2(data, target)
}
})
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsInt64x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt64x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt64x8(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS UINT8 BENCHMARKS
// ============================================================================
func BenchmarkContainsUint8(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateUint8(bs.size)
target := uint8(255)
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b) // ContainsUint8x16 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint8x16(data, target)
}
})
b.Run("AVX512-x32", func(b *testing.B) {
requireAVX512(b) // ContainsUint8x32 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint8x32(data, target)
}
})
b.Run("AVX512-x64", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint8x64(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS UINT16 BENCHMARKS
// ============================================================================
func BenchmarkContainsUint16(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateUint16(bs.size)
target := uint16(42)
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsUint16x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint16x8(data, target)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b) // ContainsUint16x16 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint16x16(data, target)
}
})
b.Run("AVX512-x32", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint16x32(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS UINT32 BENCHMARKS
// ============================================================================
func BenchmarkContainsUint32(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateUint32(bs.size)
target := uint32(42)
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsUint32x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint32x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsUint32x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint32x8(data, target)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint32x16(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS UINT64 BENCHMARKS
// ============================================================================
func BenchmarkContainsUint64(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateUint64(bs.size)
target := uint64(42)
b.Run("AVX512-x2", func(b *testing.B) {
requireAVX512(b) // ContainsUint64x2 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint64x2(data, target)
}
})
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsUint64x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint64x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsUint64x8(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS FLOAT32 BENCHMARKS
// ============================================================================
func BenchmarkContainsFloat32(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateFloat32(bs.size)
target := float32(42.5)
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsFloat32x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsFloat32x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsFloat32x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsFloat32x8(data, target)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsFloat32x16(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS FLOAT64 BENCHMARKS
// ============================================================================
func BenchmarkContainsFloat64(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateFloat64(bs.size)
target := float64(42.5)
b.Run("AVX512-x2", func(b *testing.B) {
requireAVX512(b) // ContainsFloat64x2 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsFloat64x2(data, target)
}
})
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsFloat64x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsFloat64x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsFloat64x8(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS WORST-CASE BENCHMARKS (target at end)
// ============================================================================
// These benchmarks test worst-case performance where target is at the very end
func BenchmarkContainsWorstCase(b *testing.B) {
size := 1024
data := make([]int32, size)
for i := range data {
data[i] = int32(i)
}
target := int32(size - 1) // Target at the very end
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x8(data, target)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x16 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x16(data, target)
}
})
}
// ============================================================================
// CONTAINS BEST-CASE BENCHMARKS (target at beginning)
// ============================================================================
// These benchmarks test best-case performance where target is at the beginning
func BenchmarkContainsBestCase(b *testing.B) {
size := 1024
data := make([]int32, size)
for i := range data {
data[i] = int32(i)
}
target := int32(0) // Target at the very beginning
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x8(data, target)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x16 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x16(data, target)
}
})
}
// ============================================================================
// CONTAINS NEGATIVE-CASE BENCHMARKS (target not present)
// ============================================================================
// These benchmarks test performance when target is not in the collection
func BenchmarkContainsNegative(b *testing.B) {
for _, bs := range containsBenchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt32(bs.size)
target := int32(999999) // Target that's unlikely to be in the data
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsInt32x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x8(data, target)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt32x16(data, target)
}
})
})
}
}
// ============================================================================
// CONTAINS LANE WIDTH COMPARISON BENCHMARK
// ============================================================================
// This benchmark shows how performance scales with SIMD register width
func BenchmarkContainsInt8ByWidth(b *testing.B) {
requireAVX512(b)
size := 4096
data := generateInt8(size)
target := int8(42)
benchmarks := []struct {
name string
fn func() bool
}{
{"AVX512-x16", func() bool { return ContainsInt8x16(data, target) }},
{"AVX512-x32", func() bool { return ContainsInt8x32(data, target) }},
{"AVX512-x64", func() bool { return ContainsInt8x64(data, target) }},
}
for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = bm.fn()
}
})
}
}
// ============================================================================
// STEADY STATE BENCHMARK
// ============================================================================
// This benchmark demonstrates the steady-state performance after warmup
func BenchmarkContainsInt64SteadyState(b *testing.B) {
requireAVX512(b)
size := 8192
data := generateInt64(size)
target := int64(42)
// Warmup phase
for i := 0; i < 1000; i++ {
ContainsInt64x2(data, target)
ContainsInt64x4(data, target)
ContainsInt64x8(data, target)
}
b.ResetTimer() // Reset timer to exclude warmup
b.Run("AVX512-x2", func(b *testing.B) {
requireAVX512(b) // ContainsInt64x2 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt64x2(data, target)
}
})
b.Run("AVX512-x4", func(b *testing.B) {
requireAVX512(b) // ContainsInt64x4 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt64x4(data, target)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b) // ContainsInt64x8 is in intersect_avx512.go which uses AVX-512
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ContainsInt64x8(data, target)
}
})
}
================================================
FILE: exp/simd/math.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"github.com/samber/lo"
)
// SumInt8 sums a slice of int8 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using int8, which can overflow for large collections.
// If the sum exceeds the int8 range (-128 to 127), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumInt8[T ~int8](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumInt8x64(collection)
case simdFeatureAVX2:
return SumInt8x32(collection)
case simdFeatureAVX:
return SumInt8x16(collection)
default:
return lo.Sum(collection)
}
}
// SumInt16 sums a slice of int16 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using int16, which can overflow for large collections.
// If the sum exceeds the int16 range (-32768 to 32767), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumInt16[T ~int16](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumInt16x32(collection)
case simdFeatureAVX2:
return SumInt16x16(collection)
case simdFeatureAVX:
return SumInt16x8(collection)
default:
return lo.Sum(collection)
}
}
// SumInt32 sums a slice of int32 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using int32, which can overflow for very large collections.
// If the sum exceeds the int32 range (-2147483648 to 2147483647), the result will wrap around silently.
// For collections that may overflow, consider using SumInt64 or handle overflow detection externally.
func SumInt32[T ~int32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumInt32x16(collection)
case simdFeatureAVX2:
return SumInt32x8(collection)
case simdFeatureAVX:
return SumInt32x4(collection)
default:
return lo.Sum(collection)
}
}
// SumInt64 sums a slice of int64 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using int64, which can overflow for extremely large collections.
// If the sum exceeds the int64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
func SumInt64[T ~int64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumInt64x8(collection)
case simdFeatureAVX2:
return SumInt64x4(collection)
case simdFeatureAVX:
return SumInt64x2(collection)
default:
return lo.Sum(collection)
}
}
// SumUint8 sums a slice of uint8 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using uint8, which can overflow for large collections.
// If the sum exceeds the uint8 range (0 to 255), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumUint8[T ~uint8](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumUint8x64(collection)
case simdFeatureAVX2:
return SumUint8x32(collection)
case simdFeatureAVX:
return SumUint8x16(collection)
default:
return lo.Sum(collection)
}
}
// SumUint16 sums a slice of uint16 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using uint16, which can overflow for large collections.
// If the sum exceeds the uint16 range (0 to 65535), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumUint16[T ~uint16](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumUint16x32(collection)
case simdFeatureAVX2:
return SumUint16x16(collection)
case simdFeatureAVX:
return SumUint16x8(collection)
default:
return lo.Sum(collection)
}
}
// SumUint32 sums a slice of uint32 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using uint32, which can overflow for very large collections.
// If the sum exceeds the uint32 range (0 to 4294967295), the result will wrap around silently.
// For collections that may overflow, consider using SumUint64 or handle overflow detection externally.
func SumUint32[T ~uint32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumUint32x16(collection)
case simdFeatureAVX2:
return SumUint32x8(collection)
case simdFeatureAVX:
return SumUint32x4(collection)
default:
return lo.Sum(collection)
}
}
// SumUint64 sums a slice of uint64 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using uint64, which can overflow for extremely large collections.
// If the sum exceeds the uint64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
func SumUint64[T ~uint64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumUint64x8(collection)
case simdFeatureAVX2:
return SumUint64x4(collection)
case simdFeatureAVX:
return SumUint64x2(collection)
default:
return lo.Sum(collection)
}
}
// SumFloat32 sums a slice of float32 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using float32. Overflow will result in +/-Inf rather than wrapping.
// For collections requiring high precision or large sums, consider using SumFloat64.
func SumFloat32[T ~float32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumFloat32x16(collection)
case simdFeatureAVX2:
return SumFloat32x8(collection)
case simdFeatureAVX:
return SumFloat32x4(collection)
default:
return lo.Sum(collection)
}
}
// SumFloat64 sums a slice of float64 using the best available SIMD instruction set.
// Overflow: The accumulation is performed using float64. Overflow will result in +/-Inf rather than wrapping.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Float).
func SumFloat64[T ~float64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumFloat64x8(collection)
case simdFeatureAVX2:
return SumFloat64x4(collection)
case simdFeatureAVX:
return SumFloat64x2(collection)
default:
return lo.Sum(collection)
}
}
// MeanInt8 calculates the mean of a slice of int8 using the best available SIMD instruction set.
func MeanInt8[T ~int8](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanInt8x64(collection)
case simdFeatureAVX2:
return MeanInt8x32(collection)
case simdFeatureAVX:
return MeanInt8x16(collection)
default:
return lo.Mean(collection)
}
}
// MeanInt16 calculates the mean of a slice of int16 using the best available SIMD instruction set.
func MeanInt16[T ~int16](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanInt16x32(collection)
case simdFeatureAVX2:
return MeanInt16x16(collection)
case simdFeatureAVX:
return MeanInt16x8(collection)
default:
return lo.Mean(collection)
}
}
// MeanInt32 calculates the mean of a slice of int32 using the best available SIMD instruction set.
func MeanInt32[T ~int32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanInt32x16(collection)
case simdFeatureAVX2:
return MeanInt32x8(collection)
case simdFeatureAVX:
return MeanInt32x4(collection)
default:
return lo.Mean(collection)
}
}
// MeanInt64 calculates the mean of a slice of int64 using the best available SIMD instruction set.
func MeanInt64[T ~int64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanInt64x8(collection)
case simdFeatureAVX2:
return MeanInt64x4(collection)
case simdFeatureAVX:
return MeanInt64x2(collection)
default:
return lo.Mean(collection)
}
}
// MeanUint8 calculates the mean of a slice of uint8 using the best available SIMD instruction set.
func MeanUint8[T ~uint8](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanUint8x64(collection)
case simdFeatureAVX2:
return MeanUint8x32(collection)
case simdFeatureAVX:
return MeanUint8x16(collection)
default:
return lo.Mean(collection)
}
}
// MeanUint16 calculates the mean of a slice of uint16 using the best available SIMD instruction set.
func MeanUint16[T ~uint16](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanUint16x32(collection)
case simdFeatureAVX2:
return MeanUint16x16(collection)
case simdFeatureAVX:
return MeanUint16x8(collection)
default:
return lo.Mean(collection)
}
}
// MeanUint32 calculates the mean of a slice of uint32 using the best available SIMD instruction set.
func MeanUint32[T ~uint32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanUint32x16(collection)
case simdFeatureAVX2:
return MeanUint32x8(collection)
case simdFeatureAVX:
return MeanUint32x4(collection)
default:
return lo.Mean(collection)
}
}
// MeanUint64 calculates the mean of a slice of uint64 using the best available SIMD instruction set.
func MeanUint64[T ~uint64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanUint64x8(collection)
case simdFeatureAVX2:
return MeanUint64x4(collection)
case simdFeatureAVX:
return MeanUint64x2(collection)
default:
return lo.Mean(collection)
}
}
// MeanFloat32 calculates the mean of a slice of float32 using the best available SIMD instruction set.
func MeanFloat32[T ~float32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanFloat32x16(collection)
case simdFeatureAVX2:
return MeanFloat32x8(collection)
case simdFeatureAVX:
return MeanFloat32x4(collection)
default:
return lo.Mean(collection)
}
}
// MeanFloat64 calculates the mean of a slice of float64 using the best available SIMD instruction set.
func MeanFloat64[T ~float64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanFloat64x8(collection)
case simdFeatureAVX2:
return MeanFloat64x4(collection)
case simdFeatureAVX:
return MeanFloat64x2(collection)
default:
return lo.Mean(collection)
}
}
// MinInt8 finds the minimum value in a collection of int8 using the best available SIMD instruction set.
func MinInt8[T ~int8](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinInt8x64(collection)
case simdFeatureAVX2:
return MinInt8x32(collection)
case simdFeatureAVX:
return MinInt8x16(collection)
default:
return lo.Min(collection)
}
}
// MinInt16 finds the minimum value in a collection of int16 using the best available SIMD instruction set.
func MinInt16[T ~int16](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinInt16x32(collection)
case simdFeatureAVX2:
return MinInt16x16(collection)
case simdFeatureAVX:
return MinInt16x8(collection)
default:
return lo.Min(collection)
}
}
// MinInt32 finds the minimum value in a collection of int32 using the best available SIMD instruction set.
func MinInt32[T ~int32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinInt32x16(collection)
case simdFeatureAVX2:
return MinInt32x8(collection)
case simdFeatureAVX:
return MinInt32x4(collection)
default:
return lo.Min(collection)
}
}
// MinInt64 finds the minimum value in a collection of int64 using the best available SIMD instruction set.
func MinInt64[T ~int64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinInt64x8(collection)
case simdFeatureAVX2:
return MinInt64x4(collection)
case simdFeatureAVX:
// MinInt64x2 requires AVX-512 (archsimd Int64x2.Min); use scalar fallback
fallthrough
default:
return lo.Min(collection)
}
}
// MinUint8 finds the minimum value in a collection of uint8 using the best available SIMD instruction set.
func MinUint8[T ~uint8](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinUint8x64(collection)
case simdFeatureAVX2:
return MinUint8x32(collection)
case simdFeatureAVX:
return MinUint8x16(collection)
default:
return lo.Min(collection)
}
}
// MinUint16 finds the minimum value in a collection of uint16 using the best available SIMD instruction set.
func MinUint16[T ~uint16](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinUint16x32(collection)
case simdFeatureAVX2:
return MinUint16x16(collection)
case simdFeatureAVX:
return MinUint16x8(collection)
default:
return lo.Min(collection)
}
}
// MinUint32 finds the minimum value in a collection of uint32 using the best available SIMD instruction set.
func MinUint32[T ~uint32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinUint32x16(collection)
case simdFeatureAVX2:
return MinUint32x8(collection)
case simdFeatureAVX:
return MinUint32x4(collection)
default:
return lo.Min(collection)
}
}
// MinUint64 finds the minimum value in a collection of uint64 using the best available SIMD instruction set.
func MinUint64[T ~uint64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinUint64x8(collection)
case simdFeatureAVX2:
return MinUint64x4(collection)
case simdFeatureAVX:
// MinUint64x2 requires AVX-512; use scalar fallback
fallthrough
default:
return lo.Min(collection)
}
}
// MinFloat32 finds the minimum value in a collection of float32 using the best available SIMD instruction set.
func MinFloat32[T ~float32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinFloat32x16(collection)
case simdFeatureAVX2:
return MinFloat32x8(collection)
case simdFeatureAVX:
return MinFloat32x4(collection)
default:
return lo.Min(collection)
}
}
// MinFloat64 finds the minimum value in a collection of float64 using the best available SIMD instruction set.
func MinFloat64[T ~float64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MinFloat64x8(collection)
case simdFeatureAVX2:
return MinFloat64x4(collection)
case simdFeatureAVX:
return MinFloat64x2(collection)
default:
return lo.Min(collection)
}
}
// MaxInt8 finds the maximum value in a collection of int8 using the best available SIMD instruction set.
func MaxInt8[T ~int8](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxInt8x64(collection)
case simdFeatureAVX2:
return MaxInt8x32(collection)
case simdFeatureAVX:
return MaxInt8x16(collection)
default:
return lo.Max(collection)
}
}
// MaxInt16 finds the maximum value in a collection of int16 using the best available SIMD instruction set.
func MaxInt16[T ~int16](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxInt16x32(collection)
case simdFeatureAVX2:
return MaxInt16x16(collection)
case simdFeatureAVX:
return MaxInt16x8(collection)
default:
return lo.Max(collection)
}
}
// MaxInt32 finds the maximum value in a collection of int32 using the best available SIMD instruction set.
func MaxInt32[T ~int32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxInt32x16(collection)
case simdFeatureAVX2:
return MaxInt32x8(collection)
case simdFeatureAVX:
return MaxInt32x4(collection)
default:
return lo.Max(collection)
}
}
// MaxInt64 finds the maximum value in a collection of int64 using the best available SIMD instruction set.
func MaxInt64[T ~int64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxInt64x8(collection)
case simdFeatureAVX2:
return MaxInt64x4(collection)
case simdFeatureAVX:
// MaxInt64x2 requires AVX-512; use scalar fallback
fallthrough
default:
return lo.Max(collection)
}
}
// MaxUint8 finds the maximum value in a collection of uint8 using the best available SIMD instruction set.
func MaxUint8[T ~uint8](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxUint8x64(collection)
case simdFeatureAVX2:
return MaxUint8x32(collection)
case simdFeatureAVX:
return MaxUint8x16(collection)
default:
return lo.Max(collection)
}
}
// MaxUint16 finds the maximum value in a collection of uint16 using the best available SIMD instruction set.
func MaxUint16[T ~uint16](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxUint16x32(collection)
case simdFeatureAVX2:
return MaxUint16x16(collection)
case simdFeatureAVX:
return MaxUint16x8(collection)
default:
return lo.Max(collection)
}
}
// MaxUint32 finds the maximum value in a collection of uint32 using the best available SIMD instruction set.
func MaxUint32[T ~uint32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxUint32x16(collection)
case simdFeatureAVX2:
return MaxUint32x8(collection)
case simdFeatureAVX:
return MaxUint32x4(collection)
default:
return lo.Max(collection)
}
}
// MaxUint64 finds the maximum value in a collection of uint64 using the best available SIMD instruction set.
func MaxUint64[T ~uint64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxUint64x8(collection)
case simdFeatureAVX2:
return MaxUint64x4(collection)
case simdFeatureAVX:
// MaxUint64x2 requires AVX-512; use scalar fallback
fallthrough
default:
return lo.Max(collection)
}
}
// MaxFloat32 finds the maximum value in a collection of float32 using the best available SIMD instruction set.
func MaxFloat32[T ~float32](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxFloat32x16(collection)
case simdFeatureAVX2:
return MaxFloat32x8(collection)
case simdFeatureAVX:
return MaxFloat32x4(collection)
default:
return lo.Max(collection)
}
}
// MaxFloat64 finds the maximum value in a collection of float64 using the best available SIMD instruction set.
func MaxFloat64[T ~float64](collection []T) T {
switch currentSimdFeature {
case simdFeatureAVX512:
return MaxFloat64x8(collection)
case simdFeatureAVX2:
return MaxFloat64x4(collection)
case simdFeatureAVX:
return MaxFloat64x2(collection)
default:
return lo.Max(collection)
}
}
// ClampInt8 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampInt8[T ~int8, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampInt8x64(collection, min, max)
case simdFeatureAVX2:
return ClampInt8x32(collection, min, max)
case simdFeatureAVX:
return ClampInt8x16(collection, min, max)
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampInt16 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampInt16[T ~int16, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampInt16x32(collection, min, max)
case simdFeatureAVX2:
return ClampInt16x16(collection, min, max)
case simdFeatureAVX:
return ClampInt16x8(collection, min, max)
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampInt32 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampInt32[T ~int32, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampInt32x16(collection, min, max)
case simdFeatureAVX2:
return ClampInt32x8(collection, min, max)
case simdFeatureAVX:
return ClampInt32x4(collection, min, max)
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampInt64 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampInt64[T ~int64, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampInt64x8(collection, min, max)
case simdFeatureAVX2:
return ClampInt64x4(collection, min, max)
case simdFeatureAVX:
// ClampInt64x2 requires AVX-512; use scalar fallback
fallthrough
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampUint8 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampUint8[T ~uint8, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampUint8x64(collection, min, max)
case simdFeatureAVX2:
return ClampUint8x32(collection, min, max)
case simdFeatureAVX:
return ClampUint8x16(collection, min, max)
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampUint16 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampUint16[T ~uint16, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampUint16x32(collection, min, max)
case simdFeatureAVX2:
return ClampUint16x16(collection, min, max)
case simdFeatureAVX:
return ClampUint16x8(collection, min, max)
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampUint32 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampUint32[T ~uint32, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampUint32x16(collection, min, max)
case simdFeatureAVX2:
return ClampUint32x8(collection, min, max)
case simdFeatureAVX:
return ClampUint32x4(collection, min, max)
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampUint64 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampUint64[T ~uint64, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampUint64x8(collection, min, max)
case simdFeatureAVX2:
return ClampUint64x4(collection, min, max)
case simdFeatureAVX:
// ClampUint64x2 requires AVX-512; use scalar fallback
fallthrough
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampFloat32 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampFloat32[T ~float32, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampFloat32x16(collection, min, max)
case simdFeatureAVX2:
return ClampFloat32x8(collection, min, max)
case simdFeatureAVX:
return ClampFloat32x4(collection, min, max)
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// ClampFloat64 clamps each element in collection between min and max values using the best available SIMD instruction set.
func ClampFloat64[T ~float64, Slice ~[]T](collection Slice, min, max T) Slice {
switch currentSimdFeature {
case simdFeatureAVX512:
return ClampFloat64x8(collection, min, max)
case simdFeatureAVX2:
return ClampFloat64x4(collection, min, max)
case simdFeatureAVX:
return ClampFloat64x2(collection, min, max)
default:
result := make(Slice, len(collection))
for i, v := range collection {
if v < min {
result[i] = min
} else if v > max {
result[i] = max
} else {
result[i] = v
}
}
return result
}
}
// SumByInt8 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using int8, which can overflow for large collections.
// If the sum exceeds the int8 range (-128 to 127), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
// Play: https://go.dev/play/p/TBD
func SumByInt8[T any, R ~int8](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByInt8x64(collection, iteratee)
case simdFeatureAVX2:
return SumByInt8x32(collection, iteratee)
case simdFeatureAVX:
return SumByInt8x16(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByInt16 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using int16, which can overflow for large collections.
// If the sum exceeds the int16 range (-32768 to 32767), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
// Play: https://go.dev/play/p/TBD
func SumByInt16[T any, R ~int16](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByInt16x32(collection, iteratee)
case simdFeatureAVX2:
return SumByInt16x16(collection, iteratee)
case simdFeatureAVX:
return SumByInt16x8(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByInt32 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using int32, which can overflow for very large collections.
// If the sum exceeds the int32 range (-2147483648 to 2147483647), the result will wrap around silently.
// For collections that may overflow, consider using SumByInt64 or handle overflow detection externally.
// Play: https://go.dev/play/p/TBD
func SumByInt32[T any, R ~int32](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByInt32x16(collection, iteratee)
case simdFeatureAVX2:
return SumByInt32x8(collection, iteratee)
case simdFeatureAVX:
return SumByInt32x4(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByInt64 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using int64, which can overflow for extremely large collections.
// If the sum exceeds the int64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
// Play: https://go.dev/play/p/TBD
func SumByInt64[T any, R ~int64](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByInt64x8(collection, iteratee)
case simdFeatureAVX2:
return SumByInt64x4(collection, iteratee)
case simdFeatureAVX:
return SumByInt64x2(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByUint8 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using uint8, which can overflow for large collections.
// If the sum exceeds the uint8 range (0 to 255), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
// Play: https://go.dev/play/p/TBD
func SumByUint8[T any, R ~uint8](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByUint8x64(collection, iteratee)
case simdFeatureAVX2:
return SumByUint8x32(collection, iteratee)
case simdFeatureAVX:
return SumByUint8x16(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByUint16 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using uint16, which can overflow for large collections.
// If the sum exceeds the uint16 range (0 to 65535), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
// Play: https://go.dev/play/p/TBD
func SumByUint16[T any, R ~uint16](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByUint16x32(collection, iteratee)
case simdFeatureAVX2:
return SumByUint16x16(collection, iteratee)
case simdFeatureAVX:
return SumByUint16x8(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByUint32 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using uint32, which can overflow for very large collections.
// If the sum exceeds the uint32 range (0 to 4294967295), the result will wrap around silently.
// For collections that may overflow, consider using SumByUint64 or handle overflow detection externally.
// Play: https://go.dev/play/p/TBD
func SumByUint32[T any, R ~uint32](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByUint32x16(collection, iteratee)
case simdFeatureAVX2:
return SumByUint32x8(collection, iteratee)
case simdFeatureAVX:
return SumByUint32x4(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByUint64 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using uint64, which can overflow for extremely large collections.
// If the sum exceeds the uint64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
// Play: https://go.dev/play/p/TBD
func SumByUint64[T any, R ~uint64](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByUint64x8(collection, iteratee)
case simdFeatureAVX2:
return SumByUint64x4(collection, iteratee)
case simdFeatureAVX:
return SumByUint64x2(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByFloat32 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using float32. Overflow will result in +/-Inf rather than wrapping.
// For collections requiring high precision or large sums, consider using SumByFloat64.
// Play: https://go.dev/play/p/TBD
func SumByFloat32[T any, R ~float32](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByFloat32x16(collection, iteratee)
case simdFeatureAVX2:
return SumByFloat32x8(collection, iteratee)
case simdFeatureAVX:
return SumByFloat32x4(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// SumByFloat64 sums the values extracted by iteratee from a slice using the best available SIMD instruction set.
// Overflow: The accumulation is performed using float64. Overflow will result in +/-Inf rather than wrapping.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Float).
// Play: https://go.dev/play/p/TBD
func SumByFloat64[T any, R ~float64](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return SumByFloat64x8(collection, iteratee)
case simdFeatureAVX2:
return SumByFloat64x4(collection, iteratee)
case simdFeatureAVX:
return SumByFloat64x2(collection, iteratee)
default:
return lo.SumBy(collection, iteratee)
}
}
// MeanByInt8 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByInt8[T any, R ~int8](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByInt8x64(collection, iteratee)
case simdFeatureAVX2:
return MeanByInt8x32(collection, iteratee)
case simdFeatureAVX:
return MeanByInt8x16(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByInt16 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByInt16[T any, R ~int16](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByInt16x32(collection, iteratee)
case simdFeatureAVX2:
return MeanByInt16x16(collection, iteratee)
case simdFeatureAVX:
return MeanByInt16x8(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByInt32 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByInt32[T any, R ~int32](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByInt32x16(collection, iteratee)
case simdFeatureAVX2:
return MeanByInt32x8(collection, iteratee)
case simdFeatureAVX:
return MeanByInt32x4(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByInt64 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByInt64[T any, R ~int64](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByInt64x8(collection, iteratee)
case simdFeatureAVX2:
return MeanByInt64x4(collection, iteratee)
case simdFeatureAVX:
return MeanByInt64x2(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByUint8 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByUint8[T any, R ~uint8](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByUint8x64(collection, iteratee)
case simdFeatureAVX2:
return MeanByUint8x32(collection, iteratee)
case simdFeatureAVX:
return MeanByUint8x16(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByUint16 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByUint16[T any, R ~uint16](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByUint16x32(collection, iteratee)
case simdFeatureAVX2:
return MeanByUint16x16(collection, iteratee)
case simdFeatureAVX:
return MeanByUint16x8(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByUint32 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByUint32[T any, R ~uint32](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByUint32x16(collection, iteratee)
case simdFeatureAVX2:
return MeanByUint32x8(collection, iteratee)
case simdFeatureAVX:
return MeanByUint32x4(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByUint64 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByUint64[T any, R ~uint64](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByUint64x8(collection, iteratee)
case simdFeatureAVX2:
return MeanByUint64x4(collection, iteratee)
case simdFeatureAVX:
return MeanByUint64x2(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByFloat32 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByFloat32[T any, R ~float32](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByFloat32x16(collection, iteratee)
case simdFeatureAVX2:
return MeanByFloat32x8(collection, iteratee)
case simdFeatureAVX:
return MeanByFloat32x4(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
// MeanByFloat64 calculates the mean of values extracted by iteratee from a slice using the best available SIMD instruction set.
// Play: https://go.dev/play/p/TBD
func MeanByFloat64[T any, R ~float64](collection []T, iteratee func(item T) R) R {
switch currentSimdFeature {
case simdFeatureAVX512:
return MeanByFloat64x8(collection, iteratee)
case simdFeatureAVX2:
return MeanByFloat64x4(collection, iteratee)
case simdFeatureAVX:
return MeanByFloat64x2(collection, iteratee)
default:
return lo.MeanBy(collection, iteratee)
}
}
================================================
FILE: exp/simd/math_avx.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"simd/archsimd"
"unsafe"
"github.com/samber/lo"
)
// AVX (128-bit) SIMD sum functions - 16/8/4/2 lanes
// SumInt8x16 sums a slice of int8 using AVX SIMD (Int8x16, 16 lanes).
// Overflow: The accumulation is performed using int8, which can overflow for large collections.
// If the sum exceeds the int8 range (-128 to 127), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumInt8x16[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt8(collection, length)
var acc archsimd.Int8x16
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x16Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int8
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt16x8 sums a slice of int16 using AVX SIMD (Int16x8, 8 lanes).
// Overflow: The accumulation is performed using int16, which can overflow for large collections.
// If the sum exceeds the int16 range (-32768 to 32767), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumInt16x8[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt16(collection, length)
var acc archsimd.Int16x8
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x8Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int16
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt32x4 sums a slice of int32 using AVX SIMD (Int32x4, 4 lanes).
// Overflow: The accumulation is performed using int32, which can overflow for very large collections.
// If the sum exceeds the int32 range (-2147483648 to 2147483647), the result will wrap around silently.
// For collections that may overflow, consider using SumInt64x2 or handle overflow detection externally.
func SumInt32x4[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceInt32(collection, length)
var acc archsimd.Int32x4
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x4Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt64x2 sums a slice of int64 using AVX SIMD (Int64x2, 2 lanes).
// Overflow: The accumulation is performed using int64, which can overflow for extremely large collections.
// If the sum exceeds the int64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
func SumInt64x2[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceInt64(collection, length)
var acc archsimd.Int64x2
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x2Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint8x16 sums a slice of uint8 using AVX SIMD (Uint8x16, 16 lanes).
// Overflow: The accumulation is performed using uint8, which can overflow for large collections.
// If the sum exceeds the uint8 range (0 to 255), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumUint8x16[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint8(collection, length)
var acc archsimd.Uint8x16
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x16Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint8
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint16x8 sums a slice of uint16 using AVX SIMD (Uint16x8, 8 lanes).
// Overflow: The accumulation is performed using uint16, which can overflow for large collections.
// If the sum exceeds the uint16 range (0 to 65535), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumUint16x8[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint16(collection, length)
var acc archsimd.Uint16x8
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x8Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint16
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint32x4 sums a slice of uint32 using AVX SIMD (Uint32x4, 4 lanes).
// Overflow: The accumulation is performed using uint32, which can overflow for very large collections.
// If the sum exceeds the uint32 range (0 to 4294967295), the result will wrap around silently.
// For collections that may overflow, consider using SumUint64x2 or handle overflow detection externally.
func SumUint32x4[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceUint32(collection, length)
var acc archsimd.Uint32x4
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x4Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint64x2 sums a slice of uint64 using AVX SIMD (Uint64x2, 2 lanes).
// Overflow: The accumulation is performed using uint64, which can overflow for extremely large collections.
// If the sum exceeds the uint64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
func SumUint64x2[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceUint64(collection, length)
var acc archsimd.Uint64x2
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x2Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumFloat32x4 sums a slice of float32 using AVX SIMD (Float32x4, 4 lanes).
// Overflow: The accumulation is performed using float32. Overflow will result in +/-Inf rather than wrapping.
// For collections requiring high precision or large sums, consider using SumFloat64x2.
func SumFloat32x4[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceFloat32(collection, length)
var acc archsimd.Float32x4
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x4Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]float32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumFloat64x2 sums a slice of float64 using AVX SIMD (Float64x2, 2 lanes).
// Overflow: The accumulation is performed using float64. Overflow will result in +/-Inf rather than wrapping.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Float).
func SumFloat64x2[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceFloat64(collection, length)
var acc archsimd.Float64x2
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x2Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]float64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// MeanInt8x16 calculates the mean of a slice of int8 using AVX SIMD
func MeanInt8x16[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumInt8x16(collection)
return sum / T(length)
}
// MeanInt16x8 calculates the mean of a slice of int16 using AVX SIMD
func MeanInt16x8[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumInt16x8(collection)
return sum / T(length)
}
// MeanInt32x4 calculates the mean of a slice of int32 using AVX SIMD
func MeanInt32x4[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumInt32x4(collection)
return sum / T(length)
}
// MeanInt64x2 calculates the mean of a slice of int64 using AVX SIMD
func MeanInt64x2[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumInt64x2(collection)
return sum / T(length)
}
// MeanUint8x16 calculates the mean of a slice of uint8 using AVX SIMD
func MeanUint8x16[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumUint8x16(collection)
return sum / T(length)
}
// MeanUint16x8 calculates the mean of a slice of uint16 using AVX SIMD
func MeanUint16x8[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumUint16x8(collection)
return sum / T(length)
}
// MeanUint32x4 calculates the mean of a slice of uint32 using AVX SIMD
func MeanUint32x4[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumUint32x4(collection)
return sum / T(length)
}
// MeanUint64x2 calculates the mean of a slice of uint64 using AVX SIMD
func MeanUint64x2[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumUint64x2(collection)
return sum / T(length)
}
// MeanFloat32x4 calculates the mean of a slice of float32 using AVX SIMD
func MeanFloat32x4[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumFloat32x4(collection)
return sum / T(length)
}
// MeanFloat64x2 calculates the mean of a slice of float64 using AVX SIMD
func MeanFloat64x2[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumFloat64x2(collection)
return sum / T(length)
}
// ClampInt8x16 clamps each element in collection between min and max values using AVX SIMD
func ClampInt8x16[T ~int8, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes16
base := unsafeSliceInt8(collection, length)
minVec := archsimd.BroadcastInt8x16(int8(min))
maxVec := archsimd.BroadcastInt8x16(int8(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x16Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int8)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt16x8 clamps each element in collection between min and max values using AVX SIMD
func ClampInt16x8[T ~int16, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes8
base := unsafeSliceInt16(collection, length)
minVec := archsimd.BroadcastInt16x8(int16(min))
maxVec := archsimd.BroadcastInt16x8(int16(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x8Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int16)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt32x4 clamps each element in collection between min and max values using AVX SIMD
func ClampInt32x4[T ~int32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes4
base := unsafeSliceInt32(collection, length)
minVec := archsimd.BroadcastInt32x4(int32(min))
maxVec := archsimd.BroadcastInt32x4(int32(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x4Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint8x16 clamps each element in collection between min and max values using AVX SIMD
func ClampUint8x16[T ~uint8, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes16
base := unsafeSliceUint8(collection, length)
minVec := archsimd.BroadcastUint8x16(uint8(min))
maxVec := archsimd.BroadcastUint8x16(uint8(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x16Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint8)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint16x8 clamps each element in collection between min and max values using AVX SIMD
func ClampUint16x8[T ~uint16, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes8
base := unsafeSliceUint16(collection, length)
minVec := archsimd.BroadcastUint16x8(uint16(min))
maxVec := archsimd.BroadcastUint16x8(uint16(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x8Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint16)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint32x4 clamps each element in collection between min and max values using AVX SIMD
func ClampUint32x4[T ~uint32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes4
base := unsafeSliceUint32(collection, length)
minVec := archsimd.BroadcastUint32x4(uint32(min))
maxVec := archsimd.BroadcastUint32x4(uint32(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x4Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampFloat32x4 clamps each element in collection between min and max values using AVX SIMD
func ClampFloat32x4[T ~float32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes4
base := unsafeSliceFloat32(collection, length)
minVec := archsimd.BroadcastFloat32x4(float32(min))
maxVec := archsimd.BroadcastFloat32x4(float32(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x4Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]float32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampFloat64x2 clamps each element in collection between min and max values using AVX SIMD
func ClampFloat64x2[T ~float64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes2
base := unsafeSliceFloat64(collection, length)
minVec := archsimd.BroadcastFloat64x2(float64(min))
maxVec := archsimd.BroadcastFloat64x2(float64(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x2Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]float64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// MinInt8x16 finds the minimum value in a collection of int8 using AVX SIMD
func MinInt8x16[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt8(collection, length)
var minVec archsimd.Int8x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x16Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int8
if firstInitialized {
var buf [lanes]int8
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int8(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt16x8 finds the minimum value in a collection of int16 using AVX SIMD
func MinInt16x8[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt16(collection, length)
var minVec archsimd.Int16x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x8Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int16
if firstInitialized {
var buf [lanes]int16
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int16(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt32x4 finds the minimum value in a collection of int32 using AVX SIMD
func MinInt32x4[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceInt32(collection, length)
var minVec archsimd.Int32x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x4Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int32
if firstInitialized {
var buf [lanes]int32
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint8x16 finds the minimum value in a collection of uint8 using AVX SIMD
func MinUint8x16[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint8(collection, length)
var minVec archsimd.Uint8x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x16Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint8
if firstInitialized {
var buf [lanes]uint8
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint8(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint16x8 finds the minimum value in a collection of uint16 using AVX SIMD
func MinUint16x8[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint16(collection, length)
var minVec archsimd.Uint16x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x8Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint16
if firstInitialized {
var buf [lanes]uint16
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint16(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint32x4 finds the minimum value in a collection of uint32 using AVX SIMD
func MinUint32x4[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceUint32(collection, length)
var minVec archsimd.Uint32x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x4Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint32
if firstInitialized {
var buf [lanes]uint32
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinFloat32x4 finds the minimum value in a collection of float32 using AVX SIMD
func MinFloat32x4[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceFloat32(collection, length)
var minVec archsimd.Float32x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x4Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal float32
if firstInitialized {
var buf [lanes]float32
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = float32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinFloat64x2 finds the minimum value in a collection of float64 using AVX SIMD
func MinFloat64x2[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceFloat64(collection, length)
var minVec archsimd.Float64x2
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x2Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal float64
if firstInitialized {
var buf [lanes]float64
minVec.Store(&buf)
minVal = min(buf[0], buf[1])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = float64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MaxInt8x16 finds the maximum value in a collection of int8 using AVX SIMD
func MaxInt8x16[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt8(collection, length)
var maxVec archsimd.Int8x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x16Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int8
if firstInitialized {
var buf [lanes]int8
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int8(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt16x8 finds the maximum value in a collection of int16 using AVX SIMD
func MaxInt16x8[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt16(collection, length)
var maxVec archsimd.Int16x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x8Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int16
if firstInitialized {
var buf [lanes]int16
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int16(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt32x4 finds the maximum value in a collection of int32 using AVX SIMD
func MaxInt32x4[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceInt32(collection, length)
var maxVec archsimd.Int32x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x4Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int32
if firstInitialized {
var buf [lanes]int32
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint8x16 finds the maximum value in a collection of uint8 using AVX SIMD
func MaxUint8x16[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint8(collection, length)
var maxVec archsimd.Uint8x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x16Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint8
if firstInitialized {
var buf [lanes]uint8
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint8(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint16x8 finds the maximum value in a collection of uint16 using AVX SIMD
func MaxUint16x8[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint16(collection, length)
var maxVec archsimd.Uint16x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x8Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint16
if firstInitialized {
var buf [lanes]uint16
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint16(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint32x4 finds the maximum value in a collection of uint32 using AVX SIMD
func MaxUint32x4[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceUint32(collection, length)
var maxVec archsimd.Uint32x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x4Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint32
if firstInitialized {
var buf [lanes]uint32
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxFloat32x4 finds the maximum value in a collection of float32 using AVX SIMD
func MaxFloat32x4[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceFloat32(collection, length)
var maxVec archsimd.Float32x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x4Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal float32
if firstInitialized {
var buf [lanes]float32
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = float32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxFloat64x2 finds the maximum value in a collection of float64 using AVX SIMD
func MaxFloat64x2[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceFloat64(collection, length)
var maxVec archsimd.Float64x2
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x2Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal float64
if firstInitialized {
var buf [lanes]float64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = float64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// AVX (128-bit) SIMD sumBy functions - 16/8/4/2 lanes
// These implementations use lo.Map to apply the iteratee, then chain with SIMD sum functions.
// SumByInt8x16 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByInt8x16[T any, R ~int8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt8x16(mapped)
}
// SumByInt16x8 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByInt16x8[T any, R ~int16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt16x8(mapped)
}
// SumByInt32x4 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByInt32x4[T any, R ~int32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt32x4(mapped)
}
// SumByInt64x2 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByInt64x2[T any, R ~int64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt64x2(mapped)
}
// SumByUint8x16 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByUint8x16[T any, R ~uint8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint8x16(mapped)
}
// SumByUint16x8 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByUint16x8[T any, R ~uint16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint16x8(mapped)
}
// SumByUint32x4 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByUint32x4[T any, R ~uint32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint32x4(mapped)
}
// SumByUint64x2 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByUint64x2[T any, R ~uint64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint64x2(mapped)
}
// SumByFloat32x4 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByFloat32x4[T any, R ~float32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumFloat32x4(mapped)
}
// SumByFloat64x2 sums the values extracted by iteratee from a slice using AVX SIMD.
func SumByFloat64x2[T any, R ~float64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumFloat64x2(mapped)
}
// AVX (128-bit) SIMD meanBy functions - 16/8/4/2 lanes
// These implementations use lo.Map to apply the iteratee, then chain with SIMD mean functions.
// MeanByInt8x16 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByInt8x16[T any, R ~int8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt8x16(mapped)
}
// MeanByInt16x8 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByInt16x8[T any, R ~int16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt16x8(mapped)
}
// MeanByInt32x4 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByInt32x4[T any, R ~int32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt32x4(mapped)
}
// MeanByInt64x2 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByInt64x2[T any, R ~int64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt64x2(mapped)
}
// MeanByUint8x16 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByUint8x16[T any, R ~uint8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint8x16(mapped)
}
// MeanByUint16x8 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByUint16x8[T any, R ~uint16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint16x8(mapped)
}
// MeanByUint32x4 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByUint32x4[T any, R ~uint32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint32x4(mapped)
}
// MeanByUint64x2 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByUint64x2[T any, R ~uint64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint64x2(mapped)
}
// MeanByFloat32x4 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByFloat32x4[T any, R ~float32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanFloat32x4(mapped)
}
// MeanByFloat64x2 calculates the mean of values extracted by iteratee from a slice using AVX SIMD.
func MeanByFloat64x2[T any, R ~float64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanFloat64x2(mapped)
}
================================================
FILE: exp/simd/math_avx2.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"simd/archsimd"
"unsafe"
"github.com/samber/lo"
)
// AVX2 (256-bit) SIMD sum functions - 32/16/8/4 lanes
// SumInt8x32 sums a slice of int8 using AVX2 SIMD (Int8x32, 32 lanes).
// Overflow: The accumulation is performed using int8, which can overflow for large collections.
// If the sum exceeds the int8 range (-128 to 127), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumInt8x32[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceInt8(collection, length)
var acc archsimd.Int8x32
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x32Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int8
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt16x16 sums a slice of int16 using AVX2 SIMD (Int16x16, 16 lanes).
// Overflow: The accumulation is performed using int16, which can overflow for large collections.
// If the sum exceeds the int16 range (-32768 to 32767), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumInt16x16[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt16(collection, length)
var acc archsimd.Int16x16
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x16Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int16
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt32x8 sums a slice of int32 using AVX2 SIMD (Int32x8, 8 lanes).
// Overflow: The accumulation is performed using int32, which can overflow for very large collections.
// If the sum exceeds the int32 range (-2147483648 to 2147483647), the result will wrap around silently.
// For collections that may overflow, consider using SumInt64x4 or handle overflow detection externally.
func SumInt32x8[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt32(collection, length)
var acc archsimd.Int32x8
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x8Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt64x4 sums a slice of int64 using AVX2 SIMD (Int64x4, 4 lanes).
// Overflow: The accumulation is performed using int64, which can overflow for extremely large collections.
// If the sum exceeds the int64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
func SumInt64x4[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceInt64(collection, length)
var acc archsimd.Int64x4
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x4Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint8x32 sums a slice of uint8 using AVX2 SIMD (Uint8x32, 32 lanes).
// Overflow: The accumulation is performed using uint8, which can overflow for large collections.
// If the sum exceeds the uint8 range (0 to 255), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumUint8x32[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceUint8(collection, length)
var acc archsimd.Uint8x32
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x32Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint8
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint16x16 sums a slice of uint16 using AVX2 SIMD (Uint16x16, 16 lanes).
// Overflow: The accumulation is performed using uint16, which can overflow for large collections.
// If the sum exceeds the uint16 range (0 to 65535), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumUint16x16[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint16(collection, length)
var acc archsimd.Uint16x16
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x16Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint16
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint32x8 sums a slice of uint32 using AVX2 SIMD (Uint32x8, 8 lanes).
// Overflow: The accumulation is performed using uint32, which can overflow for very large collections.
// If the sum exceeds the uint32 range (0 to 4294967295), the result will wrap around silently.
// For collections that may overflow, consider using SumUint64x4 or handle overflow detection externally.
func SumUint32x8[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint32(collection, length)
var acc archsimd.Uint32x8
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x8Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint64x4 sums a slice of uint64 using AVX2 SIMD (Uint64x4, 4 lanes).
// Overflow: The accumulation is performed using uint64, which can overflow for extremely large collections.
// If the sum exceeds the uint64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
func SumUint64x4[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceUint64(collection, length)
var acc archsimd.Uint64x4
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x4Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumFloat32x8 sums a slice of float32 using AVX2 SIMD (Float32x8, 8 lanes).
// Overflow: The accumulation is performed using float32. Overflow will result in +/-Inf rather than wrapping.
// For collections requiring high precision or large sums, consider using SumFloat64x4.
func SumFloat32x8[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceFloat32(collection, length)
var acc archsimd.Float32x8
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x8Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]float32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumFloat64x4 sums a slice of float64 using AVX2 SIMD (Float64x4, 4 lanes).
// Overflow: The accumulation is performed using float64. Overflow will result in +/-Inf rather than wrapping.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Float).
func SumFloat64x4[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceFloat64(collection, length)
var acc archsimd.Float64x4
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x4Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]float64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// MeanInt8x32 calculates the mean of a slice of int8 using AVX2 SIMD
func MeanInt8x32[T ~int8](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumInt8x32(collection)
return sum / T(len(collection))
}
// MeanInt16x16 calculates the mean of a slice of int16 using AVX2 SIMD
func MeanInt16x16[T ~int16](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumInt16x16(collection)
return sum / T(len(collection))
}
// MeanInt32x8 calculates the mean of a slice of int32 using AVX2 SIMD
func MeanInt32x8[T ~int32](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumInt32x8(collection)
return sum / T(len(collection))
}
// MeanInt64x4 calculates the mean of a slice of int64 using AVX2 SIMD
func MeanInt64x4[T ~int64](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumInt64x4(collection)
return sum / T(len(collection))
}
// MeanUint8x32 calculates the mean of a slice of uint8 using AVX2 SIMD
func MeanUint8x32[T ~uint8](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumUint8x32(collection)
return sum / T(len(collection))
}
// MeanUint16x16 calculates the mean of a slice of uint16 using AVX2 SIMD
func MeanUint16x16[T ~uint16](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumUint16x16(collection)
return sum / T(len(collection))
}
// MeanUint32x8 calculates the mean of a slice of uint32 using AVX2 SIMD
func MeanUint32x8[T ~uint32](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumUint32x8(collection)
return sum / T(len(collection))
}
// MeanUint64x4 calculates the mean of a slice of uint64 using AVX2 SIMD
func MeanUint64x4[T ~uint64](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumUint64x4(collection)
return sum / T(len(collection))
}
// MeanFloat32x8 calculates the mean of a slice of float32 using AVX2 SIMD
func MeanFloat32x8[T ~float32](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumFloat32x8(collection)
return sum / T(len(collection))
}
// MeanFloat64x4 calculates the mean of a slice of float64 using AVX2 SIMD
func MeanFloat64x4[T ~float64](collection []T) T {
if T(len(collection)) == 0 {
return 0
}
sum := SumFloat64x4(collection)
return sum / T(len(collection))
}
// ClampInt8x32 clamps each element in collection between min and max values using AVX2 SIMD
func ClampInt8x32[T ~int8, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes32
base := unsafeSliceInt8(collection, length)
minVec := archsimd.BroadcastInt8x32(int8(min))
maxVec := archsimd.BroadcastInt8x32(int8(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x32Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int8)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt16x16 clamps each element in collection between min and max values using AVX2 SIMD
func ClampInt16x16[T ~int16, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes16
base := unsafeSliceInt16(collection, length)
minVec := archsimd.BroadcastInt16x16(int16(min))
maxVec := archsimd.BroadcastInt16x16(int16(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x16Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int16)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt32x8 clamps each element in collection between min and max values using AVX2 SIMD
func ClampInt32x8[T ~int32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes8
base := unsafeSliceInt32(collection, length)
minVec := archsimd.BroadcastInt32x8(int32(min))
maxVec := archsimd.BroadcastInt32x8(int32(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x8Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt64x4 clamps each element in collection between min and max values using AVX2 SIMD and AVX-512 SIMD.
func ClampInt64x4[T ~int64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes4
base := unsafeSliceInt64(collection, length)
minVec := archsimd.BroadcastInt64x4(int64(min))
maxVec := archsimd.BroadcastInt64x4(int64(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x4Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint8x32 clamps each element in collection between min and max values using AVX2 SIMD
func ClampUint8x32[T ~uint8, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes32
base := unsafeSliceUint8(collection, length)
minVec := archsimd.BroadcastUint8x32(uint8(min))
maxVec := archsimd.BroadcastUint8x32(uint8(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x32Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint8)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint16x16 clamps each element in collection between min and max values using AVX2 SIMD
func ClampUint16x16[T ~uint16, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes16
base := unsafeSliceUint16(collection, length)
minVec := archsimd.BroadcastUint16x16(uint16(min))
maxVec := archsimd.BroadcastUint16x16(uint16(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x16Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint16)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint32x8 clamps each element in collection between min and max values using AVX2 SIMD
func ClampUint32x8[T ~uint32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes8
base := unsafeSliceUint32(collection, length)
minVec := archsimd.BroadcastUint32x8(uint32(min))
maxVec := archsimd.BroadcastUint32x8(uint32(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x8Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint64x4 clamps each element in collection between min and max values using AVX2 SIMD and AVX-512 SIMD.
func ClampUint64x4[T ~uint64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes4
base := unsafeSliceUint64(collection, length)
minVec := archsimd.BroadcastUint64x4(uint64(min))
maxVec := archsimd.BroadcastUint64x4(uint64(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x4Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampFloat32x8 clamps each element in collection between min and max values using AVX2 SIMD
func ClampFloat32x8[T ~float32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes8
base := unsafeSliceFloat32(collection, length)
minVec := archsimd.BroadcastFloat32x8(float32(min))
maxVec := archsimd.BroadcastFloat32x8(float32(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x8Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]float32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampFloat64x4 clamps each element in collection between min and max values using AVX2 SIMD
func ClampFloat64x4[T ~float64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes4
base := unsafeSliceFloat64(collection, length)
minVec := archsimd.BroadcastFloat64x4(float64(min))
maxVec := archsimd.BroadcastFloat64x4(float64(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x4Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]float64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// MinInt8x32 finds the minimum value in a collection of int8 using AVX2 SIMD
func MinInt8x32[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceInt8(collection, length)
var minVec archsimd.Int8x32
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x32Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int8
if firstInitialized {
var buf [lanes]int8
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int8(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt16x16 finds the minimum value in a collection of int16 using AVX2 SIMD
func MinInt16x16[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt16(collection, length)
var minVec archsimd.Int16x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x16Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int16
if firstInitialized {
var buf [lanes]int16
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int16(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt32x8 finds the minimum value in a collection of int32 using AVX2 SIMD
func MinInt32x8[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt32(collection, length)
var minVec archsimd.Int32x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x8Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int32
if firstInitialized {
var buf [lanes]int32
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt64x4 finds the minimum value in a collection of int64 using AVX2 SIMD
func MinInt64x4[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceInt64(collection, length)
var minVec archsimd.Int64x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x4Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int64
if firstInitialized {
var buf [lanes]int64
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint8x32 finds the minimum value in a collection of uint8 using AVX2 SIMD
func MinUint8x32[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceUint8(collection, length)
var minVec archsimd.Uint8x32
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x32Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint8
if firstInitialized {
var buf [lanes]uint8
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint8(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint16x16 finds the minimum value in a collection of uint16 using AVX2 SIMD
func MinUint16x16[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint16(collection, length)
var minVec archsimd.Uint16x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x16Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint16
if firstInitialized {
var buf [lanes]uint16
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint16(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint32x8 finds the minimum value in a collection of uint32 using AVX2 SIMD
func MinUint32x8[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint32(collection, length)
var minVec archsimd.Uint32x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x8Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint32
if firstInitialized {
var buf [lanes]uint32
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint64x4 finds the minimum value in a collection of uint64 using AVX2 SIMD
func MinUint64x4[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceUint64(collection, length)
var minVec archsimd.Uint64x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x4Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint64
if firstInitialized {
var buf [lanes]uint64
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinFloat32x8 finds the minimum value in a collection of float32 using AVX2 SIMD
func MinFloat32x8[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceFloat32(collection, length)
var minVec archsimd.Float32x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x8Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal float32
if firstInitialized {
var buf [lanes]float32
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = float32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinFloat64x4 finds the minimum value in a collection of float64 using AVX2 SIMD
func MinFloat64x4[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceFloat64(collection, length)
var minVec archsimd.Float64x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x4Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal float64
if firstInitialized {
var buf [lanes]float64
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = float64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MaxInt8x32 finds the maximum value in a collection of int8 using AVX2 SIMD
func MaxInt8x32[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceInt8(collection, length)
var maxVec archsimd.Int8x32
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x32Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int8
if firstInitialized {
var buf [lanes]int8
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int8(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt16x16 finds the maximum value in a collection of int16 using AVX2 SIMD
func MaxInt16x16[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt16(collection, length)
var maxVec archsimd.Int16x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x16Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int16
if firstInitialized {
var buf [lanes]int16
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int16(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt32x8 finds the maximum value in a collection of int32 using AVX2 SIMD
func MaxInt32x8[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt32(collection, length)
var maxVec archsimd.Int32x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x8Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int32
if firstInitialized {
var buf [lanes]int32
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt64x4 finds the maximum value in a collection of int64 using AVX2 SIMD
func MaxInt64x4[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceInt64(collection, length)
var maxVec archsimd.Int64x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x4Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int64
if firstInitialized {
var buf [lanes]int64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint8x32 finds the maximum value in a collection of uint8 using AVX2 SIMD
func MaxUint8x32[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceUint8(collection, length)
var maxVec archsimd.Uint8x32
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x32Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint8
if firstInitialized {
var buf [lanes]uint8
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint8(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint16x16 finds the maximum value in a collection of uint16 using AVX2 SIMD
func MaxUint16x16[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint16(collection, length)
var maxVec archsimd.Uint16x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x16Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint16
if firstInitialized {
var buf [lanes]uint16
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint16(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint32x8 finds the maximum value in a collection of uint32 using AVX2 SIMD
func MaxUint32x8[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint32(collection, length)
var maxVec archsimd.Uint32x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x8Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint32
if firstInitialized {
var buf [lanes]uint32
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint64x4 finds the maximum value in a collection of uint64 using AVX2 SIMD
func MaxUint64x4[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceUint64(collection, length)
var maxVec archsimd.Uint64x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x4Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint64
if firstInitialized {
var buf [lanes]uint64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxFloat32x8 finds the maximum value in a collection of float32 using AVX2 SIMD
func MaxFloat32x8[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceFloat32(collection, length)
var maxVec archsimd.Float32x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x8Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal float32
if firstInitialized {
var buf [lanes]float32
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = float32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxFloat64x4 finds the maximum value in a collection of float64 using AVX2 SIMD
func MaxFloat64x4[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes4
base := unsafeSliceFloat64(collection, length)
var maxVec archsimd.Float64x4
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x4Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal float64
if firstInitialized {
var buf [lanes]float64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = float64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// AVX2 (256-bit) SIMD sumBy functions - 32/16/8/4 lanes
// These implementations use lo.Map to apply the iteratee, then chain with SIMD sum functions.
// SumByInt8x32 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByInt8x32[T any, R ~int8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt8x32(mapped)
}
// SumByInt16x16 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByInt16x16[T any, R ~int16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt16x16(mapped)
}
// SumByInt32x8 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByInt32x8[T any, R ~int32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt32x8(mapped)
}
// SumByInt64x4 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByInt64x4[T any, R ~int64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt64x4(mapped)
}
// SumByUint8x32 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByUint8x32[T any, R ~uint8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint8x32(mapped)
}
// SumByUint16x16 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByUint16x16[T any, R ~uint16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint16x16(mapped)
}
// SumByUint32x8 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByUint32x8[T any, R ~uint32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint32x8(mapped)
}
// SumByUint64x4 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByUint64x4[T any, R ~uint64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint64x4(mapped)
}
// SumByFloat32x8 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByFloat32x8[T any, R ~float32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumFloat32x8(mapped)
}
// SumByFloat64x4 sums the values extracted by iteratee from a slice using AVX2 SIMD.
func SumByFloat64x4[T any, R ~float64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumFloat64x4(mapped)
}
// AVX2 (256-bit) SIMD meanBy functions - 32/16/8/4 lanes
// These implementations use lo.Map to apply the iteratee, then chain with SIMD mean functions.
// MeanByInt8x32 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByInt8x32[T any, R ~int8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt8x32(mapped)
}
// MeanByInt16x16 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByInt16x16[T any, R ~int16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt16x16(mapped)
}
// MeanByInt32x8 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByInt32x8[T any, R ~int32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt32x8(mapped)
}
// MeanByInt64x4 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByInt64x4[T any, R ~int64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt64x4(mapped)
}
// MeanByUint8x32 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByUint8x32[T any, R ~uint8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint8x32(mapped)
}
// MeanByUint16x16 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByUint16x16[T any, R ~uint16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint16x16(mapped)
}
// MeanByUint32x8 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByUint32x8[T any, R ~uint32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint32x8(mapped)
}
// MeanByUint64x4 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByUint64x4[T any, R ~uint64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint64x4(mapped)
}
// MeanByFloat32x8 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByFloat32x8[T any, R ~float32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanFloat32x8(mapped)
}
// MeanByFloat64x4 calculates the mean of values extracted by iteratee from a slice using AVX2 SIMD.
func MeanByFloat64x4[T any, R ~float64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanFloat64x4(mapped)
}
================================================
FILE: exp/simd/math_avx2_test.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"math/rand/v2"
"testing"
"github.com/samber/lo"
)
func TestSumInt8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 32", make([]int8, 32)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := SumInt8x32(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt8x32() = %v, want %v", got, want)
}
})
}
}
func TestSumInt16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 16", make([]int16, 16)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := SumInt16x16(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt16x16() = %v, want %v", got, want)
}
})
}
}
func TestSumInt32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 8", make([]int32, 8)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int32(rand.Int32())
}
}
got := SumInt32x8(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt32x8() = %v, want %v", got, want)
}
})
}
}
func TestSumInt64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 4", make([]int64, 4)},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := SumInt64x4(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt64x4() = %v, want %v", got, want)
}
})
}
}
func TestSumUint8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 32", make([]uint8, 32)},
{"large", make([]uint8, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := SumUint8x32(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint8x32() = %v, want %v", got, want)
}
})
}
}
func TestSumUint16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint16, 16)},
{"large", make([]uint16, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := SumUint16x16(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint16x16() = %v, want %v", got, want)
}
})
}
}
func TestSumUint32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint32, 8)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := SumUint32x8(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint32x8() = %v, want %v", got, want)
}
})
}
}
func TestSumUint64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 4", make([]uint64, 4)},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := SumUint64x4(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint64x4() = %v, want %v", got, want)
}
})
}
}
func TestSumFloat32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 8", make([]float32, 8)},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := SumFloat32x8(tc.input)
want := lo.Sum(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumFloat32x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestSumFloat64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 4", make([]float64, 4)},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := SumFloat64x4(tc.input)
want := lo.Sum(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumFloat64x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVX2TypeAlias(t *testing.T) {
requireAVX2(t)
input := []myInt16{1, 2, 3, 4, 5}
got := SumInt16x16(input)
want := lo.Sum(input)
if got != want {
t.Errorf("SumInt16x16() with type alias = %v, want %v", got, want)
}
}
func TestMeanInt8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 32", make([]int8, 32)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MeanInt8x32(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt8x32() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 16", make([]int16, 16)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MeanInt16x16(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt16x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 8", make([]int32, 8)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int32(rand.Int32())
}
}
got := MeanInt32x8(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt32x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 4", make([]int64, 4)},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MeanInt64x4(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt64x4() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 32", make([]uint8, 32)},
{"large", make([]uint8, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MeanUint8x32(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint8x32() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint16, 16)},
{"large", make([]uint16, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MeanUint16x16(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint16x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint32, 8)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MeanUint32x8(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint32x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 4", make([]uint64, 4)},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MeanUint64x4(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint64x4() = %v, want %v", got, want)
}
})
}
}
func TestMeanFloat32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 8", make([]float32, 8)},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MeanFloat32x8(tc.input)
want := lo.Mean(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanFloat32x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMeanFloat64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 4", make([]float64, 4)},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MeanFloat64x4(tc.input)
want := lo.Mean(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanFloat64x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVX2MeanTypeAlias(t *testing.T) {
requireAVX2(t)
input := []myInt16{1, 2, 3, 4, 5}
got := MeanInt16x16(input)
want := lo.Mean(input)
if got != want {
t.Errorf("MeanInt16x16() with type alias = %v, want %v", got, want)
}
}
func TestClampInt8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int8
min int8
max int8
}{
{"empty", []int8{}, -10, 10},
{"single", []int8{42}, -10, 10},
{"small", []int8{1, 2, 3, 4, 5}, 2, 4},
{"exactly 32", make([]int8, 32), 5, 10},
{"large", make([]int8, 1000), -5, 5},
{"all below min", []int8{-10, -20, -30}, -5, 10},
{"all above max", []int8{20, 30, 40}, -10, 10},
{"already clamped", []int8{5, 6, 7}, 5, 10},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := ClampInt8x32(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt8x32() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt8x32()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt8x32()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int16
min int16
max int16
}{
{"empty", []int16{}, -100, 100},
{"single", []int16{42}, -10, 10},
{"small", []int16{1, 2, 3, 4, 5}, 2, 4},
{"exactly 16", make([]int16, 16), 50, 100},
{"large", make([]int16, 1000), -50, 50},
{"all below min", []int16{-100, -200, -300}, -50, 100},
{"all above max", []int16{200, 300, 400}, -100, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := ClampInt16x16(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt16x16() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt16x16()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt16x16()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int32
min int32
max int32
}{
{"empty", []int32{}, -100, 100},
{"single", []int32{42}, -10, 10},
{"small", []int32{1, 2, 3, 4, 5}, 2, 4},
{"exactly 8", make([]int32, 8), 50, 100},
{"large", make([]int32, 1000), -50, 50},
{"negative range", []int32{-100, -50, 0, 50, 100}, -30, -10},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := ClampInt32x8(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt32x8() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt32x8()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt32x8()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
input []int64
min int64
max int64
}{
{"empty", []int64{}, -100, 100},
{"single", []int64{42}, -10, 10},
{"small", []int64{1, 2, 3, 4, 5}, 2, 4},
{"exactly 4", make([]int64, 4), 50, 100},
{"large", make([]int64, 1000), -50, 50},
{"all below min", []int64{-1000, -2000, -3000}, -500, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := ClampInt64x4(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt64x4() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt64x4()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt64x4()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint8
min uint8
max uint8
}{
{"empty", []uint8{}, 10, 100},
{"single", []uint8{42}, 10, 100},
{"small", []uint8{1, 2, 3, 4, 5}, 2, 4},
{"exactly 32", make([]uint8, 32), 50, 100},
{"large", make([]uint8, 1000), 50, 200},
{"all below min", []uint8{1, 2, 3}, 10, 100},
{"all above max", []uint8{200, 225, 250}, 50, 150},
{"max values", []uint8{255, 255, 255}, 100, 200},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := ClampUint8x32(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint8x32() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint8x32()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint8x32()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint16
min uint16
max uint16
}{
{"empty", []uint16{}, 100, 1000},
{"single", []uint16{42}, 10, 100},
{"small", []uint16{1, 2, 3, 4, 5}, 2, 4},
{"exactly 16", make([]uint16, 16), 500, 1000},
{"large", make([]uint16, 1000), 500, 5000},
{"all below min", []uint16{1, 2, 3}, 10, 100},
{"all above max", []uint16{2000, 3000, 4000}, 100, 1000},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := ClampUint16x16(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint16x16() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint16x16()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint16x16()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint32
min uint32
max uint32
}{
{"empty", []uint32{}, 100, 1000},
{"single", []uint32{42}, 10, 100},
{"small", []uint32{1, 2, 3, 4, 5}, 2, 4},
{"exactly 8", make([]uint32, 8), 500, 1000},
{"large", make([]uint32, 1000), 500, 5000},
{"all below min", []uint32{1, 2, 3}, 10, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := ClampUint32x8(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint32x8() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint32x8()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint32x8()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
input []uint64
min uint64
max uint64
}{
{"empty", []uint64{}, 100, 1000},
{"single", []uint64{42}, 10, 100},
{"small", []uint64{1, 2, 3, 4, 5}, 2, 4},
{"exactly 4", make([]uint64, 4), 500, 1000},
{"large", make([]uint64, 1000), 500, 5000},
{"all below min", []uint64{1, 2, 3}, 10, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := ClampUint64x4(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint64x4() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint64x4()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint64x4()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampFloat32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float32
min float32
max float32
}{
{"empty", []float32{}, -10.0, 10.0},
{"single", []float32{42.5}, -10.0, 10.0},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 2.0, 4.0},
{"exactly 8", make([]float32, 8), -5.0, 10.0},
{"large", make([]float32, 1000), -5.0, 5.0},
{"negative range", []float32{-10.0, -5.0, 0.0, 5.0, 10.0}, -3.0, -1.0},
{"all below min", []float32{-20.0, -30.0, -40.0}, -10.0, 10.0},
{"all above max", []float32{20.0, 30.0, 40.0}, -10.0, 10.0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()*200 - 100
}
}
got := ClampFloat32x8(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampFloat32x8() returned length %d, want %d", len(got), len(tc.input))
}
const epsilon = 1e-3
for i, v := range got {
if v < tc.min-epsilon || v > tc.max+epsilon {
t.Errorf("ClampFloat32x8()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if diff := v - expected; diff < -epsilon || diff > epsilon {
t.Errorf("ClampFloat32x8()[%d] = %v, want %v (original: %v, diff: %v)", i, v, expected, original, diff)
}
}
})
}
}
func TestClampFloat64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float64
min float64
max float64
}{
{"empty", []float64{}, -10.0, 10.0},
{"single", []float64{42.5}, -10.0, 10.0},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 2.0, 4.0},
{"exactly 4", make([]float64, 4), -5.0, 10.0},
{"large", make([]float64, 1000), -5.0, 5.0},
{"negative range", []float64{-10.0, -5.0, 0.0, 5.0, 10.0}, -3.0, -1.0},
{"all below min", []float64{-20.0, -30.0, -40.0}, -10.0, 10.0},
{"all above max", []float64{20.0, 30.0, 40.0}, -10.0, 10.0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()*200 - 100
}
}
got := ClampFloat64x4(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampFloat64x4() returned length %d, want %d", len(got), len(tc.input))
}
const epsilon = 1e-3
for i, v := range got {
if v < tc.min-epsilon || v > tc.max+epsilon {
t.Errorf("ClampFloat64x4()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if diff := v - expected; diff < -epsilon || diff > epsilon {
t.Errorf("ClampFloat64x4()[%d] = %v, want %v (original: %v, diff: %v)", i, v, expected, original, diff)
}
}
})
}
}
// Test type aliases work correctly
func TestAVX2ClampTypeAlias(t *testing.T) {
requireAVX2(t)
input := []myInt32{-5, 0, 10, 15, 20}
min := myInt32(0)
max := myInt32(10)
got := ClampInt32x8(input, min, max)
for i, v := range got {
if v < min || v > max {
t.Errorf("ClampInt32x8()[%d] with type alias = %v, outside range [%v, %v]", i, v, min, max)
}
original := input[i]
expected := original
if expected < min {
expected = min
} else if expected > max {
expected = max
}
if v != expected {
t.Errorf("ClampInt32x8()[%d] with type alias = %v, want %v (original: %v)", i, v, expected, original)
}
}
}
func TestMinInt8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 32", make([]int8, 32)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MinInt8x32(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt8x32() = %v, want %v", got, want)
}
})
}
}
func TestMinInt16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 16", make([]int16, 16)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MinInt16x16(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt16x16() = %v, want %v", got, want)
}
})
}
}
func TestMinInt32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 8", make([]int32, 8)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := MinInt32x8(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt32x8() = %v, want %v", got, want)
}
})
}
}
func TestMinInt64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 4", make([]int64, 4)},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MinInt64x4(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt64x4() = %v, want %v", got, want)
}
})
}
}
func TestMinUint8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 32", make([]uint8, 32)},
{"large", make([]uint8, 1000)},
{"max values", []uint8{255, 100, 50}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MinUint8x32(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint8x32() = %v, want %v", got, want)
}
})
}
}
func TestMinUint16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint16, 16)},
{"large", make([]uint16, 1000)},
{"max values", []uint16{65535, 1000, 500}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MinUint16x16(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint16x16() = %v, want %v", got, want)
}
})
}
}
func TestMinUint32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint32, 8)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MinUint32x8(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint32x8() = %v, want %v", got, want)
}
})
}
}
func TestMinUint64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 4", make([]uint64, 4)},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MinUint64x4(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint64x4() = %v, want %v", got, want)
}
})
}
}
func TestMinFloat32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 8", make([]float32, 8)},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
{"zeros", []float32{0, 0, 0, 0}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MinFloat32x8(tc.input)
want := lo.Min(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MinFloat32x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMinFloat64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 4", []float64{1.0, 2.0, 3.0, 4.0}},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MinFloat64x4(tc.input)
want := lo.Min(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MinFloat64x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVX2MinTypeAlias(t *testing.T) {
requireAVX2(t)
input := []myInt32{5, 2, 8, 1, 9}
got := MinInt32x8(input)
want := myInt32(1)
if got != want {
t.Errorf("MinInt32x8() with type alias = %v, want %v", got, want)
}
}
func TestMaxInt8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 32", make([]int8, 32)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MaxInt8x32(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt8x32() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 16", make([]int16, 16)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MaxInt16x16(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt16x16() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 8", make([]int32, 8)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := MaxInt32x8(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt32x8() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 4", make([]int64, 4)},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MaxInt64x4(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt64x4() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 32", make([]uint8, 32)},
{"large", make([]uint8, 1000)},
{"max values", []uint8{255, 100, 50}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MaxUint8x32(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint8x32() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint16x16(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint16, 16)},
{"large", make([]uint16, 1000)},
{"max values", []uint16{65535, 1000, 500}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MaxUint16x16(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint16x16() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint32, 8)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MaxUint32x8(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint32x8() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint64x4(t *testing.T) {
requireAVX2(t)
requireAVX512(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 4", make([]uint64, 4)},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MaxUint64x4(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint64x4() = %v, want %v", got, want)
}
})
}
}
func TestMaxFloat32x8(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 8", make([]float32, 8)},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
{"zeros", []float32{0, 0, 0, 0}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MaxFloat32x8(tc.input)
want := lo.Max(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MaxFloat32x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMaxFloat64x4(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 4", []float64{1.0, 2.0, 3.0, 4.0}},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MaxFloat64x4(tc.input)
want := lo.Max(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MaxFloat64x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVX2MaxTypeAlias(t *testing.T) {
requireAVX2(t)
input := []myInt32{5, 2, 8, 1, 9}
got := MaxInt32x8(input)
want := myInt32(9)
if got != want {
t.Errorf("MaxInt32x8() with type alias = %v, want %v", got, want)
}
}
// SumBy tests
type avx2Item struct {
Value int8
Weight int8
Multiplier int8
}
func TestSumByInt8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []avx2Item
}{
{"empty", []avx2Item{}},
{"single", []avx2Item{{Value: 42}}},
{"small", []avx2Item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}},
{"exactly 32", make([]avx2Item, 32)},
{"large", make([]avx2Item, 1000)},
{"negative", []avx2Item{{Value: -1}, {Value: -2}, {Value: -3}, {Value: 4}, {Value: 5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int8(rand.IntN(256) - 128)
}
}
// Using Value field as the iteratee
got := SumByInt8x32(tc.input, func(i avx2Item) int8 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2Item, _ int) int8 { return i.Value }))
if got != want {
t.Errorf("SumByInt8x32() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt16x16(t *testing.T) {
requireAVX2(t)
type avx2ItemInt16 struct {
Value int16
}
testCases := []struct {
name string
input []avx2ItemInt16
}{
{"empty", []avx2ItemInt16{}},
{"single", []avx2ItemInt16{{Value: 42}}},
{"small", []avx2ItemInt16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]avx2ItemInt16, 16)},
{"large", make([]avx2ItemInt16, 1000)},
{"negative", []avx2ItemInt16{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int16(rand.IntN(65536) - 32768)
}
}
got := SumByInt16x16(tc.input, func(i avx2ItemInt16) int16 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemInt16, _ int) int16 { return i.Value }))
if got != want {
t.Errorf("SumByInt16x16() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt32x8(t *testing.T) {
requireAVX2(t)
type avx2ItemInt32 struct {
Value int32
}
testCases := []struct {
name string
input []avx2ItemInt32
}{
{"empty", []avx2ItemInt32{}},
{"single", []avx2ItemInt32{{Value: 42}}},
{"small", []avx2ItemInt32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]avx2ItemInt32, 8)},
{"large", make([]avx2ItemInt32, 1000)},
{"negative", []avx2ItemInt32{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int32()
}
}
got := SumByInt32x8(tc.input, func(i avx2ItemInt32) int32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemInt32, _ int) int32 { return i.Value }))
if got != want {
t.Errorf("SumByInt32x8() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt64x4(t *testing.T) {
requireAVX2(t)
type avx2ItemInt64 struct {
Value int64
}
testCases := []struct {
name string
input []avx2ItemInt64
}{
{"empty", []avx2ItemInt64{}},
{"single", []avx2ItemInt64{{Value: 42}}},
{"small", []avx2ItemInt64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 4", []avx2ItemInt64{{1}, {2}, {3}, {4}}},
{"large", make([]avx2ItemInt64, 1000)},
{"negative", []avx2ItemInt64{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int64()
}
}
got := SumByInt64x4(tc.input, func(i avx2ItemInt64) int64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemInt64, _ int) int64 { return i.Value }))
if got != want {
t.Errorf("SumByInt64x4() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint8x32(t *testing.T) {
requireAVX2(t)
type avx2ItemUint8 struct {
Value uint8
}
testCases := []struct {
name string
input []avx2ItemUint8
}{
{"empty", []avx2ItemUint8{}},
{"single", []avx2ItemUint8{{Value: 42}}},
{"small", []avx2ItemUint8{{1}, {2}, {3}, {4}, {5}}},
{"exactly 32", make([]avx2ItemUint8, 32)},
{"large", make([]avx2ItemUint8, 1000)},
{"max values", []avx2ItemUint8{{Value: 255}, {Value: 255}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint8(rand.IntN(256))
}
}
got := SumByUint8x32(tc.input, func(i avx2ItemUint8) uint8 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemUint8, _ int) uint8 { return i.Value }))
if got != want {
t.Errorf("SumByUint8x32() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint16x16(t *testing.T) {
requireAVX2(t)
type avx2ItemUint16 struct {
Value uint16
}
testCases := []struct {
name string
input []avx2ItemUint16
}{
{"empty", []avx2ItemUint16{}},
{"single", []avx2ItemUint16{{Value: 42}}},
{"small", []avx2ItemUint16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]avx2ItemUint16, 16)},
{"large", make([]avx2ItemUint16, 1000)},
{"max values", []avx2ItemUint16{{Value: 65535}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint16(rand.IntN(65536))
}
}
got := SumByUint16x16(tc.input, func(i avx2ItemUint16) uint16 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemUint16, _ int) uint16 { return i.Value }))
if got != want {
t.Errorf("SumByUint16x16() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint32x8(t *testing.T) {
requireAVX2(t)
type avx2ItemUint32 struct {
Value uint32
}
testCases := []struct {
name string
input []avx2ItemUint32
}{
{"empty", []avx2ItemUint32{}},
{"single", []avx2ItemUint32{{Value: 42}}},
{"small", []avx2ItemUint32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]avx2ItemUint32, 8)},
{"large", make([]avx2ItemUint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint32()
}
}
got := SumByUint32x8(tc.input, func(i avx2ItemUint32) uint32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemUint32, _ int) uint32 { return i.Value }))
if got != want {
t.Errorf("SumByUint32x8() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint64x4(t *testing.T) {
requireAVX2(t)
type avx2ItemUint64 struct {
Value uint64
}
testCases := []struct {
name string
input []avx2ItemUint64
}{
{"empty", []avx2ItemUint64{}},
{"single", []avx2ItemUint64{{Value: 42}}},
{"small", []avx2ItemUint64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 4", []avx2ItemUint64{{1}, {2}, {3}, {4}}},
{"large", make([]avx2ItemUint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint64()
}
}
got := SumByUint64x4(tc.input, func(i avx2ItemUint64) uint64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemUint64, _ int) uint64 { return i.Value }))
if got != want {
t.Errorf("SumByUint64x4() = %v, want %v", got, want)
}
})
}
}
func TestSumByFloat32x8(t *testing.T) {
requireAVX2(t)
type avx2ItemFloat32 struct {
Value float32
}
testCases := []struct {
name string
input []avx2ItemFloat32
}{
{"empty", []avx2ItemFloat32{}},
{"single", []avx2ItemFloat32{{Value: 42.5}}},
{"small", []avx2ItemFloat32{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 8", make([]avx2ItemFloat32, 8)},
{"large", make([]avx2ItemFloat32, 1000)},
{"negative", []avx2ItemFloat32{{-1.1}, {-2.2}, {3.3}, {4.4}}},
{"zeros", []avx2ItemFloat32{{0}, {0}, {0}, {0}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float32()
}
}
got := SumByFloat32x8(tc.input, func(i avx2ItemFloat32) float32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemFloat32, _ int) float32 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumByFloat32x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestSumByFloat64x4(t *testing.T) {
requireAVX2(t)
type avx2ItemFloat64 struct {
Value float64
}
testCases := []struct {
name string
input []avx2ItemFloat64
}{
{"empty", []avx2ItemFloat64{}},
{"single", []avx2ItemFloat64{{Value: 42.5}}},
{"small", []avx2ItemFloat64{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 4", []avx2ItemFloat64{{1.0}, {2.0}, {3.0}, {4.0}}},
{"large", make([]avx2ItemFloat64, 1000)},
{"negative", []avx2ItemFloat64{{-1.1}, {-2.2}, {3.3}, {4.4}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float64()
}
}
got := SumByFloat64x4(tc.input, func(i avx2ItemFloat64) float64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx2ItemFloat64, _ int) float64 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumByFloat64x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type alias works correctly for SumBy
func TestAVX2SumByTypeAlias(t *testing.T) {
requireAVX2(t)
type myAVX2Item struct {
Value myInt8
}
input := []myAVX2Item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}
got := SumByInt8x32(input, func(i myAVX2Item) myInt8 { return i.Value })
want := myInt8(15)
if got != want {
t.Errorf("SumByInt8x32() with type alias = %v, want %v", got, want)
}
}
// MeanBy tests
func TestMeanByInt8x32(t *testing.T) {
requireAVX2(t)
testCases := []struct {
name string
input []avx2Item
}{
{"empty", []avx2Item{}},
{"single", []avx2Item{{Value: 42}}},
{"small", []avx2Item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}},
{"exactly 32", make([]avx2Item, 32)},
{"large", make([]avx2Item, 1000)},
{"negative", []avx2Item{{Value: -1}, {Value: -2}, {Value: -3}, {Value: 4}, {Value: 5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int8(rand.IntN(256) - 128)
}
}
got := MeanByInt8x32(tc.input, func(i avx2Item) int8 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2Item, _ int) int8 { return i.Value }))
if got != want {
t.Errorf("MeanByInt8x32() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt16x16(t *testing.T) {
requireAVX2(t)
type avx2ItemInt16 struct {
Value int16
}
testCases := []struct {
name string
input []avx2ItemInt16
}{
{"empty", []avx2ItemInt16{}},
{"single", []avx2ItemInt16{{Value: 42}}},
{"small", []avx2ItemInt16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]avx2ItemInt16, 16)},
{"large", make([]avx2ItemInt16, 1000)},
{"negative", []avx2ItemInt16{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int16(rand.IntN(65536) - 32768)
}
}
got := MeanByInt16x16(tc.input, func(i avx2ItemInt16) int16 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemInt16, _ int) int16 { return i.Value }))
if got != want {
t.Errorf("MeanByInt16x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt32x8(t *testing.T) {
requireAVX2(t)
type avx2ItemInt32 struct {
Value int32
}
testCases := []struct {
name string
input []avx2ItemInt32
}{
{"empty", []avx2ItemInt32{}},
{"single", []avx2ItemInt32{{Value: 42}}},
{"small", []avx2ItemInt32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]avx2ItemInt32, 8)},
{"large", make([]avx2ItemInt32, 1000)},
{"negative", []avx2ItemInt32{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int32()
}
}
got := MeanByInt32x8(tc.input, func(i avx2ItemInt32) int32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemInt32, _ int) int32 { return i.Value }))
if got != want {
t.Errorf("MeanByInt32x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt64x4(t *testing.T) {
requireAVX2(t)
type avx2ItemInt64 struct {
Value int64
}
testCases := []struct {
name string
input []avx2ItemInt64
}{
{"empty", []avx2ItemInt64{}},
{"single", []avx2ItemInt64{{Value: 42}}},
{"small", []avx2ItemInt64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 4", []avx2ItemInt64{{1}, {2}, {3}, {4}}},
{"large", make([]avx2ItemInt64, 1000)},
{"negative", []avx2ItemInt64{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int64()
}
}
got := MeanByInt64x4(tc.input, func(i avx2ItemInt64) int64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemInt64, _ int) int64 { return i.Value }))
if got != want {
t.Errorf("MeanByInt64x4() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint8x32(t *testing.T) {
requireAVX2(t)
type avx2ItemUint8 struct {
Value uint8
}
testCases := []struct {
name string
input []avx2ItemUint8
}{
{"empty", []avx2ItemUint8{}},
{"single", []avx2ItemUint8{{Value: 42}}},
{"small", []avx2ItemUint8{{1}, {2}, {3}, {4}, {5}}},
{"exactly 32", make([]avx2ItemUint8, 32)},
{"large", make([]avx2ItemUint8, 1000)},
{"max values", []avx2ItemUint8{{Value: 255}, {Value: 255}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint8(rand.IntN(256))
}
}
got := MeanByUint8x32(tc.input, func(i avx2ItemUint8) uint8 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemUint8, _ int) uint8 { return i.Value }))
if got != want {
t.Errorf("MeanByUint8x32() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint16x16(t *testing.T) {
requireAVX2(t)
type avx2ItemUint16 struct {
Value uint16
}
testCases := []struct {
name string
input []avx2ItemUint16
}{
{"empty", []avx2ItemUint16{}},
{"single", []avx2ItemUint16{{Value: 42}}},
{"small", []avx2ItemUint16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]avx2ItemUint16, 16)},
{"large", make([]avx2ItemUint16, 1000)},
{"max values", []avx2ItemUint16{{Value: 65535}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint16(rand.IntN(65536))
}
}
got := MeanByUint16x16(tc.input, func(i avx2ItemUint16) uint16 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemUint16, _ int) uint16 { return i.Value }))
if got != want {
t.Errorf("MeanByUint16x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint32x8(t *testing.T) {
requireAVX2(t)
type avx2ItemUint32 struct {
Value uint32
}
testCases := []struct {
name string
input []avx2ItemUint32
}{
{"empty", []avx2ItemUint32{}},
{"single", []avx2ItemUint32{{Value: 42}}},
{"small", []avx2ItemUint32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]avx2ItemUint32, 8)},
{"large", make([]avx2ItemUint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint32()
}
}
got := MeanByUint32x8(tc.input, func(i avx2ItemUint32) uint32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemUint32, _ int) uint32 { return i.Value }))
if got != want {
t.Errorf("MeanByUint32x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint64x4(t *testing.T) {
requireAVX2(t)
type avx2ItemUint64 struct {
Value uint64
}
testCases := []struct {
name string
input []avx2ItemUint64
}{
{"empty", []avx2ItemUint64{}},
{"single", []avx2ItemUint64{{Value: 42}}},
{"small", []avx2ItemUint64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 4", []avx2ItemUint64{{1}, {2}, {3}, {4}}},
{"large", make([]avx2ItemUint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint64()
}
}
got := MeanByUint64x4(tc.input, func(i avx2ItemUint64) uint64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemUint64, _ int) uint64 { return i.Value }))
if got != want {
t.Errorf("MeanByUint64x4() = %v, want %v", got, want)
}
})
}
}
func TestMeanByFloat32x8(t *testing.T) {
requireAVX2(t)
type avx2ItemFloat32 struct {
Value float32
}
testCases := []struct {
name string
input []avx2ItemFloat32
}{
{"empty", []avx2ItemFloat32{}},
{"single", []avx2ItemFloat32{{Value: 42.5}}},
{"small", []avx2ItemFloat32{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 8", make([]avx2ItemFloat32, 8)},
{"large", make([]avx2ItemFloat32, 1000)},
{"negative", []avx2ItemFloat32{{-1.1}, {-2.2}, {3.3}, {4.4}}},
{"zeros", []avx2ItemFloat32{{0}, {0}, {0}, {0}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float32()
}
}
got := MeanByFloat32x8(tc.input, func(i avx2ItemFloat32) float32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemFloat32, _ int) float32 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanByFloat32x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMeanByFloat64x4(t *testing.T) {
requireAVX2(t)
type avx2ItemFloat64 struct {
Value float64
}
testCases := []struct {
name string
input []avx2ItemFloat64
}{
{"empty", []avx2ItemFloat64{}},
{"single", []avx2ItemFloat64{{Value: 42.5}}},
{"small", []avx2ItemFloat64{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 4", []avx2ItemFloat64{{1.0}, {2.0}, {3.0}, {4.0}}},
{"large", make([]avx2ItemFloat64, 1000)},
{"negative", []avx2ItemFloat64{{-1.1}, {-2.2}, {3.3}, {4.4}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float64()
}
}
got := MeanByFloat64x4(tc.input, func(i avx2ItemFloat64) float64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx2ItemFloat64, _ int) float64 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanByFloat64x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type alias works correctly for MeanBy
func TestAVX2MeanByTypeAlias(t *testing.T) {
requireAVX2(t)
type myAVX2Item struct {
Value myInt8
}
input := []myAVX2Item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}
got := MeanByInt8x32(input, func(i myAVX2Item) myInt8 { return i.Value })
want := myInt8(3)
if got != want {
t.Errorf("MeanByInt8x32() with type alias = %v, want %v", got, want)
}
}
================================================
FILE: exp/simd/math_avx512.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"simd/archsimd"
"unsafe"
"github.com/samber/lo"
)
// AVX-512 (512-bit) SIMD sum functions - 64/32/16/8 lanes
// SumInt8x64 sums a slice of int8 using AVX-512 SIMD (Int8x64, 64 lanes).
// Overflow: The accumulation is performed using int8, which can overflow for large collections.
// If the sum exceeds the int8 range (-128 to 127), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumInt8x64[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes64
base := unsafeSliceInt8(collection, length)
var acc archsimd.Int8x64
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x64Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int8
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt16x32 sums a slice of int16 using AVX-512 SIMD (Int16x32, 32 lanes).
// Overflow: The accumulation is performed using int16, which can overflow for large collections.
// If the sum exceeds the int16 range (-32768 to 32767), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumInt16x32[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceInt16(collection, length)
var acc archsimd.Int16x32
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x32Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int16
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt32x16 sums a slice of int32 using AVX-512 SIMD (Int32x16, 16 lanes).
// Overflow: The accumulation is performed using int32, which can overflow for very large collections.
// If the sum exceeds the int32 range (-2147483648 to 2147483647), the result will wrap around silently.
// For collections that may overflow, consider using SumInt64x8 or handle overflow detection externally.
func SumInt32x16[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt32(collection, length)
var acc archsimd.Int32x16
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x16Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumInt64x8 sums a slice of int64 using AVX-512 SIMD (Int64x8, 8 lanes).
// Overflow: The accumulation is performed using int64, which can overflow for extremely large collections.
// If the sum exceeds the int64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
func SumInt64x8[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt64(collection, length)
var acc archsimd.Int64x8
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x8Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]int64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint8x64 sums a slice of uint8 using AVX-512 SIMD (Uint8x64, 64 lanes).
// Overflow: The accumulation is performed using uint8, which can overflow for large collections.
// If the sum exceeds the uint8 range (0 to 255), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumUint8x64[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes64
base := unsafeSliceUint8(collection, length)
var acc archsimd.Uint8x64
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x64Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint8
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint16x32 sums a slice of uint16 using AVX-512 SIMD (Uint16x32, 32 lanes).
// Overflow: The accumulation is performed using uint16, which can overflow for large collections.
// If the sum exceeds the uint16 range (0 to 65535), the result will wrap around silently.
// For collections that may overflow, consider using a wider type or handle overflow detection externally.
func SumUint16x32[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceUint16(collection, length)
var acc archsimd.Uint16x32
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x32Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint16
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint32x16 sums a slice of uint32 using AVX-512 SIMD (Uint32x16, 16 lanes).
// Overflow: The accumulation is performed using uint32, which can overflow for very large collections.
// If the sum exceeds the uint32 range (0 to 4294967295), the result will wrap around silently.
// For collections that may overflow, consider using SumUint64x8 or handle overflow detection externally.
func SumUint32x16[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint32(collection, length)
var acc archsimd.Uint32x16
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x16Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumUint64x8 sums a slice of uint64 using AVX-512 SIMD (Uint64x8, 8 lanes).
// Overflow: The accumulation is performed using uint64, which can overflow for extremely large collections.
// If the sum exceeds the uint64 range, the result will wrap around silently.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Int).
func SumUint64x8[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint64(collection, length)
var acc archsimd.Uint64x8
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x8Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]uint64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumFloat32x16 sums a slice of float32 using AVX-512 SIMD (Float32x16, 16 lanes).
// Overflow: The accumulation is performed using float32. Overflow will result in +/-Inf rather than wrapping.
// For collections requiring high precision or large sums, consider using SumFloat64x8.
func SumFloat32x16[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceFloat32(collection, length)
var acc archsimd.Float32x16
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x16Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]float32
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// SumFloat64x8 sums a slice of float64 using AVX-512 SIMD (Float64x8, 8 lanes).
// Overflow: The accumulation is performed using float64. Overflow will result in +/-Inf rather than wrapping.
// For collections that may overflow, handle overflow detection externally (e.g., using big.Float).
func SumFloat64x8[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceFloat64(collection, length)
var acc archsimd.Float64x8
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x8Slice(base[i : i+lanes])
acc = acc.Add(v)
}
var buf [lanes]float64
acc.Store(&buf)
var sum T
for k := uint(0); k < lanes; k++ {
sum += T(buf[k])
}
for ; i < length; i++ {
sum += collection[i]
}
return sum
}
// MeanInt8x64 calculates the mean of a slice of int8 using AVX-512 SIMD
func MeanInt8x64[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumInt8x64(collection)
return sum / T(length)
}
// MeanInt16x32 calculates the mean of a slice of int16 using AVX-512 SIMD
func MeanInt16x32[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumInt16x32(collection)
return sum / T(length)
}
// MeanInt32x16 calculates the mean of a slice of int32 using AVX-512 SIMD
func MeanInt32x16[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumInt32x16(collection)
return sum / T(length)
}
// MeanInt64x8 calculates the mean of a slice of int64 using AVX-512 SIMD
func MeanInt64x8[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumInt64x8(collection)
return sum / T(length)
}
// MeanUint8x64 calculates the mean of a slice of uint8 using AVX-512 SIMD
func MeanUint8x64[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumUint8x64(collection)
return sum / T(length)
}
// MeanUint16x32 calculates the mean of a slice of uint16 using AVX-512 SIMD
func MeanUint16x32[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumUint16x32(collection)
return sum / T(length)
}
// MeanUint32x16 calculates the mean of a slice of uint32 using AVX-512 SIMD
func MeanUint32x16[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumUint32x16(collection)
return sum / T(length)
}
// MeanUint64x8 calculates the mean of a slice of uint64 using AVX-512 SIMD
func MeanUint64x8[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumUint64x8(collection)
return sum / T(length)
}
// MeanFloat32x16 calculates the mean of a slice of float32 using AVX-512 SIMD
func MeanFloat32x16[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumFloat32x16(collection)
return sum / T(length)
}
// MeanFloat64x8 calculates the mean of a slice of float64 using AVX-512 SIMD
func MeanFloat64x8[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
sum := SumFloat64x8(collection)
return sum / T(length)
}
// ClampInt8x64 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampInt8x64[T ~int8, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes64
minVec := archsimd.BroadcastInt8x64(int8(min))
maxVec := archsimd.BroadcastInt8x64(int8(max))
base := unsafeSliceInt8(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadInt8x64Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int8)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt16x32 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampInt16x32[T ~int16, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes32
minVec := archsimd.BroadcastInt16x32(int16(min))
maxVec := archsimd.BroadcastInt16x32(int16(max))
base := unsafeSliceInt16(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadInt16x32Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int16)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt32x16 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampInt32x16[T ~int32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes16
minVec := archsimd.BroadcastInt32x16(int32(min))
maxVec := archsimd.BroadcastInt32x16(int32(max))
base := unsafeSliceInt32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadInt32x16Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt64x2 clamps each element in collection between min and max values using AVX-512 SIMD.
// Int64x2 Min/Max operations in archsimd require AVX-512 (VPMAXSQ/VPMINSQ).
func ClampInt64x2[T ~int64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes2
base := unsafeSliceInt64(collection, length)
minVec := archsimd.BroadcastInt64x2(int64(min))
maxVec := archsimd.BroadcastInt64x2(int64(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x2Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint64x2 clamps each element in collection between min and max values using AVX-512 SIMD.
// Uint64x2 Min/Max operations in archsimd require AVX-512.
func ClampUint64x2[T ~uint64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes2
base := unsafeSliceUint64(collection, length)
minVec := archsimd.BroadcastUint64x2(uint64(min))
maxVec := archsimd.BroadcastUint64x2(uint64(max))
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x2Slice(base[i : i+lanes])
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampInt64x8 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampInt64x8[T ~int64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes8
minVec := archsimd.BroadcastInt64x8(int64(min))
maxVec := archsimd.BroadcastInt64x8(int64(max))
base := unsafeSliceInt64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadInt64x8Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]int64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint8x64 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampUint8x64[T ~uint8, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes64
minVec := archsimd.BroadcastUint8x64(uint8(min))
maxVec := archsimd.BroadcastUint8x64(uint8(max))
base := unsafeSliceUint8(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadUint8x64Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint8)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint16x32 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampUint16x32[T ~uint16, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes32
minVec := archsimd.BroadcastUint16x32(uint16(min))
maxVec := archsimd.BroadcastUint16x32(uint16(max))
base := unsafeSliceUint16(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadUint16x32Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint16)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint32x16 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampUint32x16[T ~uint32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes16
minVec := archsimd.BroadcastUint32x16(uint32(min))
maxVec := archsimd.BroadcastUint32x16(uint32(max))
base := unsafeSliceUint32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadUint32x16Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampUint64x8 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampUint64x8[T ~uint64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes8
minVec := archsimd.BroadcastUint64x8(uint64(min))
maxVec := archsimd.BroadcastUint64x8(uint64(max))
base := unsafeSliceUint64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadUint64x8Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]uint64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampFloat32x16 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampFloat32x16[T ~float32, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes16
minVec := archsimd.BroadcastFloat32x16(float32(min))
maxVec := archsimd.BroadcastFloat32x16(float32(max))
base := unsafeSliceFloat32(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadFloat32x16Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]float32)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// ClampFloat64x8 clamps each element in collection between min and max values using AVX-512 SIMD
func ClampFloat64x8[T ~float64, Slice ~[]T](collection Slice, min, max T) Slice {
length := uint(len(collection))
if length == 0 {
return collection
}
result := make(Slice, length)
const lanes = simdLanes8
minVec := archsimd.BroadcastFloat64x8(float64(min))
maxVec := archsimd.BroadcastFloat64x8(float64(max))
base := unsafeSliceFloat64(collection, length)
i := uint(0)
for ; i+lanes <= length; i += lanes {
c := base[i : i+lanes]
v := archsimd.LoadFloat64x8Slice(c)
clamped := v.Max(minVec).Min(maxVec)
// bearer:disable go_gosec_unsafe_unsafe
clamped.Store((*[lanes]float64)(unsafe.Pointer(&result[i])))
}
for ; i < length; i++ {
val := collection[i]
if val < min {
val = min
} else if val > max {
val = max
}
result[i] = val
}
return result
}
// MinInt8x64 finds the minimum value in a collection of int8 using AVX-512 SIMD
func MinInt8x64[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes64
base := unsafeSliceInt8(collection, length)
var minVec archsimd.Int8x64
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x64Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int8
if firstInitialized {
var buf [lanes]int8
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39],
buf[40], buf[41], buf[42], buf[43], buf[44], buf[45], buf[46], buf[47],
buf[48], buf[49], buf[50], buf[51], buf[52], buf[53], buf[54], buf[55],
buf[56], buf[57], buf[58], buf[59], buf[60], buf[61], buf[62], buf[63],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int8(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt16x32 finds the minimum value in a collection of int16 using AVX-512 SIMD
func MinInt16x32[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceInt16(collection, length)
var minVec archsimd.Int16x32
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x32Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int16
if firstInitialized {
var buf [lanes]int16
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int16(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt32x16 finds the minimum value in a collection of int32 using AVX-512 SIMD
func MinInt32x16[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt32(collection, length)
var minVec archsimd.Int32x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x16Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int32
if firstInitialized {
var buf [lanes]int32
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt64x2 finds the minimum value in a collection of int64 using AVX-512 SIMD.
// Int64x2 Min operations in archsimd require AVX-512.
func MinInt64x2[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceInt64(collection, length)
var minVec archsimd.Int64x2
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x2Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int64
if firstInitialized {
var buf [lanes]int64
minVec.Store(&buf)
minVal = min(buf[0], buf[1])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint64x2 finds the minimum value in a collection of uint64 using AVX-512 SIMD.
// Uint64x2 Min operations in archsimd require AVX-512.
func MinUint64x2[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceUint64(collection, length)
var minVec archsimd.Uint64x2
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x2Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint64
if firstInitialized {
var buf [lanes]uint64
minVec.Store(&buf)
minVal = min(buf[0], buf[1])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinInt64x8 finds the minimum value in a collection of int64 using AVX-512 SIMD
func MinInt64x8[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt64(collection, length)
var minVec archsimd.Int64x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x8Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal int64
if firstInitialized {
var buf [lanes]int64
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = int64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint8x64 finds the minimum value in a collection of uint8 using AVX-512 SIMD
func MinUint8x64[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes64
base := unsafeSliceUint8(collection, length)
var minVec archsimd.Uint8x64
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x64Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint8
if firstInitialized {
var buf [lanes]uint8
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39],
buf[40], buf[41], buf[42], buf[43], buf[44], buf[45], buf[46], buf[47],
buf[48], buf[49], buf[50], buf[51], buf[52], buf[53], buf[54], buf[55],
buf[56], buf[57], buf[58], buf[59], buf[60], buf[61], buf[62], buf[63],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint8(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint16x32 finds the minimum value in a collection of uint16 using AVX-512 SIMD
func MinUint16x32[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceUint16(collection, length)
var minVec archsimd.Uint16x32
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x32Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint16
if firstInitialized {
var buf [lanes]uint16
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint16(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint32x16 finds the minimum value in a collection of uint32 using AVX-512 SIMD
func MinUint32x16[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint32(collection, length)
var minVec archsimd.Uint32x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x16Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint32
if firstInitialized {
var buf [lanes]uint32
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinUint64x8 finds the minimum value in a collection of uint64 using AVX-512 SIMD
func MinUint64x8[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint64(collection, length)
var minVec archsimd.Uint64x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x8Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal uint64
if firstInitialized {
var buf [lanes]uint64
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = uint64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinFloat32x16 finds the minimum value in a collection of float32 using AVX-512 SIMD
func MinFloat32x16[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceFloat32(collection, length)
var minVec archsimd.Float32x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x16Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal float32
if firstInitialized {
var buf [lanes]float32
minVec.Store(&buf)
minVal = min(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = float32(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MinFloat64x8 finds the minimum value in a collection of float64 using AVX-512 SIMD
func MinFloat64x8[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceFloat64(collection, length)
var minVec archsimd.Float64x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x8Slice(base[i : i+lanes])
if !firstInitialized {
minVec = v
firstInitialized = true
} else {
minVec = minVec.Min(v)
}
}
// Find minimum in the vector (only if we processed any vectors)
var minVal float64
if firstInitialized {
var buf [lanes]float64
minVec.Store(&buf)
minVal = min(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] < T(minVal) {
minVal = float64(collection[i])
firstInitialized = true
}
}
return T(minVal)
}
// MaxInt8x64 finds the maximum value in a collection of int8 using AVX-512 SIMD
func MaxInt8x64[T ~int8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes64
base := unsafeSliceInt8(collection, length)
var maxVec archsimd.Int8x64
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt8x64Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int8
if firstInitialized {
var buf [lanes]int8
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39],
buf[40], buf[41], buf[42], buf[43], buf[44], buf[45], buf[46], buf[47],
buf[48], buf[49], buf[50], buf[51], buf[52], buf[53], buf[54], buf[55],
buf[56], buf[57], buf[58], buf[59], buf[60], buf[61], buf[62], buf[63],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int8(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt16x32 finds the maximum value in a collection of int16 using AVX-512 SIMD
func MaxInt16x32[T ~int16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceInt16(collection, length)
var maxVec archsimd.Int16x32
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt16x32Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int16
if firstInitialized {
var buf [lanes]int16
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int16(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt32x16 finds the maximum value in a collection of int32 using AVX-512 SIMD
func MaxInt32x16[T ~int32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceInt32(collection, length)
var maxVec archsimd.Int32x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt32x16Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int32
if firstInitialized {
var buf [lanes]int32
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt64x2 finds the maximum value in a collection of int64 using AVX-512 SIMD.
// Int64x2 Max operations in archsimd require AVX-512.
func MaxInt64x2[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceInt64(collection, length)
var maxVec archsimd.Int64x2
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x2Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int64
if firstInitialized {
var buf [lanes]int64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint64x2 finds the maximum value in a collection of uint64 using AVX-512 SIMD.
// Uint64x2 Max operations in archsimd require AVX-512.
func MaxUint64x2[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes2
base := unsafeSliceUint64(collection, length)
var maxVec archsimd.Uint64x2
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x2Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint64
if firstInitialized {
var buf [lanes]uint64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxInt64x8 finds the maximum value in a collection of int64 using AVX-512 SIMD
func MaxInt64x8[T ~int64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceInt64(collection, length)
var maxVec archsimd.Int64x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadInt64x8Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal int64
if firstInitialized {
var buf [lanes]int64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = int64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint8x64 finds the maximum value in a collection of uint8 using AVX-512 SIMD
func MaxUint8x64[T ~uint8](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes64
base := unsafeSliceUint8(collection, length)
var maxVec archsimd.Uint8x64
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint8x64Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint8
if firstInitialized {
var buf [lanes]uint8
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39],
buf[40], buf[41], buf[42], buf[43], buf[44], buf[45], buf[46], buf[47],
buf[48], buf[49], buf[50], buf[51], buf[52], buf[53], buf[54], buf[55],
buf[56], buf[57], buf[58], buf[59], buf[60], buf[61], buf[62], buf[63],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint8(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint16x32 finds the maximum value in a collection of uint16 using AVX-512 SIMD
func MaxUint16x32[T ~uint16](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes32
base := unsafeSliceUint16(collection, length)
var maxVec archsimd.Uint16x32
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint16x32Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint16
if firstInitialized {
var buf [lanes]uint16
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
buf[16], buf[17], buf[18], buf[19], buf[20], buf[21], buf[22], buf[23],
buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint16(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint32x16 finds the maximum value in a collection of uint32 using AVX-512 SIMD
func MaxUint32x16[T ~uint32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceUint32(collection, length)
var maxVec archsimd.Uint32x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint32x16Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint32
if firstInitialized {
var buf [lanes]uint32
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxUint64x8 finds the maximum value in a collection of uint64 using AVX-512 SIMD
func MaxUint64x8[T ~uint64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceUint64(collection, length)
var maxVec archsimd.Uint64x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadUint64x8Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal uint64
if firstInitialized {
var buf [lanes]uint64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = uint64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxFloat32x16 finds the maximum value in a collection of float32 using AVX-512 SIMD
func MaxFloat32x16[T ~float32](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes16
base := unsafeSliceFloat32(collection, length)
var maxVec archsimd.Float32x16
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat32x16Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal float32
if firstInitialized {
var buf [lanes]float32
maxVec.Store(&buf)
maxVal = max(
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15],
)
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = float32(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// MaxFloat64x8 finds the maximum value in a collection of float64 using AVX-512 SIMD
func MaxFloat64x8[T ~float64](collection []T) T {
length := uint(len(collection))
if length == 0 {
return 0
}
const lanes = simdLanes8
base := unsafeSliceFloat64(collection, length)
var maxVec archsimd.Float64x8
firstInitialized := false
i := uint(0)
for ; i+lanes <= length; i += lanes {
v := archsimd.LoadFloat64x8Slice(base[i : i+lanes])
if !firstInitialized {
maxVec = v
firstInitialized = true
} else {
maxVec = maxVec.Max(v)
}
}
// Find maximum in the vector (only if we processed any vectors)
var maxVal float64
if firstInitialized {
var buf [lanes]float64
maxVec.Store(&buf)
maxVal = max(buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])
}
// Handle remaining elements
for ; i < length; i++ {
if !firstInitialized || collection[i] > T(maxVal) {
maxVal = float64(collection[i])
firstInitialized = true
}
}
return T(maxVal)
}
// AVX-512 (512-bit) SIMD sumBy functions - 64/32/16/8 lanes
// These implementations use lo.Map to apply the iteratee, then chain with SIMD sum functions.
// SumByInt8x64 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByInt8x64[T any, R ~int8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt8x64(mapped)
}
// SumByInt16x32 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByInt16x32[T any, R ~int16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt16x32(mapped)
}
// SumByInt32x16 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByInt32x16[T any, R ~int32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt32x16(mapped)
}
// SumByInt64x8 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByInt64x8[T any, R ~int64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumInt64x8(mapped)
}
// SumByUint8x64 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByUint8x64[T any, R ~uint8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint8x64(mapped)
}
// SumByUint16x32 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByUint16x32[T any, R ~uint16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint16x32(mapped)
}
// SumByUint32x16 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByUint32x16[T any, R ~uint32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint32x16(mapped)
}
// SumByUint64x8 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByUint64x8[T any, R ~uint64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumUint64x8(mapped)
}
// SumByFloat32x16 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByFloat32x16[T any, R ~float32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumFloat32x16(mapped)
}
// SumByFloat64x8 sums the values extracted by iteratee from a slice using AVX-512 SIMD.
func SumByFloat64x8[T any, R ~float64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return SumFloat64x8(mapped)
}
// AVX-512 (512-bit) SIMD meanBy functions - 64/32/16/8 lanes
// These implementations use lo.Map to apply the iteratee, then chain with SIMD mean functions.
// MeanByInt8x64 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByInt8x64[T any, R ~int8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt8x64(mapped)
}
// MeanByInt16x32 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByInt16x32[T any, R ~int16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt16x32(mapped)
}
// MeanByInt32x16 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByInt32x16[T any, R ~int32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt32x16(mapped)
}
// MeanByInt64x8 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByInt64x8[T any, R ~int64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanInt64x8(mapped)
}
// MeanByUint8x64 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByUint8x64[T any, R ~uint8](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint8x64(mapped)
}
// MeanByUint16x32 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByUint16x32[T any, R ~uint16](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint16x32(mapped)
}
// MeanByUint32x16 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByUint32x16[T any, R ~uint32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint32x16(mapped)
}
// MeanByUint64x8 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByUint64x8[T any, R ~uint64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanUint64x8(mapped)
}
// MeanByFloat32x16 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByFloat32x16[T any, R ~float32](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanFloat32x16(mapped)
}
// MeanByFloat64x8 calculates the mean of values extracted by iteratee from a slice using AVX-512 SIMD.
func MeanByFloat64x8[T any, R ~float64](collection []T, iteratee func(item T) R) R {
mapped := lo.Map(collection, func(item T, _ int) R { return iteratee(item) })
return MeanFloat64x8(mapped)
}
================================================
FILE: exp/simd/math_avx512_test.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"math/rand/v2"
"testing"
"github.com/samber/lo"
)
func TestSumInt8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 64", make([]int8, 64)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := SumInt8x64(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt8x64() = %v, want %v", got, want)
}
})
}
}
func TestSumInt16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 32", make([]int16, 32)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := SumInt16x32(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt16x32() = %v, want %v", got, want)
}
})
}
}
func TestSumInt32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 16", make([]int32, 16)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int32(rand.Int32())
}
}
got := SumInt32x16(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt32x16() = %v, want %v", got, want)
}
})
}
}
func TestSumInt64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 8", make([]int64, 8)},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := SumInt64x8(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt64x8() = %v, want %v", got, want)
}
})
}
}
func TestSumUint8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 64", make([]uint8, 64)},
{"large", make([]uint8, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := SumUint8x64(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint8x64() = %v, want %v", got, want)
}
})
}
}
func TestSumUint16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 32", make([]uint16, 32)},
{"large", make([]uint16, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := SumUint16x32(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint16x32() = %v, want %v", got, want)
}
})
}
}
func TestSumUint32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint32, 16)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := SumUint32x16(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint32x16() = %v, want %v", got, want)
}
})
}
}
func TestSumUint64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint64, 8)},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := SumUint64x8(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint64x8() = %v, want %v", got, want)
}
})
}
}
func TestSumFloat32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 16", make([]float32, 16)},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := SumFloat32x16(tc.input)
want := lo.Sum(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumFloat32x16() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestSumFloat64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 8", make([]float64, 8)},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := SumFloat64x8(tc.input)
want := lo.Sum(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumFloat64x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVX512TypeAlias(t *testing.T) {
requireAVX512(t)
input := []myInt32{1, 2, 3, 4, 5}
got := SumInt32x16(input)
want := lo.Sum(input)
if got != want {
t.Errorf("SumInt32x16() with type alias = %v, want %v", got, want)
}
}
func TestMeanInt8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 64", make([]int8, 64)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MeanInt8x64(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt8x64() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 32", make([]int16, 32)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MeanInt16x32(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt16x32() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 16", make([]int32, 16)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int32(rand.Int32())
}
}
got := MeanInt32x16(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt32x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 8", make([]int64, 8)},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MeanInt64x8(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt64x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 64", make([]uint8, 64)},
{"large", make([]uint8, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MeanUint8x64(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint8x64() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 32", make([]uint16, 32)},
{"large", make([]uint16, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MeanUint16x32(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint16x32() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint32, 16)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MeanUint32x16(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint32x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint64, 8)},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MeanUint64x8(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint64x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanFloat32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 16", make([]float32, 16)},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MeanFloat32x16(tc.input)
want := lo.Mean(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanFloat32x16() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMeanFloat64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 8", make([]float64, 8)},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MeanFloat64x8(tc.input)
want := lo.Mean(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanFloat64x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVX512MeanTypeAlias(t *testing.T) {
requireAVX512(t)
input := []myInt32{1, 2, 3, 4, 5}
got := MeanInt32x16(input)
want := lo.Mean(input)
if got != want {
t.Errorf("MeanInt32x16() with type alias = %v, want %v", got, want)
}
}
func TestClampInt8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int8
min int8
max int8
}{
{"empty", []int8{}, -10, 10},
{"single", []int8{42}, -10, 10},
{"small", []int8{1, 2, 3, 4, 5}, 2, 4},
{"exactly 64", make([]int8, 64), 5, 10},
{"large", make([]int8, 1000), -5, 5},
{"all below min", []int8{-10, -20, -30}, -5, 10},
{"all above max", []int8{20, 30, 40}, -10, 10},
{"already clamped", []int8{5, 6, 7}, 5, 10},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := ClampInt8x64(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt8x64() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt8x64()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt8x64()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int16
min int16
max int16
}{
{"empty", []int16{}, -100, 100},
{"single", []int16{42}, -10, 10},
{"small", []int16{1, 2, 3, 4, 5}, 2, 4},
{"exactly 32", make([]int16, 32), 50, 100},
{"large", make([]int16, 1000), -50, 50},
{"all below min", []int16{-100, -200, -300}, -50, 100},
{"all above max", []int16{200, 300, 400}, -100, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := ClampInt16x32(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt16x32() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt16x32()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt16x32()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int32
min int32
max int32
}{
{"empty", []int32{}, -100, 100},
{"single", []int32{42}, -10, 10},
{"small", []int32{1, 2, 3, 4, 5}, 2, 4},
{"exactly 16", make([]int32, 16), 50, 100},
{"large", make([]int32, 1000), -50, 50},
{"negative range", []int32{-100, -50, 0, 50, 100}, -30, -10},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := ClampInt32x16(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt32x16() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt32x16()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt32x16()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt64x2(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int64
min int64
max int64
}{
{"empty", []int64{}, -100, 100},
{"single", []int64{42}, -10, 10},
{"small", []int64{1, 2, 3, 4, 5}, 2, 4},
{"exactly 2", []int64{-100, 200}, -50, 50},
{"large", make([]int64, 1000), -50, 50},
{"all below min", []int64{-1000, -2000, -3000}, -500, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := ClampInt64x2(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt64x2() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt64x2()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt64x2()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int64
min int64
max int64
}{
{"empty", []int64{}, -100, 100},
{"single", []int64{42}, -10, 10},
{"small", []int64{1, 2, 3, 4, 5}, 2, 4},
{"exactly 8", make([]int64, 8), 50, 100},
{"large", make([]int64, 1000), -50, 50},
{"all below min", []int64{-1000, -2000, -3000}, -500, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := ClampInt64x8(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt64x8() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt64x8()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt64x8()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint8
min uint8
max uint8
}{
{"empty", []uint8{}, 10, 100},
{"single", []uint8{42}, 10, 100},
{"small", []uint8{1, 2, 3, 4, 5}, 2, 4},
{"exactly 64", make([]uint8, 64), 50, 100},
{"large", make([]uint8, 1000), 50, 200},
{"all below min", []uint8{1, 2, 3}, 10, 100},
{"all above max", []uint8{200, 225, 250}, 50, 150},
{"max values", []uint8{255, 255, 255}, 100, 200},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := ClampUint8x64(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint8x64() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint8x64()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint8x64()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint16
min uint16
max uint16
}{
{"empty", []uint16{}, 100, 1000},
{"single", []uint16{42}, 10, 100},
{"small", []uint16{1, 2, 3, 4, 5}, 2, 4},
{"exactly 32", make([]uint16, 32), 500, 1000},
{"large", make([]uint16, 1000), 500, 5000},
{"all below min", []uint16{1, 2, 3}, 10, 100},
{"all above max", []uint16{2000, 3000, 4000}, 100, 1000},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := ClampUint16x32(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint16x32() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint16x32()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint16x32()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint32
min uint32
max uint32
}{
{"empty", []uint32{}, 100, 1000},
{"single", []uint32{42}, 10, 100},
{"small", []uint32{1, 2, 3, 4, 5}, 2, 4},
{"exactly 16", make([]uint32, 16), 500, 1000},
{"large", make([]uint32, 1000), 500, 5000},
{"all below min", []uint32{1, 2, 3}, 10, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := ClampUint32x16(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint32x16() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint32x16()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint32x16()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint64x2(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint64
min uint64
max uint64
}{
{"empty", []uint64{}, 100, 1000},
{"single", []uint64{42}, 10, 100},
{"small", []uint64{1, 2, 3, 4, 5}, 2, 4},
{"exactly 2", []uint64{50, 2000}, 100, 1000},
{"large", make([]uint64, 1000), 500, 5000},
{"all below min", []uint64{1, 2, 3}, 10, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := ClampUint64x2(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint64x2() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint64x2()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint64x2()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint64
min uint64
max uint64
}{
{"empty", []uint64{}, 100, 1000},
{"single", []uint64{42}, 10, 100},
{"small", []uint64{1, 2, 3, 4, 5}, 2, 4},
{"exactly 8", make([]uint64, 8), 500, 1000},
{"large", make([]uint64, 1000), 500, 5000},
{"all below min", []uint64{1, 2, 3}, 10, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := ClampUint64x8(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint64x8() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint64x8()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint64x8()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampFloat32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float32
min float32
max float32
}{
{"empty", []float32{}, -10.0, 10.0},
{"single", []float32{42.5}, -10.0, 10.0},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 2.0, 4.0},
{"exactly 16", make([]float32, 16), -5.0, 10.0},
{"large", make([]float32, 1000), -5.0, 5.0},
{"negative range", []float32{-10.0, -5.0, 0.0, 5.0, 10.0}, -3.0, -1.0},
{"all below min", []float32{-20.0, -30.0, -40.0}, -10.0, 10.0},
{"all above max", []float32{20.0, 30.0, 40.0}, -10.0, 10.0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()*200 - 100
}
}
got := ClampFloat32x16(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampFloat32x16() returned length %d, want %d", len(got), len(tc.input))
}
const epsilon = 1e-3
for i, v := range got {
if v < tc.min-epsilon || v > tc.max+epsilon {
t.Errorf("ClampFloat32x16()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if diff := v - expected; diff < -epsilon || diff > epsilon {
t.Errorf("ClampFloat32x16()[%d] = %v, want %v (original: %v, diff: %v)", i, v, expected, original, diff)
}
}
})
}
}
func TestClampFloat64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float64
min float64
max float64
}{
{"empty", []float64{}, -10.0, 10.0},
{"single", []float64{42.5}, -10.0, 10.0},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 2.0, 4.0},
{"exactly 8", make([]float64, 8), -5.0, 10.0},
{"large", make([]float64, 1000), -5.0, 5.0},
{"negative range", []float64{-10.0, -5.0, 0.0, 5.0, 10.0}, -3.0, -1.0},
{"all below min", []float64{-20.0, -30.0, -40.0}, -10.0, 10.0},
{"all above max", []float64{20.0, 30.0, 40.0}, -10.0, 10.0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()*200 - 100
}
}
got := ClampFloat64x8(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampFloat64x8() returned length %d, want %d", len(got), len(tc.input))
}
const epsilon = 1e-3
for i, v := range got {
if v < tc.min-epsilon || v > tc.max+epsilon {
t.Errorf("ClampFloat64x8()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if diff := v - expected; diff < -epsilon || diff > epsilon {
t.Errorf("ClampFloat64x8()[%d] = %v, want %v (original: %v, diff: %v)", i, v, expected, original, diff)
}
}
})
}
}
// Test type aliases work correctly
func TestAVX512ClampTypeAlias(t *testing.T) {
requireAVX512(t)
input := []myInt32{-5, 0, 10, 15, 20}
min := myInt32(0)
max := myInt32(10)
got := ClampInt32x16(input, min, max)
for i, v := range got {
if v < min || v > max {
t.Errorf("ClampInt32x16()[%d] with type alias = %v, outside range [%v, %v]", i, v, min, max)
}
original := input[i]
expected := original
if expected < min {
expected = min
} else if expected > max {
expected = max
}
if v != expected {
t.Errorf("ClampInt32x16()[%d] with type alias = %v, want %v (original: %v)", i, v, expected, original)
}
}
}
func TestMinInt8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 64", make([]int8, 64)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MinInt8x64(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt8x64() = %v, want %v", got, want)
}
})
}
}
func TestMinInt16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 32", make([]int16, 32)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MinInt16x32(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt16x32() = %v, want %v", got, want)
}
})
}
}
func TestMinInt32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 16", make([]int32, 16)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := MinInt32x16(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt32x16() = %v, want %v", got, want)
}
})
}
}
func TestMinInt64x2(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 2", []int64{1, 2}},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MinInt64x2(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt64x2() = %v, want %v", got, want)
}
})
}
}
func TestMinInt64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 8", make([]int64, 8)},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MinInt64x8(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt64x8() = %v, want %v", got, want)
}
})
}
}
func TestMinUint8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 64", make([]uint8, 64)},
{"large", make([]uint8, 1000)},
{"max values", []uint8{255, 100, 50}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MinUint8x64(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint8x64() = %v, want %v", got, want)
}
})
}
}
func TestMinUint16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 32", make([]uint16, 32)},
{"large", make([]uint16, 1000)},
{"max values", []uint16{65535, 1000, 500}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MinUint16x32(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint16x32() = %v, want %v", got, want)
}
})
}
}
func TestMinUint32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint32, 16)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MinUint32x16(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint32x16() = %v, want %v", got, want)
}
})
}
}
func TestMinUint64x2(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 2", []uint64{1, 2}},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MinUint64x2(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint64x2() = %v, want %v", got, want)
}
})
}
}
func TestMinUint64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint64, 8)},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MinUint64x8(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint64x8() = %v, want %v", got, want)
}
})
}
}
func TestMinFloat32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 16", make([]float32, 16)},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
{"zeros", []float32{0, 0, 0, 0}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MinFloat32x16(tc.input)
want := lo.Min(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MinFloat32x16() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMinFloat64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 8", []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MinFloat64x8(tc.input)
want := lo.Min(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MinFloat64x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVX512MinTypeAlias(t *testing.T) {
requireAVX512(t)
input := []myInt32{5, 2, 8, 1, 9}
got := MinInt32x16(input)
want := myInt32(1)
if got != want {
t.Errorf("MinInt32x16() with type alias = %v, want %v", got, want)
}
}
func TestMaxInt8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 64", make([]int8, 64)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MaxInt8x64(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt8x64() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 32", make([]int16, 32)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MaxInt16x32(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt16x32() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 16", make([]int32, 16)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := MaxInt32x16(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt32x16() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt64x2(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 2", []int64{1, 2}},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MaxInt64x2(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt64x2() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 8", make([]int64, 8)},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MaxInt64x8(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt64x8() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 64", make([]uint8, 64)},
{"large", make([]uint8, 1000)},
{"max values", []uint8{255, 100, 50}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MaxUint8x64(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint8x64() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint16x32(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 32", make([]uint16, 32)},
{"large", make([]uint16, 1000)},
{"max values", []uint16{65535, 1000, 500}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MaxUint16x32(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint16x32() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint32, 16)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MaxUint32x16(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint32x16() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint64x2(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 2", []uint64{1, 2}},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MaxUint64x2(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint64x2() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint64, 8)},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MaxUint64x8(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint64x8() = %v, want %v", got, want)
}
})
}
}
func TestMaxFloat32x16(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 16", make([]float32, 16)},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
{"zeros", []float32{0, 0, 0, 0}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MaxFloat32x16(tc.input)
want := lo.Max(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MaxFloat32x16() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMaxFloat64x8(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 8", []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MaxFloat64x8(tc.input)
want := lo.Max(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MaxFloat64x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVX512MaxTypeAlias(t *testing.T) {
requireAVX512(t)
input := []myInt32{5, 2, 8, 1, 9}
got := MaxInt32x16(input)
want := myInt32(9)
if got != want {
t.Errorf("MaxInt32x16() with type alias = %v, want %v", got, want)
}
}
// SumBy tests
type avx512Item struct {
Value int8
Weight int8
Multiplier int8
}
func TestSumByInt8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []avx512Item
}{
{"empty", []avx512Item{}},
{"single", []avx512Item{{Value: 42}}},
{"small", []avx512Item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}},
{"exactly 64", make([]avx512Item, 64)},
{"large", make([]avx512Item, 1000)},
{"negative", []avx512Item{{Value: -1}, {Value: -2}, {Value: -3}, {Value: 4}, {Value: 5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int8(rand.IntN(256) - 128)
}
}
// Using Value field as the iteratee
got := SumByInt8x64(tc.input, func(i avx512Item) int8 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512Item, _ int) int8 { return i.Value }))
if got != want {
t.Errorf("SumByInt8x64() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt16x32(t *testing.T) {
requireAVX512(t)
type avx512ItemInt16 struct {
Value int16
}
testCases := []struct {
name string
input []avx512ItemInt16
}{
{"empty", []avx512ItemInt16{}},
{"single", []avx512ItemInt16{{Value: 42}}},
{"small", []avx512ItemInt16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 32", make([]avx512ItemInt16, 32)},
{"large", make([]avx512ItemInt16, 1000)},
{"negative", []avx512ItemInt16{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int16(rand.IntN(65536) - 32768)
}
}
got := SumByInt16x32(tc.input, func(i avx512ItemInt16) int16 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemInt16, _ int) int16 { return i.Value }))
if got != want {
t.Errorf("SumByInt16x32() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt32x16(t *testing.T) {
requireAVX512(t)
type avx512ItemInt32 struct {
Value int32
}
testCases := []struct {
name string
input []avx512ItemInt32
}{
{"empty", []avx512ItemInt32{}},
{"single", []avx512ItemInt32{{Value: 42}}},
{"small", []avx512ItemInt32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]avx512ItemInt32, 16)},
{"large", make([]avx512ItemInt32, 1000)},
{"negative", []avx512ItemInt32{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int32()
}
}
got := SumByInt32x16(tc.input, func(i avx512ItemInt32) int32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemInt32, _ int) int32 { return i.Value }))
if got != want {
t.Errorf("SumByInt32x16() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt64x8(t *testing.T) {
requireAVX512(t)
type avx512ItemInt64 struct {
Value int64
}
testCases := []struct {
name string
input []avx512ItemInt64
}{
{"empty", []avx512ItemInt64{}},
{"single", []avx512ItemInt64{{Value: 42}}},
{"small", []avx512ItemInt64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]avx512ItemInt64, 8)},
{"large", make([]avx512ItemInt64, 1000)},
{"negative", []avx512ItemInt64{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int64()
}
}
got := SumByInt64x8(tc.input, func(i avx512ItemInt64) int64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemInt64, _ int) int64 { return i.Value }))
if got != want {
t.Errorf("SumByInt64x8() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint8x64(t *testing.T) {
requireAVX512(t)
type avx512ItemUint8 struct {
Value uint8
}
testCases := []struct {
name string
input []avx512ItemUint8
}{
{"empty", []avx512ItemUint8{}},
{"single", []avx512ItemUint8{{Value: 42}}},
{"small", []avx512ItemUint8{{1}, {2}, {3}, {4}, {5}}},
{"exactly 64", make([]avx512ItemUint8, 64)},
{"large", make([]avx512ItemUint8, 1000)},
{"max values", []avx512ItemUint8{{Value: 255}, {Value: 255}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint8(rand.IntN(256))
}
}
got := SumByUint8x64(tc.input, func(i avx512ItemUint8) uint8 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemUint8, _ int) uint8 { return i.Value }))
if got != want {
t.Errorf("SumByUint8x64() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint16x32(t *testing.T) {
requireAVX512(t)
type avx512ItemUint16 struct {
Value uint16
}
testCases := []struct {
name string
input []avx512ItemUint16
}{
{"empty", []avx512ItemUint16{}},
{"single", []avx512ItemUint16{{Value: 42}}},
{"small", []avx512ItemUint16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 32", make([]avx512ItemUint16, 32)},
{"large", make([]avx512ItemUint16, 1000)},
{"max values", []avx512ItemUint16{{Value: 65535}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint16(rand.IntN(65536))
}
}
got := SumByUint16x32(tc.input, func(i avx512ItemUint16) uint16 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemUint16, _ int) uint16 { return i.Value }))
if got != want {
t.Errorf("SumByUint16x32() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint32x16(t *testing.T) {
requireAVX512(t)
type avx512ItemUint32 struct {
Value uint32
}
testCases := []struct {
name string
input []avx512ItemUint32
}{
{"empty", []avx512ItemUint32{}},
{"single", []avx512ItemUint32{{Value: 42}}},
{"small", []avx512ItemUint32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]avx512ItemUint32, 16)},
{"large", make([]avx512ItemUint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint32()
}
}
got := SumByUint32x16(tc.input, func(i avx512ItemUint32) uint32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemUint32, _ int) uint32 { return i.Value }))
if got != want {
t.Errorf("SumByUint32x16() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint64x8(t *testing.T) {
requireAVX512(t)
type avx512ItemUint64 struct {
Value uint64
}
testCases := []struct {
name string
input []avx512ItemUint64
}{
{"empty", []avx512ItemUint64{}},
{"single", []avx512ItemUint64{{Value: 42}}},
{"small", []avx512ItemUint64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]avx512ItemUint64, 8)},
{"large", make([]avx512ItemUint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint64()
}
}
got := SumByUint64x8(tc.input, func(i avx512ItemUint64) uint64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemUint64, _ int) uint64 { return i.Value }))
if got != want {
t.Errorf("SumByUint64x8() = %v, want %v", got, want)
}
})
}
}
func TestSumByFloat32x16(t *testing.T) {
requireAVX512(t)
type avx512ItemFloat32 struct {
Value float32
}
testCases := []struct {
name string
input []avx512ItemFloat32
}{
{"empty", []avx512ItemFloat32{}},
{"single", []avx512ItemFloat32{{Value: 42.5}}},
{"small", []avx512ItemFloat32{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 16", make([]avx512ItemFloat32, 16)},
{"large", make([]avx512ItemFloat32, 1000)},
{"negative", []avx512ItemFloat32{{-1.1}, {-2.2}, {3.3}, {4.4}}},
{"zeros", []avx512ItemFloat32{{0}, {0}, {0}, {0}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float32()
}
}
got := SumByFloat32x16(tc.input, func(i avx512ItemFloat32) float32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemFloat32, _ int) float32 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumByFloat32x16() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestSumByFloat64x8(t *testing.T) {
requireAVX512(t)
type avx512ItemFloat64 struct {
Value float64
}
testCases := []struct {
name string
input []avx512ItemFloat64
}{
{"empty", []avx512ItemFloat64{}},
{"single", []avx512ItemFloat64{{Value: 42.5}}},
{"small", []avx512ItemFloat64{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 8", make([]avx512ItemFloat64, 8)},
{"large", make([]avx512ItemFloat64, 1000)},
{"negative", []avx512ItemFloat64{{-1.1}, {-2.2}, {3.3}, {4.4}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float64()
}
}
got := SumByFloat64x8(tc.input, func(i avx512ItemFloat64) float64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i avx512ItemFloat64, _ int) float64 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumByFloat64x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type alias works correctly for SumBy
func TestAVX512SumByTypeAlias(t *testing.T) {
requireAVX512(t)
type myAVX512Item struct {
Value myInt8
}
input := []myAVX512Item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}
got := SumByInt8x64(input, func(i myAVX512Item) myInt8 { return i.Value })
want := myInt8(15)
if got != want {
t.Errorf("SumByInt8x64() with type alias = %v, want %v", got, want)
}
}
// MeanBy tests
func TestMeanByInt8x64(t *testing.T) {
requireAVX512(t)
testCases := []struct {
name string
input []avx512Item
}{
{"empty", []avx512Item{}},
{"single", []avx512Item{{Value: 42}}},
{"small", []avx512Item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}},
{"exactly 64", make([]avx512Item, 64)},
{"large", make([]avx512Item, 1000)},
{"negative", []avx512Item{{Value: -1}, {Value: -2}, {Value: -3}, {Value: 4}, {Value: 5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int8(rand.IntN(256) - 128)
}
}
got := MeanByInt8x64(tc.input, func(i avx512Item) int8 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512Item, _ int) int8 { return i.Value }))
if got != want {
t.Errorf("MeanByInt8x64() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt16x32(t *testing.T) {
requireAVX512(t)
type avx512ItemInt16 struct {
Value int16
}
testCases := []struct {
name string
input []avx512ItemInt16
}{
{"empty", []avx512ItemInt16{}},
{"single", []avx512ItemInt16{{Value: 42}}},
{"small", []avx512ItemInt16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 32", make([]avx512ItemInt16, 32)},
{"large", make([]avx512ItemInt16, 1000)},
{"negative", []avx512ItemInt16{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int16(rand.IntN(65536) - 32768)
}
}
got := MeanByInt16x32(tc.input, func(i avx512ItemInt16) int16 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemInt16, _ int) int16 { return i.Value }))
if got != want {
t.Errorf("MeanByInt16x32() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt32x16(t *testing.T) {
requireAVX512(t)
type avx512ItemInt32 struct {
Value int32
}
testCases := []struct {
name string
input []avx512ItemInt32
}{
{"empty", []avx512ItemInt32{}},
{"single", []avx512ItemInt32{{Value: 42}}},
{"small", []avx512ItemInt32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]avx512ItemInt32, 16)},
{"large", make([]avx512ItemInt32, 1000)},
{"negative", []avx512ItemInt32{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int32()
}
}
got := MeanByInt32x16(tc.input, func(i avx512ItemInt32) int32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemInt32, _ int) int32 { return i.Value }))
if got != want {
t.Errorf("MeanByInt32x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt64x8(t *testing.T) {
requireAVX512(t)
type avx512ItemInt64 struct {
Value int64
}
testCases := []struct {
name string
input []avx512ItemInt64
}{
{"empty", []avx512ItemInt64{}},
{"single", []avx512ItemInt64{{Value: 42}}},
{"small", []avx512ItemInt64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]avx512ItemInt64, 8)},
{"large", make([]avx512ItemInt64, 1000)},
{"negative", []avx512ItemInt64{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int64()
}
}
got := MeanByInt64x8(tc.input, func(i avx512ItemInt64) int64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemInt64, _ int) int64 { return i.Value }))
if got != want {
t.Errorf("MeanByInt64x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint8x64(t *testing.T) {
requireAVX512(t)
type avx512ItemUint8 struct {
Value uint8
}
testCases := []struct {
name string
input []avx512ItemUint8
}{
{"empty", []avx512ItemUint8{}},
{"single", []avx512ItemUint8{{Value: 42}}},
{"small", []avx512ItemUint8{{1}, {2}, {3}, {4}, {5}}},
{"exactly 64", make([]avx512ItemUint8, 64)},
{"large", make([]avx512ItemUint8, 1000)},
{"max values", []avx512ItemUint8{{Value: 255}, {Value: 255}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint8(rand.IntN(256))
}
}
got := MeanByUint8x64(tc.input, func(i avx512ItemUint8) uint8 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemUint8, _ int) uint8 { return i.Value }))
if got != want {
t.Errorf("MeanByUint8x64() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint16x32(t *testing.T) {
requireAVX512(t)
type avx512ItemUint16 struct {
Value uint16
}
testCases := []struct {
name string
input []avx512ItemUint16
}{
{"empty", []avx512ItemUint16{}},
{"single", []avx512ItemUint16{{Value: 42}}},
{"small", []avx512ItemUint16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 32", make([]avx512ItemUint16, 32)},
{"large", make([]avx512ItemUint16, 1000)},
{"max values", []avx512ItemUint16{{Value: 65535}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint16(rand.IntN(65536))
}
}
got := MeanByUint16x32(tc.input, func(i avx512ItemUint16) uint16 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemUint16, _ int) uint16 { return i.Value }))
if got != want {
t.Errorf("MeanByUint16x32() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint32x16(t *testing.T) {
requireAVX512(t)
type avx512ItemUint32 struct {
Value uint32
}
testCases := []struct {
name string
input []avx512ItemUint32
}{
{"empty", []avx512ItemUint32{}},
{"single", []avx512ItemUint32{{Value: 42}}},
{"small", []avx512ItemUint32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]avx512ItemUint32, 16)},
{"large", make([]avx512ItemUint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint32()
}
}
got := MeanByUint32x16(tc.input, func(i avx512ItemUint32) uint32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemUint32, _ int) uint32 { return i.Value }))
if got != want {
t.Errorf("MeanByUint32x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint64x8(t *testing.T) {
requireAVX512(t)
type avx512ItemUint64 struct {
Value uint64
}
testCases := []struct {
name string
input []avx512ItemUint64
}{
{"empty", []avx512ItemUint64{}},
{"single", []avx512ItemUint64{{Value: 42}}},
{"small", []avx512ItemUint64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]avx512ItemUint64, 8)},
{"large", make([]avx512ItemUint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint64()
}
}
got := MeanByUint64x8(tc.input, func(i avx512ItemUint64) uint64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemUint64, _ int) uint64 { return i.Value }))
if got != want {
t.Errorf("MeanByUint64x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanByFloat32x16(t *testing.T) {
requireAVX512(t)
type avx512ItemFloat32 struct {
Value float32
}
testCases := []struct {
name string
input []avx512ItemFloat32
}{
{"empty", []avx512ItemFloat32{}},
{"single", []avx512ItemFloat32{{Value: 42.5}}},
{"small", []avx512ItemFloat32{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 16", make([]avx512ItemFloat32, 16)},
{"large", make([]avx512ItemFloat32, 1000)},
{"negative", []avx512ItemFloat32{{-1.1}, {-2.2}, {3.3}, {4.4}}},
{"zeros", []avx512ItemFloat32{{0}, {0}, {0}, {0}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float32()
}
}
got := MeanByFloat32x16(tc.input, func(i avx512ItemFloat32) float32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemFloat32, _ int) float32 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanByFloat32x16() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMeanByFloat64x8(t *testing.T) {
requireAVX512(t)
type avx512ItemFloat64 struct {
Value float64
}
testCases := []struct {
name string
input []avx512ItemFloat64
}{
{"empty", []avx512ItemFloat64{}},
{"single", []avx512ItemFloat64{{Value: 42.5}}},
{"small", []avx512ItemFloat64{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 8", make([]avx512ItemFloat64, 8)},
{"large", make([]avx512ItemFloat64, 1000)},
{"negative", []avx512ItemFloat64{{-1.1}, {-2.2}, {3.3}, {4.4}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float64()
}
}
got := MeanByFloat64x8(tc.input, func(i avx512ItemFloat64) float64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i avx512ItemFloat64, _ int) float64 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanByFloat64x8() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type alias works correctly for MeanBy
func TestAVX512MeanByTypeAlias(t *testing.T) {
requireAVX512(t)
type myAVX512Item struct {
Value myInt8
}
input := []myAVX512Item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}
got := MeanByInt8x64(input, func(i myAVX512Item) myInt8 { return i.Value })
want := myInt8(3)
if got != want {
t.Errorf("MeanByInt8x64() with type alias = %v, want %v", got, want)
}
}
================================================
FILE: exp/simd/math_avx_test.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"math/rand/v2"
"testing"
"github.com/samber/lo"
)
func TestSumInt8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 16", make([]int8, 16)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
{"mixed", []int8{-128, 0, 127, 1, -1}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := SumInt8x16(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt8x16() = %v, want %v", got, want)
}
})
}
}
func TestSumInt16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 8", make([]int16, 8)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
{"mixed", []int16{-32768, 0, 32767, 1, -1}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := SumInt16x8(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt16x8() = %v, want %v", got, want)
}
})
}
}
func TestSumInt32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 4", make([]int32, 4)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int32(rand.Int32())
}
}
got := SumInt32x4(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt32x4() = %v, want %v", got, want)
}
})
}
}
func TestSumInt64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 2", []int64{1, 2}},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := SumInt64x2(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumInt64x2() = %v, want %v", got, want)
}
})
}
}
func TestSumUint8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint8, 16)},
{"large", make([]uint8, 1000)},
{"max values", []uint8{255, 255, 1}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := SumUint8x16(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint8x16() = %v, want %v", got, want)
}
})
}
}
func TestSumUint16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint16, 8)},
{"large", make([]uint16, 1000)},
{"max values", []uint16{65535, 1}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := SumUint16x8(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint16x8() = %v, want %v", got, want)
}
})
}
}
func TestSumUint32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 4", make([]uint32, 4)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := SumUint32x4(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint32x4() = %v, want %v", got, want)
}
})
}
}
func TestSumUint64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 2", []uint64{1, 2}},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := SumUint64x2(tc.input)
want := lo.Sum(tc.input)
if got != want {
t.Errorf("SumUint64x2() = %v, want %v", got, want)
}
})
}
}
func TestSumFloat32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 4", []float32{1.0, 2.0, 3.0, 4.0}},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
{"zeros", []float32{0, 0, 0, 0}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := SumFloat32x4(tc.input)
want := lo.Sum(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumFloat32x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestSumFloat64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 2", []float64{1.0, 2.0}},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := SumFloat64x2(tc.input)
want := lo.Sum(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumFloat64x2() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVXTypeAlias(t *testing.T) {
requireAVX(t)
input := []myInt8{1, 2, 3, 4, 5}
got := SumInt8x16(input)
want := lo.Sum(input)
if got != want {
t.Errorf("SumInt8x16() with type alias = %v, want %v", got, want)
}
}
func TestClampInt8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int8
min int8
max int8
}{
{"empty", []int8{}, -10, 10},
{"single", []int8{42}, -10, 10},
{"small", []int8{1, 2, 3, 4, 5}, 2, 4},
{"exactly 16", make([]int8, 16), 5, 10},
{"large", make([]int8, 1000), -5, 5},
{"all below min", []int8{-10, -20, -30}, -5, 10},
{"all above max", []int8{20, 30, 40}, -10, 10},
{"already clamped", []int8{5, 6, 7}, 5, 10},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := ClampInt8x16(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt8x16() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt8x16()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
// Check that the value was properly clamped from the original
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt8x16()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int16
min int16
max int16
}{
{"empty", []int16{}, -100, 100},
{"single", []int16{42}, -10, 10},
{"small", []int16{1, 2, 3, 4, 5}, 2, 4},
{"exactly 8", make([]int16, 8), 50, 100},
{"large", make([]int16, 1000), -50, 50},
{"all below min", []int16{-100, -200, -300}, -50, 100},
{"all above max", []int16{200, 300, 400}, -100, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := ClampInt16x8(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt16x8() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt16x8()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt16x8()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampInt32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int32
min int32
max int32
}{
{"empty", []int32{}, -100, 100},
{"single", []int32{42}, -10, 10},
{"small", []int32{1, 2, 3, 4, 5}, 2, 4},
{"exactly 4", []int32{1, 100, -50, 200}, 0, 100},
{"large", make([]int32, 1000), -50, 50},
{"negative range", []int32{-100, -50, 0, 50, 100}, -30, -10},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := ClampInt32x4(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampInt32x4() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampInt32x4()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampInt32x4()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint8
min uint8
max uint8
}{
{"empty", []uint8{}, 10, 100},
{"single", []uint8{42}, 10, 100},
{"small", []uint8{1, 2, 3, 4, 5}, 2, 4},
{"exactly 16", make([]uint8, 16), 50, 100},
{"large", make([]uint8, 1000), 50, 200},
{"all below min", []uint8{1, 2, 3}, 10, 100},
{"all above max", []uint8{200, 225, 250}, 50, 150},
{"max values", []uint8{255, 255, 255}, 100, 200},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := ClampUint8x16(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint8x16() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint8x16()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint8x16()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint16
min uint16
max uint16
}{
{"empty", []uint16{}, 100, 1000},
{"single", []uint16{42}, 10, 100},
{"small", []uint16{1, 2, 3, 4, 5}, 2, 4},
{"exactly 8", make([]uint16, 8), 500, 1000},
{"large", make([]uint16, 1000), 500, 5000},
{"all below min", []uint16{1, 2, 3}, 10, 100},
{"all above max", []uint16{2000, 3000, 4000}, 100, 1000},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := ClampUint16x8(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint16x8() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint16x8()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint16x8()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampUint32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint32
min uint32
max uint32
}{
{"empty", []uint32{}, 100, 1000},
{"single", []uint32{42}, 10, 100},
{"small", []uint32{1, 2, 3, 4, 5}, 2, 4},
{"exactly 4", []uint32{1, 1000, 50, 2000}, 100, 1000},
{"large", make([]uint32, 1000), 500, 5000},
{"all below min", []uint32{1, 2, 3}, 10, 100},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := ClampUint32x4(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampUint32x4() returned length %d, want %d", len(got), len(tc.input))
}
for i, v := range got {
if v < tc.min || v > tc.max {
t.Errorf("ClampUint32x4()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if v != expected {
t.Errorf("ClampUint32x4()[%d] = %v, want %v (original: %v)", i, v, expected, original)
}
}
})
}
}
func TestClampFloat32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float32
min float32
max float32
}{
{"empty", []float32{}, -10.0, 10.0},
{"single", []float32{42.5}, -10.0, 10.0},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}, 2.0, 4.0},
{"exactly 4", []float32{1.0, 10.0, -5.0, 20.0}, -5.0, 10.0},
{"large", make([]float32, 1000), -5.0, 5.0},
{"negative range", []float32{-10.0, -5.0, 0.0, 5.0, 10.0}, -3.0, -1.0},
{"all below min", []float32{-20.0, -30.0, -40.0}, -10.0, 10.0},
{"all above max", []float32{20.0, 30.0, 40.0}, -10.0, 10.0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()*200 - 100
}
}
got := ClampFloat32x4(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampFloat32x4() returned length %d, want %d", len(got), len(tc.input))
}
const epsilon = 1e-3
for i, v := range got {
if v < tc.min-epsilon || v > tc.max+epsilon {
t.Errorf("ClampFloat32x4()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if diff := v - expected; diff < -epsilon || diff > epsilon {
t.Errorf("ClampFloat32x4()[%d] = %v, want %v (original: %v, diff: %v)", i, v, expected, original, diff)
}
}
})
}
}
func TestClampFloat64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float64
min float64
max float64
}{
{"empty", []float64{}, -10.0, 10.0},
{"single", []float64{42.5}, -10.0, 10.0},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}, 2.0, 4.0},
{"exactly 2", []float64{-100.0, 200.0}, -50.0, 50.0},
{"large", make([]float64, 1000), -5.0, 5.0},
{"negative range", []float64{-10.0, -5.0, 0.0, 5.0, 10.0}, -3.0, -1.0},
{"all below min", []float64{-20.0, -30.0, -40.0}, -10.0, 10.0},
{"all above max", []float64{20.0, 30.0, 40.0}, -10.0, 10.0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()*200 - 100
}
}
got := ClampFloat64x2(tc.input, tc.min, tc.max)
if len(got) != len(tc.input) {
t.Errorf("ClampFloat64x2() returned length %d, want %d", len(got), len(tc.input))
}
const epsilon = 1e-3
for i, v := range got {
if v < tc.min-epsilon || v > tc.max+epsilon {
t.Errorf("ClampFloat64x2()[%d] = %v, outside range [%v, %v]", i, v, tc.min, tc.max)
}
original := tc.input[i]
expected := original
if expected < tc.min {
expected = tc.min
} else if expected > tc.max {
expected = tc.max
}
if diff := v - expected; diff < -epsilon || diff > epsilon {
t.Errorf("ClampFloat64x2()[%d] = %v, want %v (original: %v, diff: %v)", i, v, expected, original, diff)
}
}
})
}
}
// Test type aliases work correctly
func TestAVXClampTypeAlias(t *testing.T) {
requireAVX(t)
input := []myInt8{-5, 0, 10, 15, 20}
min := myInt8(0)
max := myInt8(10)
got := ClampInt8x16(input, min, max)
for i, v := range got {
if v < min || v > max {
t.Errorf("ClampInt8x16()[%d] with type alias = %v, outside range [%v, %v]", i, v, min, max)
}
original := input[i]
expected := original
if expected < min {
expected = min
} else if expected > max {
expected = max
}
if v != expected {
t.Errorf("ClampInt8x16()[%d] with type alias = %v, want %v (original: %v)", i, v, expected, original)
}
}
}
func TestMeanInt8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 16", make([]int8, 16)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MeanInt8x16(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt8x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 8", make([]int16, 8)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MeanInt16x8(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt16x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 4", make([]int32, 4)},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int32(rand.Int32())
}
}
got := MeanInt32x4(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt32x4() = %v, want %v", got, want)
}
})
}
}
func TestMeanInt64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int64
}{
{"empty", []int64{}},
{"single", []int64{42}},
{"small", []int64{1, 2, 3, 4, 5}},
{"exactly 2", []int64{1, 2}},
{"large", make([]int64, 1000)},
{"negative", []int64{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int64()
}
}
got := MeanInt64x2(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanInt64x2() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint8, 16)},
{"large", make([]uint8, 1000)},
{"max values", []uint8{255, 255, 1}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MeanUint8x16(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint8x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint16, 8)},
{"large", make([]uint16, 1000)},
{"max values", []uint16{65535, 1}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MeanUint16x8(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint16x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 4", make([]uint32, 4)},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MeanUint32x4(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint32x4() = %v, want %v", got, want)
}
})
}
}
func TestMeanUint64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint64
}{
{"empty", []uint64{}},
{"single", []uint64{42}},
{"small", []uint64{1, 2, 3, 4, 5}},
{"exactly 2", []uint64{1, 2}},
{"large", make([]uint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint64()
}
}
got := MeanUint64x2(tc.input)
want := lo.Mean(tc.input)
if got != want {
t.Errorf("MeanUint64x2() = %v, want %v", got, want)
}
})
}
}
func TestMeanFloat32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 4", []float32{1.0, 2.0, 3.0, 4.0}},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
{"zeros", []float32{0, 0, 0, 0}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MeanFloat32x4(tc.input)
want := lo.Mean(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanFloat32x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMeanFloat64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 2", []float64{1.0, 2.0}},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MeanFloat64x2(tc.input)
want := lo.Mean(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanFloat64x2() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVXMeanTypeAlias(t *testing.T) {
requireAVX(t)
input := []myInt8{1, 2, 3, 4, 5}
got := MeanInt8x16(input)
want := lo.Mean(input)
if got != want {
t.Errorf("MeanInt8x16() with type alias = %v, want %v", got, want)
}
}
func TestMinInt8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 16", make([]int8, 16)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MinInt8x16(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt8x16() = %v, want %v", got, want)
}
})
}
}
func TestMinInt16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 8", make([]int16, 8)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MinInt16x8(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt16x8() = %v, want %v", got, want)
}
})
}
}
func TestMinInt32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 4", []int32{1, 2, 3, 4}},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := MinInt32x4(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinInt32x4() = %v, want %v", got, want)
}
})
}
}
func TestMinUint8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint8, 16)},
{"large", make([]uint8, 1000)},
{"max values", []uint8{255, 100, 50}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MinUint8x16(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint8x16() = %v, want %v", got, want)
}
})
}
}
func TestMinUint16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint16, 8)},
{"large", make([]uint16, 1000)},
{"max values", []uint16{65535, 1000, 500}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MinUint16x8(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint16x8() = %v, want %v", got, want)
}
})
}
}
func TestMinUint32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 4", []uint32{1, 2, 3, 4}},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MinUint32x4(tc.input)
want := lo.Min(tc.input)
if got != want {
t.Errorf("MinUint32x4() = %v, want %v", got, want)
}
})
}
}
func TestMinFloat32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 4", []float32{1.0, 2.0, 3.0, 4.0}},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
{"zeros", []float32{0, 0, 0, 0}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MinFloat32x4(tc.input)
want := lo.Min(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MinFloat32x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMinFloat64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 2", []float64{1.0, 2.0}},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MinFloat64x2(tc.input)
want := lo.Min(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MinFloat64x2() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVXMinTypeAlias(t *testing.T) {
requireAVX(t)
input := []myInt8{5, 2, 8, 1, 9}
got := MinInt8x16(input)
want := myInt8(1)
if got != want {
t.Errorf("MinInt8x16() with type alias = %v, want %v", got, want)
}
}
func TestMaxInt8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int8
}{
{"empty", []int8{}},
{"single", []int8{42}},
{"small", []int8{1, 2, 3, 4, 5}},
{"exactly 16", make([]int8, 16)},
{"large", make([]int8, 1000)},
{"negative", []int8{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int8(rand.IntN(256) - 128)
}
}
got := MaxInt8x16(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt8x16() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int16
}{
{"empty", []int16{}},
{"single", []int16{42}},
{"small", []int16{1, 2, 3, 4, 5}},
{"exactly 8", make([]int16, 8)},
{"large", make([]int16, 1000)},
{"negative", []int16{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = int16(rand.IntN(65536) - 32768)
}
}
got := MaxInt16x8(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt16x8() = %v, want %v", got, want)
}
})
}
}
func TestMaxInt32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []int32
}{
{"empty", []int32{}},
{"single", []int32{42}},
{"small", []int32{1, 2, 3, 4, 5}},
{"exactly 4", []int32{1, 2, 3, 4}},
{"large", make([]int32, 1000)},
{"negative", []int32{-1, -2, -3, 4, 5}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Int32()
}
}
got := MaxInt32x4(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxInt32x4() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint8
}{
{"empty", []uint8{}},
{"single", []uint8{42}},
{"small", []uint8{1, 2, 3, 4, 5}},
{"exactly 16", make([]uint8, 16)},
{"large", make([]uint8, 1000)},
{"max values", []uint8{255, 100, 50}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint8(rand.IntN(256))
}
}
got := MaxUint8x16(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint8x16() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint16x8(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint16
}{
{"empty", []uint16{}},
{"single", []uint16{42}},
{"small", []uint16{1, 2, 3, 4, 5}},
{"exactly 8", make([]uint16, 8)},
{"large", make([]uint16, 1000)},
{"max values", []uint16{65535, 1000, 500}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = uint16(rand.IntN(65536))
}
}
got := MaxUint16x8(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint16x8() = %v, want %v", got, want)
}
})
}
}
func TestMaxUint32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []uint32
}{
{"empty", []uint32{}},
{"single", []uint32{42}},
{"small", []uint32{1, 2, 3, 4, 5}},
{"exactly 4", []uint32{1, 2, 3, 4}},
{"large", make([]uint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Uint32()
}
}
got := MaxUint32x4(tc.input)
want := lo.Max(tc.input)
if got != want {
t.Errorf("MaxUint32x4() = %v, want %v", got, want)
}
})
}
}
func TestMaxFloat32x4(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float32
}{
{"empty", []float32{}},
{"single", []float32{42.5}},
{"small", []float32{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 4", []float32{1.0, 2.0, 3.0, 4.0}},
{"large", make([]float32, 1000)},
{"negative", []float32{-1.1, -2.2, 3.3, 4.4}},
{"zeros", []float32{0, 0, 0, 0}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float32()
}
}
got := MaxFloat32x4(tc.input)
want := lo.Max(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MaxFloat32x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMaxFloat64x2(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []float64
}{
{"empty", []float64{}},
{"single", []float64{42.5}},
{"small", []float64{1.1, 2.2, 3.3, 4.4, 5.5}},
{"exactly 2", []float64{1.0, 2.0}},
{"large", make([]float64, 1000)},
{"negative", []float64{-1.1, -2.2, 3.3, 4.4}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0] == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i] = rand.Float64()
}
}
got := MaxFloat64x2(tc.input)
want := lo.Max(tc.input)
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MaxFloat64x2() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type aliases work correctly
func TestAVXMaxTypeAlias(t *testing.T) {
requireAVX(t)
input := []myInt8{5, 2, 8, 1, 9}
got := MaxInt8x16(input)
want := myInt8(9)
if got != want {
t.Errorf("MaxInt8x16() with type alias = %v, want %v", got, want)
}
}
// SumBy tests
type item struct {
Value int8
Weight int8
Multiplier int8
}
func TestSumByInt8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []item
}{
{"empty", []item{}},
{"single", []item{{Value: 42}}},
{"small", []item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}},
{"exactly 16", make([]item, 16)},
{"large", make([]item, 1000)},
{"negative", []item{{Value: -1}, {Value: -2}, {Value: -3}, {Value: 4}, {Value: 5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int8(rand.IntN(256) - 128)
}
}
// Using Value field as the iteratee
got := SumByInt8x16(tc.input, func(i item) int8 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i item, _ int) int8 { return i.Value }))
if got != want {
t.Errorf("SumByInt8x16() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt16x8(t *testing.T) {
requireAVX(t)
type itemInt16 struct {
Value int16
}
testCases := []struct {
name string
input []itemInt16
}{
{"empty", []itemInt16{}},
{"single", []itemInt16{{Value: 42}}},
{"small", []itemInt16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]itemInt16, 8)},
{"large", make([]itemInt16, 1000)},
{"negative", []itemInt16{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int16(rand.IntN(65536) - 32768)
}
}
got := SumByInt16x8(tc.input, func(i itemInt16) int16 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemInt16, _ int) int16 { return i.Value }))
if got != want {
t.Errorf("SumByInt16x8() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt32x4(t *testing.T) {
requireAVX(t)
type itemInt32 struct {
Value int32
}
testCases := []struct {
name string
input []itemInt32
}{
{"empty", []itemInt32{}},
{"single", []itemInt32{{Value: 42}}},
{"small", []itemInt32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 4", []itemInt32{{1}, {2}, {3}, {4}}},
{"large", make([]itemInt32, 1000)},
{"negative", []itemInt32{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int32()
}
}
got := SumByInt32x4(tc.input, func(i itemInt32) int32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemInt32, _ int) int32 { return i.Value }))
if got != want {
t.Errorf("SumByInt32x4() = %v, want %v", got, want)
}
})
}
}
func TestSumByInt64x2(t *testing.T) {
requireAVX(t)
type itemInt64 struct {
Value int64
}
testCases := []struct {
name string
input []itemInt64
}{
{"empty", []itemInt64{}},
{"single", []itemInt64{{Value: 42}}},
{"small", []itemInt64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 2", []itemInt64{{1}, {2}}},
{"large", make([]itemInt64, 1000)},
{"negative", []itemInt64{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int64()
}
}
got := SumByInt64x2(tc.input, func(i itemInt64) int64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemInt64, _ int) int64 { return i.Value }))
if got != want {
t.Errorf("SumByInt64x2() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint8x16(t *testing.T) {
requireAVX(t)
type itemUint8 struct {
Value uint8
}
testCases := []struct {
name string
input []itemUint8
}{
{"empty", []itemUint8{}},
{"single", []itemUint8{{Value: 42}}},
{"small", []itemUint8{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]itemUint8, 16)},
{"large", make([]itemUint8, 1000)},
{"max values", []itemUint8{{Value: 255}, {Value: 255}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint8(rand.IntN(256))
}
}
got := SumByUint8x16(tc.input, func(i itemUint8) uint8 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemUint8, _ int) uint8 { return i.Value }))
if got != want {
t.Errorf("SumByUint8x16() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint16x8(t *testing.T) {
requireAVX(t)
type itemUint16 struct {
Value uint16
}
testCases := []struct {
name string
input []itemUint16
}{
{"empty", []itemUint16{}},
{"single", []itemUint16{{Value: 42}}},
{"small", []itemUint16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]itemUint16, 8)},
{"large", make([]itemUint16, 1000)},
{"max values", []itemUint16{{Value: 65535}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint16(rand.IntN(65536))
}
}
got := SumByUint16x8(tc.input, func(i itemUint16) uint16 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemUint16, _ int) uint16 { return i.Value }))
if got != want {
t.Errorf("SumByUint16x8() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint32x4(t *testing.T) {
requireAVX(t)
type itemUint32 struct {
Value uint32
}
testCases := []struct {
name string
input []itemUint32
}{
{"empty", []itemUint32{}},
{"single", []itemUint32{{Value: 42}}},
{"small", []itemUint32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 4", []itemUint32{{1}, {2}, {3}, {4}}},
{"large", make([]itemUint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint32()
}
}
got := SumByUint32x4(tc.input, func(i itemUint32) uint32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemUint32, _ int) uint32 { return i.Value }))
if got != want {
t.Errorf("SumByUint32x4() = %v, want %v", got, want)
}
})
}
}
func TestSumByUint64x2(t *testing.T) {
requireAVX(t)
type itemUint64 struct {
Value uint64
}
testCases := []struct {
name string
input []itemUint64
}{
{"empty", []itemUint64{}},
{"single", []itemUint64{{Value: 42}}},
{"small", []itemUint64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 2", []itemUint64{{1}, {2}}},
{"large", make([]itemUint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint64()
}
}
got := SumByUint64x2(tc.input, func(i itemUint64) uint64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemUint64, _ int) uint64 { return i.Value }))
if got != want {
t.Errorf("SumByUint64x2() = %v, want %v", got, want)
}
})
}
}
func TestSumByFloat32x4(t *testing.T) {
requireAVX(t)
type itemFloat32 struct {
Value float32
}
testCases := []struct {
name string
input []itemFloat32
}{
{"empty", []itemFloat32{}},
{"single", []itemFloat32{{Value: 42.5}}},
{"small", []itemFloat32{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 4", []itemFloat32{{1.0}, {2.0}, {3.0}, {4.0}}},
{"large", make([]itemFloat32, 1000)},
{"negative", []itemFloat32{{-1.1}, {-2.2}, {3.3}, {4.4}}},
{"zeros", []itemFloat32{{0}, {0}, {0}, {0}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float32()
}
}
got := SumByFloat32x4(tc.input, func(i itemFloat32) float32 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemFloat32, _ int) float32 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumByFloat32x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestSumByFloat64x2(t *testing.T) {
requireAVX(t)
type itemFloat64 struct {
Value float64
}
testCases := []struct {
name string
input []itemFloat64
}{
{"empty", []itemFloat64{}},
{"single", []itemFloat64{{Value: 42.5}}},
{"small", []itemFloat64{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 2", []itemFloat64{{1.0}, {2.0}}},
{"large", make([]itemFloat64, 1000)},
{"negative", []itemFloat64{{-1.1}, {-2.2}, {3.3}, {4.4}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float64()
}
}
got := SumByFloat64x2(tc.input, func(i itemFloat64) float64 { return i.Value })
want := lo.Sum(lo.Map(tc.input, func(i itemFloat64, _ int) float64 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("SumByFloat64x2() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type alias works correctly for SumBy
func TestAVXSumByTypeAlias(t *testing.T) {
requireAVX(t)
type myItem struct {
Value myInt8
}
input := []myItem{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}
got := SumByInt8x16(input, func(i myItem) myInt8 { return i.Value })
want := myInt8(15)
if got != want {
t.Errorf("SumByInt8x16() with type alias = %v, want %v", got, want)
}
}
// MeanBy tests
func TestMeanByInt8x16(t *testing.T) {
requireAVX(t)
testCases := []struct {
name string
input []item
}{
{"empty", []item{}},
{"single", []item{{Value: 42}}},
{"small", []item{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}},
{"exactly 16", make([]item, 16)},
{"large", make([]item, 1000)},
{"negative", []item{{Value: -1}, {Value: -2}, {Value: -3}, {Value: 4}, {Value: 5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int8(rand.IntN(256) - 128)
}
}
got := MeanByInt8x16(tc.input, func(i item) int8 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i item, _ int) int8 { return i.Value }))
if got != want {
t.Errorf("MeanByInt8x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt16x8(t *testing.T) {
requireAVX(t)
type itemInt16 struct {
Value int16
}
testCases := []struct {
name string
input []itemInt16
}{
{"empty", []itemInt16{}},
{"single", []itemInt16{{Value: 42}}},
{"small", []itemInt16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]itemInt16, 8)},
{"large", make([]itemInt16, 1000)},
{"negative", []itemInt16{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = int16(rand.IntN(65536) - 32768)
}
}
got := MeanByInt16x8(tc.input, func(i itemInt16) int16 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemInt16, _ int) int16 { return i.Value }))
if got != want {
t.Errorf("MeanByInt16x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt32x4(t *testing.T) {
requireAVX(t)
type itemInt32 struct {
Value int32
}
testCases := []struct {
name string
input []itemInt32
}{
{"empty", []itemInt32{}},
{"single", []itemInt32{{Value: 42}}},
{"small", []itemInt32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 4", []itemInt32{{1}, {2}, {3}, {4}}},
{"large", make([]itemInt32, 1000)},
{"negative", []itemInt32{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int32()
}
}
got := MeanByInt32x4(tc.input, func(i itemInt32) int32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemInt32, _ int) int32 { return i.Value }))
if got != want {
t.Errorf("MeanByInt32x4() = %v, want %v", got, want)
}
})
}
}
func TestMeanByInt64x2(t *testing.T) {
requireAVX(t)
type itemInt64 struct {
Value int64
}
testCases := []struct {
name string
input []itemInt64
}{
{"empty", []itemInt64{}},
{"single", []itemInt64{{Value: 42}}},
{"small", []itemInt64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 2", []itemInt64{{1}, {2}}},
{"large", make([]itemInt64, 1000)},
{"negative", []itemInt64{{-1}, {-2}, {-3}, {4}, {5}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Int64()
}
}
got := MeanByInt64x2(tc.input, func(i itemInt64) int64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemInt64, _ int) int64 { return i.Value }))
if got != want {
t.Errorf("MeanByInt64x2() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint8x16(t *testing.T) {
requireAVX(t)
type itemUint8 struct {
Value uint8
}
testCases := []struct {
name string
input []itemUint8
}{
{"empty", []itemUint8{}},
{"single", []itemUint8{{Value: 42}}},
{"small", []itemUint8{{1}, {2}, {3}, {4}, {5}}},
{"exactly 16", make([]itemUint8, 16)},
{"large", make([]itemUint8, 1000)},
{"max values", []itemUint8{{Value: 255}, {Value: 255}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint8(rand.IntN(256))
}
}
got := MeanByUint8x16(tc.input, func(i itemUint8) uint8 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemUint8, _ int) uint8 { return i.Value }))
if got != want {
t.Errorf("MeanByUint8x16() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint16x8(t *testing.T) {
requireAVX(t)
type itemUint16 struct {
Value uint16
}
testCases := []struct {
name string
input []itemUint16
}{
{"empty", []itemUint16{}},
{"single", []itemUint16{{Value: 42}}},
{"small", []itemUint16{{1}, {2}, {3}, {4}, {5}}},
{"exactly 8", make([]itemUint16, 8)},
{"large", make([]itemUint16, 1000)},
{"max values", []itemUint16{{Value: 65535}, {Value: 1}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = uint16(rand.IntN(65536))
}
}
got := MeanByUint16x8(tc.input, func(i itemUint16) uint16 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemUint16, _ int) uint16 { return i.Value }))
if got != want {
t.Errorf("MeanByUint16x8() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint32x4(t *testing.T) {
requireAVX(t)
type itemUint32 struct {
Value uint32
}
testCases := []struct {
name string
input []itemUint32
}{
{"empty", []itemUint32{}},
{"single", []itemUint32{{Value: 42}}},
{"small", []itemUint32{{1}, {2}, {3}, {4}, {5}}},
{"exactly 4", []itemUint32{{1}, {2}, {3}, {4}}},
{"large", make([]itemUint32, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint32()
}
}
got := MeanByUint32x4(tc.input, func(i itemUint32) uint32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemUint32, _ int) uint32 { return i.Value }))
if got != want {
t.Errorf("MeanByUint32x4() = %v, want %v", got, want)
}
})
}
}
func TestMeanByUint64x2(t *testing.T) {
requireAVX(t)
type itemUint64 struct {
Value uint64
}
testCases := []struct {
name string
input []itemUint64
}{
{"empty", []itemUint64{}},
{"single", []itemUint64{{Value: 42}}},
{"small", []itemUint64{{1}, {2}, {3}, {4}, {5}}},
{"exactly 2", []itemUint64{{1}, {2}}},
{"large", make([]itemUint64, 1000)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Uint64()
}
}
got := MeanByUint64x2(tc.input, func(i itemUint64) uint64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemUint64, _ int) uint64 { return i.Value }))
if got != want {
t.Errorf("MeanByUint64x2() = %v, want %v", got, want)
}
})
}
}
func TestMeanByFloat32x4(t *testing.T) {
requireAVX(t)
type itemFloat32 struct {
Value float32
}
testCases := []struct {
name string
input []itemFloat32
}{
{"empty", []itemFloat32{}},
{"single", []itemFloat32{{Value: 42.5}}},
{"small", []itemFloat32{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 4", []itemFloat32{{1.0}, {2.0}, {3.0}, {4.0}}},
{"large", make([]itemFloat32, 1000)},
{"negative", []itemFloat32{{-1.1}, {-2.2}, {3.3}, {4.4}}},
{"zeros", []itemFloat32{{0}, {0}, {0}, {0}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float32()
}
}
got := MeanByFloat32x4(tc.input, func(i itemFloat32) float32 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemFloat32, _ int) float32 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanByFloat32x4() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
func TestMeanByFloat64x2(t *testing.T) {
requireAVX(t)
type itemFloat64 struct {
Value float64
}
testCases := []struct {
name string
input []itemFloat64
}{
{"empty", []itemFloat64{}},
{"single", []itemFloat64{{Value: 42.5}}},
{"small", []itemFloat64{{1.1}, {2.2}, {3.3}, {4.4}, {5.5}}},
{"exactly 2", []itemFloat64{{1.0}, {2.0}}},
{"large", make([]itemFloat64, 1000)},
{"negative", []itemFloat64{{-1.1}, {-2.2}, {3.3}, {4.4}}},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.input) > 0 && tc.input[0].Value == 0 && len(tc.input) > 6 {
for i := range tc.input {
tc.input[i].Value = rand.Float64()
}
}
got := MeanByFloat64x2(tc.input, func(i itemFloat64) float64 { return i.Value })
want := lo.Mean(lo.Map(tc.input, func(i itemFloat64, _ int) float64 { return i.Value }))
const epsilon = 1e-3
if diff := got - want; diff < -epsilon || diff > epsilon {
t.Errorf("MeanByFloat64x2() = %v, want %v (diff: %v)", got, want, diff)
}
})
}
}
// Test type alias works correctly for MeanBy
func TestAVXMeanByTypeAlias(t *testing.T) {
requireAVX(t)
type myItem struct {
Value myInt8
}
input := []myItem{{Value: 1}, {Value: 2}, {Value: 3}, {Value: 4}, {Value: 5}}
got := MeanByInt8x16(input, func(i myItem) myInt8 { return i.Value })
want := myInt8(3)
if got != want {
t.Errorf("MeanByInt8x16() with type alias = %v, want %v", got, want)
}
}
================================================
FILE: exp/simd/math_bench_test.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"math/rand"
"simd/archsimd"
"testing"
"time"
"github.com/samber/lo"
)
// Benchmark suite for SIMD math operations compared to core lo package fallbacks.
// These benchmarks measure the performance of Sum, Mean, Min, and Max operations
// across different SIMD implementations (AVX, AVX2, AVX512) and data sizes.
// Benchmark sizes to demonstrate performance characteristics at different scales
var benchmarkSizes = []struct {
name string
size int
}{
{"small", 8}, // Smaller than AVX width (16 lanes for int8)
{"medium", 128}, // Between AVX (16) and AVX2 (32) width for int8
{"large", 1024}, // Well above SIMD register widths
{"xlarge", 8192}, // Large dataset for real-world performance
}
func init() {
// Seeded for reproducibility
rand.Seed(time.Now().UnixNano())
}
// Helper function to generate random test data
type benchDataGenerator[T any] func(n int) []T
func generateInt8(n int) []int8 {
data := make([]int8, n)
for i := range data {
data[i] = int8(rand.Intn(127) - 64)
}
return data
}
func generateInt16(n int) []int16 {
data := make([]int16, n)
for i := range data {
data[i] = int16(rand.Intn(32767) - 16384)
}
return data
}
func generateInt32(n int) []int32 {
data := make([]int32, n)
for i := range data {
data[i] = int32(rand.Intn(1000) - 500)
}
return data
}
func generateInt64(n int) []int64 {
data := make([]int64, n)
for i := range data {
data[i] = rand.Int63() % 10000
}
return data
}
func generateUint8(n int) []uint8 {
data := make([]uint8, n)
for i := range data {
data[i] = uint8(rand.Uint32() % 256)
}
return data
}
func generateUint16(n int) []uint16 {
data := make([]uint16, n)
for i := range data {
data[i] = uint16(rand.Uint32() % 65536)
}
return data
}
func generateUint32(n int) []uint32 {
data := make([]uint32, n)
for i := range data {
data[i] = rand.Uint32() % 10000
}
return data
}
func generateUint64(n int) []uint64 {
data := make([]uint64, n)
for i := range data {
data[i] = rand.Uint64() % 10000
}
return data
}
func generateFloat32(n int) []float32 {
data := make([]float32, n)
for i := range data {
data[i] = rand.Float32()*100 - 50
}
return data
}
func generateFloat64(n int) []float64 {
data := make([]float64, n)
for i := range data {
data[i] = rand.Float64()*100 - 50
}
return data
}
// ============================================================================
// SUM BENCHMARKS
// ============================================================================
func BenchmarkSumInt8(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt8(bs.size)
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Sum(data)
}
})
b.Run("AVX-x16", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt8x16(data)
}
})
b.Run("AVX2-x32", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt8x32(data)
}
})
b.Run("AVX512-x64", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt8x64(data)
}
})
})
}
}
func BenchmarkSumInt16(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt16(bs.size)
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Sum(data)
}
})
b.Run("AVX-x8", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt16x8(data)
}
})
b.Run("AVX2-x16", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt16x16(data)
}
})
b.Run("AVX512-x32", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt16x32(data)
}
})
})
}
}
func BenchmarkSumInt32(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt32(bs.size)
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Sum(data)
}
})
b.Run("AVX-x4", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt32x4(data)
}
})
b.Run("AVX2-x8", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt32x8(data)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt32x16(data)
}
})
})
}
}
func BenchmarkSumInt64(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt64(bs.size)
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Sum(data)
}
})
b.Run("AVX-x2", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt64x2(data)
}
})
b.Run("AVX2-x4", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt64x4(data)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt64x8(data)
}
})
})
}
}
func BenchmarkSumFloat32(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateFloat32(bs.size)
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Sum(data)
}
})
b.Run("AVX-x4", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumFloat32x4(data)
}
})
b.Run("AVX2-x8", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumFloat32x8(data)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumFloat32x16(data)
}
})
})
}
}
func BenchmarkSumFloat64(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateFloat64(bs.size)
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Sum(data)
}
})
b.Run("AVX-x2", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumFloat64x2(data)
}
})
b.Run("AVX2-x4", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumFloat64x4(data)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumFloat64x8(data)
}
})
})
}
}
// ============================================================================
// MEAN BENCHMARKS
// ============================================================================
func BenchmarkMeanInt32(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt32(bs.size)
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Mean(data)
}
})
b.Run("AVX-x4", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MeanInt32x4(data)
}
})
b.Run("AVX2-x8", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MeanInt32x8(data)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MeanInt32x16(data)
}
})
})
}
}
func BenchmarkMeanFloat64(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateFloat64(bs.size)
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Mean(data)
}
})
b.Run("AVX-x2", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MeanFloat64x2(data)
}
})
b.Run("AVX2-x4", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MeanFloat64x4(data)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MeanFloat64x8(data)
}
})
})
}
}
// ============================================================================
// MIN BENCHMARKS
// ============================================================================
func BenchmarkMinInt32(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt32(bs.size)
b.Run("AVX-x4", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MinInt32x4(data)
}
})
b.Run("AVX2-x8", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MinInt32x8(data)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MinInt32x16(data)
}
})
})
}
}
func BenchmarkMinFloat64(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateFloat64(bs.size)
b.Run("AVX-x2", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MinFloat64x2(data)
}
})
b.Run("AVX2-x4", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MinFloat64x4(data)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MinFloat64x8(data)
}
})
})
}
}
// ============================================================================
// MAX BENCHMARKS
// ============================================================================
func BenchmarkMaxInt32(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateInt32(bs.size)
b.Run("AVX-x4", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MaxInt32x4(data)
}
})
b.Run("AVX2-x8", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MaxInt32x8(data)
}
})
b.Run("AVX512-x16", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MaxInt32x16(data)
}
})
})
}
}
func BenchmarkMaxFloat64(b *testing.B) {
for _, bs := range benchmarkSizes {
b.Run(bs.name, func(b *testing.B) {
data := generateFloat64(bs.size)
b.Run("AVX-x2", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MaxFloat64x2(data)
}
})
b.Run("AVX2-x4", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MaxFloat64x4(data)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = MaxFloat64x8(data)
}
})
})
}
}
// ============================================================================
// LANE WIDTH COMPARISON BENCHMARKS
// ============================================================================
// These benchmarks show how performance scales with SIMD register width
func BenchmarkSumInt8ByWidth(b *testing.B) {
size := 4096 // Large enough to see differences across implementations
data := generateInt8(size)
benchmarks := []struct {
name string
fn func() int8
}{
{"Fallback-lo", func() int8 { return lo.Sum(data) }},
{"AVX-x16", func() int8 { return SumInt8x16(data) }},
{"AVX2-x32", func() int8 { return SumInt8x32(data) }},
{"AVX512-x64", func() int8 { return SumInt8x64(data) }},
}
for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
if bm.name == "AVX-x16" {
requireAVX(b)
}
if bm.name == "AVX2-x32" {
requireAVX2(b)
}
if bm.name == "AVX512-x64" {
requireAVX512(b)
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = bm.fn()
}
})
}
}
// ============================================================================
// COMPARATIVE BENCHMARK WITH WARMUP
// ============================================================================
// This benchmark demonstrates the steady-state performance after warmup
func BenchmarkSumInt64SteadyState(b *testing.B) {
size := 8192
data := generateInt64(size)
// Warmup phase to ensure JIT compilation if applicable
for i := 0; i < 1000; i++ {
lo.Sum(data)
SumInt64x2(data)
if archsimd.X86.AVX2() {
SumInt64x4(data)
}
if archsimd.X86.AVX512() {
SumInt64x8(data)
}
}
b.ResetTimer() // Reset timer to exclude warmup
b.Run("Fallback-lo", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = lo.Sum(data)
}
})
b.Run("AVX-x2", func(b *testing.B) {
requireAVX(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt64x2(data)
}
})
b.Run("AVX2-x4", func(b *testing.B) {
requireAVX2(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt64x4(data)
}
})
b.Run("AVX512-x8", func(b *testing.B) {
requireAVX512(b)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = SumInt64x8(data)
}
})
}
================================================
FILE: exp/simd/simd.go
================================================
package simd
// Empty file to satisfy the build constraint for non-supported architectures.
================================================
FILE: exp/simd/simd_test.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import (
"fmt"
"os"
"strings"
"simd/archsimd"
)
func init() {
for _, arg := range os.Args {
if strings.HasPrefix(arg, "-test.bench=") {
bench := strings.TrimPrefix(arg, "-test.bench=")
if bench != "" && bench != "none" {
fmt.Fprintf(os.Stdout, "archsimd.X86: AVX=%v AVX2=%v AVX512=%v\n",
archsimd.X86.AVX(), archsimd.X86.AVX2(), archsimd.X86.AVX512())
break
}
}
}
}
// Type aliases for testing
type (
myInt8 int8
myInt16 int16
myInt32 int32
myInt64 int64
myUint8 uint8
myUint16 uint16
myUint32 uint32
myUint64 uint64
myFloat32 float32
myFloat64 float64
)
================================================
FILE: exp/simd/unsafe.go
================================================
//go:build go1.26 && goexperiment.simd && amd64
package simd
import "unsafe"
// unsafeSliceInt8 converts a []T (where T ~int8) to []int8 via unsafe operations.
// This helper reduces code duplication and the risk of copy-paste errors.
//
//go:nosplit
func unsafeSliceInt8[T ~int8](collection []T, length uint) []int8 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*int8)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceInt16 converts a []T (where T ~int16) to []int16 via unsafe operations.
//
//go:nosplit
func unsafeSliceInt16[T ~int16](collection []T, length uint) []int16 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*int16)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceInt32 converts a []T (where T ~int32) to []int32 via unsafe operations.
//
//go:nosplit
func unsafeSliceInt32[T ~int32](collection []T, length uint) []int32 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*int32)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceInt64 converts a []T (where T ~int64) to []int64 via unsafe operations.
//
//go:nosplit
func unsafeSliceInt64[T ~int64](collection []T, length uint) []int64 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*int64)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceUint8 converts a []T (where T ~uint8) to []uint8 via unsafe operations.
//
//go:nosplit
func unsafeSliceUint8[T ~uint8](collection []T, length uint) []uint8 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*uint8)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceUint16 converts a []T (where T ~uint16) to []uint16 via unsafe operations.
//
//go:nosplit
func unsafeSliceUint16[T ~uint16](collection []T, length uint) []uint16 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*uint16)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceUint32 converts a []T (where T ~uint32) to []uint32 via unsafe operations.
//
//go:nosplit
func unsafeSliceUint32[T ~uint32](collection []T, length uint) []uint32 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*uint32)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceUint64 converts a []T (where T ~uint64) to []uint64 via unsafe operations.
//
//go:nosplit
func unsafeSliceUint64[T ~uint64](collection []T, length uint) []uint64 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*uint64)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceFloat32 converts a []T (where T ~float32) to []float32 via unsafe operations.
//
//go:nosplit
func unsafeSliceFloat32[T ~float32](collection []T, length uint) []float32 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*float32)(unsafe.Pointer(&collection[0])), length)
}
// unsafeSliceFloat64 converts a []T (where T ~float64) to []float64 via unsafe operations.
//
//go:nosplit
func unsafeSliceFloat64[T ~float64](collection []T, length uint) []float64 {
// bearer:disable go_gosec_unsafe_unsafe
return unsafe.Slice((*float64)(unsafe.Pointer(&collection[0])), length)
}
================================================
FILE: find.go
================================================
package lo
import (
"time"
"github.com/samber/lo/internal/constraints"
"github.com/samber/lo/internal/xrand"
)
// IndexOf returns the index at which the first occurrence of a value is found in a slice or -1
// if the value cannot be found.
// Play: https://go.dev/play/p/Eo7W0lvKTky
func IndexOf[T comparable](collection []T, element T) int {
for i := range collection {
if collection[i] == element {
return i
}
}
return -1
}
// LastIndexOf returns the index at which the last occurrence of a value is found in a slice or -1
// if the value cannot be found.
// Play: https://go.dev/play/p/Eo7W0lvKTky
func LastIndexOf[T comparable](collection []T, element T) int {
length := len(collection)
for i := length - 1; i >= 0; i-- {
if collection[i] == element {
return i
}
}
return -1
}
// HasPrefix returns true if the collection has the prefix.
// Play: https://go.dev/play/p/SrljzVDpMQM
func HasPrefix[T comparable](collection, prefix []T) bool {
if len(collection) < len(prefix) {
return false
}
for i := range prefix {
if collection[i] != prefix[i] {
return false
}
}
return true
}
// HasSuffix returns true if the collection has the suffix.
// Play: https://go.dev/play/p/bJeLetQNAON
func HasSuffix[T comparable](collection, suffix []T) bool {
if len(collection) < len(suffix) {
return false
}
for i := range suffix {
if collection[len(collection)-len(suffix)+i] != suffix[i] {
return false
}
}
return true
}
// Find searches for an element in a slice based on a predicate. Returns element and true if element was found.
// Play: https://go.dev/play/p/Eo7W0lvKTky
func Find[T any](collection []T, predicate func(item T) bool) (T, bool) {
for i := range collection {
if predicate(collection[i]) {
return collection[i], true
}
}
var result T
return result, false
}
// FindErr searches for an element in a slice based on a predicate that can return an error.
// Returns the element and nil error if the element is found.
// Returns zero value and nil error if the element is not found.
// If the predicate returns an error, iteration stops immediately and returns zero value and the error.
// Play: https://go.dev/play/p/XK-qtpQWXJ9
func FindErr[T any](collection []T, predicate func(item T) (bool, error)) (T, error) {
for i := range collection {
matches, err := predicate(collection[i])
if err != nil {
var result T
return result, err
}
if matches {
return collection[i], nil
}
}
var result T
return result, nil
}
// FindIndexOf searches for an element in a slice based on a predicate and returns the index and true.
// Returns -1 and false if the element is not found.
// Play: https://go.dev/play/p/XWSEM4Ic_t0
func FindIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bool) {
for i := range collection {
if predicate(collection[i]) {
return collection[i], i, true
}
}
var result T
return result, -1, false
}
// FindLastIndexOf searches for the last element in a slice based on a predicate and returns the index and true.
// Returns -1 and false if the element is not found.
// Play: https://go.dev/play/p/2VhPMiQvX-D
func FindLastIndexOf[T any](collection []T, predicate func(item T) bool) (T, int, bool) {
length := len(collection)
for i := length - 1; i >= 0; i-- {
if predicate(collection[i]) {
return collection[i], i, true
}
}
var result T
return result, -1, false
}
// FindOrElse searches for an element in a slice based on a predicate. Returns the element if found or a given fallback value otherwise.
// Play: https://go.dev/play/p/Eo7W0lvKTky
func FindOrElse[T any](collection []T, fallback T, predicate func(item T) bool) T {
for i := range collection {
if predicate(collection[i]) {
return collection[i]
}
}
return fallback
}
// FindKey returns the key of the first value matching.
// Play: https://go.dev/play/p/Bg0w1VDPYXx
func FindKey[K, V comparable](object map[K]V, value V) (K, bool) {
for k, v := range object {
if v == value {
return k, true
}
}
return Empty[K](), false
}
// FindKeyBy returns the key of the first element predicate returns true for.
// Play: https://go.dev/play/p/9IbiPElcyo8
func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value V) bool) (K, bool) {
for k, v := range object {
if predicate(k, v) {
return k, true
}
}
return Empty[K](), false
}
// FindUniques returns a slice with all the elements that appear in the collection only once.
// The order of result values is determined by the order they occur in the collection.
// Play: https://go.dev/play/p/NV5vMK_2Z_n
func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice {
isDupl := make(map[T]bool, len(collection))
duplicates := 0
for i := range collection {
duplicated, seen := isDupl[collection[i]]
if !duplicated {
isDupl[collection[i]] = seen
if seen {
duplicates++
}
}
}
result := make(Slice, 0, len(isDupl)-duplicates)
for i := range collection {
if duplicated := isDupl[collection[i]]; !duplicated {
result = append(result, collection[i])
}
}
return result
}
// FindUniquesBy returns a slice with all the elements that appear in the collection only once.
// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
// Play: https://go.dev/play/p/2vmxCs4kW_m
func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice {
isDupl := make(map[U]bool, len(collection))
duplicates := 0
for i := range collection {
key := iteratee(collection[i])
duplicated, seen := isDupl[key]
if !duplicated {
isDupl[key] = seen
if seen {
duplicates++
}
}
}
result := make(Slice, 0, len(isDupl)-duplicates)
for i := range collection {
key := iteratee(collection[i])
if duplicated := isDupl[key]; !duplicated {
result = append(result, collection[i])
}
}
return result
}
// FindDuplicates returns a slice with the first occurrence of each duplicated element in the collection.
// The order of result values is determined by the order they occur in the collection.
// Play: https://go.dev/play/p/muFgL_XBwoP
func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice {
isDupl := make(map[T]bool, len(collection))
duplicates := 0
for i := range collection {
duplicated, seen := isDupl[collection[i]]
if !duplicated {
isDupl[collection[i]] = seen
if seen {
duplicates++
}
}
}
result := make(Slice, 0, duplicates)
for i := range collection {
if duplicated := isDupl[collection[i]]; duplicated {
result = append(result, collection[i])
isDupl[collection[i]] = false
}
}
return result
}
// FindDuplicatesBy returns a slice with the first occurrence of each duplicated element in the collection.
// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
// Play: https://go.dev/play/p/LKdYdNHuGJG
func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice {
isDupl := make(map[U]bool, len(collection))
duplicates := 0
for i := range collection {
key := iteratee(collection[i])
duplicated, seen := isDupl[key]
if !duplicated {
isDupl[key] = seen
if seen {
duplicates++
}
}
}
result := make(Slice, 0, duplicates)
for i := range collection {
key := iteratee(collection[i])
if duplicated := isDupl[key]; duplicated {
result = append(result, collection[i])
isDupl[key] = false
}
}
return result
}
// FindDuplicatesByErr returns a slice with the first occurrence of each duplicated element in the collection.
// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
// If the iteratee returns an error, iteration stops immediately and the error is returned with a nil slice.
// Play: https://go.dev/play/p/HiVILQqdFP0
func FindDuplicatesByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error) {
isDupl := make(map[U]bool, len(collection))
duplicates := 0
// First pass: identify duplicates
for i := range collection {
key, err := iteratee(collection[i])
if err != nil {
var result Slice
return result, err
}
duplicated, seen := isDupl[key]
if !duplicated {
isDupl[key] = seen
if seen {
duplicates++
}
}
}
result := make(Slice, 0, duplicates)
// Second pass: collect first occurrences of duplicates
for i := range collection {
key, err := iteratee(collection[i])
if err != nil {
var result Slice
return result, err
}
if duplicated := isDupl[key]; duplicated {
result = append(result, collection[i])
isDupl[key] = false
}
}
return result, nil
}
// Min search the minimum value of a collection.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/fJFLwpY8eMN
func Min[T constraints.Ordered](collection []T) T {
var mIn T
if len(collection) == 0 {
return mIn
}
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if item < mIn {
mIn = item
}
}
return mIn
}
// MinIndex search the minimum value of a collection and the index of the minimum value.
// Returns (zero value, -1) when the collection is empty.
// Play: https://go.dev/play/p/RxAidik4p50
func MinIndex[T constraints.Ordered](collection []T) (T, int) {
var (
mIn T
index int
)
if len(collection) == 0 {
return mIn, -1
}
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if item < mIn {
mIn = item
index = i
}
}
return mIn, index
}
// MinBy search the minimum value of a collection using the given comparison function.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/-B1PsrHVnfx
func MinBy[T any](collection []T, less func(a, b T) bool) T {
var mIn T
if len(collection) == 0 {
return mIn
}
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if less(item, mIn) {
mIn = item
}
}
return mIn
}
// MinByErr search the minimum value of a collection using the given comparison function.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns zero value and nil error when the collection is empty.
// If the comparison function returns an error, iteration stops and the error is returned.
// Play: https://go.dev/play/p/nvDYGS8q895
func MinByErr[T any](collection []T, less func(a, b T) (bool, error)) (T, error) {
var mIn T
if len(collection) == 0 {
return mIn, nil
}
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
isLess, err := less(item, mIn)
if err != nil {
var zero T
return zero, err
}
if isLess {
mIn = item
}
}
return mIn, nil
}
// MinIndexBy search the minimum value of a collection using the given comparison function and the index of the minimum value.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
// Play: https://go.dev/play/p/zwwPRqWhnUY
func MinIndexBy[T any](collection []T, less func(a, b T) bool) (T, int) {
var (
mIn T
index int
)
if len(collection) == 0 {
return mIn, -1
}
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if less(item, mIn) {
mIn = item
index = i
}
}
return mIn, index
}
// MinIndexByErr search the minimum value of a collection using the given comparison function and the index of the minimum value.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
// Comparison function can return an error to stop iteration immediately.
// Play: https://go.dev/play/p/MUqi_NvTKM1
func MinIndexByErr[T any](collection []T, less func(a, b T) (bool, error)) (T, int, error) {
var (
mIn T
index int
)
if len(collection) == 0 {
return mIn, -1, nil
}
mIn = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
isLess, err := less(item, mIn)
if err != nil {
var zero T
return zero, -1, err
}
if isLess {
mIn = item
index = i
}
}
return mIn, index, nil
}
// Earliest search the minimum time.Time of a collection.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/pRyy0c6hsBs
func Earliest(times ...time.Time) time.Time {
var mIn time.Time
if len(times) == 0 {
return mIn
}
mIn = times[0]
for i := 1; i < len(times); i++ {
item := times[i]
if item.Before(mIn) {
mIn = item
}
}
return mIn
}
// EarliestBy search the minimum time.Time of a collection using the given iteratee function.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/0XvCF6vuLXC
func EarliestBy[T any](collection []T, iteratee func(item T) time.Time) T {
var earliest T
if len(collection) == 0 {
return earliest
}
earliest = collection[0]
earliestTime := iteratee(collection[0])
for i := 1; i < len(collection); i++ {
itemTime := iteratee(collection[i])
if itemTime.Before(earliestTime) {
earliest = collection[i]
earliestTime = itemTime
}
}
return earliest
}
// EarliestByErr search the minimum time.Time of a collection using the given iteratee function.
// Returns zero value and nil error when the collection is empty.
// If the iteratee returns an error, iteration stops and the error is returned.
// Play: https://go.dev/play/p/zJUBUj7ANvq
func EarliestByErr[T any](collection []T, iteratee func(item T) (time.Time, error)) (T, error) {
var earliest T
if len(collection) == 0 {
return earliest, nil
}
earliestTime, err := iteratee(collection[0])
if err != nil {
return earliest, err
}
earliest = collection[0]
for i := 1; i < len(collection); i++ {
itemTime, err := iteratee(collection[i])
if err != nil {
return earliest, err
}
if itemTime.Before(earliestTime) {
earliest = collection[i]
earliestTime = itemTime
}
}
return earliest, nil
}
// Max searches the maximum value of a collection.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/wYvG8gRRFw-
func Max[T constraints.Ordered](collection []T) T {
var mAx T
if len(collection) == 0 {
return mAx
}
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if item > mAx {
mAx = item
}
}
return mAx
}
// MaxIndex searches the maximum value of a collection and the index of the maximum value.
// Returns (zero value, -1) when the collection is empty.
// Play: https://go.dev/play/p/RFkB4Mzb1qt
func MaxIndex[T constraints.Ordered](collection []T) (T, int) {
var (
mAx T
index int
)
if len(collection) == 0 {
return mAx, -1
}
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if item > mAx {
mAx = item
index = i
}
}
return mAx, index
}
// MaxBy search the maximum value of a collection using the given comparison function.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns zero value when the collection is empty.
//
// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
// See https://github.com/samber/lo/issues/129
//
// Play: https://go.dev/play/p/PJCc-ThrwX1
func MaxBy[T any](collection []T, greater func(a, b T) bool) T {
var mAx T
if len(collection) == 0 {
return mAx
}
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if greater(item, mAx) {
mAx = item
}
}
return mAx
}
// MaxByErr search the maximum value of a collection using the given comparison function.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns zero value and nil error when the collection is empty.
// If the comparison function returns an error, iteration stops and the error is returned.
//
// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
// See https://github.com/samber/lo/issues/129
//
// Play: https://go.dev/play/p/s-63-6_9zqM
func MaxByErr[T any](collection []T, greater func(a, b T) (bool, error)) (T, error) {
var mAx T
if len(collection) == 0 {
return mAx, nil
}
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
isGreater, err := greater(item, mAx)
if err != nil {
return mAx, err
}
if isGreater {
mAx = item
}
}
return mAx, nil
}
// MaxIndexBy search the maximum value of a collection using the given comparison function and the index of the maximum value.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
//
// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
// See https://github.com/samber/lo/issues/129
//
// Play: https://go.dev/play/p/5yd4W7pe2QJ
func MaxIndexBy[T any](collection []T, greater func(a, b T) bool) (T, int) {
var (
mAx T
index int
)
if len(collection) == 0 {
return mAx, -1
}
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
if greater(item, mAx) {
mAx = item
index = i
}
}
return mAx, index
}
// MaxIndexByErr search the maximum value of a collection using the given comparison function and the index of the maximum value.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns (zero value, -1, nil) when the collection is empty.
// If the comparison function returns an error, iteration stops and the error is returned.
//
// Note: the comparison function is inconsistent with most languages, since we use the opposite of the usual convention.
// See https://github.com/samber/lo/issues/129
func MaxIndexByErr[T any](collection []T, greater func(a, b T) (bool, error)) (T, int, error) {
var (
mAx T
index int
)
if len(collection) == 0 {
return mAx, -1, nil
}
mAx = collection[0]
for i := 1; i < len(collection); i++ {
item := collection[i]
isGreater, err := greater(item, mAx)
if err != nil {
var zero T
return zero, -1, err
}
if isGreater {
mAx = item
index = i
}
}
return mAx, index, nil
}
// Latest search the maximum time.Time of a collection.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/dBfdf5s8s-Y
func Latest(times ...time.Time) time.Time {
var mAx time.Time
if len(times) == 0 {
return mAx
}
mAx = times[0]
for i := 1; i < len(times); i++ {
item := times[i]
if item.After(mAx) {
mAx = item
}
}
return mAx
}
// LatestBy search the maximum time.Time of a collection using the given iteratee function.
// Returns zero value when the collection is empty.
// Play: https://go.dev/play/p/p1HA8XumaMU
func LatestBy[T any](collection []T, iteratee func(item T) time.Time) T {
var latest T
if len(collection) == 0 {
return latest
}
latest = collection[0]
latestTime := iteratee(collection[0])
for i := 1; i < len(collection); i++ {
itemTime := iteratee(collection[i])
if itemTime.After(latestTime) {
latest = collection[i]
latestTime = itemTime
}
}
return latest
}
// LatestByErr search the maximum time.Time of a collection using the given iteratee function.
// Returns zero value and nil error when the collection is empty.
// If the iteratee returns an error, iteration stops and the error is returned.
// Play: https://go.dev/play/p/WpBUptwnxuG
func LatestByErr[T any](collection []T, iteratee func(item T) (time.Time, error)) (T, error) {
var latest T
if len(collection) == 0 {
return latest, nil
}
latestTime, err := iteratee(collection[0])
if err != nil {
return latest, err
}
latest = collection[0]
for i := 1; i < len(collection); i++ {
itemTime, err := iteratee(collection[i])
if err != nil {
return latest, err
}
if itemTime.After(latestTime) {
latest = collection[i]
latestTime = itemTime
}
}
return latest, nil
}
// First returns the first element of a collection and check for availability of the first element.
// Play: https://go.dev/play/p/94lu5X6_cbf
func First[T any](collection []T) (T, bool) {
length := len(collection)
if length == 0 {
var t T
return t, false
}
return collection[0], true
}
// FirstOrEmpty returns the first element of a collection or zero value if empty.
// Play: https://go.dev/play/p/i200n9wgrDA
func FirstOrEmpty[T any](collection []T) T {
i, _ := First(collection)
return i
}
// FirstOr returns the first element of a collection or the fallback value if empty.
// Play: https://go.dev/play/p/x9CxQyRFXeZ
func FirstOr[T any](collection []T, fallback T) T {
i, ok := First(collection)
if !ok {
return fallback
}
return i
}
// Last returns the last element of a collection or error if empty.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func Last[T any](collection []T) (T, bool) {
length := len(collection)
if length == 0 {
var t T
return t, false
}
return collection[length-1], true
}
// LastOrEmpty returns the last element of a collection or zero value if empty.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func LastOrEmpty[T any](collection []T) T {
i, _ := Last(collection)
return i
}
// LastOr returns the last element of a collection or the fallback value if empty.
// Play: https://go.dev/play/p/ul45Z0y2EFO
func LastOr[T any](collection []T, fallback T) T {
i, ok := Last(collection)
if !ok {
return fallback
}
return i
}
// Nth returns the element at index `nth` of collection. If `nth` is negative, the nth element
// from the end is returned. An error is returned when nth is out of slice bounds.
// Play: https://go.dev/play/p/mNFI9-kIZZ5
func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) {
value, ok := sliceNth(collection, nth)
return value, Validate(ok, "nth: %d out of slice bounds", nth)
}
func sliceNth[T any, N constraints.Integer](collection []T, nth N) (T, bool) {
n := int(nth)
l := len(collection)
if n >= l || -n > l {
return Empty[T](), false
}
if n >= 0 {
return collection[n], true
}
return collection[l+n], true
}
// NthOr returns the element at index `nth` of collection.
// If `nth` is negative, it returns the nth element from the end.
// If `nth` is out of slice bounds, it returns the fallback value instead of an error.
// Play: https://go.dev/play/p/njKcNhBBVsF
func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T {
value, ok := sliceNth(collection, nth)
if !ok {
return fallback
}
return value
}
// NthOrEmpty returns the element at index `nth` of collection.
// If `nth` is negative, it returns the nth element from the end.
// If `nth` is out of slice bounds, it returns the zero value (empty value) for that type.
// Play: https://go.dev/play/p/sHoh88KWt6B
func NthOrEmpty[T any, N constraints.Integer](collection []T, nth N) T {
value, _ := sliceNth(collection, nth)
return value
}
// randomIntGenerator is a function that should return a random integer in the range [0, n)
// where n is the argument passed to the randomIntGenerator.
type randomIntGenerator func(n int) int
// Sample returns a random item from collection.
// Play: https://go.dev/play/p/vCcSJbh5s6l
func Sample[T any](collection []T) T {
return SampleBy(collection, xrand.IntN)
}
// SampleBy returns a random item from collection, using randomIntGenerator as the random index generator.
// Play: https://go.dev/play/p/HDmKmMgq0XN
func SampleBy[T any](collection []T, randomIntGenerator randomIntGenerator) T {
size := len(collection)
if size == 0 {
return Empty[T]()
}
return collection[randomIntGenerator(size)]
}
// Samples returns N random unique items from collection.
// Play: https://go.dev/play/p/QYRD8aufD0C
func Samples[T any, Slice ~[]T](collection Slice, count int) Slice {
return SamplesBy(collection, count, xrand.IntN)
}
// SamplesBy returns N random unique items from collection, using randomIntGenerator as the random index generator.
// Play: https://go.dev/play/p/Dy9bGDhD_Gw
func SamplesBy[T any, Slice ~[]T](collection Slice, count int, randomIntGenerator randomIntGenerator) Slice {
if count <= 0 {
return Slice{}
}
size := len(collection)
if size < count {
count = size
}
indexes := Range(size)
results := make(Slice, count)
for i := range results {
n := len(indexes)
index := randomIntGenerator(n)
results[i] = collection[indexes[index]]
// Removes index.
// It is faster to swap with last element and remove it.
indexes[index] = indexes[n-1]
indexes = indexes[:n-1]
}
return results
}
================================================
FILE: find_test.go
================================================
package lo
import (
"errors"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestIndexOf(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := IndexOf([]int{0, 1, 2, 1, 2, 3}, 2)
result2 := IndexOf([]int{0, 1, 2, 1, 2, 3}, 6)
is.Equal(2, result1)
is.Equal(-1, result2)
}
func TestLastIndexOf(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 2)
result2 := LastIndexOf([]int{0, 1, 2, 1, 2, 3}, 6)
is.Equal(4, result1)
is.Equal(-1, result2)
}
func TestHasPrefix(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(HasPrefix([]int{1, 2, 3, 4}, []int{1, 2}))
is.False(HasPrefix([]int{1, 2, 3, 4}, []int{42}))
is.True(HasPrefix([]int{1, 2, 3, 4}, nil))
}
func TestHasSuffix(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(HasSuffix([]int{1, 2, 3, 4}, []int{3, 4}))
is.False(HasSuffix([]int{1, 2, 3, 4}, []int{42}))
is.True(HasSuffix([]int{1, 2, 3, 4}, nil))
}
func TestFind(t *testing.T) {
t.Parallel()
is := assert.New(t)
index := 0
result1, ok1 := Find([]string{"a", "b", "c", "d"}, func(item string) bool {
is.Equal([]string{"a", "b", "c", "d"}[index], item)
index++
return item == "b"
})
result2, ok2 := Find([]string{"foobar"}, func(item string) bool {
is.Equal("foobar", item)
return item == "b"
})
is.True(ok1)
is.Equal("b", result1)
is.False(ok2)
is.Empty(result2)
}
func TestFindErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
// Test normal operation (no error) - table driven
tests := []struct {
name string
input []string
expected string
}{
{
name: "finds matching element",
input: []string{"a", "b", "c", "d"},
expected: "b",
},
{
name: "element not found",
input: []string{"foobar"},
expected: "",
},
{
name: "empty collection",
input: []string{},
expected: "",
},
{
name: "single element found",
input: []string{"b"},
expected: "b",
},
{
name: "single element not found",
input: []string{"a"},
expected: "",
},
{
name: "finds first match",
input: []string{"a", "b", "c", "b"},
expected: "b", // first "b"
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result, err := FindErr(tt.input, func(item string) (bool, error) {
return item == "b", nil
})
is.NoError(err)
is.Equal(tt.expected, result)
})
}
// Test error cases - table driven with callback count verification
errorTests := []struct {
name string
input []string
errorAt string
expectedCalls int
}{
{
name: "error at first element",
input: []string{"b", "c", "d"},
errorAt: "b",
expectedCalls: 1,
},
{
name: "error at second element",
input: []string{"a", "b", "c"},
errorAt: "b",
expectedCalls: 2,
},
{
name: "error at third element",
input: []string{"a", "c", "b"},
errorAt: "b",
expectedCalls: 3,
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
result, err := FindErr(tt.input, func(item string) (bool, error) {
callbackCount++
if item == tt.errorAt {
return false, testErr
}
return item == "b", nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount, "callback count mismatch - iteration didn't stop early")
is.Empty(result, "zero value should be returned on error")
})
}
}
func TestFindIndexOf(t *testing.T) {
t.Parallel()
is := assert.New(t)
index := 0
item1, index1, ok1 := FindIndexOf([]string{"a", "b", "c", "d", "b"}, func(item string) bool {
is.Equal([]string{"a", "b", "c", "d", "b"}[index], item)
index++
return item == "b"
})
item2, index2, ok2 := FindIndexOf([]string{"foobar"}, func(item string) bool {
is.Equal("foobar", item)
return item == "b"
})
is.Equal("b", item1)
is.True(ok1)
is.Equal(1, index1)
is.Empty(item2)
is.False(ok2)
is.Equal(-1, index2)
}
func TestFindLastIndexOf(t *testing.T) {
t.Parallel()
is := assert.New(t)
index := 0
item1, index1, ok1 := FindLastIndexOf([]string{"a", "b", "c", "d", "b"}, func(item string) bool {
is.Equal([]string{"b", "d", "c", "b", "a"}[index], item)
index++
return item == "b"
})
item2, index2, ok2 := FindLastIndexOf([]string{"foobar"}, func(item string) bool {
is.Equal("foobar", item)
return item == "b"
})
is.Equal("b", item1)
is.True(ok1)
is.Equal(4, index1)
is.Empty(item2)
is.False(ok2)
is.Equal(-1, index2)
}
func TestFindOrElse(t *testing.T) {
t.Parallel()
is := assert.New(t)
index := 0
result1 := FindOrElse([]string{"a", "b", "c", "d"}, "x", func(item string) bool {
is.Equal([]string{"a", "b", "c", "d"}[index], item)
index++
return item == "b"
})
result2 := FindOrElse([]string{"foobar"}, "x", func(item string) bool {
is.Equal("foobar", item)
return item == "b"
})
is.Equal("b", result1)
is.Equal("x", result2)
}
func TestFindKey(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, ok1 := FindKey(map[string]int{"foo": 1, "bar": 2, "baz": 3}, 2)
is.Equal("bar", result1)
is.True(ok1)
result2, ok2 := FindKey(map[string]int{"foo": 1, "bar": 2, "baz": 3}, 42)
is.Empty(result2)
is.False(ok2)
type test struct {
foobar string
}
result3, ok3 := FindKey(map[string]test{"foo": {"foo"}, "bar": {"bar"}, "baz": {"baz"}}, test{"foo"})
is.Equal("foo", result3)
is.True(ok3)
result4, ok4 := FindKey(map[string]test{"foo": {"foo"}, "bar": {"bar"}, "baz": {"baz"}}, test{"hello world"})
is.Empty(result4)
is.False(ok4)
}
func TestFindKeyBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, ok1 := FindKeyBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool {
return k == "foo"
})
is.Equal("foo", result1)
is.True(ok1)
result2, ok2 := FindKeyBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool {
return false
})
is.Empty(result2)
is.False(ok2)
}
func TestFindUniques(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FindUniques([]int{1, 2, 3})
is.Equal([]int{1, 2, 3}, result1)
result2 := FindUniques([]int{1, 2, 2, 3, 1, 2})
is.Equal([]int{3}, result2)
result3 := FindUniques([]int{1, 2, 2, 1})
is.Empty(result3)
result4 := FindUniques([]int{})
is.Empty(result4)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := FindUniques(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFindUniquesBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FindUniquesBy([]int{0, 1, 2}, func(i int) int {
return i % 3
})
is.Equal([]int{0, 1, 2}, result1)
result2 := FindUniquesBy([]int{0, 1, 2, 3, 4}, func(i int) int {
return i % 3
})
is.Equal([]int{2}, result2)
result3 := FindUniquesBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i % 3
})
is.Empty(result3)
result4 := FindUniquesBy([]int{}, func(i int) int {
return i % 3
})
is.Empty(result4)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := FindUniquesBy(allStrings, func(i string) string {
return i
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFindDuplicates(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FindDuplicates([]int{1, 2, 2, 1, 2, 3})
is.Equal([]int{1, 2}, result1)
result2 := FindDuplicates([]int{1, 2, 3})
is.Empty(result2)
result3 := FindDuplicates([]int{})
is.Empty(result3)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := FindDuplicates(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFindDuplicatesBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FindDuplicatesBy([]int{3, 4, 5, 6, 7}, func(i int) int {
return i % 3
})
is.Equal([]int{3, 4}, result1)
result2 := FindDuplicatesBy([]int{0, 1, 2, 3, 4}, func(i int) int {
return i % 5
})
is.Empty(result2)
result3 := FindDuplicatesBy([]int{}, func(i int) int {
return i % 3
})
is.Empty(result3)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := FindDuplicatesBy(allStrings, func(i string) string {
return i
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFindDuplicatesByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Table-driven tests for normal operation
tests := []struct {
name string
input []int
expected []int
}{
{
name: "finds duplicates by key",
input: []int{3, 4, 5, 6, 7},
expected: []int{3, 4},
},
{
name: "no duplicates",
input: []int{0, 1, 2, 3, 4},
expected: []int{0, 1},
},
{
name: "empty collection",
input: []int{},
expected: []int{},
},
{
name: "all duplicates",
input: []int{0, 3, 6, 9},
expected: []int{0},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result, err := FindDuplicatesByErr(tt.input, func(i int) (int, error) {
return i % 3, nil
})
is.NoError(err)
is.Equal(tt.expected, result)
})
}
// Table-driven tests with callback count verification for early return
testErr := errors.New("test error")
errorTests := []struct {
name string
input []int
errorAt int
expectedCalls int
}{
{
name: "error in first pass at element 0",
input: []int{3, 4, 5},
errorAt: 0,
expectedCalls: 1,
},
{
name: "error in first pass at element 2",
input: []int{3, 4, 5},
errorAt: 2,
expectedCalls: 3,
},
{
name: "error in second pass at first duplicate",
input: []int{3, 4, 5, 6},
errorAt: 3,
expectedCalls: 4, // First pass completes (4 items), error at first item of second pass
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
result, err := FindDuplicatesByErr(tt.input, func(i int) (int, error) {
callbackCount++
if i == tt.input[tt.errorAt] {
return 0, testErr
}
return i % 3, nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount, "callback count mismatch - iteration didn't stop early")
is.Nil(result, "nil should be returned on error")
})
}
// Test type preservation
type myStrings []string
allStrings := myStrings{"a", "b", "a", "c", "b"}
result, err := FindDuplicatesByErr(allStrings, func(s string) (string, error) {
return s, nil
})
is.NoError(err)
is.IsType(result, allStrings, "type preserved")
}
func TestMin(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Min([]int{1, 2, 3})
result2 := Min([]int{3, 2, 1})
result3 := Min([]time.Duration{time.Second, time.Minute, time.Hour})
result4 := Min([]int{})
is.Equal(1, result1)
is.Equal(1, result2)
is.Equal(time.Second, result3)
is.Zero(result4)
}
func TestMinIndex(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, index1 := MinIndex([]int{1, 2, 3})
result2, index2 := MinIndex([]int{3, 2, 1})
result3, index3 := MinIndex([]time.Duration{time.Second, time.Minute, time.Hour})
result4, index4 := MinIndex([]int{})
is.Equal(1, result1)
is.Zero(index1)
is.Equal(1, result2)
is.Equal(2, index2)
is.Equal(time.Second, result3)
is.Zero(index3)
is.Zero(result4)
is.Equal(-1, index4)
}
func TestMinBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MinBy([]string{"s1", "string2", "s3"}, func(item, mIn string) bool {
return len(item) < len(mIn)
})
result2 := MinBy([]string{"string1", "string2", "s3"}, func(item, mIn string) bool {
return len(item) < len(mIn)
})
result3 := MinBy([]string{}, func(item, mIn string) bool {
return len(item) < len(mIn)
})
is.Equal("s1", result1)
is.Equal("s3", result2)
is.Empty(result3)
}
func TestMinByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
// Test normal operation (no error) - table driven
tests := []struct {
name string
input []string
expected string
}{
{
name: "finds min by length - first match",
input: []string{"s1", "string2", "s3"},
expected: "s1",
},
{
name: "finds min by length - third match",
input: []string{"string1", "string2", "s3"},
expected: "s3",
},
{
name: "empty collection",
input: []string{},
expected: "",
},
{
name: "single element",
input: []string{"single"},
expected: "single",
},
{
name: "all equal length",
input: []string{"a", "b", "c"},
expected: "a", // first minimal value
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result, err := MinByErr(tt.input, func(item, mIn string) (bool, error) {
return len(item) < len(mIn), nil
})
is.NoError(err)
is.Equal(tt.expected, result)
})
}
// Test error cases - table driven
errorTests := []struct {
name string
input []string
errorAt string
expectedCalls int
}{
{
name: "error at second comparison",
input: []string{"a", "bb", "ccc"},
errorAt: "bb",
expectedCalls: 1, // Only first comparison (initial element vs second)
},
{
name: "error at third comparison",
input: []string{"a", "bb", "ccc"},
errorAt: "ccc",
expectedCalls: 2, // First two comparisons
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
result, err := MinByErr(tt.input, func(item, mIn string) (bool, error) {
callbackCount++
if item == tt.errorAt {
return false, testErr
}
return len(item) < len(mIn), nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount)
is.Empty(result) // Zero value on error
})
}
}
func TestMinIndexBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, index1 := MinIndexBy([]string{"s1", "string2", "s3"}, func(item, mIn string) bool {
return len(item) < len(mIn)
})
result2, index2 := MinIndexBy([]string{"string1", "string2", "s3"}, func(item, mIn string) bool {
return len(item) < len(mIn)
})
result3, index3 := MinIndexBy([]string{}, func(item, mIn string) bool {
return len(item) < len(mIn)
})
is.Equal("s1", result1)
is.Zero(index1)
is.Equal("s3", result2)
is.Equal(2, index2)
is.Empty(result3)
is.Equal(-1, index3)
}
func TestMinIndexByErr(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input []string
less func(a, b string) (bool, error)
wantValue string
wantIndex int
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "empty slice",
input: []string{},
less: func(a, b string) (bool, error) { return len(a) < len(b), nil },
wantValue: "",
wantIndex: -1,
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "success case",
input: []string{"s1", "string2", "s3"},
less: func(a, b string) (bool, error) { return len(a) < len(b), nil },
wantValue: "s1",
wantIndex: 0,
wantErr: false,
expectedCallbackCount: 2,
},
{
name: "error on first comparison",
input: []string{"s1", "string2", "s3"},
less: func(a, b string) (bool, error) {
return false, errors.New("comparison error")
},
wantValue: "",
wantIndex: -1,
wantErr: true,
errMsg: "comparison error",
expectedCallbackCount: 1,
},
{
name: "error on second comparison",
input: []string{"a", "bb", "ccc", "error", "e"},
less: func(a, b string) (bool, error) {
if a == "error" || b == "error" {
return false, errors.New("error value encountered")
}
return len(a) < len(b), nil
},
wantValue: "",
wantIndex: -1,
wantErr: true,
errMsg: "error value encountered",
expectedCallbackCount: 3,
},
{
name: "single element",
input: []string{"single"},
less: func(a, b string) (bool, error) { return len(a) < len(b), nil },
wantValue: "single",
wantIndex: 0,
wantErr: false,
expectedCallbackCount: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
wrappedLess := func(a, b string) (bool, error) {
callbackCount++
return tt.less(a, b)
}
value, index, err := MinIndexByErr(tt.input, wrappedLess)
if tt.wantErr {
assert.Error(t, err)
if tt.errMsg != "" {
assert.Equal(t, tt.errMsg, err.Error())
}
assert.Empty(t, value)
assert.Equal(t, -1, index)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.wantValue, value)
assert.Equal(t, tt.wantIndex, index)
}
assert.Equal(t, tt.expectedCallbackCount, callbackCount, "callback count mismatch")
})
}
}
func TestEarliest(t *testing.T) {
t.Parallel()
is := assert.New(t)
a := time.Now()
b := a.Add(time.Hour)
result1 := Earliest(a, b)
result2 := Earliest()
is.Equal(a, result1)
is.Zero(result2)
}
func TestEarliestBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type foo struct {
bar time.Time
}
t1 := time.Now()
t2 := t1.Add(time.Hour)
t3 := t1.Add(-time.Hour)
result1 := EarliestBy([]foo{{t1}, {t2}, {t3}}, func(i foo) time.Time {
return i.bar
})
result2 := EarliestBy([]foo{{t1}}, func(i foo) time.Time {
return i.bar
})
result3 := EarliestBy([]foo{}, func(i foo) time.Time {
return i.bar
})
is.Equal(foo{t3}, result1)
is.Equal(foo{t1}, result2)
is.Zero(result3)
}
func TestEarliestByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
type foo struct {
bar time.Time
}
t1 := time.Now()
t2 := t1.Add(time.Hour)
t3 := t1.Add(-time.Hour)
// Test normal operation (no error) - table driven
tests := []struct {
name string
input []foo
expected foo
}{
{
name: "finds earliest time",
input: []foo{{t1}, {t2}, {t3}},
expected: foo{t3},
},
{
name: "single element",
input: []foo{{t1}},
expected: foo{t1},
},
{
name: "empty collection",
input: []foo{},
expected: foo{},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result, err := EarliestByErr(tt.input, func(i foo) (time.Time, error) {
return i.bar, nil
})
is.NoError(err)
is.Equal(tt.expected, result)
})
}
// Test error cases - table driven
errorTests := []struct {
name string
input []foo
errorAt int
expectedCalls int
}{
{
name: "error at first element",
input: []foo{{t1}, {t2}, {t3}},
errorAt: 0,
expectedCalls: 1, // Only first callback
},
{
name: "error at second element",
input: []foo{{t1}, {t2}, {t3}},
errorAt: 1,
expectedCalls: 2, // First two callbacks
},
{
name: "error at third element",
input: []foo{{t1}, {t2}, {t3}},
errorAt: 2,
expectedCalls: 3, // All three callbacks
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
_, err := EarliestByErr(tt.input, func(i foo) (time.Time, error) {
callbackCount++
if len(tt.input) > 0 && i == tt.input[tt.errorAt] {
return time.Time{}, testErr
}
return i.bar, nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount)
})
}
}
func TestMax(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Max([]int{1, 2, 3})
result2 := Max([]int{3, 2, 1})
result3 := Max([]time.Duration{time.Second, time.Minute, time.Hour})
result4 := Max([]int{})
is.Equal(3, result1)
is.Equal(3, result2)
is.Equal(time.Hour, result3)
is.Zero(result4)
}
func TestMaxIndex(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, index1 := MaxIndex([]int{1, 2, 3})
result2, index2 := MaxIndex([]int{3, 2, 1})
result3, index3 := MaxIndex([]time.Duration{time.Second, time.Minute, time.Hour})
result4, index4 := MaxIndex([]int{})
is.Equal(3, result1)
is.Equal(2, index1)
is.Equal(3, result2)
is.Zero(index2)
is.Equal(time.Hour, result3)
is.Equal(2, index3)
is.Zero(result4)
is.Equal(-1, index4)
}
func TestMaxBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MaxBy([]string{"s1", "string2", "s3"}, func(item, mAx string) bool {
return len(item) > len(mAx)
})
result2 := MaxBy([]string{"string1", "string2", "s3"}, func(item, mAx string) bool {
return len(item) > len(mAx)
})
result3 := MaxBy([]string{}, func(item, mAx string) bool {
return len(item) > len(mAx)
})
is.Equal("string2", result1)
is.Equal("string1", result2)
is.Empty(result3)
}
func TestMaxByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
// Test normal operation (no error) - table driven
tests := []struct {
name string
input []string
expected string
}{
{
name: "finds max by length - second match",
input: []string{"s1", "string2", "s3"},
expected: "string2",
},
{
name: "finds max by length - first match",
input: []string{"string1", "string2", "s3"},
expected: "string1",
},
{
name: "empty collection",
input: []string{},
expected: "",
},
{
name: "single element",
input: []string{"single"},
expected: "single",
},
{
name: "all equal length",
input: []string{"a", "b", "c"},
expected: "a", // first maximal value
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result, err := MaxByErr(tt.input, func(item, mAx string) (bool, error) {
return len(item) > len(mAx), nil
})
is.NoError(err)
is.Equal(tt.expected, result)
})
}
// Test error cases - table driven
errorTests := []struct {
name string
input []string
errorAt string
expectedCalls int
}{
{
name: "error at second comparison",
input: []string{"a", "bb", "ccc"},
errorAt: "bb",
expectedCalls: 1, // Only first comparison (initial element vs second)
},
{
name: "error at third comparison",
input: []string{"a", "bb", "ccc"},
errorAt: "ccc",
expectedCalls: 2, // First two comparisons
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
result, err := MaxByErr(tt.input, func(item, mAx string) (bool, error) {
callbackCount++
if item == tt.errorAt {
return false, testErr
}
return len(item) > len(mAx), nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount)
// Result should be the current max at the time of error
if tt.expectedCalls == 1 {
is.Equal("a", result) // Still the first element
} else {
is.Equal("bb", result) // "bb" became max after second comparison
}
})
}
}
func TestMaxIndexBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, index1 := MaxIndexBy([]string{"s1", "string2", "s3"}, func(item, mAx string) bool {
return len(item) > len(mAx)
})
result2, index2 := MaxIndexBy([]string{"string1", "string2", "s3"}, func(item, mAx string) bool {
return len(item) > len(mAx)
})
result3, index3 := MaxIndexBy([]string{}, func(item, mAx string) bool {
return len(item) > len(mAx)
})
is.Equal("string2", result1)
is.Equal(1, index1)
is.Equal("string1", result2)
is.Zero(index2)
is.Empty(result3)
is.Equal(-1, index3)
}
func TestMaxIndexByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
// Test normal operation (no error) - table driven
tests := []struct {
name string
input []string
expectedResult string
expectedIndex int
}{
{
name: "finds max by length - second match",
input: []string{"s1", "string2", "s3"},
expectedResult: "string2",
expectedIndex: 1,
},
{
name: "finds max by length - first match",
input: []string{"string1", "string2", "s3"},
expectedResult: "string1",
expectedIndex: 0,
},
{
name: "empty collection",
input: []string{},
expectedResult: "",
expectedIndex: -1,
},
{
name: "single element",
input: []string{"single"},
expectedResult: "single",
expectedIndex: 0,
},
{
name: "all equal length",
input: []string{"a", "b", "c"},
expectedResult: "a", // first maximal value
expectedIndex: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result, index, err := MaxIndexByErr(tt.input, func(item, mAx string) (bool, error) {
return len(item) > len(mAx), nil
})
is.NoError(err)
is.Equal(tt.expectedResult, result)
is.Equal(tt.expectedIndex, index)
})
}
// Test error cases - table driven
errorTests := []struct {
name string
input []string
errorAt string
expectedCalls int
expectedResult string
expectedIndex int
}{
{
name: "error at second comparison",
input: []string{"a", "bb", "ccc"},
errorAt: "bb",
expectedCalls: 1, // Only first comparison (initial element vs second)
expectedResult: "",
expectedIndex: -1,
},
{
name: "error at third comparison",
input: []string{"a", "bb", "ccc"},
errorAt: "ccc",
expectedCalls: 2, // First two comparisons
expectedResult: "",
expectedIndex: -1,
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
result, index, err := MaxIndexByErr(tt.input, func(item, mAx string) (bool, error) {
callbackCount++
if item == tt.errorAt {
return false, testErr
}
return len(item) > len(mAx), nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount)
is.Equal(tt.expectedResult, result)
is.Equal(tt.expectedIndex, index)
})
}
}
func TestLatest(t *testing.T) {
t.Parallel()
is := assert.New(t)
a := time.Now()
b := a.Add(time.Hour)
result1 := Latest(a, b)
result2 := Latest()
is.Equal(b, result1)
is.Zero(result2)
}
func TestLatestBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type foo struct {
bar time.Time
}
t1 := time.Now()
t2 := t1.Add(time.Hour)
t3 := t1.Add(-time.Hour)
result1 := LatestBy([]foo{{t1}, {t2}, {t3}}, func(i foo) time.Time {
return i.bar
})
result2 := LatestBy([]foo{{t1}}, func(i foo) time.Time {
return i.bar
})
result3 := LatestBy([]foo{}, func(i foo) time.Time {
return i.bar
})
is.Equal(foo{t2}, result1)
is.Equal(foo{t1}, result2)
is.Zero(result3)
}
func TestLatestByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
type foo struct {
bar time.Time
}
t1 := time.Now()
t2 := t1.Add(time.Hour)
t3 := t1.Add(-time.Hour)
// Test normal operation (no error) - table driven
tests := []struct {
name string
input []foo
expected foo
}{
{
name: "finds latest time",
input: []foo{{t1}, {t2}, {t3}},
expected: foo{t2},
},
{
name: "single element",
input: []foo{{t1}},
expected: foo{t1},
},
{
name: "empty collection",
input: []foo{},
expected: foo{},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result, err := LatestByErr(tt.input, func(i foo) (time.Time, error) {
return i.bar, nil
})
is.NoError(err)
is.Equal(tt.expected, result)
})
}
// Test error cases - table driven
errorTests := []struct {
name string
input []foo
errorAt int
expectedCalls int
}{
{
name: "error at first element",
input: []foo{{t1}, {t2}, {t3}},
errorAt: 0,
expectedCalls: 1, // Only first callback
},
{
name: "error at second element",
input: []foo{{t1}, {t2}, {t3}},
errorAt: 1,
expectedCalls: 2, // First two callbacks
},
{
name: "error at third element",
input: []foo{{t1}, {t2}, {t3}},
errorAt: 2,
expectedCalls: 3, // All three callbacks
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
_, err := LatestByErr(tt.input, func(i foo) (time.Time, error) {
callbackCount++
if len(tt.input) > 0 && i == tt.input[tt.errorAt] {
return time.Time{}, testErr
}
return i.bar, nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount)
})
}
}
func TestFirst(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, ok1 := First([]int{1, 2, 3})
result2, ok2 := First([]int{})
is.Equal(1, result1)
is.True(ok1)
is.Zero(result2)
is.False(ok2)
}
func TestFirstOrEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FirstOrEmpty([]int{1, 2, 3})
result2 := FirstOrEmpty([]int{})
result3 := FirstOrEmpty([]string{})
is.Equal(1, result1)
is.Zero(result2)
is.Empty(result3)
}
func TestFirstOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FirstOr([]int{1, 2, 3}, 63)
result2 := FirstOr([]int{}, 23)
result3 := FirstOr([]string{}, "test")
is.Equal(1, result1)
is.Equal(23, result2)
is.Equal("test", result3)
}
func TestLast(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, ok1 := Last([]int{1, 2, 3})
result2, ok2 := Last([]int{})
is.Equal(3, result1)
is.True(ok1)
is.Zero(result2)
is.False(ok2)
}
func TestLastOrEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := LastOrEmpty([]int{1, 2, 3})
result2 := LastOrEmpty([]int{})
result3 := LastOrEmpty([]string{})
is.Equal(3, result1)
is.Zero(result2)
is.Empty(result3)
}
func TestLastOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := LastOr([]int{1, 2, 3}, 63)
result2 := LastOr([]int{}, 23)
result3 := LastOr([]string{}, "test")
is.Equal(3, result1)
is.Equal(23, result2)
is.Equal("test", result3)
}
func TestNth(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, err1 := Nth([]int{0, 1, 2, 3}, 2)
result2, err2 := Nth([]int{0, 1, 2, 3}, -2)
result3, err3 := Nth([]int{0, 1, 2, 3}, 42)
result4, err4 := Nth([]int{}, 0)
result5, err5 := Nth([]int{42}, 0)
result6, err6 := Nth([]int{42}, -1)
is.Equal(2, result1)
is.NoError(err1)
is.Equal(2, result2)
is.NoError(err2)
is.Zero(result3)
is.EqualError(err3, "nth: 42 out of slice bounds")
is.Zero(result4)
is.EqualError(err4, "nth: 0 out of slice bounds")
is.Equal(42, result5)
is.NoError(err5)
is.Equal(42, result6)
is.NoError(err6)
}
func TestNthOr(t *testing.T) {
t.Parallel()
t.Run("Integers", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
const defaultValue = -1
intSlice := []int{10, 20, 30, 40, 50}
is.Equal(30, NthOr(intSlice, 2, defaultValue))
is.Equal(50, NthOr(intSlice, -1, defaultValue))
is.Equal(defaultValue, NthOr(intSlice, 5, defaultValue))
})
t.Run("Strings", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
const defaultValue = "none"
strSlice := []string{"apple", "banana", "cherry", "date"}
is.Equal("banana", NthOr(strSlice, 1, defaultValue)) // Index 1, expected "banana"
is.Equal("cherry", NthOr(strSlice, -2, defaultValue)) // Negative index -2, expected "cherry"
is.Equal(defaultValue, NthOr(strSlice, 10, defaultValue)) // Out of bounds, fallback "none"
})
t.Run("Structs", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
ID int
Name string
}
userSlice := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}
expectedUser := User{ID: 1, Name: "Alice"}
is.Equal(expectedUser, NthOr(userSlice, 0, User{ID: 0, Name: "Unknown"}))
expectedUser = User{ID: 3, Name: "Charlie"}
is.Equal(expectedUser, NthOr(userSlice, -1, User{ID: 0, Name: "Unknown"}))
expectedUser = User{ID: 0, Name: "Unknown"}
is.Equal(expectedUser, NthOr(userSlice, 10, User{ID: 0, Name: "Unknown"}))
})
}
func TestNthOrEmpty(t *testing.T) {
t.Parallel()
t.Run("Integers", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
intSlice := []int{10, 20, 30, 40, 50}
is.Equal(30, NthOrEmpty(intSlice, 2))
is.Equal(50, NthOrEmpty(intSlice, -1))
is.Zero(NthOrEmpty(intSlice, 10))
})
t.Run("Strings", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
strSlice := []string{"apple", "banana", "cherry", "date"}
is.Equal("banana", NthOrEmpty(strSlice, 1))
is.Equal("cherry", NthOrEmpty(strSlice, -2))
is.Empty(NthOrEmpty(strSlice, 10))
})
t.Run("Structs", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
ID int
Name string
}
userSlice := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}
expectedUser := User{ID: 1, Name: "Alice"}
is.Equal(expectedUser, NthOrEmpty(userSlice, 0))
expectedUser = User{ID: 3, Name: "Charlie"}
is.Equal(expectedUser, NthOrEmpty(userSlice, -1))
is.Zero(NthOrEmpty(userSlice, 10))
})
}
func TestSample(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Sample([]string{"a", "b", "c"})
result2 := Sample([]string{})
is.True(Contains([]string{"a", "b", "c"}, result1))
is.Empty(result2)
}
func TestSampleBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
r := rand.New(rand.NewSource(42))
result1 := SampleBy([]string{"a", "b", "c"}, r.Intn)
result2 := SampleBy([]string{}, rand.Intn)
is.True(Contains([]string{"a", "b", "c"}, result1))
is.Empty(result2)
}
func TestSamples(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Samples([]string{"a", "b", "c"}, 3)
result2 := Samples([]string{}, 3)
is.ElementsMatch(result1, []string{"a", "b", "c"})
is.Empty(result2)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Samples(allStrings, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestSamplesBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
r := rand.New(rand.NewSource(42))
result1 := SamplesBy([]string{"a", "b", "c"}, 3, r.Intn)
result2 := SamplesBy([]string{}, 3, r.Intn)
result3 := SamplesBy([]string{"a", "b", "c"}, 3, func(n int) int { return n - 1 })
result4 := SamplesBy([]string{"a", "b", "c"}, 3, func(int) int { return 0 })
result5 := SamplesBy([]string{"a", "b", "c"}, 0, func(int) int { return 1 })
result6 := SamplesBy([]string{"a", "b", "c"}, -1, nil)
// index out of range [1] with length 1
is.Panics(func() {
SamplesBy([]string{"a", "b", "c"}, 3, func(int) int { return 1 })
})
is.ElementsMatch(result1, []string{"a", "b", "c"})
is.Empty(result2)
is.Equal([]string{"c", "b", "a"}, result3)
is.Equal([]string{"a", "c", "b"}, result4)
is.Empty(result5)
is.Empty(result6)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := SamplesBy(allStrings, 2, r.Intn)
is.IsType(nonempty, allStrings, "type preserved")
}
================================================
FILE: func.go
================================================
package lo
// Partial returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/Sy1gAQiQZ3v
func Partial[T1, T2, R any](f func(a T1, b T2) R, arg1 T1) func(T2) R {
return func(t2 T2) R {
return f(arg1, t2)
}
}
// Partial1 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/D-ASTXCLBzw
func Partial1[T1, T2, R any](f func(T1, T2) R, arg1 T1) func(T2) R {
return Partial(f, arg1)
}
// Partial2 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/-xiPjy4JChJ
func Partial2[T1, T2, T3, R any](f func(T1, T2, T3) R, arg1 T1) func(T2, T3) R {
return func(t2 T2, t3 T3) R {
return f(arg1, t2, t3)
}
}
// Partial3 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/zWtSutpI26m
func Partial3[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) R, arg1 T1) func(T2, T3, T4) R {
return func(t2 T2, t3 T3, t4 T4) R {
return f(arg1, t2, t3, t4)
}
}
// Partial4 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/kBrnnMTcJm0
func Partial4[T1, T2, T3, T4, T5, R any](f func(T1, T2, T3, T4, T5) R, arg1 T1) func(T2, T3, T4, T5) R {
return func(t2 T2, t3 T3, t4 T4, t5 T5) R {
return f(arg1, t2, t3, t4, t5)
}
}
// Partial5 returns new function that, when called, has its first argument set to the provided value.
// Play: https://go.dev/play/p/7Is7K2y_VC3
func Partial5[T1, T2, T3, T4, T5, T6, R any](f func(T1, T2, T3, T4, T5, T6) R, arg1 T1) func(T2, T3, T4, T5, T6) R {
return func(t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) R {
return f(arg1, t2, t3, t4, t5, t6)
}
}
================================================
FILE: func_test.go
================================================
package lo
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPartial(t *testing.T) {
t.Parallel()
is := assert.New(t)
add := func(x float64, y int) string {
return strconv.Itoa(int(x) + y)
}
f := Partial(add, 5)
is.Equal("15", f(10))
is.Equal("0", f(-5))
}
func TestPartial1(t *testing.T) {
t.Parallel()
is := assert.New(t)
add := func(x float64, y int) string {
return strconv.Itoa(int(x) + y)
}
f := Partial1(add, 5)
is.Equal("15", f(10))
is.Equal("0", f(-5))
}
func TestPartial2(t *testing.T) {
t.Parallel()
is := assert.New(t)
add := func(x float64, y, z int) string {
return strconv.Itoa(int(x) + y + z)
}
f := Partial2(add, 5)
is.Equal("24", f(10, 9))
is.Equal("8", f(-5, 8))
}
func TestPartial3(t *testing.T) {
t.Parallel()
is := assert.New(t)
add := func(x float64, y, z int, a float32) string {
return strconv.Itoa(int(x) + y + z + int(a))
}
f := Partial3(add, 5)
is.Equal("21", f(10, 9, -3))
is.Equal("15", f(-5, 8, 7))
}
func TestPartial4(t *testing.T) {
t.Parallel()
is := assert.New(t)
add := func(x float64, y, z int, a float32, b int32) string {
return strconv.Itoa(int(x) + y + z + int(a) + int(b))
}
f := Partial4(add, 5)
is.Equal("21", f(10, 9, -3, 0))
is.Equal("14", f(-5, 8, 7, -1))
}
func TestPartial5(t *testing.T) {
t.Parallel()
is := assert.New(t)
add := func(x float64, y, z int, a float32, b int32, c int) string {
return strconv.Itoa(int(x) + y + z + int(a) + int(b) + c)
}
f := Partial5(add, 5)
is.Equal("26", f(10, 9, -3, 0, 5))
is.Equal("21", f(-5, 8, 7, -1, 7))
}
================================================
FILE: go.mod
================================================
module github.com/samber/lo
go 1.18
//
// Dev dependencies are excluded from releases. Please check CI.
//
require (
github.com/stretchr/testify v1.11.1
github.com/thoas/go-funk v0.9.3
go.uber.org/goleak v1.2.1
golang.org/x/text v0.22.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw=
github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: internal/constraints/README.md
================================================
# Constraints
This package is for Go 1.18 retrocompatiblity purpose.
================================================
FILE: internal/constraints/constraints.go
================================================
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package constraints defines a set of useful constraints to be used
// with type parameters.
package constraints
// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint that permits any unsigned integer type.
// If future releases of Go add new predeclared unsigned integer types,
// this constraint will be modified to include them.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}
// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
~float32 | ~float64
}
// Complex is a constraint that permits any complex numeric type.
// If future releases of Go add new predeclared complex numeric types,
// this constraint will be modified to include them.
type Complex interface {
~complex64 | ~complex128
}
================================================
FILE: internal/constraints/ordered_go118.go
================================================
//go:build !go1.21
package constraints
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered interface {
Integer | Float | ~string
}
================================================
FILE: internal/constraints/ordered_go121.go
================================================
//go:build go1.21
package constraints
import (
"cmp"
)
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered = cmp.Ordered
================================================
FILE: internal/xrand/ordered_go118.go
================================================
//go:build !go1.22
package xrand
import "math/rand"
// Shuffle returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
func Shuffle(n int, swap func(i, j int)) {
rand.Shuffle(n, swap)
}
// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
func IntN(n int) int {
// bearer:disable go_gosec_crypto_weak_random
return rand.Intn(n)
}
// Int64 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
func Int64() int64 {
// bearer:disable go_gosec_crypto_weak_random
n := rand.Int63()
// bearer:disable go_gosec_crypto_weak_random
if rand.Intn(2) == 0 {
return -n
}
return n
}
================================================
FILE: internal/xrand/ordered_go122.go
================================================
//go:build go1.22
package xrand
import "math/rand/v2"
// Shuffle returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
func Shuffle(n int, swap func(i, j int)) {
rand.Shuffle(n, swap)
}
// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n)
// from the default Source.
// It panics if n <= 0.
func IntN(n int) int {
return rand.IntN(n)
}
// Int64 returns a non-negative pseudo-random 63-bit integer as an int64
// from the default Source.
func Int64() int64 {
return rand.Int64()
}
================================================
FILE: internal/xtime/README.md
================================================
# xtime
Lightweight mock for time package.
A dedicated package such as [jonboulle/clockwork](https://github.com/jonboulle/clockwork/) would be better, but I would rather limit dependencies for this package. `clockwork` does not support Go 1.18 anymore.
================================================
FILE: internal/xtime/fake.go
================================================
//nolint:revive
package xtime
import (
"time"
)
func NewFakeClock() *FakeClock {
return NewFakeClockAt(time.Now())
}
func NewFakeClockAt(t time.Time) *FakeClock {
return &FakeClock{
time: t,
}
}
type FakeClock struct {
_ noCopy
// Not protected by a mutex. If a warning is thrown in your tests,
// just disable parallel tests.
time time.Time
}
func (c *FakeClock) Now() time.Time {
return c.time
}
func (c *FakeClock) Since(t time.Time) time.Duration {
return c.time.Sub(t)
}
func (c *FakeClock) Until(t time.Time) time.Duration {
return t.Sub(c.time)
}
func (c *FakeClock) Sleep(d time.Duration) {
c.time = c.time.Add(d)
}
================================================
FILE: internal/xtime/noCopy.go
================================================
package xtime
// noCopy may be added to structs which must not be copied
// after the first use.
//
// See https://golang.org/issues/8005#issuecomment-190753527
// for details.
//
// Note that it must not be embedded, due to the Lock and Unlock methods.
type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
================================================
FILE: internal/xtime/real.go
================================================
//nolint:revive
package xtime
import (
"time"
)
func NewRealClock() *RealClock {
return &RealClock{}
}
type RealClock struct {
_ noCopy
}
func (c *RealClock) Now() time.Time {
return time.Now()
}
func (c *RealClock) Since(t time.Time) time.Duration {
return time.Since(t)
}
func (c *RealClock) Until(t time.Time) time.Duration {
return time.Until(t)
}
func (c *RealClock) Sleep(d time.Duration) {
time.Sleep(d)
}
================================================
FILE: internal/xtime/time.go
================================================
//nolint:revive
package xtime
import "time"
var clock Clock = &RealClock{}
func SetClock(c Clock) {
clock = c
}
func Now() time.Time {
return clock.Now()
}
func Since(t time.Time) time.Duration {
return clock.Since(t)
}
func Until(t time.Time) time.Duration {
return clock.Until(t)
}
func Sleep(d time.Duration) {
clock.Sleep(d)
}
type Clock interface {
Now() time.Time
Since(t time.Time) time.Duration
Until(t time.Time) time.Duration
Sleep(d time.Duration)
}
================================================
FILE: intersect.go
================================================
package lo
// Contains returns true if an element is present in a collection.
// Play: https://go.dev/play/p/W1EvyqY6t9j
func Contains[T comparable](collection []T, element T) bool {
for i := range collection {
if collection[i] == element {
return true
}
}
return false
}
// ContainsBy returns true if predicate function return true.
// Play: https://go.dev/play/p/W1EvyqY6t9j
func ContainsBy[T any](collection []T, predicate func(item T) bool) bool {
for i := range collection {
if predicate(collection[i]) {
return true
}
}
return false
}
// Every returns true if all elements of a subset are contained in a collection or if the subset is empty.
// Play: https://go.dev/play/p/W1EvyqY6t9j
func Every[T comparable](collection, subset []T) bool {
if len(subset) == 0 {
return true
}
seen := Keyify(collection)
for _, item := range subset {
if _, ok := seen[item]; !ok {
return false
}
}
return true
}
// EveryBy returns true if the predicate returns true for all elements in the collection or if the collection is empty.
// Play: https://go.dev/play/p/dn1-vhHsq9x
func EveryBy[T any](collection []T, predicate func(item T) bool) bool {
for i := range collection {
if !predicate(collection[i]) {
return false
}
}
return true
}
// Some returns true if at least 1 element of a subset is contained in a collection.
// If the subset is empty Some returns false.
// Play: https://go.dev/play/p/Lj4ceFkeT9V
func Some[T comparable](collection, subset []T) bool {
if len(subset) == 0 {
return false
}
seen := Keyify(subset)
for i := range collection {
if _, ok := seen[collection[i]]; ok {
return true
}
}
return false
}
// SomeBy returns true if the predicate returns true for any of the elements in the collection.
// If the collection is empty SomeBy returns false.
// Play: https://go.dev/play/p/DXF-TORBudx
func SomeBy[T any](collection []T, predicate func(item T) bool) bool {
for i := range collection {
if predicate(collection[i]) {
return true
}
}
return false
}
// None returns true if no element of a subset is contained in a collection or if the subset is empty.
// Play: https://go.dev/play/p/fye7JsmxzPV
func None[T comparable](collection, subset []T) bool {
if len(subset) == 0 {
return true
}
seen := Keyify(subset)
for i := range collection {
if _, ok := seen[collection[i]]; ok {
return false
}
}
return true
}
// NoneBy returns true if the predicate returns true for none of the elements in the collection or if the collection is empty.
// Play: https://go.dev/play/p/O64WZ32H58S
func NoneBy[T any](collection []T, predicate func(item T) bool) bool {
for i := range collection {
if predicate(collection[i]) {
return false
}
}
return true
}
// Intersect returns the intersection between collections.
// Play: https://go.dev/play/p/uuElL9X9e58
func Intersect[T comparable, Slice ~[]T](lists ...Slice) Slice {
if len(lists) == 0 {
return Slice{}
}
last := lists[len(lists)-1]
seen := make(map[T]bool, len(last))
for _, item := range last {
seen[item] = false
}
for i := len(lists) - 2; i > 0 && len(seen) != 0; i-- {
for _, item := range lists[i] {
if _, ok := seen[item]; ok {
seen[item] = true
}
}
for k, v := range seen {
if v {
seen[k] = false
} else {
delete(seen, k)
}
}
}
result := make(Slice, 0, len(seen))
for _, item := range lists[0] {
if _, ok := seen[item]; ok {
result = append(result, item)
delete(seen, item)
}
}
return result
}
// IntersectBy returns the intersection between two collections using a custom key selector function.
// Play: https://go.dev/play/p/uWF8y2-zmtf
func IntersectBy[T any, K comparable, Slice ~[]T](transform func(T) K, lists ...Slice) Slice {
if len(lists) == 0 {
return Slice{}
}
last := lists[len(lists)-1]
seen := make(map[K]bool, len(last))
for _, item := range last {
k := transform(item)
seen[k] = false
}
for i := len(lists) - 2; i > 0 && len(seen) != 0; i-- {
for _, item := range lists[i] {
k := transform(item)
if _, ok := seen[k]; ok {
seen[k] = true
}
}
for k, v := range seen {
if v {
seen[k] = false
} else {
delete(seen, k)
}
}
}
result := make(Slice, 0, len(seen))
for _, item := range lists[0] {
k := transform(item)
if _, ok := seen[k]; ok {
result = append(result, item)
delete(seen, k)
}
}
return result
}
// Difference returns the difference between two collections.
// The first value is the collection of elements absent from list2.
// The second value is the collection of elements absent from list1.
// Play: https://go.dev/play/p/pKE-JgzqRpz
func Difference[T comparable, Slice ~[]T](list1, list2 Slice) (Slice, Slice) {
left := make(Slice, 0, len(list1))
right := make(Slice, 0, len(list2))
seenLeft := Keyify(list1)
seenRight := Keyify(list2)
for i := range list1 {
if _, ok := seenRight[list1[i]]; !ok {
left = append(left, list1[i])
}
}
for i := range list2 {
if _, ok := seenLeft[list2[i]]; !ok {
right = append(right, list2[i])
}
}
return left, right
}
// Union returns all distinct elements from given collections.
// result returns will not change the order of elements relatively.
// Play: https://go.dev/play/p/-hsqZNTH0ej
func Union[T comparable, Slice ~[]T](lists ...Slice) Slice {
var capLen int
for _, list := range lists {
capLen += len(list)
}
result := make(Slice, 0, capLen)
seen := make(map[T]struct{}, capLen)
for i := range lists {
for j := range lists[i] {
if _, ok := seen[lists[i][j]]; !ok {
seen[lists[i][j]] = struct{}{}
result = append(result, lists[i][j])
}
}
}
return result
}
// Without returns a slice excluding all given values.
// Play: https://go.dev/play/p/PcAVtYJsEsS
func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice {
excludeMap := Keyify(exclude)
result := make(Slice, 0, len(collection))
for i := range collection {
if _, ok := excludeMap[collection[i]]; !ok {
result = append(result, collection[i])
}
}
return result
}
// WithoutBy filters a slice by excluding elements whose extracted keys match any in the exclude list.
// Returns a new slice containing only the elements whose keys are not in the exclude list.
// Play: https://go.dev/play/p/VgWJOF01NbJ
func WithoutBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K, exclude ...K) Slice {
excludeMap := Keyify(exclude)
result := make(Slice, 0, len(collection))
for _, item := range collection {
if _, ok := excludeMap[iteratee(item)]; !ok {
result = append(result, item)
}
}
return result
}
// WithoutByErr filters a slice by excluding elements whose extracted keys match any in the exclude list.
// It returns the first error returned by the iteratee.
func WithoutByErr[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) (K, error), exclude ...K) (Slice, error) {
excludeMap := Keyify(exclude)
result := make(Slice, 0, len(collection))
for _, item := range collection {
key, err := iteratee(item)
if err != nil {
return nil, err
}
if _, ok := excludeMap[key]; !ok {
result = append(result, item)
}
}
return result, nil
}
// WithoutEmpty returns a slice excluding zero values.
//
// Deprecated: Use lo.Compact instead.
// Play: https://go.dev/play/p/iZvYJWuniJm
func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice {
return Compact(collection)
}
// WithoutNth returns a slice excluding the nth value.
// Play: https://go.dev/play/p/5g3F9R2H1xL
func WithoutNth[T any, Slice ~[]T](collection Slice, nths ...int) Slice {
toRemove := Keyify(nths)
result := make(Slice, 0, len(collection))
for i := range collection {
if _, ok := toRemove[i]; !ok {
result = append(result, collection[i])
}
}
return result
}
// ElementsMatch returns true if lists contain the same set of elements (including empty set).
// If there are duplicate elements, the number of occurrences in each list should match.
// The order of elements is not checked.
// Play: https://go.dev/play/p/XWSEM4Ic_t0
func ElementsMatch[T comparable, Slice ~[]T](list1, list2 Slice) bool {
return ElementsMatchBy(list1, list2, func(item T) T { return item })
}
// ElementsMatchBy returns true if lists contain the same set of elements' keys (including empty set).
// If there are duplicate keys, the number of occurrences in each list should match.
// The order of elements is not checked.
// Play: https://go.dev/play/p/XWSEM4Ic_t0
func ElementsMatchBy[T any, K comparable](list1, list2 []T, iteratee func(item T) K) bool {
if len(list1) != len(list2) {
return false
}
if len(list1) == 0 {
return true
}
counters := make(map[K]int, len(list1))
for _, el := range list1 {
counters[iteratee(el)]++
}
for _, el := range list2 {
counters[iteratee(el)]--
}
for _, count := range counters {
if count != 0 {
return false
}
}
return true
}
================================================
FILE: intersect_test.go
================================================
package lo
import (
"errors"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestContains(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Contains([]int{0, 1, 2, 3, 4, 5}, 5)
result2 := Contains([]int{0, 1, 2, 3, 4, 5}, 6)
is.True(result1)
is.False(result2)
}
func TestContainsBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type a struct {
A int
B string
}
a1 := []a{{A: 1, B: "1"}, {A: 2, B: "2"}, {A: 3, B: "3"}}
result1 := ContainsBy(a1, func(t a) bool { return t.A == 1 && t.B == "2" })
result2 := ContainsBy(a1, func(t a) bool { return t.A == 2 && t.B == "2" })
a2 := []string{"aaa", "bbb", "ccc"}
result3 := ContainsBy(a2, func(t string) bool { return t == "ccc" })
result4 := ContainsBy(a2, func(t string) bool { return t == "ddd" })
is.False(result1)
is.True(result2)
is.True(result3)
is.False(result4)
}
func TestEvery(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
result2 := Every([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
result3 := Every([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
result4 := Every([]int{0, 1, 2, 3, 4, 5}, []int{})
is.True(result1)
is.False(result2)
is.False(result3)
is.True(result4)
}
func TestEveryBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := EveryBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 5
})
is.True(result1)
result2 := EveryBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 3
})
is.False(result2)
result3 := EveryBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 0
})
is.False(result3)
result4 := EveryBy([]int{}, func(x int) bool {
return x < 5
})
is.True(result4)
}
func TestSome(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Some([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
result2 := Some([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
result3 := Some([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
result4 := Some([]int{0, 1, 2, 3, 4, 5}, []int{})
is.True(result1)
is.True(result2)
is.False(result3)
is.False(result4)
}
func TestSomeBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := SomeBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 5
})
is.True(result1)
result2 := SomeBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 3
})
is.True(result2)
result3 := SomeBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 0
})
is.False(result3)
result4 := SomeBy([]int{}, func(x int) bool {
return x < 5
})
is.False(result4)
}
func TestNone(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := None([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
result2 := None([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
result3 := None([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
result4 := None([]int{0, 1, 2, 3, 4, 5}, []int{})
is.False(result1)
is.False(result2)
is.True(result3)
is.True(result4)
}
func TestNoneBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := NoneBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 5
})
is.False(result1)
result2 := NoneBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 3
})
is.False(result2)
result3 := NoneBy([]int{1, 2, 3, 4}, func(x int) bool {
return x < 0
})
is.True(result3)
result4 := NoneBy([]int{}, func(x int) bool {
return x < 5
})
is.True(result4)
}
func TestIntersect(t *testing.T) {
t.Parallel()
is := assert.New(t)
result0 := Intersect[int, []int]()
result1 := Intersect([]int{1})
result2 := Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
result3 := Intersect([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
result4 := Intersect([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
result5 := Intersect([]int{0, 6}, []int{0, 1, 2, 3, 4, 5})
result6 := Intersect([]int{0, 6, 0}, []int{0, 1, 2, 3, 4, 5})
result7 := Intersect([]int{0, 6, 0, 3}, []int{0, 1, 2, 3, 4, 5}, []int{0, 6})
result8 := Intersect([]int{0, 6, 0, 3}, []int{0, 1, 2, 3, 4, 5}, []int{1, 6})
result9 := Intersect([]int{0, 1, 1}, []int{2}, []int{3})
resultA := Intersect([]int{0, 1, 1})
is.Empty(result0)
is.ElementsMatch([]int{1}, result1)
is.ElementsMatch([]int{0, 2}, result2)
is.ElementsMatch([]int{0}, result3)
is.Empty(result4)
is.ElementsMatch([]int{0}, result5)
is.ElementsMatch([]int{0}, result6)
is.ElementsMatch([]int{0}, result7)
is.Empty(result8)
is.Empty(result9)
is.ElementsMatch([]int{0, 1}, resultA)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Intersect(allStrings, allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestIntersectBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
ID int
Name string
}
list1 := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}
list2 := []User{
{ID: 2, Name: "Robert"},
{ID: 3, Name: "Charlie"},
{ID: 4, Name: "Alice"},
}
intersectByID := IntersectBy(func(u User) int {
return u.ID
}, list1, list2)
is.ElementsMatch(intersectByID, []User{{ID: 2, Name: "Bob"}, {ID: 3, Name: "Charlie"}})
intersectByName := IntersectBy(func(u User) string {
return u.Name
}, list1, list2)
is.ElementsMatch(intersectByName, []User{{ID: 3, Name: "Charlie"}, {ID: 1, Name: "Alice"}})
intersectByIDAndName := IntersectBy(func(u User) string {
return strconv.Itoa(u.ID) + u.Name
}, list1, list2)
is.ElementsMatch(intersectByIDAndName, []User{{ID: 3, Name: "Charlie"}})
result := IntersectBy(strconv.Itoa, []int{0, 6, 0, 3}, []int{0, 1, 2, 3, 4, 5}, []int{0, 6})
is.ElementsMatch(result, []int{0})
result = IntersectBy(strconv.Itoa, []int{0, 1, 1})
is.ElementsMatch(result, []int{0, 1})
}
func TestDifference(t *testing.T) {
t.Parallel()
is := assert.New(t)
left1, right1 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6})
is.Equal([]int{1, 3, 4, 5}, left1)
is.Equal([]int{6}, right1)
left2, right2 := Difference([]int{1, 2, 3, 4, 5}, []int{0, 6})
is.Equal([]int{1, 2, 3, 4, 5}, left2)
is.Equal([]int{0, 6}, right2)
left3, right3 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5})
is.Empty(left3)
is.Empty(right3)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
a, b := Difference(allStrings, allStrings)
is.IsType(a, allStrings, "type preserved")
is.IsType(b, allStrings, "type preserved")
}
func TestUnion(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Union([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10})
result2 := Union([]int{0, 1, 2, 3, 4, 5}, []int{6, 7})
result3 := Union([]int{0, 1, 2, 3, 4, 5}, []int{})
result4 := Union([]int{0, 1, 2}, []int{0, 1, 2, 3, 3})
result5 := Union([]int{0, 1, 2}, []int{0, 1, 2})
result6 := Union([]int{}, []int{})
is.Equal([]int{0, 1, 2, 3, 4, 5, 10}, result1)
is.Equal([]int{0, 1, 2, 3, 4, 5, 6, 7}, result2)
is.Equal([]int{0, 1, 2, 3, 4, 5}, result3)
is.Equal([]int{0, 1, 2, 3}, result4)
is.Equal([]int{0, 1, 2}, result5)
is.Empty(result6)
result11 := Union([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10}, []int{0, 1, 11})
result12 := Union([]int{0, 1, 2, 3, 4, 5}, []int{6, 7}, []int{8, 9})
result13 := Union([]int{0, 1, 2, 3, 4, 5}, []int{}, []int{})
result14 := Union([]int{0, 1, 2}, []int{0, 1, 2}, []int{0, 1, 2})
result15 := Union([]int{}, []int{}, []int{})
is.Equal([]int{0, 1, 2, 3, 4, 5, 10, 11}, result11)
is.Equal([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, result12)
is.Equal([]int{0, 1, 2, 3, 4, 5}, result13)
is.Equal([]int{0, 1, 2}, result14)
is.Empty(result15)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Union(allStrings, allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestWithout(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Without([]int{0, 2, 10}, 0, 1, 2, 3, 4, 5)
result2 := Without([]int{0, 7}, 0, 1, 2, 3, 4, 5)
result3 := Without([]int{}, 0, 1, 2, 3, 4, 5)
result4 := Without([]int{0, 1, 2}, 0, 1, 2)
result5 := Without([]int{})
is.Equal([]int{10}, result1)
is.Equal([]int{7}, result2)
is.Empty(result3)
is.Empty(result4)
is.Empty(result5)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Without(allStrings, "")
is.IsType(nonempty, allStrings, "type preserved")
}
func TestWithoutBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
Name string
Age int
}
result1 := WithoutBy([]User{{Name: "nick"}, {Name: "peter"}},
func(item User) string {
return item.Name
}, "nick", "lily")
result2 := WithoutBy([]User{}, func(item User) int { return item.Age }, 1, 2, 3)
result3 := WithoutBy([]User{}, func(item User) string { return item.Name })
is.Equal([]User{{Name: "peter"}}, result1)
is.Empty(result2)
is.Empty(result3)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := WithoutBy(allStrings, func(s string) string {
return s
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestWithoutByErr(t *testing.T) {
t.Parallel()
type User struct {
Name string
Age int
}
tests := []struct {
name string
input []User
iteratee func(User) (string, error)
exclude []string
want []User
wantErr string
wantCallCount int
}{
{
name: "exclude by name",
input: []User{{Name: "nick"}, {Name: "peter"}},
iteratee: func(item User) (string, error) {
return item.Name, nil
},
exclude: []string{"nick", "lily"},
want: []User{{Name: "peter"}},
wantErr: "",
wantCallCount: 2,
},
{
name: "empty exclude list",
input: []User{{Name: "nick"}, {Name: "peter"}},
iteratee: func(item User) (string, error) {
return item.Name, nil
},
exclude: []string{},
want: []User{{Name: "nick"}, {Name: "peter"}},
wantErr: "",
wantCallCount: 2,
},
{
name: "error on second element",
input: []User{{Name: "nick"}, {Name: "peter"}, {Name: "lily"}},
iteratee: func(item User) (string, error) {
if item.Name == "peter" {
return "", errors.New("peter not allowed")
}
return item.Name, nil
},
exclude: []string{"nick"},
want: nil,
wantErr: "peter not allowed",
wantCallCount: 2, // stops early at error
},
{
name: "error on first element",
input: []User{{Name: "nick"}, {Name: "peter"}},
iteratee: func(item User) (string, error) {
return "", errors.New("first element error")
},
exclude: []string{"nick"},
want: nil,
wantErr: "first element error",
wantCallCount: 1,
},
{
name: "all excluded",
input: []User{{Name: "nick"}, {Name: "peter"}},
iteratee: func(item User) (string, error) {
return item.Name, nil
},
exclude: []string{"nick", "peter", "lily"},
want: []User{},
wantErr: "",
wantCallCount: 2,
},
{
name: "none excluded",
input: []User{{Name: "nick"}, {Name: "peter"}},
iteratee: func(item User) (string, error) {
return item.Name, nil
},
exclude: []string{"alice"},
want: []User{{Name: "nick"}, {Name: "peter"}},
wantErr: "",
wantCallCount: 2,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callCount := 0
wrappedIteratee := func(item User) (string, error) {
callCount++
return tt.iteratee(item)
}
got, err := WithoutByErr(tt.input, wrappedIteratee, tt.exclude...)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
if tt.wantCallCount > 0 {
is.Equal(tt.wantCallCount, callCount, "should stop early on error")
}
} else {
is.NoError(err)
is.Equal(tt.want, got)
is.Equal(tt.wantCallCount, callCount)
}
})
}
t.Run("type preserved", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty, err := WithoutByErr(allStrings, func(s string) (string, error) {
return s, nil
})
is.NoError(err)
is.IsType(nonempty, allStrings, "type preserved")
})
}
func TestWithoutEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := WithoutEmpty([]int{0, 1, 2})
result2 := WithoutEmpty([]int{1, 2})
result3 := WithoutEmpty([]int{})
result4 := WithoutEmpty([]*int{ToPtr(0), ToPtr(1), nil, ToPtr(2)})
is.Equal([]int{1, 2}, result1)
is.Equal([]int{1, 2}, result2)
is.Empty(result3)
is.Equal([]*int{ToPtr(0), ToPtr(1), ToPtr(2)}, result4)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := WithoutEmpty(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestWithoutNth(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := WithoutNth([]int{5, 6, 7}, 1, 0)
is.Equal([]int{7}, result1)
result2 := WithoutNth([]int{1, 2})
is.Equal([]int{1, 2}, result2)
result3 := WithoutNth([]int{})
is.Empty(result3)
result4 := WithoutNth([]int{0, 1, 2, 3}, -1, 4)
is.Equal([]int{0, 1, 2, 3}, result4)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := WithoutNth(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
// This works for non-comparable as well
result5 := WithoutNth([]func() int{func() int { return 1 }, func() int { return 2 }, func() int { return 3 }}, 1)
is.Equal([]int{1, 3}, Map(result5, func(f func() int, _ int) int { return f() }))
}
func TestElementsMatch(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.False(ElementsMatch([]int{}, []int{1}))
is.False(ElementsMatch([]int{1}, []int{2}))
is.False(ElementsMatch([]int{1}, []int{1, 2}))
is.False(ElementsMatch([]int{1, 1, 2}, []int{2, 2, 1}))
is.True(ElementsMatch([]int{}, nil))
is.True(ElementsMatch([]int{1}, []int{1}))
is.True(ElementsMatch([]int{1, 1}, []int{1, 1}))
is.True(ElementsMatch([]int{1, 2}, []int{2, 1}))
is.True(ElementsMatch([]int{1, 1, 2}, []int{1, 2, 1}))
}
func TestElementsMatchBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type someType struct {
key string
}
is.True(ElementsMatchBy(
[]someType{{key: "a"}, {key: "b"}},
[]someType{{key: "b"}, {key: "a"}},
func(item someType) string { return item.key },
))
}
================================================
FILE: it/channel.go
================================================
//go:build go1.23
package it
import (
"iter"
"github.com/samber/lo"
)
// SeqToChannel returns a read-only channel of collection elements.
// Play: https://go.dev/play/p/id3jqJPffT6
func SeqToChannel[T any](bufferSize int, collection iter.Seq[T]) <-chan T {
ch := make(chan T, bufferSize)
go func() {
for item := range collection {
ch <- item
}
close(ch)
}()
return ch
}
// SeqToChannel2 returns a read-only channel of collection elements.
// Play: https://go.dev/play/p/rpJdVnXUaG-
func SeqToChannel2[K, V any](bufferSize int, collection iter.Seq2[K, V]) <-chan lo.Tuple2[K, V] {
ch := make(chan lo.Tuple2[K, V], bufferSize)
go func() {
for k, v := range collection {
ch <- lo.Tuple2[K, V]{A: k, B: v}
}
close(ch)
}()
return ch
}
// ChannelToSeq returns a sequence built from channels items. Blocks until channel closes.
// Play: https://go.dev/play/p/IXqSs2Ooqpm
func ChannelToSeq[T any](ch <-chan T) iter.Seq[T] {
return func(yield func(T) bool) {
for item := range ch {
if !yield(item) {
return
}
}
}
}
================================================
FILE: it/channel_test.go
================================================
//go:build go1.23
package it
import (
"maps"
"slices"
"testing"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
func TestSeqToChannel(t *testing.T) {
t.Parallel()
is := assert.New(t)
ch := SeqToChannel(2, values(1, 2, 3))
r1, ok1 := <-ch
r2, ok2 := <-ch
r3, ok3 := <-ch
is.True(ok1)
is.Equal(1, r1)
is.True(ok2)
is.Equal(2, r2)
is.True(ok3)
is.Equal(3, r3)
_, ok4 := <-ch
is.False(ok4)
}
func TestSeqToChannel2(t *testing.T) {
t.Parallel()
is := assert.New(t)
ch := SeqToChannel2(2, maps.All(map[string]int{"a": 1, "b": 2, "c": 3}))
r1, ok1 := <-ch
r2, ok2 := <-ch
r3, ok3 := <-ch
is.True(ok1)
is.True(ok2)
is.True(ok3)
is.ElementsMatch([]lo.Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}, {A: "c", B: 3}}, []lo.Tuple2[string, int]{r1, r2, r3})
_, ok4 := <-ch
is.False(ok4)
}
func TestChannelToSeq(t *testing.T) {
t.Parallel()
is := assert.New(t)
ch := SeqToChannel(2, values(1, 2, 3))
items := ChannelToSeq(ch)
is.Equal([]int{1, 2, 3}, slices.Collect(items))
}
================================================
FILE: it/find.go
================================================
//go:build go1.23
package it
import (
"iter"
"slices"
"time"
"github.com/samber/lo"
"github.com/samber/lo/internal/constraints"
"github.com/samber/lo/internal/xrand"
)
// IndexOf returns the index at which the first occurrence of a value is found in a sequence or -1
// if the value cannot be found.
// Will iterate through the entire sequence if element is not found.
// Play: https://go.dev/play/p/1OZHU2yfb-m
func IndexOf[T comparable](collection iter.Seq[T], element T) int {
var i int
for item := range collection {
if item == element {
return i
}
i++
}
return -1
}
// LastIndexOf returns the index at which the last occurrence of a value is found in a sequence or -1
// if the value cannot be found.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/QPATR3VC5wT
func LastIndexOf[T comparable](collection iter.Seq[T], element T) int {
index := -1
var i int
for item := range collection {
if item == element {
index = i
}
i++
}
return index
}
// HasPrefix returns true if the collection has the prefix.
// Will iterate at most the size of prefix.
// Play: https://go.dev/play/p/Fyj6uq-G5IH
func HasPrefix[T comparable](collection iter.Seq[T], prefix ...T) bool {
if len(prefix) == 0 {
return true
}
var i int
for item := range collection {
if item != prefix[i] {
return false
}
i++
if i == len(prefix) {
return true
}
}
return false
}
// HasSuffix returns true if the collection has the suffix.
// Will iterate through the entire sequence and allocate a slice the size of suffix.
// Play: https://go.dev/play/p/r6bF9Rmq5S0
func HasSuffix[T comparable](collection iter.Seq[T], suffix ...T) bool {
if len(suffix) == 0 {
return true
}
n := len(suffix)
buf := make([]T, n)
var i int
for buf[i%n] = range collection {
i++
}
if i < n {
return false
}
for j := range suffix {
if suffix[j] != buf[(i+j)%n] {
return false
}
}
return true
}
// Find searches for an element in a sequence based on a predicate. Returns element and true if element was found.
// Will iterate through the entire sequence if predicate never returns true.
// Play: https://go.dev/play/p/4w28pF_l58a
func Find[T any](collection iter.Seq[T], predicate func(item T) bool) (T, bool) {
for item := range collection {
if predicate(item) {
return item, true
}
}
return lo.Empty[T](), false
}
// FindIndexOf searches for an element in a sequence based on a predicate and returns the index and true.
// Returns -1 and false if the element is not found.
// Will iterate through the entire sequence if predicate never returns true.
// Play: https://go.dev/play/p/ihchBAEkhXO
func FindIndexOf[T any](collection iter.Seq[T], predicate func(item T) bool) (T, int, bool) {
var i int
for item := range collection {
if predicate(item) {
return item, i, true
}
i++
}
return lo.Empty[T](), -1, false
}
// FindLastIndexOf searches for the last element in a sequence based on a predicate and returns the index and true.
// Returns -1 and false if the element is not found.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/ezz6hXaC4Md
func FindLastIndexOf[T any](collection iter.Seq[T], predicate func(item T) bool) (T, int, bool) {
var result T
index := -1
var ok bool
var i int
for item := range collection {
if predicate(item) {
result = item
index = i
ok = true
}
i++
}
return result, index, ok
}
// FindOrElse searches for an element in a sequence based on a predicate. Returns the element if found or a given fallback value otherwise.
// Will iterate through the entire sequence if predicate never returns true.
// Play: https://go.dev/play/p/1harvaiGMfI
func FindOrElse[T any](collection iter.Seq[T], fallback T, predicate func(item T) bool) T {
if result, ok := Find(collection, predicate); ok {
return result
}
return fallback
}
// FindUniques returns a sequence with all the elements that appear in the collection only once.
// The order of result values is determined by the order they occur in the collection.
// Will iterate through the entire sequence before yielding and allocate a map large enough to hold all distinct elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/O8dwXEbT56F
func FindUniques[T comparable, I ~func(func(T) bool)](collection I) I {
return FindUniquesBy(collection, func(item T) T { return item })
}
// FindUniquesBy returns a sequence with all the elements that appear in the collection only once.
// The order of result values is determined by the order they occur in the sequence. A transform function is
// invoked for each element in the sequence to generate the criterion by which uniqueness is computed.
// Will iterate through the entire sequence before yielding and allocate a map large enough to hold all distinct transformed elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/TiwGIzeDuML
func FindUniquesBy[T any, U comparable, I ~func(func(T) bool)](collection I, transform func(item T) U) I {
return func(yield func(T) bool) {
isDupl := make(map[U]bool)
for item := range collection {
key := transform(item)
duplicated, seen := isDupl[key]
if !duplicated {
isDupl[key] = seen
}
}
for item := range collection {
key := transform(item)
if duplicated := isDupl[key]; !duplicated && !yield(item) {
return
}
}
}
}
// FindDuplicates returns a sequence with the first occurrence of each duplicated element in the collection.
// The order of result values is determined by the order duplicates occur in the collection.
// Will allocate a map large enough to hold all distinct elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/dw-VLQXKijT
func FindDuplicates[T comparable, I ~func(func(T) bool)](collection I) I {
return FindDuplicatesBy(collection, func(item T) T { return item })
}
// FindDuplicatesBy returns a sequence with the first occurrence of each duplicated element in the collection.
// The order of result values is determined by the order duplicates occur in the sequence. A transform function is
// invoked for each element in the sequence to generate the criterion by which uniqueness is computed.
// Will allocate a map large enough to hold all distinct transformed elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/tm1tZdC93OH
func FindDuplicatesBy[T any, U comparable, I ~func(func(T) bool)](collection I, transform func(item T) U) I {
return func(yield func(T) bool) {
isDupl := make(map[U]lo.Tuple2[T, bool])
for item := range collection {
key := transform(item)
if duplicated, ok := isDupl[key]; !ok {
isDupl[key] = lo.Tuple2[T, bool]{A: item}
} else if !duplicated.B {
if !yield(duplicated.A) {
return
}
isDupl[key] = lo.Tuple2[T, bool]{A: item, B: true}
}
}
}
}
// Min search the minimum value of a collection.
// Returns zero value when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/0VihyYEaM-M
func Min[T constraints.Ordered](collection iter.Seq[T]) T {
return MinBy(collection, func(a, b T) bool { return a < b })
}
// MinIndex search the minimum value of a collection and the index of the minimum value.
// Returns (zero value, -1) when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/70ncPxECj6l
func MinIndex[T constraints.Ordered](collection iter.Seq[T]) (T, int) {
return MinIndexBy(collection, func(a, b T) bool { return a < b })
}
// MinBy search the minimum value of a collection using the given comparison function.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns zero value when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/J5koo8khN-g
func MinBy[T any](collection iter.Seq[T], comparison func(a, b T) bool) T {
first := true
var mIn T
for item := range collection {
if first {
mIn = item
first = false
} else if comparison(item, mIn) {
mIn = item
}
}
return mIn
}
// MinIndexBy search the minimum value of a collection using the given comparison function and the index of the minimum value.
// If several values of the collection are equal to the smallest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/blldzWJpqVa
func MinIndexBy[T any](collection iter.Seq[T], comparison func(a, b T) bool) (T, int) {
var mIn T
index := -1
var i int
for item := range collection {
if i == 0 || comparison(item, mIn) {
mIn = item
index = i
}
i++
}
return mIn, index
}
// Earliest search the minimum time.Time of a collection.
// Returns zero value when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/fI6_S10H7Py
func Earliest(times iter.Seq[time.Time]) time.Time {
return MinBy(times, func(a, b time.Time) bool { return a.Before(b) })
}
// EarliestBy search the minimum time.Time of a collection using the given transform function.
// Returns zero value when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/y_Pf3Jmw-B4
func EarliestBy[T any](collection iter.Seq[T], transform func(item T) time.Time) T {
return MinBy(collection, func(a, b T) bool { return transform(a).Before(transform(b)) })
}
// Max searches the maximum value of a collection.
// Returns zero value when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/C2ZtW2bsBZ6
func Max[T constraints.Ordered](collection iter.Seq[T]) T {
return MaxBy(collection, func(a, b T) bool { return a > b })
}
// MaxIndex searches the maximum value of a collection and the index of the maximum value.
// Returns (zero value, -1) when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/zeu2wUvhl5e
func MaxIndex[T constraints.Ordered](collection iter.Seq[T]) (T, int) {
return MaxIndexBy(collection, func(a, b T) bool { return a > b })
}
// MaxBy search the maximum value of a collection using the given comparison function.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns zero value when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/yBhXFJb5oxC
func MaxBy[T any](collection iter.Seq[T], comparison func(a, b T) bool) T {
first := true
var mAx T
for item := range collection {
if first {
mAx = item
first = false
} else if comparison(item, mAx) {
mAx = item
}
}
return mAx
}
// MaxIndexBy search the maximum value of a collection using the given comparison function and the index of the maximum value.
// If several values of the collection are equal to the greatest value, returns the first such value.
// Returns (zero value, -1) when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/MXyE6BTILjx
func MaxIndexBy[T any](collection iter.Seq[T], comparison func(a, b T) bool) (T, int) {
var mAx T
index := -1
var i int
for item := range collection {
if i == 0 || comparison(item, mAx) {
mAx = item
index = i
}
i++
}
return mAx, index
}
// Latest search the maximum time.Time of a collection.
// Returns zero value when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/r5Yq6ATSHoH
func Latest(times iter.Seq[time.Time]) time.Time {
return MaxBy(times, func(a, b time.Time) bool { return a.After(b) })
}
// LatestBy search the maximum time.Time of a collection using the given transform function.
// Returns zero value when the collection is empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/o_daRzHrDUU
func LatestBy[T any](collection iter.Seq[T], transform func(item T) time.Time) T {
return MaxBy(collection, func(a, b T) bool { return transform(a).After(transform(b)) })
}
// First returns the first element of a collection and check for availability of the first element.
// Will iterate at most once.
// Play: https://go.dev/play/p/EhNyrc8jPfY
func First[T any](collection iter.Seq[T]) (T, bool) {
for item := range collection {
return item, true
}
return lo.Empty[T](), false
}
// FirstOrEmpty returns the first element of a collection or zero value if empty.
// Will iterate at most once.
// Play: https://go.dev/play/p/NTUTgPCfevx
func FirstOrEmpty[T any](collection iter.Seq[T]) T {
i, _ := First(collection)
return i
}
// FirstOr returns the first element of a collection or the fallback value if empty.
// Will iterate at most once.
// Play: https://go.dev/play/p/wGFXI5NHkE2
func FirstOr[T any](collection iter.Seq[T], fallback T) T {
if i, ok := First(collection); ok {
return i
}
return fallback
}
// Last returns the last element of a collection or error if empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/eGZV-sSmn_Q
func Last[T any](collection iter.Seq[T]) (T, bool) {
var t T
var ok bool
for item := range collection {
t = item
ok = true
}
return t, ok
}
// LastOrEmpty returns the last element of a collection or zero value if empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/teODFK4YqM4
func LastOrEmpty[T any](collection iter.Seq[T]) T {
i, _ := Last(collection)
return i
}
// LastOr returns the last element of a collection or the fallback value if empty.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/HNubjW2Mrxs
func LastOr[T any](collection iter.Seq[T], fallback T) T {
if i, ok := Last(collection); ok {
return i
}
return fallback
}
// Nth returns the element at index `nth` of collection. An error is returned when nth is out of bounds.
// Will iterate n times through the sequence.
// Play: https://go.dev/play/p/FqgCobsKqva
func Nth[T any, N constraints.Integer](collection iter.Seq[T], nth N) (T, error) {
value, ok := seqNth(collection, nth)
return value, lo.Validate(ok, "nth: %d out of bounds", nth)
}
func seqNth[T any, N constraints.Integer](collection iter.Seq[T], nth N) (T, bool) {
if nth >= 0 {
var i N
for item := range collection {
if i == nth {
return item, true
}
i++
}
}
return lo.Empty[T](), false
}
// NthOr returns the element at index `nth` of collection.
// If `nth` is out of bounds, it returns the fallback value instead of an error.
// Will iterate n times through the sequence.
// Play: https://go.dev/play/p/MNweuhpy4Ym
func NthOr[T any, N constraints.Integer](collection iter.Seq[T], nth N, fallback T) T {
value, ok := seqNth(collection, nth)
if !ok {
return fallback
}
return value
}
// NthOrEmpty returns the element at index `nth` of collection.
// If `nth` is out of bounds, it returns the zero value (empty value) for that type.
// Will iterate n times through the sequence.
// Play: https://go.dev/play/p/pC0Zhu3EUhe
func NthOrEmpty[T any, N constraints.Integer](collection iter.Seq[T], nth N) T {
value, _ := seqNth(collection, nth)
return value
}
// Sample returns a random item from collection.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/YDJVX0UXYDi
func Sample[T any](collection iter.Seq[T]) T {
return SampleBy(collection, xrand.IntN)
}
// SampleBy returns a random item from collection, using randomIntGenerator as the random index generator.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/QQooySxORib
func SampleBy[T any](collection iter.Seq[T], randomIntGenerator func(int) int) T {
slice := slices.Collect(collection)
return lo.SampleBy(slice, randomIntGenerator)
}
// Samples returns N random unique items from collection.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/GUTFx9LQ8pP
func Samples[T any, I ~func(func(T) bool)](collection I, count int) I {
return SamplesBy(collection, count, xrand.IntN)
}
// SamplesBy returns N random unique items from collection, using randomIntGenerator as the random index generator.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/fX2FEtixrVG
func SamplesBy[T any, I ~func(func(T) bool)](collection I, count int, randomIntGenerator func(int) int) I {
slice := slices.Collect(iter.Seq[T](collection))
seq := lo.SamplesBy(slice, count, randomIntGenerator)
return I(slices.Values(seq))
}
================================================
FILE: it/find_example_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"slices"
"time"
)
func ExampleIndexOf() {
list := slices.Values([]string{"foo", "bar", "baz"})
result := IndexOf(list, "bar")
fmt.Printf("%d", result)
// Output: 1
}
func ExampleIndexOf_notFound() {
list := slices.Values([]string{"foo", "bar", "baz"})
result := IndexOf(list, "qux")
fmt.Printf("%d", result)
// Output: -1
}
func ExampleLastIndexOf() {
list := slices.Values([]string{"foo", "bar", "baz", "bar"})
result := LastIndexOf(list, "bar")
fmt.Printf("%d", result)
// Output: 3
}
func ExampleLastIndexOf_notFound() {
list := slices.Values([]string{"foo", "bar", "baz"})
result := LastIndexOf(list, "qux")
fmt.Printf("%d", result)
// Output: -1
}
func ExampleFind() {
type User struct {
Name string
Age int
}
users := slices.Values([]User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
})
result, found := Find(users, func(user User) bool {
return user.Age > 30
})
fmt.Printf("%s %t", result.Name, found)
// Output: Charlie true
}
func ExampleFind_notFound() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result, found := Find(list, func(n int) bool {
return n > 10
})
fmt.Printf("%d %t", result, found)
// Output: 0 false
}
func ExampleFindIndexOf() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result, index, found := FindIndexOf(list, func(n int) bool {
return n > 2
})
fmt.Printf("%d %d %t", result, index, found)
// Output: 3 2 true
}
func ExampleFindIndexOf_notFound() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result, index, found := FindIndexOf(list, func(n int) bool {
return n > 10
})
fmt.Printf("%d %d %t", result, index, found)
// Output: 0 -1 false
}
func ExampleFindLastIndexOf() {
list := slices.Values([]int{1, 2, 3, 4, 3, 5})
result, index, found := FindLastIndexOf(list, func(n int) bool {
return n == 3
})
fmt.Printf("%d %d %t", result, index, found)
// Output: 3 4 true
}
func ExampleFindLastIndexOf_notFound() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result, index, found := FindLastIndexOf(list, func(n int) bool {
return n > 10
})
fmt.Printf("%d %d %t", result, index, found)
// Output: 0 -1 false
}
func ExampleFindOrElse() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := FindOrElse(list, -1, func(n int) bool {
return n > 10
})
fmt.Printf("%d", result)
// Output: -1
}
func ExampleFindOrElse_found() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := FindOrElse(list, -1, func(n int) bool {
return n > 3
})
fmt.Printf("%d", result)
// Output: 4
}
func ExampleFindUniques() {
list := slices.Values([]int{1, 2, 2, 3, 3, 3, 4, 5})
result := FindUniques(list)
fmt.Printf("%v", slices.Collect(result))
// Output: [1 4 5]
}
func ExampleFindUniquesBy() {
type User struct {
Name string
Age int
}
users := slices.Values([]User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 25},
{Name: "David", Age: 30},
{Name: "Eve", Age: 35},
})
result := FindUniquesBy(users, func(user User) int {
return user.Age
})
fmt.Printf("%d", len(slices.Collect(result)))
// Output: 1
}
func ExampleFindDuplicates() {
list := slices.Values([]int{1, 2, 2, 3, 3, 3, 4, 5})
result := FindDuplicates(list)
fmt.Printf("%v", slices.Collect(result))
// Output: [2 3]
}
func ExampleFindDuplicatesBy() {
type User struct {
Name string
Age int
}
users := slices.Values([]User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 25},
{Name: "David", Age: 30},
{Name: "Eve", Age: 35},
})
result := FindDuplicatesBy(users, func(user User) int {
return user.Age
})
fmt.Printf("%d", len(slices.Collect(result)))
// Output: 2
}
func ExampleMin() {
list := slices.Values([]int{3, 1, 4, 1, 5, 9, 2, 6})
result := Min(list)
fmt.Printf("%d", result)
// Output: 1
}
func ExampleMin_empty() {
list := slices.Values([]int{})
result := Min(list)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleMinIndex() {
list := slices.Values([]int{3, 1, 4, 1, 5, 9, 2, 6})
result, index := MinIndex(list)
fmt.Printf("%d %d", result, index)
// Output: 1 1
}
func ExampleMinIndex_empty() {
list := slices.Values([]int{})
result, index := MinIndex(list)
fmt.Printf("%d %d", result, index)
// Output: 0 -1
}
func ExampleMinBy() {
type User struct {
Name string
Age int
}
users := slices.Values([]User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
})
result := MinBy(users, func(a, b User) bool {
return a.Age < b.Age
})
fmt.Printf("%s", result.Name)
// Output: Alice
}
func ExampleMinIndexBy() {
type User struct {
Name string
Age int
}
users := slices.Values([]User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
})
result, index := MinIndexBy(users, func(a, b User) bool {
return a.Age < b.Age
})
fmt.Printf("%s %d", result.Name, index)
// Output: Alice 0
}
func ExampleEarliest() {
now := time.Now()
past := now.Add(-time.Hour)
future := now.Add(time.Hour)
result := Earliest(slices.Values([]time.Time{future, now, past}))
fmt.Printf("%t", result.Equal(past))
// Output: true
}
func ExampleEarliestBy() {
type Event struct {
Name string
Time time.Time
}
now := time.Now()
events := slices.Values([]Event{
{Name: "Event A", Time: now.Add(time.Hour)},
{Name: "Event B", Time: now},
{Name: "Event C", Time: now.Add(-time.Hour)},
})
result := EarliestBy(events, func(event Event) time.Time {
return event.Time
})
fmt.Printf("%s", result.Name)
// Output: Event C
}
func ExampleMax() {
list := slices.Values([]int{3, 1, 4, 1, 5, 9, 2, 6})
result := Max(list)
fmt.Printf("%d", result)
// Output: 9
}
func ExampleMax_empty() {
list := slices.Values([]int{})
result := Max(list)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleMaxIndex() {
list := slices.Values([]int{3, 1, 4, 1, 5, 9, 2, 6})
result, index := MaxIndex(list)
fmt.Printf("%d %d", result, index)
// Output: 9 5
}
func ExampleMaxIndex_empty() {
list := slices.Values([]int{})
result, index := MaxIndex(list)
fmt.Printf("%d %d", result, index)
// Output: 0 -1
}
func ExampleMaxBy() {
type User struct {
Name string
Age int
}
users := slices.Values([]User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
})
result := MaxBy(users, func(a, b User) bool {
return a.Age > b.Age
})
fmt.Printf("%s", result.Name)
// Output: Charlie
}
func ExampleMaxIndexBy() {
type User struct {
Name string
Age int
}
users := slices.Values([]User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
})
result, index := MaxIndexBy(users, func(a, b User) bool {
return a.Age > b.Age
})
fmt.Printf("%s %d", result.Name, index)
// Output: Charlie 2
}
func ExampleLatest() {
now := time.Now()
past := now.Add(-time.Hour)
future := now.Add(time.Hour)
result := Latest(slices.Values([]time.Time{future, now, past}))
fmt.Printf("%t", result.Equal(future))
// Output: true
}
func ExampleLatestBy() {
type Event struct {
Name string
Time time.Time
}
now := time.Now()
events := slices.Values([]Event{
{Name: "Event A", Time: now.Add(time.Hour)},
{Name: "Event B", Time: now},
{Name: "Event C", Time: now.Add(-time.Hour)},
})
result := LatestBy(events, func(event Event) time.Time {
return event.Time
})
fmt.Printf("%s", result.Name)
// Output: Event A
}
func ExampleFirst() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result, found := First(list)
fmt.Printf("%d %t", result, found)
// Output: 1 true
}
func ExampleFirst_empty() {
list := slices.Values([]int{})
result, found := First(list)
fmt.Printf("%d %t", result, found)
// Output: 0 false
}
func ExampleFirstOrEmpty() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := FirstOrEmpty(list)
fmt.Printf("%d", result)
// Output: 1
}
func ExampleFirstOrEmpty_empty() {
list := slices.Values([]int{})
result := FirstOrEmpty(list)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleFirstOr() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := FirstOr(list, -1)
fmt.Printf("%d", result)
// Output: 1
}
func ExampleFirstOr_empty() {
list := slices.Values([]int{})
result := FirstOr(list, -1)
fmt.Printf("%d", result)
// Output: -1
}
func ExampleLast() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result, found := Last(list)
fmt.Printf("%d %t", result, found)
// Output: 5 true
}
func ExampleLast_empty() {
list := slices.Values([]int{})
result, found := Last(list)
fmt.Printf("%d %t", result, found)
// Output: 0 false
}
func ExampleLastOrEmpty() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := LastOrEmpty(list)
fmt.Printf("%d", result)
// Output: 5
}
func ExampleLastOrEmpty_empty() {
list := slices.Values([]int{})
result := LastOrEmpty(list)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleLastOr() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := LastOr(list, -1)
fmt.Printf("%d", result)
// Output: 5
}
func ExampleLastOr_empty() {
list := slices.Values([]int{})
result := LastOr(list, -1)
fmt.Printf("%d", result)
// Output: -1
}
func ExampleNth() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result, err := Nth(list, 2)
fmt.Printf("%d %v", result, err)
// Output: 3
}
func ExampleNth_outOfBounds() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result, err := Nth(list, 10)
fmt.Printf("%d %v", result, err)
// Output: 0 nth: 10 out of bounds
}
func ExampleNthOr() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := NthOr(list, 2, -1)
fmt.Printf("%d", result)
// Output: 3
}
func ExampleNthOr_outOfBounds() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := NthOr(list, 10, -1)
fmt.Printf("%d", result)
// Output: -1
}
func ExampleNthOrEmpty() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := NthOrEmpty(list, 2)
fmt.Printf("%d", result)
// Output: 3
}
func ExampleNthOrEmpty_outOfBounds() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := NthOrEmpty(list, 10)
fmt.Printf("%d", result)
// Output: 0
}
================================================
FILE: it/find_test.go
================================================
//go:build go1.23
package it
import (
"iter"
"slices"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/samber/lo/internal/xrand"
)
func TestIndexOf(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := IndexOf(values(0, 1, 2, 1, 2, 3), 2)
result2 := IndexOf(values(0, 1, 2, 1, 2, 3), 6)
is.Equal(2, result1)
is.Equal(-1, result2)
}
func TestLastIndexOf(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := LastIndexOf(values(0, 1, 2, 1, 2, 3), 2)
result2 := LastIndexOf(values(0, 1, 2, 1, 2, 3), 6)
is.Equal(4, result1)
is.Equal(-1, result2)
}
func TestHasPrefix(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(HasPrefix(values(1, 2, 3, 4), 1, 2, 3, 4))
is.True(HasPrefix(values(1, 2, 3, 4), 1, 2))
is.False(HasPrefix(values(1, 2, 3, 4), 42))
is.False(HasPrefix(values(1, 2), 1, 2, 3, 4))
is.True(HasPrefix(values(1, 2, 3, 4)))
}
func TestHasSuffix(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(HasSuffix(values(1, 2, 3, 4), 1, 2, 3, 4))
is.True(HasSuffix(values(1, 2, 3, 4), 3, 4))
is.True(HasSuffix(values(1, 2, 3, 4, 5), 3, 4, 5))
is.False(HasSuffix(values(1, 2, 3, 4), 42))
is.False(HasSuffix(values(1, 2), 1, 2, 3, 4))
is.True(HasSuffix(values(1, 2, 3, 4)))
is.False(HasSuffix(values(0), 0, 0))
}
func TestFind(t *testing.T) {
t.Parallel()
is := assert.New(t)
index := 0
result1, ok1 := Find(values("a", "b", "c", "d"), func(item string) bool {
is.Equal([]string{"a", "b", "c", "d"}[index], item)
index++
return item == "b"
})
result2, ok2 := Find(values("foobar"), func(item string) bool {
is.Equal("foobar", item)
return item == "b"
})
is.True(ok1)
is.Equal("b", result1)
is.False(ok2)
is.Empty(result2)
}
func TestFindIndexOf(t *testing.T) {
t.Parallel()
is := assert.New(t)
index := 0
item1, index1, ok1 := FindIndexOf(values("a", "b", "c", "d", "b"), func(item string) bool {
is.Equal([]string{"a", "b", "c", "d", "b"}[index], item)
index++
return item == "b"
})
item2, index2, ok2 := FindIndexOf(values("foobar"), func(item string) bool {
is.Equal("foobar", item)
return item == "b"
})
is.Equal("b", item1)
is.True(ok1)
is.Equal(1, index1)
is.Empty(item2)
is.False(ok2)
is.Equal(-1, index2)
}
func TestFindLastIndexOf(t *testing.T) {
t.Parallel()
is := assert.New(t)
item1, index1, ok1 := FindLastIndexOf(values("a", "b", "c", "d", "b"), func(item string) bool {
return item == "b"
})
item2, index2, ok2 := FindLastIndexOf(values("foobar"), func(item string) bool {
return item == "b"
})
is.Equal("b", item1)
is.True(ok1)
is.Equal(4, index1)
is.Empty(item2)
is.False(ok2)
is.Equal(-1, index2)
}
func TestFindOrElse(t *testing.T) {
t.Parallel()
is := assert.New(t)
index := 0
result1 := FindOrElse(values("a", "b", "c", "d"), "x", func(item string) bool {
is.Equal([]string{"a", "b", "c", "d"}[index], item)
index++
return item == "b"
})
result2 := FindOrElse(values("foobar"), "x", func(item string) bool {
is.Equal("foobar", item)
return item == "b"
})
is.Equal("b", result1)
is.Equal("x", result2)
}
func TestFindUniques(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FindUniques(values(1, 2, 3))
is.Equal([]int{1, 2, 3}, slices.Collect(result1))
result2 := FindUniques(values(1, 2, 2, 3, 1, 2))
is.Equal([]int{3}, slices.Collect(result2))
result3 := FindUniques(values(1, 2, 2, 1))
is.Empty(slices.Collect(result3))
result4 := FindUniques(values[int]())
is.Empty(slices.Collect(result4))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := FindUniques(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFindUniquesBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FindUniquesBy(values(0, 1, 2), func(i int) int {
return i % 3
})
is.Equal([]int{0, 1, 2}, slices.Collect(result1))
result2 := FindUniquesBy(values(0, 1, 2, 3, 4), func(i int) int {
return i % 3
})
is.Equal([]int{2}, slices.Collect(result2))
result3 := FindUniquesBy(values(0, 1, 2, 3, 4, 5), func(i int) int {
return i % 3
})
is.Empty(slices.Collect(result3))
result4 := FindUniquesBy(values[int](), func(i int) int {
return i % 3
})
is.Empty(slices.Collect(result4))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := FindUniquesBy(allStrings, func(i string) string {
return i
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFindDuplicates(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FindDuplicates(values(1, 2, 2, 1, 2, 3))
is.Equal([]int{2, 1}, slices.Collect(result1))
result2 := FindDuplicates(values(1, 2, 3))
is.Empty(slices.Collect(result2))
result3 := FindDuplicates(values[int]())
is.Empty(slices.Collect(result3))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := FindDuplicates(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFindDuplicatesBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FindDuplicatesBy(values(3, 4, 5, 6, 7), func(i int) int {
return i % 3
})
is.Equal([]int{3, 4}, slices.Collect(result1))
result2 := FindDuplicatesBy(values(0, 1, 2, 3, 4), func(i int) int {
return i % 5
})
is.Empty(slices.Collect(result2))
result3 := FindDuplicatesBy(values[int](), func(i int) int {
return i % 3
})
is.Empty(slices.Collect(result3))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := FindDuplicatesBy(allStrings, func(i string) string {
return i
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestMin(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Min(values(1, 2, 3))
result2 := Min(values(3, 2, 1))
result3 := Min(values(time.Second, time.Minute, time.Hour))
result4 := Min(values[int]())
is.Equal(1, result1)
is.Equal(1, result2)
is.Equal(time.Second, result3)
is.Zero(result4)
}
func TestMinIndex(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, index1 := MinIndex(values(1, 2, 3))
result2, index2 := MinIndex(values(3, 2, 1))
result3, index3 := MinIndex(values(time.Second, time.Minute, time.Hour))
result4, index4 := MinIndex(values[int]())
is.Equal(1, result1)
is.Zero(index1)
is.Equal(1, result2)
is.Equal(2, index2)
is.Equal(time.Second, result3)
is.Zero(index3)
is.Zero(result4)
is.Equal(-1, index4)
}
func TestMinBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MinBy(values("s1", "string2", "s3"), func(item, mIn string) bool {
return len(item) < len(mIn)
})
result2 := MinBy(values("string1", "string2", "s3"), func(item, mIn string) bool {
return len(item) < len(mIn)
})
result3 := MinBy(values[string](), func(item, mIn string) bool {
return len(item) < len(mIn)
})
is.Equal("s1", result1)
is.Equal("s3", result2)
is.Empty(result3)
}
func TestMinIndexBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, index1 := MinIndexBy(values("s1", "string2", "s3"), func(item, mIn string) bool {
return len(item) < len(mIn)
})
result2, index2 := MinIndexBy(values("string1", "string2", "s3"), func(item, mIn string) bool {
return len(item) < len(mIn)
})
result3, index3 := MinIndexBy(values[string](), func(item, mIn string) bool {
return len(item) < len(mIn)
})
is.Equal("s1", result1)
is.Zero(index1)
is.Equal("s3", result2)
is.Equal(2, index2)
is.Empty(result3)
is.Equal(-1, index3)
}
func TestEarliest(t *testing.T) {
t.Parallel()
is := assert.New(t)
a := time.Now()
b := a.Add(time.Hour)
result1 := Earliest(values(a, b))
result2 := Earliest(values[time.Time]())
is.Equal(a, result1)
is.Zero(result2)
}
func TestEarliestBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type foo struct {
bar time.Time
}
t1 := time.Now()
t2 := t1.Add(time.Hour)
t3 := t1.Add(-time.Hour)
result1 := EarliestBy(values(foo{t1}, foo{t2}, foo{t3}), func(i foo) time.Time {
return i.bar
})
result2 := EarliestBy(values(foo{t1}), func(i foo) time.Time {
return i.bar
})
result3 := EarliestBy(values[foo](), func(i foo) time.Time {
return i.bar
})
is.Equal(foo{t3}, result1)
is.Equal(foo{t1}, result2)
is.Zero(result3)
}
func TestMax(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Max(values(1, 2, 3))
result2 := Max(values(3, 2, 1))
result3 := Max(values(time.Second, time.Minute, time.Hour))
result4 := Max(values[int]())
is.Equal(3, result1)
is.Equal(3, result2)
is.Equal(time.Hour, result3)
is.Zero(result4)
}
func TestMaxIndex(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, index1 := MaxIndex(values(1, 2, 3))
result2, index2 := MaxIndex(values(3, 2, 1))
result3, index3 := MaxIndex(values(time.Second, time.Minute, time.Hour))
result4, index4 := MaxIndex(values[int]())
is.Equal(3, result1)
is.Equal(2, index1)
is.Equal(3, result2)
is.Zero(index2)
is.Equal(time.Hour, result3)
is.Equal(2, index3)
is.Zero(result4)
is.Equal(-1, index4)
}
func TestMaxBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MaxBy(values("s1", "string2", "s3"), func(item, mAx string) bool {
return len(item) > len(mAx)
})
result2 := MaxBy(values("string1", "string2", "s3"), func(item, mAx string) bool {
return len(item) > len(mAx)
})
result3 := MaxBy(values[string](), func(item, mAx string) bool {
return len(item) > len(mAx)
})
is.Equal("string2", result1)
is.Equal("string1", result2)
is.Empty(result3)
}
func TestMaxIndexBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, index1 := MaxIndexBy(values("s1", "string2", "s3"), func(item, mAx string) bool {
return len(item) > len(mAx)
})
result2, index2 := MaxIndexBy(values("string1", "string2", "s3"), func(item, mAx string) bool {
return len(item) > len(mAx)
})
result3, index3 := MaxIndexBy(values[string](), func(item, mAx string) bool {
return len(item) > len(mAx)
})
is.Equal("string2", result1)
is.Equal(1, index1)
is.Equal("string1", result2)
is.Zero(index2)
is.Empty(result3)
is.Equal(-1, index3)
}
func TestLatest(t *testing.T) {
t.Parallel()
is := assert.New(t)
a := time.Now()
b := a.Add(time.Hour)
result1 := Latest(values(a, b))
result2 := Latest(values[time.Time]())
is.Equal(b, result1)
is.Zero(result2)
}
func TestLatestBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type foo struct {
bar time.Time
}
t1 := time.Now()
t2 := t1.Add(time.Hour)
t3 := t1.Add(-time.Hour)
result1 := LatestBy(values(foo{t1}, foo{t2}, foo{t3}), func(i foo) time.Time {
return i.bar
})
result2 := LatestBy(values(foo{t1}), func(i foo) time.Time {
return i.bar
})
result3 := LatestBy(values[foo](), func(i foo) time.Time {
return i.bar
})
is.Equal(foo{t2}, result1)
is.Equal(foo{t1}, result2)
is.Zero(result3)
}
func TestFirst(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, ok1 := First(values(1, 2, 3))
result2, ok2 := First(values[int]())
is.Equal(1, result1)
is.True(ok1)
is.Zero(result2)
is.False(ok2)
}
func TestFirstOrEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FirstOrEmpty(values(1, 2, 3))
result2 := FirstOrEmpty(values[int]())
result3 := FirstOrEmpty(values[string]())
is.Equal(1, result1)
is.Zero(result2)
is.Empty(result3)
}
func TestFirstOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FirstOr(values(1, 2, 3), 63)
result2 := FirstOr(values[int](), 23)
result3 := FirstOr(values[string](), "test")
is.Equal(1, result1)
is.Equal(23, result2)
is.Equal("test", result3)
}
func TestLast(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, ok1 := Last(values(1, 2, 3))
result2, ok2 := Last(values[int]())
is.Equal(3, result1)
is.True(ok1)
is.Zero(result2)
is.False(ok2)
}
func TestLastOrEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := LastOrEmpty(values(1, 2, 3))
result2 := LastOrEmpty(values[int]())
result3 := LastOrEmpty(values[string]())
is.Equal(3, result1)
is.Zero(result2)
is.Empty(result3)
}
func TestLastOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := LastOr(values(1, 2, 3), 63)
result2 := LastOr(values[int](), 23)
result3 := LastOr(values[string](), "test")
is.Equal(3, result1)
is.Equal(23, result2)
is.Equal("test", result3)
}
func TestNth(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1, err1 := Nth(values(0, 1, 2, 3), 2)
result2, err2 := Nth(values(0, 1, 2, 3), -2)
result3, err3 := Nth(values(0, 1, 2, 3), 42)
result4, err4 := Nth(values[int](), 0)
result5, err5 := Nth(values(42), 0)
result6, err6 := Nth(values(42), -1)
is.Equal(2, result1)
is.NoError(err1)
is.Zero(result2)
is.EqualError(err2, "nth: -2 out of bounds")
is.Zero(result3)
is.EqualError(err3, "nth: 42 out of bounds")
is.Zero(result4)
is.EqualError(err4, "nth: 0 out of bounds")
is.Equal(42, result5)
is.NoError(err5)
is.Zero(result6)
is.EqualError(err6, "nth: -1 out of bounds")
}
func TestNthOr(t *testing.T) {
t.Parallel()
t.Run("Integers", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
const defaultValue = -1
ints := values(10, 20, 30, 40, 50)
is.Equal(30, NthOr(ints, 2, defaultValue))
is.Equal(defaultValue, NthOr(ints, -1, defaultValue))
is.Equal(defaultValue, NthOr(ints, 5, defaultValue))
})
t.Run("Strings", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
const defaultValue = "none"
strs := values("apple", "banana", "cherry", "date")
is.Equal("banana", NthOr(strs, 1, defaultValue)) // Index 1, expected "banana"
is.Equal(defaultValue, NthOr(strs, -2, defaultValue)) // Negative index -2, expected "cherry"
is.Equal(defaultValue, NthOr(strs, 10, defaultValue)) // Out of bounds, fallback "none"
})
t.Run("Structs", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
ID int
Name string
}
users := values(
User{ID: 1, Name: "Alice"},
User{ID: 2, Name: "Bob"},
User{ID: 3, Name: "Charlie"},
)
defaultValue := User{ID: 0, Name: "Unknown"}
is.Equal(User{ID: 1, Name: "Alice"}, NthOr(users, 0, defaultValue))
is.Equal(defaultValue, NthOr(users, -1, defaultValue))
is.Equal(defaultValue, NthOr(users, 10, defaultValue))
})
}
func TestNthOrEmpty(t *testing.T) {
t.Parallel()
t.Run("Integers", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
ints := values(10, 20, 30, 40, 50)
is.Equal(30, NthOrEmpty(ints, 2))
is.Zero(NthOrEmpty(ints, -1))
is.Zero(NthOrEmpty(ints, 10))
})
t.Run("Strings", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
strs := values("apple", "banana", "cherry", "date")
is.Equal("banana", NthOrEmpty(strs, 1))
is.Empty(NthOrEmpty(strs, -2))
is.Empty(NthOrEmpty(strs, 10))
})
t.Run("Structs", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
ID int
Name string
}
users := values(
User{ID: 1, Name: "Alice"},
User{ID: 2, Name: "Bob"},
User{ID: 3, Name: "Charlie"},
)
is.Equal(User{ID: 1, Name: "Alice"}, NthOrEmpty(users, 0))
is.Zero(NthOrEmpty(users, -1))
is.Zero(NthOrEmpty(users, 10))
})
}
func TestSample(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Sample(values("a", "b", "c"))
result2 := Sample(values[string]())
is.True(Contains(values("a", "b", "c"), result1))
is.Empty(result2)
}
func TestSampleBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := SampleBy(values("a", "b", "c"), xrand.IntN)
result2 := SampleBy(values[string](), xrand.IntN)
is.True(Contains(values("a", "b", "c"), result1))
is.Empty(result2)
}
func TestSamples(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Samples(values("a", "b", "c"), 3)
result2 := Samples(values[string](), 3)
is.ElementsMatch(slices.Collect(result1), []string{"a", "b", "c"})
is.Empty(slices.Collect(result2))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Samples(allStrings, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestSamplesBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := SamplesBy(values("a", "b", "c"), 3, xrand.IntN)
result2 := SamplesBy(values[string](), 3, xrand.IntN)
result3 := SamplesBy(values("a", "b", "c"), 3, func(n int) int { return n - 1 })
result4 := SamplesBy(values("a", "b", "c"), 3, func(int) int { return 0 })
result5 := SamplesBy(values("a", "b", "c"), 0, func(int) int { return 1 })
result6 := SamplesBy(values("a", "b", "c"), -1, nil)
// index out of range [1] with length 1
is.Panics(func() {
SamplesBy(values("a", "b", "c"), 3, func(int) int { return 1 })
})
is.ElementsMatch(slices.Collect(result1), []string{"a", "b", "c"})
is.Empty(slices.Collect(result2))
is.Equal([]string{"c", "b", "a"}, slices.Collect(result3))
is.Equal([]string{"a", "c", "b"}, slices.Collect(result4))
is.Empty(slices.Collect(result5))
is.Empty(slices.Collect(result6))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := SamplesBy(allStrings, 2, xrand.IntN)
is.IsType(nonempty, allStrings, "type preserved")
}
================================================
FILE: it/intersect.go
================================================
//go:build go1.23
package it
import (
"iter"
"github.com/samber/lo"
)
// Contains returns true if an element is present in a collection.
// Will iterate through the entire sequence if element is not found.
// Play: https://go.dev/play/p/1edj7hH3TS2
func Contains[T comparable](collection iter.Seq[T], element T) bool {
return ContainsBy(collection, func(item T) bool { return item == element })
}
// ContainsBy returns true if predicate function return true.
// Will iterate through the entire sequence if predicate never returns true.
func ContainsBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool {
for item := range collection {
if predicate(item) {
return true
}
}
return false
}
// Every returns true if all elements of a subset are contained in a collection or if the subset is empty.
// Will iterate through the entire sequence if subset elements always match.
// Play: https://go.dev/play/p/rwM9Y353aIC
func Every[T comparable](collection iter.Seq[T], subset ...T) bool {
if len(subset) == 0 {
return true
}
set := lo.Keyify(subset)
for item := range collection {
if _, ok := set[item]; ok {
delete(set, item)
if len(set) == 0 {
return true
}
}
}
return false
}
// EveryBy returns true if the predicate returns true for all elements in the collection or if the collection is empty.
// Will iterate through the entire sequence if predicate never returns false.
func EveryBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool {
for item := range collection {
if !predicate(item) {
return false
}
}
return true
}
// Some returns true if at least 1 element of a subset is contained in a collection.
// If the subset is empty Some returns false.
// Will iterate through the entire sequence if subset elements never match.
// Play: https://go.dev/play/p/KmX-fXictQl
func Some[T comparable](collection iter.Seq[T], subset ...T) bool {
if len(subset) == 0 {
return false
}
seen := lo.Keyify(subset)
return SomeBy(collection, func(item T) bool {
_, ok := seen[item]
return ok
})
}
// SomeBy returns true if the predicate returns true for any of the elements in the collection.
// If the collection is empty SomeBy returns false.
// Will iterate through the entire sequence if predicate never returns true.
func SomeBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool {
for item := range collection {
if predicate(item) {
return true
}
}
return false
}
// None returns true if no element of a subset is contained in a collection or if the subset is empty.
// Will iterate through the entire sequence if subset elements never match.
// Play: https://go.dev/play/p/L7mm5S4a8Yo
func None[T comparable](collection iter.Seq[T], subset ...T) bool {
if len(subset) == 0 {
return true
}
seen := lo.Keyify(subset)
return NoneBy(collection, func(item T) bool {
_, ok := seen[item]
return ok
})
}
// NoneBy returns true if the predicate returns true for none of the elements in the collection or if the collection is empty.
// Will iterate through the entire sequence if predicate never returns true.
func NoneBy[T any](collection iter.Seq[T], predicate func(item T) bool) bool {
for item := range collection {
if predicate(item) {
return false
}
}
return true
}
// Intersect returns the intersection between given collections.
// Will allocate a map large enough to hold all distinct elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/kz3cGhGZZWF
func Intersect[T comparable, I ~func(func(T) bool)](lists ...I) I {
if len(lists) == 0 {
return I(Empty[T]())
}
return func(yield func(T) bool) {
last := lists[len(lists)-1]
seen := make(map[T]bool)
for item := range last {
seen[item] = false
}
for i := len(lists) - 2; i > 0 && len(seen) != 0; i-- {
for item := range lists[i] {
if _, ok := seen[item]; ok {
seen[item] = true
}
}
for k, v := range seen {
if v {
seen[k] = false
} else {
delete(seen, k)
}
}
}
for item := range lists[0] {
if _, ok := seen[item]; ok {
if !yield(item) {
return
}
delete(seen, item)
}
}
}
}
// IntersectBy returns the intersection between given collections using a
// custom key selector function.
// Will allocate a map large enough to hold all distinct elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/X2nEvHC-lE2
func IntersectBy[T any, K comparable, I ~func(func(T) bool)](transform func(T) K, lists ...I) I {
if len(lists) == 0 {
return I(Empty[T]())
}
return func(yield func(T) bool) {
last := lists[len(lists)-1]
seen := make(map[K]bool)
for item := range last {
k := transform(item)
seen[k] = false
}
for i := len(lists) - 2; i > 0 && len(seen) != 0; i-- {
for item := range lists[i] {
k := transform(item)
if _, ok := seen[k]; ok {
seen[k] = true
}
}
for k, v := range seen {
if v {
seen[k] = false
} else {
delete(seen, k)
}
}
}
for item := range lists[0] {
k := transform(item)
if _, ok := seen[k]; ok {
if !yield(item) {
return
}
delete(seen, k)
}
}
}
}
// Union returns all distinct elements from given collections.
// Will allocate a map large enough to hold all distinct elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/ImIoFNpSUUB
func Union[T comparable, I ~func(func(T) bool)](lists ...I) I {
return func(yield func(T) bool) {
seen := make(map[T]struct{})
for i := range lists {
for item := range lists[i] {
if _, ok := seen[item]; !ok {
if !yield(item) {
return
}
seen[item] = struct{}{}
}
}
}
}
}
// Without returns a sequence excluding all given values.
// Will allocate a map large enough to hold all distinct excludes.
// Play: https://go.dev/play/p/LbN55AVBZ7h
func Without[T comparable, I ~func(func(T) bool)](collection I, exclude ...T) I {
return WithoutBy(collection, func(item T) T { return item }, exclude...)
}
// WithoutBy filters a sequence by excluding elements whose extracted keys match any in the exclude list.
// Returns a sequence containing only the elements whose keys are not in the exclude list.
// Will allocate a map large enough to hold all distinct excludes.
// Play: https://go.dev/play/p/Hm734hnLnLI
func WithoutBy[T any, K comparable, I ~func(func(T) bool)](collection I, transform func(item T) K, exclude ...K) I {
set := lo.Keyify(exclude)
return Reject(collection, func(item T) bool { return lo.HasKey(set, transform(item)) })
}
// WithoutNth returns a sequence excluding the nth value.
// Will allocate a map large enough to hold all distinct nths.
// Play: https://go.dev/play/p/KGE7Lpsk18P
func WithoutNth[T comparable, I ~func(func(T) bool)](collection I, nths ...int) I {
set := lo.Keyify(nths)
return RejectI(collection, func(_ T, index int) bool { return lo.HasKey(set, index) })
}
// ElementsMatch returns true if lists contain the same set of elements (including empty set).
// If there are duplicate elements, the number of occurrences in each list should match.
// The order of elements is not checked.
// Will iterate through each sequence before returning and allocate a map large enough to hold all distinct elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/24SGQm1yMRe
func ElementsMatch[T comparable](list1, list2 iter.Seq[T]) bool {
return ElementsMatchBy(list1, list2, func(item T) T { return item })
}
// ElementsMatchBy returns true if lists contain the same set of elements' keys (including empty set).
// If there are duplicate keys, the number of occurrences in each list should match.
// The order of elements is not checked.
// Will iterate through each sequence before returning and allocate a map large enough to hold all distinct transformed elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/I3vFrmQo43E
func ElementsMatchBy[T any, K comparable](list1, list2 iter.Seq[T], transform func(item T) K) bool {
counters := make(map[K]int)
for item := range list1 {
counters[transform(item)]++
}
for item := range list2 {
counters[transform(item)]--
}
for _, count := range counters {
if count != 0 {
return false
}
}
return true
}
================================================
FILE: it/intersect_example_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"slices"
)
func ExampleWithoutBy() {
type User struct {
ID int
Name string
}
// original users
users := values(
User{ID: 1, Name: "Alice"},
User{ID: 2, Name: "Bob"},
User{ID: 3, Name: "Charlie"},
)
// exclude users with IDs 2 and 3
excludedIDs := []int{2, 3}
// extract function to get the user ID
extractID := func(user User) int {
return user.ID
}
// filtering users
filteredUsers := WithoutBy(users, extractID, excludedIDs...)
// output the filtered users
fmt.Printf("%v", slices.Collect(filteredUsers))
// Output:
// [{1 Alice}]
}
================================================
FILE: it/intersect_test.go
================================================
//go:build go1.23
package it
import (
"iter"
"slices"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestContains(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Contains(values(0, 1, 2, 3, 4, 5), 5)
result2 := Contains(values(0, 1, 2, 3, 4, 5), 6)
is.True(result1)
is.False(result2)
}
func TestContainsBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type a struct {
A int
B string
}
a1 := values(a{A: 1, B: "1"}, a{A: 2, B: "2"}, a{A: 3, B: "3"})
result1 := ContainsBy(a1, func(t a) bool { return t.A == 1 && t.B == "2" })
result2 := ContainsBy(a1, func(t a) bool { return t.A == 2 && t.B == "2" })
a2 := values("aaa", "bbb", "ccc")
result3 := ContainsBy(a2, func(t string) bool { return t == "ccc" })
result4 := ContainsBy(a2, func(t string) bool { return t == "ddd" })
is.False(result1)
is.True(result2)
is.True(result3)
is.False(result4)
}
func TestEvery(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Every(values(0, 1, 2, 3, 4, 5), 0, 2)
result2 := Every(values(0, 1, 2, 3, 4, 5), 0, 6)
result3 := Every(values(0, 1, 2, 3, 4, 5), -1, 6)
result4 := Every(values(0, 1, 2, 3, 4, 5))
is.True(result1)
is.False(result2)
is.False(result3)
is.True(result4)
}
func TestEveryBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := EveryBy(values(1, 2, 3, 4), func(x int) bool {
return x < 5
})
is.True(result1)
result2 := EveryBy(values(1, 2, 3, 4), func(x int) bool {
return x < 3
})
is.False(result2)
result3 := EveryBy(values(1, 2, 3, 4), func(x int) bool {
return x < 0
})
is.False(result3)
result4 := EveryBy(values[int](), func(x int) bool {
return x < 5
})
is.True(result4)
}
func TestSome(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Some(values(0, 1, 2, 3, 4, 5), 0, 2)
result2 := Some(values(0, 1, 2, 3, 4, 5), 0, 6)
result3 := Some(values(0, 1, 2, 3, 4, 5), -1, 6)
result4 := Some(values(0, 1, 2, 3, 4, 5))
is.True(result1)
is.True(result2)
is.False(result3)
is.False(result4)
}
func TestSomeBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := SomeBy(values(1, 2, 3, 4), func(x int) bool {
return x < 5
})
is.True(result1)
result2 := SomeBy(values(1, 2, 3, 4), func(x int) bool {
return x < 3
})
is.True(result2)
result3 := SomeBy(values(1, 2, 3, 4), func(x int) bool {
return x < 0
})
is.False(result3)
result4 := SomeBy(values[int](), func(x int) bool {
return x < 5
})
is.False(result4)
}
func TestNone(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := None(values(0, 1, 2, 3, 4, 5), 0, 2)
result2 := None(values(0, 1, 2, 3, 4, 5), 0, 6)
result3 := None(values(0, 1, 2, 3, 4, 5), -1, 6)
result4 := None(values(0, 1, 2, 3, 4, 5))
is.False(result1)
is.False(result2)
is.True(result3)
is.True(result4)
}
func TestNoneBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := NoneBy(values(1, 2, 3, 4), func(x int) bool {
return x < 5
})
is.False(result1)
result2 := NoneBy(values(1, 2, 3, 4), func(x int) bool {
return x < 3
})
is.False(result2)
result3 := NoneBy(values(1, 2, 3, 4), func(x int) bool {
return x < 0
})
is.True(result3)
result4 := NoneBy(values[int](), func(x int) bool {
return x < 5
})
is.True(result4)
}
func TestIntersect(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Intersect([]iter.Seq[int]{}...)
result2 := Intersect(values(0, 1, 2, 3, 4, 5))
result3 := Intersect(values(0, 1, 2, 3, 4, 5), values(0, 6))
result4 := Intersect(values(0, 1, 2, 3, 4, 5), values(-1, 6))
result5 := Intersect(values(0, 6, 0), values(0, 1, 2, 3, 4, 5))
result6 := Intersect(values(0, 1, 2, 3, 4, 5), values(0, 6, 0))
result7 := Intersect(values(0, 1, 2), values(1, 2, 3), values(2, 3, 4))
result8 := Intersect(values(0, 1, 2), values(1, 2, 3), values(2, 3, 4), values(3, 4, 5))
result9 := Intersect(values(0, 1, 2), values(0, 1, 2), values(1, 2, 3), values(2, 3, 4), values(3, 4, 5))
resultA := Intersect(values(0, 1, 1))
is.Empty(slices.Collect(result1))
is.Equal([]int{0, 1, 2, 3, 4, 5}, slices.Collect(result2))
is.Equal([]int{0}, slices.Collect(result3))
is.Empty(slices.Collect(result4))
is.Equal([]int{0}, slices.Collect(result5))
is.Equal([]int{0}, slices.Collect(result6))
is.Equal([]int{2}, slices.Collect(result7))
is.Empty(slices.Collect(result8))
is.Empty(slices.Collect(result9))
is.Equal([]int{0, 1}, slices.Collect(resultA))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Intersect(allStrings, allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestIntersectBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
transform := strconv.Itoa
result1 := IntersectBy(transform, []iter.Seq[int]{}...)
result2 := IntersectBy(transform, values(0, 1, 2, 3, 4, 5))
result3 := IntersectBy(transform, values(0, 1, 2, 3, 4, 5), values(0, 6))
result4 := IntersectBy(transform, values(0, 1, 2, 3, 4, 5), values(-1, 6))
result5 := IntersectBy(transform, values(0, 6, 0), values(0, 1, 2, 3, 4, 5))
result6 := IntersectBy(transform, values(0, 1, 2, 3, 4, 5), values(0, 6, 0))
result7 := IntersectBy(transform, values(0, 1, 2), values(1, 2, 3), values(2, 3, 4))
result8 := IntersectBy(transform, values(0, 1, 2), values(1, 2, 3), values(2, 3, 4), values(3, 4, 5))
result9 := IntersectBy(transform, values(0, 1, 2), values(0, 1, 2), values(1, 2, 3), values(2, 3, 4), values(3, 4, 5))
resultA := IntersectBy(transform, values(0, 1, 1))
is.Empty(slices.Collect(result1))
is.Equal([]int{0, 1, 2, 3, 4, 5}, slices.Collect(result2))
is.Equal([]int{0}, slices.Collect(result3))
is.Empty(slices.Collect(result4))
is.Equal([]int{0}, slices.Collect(result5))
is.Equal([]int{0}, slices.Collect(result6))
is.Equal([]int{2}, slices.Collect(result7))
is.Empty(slices.Collect(result8))
is.Empty(slices.Collect(result9))
is.Equal([]int{0, 1}, slices.Collect(resultA))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := IntersectBy(func(s string) string { return s + s }, allStrings, allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestUnion(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Union(values(0, 1, 2, 3, 4, 5), values(0, 2, 10))
result2 := Union(values(0, 1, 2, 3, 4, 5), values(6, 7))
result3 := Union(values(0, 1, 2, 3, 4, 5), values[int]())
result4 := Union(values(0, 1, 2), values(0, 1, 2, 3, 3))
result5 := Union(values(0, 1, 2), values(0, 1, 2))
result6 := Union(values[int](), values[int]())
is.Equal([]int{0, 1, 2, 3, 4, 5, 10}, slices.Collect(result1))
is.Equal([]int{0, 1, 2, 3, 4, 5, 6, 7}, slices.Collect(result2))
is.Equal([]int{0, 1, 2, 3, 4, 5}, slices.Collect(result3))
is.Equal([]int{0, 1, 2, 3}, slices.Collect(result4))
is.Equal([]int{0, 1, 2}, slices.Collect(result5))
is.Empty(slices.Collect(result6))
result11 := Union(values(0, 1, 2, 3, 4, 5), values(0, 2, 10), values(0, 1, 11))
result12 := Union(values(0, 1, 2, 3, 4, 5), values(6, 7), values(8, 9))
result13 := Union(values(0, 1, 2, 3, 4, 5), values[int](), values[int]())
result14 := Union(values(0, 1, 2), values(0, 1, 2), values(0, 1, 2))
result15 := Union(values[int](), values[int](), values[int]())
is.Equal([]int{0, 1, 2, 3, 4, 5, 10, 11}, slices.Collect(result11))
is.Equal([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, slices.Collect(result12))
is.Equal([]int{0, 1, 2, 3, 4, 5}, slices.Collect(result13))
is.Equal([]int{0, 1, 2}, slices.Collect(result14))
is.Empty(slices.Collect(result15))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Union(allStrings, allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestWithout(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Without(values(0, 2, 10), 0, 1, 2, 3, 4, 5)
result2 := Without(values(0, 7), 0, 1, 2, 3, 4, 5)
result3 := Without(values[int](), 0, 1, 2, 3, 4, 5)
result4 := Without(values(0, 1, 2), 0, 1, 2)
result5 := Without(values[int]())
is.Equal([]int{10}, slices.Collect(result1))
is.Equal([]int{7}, slices.Collect(result2))
is.Empty(slices.Collect(result3))
is.Empty(slices.Collect(result4))
is.Empty(slices.Collect(result5))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Without(allStrings, "")
is.IsType(nonempty, allStrings, "type preserved")
}
func TestWithoutBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
Name string
Age int
}
result1 := WithoutBy(values(User{Name: "nick"}, User{Name: "peter"}),
func(item User) string {
return item.Name
}, "nick", "lily")
result2 := WithoutBy(values[User](), func(item User) int { return item.Age }, 1, 2, 3)
result3 := WithoutBy(values[User](), func(item User) string { return item.Name })
is.Equal([]User{{Name: "peter"}}, slices.Collect(result1))
is.Empty(slices.Collect(result2))
is.Empty(slices.Collect(result3))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := WithoutBy(allStrings, func(string) string { return "" })
is.IsType(nonempty, allStrings, "type preserved")
}
func TestWithoutNth(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := WithoutNth(values(5, 6, 7), 1, 0)
is.Equal([]int{7}, slices.Collect(result1))
result2 := WithoutNth(values(1, 2))
is.Equal([]int{1, 2}, slices.Collect(result2))
result3 := WithoutNth(values[int]())
is.Empty(slices.Collect(result3))
result4 := WithoutNth(values(0, 1, 2, 3), -1, 4)
is.Equal([]int{0, 1, 2, 3}, slices.Collect(result4))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := WithoutNth(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestElementsMatch(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.False(ElementsMatch(values[int](), values(1)))
is.False(ElementsMatch(values(1), values(2)))
is.False(ElementsMatch(values(1), values(1, 2)))
is.False(ElementsMatch(values(1, 1, 2), values(2, 2, 1)))
is.True(ElementsMatch(values(1), values(1)))
is.True(ElementsMatch(values(1, 1), values(1, 1)))
is.True(ElementsMatch(values(1, 2), values(2, 1)))
is.True(ElementsMatch(values(1, 1, 2), values(1, 2, 1)))
}
func TestElementsMatchBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
type someType struct {
key string
}
is.True(ElementsMatchBy(
values(someType{key: "a"}, someType{key: "b"}),
values(someType{key: "b"}, someType{key: "a"}),
func(item someType) string { return item.key },
))
}
================================================
FILE: it/lo_test.go
================================================
//go:build go1.23
package it
import (
"iter"
"slices"
"testing"
"github.com/stretchr/testify/assert"
)
// assertSeqSupportBreak checks whether it is possible to break iteration over a [iter.Seq].
func assertSeqSupportBreak[T any](t *testing.T, seq iter.Seq[T]) iter.Seq[T] {
t.Helper()
assert.NotPanics(t, func() {
for range seq {
break
}
for range seq {
return
}
})
return seq
}
func values[T any](v ...T) iter.Seq[T] { return slices.Values(v) }
type foo struct {
bar string
}
func (f foo) Clone() foo {
return foo{f.bar}
}
================================================
FILE: it/map.go
================================================
//go:build go1.23
package it
import (
"iter"
"maps"
)
// Keys creates a sequence of the map keys.
// Play: https://go.dev/play/p/Fu7h-eW18QM
func Keys[K comparable, V any](in ...map[K]V) iter.Seq[K] {
return func(yield func(K) bool) {
for i := range in {
for k := range in[i] {
if !yield(k) {
return
}
}
}
}
}
// UniqKeys creates a sequence of unique keys in the map.
// Will allocate a map large enough to hold all distinct input keys.
// Long input sequences with heterogeneous keys can cause excessive memory usage.
// Play: https://go.dev/play/p/_NicwfgAHbO
func UniqKeys[K comparable, V any](in ...map[K]V) iter.Seq[K] {
return func(yield func(K) bool) {
var total int
for i := range in {
total += len(in[i])
}
seen := make(map[K]struct{}, total)
for i := range in {
for k := range in[i] {
if _, ok := seen[k]; !ok {
if !yield(k) {
return
}
seen[k] = struct{}{}
}
}
}
}
}
// Values creates a sequence of the map values.
// Play: https://go.dev/play/p/L9KcJ3h8E4f
func Values[K comparable, V any](in ...map[K]V) iter.Seq[V] {
return func(yield func(V) bool) {
for i := range in {
for _, v := range in[i] {
if !yield(v) {
return
}
}
}
}
}
// UniqValues creates a sequence of unique values in the map.
// Will allocate a map large enough to hold all distinct input values.
// Long input sequences with heterogeneous values can cause excessive memory usage.
// Play: https://go.dev/play/p/QQv4zGrk-fF
func UniqValues[K, V comparable](in ...map[K]V) iter.Seq[V] {
return func(yield func(V) bool) {
var total int
for i := range in {
total += len(in[i])
}
seen := make(map[V]struct{}, total)
for i := range in {
for _, v := range in[i] {
if _, ok := seen[v]; !ok {
if !yield(v) {
return
}
seen[v] = struct{}{}
}
}
}
}
}
// Entries transforms a map into a sequence of key/value pairs.
// Play: https://go.dev/play/p/ckLxqTE9KCz
func Entries[K comparable, V any](in ...map[K]V) iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
for _, m := range in {
for k, v := range m {
if !yield(k, v) {
return
}
}
}
}
}
// ToPairs transforms a map into a sequence of key/value pairs.
// Alias of Entries().
// Play: https://go.dev/play/p/N8RbJ5t6H2k
func ToPairs[K comparable, V any](in ...map[K]V) iter.Seq2[K, V] {
return Entries(in...)
}
// FromEntries transforms a sequence of key/value pairs into a map.
// Play: https://go.dev/play/p/MgEF1J5-tuK
func FromEntries[K comparable, V any](entries ...iter.Seq2[K, V]) map[K]V {
m := make(map[K]V)
for _, e := range entries {
maps.Insert(m, e)
}
return m
}
// FromPairs transforms a sequence of key/value pairs into a map.
// Alias of FromEntries().
// Play: https://go.dev/play/p/GqPl6isVR9W
func FromPairs[K comparable, V any](entries ...iter.Seq2[K, V]) map[K]V {
return FromEntries(entries...)
}
// Invert creates a sequence composed of inverted keys and values.
// Play: https://go.dev/play/p/Iph19Lgcsx-
func Invert[K, V comparable](in iter.Seq2[K, V]) iter.Seq2[V, K] {
return func(yield func(V, K) bool) {
for k, v := range in {
if !yield(v, k) {
return
}
}
}
}
// Assign merges multiple sequences of maps from left to right.
// Play: https://go.dev/play/p/lwngaIFkFAg
func Assign[K comparable, V any, Map ~map[K]V](maps ...iter.Seq[Map]) Map {
out := make(Map)
for i := range maps {
for item := range maps[i] {
for k, v := range item {
out[k] = v //nolint:modernize
}
}
}
return out
}
// ChunkEntries splits a map into a sequence of elements in groups of length equal to its size. If the map cannot be split evenly,
// the final chunk will contain the remaining elements.
// Play: https://go.dev/play/p/xtX-so65kDp
func ChunkEntries[K comparable, V any](m map[K]V, size int) iter.Seq[map[K]V] {
if size <= 0 {
panic("it.ChunkEntries: size must be greater than 0")
}
return func(yield func(map[K]V) bool) {
var result map[K]V
for k, v := range m {
if result == nil {
result = make(map[K]V, size)
}
result[k] = v
if len(result) == size {
if !yield(result) {
return
}
result = nil
}
}
if result != nil {
yield(result)
}
}
}
// MapToSeq transforms a map into a sequence based on specified transform.
// Play: https://go.dev/play/p/SEV4Vz5XFac
func MapToSeq[K comparable, V, R any](in map[K]V, transform func(key K, value V) R) iter.Seq[R] {
return func(yield func(R) bool) {
for k, v := range in {
if !yield(transform(k, v)) {
return
}
}
}
}
// FilterMapToSeq transforms a map into a sequence based on specified transform.
// The transform returns a value and a boolean. If the boolean is true, the value is added to the result sequence.
// If the boolean is false, the value is not added to the result sequence.
// The order of the keys in the input map is not specified and the order of the keys in the output sequence is not guaranteed.
// Play: https://go.dev/play/p/HQgi01x8A4o
func FilterMapToSeq[K comparable, V, R any](in map[K]V, transform func(key K, value V) (R, bool)) iter.Seq[R] {
return func(yield func(R) bool) {
for k, v := range in {
if v, ok := transform(k, v); ok && !yield(v) {
return
}
}
}
}
// FilterKeys transforms a map into a sequence based on predicate returns true for specific elements.
// It is a mix of Filter and Keys.
// Play: https://go.dev/play/p/ODxc6_IiWtO
func FilterKeys[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) iter.Seq[K] {
return func(yield func(K) bool) {
for k, v := range in {
if predicate(k, v) && !yield(k) {
return
}
}
}
}
// FilterValues transforms a map into a sequence based on predicate returns true for specific elements.
// It is a mix of Filter and Values.
// Play: https://go.dev/play/p/9SBiyOqgrl8
func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) iter.Seq[V] {
return func(yield func(V) bool) {
for k, v := range in {
if predicate(k, v) && !yield(v) {
return
}
}
}
}
// SeqToSeq2 converts a sequence into a sequence of key-value pairs keyed by index.
// Play: https://go.dev/play/p/V5wL9xY8nQr
func SeqToSeq2[T any](in iter.Seq[T]) iter.Seq2[int, T] {
return func(yield func(int, T) bool) {
var i int
for item := range in {
if !yield(i, item) {
return
}
i++
}
}
}
// Seq2KeyToSeq converts a sequence of key-value pairs into a sequence of keys.
// Play: https://go.dev/play/p/W6xM7zZ9oSt
func Seq2KeyToSeq[K, V any](in iter.Seq2[K, V]) iter.Seq[K] {
return func(yield func(K) bool) {
for k := range in {
if !yield(k) {
return
}
}
}
}
// Seq2ValueToSeq converts a sequence of key-value pairs into a sequence of values.
// Play: https://go.dev/play/p/X7yN8aA1pUv
func Seq2ValueToSeq[K, V any](in iter.Seq2[K, V]) iter.Seq[V] {
return func(yield func(V) bool) {
for _, v := range in {
if !yield(v) {
return
}
}
}
}
================================================
FILE: it/map_example_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"maps"
"slices"
"sort"
)
func ExampleKeys() {
kv := map[string]int{"foo": 1, "bar": 2}
kv2 := map[string]int{"baz": 3}
result := slices.Collect(Keys(kv, kv2))
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [bar baz foo]
}
func ExampleUniqKeys() {
kv := map[string]int{"foo": 1, "bar": 2}
kv2 := map[string]int{"bar": 3}
result := slices.Collect(UniqKeys(kv, kv2))
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [bar foo]
}
func ExampleValues() {
kv := map[string]int{"foo": 1, "bar": 2}
kv2 := map[string]int{"baz": 3}
result := slices.Collect(Values(kv, kv2))
sort.Ints(result)
fmt.Printf("%v", result)
// Output: [1 2 3]
}
func ExampleUniqValues() {
kv := map[string]int{"foo": 1, "bar": 2}
kv2 := map[string]int{"baz": 2}
result := slices.Collect(UniqValues(kv, kv2))
sort.Ints(result)
fmt.Printf("%v", result)
// Output: [1 2]
}
func ExampleEntries() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := maps.Collect(Entries(kv))
fmt.Printf("%v %v %v %v", len(result), result["foo"], result["bar"], result["baz"])
// Output: 3 1 2 3
}
func ExampleFromEntries() {
result := FromEntries(maps.All(map[string]int{
"foo": 1,
"bar": 2,
"baz": 3,
}))
fmt.Printf("%v %v %v %v", len(result), result["foo"], result["bar"], result["baz"])
// Output: 3 1 2 3
}
func ExampleInvert() {
kv := maps.All(map[string]int{"foo": 1, "bar": 2, "baz": 3})
result := maps.Collect(Invert(kv))
fmt.Printf("%v %v %v %v", len(result), result[1], result[2], result[3])
// Output: 3 foo bar baz
}
func ExampleAssign() {
result := Assign(values(
map[string]int{"a": 1, "b": 2},
map[string]int{"b": 3, "c": 4},
))
fmt.Printf("%v %v %v %v", len(result), result["a"], result["b"], result["c"])
// Output: 3 1 3 4
}
func ExampleChunkEntries() {
result := ChunkEntries(
map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
},
3,
)
for r := range result {
fmt.Printf("%d\n", len(r))
}
// Output:
// 3
// 2
}
func ExampleMapToSeq() {
kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
result := slices.Collect(MapToSeq(kv, func(k int, v int64) string {
return fmt.Sprintf("%d_%d", k, v)
}))
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [1_1 2_2 3_3 4_4]
}
func ExampleFilterMapToSeq() {
kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
result := slices.Collect(FilterMapToSeq(kv, func(k int, v int64) (string, bool) {
return fmt.Sprintf("%d_%d", k, v), k%2 == 0
}))
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [2_2 4_4]
}
func ExampleFilterKeys() {
kv := map[int]string{1: "foo", 2: "bar", 3: "baz"}
result := slices.Collect(FilterKeys(kv, func(k int, v string) bool {
return v == "foo"
}))
fmt.Printf("%v", result)
// Output: [1]
}
func ExampleFilterValues() {
kv := map[int]string{1: "foo", 2: "bar", 3: "baz"}
result := slices.Collect(FilterValues(kv, func(k int, v string) bool {
return v == "foo"
}))
fmt.Printf("%v", result)
// Output: [foo]
}
func ExampleSeqToSeq2() {
result := maps.Collect(SeqToSeq2(slices.Values([]string{"foo", "bar", "baz"})))
fmt.Printf("%v %v %v %v", len(result), result[0], result[1], result[2])
// Output: 3 foo bar baz
}
func ExampleSeq2KeyToSeq() {
result := slices.Collect(Seq2KeyToSeq(maps.All(map[string]int{
"foo": 1,
"bar": 2,
"baz": 3,
})))
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [bar baz foo]
}
func ExampleSeq2ValueToSeq() {
result := slices.Collect(Seq2ValueToSeq(maps.All(map[string]int{
"foo": 1,
"bar": 2,
"baz": 3,
})))
sort.Ints(result)
fmt.Printf("%v", result)
// Output: [1 2 3]
}
================================================
FILE: it/map_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"maps"
"slices"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := slices.Collect(Keys(map[string]int{"foo": 1, "bar": 2}))
is.ElementsMatch(r1, []string{"bar", "foo"})
r2 := slices.Collect(Keys(map[string]int{}))
is.Empty(r2)
r3 := slices.Collect(Keys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3}))
is.ElementsMatch(r3, []string{"bar", "baz", "foo"})
r4 := slices.Collect(Keys[string, int]())
is.Empty(r4)
r5 := slices.Collect(Keys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 3}))
is.ElementsMatch(r5, []string{"bar", "bar", "foo"})
}
func TestUniqKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := slices.Collect(UniqKeys(map[string]int{"foo": 1, "bar": 2}))
is.ElementsMatch(r1, []string{"bar", "foo"})
r2 := slices.Collect(UniqKeys(map[string]int{}))
is.Empty(r2)
r3 := slices.Collect(UniqKeys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3}))
is.ElementsMatch(r3, []string{"bar", "baz", "foo"})
r4 := slices.Collect(UniqKeys[string, int]())
is.Empty(r4)
r5 := slices.Collect(UniqKeys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"foo": 1, "bar": 3}))
is.ElementsMatch(r5, []string{"bar", "foo"})
// check order
r6 := slices.Collect(UniqKeys(map[string]int{"foo": 1}, map[string]int{"bar": 3}))
is.Equal([]string{"foo", "bar"}, r6)
}
func TestValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := slices.Collect(Values(map[string]int{"foo": 1, "bar": 2}))
is.ElementsMatch(r1, []int{1, 2})
r2 := slices.Collect(Values(map[string]int{}))
is.Empty(r2)
r3 := slices.Collect(Values(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3}))
is.ElementsMatch(r3, []int{1, 2, 3})
r4 := slices.Collect(Values[string, int]())
is.Empty(r4)
r5 := slices.Collect(Values(map[string]int{"foo": 1, "bar": 2}, map[string]int{"foo": 1, "bar": 3}))
is.ElementsMatch(r5, []int{1, 1, 2, 3})
}
func TestUniqValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := slices.Collect(UniqValues(map[string]int{"foo": 1, "bar": 2}))
is.ElementsMatch(r1, []int{1, 2})
r2 := slices.Collect(UniqValues(map[string]int{}))
is.Empty(r2)
r3 := slices.Collect(UniqValues(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3}))
is.ElementsMatch(r3, []int{1, 2, 3})
r4 := slices.Collect(UniqValues[string, int]())
is.Empty(r4)
r5 := slices.Collect(UniqValues(map[string]int{"foo": 1, "bar": 2}, map[string]int{"foo": 1, "bar": 3}))
is.ElementsMatch(r5, []int{1, 2, 3})
r6 := slices.Collect(UniqValues(map[string]int{"foo": 1, "bar": 1}, map[string]int{"foo": 1, "bar": 3}))
is.ElementsMatch(r6, []int{1, 3})
// check order
r7 := slices.Collect(UniqValues(map[string]int{"foo": 1}, map[string]int{"bar": 3}))
is.Equal([]int{1, 3}, r7)
}
func TestEntries(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := maps.Collect(Entries(map[string]int{"foo": 1, "bar": 2}))
is.Equal(map[string]int{"foo": 1, "bar": 2}, r1)
}
func TestToPairs(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := maps.Collect(ToPairs(map[string]int{"foo": 1, "bar": 2}))
is.Equal(map[string]int{"foo": 1, "bar": 2}, r1)
}
func TestFromEntries(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FromEntries(maps.All(map[string]int{"foo": 1, "bar": 2}))
is.Len(r1, 2)
is.Equal(1, r1["foo"])
is.Equal(2, r1["bar"])
}
func TestFromPairs(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FromPairs(maps.All(map[string]int{"baz": 3, "qux": 4}))
is.Len(r1, 2)
is.Equal(3, r1["baz"])
is.Equal(4, r1["qux"])
}
func TestInvert(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Invert(maps.All(map[string]int{"a": 1, "b": 2}))
r2 := Invert(maps.All(map[string]int{"a": 1, "b": 2, "c": 1}))
is.Equal(map[int]string{1: "a", 2: "b"}, maps.Collect(r1))
is.Len(maps.Collect(r2), 2)
}
func TestAssign(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Assign(values(map[string]int{"a": 1, "b": 2}, map[string]int{"b": 3, "c": 4}))
is.Equal(map[string]int{"a": 1, "b": 3, "c": 4}, result1)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after := Assign(values(before, before))
is.IsType(myMap{}, after, "type preserved")
}
func TestChunkEntries(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ChunkEntries(map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}, 2)
result2 := ChunkEntries(map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}, 3)
result3 := ChunkEntries(map[string]int{}, 2)
result4 := ChunkEntries(map[string]int{"a": 1}, 2)
result5 := ChunkEntries(map[string]int{"a": 1, "b": 2}, 1)
is.Len(slices.Collect(result1), 3)
is.Len(slices.Collect(result2), 2)
is.Empty(slices.Collect(result3))
is.Len(slices.Collect(result4), 1)
is.Len(slices.Collect(result5), 2)
is.PanicsWithValue("it.ChunkEntries: size must be greater than 0", func() {
ChunkEntries(map[string]int{"a": 1}, 0)
})
is.PanicsWithValue("it.ChunkEntries: size must be greater than 0", func() {
ChunkEntries(map[string]int{"a": 1}, -1)
})
type myStruct struct {
Name string
Value int
}
allStructs := []myStruct{{"one", 1}, {"two", 2}, {"three", 3}}
nonempty := ChunkEntries(map[string]myStruct{"a": allStructs[0], "b": allStructs[1], "c": allStructs[2]}, 2)
is.Len(slices.Collect(nonempty), 2)
originalMap := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
result6 := slices.Collect(ChunkEntries(originalMap, 2))
for k := range result6[0] {
result6[0][k] = 10
}
is.Equal(map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}, originalMap)
}
func TestMapToSeq(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MapToSeq(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k, v int) string {
return fmt.Sprintf("%d_%d", k, v)
})
result2 := MapToSeq(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k, _ int) string {
return strconv.FormatInt(int64(k), 10)
})
is.ElementsMatch(slices.Collect(result1), []string{"1_5", "2_6", "3_7", "4_8"})
is.ElementsMatch(slices.Collect(result2), []string{"1", "2", "3", "4"})
}
func TestFilterMapToSeq(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FilterMapToSeq(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k, v int) (string, bool) {
return fmt.Sprintf("%d_%d", k, v), k%2 == 0
})
result2 := FilterMapToSeq(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k, _ int) (string, bool) {
return strconv.FormatInt(int64(k), 10), k%2 == 0
})
is.ElementsMatch(slices.Collect(result1), []string{"2_6", "4_8"})
is.ElementsMatch(slices.Collect(result2), []string{"2", "4"})
}
func TestFilterKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FilterKeys(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) bool {
return v == "foo"
})
is.Equal([]int{1}, slices.Collect(result1))
result2 := FilterKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool {
return false
})
is.Empty(slices.Collect(result2))
}
func TestFilterValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FilterValues(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) bool {
return v == "foo"
})
is.Equal([]string{"foo"}, slices.Collect(result1))
result2 := FilterValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool {
return false
})
is.Empty(slices.Collect(result2))
}
func TestSeqToSeq2(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := maps.Collect(SeqToSeq2(values("foo", "bar")))
is.Equal(map[int]string{0: "foo", 1: "bar"}, r1)
r2 := maps.Collect(SeqToSeq2(values[string]()))
is.Empty(r2)
}
func TestSeq2KeyToSeq(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := slices.Collect(Seq2KeyToSeq(maps.All(map[string]int{"foo": 4, "bar": 5})))
is.ElementsMatch([]string{"foo", "bar"}, r1)
r2 := slices.Collect(Seq2KeyToSeq(maps.All(map[string]int{})))
is.Empty(r2)
}
func TestSeq2ValueToSeq(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := slices.Collect(Seq2ValueToSeq(maps.All(map[string]int{"foo": 4, "bar": 5})))
is.ElementsMatch([]int{4, 5}, r1)
r2 := slices.Collect(Seq2ValueToSeq(maps.All(map[string]int{})))
is.Empty(r2)
}
func BenchmarkAssign(b *testing.B) {
counts := []int{32768, 1024, 128, 32, 2}
allDifferentMap := func(b *testing.B, n int) []map[string]int {
b.Helper()
defer b.ResetTimer()
m := make([]map[string]int, 0, n)
for i := range n {
m = append(m, map[string]int{
strconv.Itoa(i): i,
strconv.Itoa(i): i,
strconv.Itoa(i): i,
strconv.Itoa(i): i,
strconv.Itoa(i): i,
strconv.Itoa(i): i,
},
)
}
return m
}
allTheSameMap := func(b *testing.B, n int) []map[string]int {
b.Helper()
defer b.ResetTimer()
m := make([]map[string]int, 0, n)
for range n {
m = append(m, map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
},
)
}
return m
}
for _, count := range counts {
differentMap := allDifferentMap(b, count)
sameMap := allTheSameMap(b, count)
b.Run(strconv.Itoa(count), func(b *testing.B) {
testCases := []struct {
name string
in []map[string]int
}{
{"different", differentMap},
{"same", sameMap},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
b.ResetTimer()
for range b.N {
result := Assign(values(tc.in...))
_ = result
}
})
}
})
}
}
================================================
FILE: it/math.go
================================================
//go:build go1.23
package it
import (
"iter"
"github.com/samber/lo"
"github.com/samber/lo/internal/constraints"
)
// Range creates a sequence of numbers (positive and/or negative) with given length.
// Play: https://go.dev/play/p/79QUZBa8Ukn
func Range(elementNum int) iter.Seq[int] {
step := lo.Ternary(elementNum < 0, -1, 1)
length := elementNum * step
return func(yield func(int) bool) {
for i, j := 0, 0; i < length; i, j = i+1, j+step {
if !yield(j) {
return
}
}
}
}
// RangeFrom creates a sequence of numbers from start with specified length.
// Play: https://go.dev/play/p/WHP_NI5scj9
func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) iter.Seq[T] {
step := lo.Ternary(elementNum < 0, -1, 1)
length := elementNum * step
return func(yield func(T) bool) {
for i, j := 0, start; i < length; i, j = i+1, j+T(step) {
if !yield(j) {
return
}
}
}
}
// RangeWithSteps creates a sequence of numbers (positive and/or negative) progressing from start up to, but not including end.
// step set to zero will return an empty sequence.
// Play: https://go.dev/play/p/qxm2YNLG0vT
func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) iter.Seq[T] {
return func(yield func(T) bool) {
if start == end || step == 0 {
return
}
if start < end {
if step < 0 {
return
}
for i := start; i < end; i += step {
if !yield(i) {
return
}
}
}
if step > 0 {
return
}
for i := start; i > end; i += step {
if !yield(i) {
return
}
}
}
}
// Sum sums the values in a collection. If collection is empty 0 is returned.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/nHbGFOEIeTa
func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T]) T {
return SumBy(collection, func(item T) T { return item })
}
// SumBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 0 is returned.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/ZNiqXNMu5QP
func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T], transform func(item T) R) R {
var sum R
for item := range collection {
sum += transform(item)
}
return sum
}
// Product gets the product of the values in a collection. If collection is empty 1 is returned.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/AOMCD1Yl5Bc
func Product[T constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T]) T {
return ProductBy(collection, func(item T) T { return item })
}
// ProductBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 1 is returned.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/dgFCRJrlPHY
func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection iter.Seq[T], transform func(item T) R) R {
var product R = 1
for item := range collection {
product *= transform(item)
}
return product
}
// Mean calculates the mean of a collection of numbers.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/Lez0CsvVRl_l
func Mean[T constraints.Float | constraints.Integer](collection iter.Seq[T]) T {
return MeanBy(collection, func(item T) T { return item })
}
// MeanBy calculates the mean of a collection of numbers using the given return value from the iteration function.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/Ked4rpztH5Y
func MeanBy[T any, R constraints.Float | constraints.Integer](collection iter.Seq[T], transform func(item T) R) R {
var sum R
var length R
for item := range collection {
sum += transform(item)
length++
}
if length == 0 {
return 0
}
return sum / length
}
// Mode returns the mode (most frequent value) of a collection.
// If multiple values have the same highest frequency, then multiple values are returned.
// If the collection is empty, then the zero value of T is returned.
// Will iterate through the entire sequence and allocate a map large enough to hold all distinct elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/c_cmMMA5EhH
func Mode[T constraints.Integer | constraints.Float](collection iter.Seq[T]) []T {
mode := make([]T, 0)
maxFreq := 0
frequency := make(map[T]int)
for item := range collection {
frequency[item]++
count := frequency[item]
if count > maxFreq {
maxFreq = count
mode = append(mode[:0], item)
} else if count == maxFreq {
mode = append(mode, item)
}
}
return mode[:len(mode):len(mode)]
}
================================================
FILE: it/math_example_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"slices"
)
func ExampleRange() {
result1 := Range(4)
result2 := Range(-4)
result3 := RangeFrom(1, 5)
result4 := RangeFrom(1.0, 5)
result5 := RangeWithSteps(0, 20, 5)
result6 := RangeWithSteps[float32](-1.0, -4.0, -1.0)
result7 := RangeWithSteps(1, 4, -1)
result8 := Range(0)
fmt.Printf("%v\n", slices.Collect(result1))
fmt.Printf("%v\n", slices.Collect(result2))
fmt.Printf("%v\n", slices.Collect(result3))
fmt.Printf("%v\n", slices.Collect(result4))
fmt.Printf("%v\n", slices.Collect(result5))
fmt.Printf("%v\n", slices.Collect(result6))
fmt.Printf("%v\n", slices.Collect(result7))
fmt.Printf("%v\n", slices.Collect(result8))
// Output:
// [0 1 2 3]
// [0 -1 -2 -3]
// [1 2 3 4 5]
// [1 2 3 4 5]
// [0 5 10 15]
// [-1 -2 -3]
// []
// []
}
func ExampleSum() {
ints := slices.Values([]int{1, 2, 3, 4, 5})
sum := Sum(ints)
fmt.Printf("%v", sum)
// Output: 15
}
func ExampleSumBy() {
ints := slices.Values([]string{"foo", "bar"})
result := SumBy(ints, func(item string) int {
return len(item)
})
fmt.Printf("%v", result)
// Output: 6
}
func ExampleProduct() {
ints := slices.Values([]int{1, 2, 3, 4, 5})
result := Product(ints)
fmt.Printf("%v", result)
// Output: 120
}
func ExampleProductBy() {
strs := slices.Values([]string{"foo", "bar"})
result := ProductBy(strs, func(item string) int {
return len(item)
})
fmt.Printf("%v", result)
// Output: 9
}
func ExampleMean() {
ints := slices.Values([]int{1, 2, 3, 4, 5})
result := Mean(ints)
fmt.Printf("%v", result)
// Output: 3
}
func ExampleMeanBy() {
strs := slices.Values([]string{"foo", "bar"})
result := MeanBy(strs, func(item string) int {
return len(item)
})
fmt.Printf("%v", result)
// Output: 3
}
================================================
FILE: it/math_test.go
================================================
//go:build go1.23
package it
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRange(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Range(4)
result2 := Range(-4)
result3 := Range(0)
is.Equal([]int{0, 1, 2, 3}, slices.Collect(result1))
is.Equal([]int{0, -1, -2, -3}, slices.Collect(result2))
is.Empty(slices.Collect(result3))
}
func TestRangeFrom(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := RangeFrom(1, 5)
result2 := RangeFrom(-1, -5)
result3 := RangeFrom(10, 0)
result4 := RangeFrom(2.0, 3)
result5 := RangeFrom(-2.0, -3)
result6 := RangeFrom(2.5, 3)
result7 := RangeFrom(-2.5, -3)
is.Equal([]int{1, 2, 3, 4, 5}, slices.Collect(result1))
is.Equal([]int{-1, -2, -3, -4, -5}, slices.Collect(result2))
is.Empty(slices.Collect(result3))
is.Equal([]float64{2.0, 3.0, 4.0}, slices.Collect(result4))
is.Equal([]float64{-2.0, -3.0, -4.0}, slices.Collect(result5))
is.Equal([]float64{2.5, 3.5, 4.5}, slices.Collect(result6))
is.Equal([]float64{-2.5, -3.5, -4.5}, slices.Collect(result7))
}
func TestRangeWithSteps(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := RangeWithSteps(0, 20, 6)
result2 := RangeWithSteps(0, 3, -5)
result3 := RangeWithSteps(1, 1, 0)
result4 := RangeWithSteps(3, 2, 1)
result5 := RangeWithSteps(1.0, 4.0, 2.0)
result6 := RangeWithSteps[float32](-1.0, -4.0, -1.0)
result7 := RangeWithSteps(0.0, 0.5, 1.0)
result8 := RangeWithSteps(0.0, 0.3, 0.1)
result9 := RangeWithSteps(0.0, 5.5, 2.5)
type f64 float64
result10 := RangeWithSteps[f64](0.0, 0.3, 0.1)
is.Equal([]int{0, 6, 12, 18}, slices.Collect(result1))
is.Empty(slices.Collect(result2))
is.Empty(slices.Collect(result3))
is.Empty(slices.Collect(result4))
is.Equal([]float64{1.0, 3.0}, slices.Collect(result5))
is.Equal([]float32{-1.0, -2.0, -3.0}, slices.Collect(result6))
is.Equal([]float64{0.0}, slices.Collect(result7))
is.Equal([]float64{0.0, 0.1, 0.2}, slices.Collect(result8))
is.Equal([]float64{0.0, 2.5, 5.0}, slices.Collect(result9))
is.Equal([]f64{0.0, 0.1, 0.2}, slices.Collect(result10))
}
func TestSum(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Sum(values[float32](2.3, 3.3, 4, 5.3))
result2 := Sum(values[int32](2, 3, 4, 5))
result3 := Sum(values[uint32](2, 3, 4, 5))
result4 := Sum(values[uint32]())
result5 := Sum(values[complex128](4_4, 2_2))
is.InEpsilon(14.9, result1, 1e-7)
is.Equal(int32(14), result2)
is.Equal(uint32(14), result3)
is.Equal(uint32(0), result4)
is.Equal(complex128(6_6), result5)
}
func TestSumBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := SumBy(values[float32](2.3, 3.3, 4, 5.3), func(n float32) float32 { return n })
result2 := SumBy(values[int32](2, 3, 4, 5), func(n int32) int32 { return n })
result3 := SumBy(values[uint32](2, 3, 4, 5), func(n uint32) uint32 { return n })
result4 := SumBy(values[uint32](), func(n uint32) uint32 { return n })
result5 := SumBy(values[complex128](4_4, 2_2), func(n complex128) complex128 { return n })
is.InEpsilon(14.9, result1, 1e-7)
is.Equal(int32(14), result2)
is.Equal(uint32(14), result3)
is.Equal(uint32(0), result4)
is.Equal(complex128(6_6), result5)
}
func TestProduct(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Product(values[float32](2.3, 3.3, 4, 5.3))
result2 := Product(values[int32](2, 3, 4, 5))
result3 := Product(values[int32](7, 8, 9, 0))
result4 := Product(values[int32](7, -1, 9, 2))
result5 := Product(values[uint32](2, 3, 4, 5))
result6 := Product(values[uint32]())
result7 := Product(values[complex128](4_4, 2_2))
is.InEpsilon(160.908, result1, 1e-7)
is.Equal(int32(120), result2)
is.Equal(int32(0), result3)
is.Equal(int32(-126), result4)
is.Equal(uint32(120), result5)
is.Equal(uint32(1), result6)
is.Equal(complex128(96_8), result7)
}
func TestProductBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ProductBy(values[float32](2.3, 3.3, 4, 5.3), func(n float32) float32 { return n })
result2 := ProductBy(values[int32](2, 3, 4, 5), func(n int32) int32 { return n })
result3 := ProductBy(values[int32](7, 8, 9, 0), func(n int32) int32 { return n })
result4 := ProductBy(values[int32](7, -1, 9, 2), func(n int32) int32 { return n })
result5 := ProductBy(values[uint32](2, 3, 4, 5), func(n uint32) uint32 { return n })
result6 := ProductBy(values[uint32](), func(n uint32) uint32 { return n })
result7 := ProductBy(values[complex128](4_4, 2_2), func(n complex128) complex128 { return n })
is.InEpsilon(160.908, result1, 1e-7)
is.Equal(int32(120), result2)
is.Equal(int32(0), result3)
is.Equal(int32(-126), result4)
is.Equal(uint32(120), result5)
is.Equal(uint32(1), result6)
is.Equal(complex128(96_8), result7)
}
func TestMean(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Mean(values[float32](2.3, 3.3, 4, 5.3))
result2 := Mean(values[int32](2, 3, 4, 5))
result3 := Mean(values[uint32](2, 3, 4, 5))
result4 := Mean(values[uint32]())
is.InEpsilon(3.725, result1, 1e-7)
is.Equal(int32(3), result2)
is.Equal(uint32(3), result3)
is.Equal(uint32(0), result4)
}
func TestMeanBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MeanBy(values[float32](2.3, 3.3, 4, 5.3), func(n float32) float32 { return n })
result2 := MeanBy(values[int32](2, 3, 4, 5), func(n int32) int32 { return n })
result3 := MeanBy(values[uint32](2, 3, 4, 5), func(n uint32) uint32 { return n })
result4 := MeanBy(values[uint32](), func(n uint32) uint32 { return n })
is.InEpsilon(3.725, result1, 1e-7)
is.Equal(int32(3), result2)
is.Equal(uint32(3), result3)
is.Equal(uint32(0), result4)
}
func TestMode(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Mode(values[float32](2.3, 3.3, 3.3, 5.3))
result2 := Mode(values[int32](2, 2, 3, 4))
result3 := Mode(values[uint32](2, 2, 3, 3))
result4 := Mode(values[uint32]())
result5 := Mode(values(1, 2, 3, 4, 5, 6, 7, 8, 9))
is.Equal([]float32{3.3}, result1)
is.Equal([]int32{2}, result2)
is.Equal([]uint32{2, 3}, result3)
is.Empty(result4)
is.Equal([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}, result5)
}
func TestModeCapacityConsistency(t *testing.T) {
t.Parallel()
is := assert.New(t)
arr := []int{1, 1, 2, 2, 3, 3, 3}
result := Mode(values(arr...))
is.Equal([]int{3}, result, "Mode should return correct mode value")
is.Equal(len(result), cap(result), "Mode slice capacity should match its length")
}
================================================
FILE: it/seq.go
================================================
//go:build go1.23
package it
import (
"iter"
"slices"
"github.com/samber/lo"
"github.com/samber/lo/internal/constraints"
"github.com/samber/lo/mutable"
)
// Length returns the length of collection.
// Will iterate through the entire sequence.
func Length[T any](collection iter.Seq[T]) int {
var count int
for range collection {
count++
}
return count
}
// Drain consumes an entire sequence.
func Drain[T any](collection iter.Seq[T]) {
for range collection { //nolint:revive
}
}
// Filter iterates over elements of collection, returning a sequence of all elements predicate returns true for.
// Play: https://go.dev/play/p/psenko2KKsX
func Filter[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I {
return FilterI(collection, func(item T, _ int) bool { return predicate(item) })
}
// FilterI iterates over elements of collection, returning a sequence of all elements predicate returns true for.
// Play: https://go.dev/play/p/5fpdlQvdL-q
func FilterI[T any, I ~func(func(T) bool)](collection I, predicate func(item T, index int) bool) I {
return func(yield func(T) bool) {
var i int
for item := range collection {
if predicate(item, i) && !yield(item) {
return
}
i++
}
}
}
// Map manipulates a sequence and transforms it to a sequence of another type.
// Play: https://go.dev/play/p/F_FkOP-9F9T
func Map[T, R any](collection iter.Seq[T], transform func(item T) R) iter.Seq[R] {
return MapI(collection, func(item T, _ int) R { return transform(item) })
}
// MapI manipulates a sequence and transforms it to a sequence of another type.
// Play: https://go.dev/play/p/6gqemRweL-r
func MapI[T, R any](collection iter.Seq[T], transform func(item T, index int) R) iter.Seq[R] {
return func(yield func(R) bool) {
var i int
for item := range collection {
if !yield(transform(item, i)) {
return
}
i++
}
}
}
// UniqMap manipulates a sequence and transforms it to a sequence of another type with unique values.
// Will allocate a map large enough to hold all distinct transformed elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/7hrfnSxfL-s
func UniqMap[T any, R comparable](collection iter.Seq[T], transform func(item T) R) iter.Seq[R] {
return Uniq(Map(collection, transform))
}
// UniqMapI manipulates a sequence and transforms it to a sequence of another type with unique values.
// Will allocate a map large enough to hold all distinct transformed elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/8isgTsyfL-t
func UniqMapI[T any, R comparable](collection iter.Seq[T], transform func(item T, index int) R) iter.Seq[R] {
return Uniq(MapI(collection, transform))
}
// FilterMap returns a sequence obtained after both filtering and mapping using the given callback function.
// The callback function should return two values:
// - the result of the mapping operation and
// - whether the result element should be included or not.
//
// Play: https://go.dev/play/p/Gxwu8j_TJuh
func FilterMap[T, R any](collection iter.Seq[T], callback func(item T) (R, bool)) iter.Seq[R] {
return FilterMapI(collection, func(item T, _ int) (R, bool) { return callback(item) })
}
// FilterMapI returns a sequence obtained after both filtering and mapping using the given callback function.
// The callback function should return two values:
// - the result of the mapping operation and
// - whether the result element should be included or not.
//
// Play: https://go.dev/play/p/0XrQKOk-vw
func FilterMapI[T, R any](collection iter.Seq[T], callback func(item T, index int) (R, bool)) iter.Seq[R] {
return func(yield func(R) bool) {
var i int
for item := range collection {
if r, ok := callback(item, i); ok && !yield(r) {
return
}
i++
}
}
}
// FlatMap manipulates a sequence and transforms and flattens it to a sequence of another type.
// The transform function can either return a sequence or a `nil`, and in the `nil` case
// no value is yielded.
// Play: https://go.dev/play/p/6toB9w2gpSy
func FlatMap[T, R any](collection iter.Seq[T], transform func(item T) iter.Seq[R]) iter.Seq[R] {
return FlatMapI(collection, func(item T, _ int) iter.Seq[R] { return transform(item) })
}
// FlatMapI manipulates a sequence and transforms and flattens it to a sequence of another type.
// The transform function can either return a sequence or a `nil`, and in the `nil` case
// no value is yielded.
// Play: https://go.dev/play/p/2ZtSMQm-xy
func FlatMapI[T, R any](collection iter.Seq[T], transform func(item T, index int) iter.Seq[R]) iter.Seq[R] {
return func(yield func(R) bool) {
var i int
for item := range collection {
for r := range transform(item, i) {
if !yield(r) {
return
}
}
i++
}
}
}
// Reduce reduces collection to a value which is the accumulated result of running each element in collection
// through accumulator, where each successive invocation is supplied the return value of the previous.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/l5wWLATXclf
func Reduce[T, R any](collection iter.Seq[T], accumulator func(agg R, item T) R, initial R) R {
return ReduceI(collection, func(agg R, item T, _ int) R { return accumulator(agg, item) }, initial)
}
// ReduceI reduces collection to a value which is the accumulated result of running each element in collection
// through accumulator, where each successive invocation is supplied the return value of the previous.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/3AuTNRn-yz
func ReduceI[T, R any](collection iter.Seq[T], accumulator func(agg R, item T, index int) R, initial R) R {
var i int
for item := range collection {
initial = accumulator(initial, item, i)
i++
}
return initial
}
// ReduceLast is like Reduce except that it iterates over elements of collection in reverse.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/D2ZGZ2pN270
func ReduceLast[T, R any](collection iter.Seq[T], accumulator func(agg R, item T) R, initial R) R {
return Reduce(Reverse(collection), accumulator, initial)
}
// ReduceLastI is like Reduce except that it iterates over elements of collection in reverse.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/5CwPTPz-zb
func ReduceLastI[T, R any](collection iter.Seq[T], accumulator func(agg R, item T, index int) R, initial R) R {
return ReduceI(Reverse(collection), accumulator, initial)
}
// ForEach iterates over elements of collection and invokes callback for each element.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/OvW9yNNYgsX
func ForEach[T any](collection iter.Seq[T], callback func(item T)) {
ForEachI(collection, func(item T, _ int) { callback(item) })
}
// ForEachI iterates over elements of collection and invokes callback for each element.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/6NhAE0-zm
func ForEachI[T any](collection iter.Seq[T], callback func(item T, index int)) {
var i int
for item := range collection {
callback(item, i)
i++
}
}
// ForEachWhile iterates over elements of collection and invokes predicate for each element
// collection return value decide to continue or break, like do while().
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/UsfPR_gmSs7
func ForEachWhile[T any](collection iter.Seq[T], predicate func(item T) bool) {
ForEachWhileI(collection, func(item T, _ int) bool { return predicate(item) })
}
// ForEachWhileI iterates over elements of collection and invokes predicate for each element
// collection return value decide to continue or break, like do while().
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/8PjCG2-zo
func ForEachWhileI[T any](collection iter.Seq[T], predicate func(item T, index int) bool) {
var i int
for item := range collection {
if !predicate(item, i) {
return
}
i++
}
}
// Times invokes callback n times and returns a sequence of results.
// The transform is invoked with index as argument.
// Play: https://go.dev/play/p/0W4IRzQuCEc
func Times[T any](count int, callback func(index int) T) iter.Seq[T] {
return func(yield func(T) bool) {
for i := range count {
if !yield(callback(i)) {
return
}
}
}
}
// Uniq returns a duplicate-free version of a sequence, in which only the first occurrence of each element is kept.
// The order of result values is determined by the order they occur in the sequence.
// Will allocate a map large enough to hold all distinct elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/D-SenTW-ipj
func Uniq[T comparable, I ~func(func(T) bool)](collection I) I {
return UniqBy(collection, func(item T) T { return item })
}
// UniqBy returns a duplicate-free version of a sequence, in which only the first occurrence of each element is kept.
// The order of result values is determined by the order they occur in the sequence. A transform function is
// invoked for each element in the sequence to generate the criterion by which uniqueness is computed.
// Will allocate a map large enough to hold all distinct transformed elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/HKrt3AvwMTR
func UniqBy[T any, U comparable, I ~func(func(T) bool)](collection I, transform func(item T) U) I {
return func(yield func(T) bool) {
seen := make(map[U]struct{})
for item := range collection {
key := transform(item)
if _, ok := seen[key]; !ok {
if !yield(item) {
return
}
seen[key] = struct{}{}
}
}
}
}
// GroupBy returns an object composed of keys generated from the results of running each element of collection through transform.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/oRIakS89OYy
func GroupBy[T any, U comparable](collection iter.Seq[T], transform func(item T) U) map[U][]T {
return GroupByMap(collection, func(item T) (U, T) { return transform(item), item })
}
// GroupByMap returns an object composed of keys generated from the results of running each element of collection through transform.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/3UoHL7-zt
func GroupByMap[T any, K comparable, V any](collection iter.Seq[T], transform func(item T) (K, V)) map[K][]V {
result := make(map[K][]V)
for item := range collection {
k, v := transform(item)
result[k] = append(result[k], v)
}
return result
}
// Chunk returns a sequence of elements split into groups of length size. If the sequence can't be split evenly,
// the final chunk will be the remaining elements.
// Play: https://go.dev/play/p/qo8esZ_L60Q
func Chunk[T any](collection iter.Seq[T], size int) iter.Seq[[]T] {
if size <= 0 {
panic("it.Chunk: size must be greater than 0")
}
return func(yield func([]T) bool) {
var newSlice []T
for item := range collection {
if newSlice == nil {
newSlice = make([]T, 0, size)
}
newSlice = append(newSlice, item)
if len(newSlice) == size {
if !yield(newSlice) {
return
}
newSlice = nil
}
}
if newSlice != nil {
yield(newSlice)
}
}
}
// Window creates a sequence of sliding windows of a given size.
// Each window overlaps with the previous one by size-1 elements.
// This is equivalent to Sliding(collection, size, 1).
// Play: https://go.dev/play/p/_1BzQYtKBhi
func Window[T any](collection iter.Seq[T], size int) iter.Seq[[]T] {
if size <= 0 {
panic("it.Window: size must be greater than 0")
}
return Sliding(collection, size, 1)
}
// Sliding creates a sequence of sliding windows of a given size with a given step.
// offset = step - size: offset == 0 means adjacent windows (no overlap/gap);
// offset < 0 means overlapping windows; offset > 0 means gaps between windows.
// Only full-size windows are yielded; a partial window at the end is not yielded.
// Play: https://go.dev/play/p/mzhO4CZeiik
func Sliding[T any](collection iter.Seq[T], size, step int) iter.Seq[[]T] {
if size <= 0 {
panic("it.Sliding: size must be greater than 0")
}
if step <= 0 {
panic("it.Sliding: step must be greater than 0")
}
return func(yield func([]T) bool) {
buffer := make([]T, size)
count := 0
offset := step - size
switch {
case offset == 0:
// Adjacent windows: no overlap, no gap.
for buffer[count] = range collection {
if count++; count == size {
if !yield(append(make([]T, 0, size), buffer...)) {
return
}
count = 0
}
}
case offset < 0:
// Overlap: next window starts inside the current one; keep tail in buffer.
for buffer[count] = range collection {
if count++; count == size {
if !yield(append(make([]T, 0, size), buffer...)) {
return
}
count -= step
copy(buffer, buffer[step:])
}
}
default: // offset > 0 (step > size)
// Gap: skip elements between windows.
skip := 0
for item := range collection {
if skip > 0 {
skip--
continue
}
buffer[count] = item
if count++; count == size {
if !yield(append(make([]T, 0, size), buffer...)) {
return
}
count = 0
skip = offset
}
}
}
}
}
// PartitionBy returns a sequence of elements split into groups. The order of grouped values is
// determined by the order they occur in collection. The grouping is generated from the results
// of running each element of collection through transform.
// Will allocate a map large enough to hold all distinct transformed elements.
// Long heterogeneous input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/VxTx8mva28z
func PartitionBy[T any, K comparable](collection iter.Seq[T], transform func(item T) K) [][]T {
var result [][]T
seen := map[K]int{}
for item := range collection {
key := transform(item)
resultIndex, ok := seen[key]
if ok {
result[resultIndex] = append(result[resultIndex], item)
} else {
seen[key] = len(result)
result = append(result, []T{item})
}
}
return result
}
// Flatten returns a sequence a single level deep.
// Play: https://go.dev/play/p/CCklxuNk7Lm
func Flatten[T any, I ~func(func(T) bool)](collection []I) I {
return func(yield func(T) bool) {
for _, item := range collection {
for item := range item {
if !yield(item) {
return
}
}
}
}
}
// Concat returns a sequence of all the elements in iterators. Concat conserves the order of the elements.
// Play: https://go.dev/play/p/Fa0u7xT2JOR
func Concat[T any, I ~func(func(T) bool)](collection ...I) I {
return Flatten(collection)
}
// Interleave round-robin alternating input sequences and sequentially appending value at index into result.
// Will allocate a slice the size of collections.
// Play: https://go.dev/play/p/kNvnz4ClLgH
func Interleave[T any](collections ...iter.Seq[T]) iter.Seq[T] {
return func(yield func(T) bool) {
next := make([]func() (T, bool), len(collections))
for i, c := range collections {
var stop func()
next[i], stop = iter.Pull(c)
defer stop()
}
var done int
for done < len(next) {
done = 0
for i, n := range next {
if n == nil {
done++
} else if t, ok := n(); !ok {
next[i] = nil
done++
} else if !yield(t) {
return
}
}
}
}
}
// Shuffle returns a sequence of shuffled values. Uses the Fisher-Yates shuffle algorithm.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/8isgTsyfL-t
func Shuffle[T any, I ~func(func(T) bool)](collection I) I {
slice := slices.Collect(iter.Seq[T](collection))
mutable.Shuffle(slice)
return I(slices.Values(slice))
}
// Reverse reverses a sequence so that the first element becomes the last, the second element becomes the second to last, and so on.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/9jthUzgF-u
func Reverse[T any, I ~func(func(T) bool)](collection I) I {
slice := slices.Collect(iter.Seq[T](collection))
return I(func(yield func(T) bool) {
for i := len(slice) - 1; i >= 0; i-- {
if !yield(slice[i]) {
return
}
}
})
}
// Fill replaces elements of a sequence with `initial` value.
// Play: https://go.dev/play/p/mHShWq5ezMc
func Fill[T lo.Clonable[T], I ~func(func(T) bool)](collection I, initial T) I {
return func(yield func(T) bool) {
for range collection {
if !yield(initial.Clone()) {
return
}
}
}
}
// Repeat builds a sequence with N copies of initial value.
// Play: https://go.dev/play/p/xs-aq0p_uDP
func Repeat[T lo.Clonable[T]](count int, initial T) iter.Seq[T] {
return RepeatBy(count, func(int) T { return initial.Clone() })
}
// RepeatBy builds a sequence with values returned by N calls of transform.
// Play: https://go.dev/play/p/i7BuZQBcUzZ
func RepeatBy[T any](count int, callback func(index int) T) iter.Seq[T] {
return func(yield func(T) bool) {
for i := range count {
if !yield(callback(i)) {
return
}
}
}
}
// KeyBy transforms a sequence to a map based on a pivot transform function.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/MMaHpzTqY0a
func KeyBy[K comparable, V any](collection iter.Seq[V], transform func(item V) K) map[K]V {
result := make(map[K]V)
for item := range collection {
k := transform(item)
result[k] = item
}
return result
}
// Associate returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original sequence.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/ayCImLr_4im
func Associate[T any, K comparable, V any](collection iter.Seq[T], transform func(item T) (K, V)) map[K]V {
return AssociateI(collection, func(item T, _ int) (K, V) { return transform(item) })
}
// AssociateI returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original sequence.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/5CwPTPz-zb
func AssociateI[T any, K comparable, V any](collection iter.Seq[T], transform func(item T, index int) (K, V)) map[K]V {
result := make(map[K]V)
var i int
for item := range collection {
k, v := transform(item, i)
result[k] = v
i++
}
return result
}
// SeqToMap returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original sequence.
// Alias of Associate().
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/6NhAE0-zm
func SeqToMap[T any, K comparable, V any](collection iter.Seq[T], transform func(item T) (K, V)) map[K]V {
return Associate(collection, transform)
}
// SeqToMapI returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original sequence.
// Alias of AssociateI().
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/7OiBF1-zn
func SeqToMapI[T any, K comparable, V any](collection iter.Seq[T], transform func(item T, index int) (K, V)) map[K]V {
return AssociateI(collection, transform)
}
// FilterSeqToMap returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original sequence.
// The third return value of the transform function is a boolean that indicates whether the key-value pair should be included in the map.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/8PjCG2-zo
func FilterSeqToMap[T any, K comparable, V any](collection iter.Seq[T], transform func(item T) (K, V, bool)) map[K]V {
return FilterSeqToMapI(collection, func(item T, _ int) (K, V, bool) {
return transform(item)
})
}
// FilterSeqToMapI returns a map containing key-value pairs provided by transform function applied to elements of the given sequence.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original sequence.
// The third return value of the transform function is a boolean that indicates whether the key-value pair should be included in the map.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/9QkDH3-zp
func FilterSeqToMapI[T any, K comparable, V any](collection iter.Seq[T], transform func(item T, index int) (K, V, bool)) map[K]V {
result := make(map[K]V)
var i int
for item := range collection {
if k, v, ok := transform(item, i); ok {
result[k] = v
}
i++
}
return result
}
// Keyify returns a map with each unique element of the sequence as a key.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/aHOD29_l-rF
func Keyify[T comparable](collection iter.Seq[T]) map[T]struct{} {
result := make(map[T]struct{})
for item := range collection {
result[item] = struct{}{}
}
return result
}
// Drop drops n elements from the beginning of a sequence.
// Play: https://go.dev/play/p/O1J1-uWc3z9
func Drop[T any, I ~func(func(T) bool)](collection I, n int) I {
if n < 0 {
panic("it.Drop: n must not be negative")
}
if n == 0 {
return collection
}
return FilterI(collection, func(_ T, index int) bool { return index >= n })
}
// DropLast drops n elements from the end of a sequence.
// Will allocate a slice of length n.
// Play: https://go.dev/play/p/-NzU5Px5Tp4
func DropLast[T any, I ~func(func(T) bool)](collection I, n int) I {
if n < 0 {
panic("it.DropLast: n must not be negative")
}
if n == 0 {
return collection
}
return func(yield func(T) bool) {
buf := make([]T, n)
var count int
for item := range collection {
idx := count % n
count++
if count > n && !yield(buf[idx]) {
return
}
buf[idx] = item
}
}
}
// DropWhile drops elements from the beginning of a sequence while the predicate returns true.
// Play: https://go.dev/play/p/zSM8x08a9QD
func DropWhile[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I {
return func(yield func(T) bool) {
dropping := true
for item := range collection {
dropping = dropping && predicate(item)
if !dropping && !yield(item) {
return
}
}
}
}
// DropLastWhile drops elements from the end of a sequence while the predicate returns true.
// Will allocate a slice large enough to hold the longest sequence of matching elements.
// Long input sequences of consecutive matches can cause excessive memory usage.
// Play: https://go.dev/play/p/qZ81Cq7R-Yt
func DropLastWhile[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I {
return func(yield func(T) bool) {
var buf []T
for item := range collection {
if predicate(item) {
buf = append(buf, item)
continue
}
if len(buf) > 0 {
for i := range buf {
if !yield(buf[i]) {
return
}
}
buf = buf[:0]
}
if !yield(item) {
return
}
}
}
}
// Take takes the first n elements from a sequence.
func Take[T any, I ~func(func(T) bool)](collection I, n int) I {
if n < 0 {
panic("it.Take: n must not be negative")
}
if n == 0 {
return I(func(func(T) bool) {})
}
return func(yield func(T) bool) {
count := 0
for item := range collection {
count++
if !yield(item) || count >= n {
return
}
}
}
}
// TakeWhile takes elements from the beginning of a sequence while the predicate returns true.
func TakeWhile[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I {
return func(yield func(T) bool) {
for item := range collection {
if !predicate(item) || !yield(item) {
return
}
}
}
}
// DropByIndex drops elements from a sequence by the index.
// Will allocate a map large enough to hold all distinct indexes.
// Play: https://go.dev/play/p/vPbrZYgiU4q
func DropByIndex[T any, I ~func(func(T) bool)](collection I, indexes ...int) I {
set := lo.Keyify(indexes)
return RejectI(collection, func(_ T, index int) bool { return lo.HasKey(set, index) })
}
// TakeFilter filters elements and takes the first n elements that match the predicate.
// Equivalent to calling Take(Filter(...)), but more efficient as it stops after finding n matches.
// Play: https://go.dev/play/p/Db68Bhu4MCA
func TakeFilter[T any, I ~func(func(T) bool)](collection I, n int, predicate func(item T) bool) I {
return TakeFilterI(collection, n, func(item T, _ int) bool { return predicate(item) })
}
// TakeFilterI filters elements and takes the first n elements that match the predicate.
// Equivalent to calling Take(FilterI(...)), but more efficient as it stops after finding n matches.
func TakeFilterI[T any, I ~func(func(T) bool)](collection I, n int, predicate func(item T, index int) bool) I {
if n < 0 {
panic("it.TakeFilterI: n must not be negative")
}
if n == 0 {
return I(func(func(T) bool) {})
}
return func(yield func(T) bool) {
var count int
var index int
for item := range collection {
if predicate(item, index) {
count++
if !yield(item) || count >= n {
return
}
}
index++
}
}
}
// Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return true for.
// Play: https://go.dev/play/p/IIQcknFhZnq
func Reject[T any, I ~func(func(T) bool)](collection I, predicate func(item T) bool) I {
return RejectI(collection, func(item T, _ int) bool { return predicate(item) })
}
// RejectI is the opposite of Filter, this method returns the elements of collection that predicate does not return true for.
// Play: https://go.dev/play/p/7YsLP1-zx
func RejectI[T any, I ~func(func(T) bool)](collection I, predicate func(item T, index int) bool) I {
return func(yield func(T) bool) {
var i int
for item := range collection {
if !predicate(item, i) && !yield(item) {
return
}
i++
}
}
}
// RejectMap is the opposite of FilterMap, this method returns a sequence obtained after both filtering and mapping using the given callback function.
// The callback function should return two values:
// - the result of the mapping operation and
// - whether the result element should be included or not.
//
// Play: https://go.dev/play/p/51jRHVYscgi
func RejectMap[T, R any](collection iter.Seq[T], callback func(item T) (R, bool)) iter.Seq[R] {
return RejectMapI(collection, func(item T, _ int) (R, bool) { return callback(item) })
}
// RejectMapI is the opposite of FilterMap, this method returns a sequence obtained after both filtering and mapping using the given callback function.
// The callback function should return two values:
// - the result of the mapping operation and
// - whether the result element should be included or not.
//
// Play: https://go.dev/play/p/9jthUzgF-u
func RejectMapI[T, R any](collection iter.Seq[T], callback func(item T, index int) (R, bool)) iter.Seq[R] {
return func(yield func(R) bool) {
var i int
for item := range collection {
if r, ok := callback(item, i); !ok && !yield(r) {
return
}
i++
}
}
}
// Count counts the number of elements in the collection that equal value.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/UcJ-6cANwfY
func Count[T comparable](collection iter.Seq[T], value T) int {
return CountBy(collection, func(item T) bool { return item == value })
}
// CountBy counts the number of elements in the collection for which predicate is true.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/m6G0o3huCOG
func CountBy[T any](collection iter.Seq[T], predicate func(item T) bool) int {
var count int
for item := range collection {
if predicate(item) {
count++
}
}
return count
}
// CountValues counts the number of each element in the collection.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/PPBT4Fp-V3B
func CountValues[T comparable](collection iter.Seq[T]) map[T]int {
return CountValuesBy(collection, func(item T) T { return item })
}
// CountValuesBy counts the number of each element returned from transform function.
// Is equivalent to chaining Map and CountValues.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/gnr_MPhYCHX
func CountValuesBy[T any, U comparable](collection iter.Seq[T], transform func(item T) U) map[U]int {
result := make(map[U]int)
for item := range collection {
result[transform(item)]++
}
return result
}
// Subset returns a subset of a sequence from `offset` up to `length` elements.
// Will iterate at most offset+length times.
// Play: https://go.dev/play/p/r-6FdqOL28Z
func Subset[T any, I ~func(func(T) bool)](collection I, offset, length int) I {
if offset < 0 {
panic("it.Subset: offset must not be negative")
}
if length < 0 {
panic("it.Subset: length must not be negative")
}
return Slice(collection, offset, offset+length)
}
// Slice returns a subset of a sequence from `start` up to, but not including `end`.
// Will iterate at most end times.
// Play: https://go.dev/play/p/jKIu1oPf5hK
func Slice[T any, I ~func(func(T) bool)](collection I, start, end int) I {
if start < 0 {
start = 0
}
if end < 0 {
end = 0
}
return func(yield func(T) bool) {
var i int
for item := range collection {
if i >= start && (i >= end || !yield(item)) {
return
}
i++
}
}
}
// Replace returns a sequence with the first n non-overlapping instances of old replaced by new.
// Play: https://go.dev/play/p/aFXjeyf0KqV
func Replace[T comparable, I ~func(func(T) bool)](collection I, old, nEw T, n int) I {
return I(func(yield func(T) bool) {
n := n
Map(iter.Seq[T](collection), func(item T) T {
if n != 0 && item == old {
n--
return nEw
}
return item
})(yield)
})
}
// ReplaceAll returns a sequence with all non-overlapping instances of old replaced by new.
// Play: https://go.dev/play/p/sOckhMvvwjc
func ReplaceAll[T comparable, I ~func(func(T) bool)](collection I, old, nEw T) I {
return Replace(collection, old, nEw, -1)
}
// Compact returns a sequence of all non-zero elements.
// Play: https://go.dev/play/p/R_xT99w2QaU
func Compact[T comparable, I ~func(func(T) bool)](collection I) I {
return Filter(collection, lo.IsNotEmpty)
}
// IsSorted checks if a sequence is sorted.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/o-BD4UOn-0U
func IsSorted[T constraints.Ordered](collection iter.Seq[T]) bool {
return IsSortedBy(collection, func(item T) T { return item })
}
// IsSortedBy checks if a sequence is sorted by transform.
// Will iterate through the entire sequence.
// Play: https://go.dev/play/p/AfYOiGWa78T
func IsSortedBy[T any, K constraints.Ordered](collection iter.Seq[T], transform func(item T) K) bool {
first := true
var prev K
for item := range collection {
key := transform(item)
if first {
first = false
} else if prev > key {
return false
}
prev = key
}
return true
}
// Splice inserts multiple elements at index i. The helper is protected against overflow errors.
// Play: https://go.dev/play/p/TevQSvDKO_i
func Splice[T any, I ~func(func(T) bool)](collection I, index int, elements ...T) I {
if index < 0 {
panic("it.Splice: index must not be negative")
}
if len(elements) == 0 {
return collection
}
return func(yield func(T) bool) {
var i int
for item := range collection {
if i == index {
for _, element := range elements {
if !yield(element) {
return
}
}
}
if !yield(item) {
return
}
i++
}
if i <= index {
for _, element := range elements {
if !yield(element) {
return
}
}
}
}
}
// CutPrefix returns collection without the provided leading prefix
// and reports whether it found the prefix.
// If collection doesn't start with prefix, CutPrefix returns collection, false.
// If prefix is empty, CutPrefix returns collection, true.
// Will iterate at most the size of separator before returning.
// Play: https://go.dev/play/p/bPnV39zVnAV
func CutPrefix[T comparable, I ~func(func(T) bool)](collection I, separator []T) (after I, found bool) { //nolint:gocyclo
if len(separator) == 0 {
return collection, true
}
next, stop := iter.Pull(iter.Seq[T](collection))
for i := range separator {
item, ok := next()
if !ok {
return func(yield func(T) bool) {
defer stop()
for j := range i {
if !yield(separator[j]) {
return
}
}
}, false
}
if item != separator[i] {
return func(yield func(T) bool) {
defer stop()
for j := range i {
if !yield(separator[j]) {
return
}
}
if ok && !yield(item) {
return
}
for {
if item, ok := next(); !ok || !yield(item) {
return
}
}
}, false
}
}
return func(yield func(T) bool) {
defer stop()
for {
if item, ok := next(); !ok || !yield(item) {
return
}
}
}, true
}
// CutSuffix returns collection without the provided ending suffix and reports
// whether it found the suffix. If collection doesn't end with suffix, CutSuffix returns collection, false.
// If suffix is empty, CutSuffix returns collection, true.
// Will iterate through the entire sequence and allocate a slice large enough to hold all elements.
// Long input sequences can cause excessive memory usage.
// Play: https://go.dev/play/p/CTRh9m1UHrZ
func CutSuffix[T comparable, I ~func(func(T) bool)](collection I, separator []T) (before I, found bool) {
slice := slices.Collect(iter.Seq[T](collection))
result, ok := lo.CutSuffix(slice, separator)
return I(slices.Values(result)), ok
}
// Trim removes all the leading and trailing cutset from the collection.
// Will allocate a map large enough to hold all distinct cutset elements.
// Play: https://go.dev/play/p/k0VCcilk4V1
func Trim[T comparable, I ~func(func(T) bool)](collection I, cutset ...T) I {
predicate := lo.Partial(lo.HasKey, lo.Keyify(cutset))
return DropLastWhile(DropWhile(collection, predicate), predicate)
}
// TrimFirst removes all the leading cutset from the collection.
// Will allocate a map large enough to hold all distinct cutset elements.
// Play: https://go.dev/play/p/4D4Ke5C5MwH
func TrimFirst[T comparable, I ~func(func(T) bool)](collection I, cutset ...T) I {
return DropWhile(collection, lo.Partial(lo.HasKey, lo.Keyify(cutset)))
}
// TrimPrefix removes all the leading prefix from the collection.
// Play: https://go.dev/play/p/Pce4zSPnThY
func TrimPrefix[T comparable, I ~func(func(T) bool)](collection I, prefix []T) I {
n := len(prefix)
if n == 0 {
return collection
}
return func(yield func(T) bool) {
var i int
for item := range collection {
if i >= 0 {
if item == prefix[i] {
i = (i + 1) % n
continue
}
for j := range i {
if !yield(prefix[j]) {
return
}
}
i = -1
}
if !yield(item) {
return
}
}
for j := range i {
if !yield(prefix[j]) {
return
}
}
}
}
// TrimLast removes all the trailing cutset from the collection.
// Will allocate a map large enough to hold all distinct cutset elements.
// Play: https://go.dev/play/p/GQLhnaeW0gd
func TrimLast[T comparable, I ~func(func(T) bool)](collection I, cutset ...T) I {
return DropLastWhile(collection, lo.Partial(lo.HasKey, lo.Keyify(cutset)))
}
// TrimSuffix removes all the trailing suffix from the collection.
// Play: https://go.dev/play/p/s9nwy9helEi
func TrimSuffix[T comparable, I ~func(func(T) bool)](collection I, suffix []T) I {
n := len(suffix)
if n == 0 {
return collection
}
return func(yield func(T) bool) {
var i int
for item := range collection {
if item == suffix[i%n] {
i++
continue
}
for j := range i {
if !yield(suffix[j%n]) {
return
}
}
i = 0
if item == suffix[0] {
i++
continue
}
if !yield(item) {
return
}
}
if i%n != 0 {
for j := range i {
if !yield(suffix[j%n]) {
return
}
}
}
}
}
// Buffer returns a sequence of slices, each containing up to size items read from the channel.
// The last slice may be smaller if the channel closes before filling the buffer.
// Play: https://go.dev/play/p/zDZdcCA20ut
func Buffer[T any](seq iter.Seq[T], size int) iter.Seq[[]T] {
return func(yield func([]T) bool) {
buffer := make([]T, 0, size)
seq(func(v T) bool {
buffer = append(buffer, v)
if len(buffer) < size {
return true // keep pulling
}
// Buffer full, yield it
result := buffer
buffer = make([]T, 0, size) // allocate new buffer
return yield(result) // false = stop, true = continue
})
// Yield remaining partial buffer
if len(buffer) > 0 {
yield(buffer)
}
}
}
================================================
FILE: it/seq_example_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"iter"
"math"
"slices"
"strconv"
)
func ExampleLength() {
list := slices.Values([]int64{1, 2, 3, 4})
result := Length(list)
fmt.Printf("%v", result)
// Output: 4
}
func ExampleDrain() {
list := slices.Values([]int64{1, 2, 3, 4})
Drain(list)
}
func ExampleFilter() {
list := slices.Values([]int64{1, 2, 3, 4})
result := Filter(list, func(nbr int64) bool {
return nbr%2 == 0
})
fmt.Printf("%v", slices.Collect(result))
// Output: [2 4]
}
func ExampleMap() {
list := slices.Values([]int64{1, 2, 3, 4})
result := Map(list, func(nbr int64) string {
return strconv.FormatInt(nbr*2, 10)
})
fmt.Printf("%v", slices.Collect(result))
// Output: [2 4 6 8]
}
func ExampleUniqMap() {
type User struct {
Name string
Age int
}
users := slices.Values([]User{{Name: "Alex", Age: 10}, {Name: "Alex", Age: 12}, {Name: "Bob", Age: 11}, {Name: "Alice", Age: 20}})
result := UniqMap(users, func(u User) string {
return u.Name
})
fmt.Printf("%v", slices.Collect(result))
// Output: [Alex Bob Alice]
}
func ExampleFilterMap() {
list := slices.Values([]int64{1, 2, 3, 4})
result := FilterMap(list, func(nbr int64) (string, bool) {
return strconv.FormatInt(nbr*2, 10), nbr%2 == 0
})
fmt.Printf("%v", slices.Collect(result))
// Output: [4 8]
}
func ExampleFlatMap() {
list := slices.Values([]int64{1, 2, 3, 4})
result := FlatMap(list, func(nbr int64) iter.Seq[string] {
return slices.Values([]string{
strconv.FormatInt(nbr, 10), // base 10
strconv.FormatInt(nbr, 2), // base 2
})
})
fmt.Printf("%v", slices.Collect(result))
// Output: [1 1 2 10 3 11 4 100]
}
func ExampleReduce() {
list := slices.Values([]int64{1, 2, 3, 4})
result := Reduce(list, func(agg, item int64) int64 {
return agg + item
}, 0)
fmt.Printf("%v", result)
// Output: 10
}
func ExampleReduceLast() {
list := slices.Values([][]int{{0, 1}, {2, 3}, {4, 5}})
result := ReduceLast(list, func(agg, item []int) []int {
return append(agg, item...)
}, []int{})
fmt.Printf("%v", result)
// Output: [4 5 2 3 0 1]
}
func ExampleForEach() {
list := slices.Values([]int64{1, 2, 3, 4})
ForEach(list, func(x int64) {
fmt.Println(x)
})
// Output:
// 1
// 2
// 3
// 4
}
func ExampleForEachWhile() {
list := slices.Values([]int64{1, 2, -math.MaxInt, 4})
ForEachWhile(list, func(x int64) bool {
if x < 0 {
return false
}
fmt.Println(x)
return true
})
// Output:
// 1
// 2
}
func ExampleTimes() {
result := Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
fmt.Printf("%v", slices.Collect(result))
// Output: [0 1 2]
}
func ExampleUniq() {
list := slices.Values([]int{1, 2, 2, 1})
result := Uniq(list)
fmt.Printf("%v", slices.Collect(result))
// Output: [1 2]
}
func ExampleUniqBy() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := UniqBy(list, func(i int) int {
return i % 3
})
fmt.Printf("%v", slices.Collect(result))
// Output: [0 1 2]
}
func ExampleGroupBy() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := GroupBy(list, func(i int) int {
return i % 3
})
fmt.Printf("%v\n", result[0])
fmt.Printf("%v\n", result[1])
fmt.Printf("%v\n", result[2])
// Output:
// [0 3]
// [1 4]
// [2 5]
}
func ExampleGroupByMap() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := GroupByMap(list, func(i int) (int, int) {
return i % 3, i * 2
})
fmt.Printf("%v\n", result[0])
fmt.Printf("%v\n", result[1])
fmt.Printf("%v\n", result[2])
// Output:
// [0 6]
// [2 8]
// [4 10]
}
func ExampleChunk() {
list := slices.Values([]int{0, 1, 2, 3, 4})
result := Chunk(list, 2)
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// [0 1]
// [2 3]
// [4]
}
func ExampleWindow() {
list := slices.Values([]int{1, 2, 3, 4, 5})
result := Window(list, 3)
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// [1 2 3]
// [2 3 4]
// [3 4 5]
}
func ExampleSliding() {
list := slices.Values([]int{1, 2, 3, 4, 5, 6, 7, 8})
result := Sliding(list, 2, 3)
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// [1 2]
// [4 5]
// [7 8]
}
func ExamplePartitionBy() {
list := slices.Values([]int{-2, -1, 0, 1, 2, 3, 4})
result := PartitionBy(list, func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
})
for _, v := range result {
fmt.Printf("%v\n", v)
}
// Output:
// [-2 -1]
// [0 2 4]
// [1 3]
}
func ExampleFlatten() {
list := []iter.Seq[int]{slices.Values([]int{0, 1, 2}), slices.Values([]int{3, 4, 5})}
result := Flatten(list)
fmt.Printf("%v", slices.Collect(result))
// Output: [0 1 2 3 4 5]
}
func ExampleConcat() {
list1 := slices.Values([]int{0, 1, 2})
list2 := slices.Values([]int{3, 4, 5})
list3 := slices.Values([]int{6, 7, 8})
result := Concat(list1, list2, list3)
fmt.Printf("%v", slices.Collect(result))
// Output: [0 1 2 3 4 5 6 7 8]
}
func ExampleInterleave() {
list1 := []iter.Seq[int]{slices.Values([]int{1, 4, 7}), slices.Values([]int{2, 5, 8}), slices.Values([]int{3, 6, 9})}
list2 := []iter.Seq[int]{slices.Values([]int{1}), slices.Values([]int{2, 5, 8}), slices.Values([]int{3, 6}), slices.Values([]int{4, 7, 9, 10})}
result1 := slices.Collect(Interleave(list1...))
result2 := slices.Collect(Interleave(list2...))
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
// Output:
// [1 2 3 4 5 6 7 8 9]
// [1 2 3 4 5 6 7 8 9 10]
}
func ExampleShuffle() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := slices.Collect(Shuffle(list))
fmt.Printf("%v", result)
}
func ExampleReverse() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := slices.Collect(Reverse(list))
fmt.Printf("%v", result)
// Output: [5 4 3 2 1 0]
}
func ExampleFill() {
list := slices.Values([]foo{{"a"}, {"a"}})
result := Fill(list, foo{"b"})
fmt.Printf("%v", slices.Collect(result))
// Output: [{b} {b}]
}
func ExampleRepeat() {
result := Repeat(2, foo{"a"})
fmt.Printf("%v", slices.Collect(result))
// Output: [{a} {a}]
}
func ExampleRepeatBy() {
result := RepeatBy(5, func(i int) string {
return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10)
})
fmt.Printf("%v", slices.Collect(result))
// Output: [0 1 4 9 16]
}
func ExampleKeyBy() {
list := slices.Values([]string{"a", "aa", "aaa"})
result := KeyBy(list, func(str string) int {
return len(str)
})
fmt.Printf("%v", result)
// Output: map[1:a 2:aa 3:aaa]
}
func ExampleSeqToMap() {
list := slices.Values([]string{"a", "aa", "aaa"})
result := SeqToMap(list, func(str string) (string, int) {
return str, len(str)
})
fmt.Printf("%v", result)
// Output: map[a:1 aa:2 aaa:3]
}
func ExampleFilterSeqToMap() {
list := slices.Values([]string{"a", "aa", "aaa"})
result := FilterSeqToMap(list, func(str string) (string, int, bool) {
return str, len(str), len(str) > 1
})
fmt.Printf("%v", result)
// Output: map[aa:2 aaa:3]
}
func ExampleKeyify() {
list := slices.Values([]string{"a", "a", "b", "b", "d"})
set := Keyify(list)
_, ok1 := set["a"]
_, ok2 := set["c"]
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", set)
// Output:
// true
// false
// map[a:{} b:{} d:{}]
}
func ExampleDrop() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := Drop(list, 2)
fmt.Printf("%v", slices.Collect(result))
// Output: [2 3 4 5]
}
func ExampleTake() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := Take(list, 3)
fmt.Printf("%v", slices.Collect(result))
// Output: [0 1 2]
}
func ExampleDropWhile() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := DropWhile(list, func(val int) bool {
return val < 2
})
fmt.Printf("%v", slices.Collect(result))
// Output: [2 3 4 5]
}
func ExampleTakeWhile() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := TakeWhile(list, func(val int) bool {
return val < 3
})
fmt.Printf("%v", slices.Collect(result))
// Output: [0 1 2]
}
func ExampleDropByIndex() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := DropByIndex(list, 2)
fmt.Printf("%v", slices.Collect(result))
// Output: [0 1 3 4 5]
}
func ExampleTakeFilter() {
list := slices.Values([]int{1, 2, 3, 4, 5, 6})
result := TakeFilter(list, 2, func(val int) bool {
return val%2 == 0
})
fmt.Printf("%v", slices.Collect(result))
// Output: [2 4]
}
func ExampleTakeFilterI() {
list := slices.Values([]int{1, 2, 3, 4, 5, 6})
result := TakeFilterI(list, 2, func(val, index int) bool {
return val%2 == 0 && index < 4
})
fmt.Printf("%v", slices.Collect(result))
// Output: [2 4]
}
func ExampleReject() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := Reject(list, func(x int) bool {
return x%2 == 0
})
fmt.Printf("%v", slices.Collect(result))
// Output: [1 3 5]
}
func ExampleCount() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5, 0, 1, 2, 3})
result := Count(list, 2)
fmt.Printf("%v", result)
// Output: 2
}
func ExampleCountBy() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5, 0, 1, 2, 3})
result := CountBy(list, func(i int) bool {
return i < 4
})
fmt.Printf("%v", result)
// Output: 8
}
func ExampleCountValues() {
result1 := CountValues(slices.Values([]int{}))
result2 := CountValues(slices.Values([]int{1, 2}))
result3 := CountValues(slices.Values([]int{1, 2, 2}))
result4 := CountValues(slices.Values([]string{"foo", "bar", ""}))
result5 := CountValues(slices.Values([]string{"foo", "bar", "bar"}))
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
// Output:
// map[]
// map[1:1 2:1]
// map[1:1 2:2]
// map[:1 bar:1 foo:1]
// map[bar:2 foo:1]
}
func ExampleCountValuesBy() {
isEven := func(v int) bool {
return v%2 == 0
}
result1 := CountValuesBy(slices.Values([]int{}), isEven)
result2 := CountValuesBy(slices.Values([]int{1, 2}), isEven)
result3 := CountValuesBy(slices.Values([]int{1, 2, 2}), isEven)
length := func(v string) int {
return len(v)
}
result4 := CountValuesBy(slices.Values([]string{"foo", "bar", ""}), length)
result5 := CountValuesBy(slices.Values([]string{"foo", "bar", "bar"}), length)
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
// Output:
// map[]
// map[false:1 true:1]
// map[false:1 true:2]
// map[0:1 3:2]
// map[3:3]
}
func ExampleSubset() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5})
result := Subset(list, 2, 3)
fmt.Printf("%v", slices.Collect(result))
// Output: [2 3 4]
}
func ExampleSlice() {
list := values(0, 1, 2, 3, 4, 5)
result := Slice(list, 1, 4)
fmt.Printf("%v\n", slices.Collect(result))
result = Slice(list, 4, 1)
fmt.Printf("%v\n", slices.Collect(result))
result = Slice(list, 4, 5)
fmt.Printf("%v\n", slices.Collect(result))
// Output:
// [1 2 3]
// []
// [4]
}
func ExampleReplace() {
list := slices.Values([]int{0, 1, 0, 1, 2, 3, 0})
result := Replace(list, 0, 42, 1)
fmt.Printf("%v\n", slices.Collect(result))
result = Replace(list, -1, 42, 1)
fmt.Printf("%v\n", slices.Collect(result))
result = Replace(list, 0, 42, 2)
fmt.Printf("%v\n", slices.Collect(result))
result = Replace(list, 0, 42, -1)
fmt.Printf("%v\n", slices.Collect(result))
// Output:
// [42 1 0 1 2 3 0]
// [0 1 0 1 2 3 0]
// [42 1 42 1 2 3 0]
// [42 1 42 1 2 3 42]
}
func ExampleCompact() {
list := slices.Values([]string{"", "foo", "", "bar", ""})
result := Compact(list)
fmt.Printf("%v", slices.Collect(result))
// Output: [foo bar]
}
func ExampleIsSorted() {
list := slices.Values([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
result := IsSorted(list)
fmt.Printf("%v", result)
// Output: true
}
func ExampleIsSortedBy() {
list := slices.Values([]string{"a", "bb", "ccc"})
result := IsSortedBy(list, func(s string) int {
return len(s)
})
fmt.Printf("%v", result)
// Output: true
}
func ExampleCutPrefix() {
collection := slices.Values([]string{"a", "b", "c", "d", "e", "f", "g"})
// Test with valid prefix
after, found := CutPrefix(collection, []string{"a", "b", "c"})
fmt.Printf("After: %v, Found: %t\n", slices.Collect(after), found)
// Test with prefix not found
after2, found2 := CutPrefix(collection, []string{"b"})
fmt.Printf("After: %v, Found: %t\n", slices.Collect(after2), found2)
// Test with empty prefix
after3, found3 := CutPrefix(collection, []string{})
fmt.Printf("After: %v, Found: %t\n", slices.Collect(after3), found3)
// Output:
// After: [d e f g], Found: true
// After: [a b c d e f g], Found: false
// After: [a b c d e f g], Found: true
}
func ExampleCutSuffix() {
collection := slices.Values([]string{"a", "b", "c", "d", "e", "f", "g"})
// Test with valid suffix
before, found := CutSuffix(collection, []string{"f", "g"})
fmt.Printf("Before: %v, Found: %t\n", slices.Collect(before), found)
// Test with suffix not found
before2, found2 := CutSuffix(collection, []string{"b"})
fmt.Printf("Before: %v, Found: %t\n", slices.Collect(before2), found2)
// Test with empty suffix
before3, found3 := CutSuffix(collection, []string{})
fmt.Printf("Before: %v, Found: %t\n", slices.Collect(before3), found3)
// Output:
// Before: [a b c d e], Found: true
// Before: [a b c d e f g], Found: false
// Before: [a b c d e f g], Found: true
}
func ExampleTrim() {
collection := slices.Values([]int{0, 1, 2, 0, 3, 0})
// Test with valid cutset
result := Trim(collection, 0)
fmt.Printf("Trim with cutset {0}: %v\n", slices.Collect(result))
// Test with string collection
words := slices.Values([]string{" hello ", "world", " "})
result2 := Trim(words, " ")
fmt.Printf("Trim with string cutset: %v\n", slices.Collect(result2))
// Test with no cutset elements
result3 := Trim(collection, 5)
fmt.Printf("Trim with cutset {5} (not present): %v\n", slices.Collect(result3))
// Output:
// Trim with cutset {0}: [1 2 0 3]
// Trim with string cutset: [ hello world ]
// Trim with cutset {5} (not present): [0 1 2 0 3 0]
}
func ExampleTrimFirst() {
collection := slices.Values([]int{0, 1, 2, 0, 3, 0})
// Test with valid cutset
result := TrimFirst(collection, 0)
fmt.Printf("TrimFirst with cutset {0}: %v\n", slices.Collect(result))
// Test with string collection
words := slices.Values([]string{" hello ", "world", " "})
result2 := TrimFirst(words, " ")
fmt.Printf("TrimFirst with string cutset: %v\n", slices.Collect(result2))
// Test with no cutset elements
result3 := TrimFirst(collection, 5)
fmt.Printf("TrimFirst with cutset {5} (not present): %v\n", slices.Collect(result3))
// Output:
// TrimFirst with cutset {0}: [1 2 0 3 0]
// TrimFirst with string cutset: [ hello world ]
// TrimFirst with cutset {5} (not present): [0 1 2 0 3 0]
}
func ExampleTrimPrefix() {
collection := slices.Values([]int{1, 2, 1, 2, 3})
// Test with valid prefix
result := TrimPrefix(collection, []int{1, 2})
fmt.Printf("TrimPrefix with prefix {1,2}: %v\n", slices.Collect(result))
// Test with string collection
words := slices.Values([]string{"hello", "hello", "world"})
result2 := TrimPrefix(words, []string{"hello"})
fmt.Printf("TrimPrefix with string prefix: %v\n", slices.Collect(result2))
// Test with prefix not present
result3 := TrimPrefix(collection, []int{5, 6})
fmt.Printf("TrimPrefix with prefix {5,6} (not present): %v\n", slices.Collect(result3))
// Output:
// TrimPrefix with prefix {1,2}: [3]
// TrimPrefix with string prefix: [world]
// TrimPrefix with prefix {5,6} (not present): [1 2 1 2 3]
}
func ExampleTrimLast() {
collection := slices.Values([]int{0, 1, 2, 0, 3, 0})
// Test with valid cutset
result := TrimLast(collection, 0)
fmt.Printf("TrimLast with cutset {0}: %v\n", slices.Collect(result))
// Test with string collection
words := slices.Values([]string{" hello ", "world", " "})
result2 := TrimLast(words, " ")
fmt.Printf("TrimLast with string cutset: %v\n", slices.Collect(result2))
// Test with no cutset elements
result3 := TrimLast(collection, 5)
fmt.Printf("TrimLast with cutset {5} (not present): %v\n", slices.Collect(result3))
// Output:
// TrimLast with cutset {0}: [0 1 2 0 3]
// TrimLast with string cutset: [ hello world ]
// TrimLast with cutset {5} (not present): [0 1 2 0 3 0]
}
func ExampleTrimSuffix() {
collection := slices.Values([]int{1, 2, 1, 2, 3})
// Test with valid suffix
result := TrimSuffix(collection, []int{1, 2})
fmt.Printf("TrimSuffix with suffix {1,2}: %v\n", slices.Collect(result))
// Test with string collection
words := slices.Values([]string{"hello", "world", "test"})
result2 := TrimSuffix(words, []string{"test"})
fmt.Printf("TrimSuffix with string suffix: %v\n", slices.Collect(result2))
// Test with suffix not present
result3 := TrimSuffix(collection, []int{5, 6})
fmt.Printf("TrimSuffix with suffix {5,6} (not present): %v\n", slices.Collect(result3))
// Output:
// TrimSuffix with suffix {1,2}: [1 2 1 2 3]
// TrimSuffix with string suffix: [hello world]
// TrimSuffix with suffix {5,6} (not present): [1 2 1 2 3]
}
================================================
FILE: it/seq_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"iter"
"math"
"slices"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLength(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Length(values[int]())
r2 := Length(values(1, 2, 3, 4))
is.Zero(r1)
is.Equal(4, r2)
}
func TestDrain(t *testing.T) {
t.Parallel()
is := assert.New(t)
var done bool
list := iter.Seq[int](func(yield func(int) bool) {
_ = yield(1) && yield(2) && yield(3)
done = true
})
Drain(list)
is.True(done)
}
func TestFilter(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Filter(values(1, 2, 3, 4), func(x int) bool {
return x%2 == 0
})
is.Equal([]int{2, 4}, slices.Collect(r1))
r2 := Filter(values("", "foo", "", "bar", ""), func(x string) bool {
return len(x) > 0
})
is.Equal([]string{"foo", "bar"}, slices.Collect(r2))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Filter(allStrings, func(x string) bool {
return len(x) > 0
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFilterI(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FilterI(values(1, 2, 3, 4), func(x, _ int) bool {
return x%2 == 0
})
is.Equal([]int{2, 4}, slices.Collect(r1))
r2 := FilterI(values("", "foo", "", "bar", ""), func(x string, _ int) bool {
return len(x) > 0
})
is.Equal([]string{"foo", "bar"}, slices.Collect(r2))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := FilterI(allStrings, func(x string, _ int) bool {
return len(x) > 0
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Map(values(1, 2, 3, 4), func(x int) string {
return "Hello"
})
result2 := Map(values[int64](1, 2, 3, 4), func(x int64) string {
return strconv.FormatInt(x, 10)
})
is.Equal([]string{"Hello", "Hello", "Hello", "Hello"}, slices.Collect(result1))
is.Equal([]string{"1", "2", "3", "4"}, slices.Collect(result2))
}
func TestMapI(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MapI(values(1, 2, 3, 4), func(x, _ int) string {
return "Hello"
})
result2 := MapI(values[int64](1, 2, 3, 4), func(x int64, _ int) string {
return strconv.FormatInt(x, 10)
})
is.Equal([]string{"Hello", "Hello", "Hello", "Hello"}, slices.Collect(result1))
is.Equal([]string{"1", "2", "3", "4"}, slices.Collect(result2))
}
func TestUniqMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
Name string
age int
}
users := values(User{Name: "Alice", age: 20}, User{Name: "Alex", age: 21}, User{Name: "Alex", age: 22})
result := UniqMap(users, func(item User) string {
return item.Name
})
is.Equal([]string{"Alice", "Alex"}, slices.Collect(result))
}
func TestUniqMapI(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
Name string
age int
}
users := values(User{Name: "Alice", age: 20}, User{Name: "Alex", age: 21}, User{Name: "Alex", age: 22})
result := UniqMapI(users, func(item User, _ int) string {
return item.Name
})
is.Equal([]string{"Alice", "Alex"}, slices.Collect(result))
}
func TestFilterMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FilterMap(values[int64](1, 2, 3, 4), func(x int64) (string, bool) {
if x%2 == 0 {
return strconv.FormatInt(x, 10), true
}
return "", false
})
r2 := FilterMap(values("cpu", "gpu", "mouse", "keyboard"), func(x string) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", true
}
return "", false
})
is.Equal([]string{"2", "4"}, slices.Collect(r1))
is.Equal([]string{"xpu", "xpu"}, slices.Collect(r2))
}
func TestFilterMapI(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FilterMapI(values[int64](1, 2, 3, 4), func(x int64, _ int) (string, bool) {
if x%2 == 0 {
return strconv.FormatInt(x, 10), true
}
return "", false
})
r2 := FilterMapI(values("cpu", "gpu", "mouse", "keyboard"), func(x string, _ int) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", true
}
return "", false
})
is.Equal([]string{"2", "4"}, slices.Collect(r1))
is.Equal([]string{"xpu", "xpu"}, slices.Collect(r2))
}
func TestFlatMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FlatMap(values(0, 1, 2, 3, 4), func(x int) iter.Seq[string] {
return values("Hello")
})
result2 := FlatMap(values[int64](0, 1, 2, 3, 4), func(x int64) iter.Seq[string] {
return func(yield func(string) bool) {
for range x {
if !yield(strconv.FormatInt(x, 10)) {
return
}
}
}
})
is.Equal([]string{"Hello", "Hello", "Hello", "Hello", "Hello"}, slices.Collect(result1))
is.Equal([]string{"1", "2", "2", "3", "3", "3", "4", "4", "4", "4"}, slices.Collect(result2))
}
func TestFlatMapI(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FlatMapI(values(0, 1, 2, 3, 4), func(x, _ int) iter.Seq[string] {
return values("Hello")
})
result2 := FlatMapI(values[int64](0, 1, 2, 3, 4), func(x int64, _ int) iter.Seq[string] {
return func(yield func(string) bool) {
for range x {
if !yield(strconv.FormatInt(x, 10)) {
return
}
}
}
})
is.Equal([]string{"Hello", "Hello", "Hello", "Hello", "Hello"}, slices.Collect(result1))
is.Equal([]string{"1", "2", "2", "3", "3", "3", "4", "4", "4", "4"}, slices.Collect(result2))
}
func TestTimes(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
is.Equal([]string{"0", "1", "2"}, slices.Collect(result1))
}
func TestReduce(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Reduce(values(1, 2, 3, 4), func(agg, item int) int {
return agg + item
}, 0)
result2 := Reduce(values(1, 2, 3, 4), func(agg, item int) int {
return agg + item
}, 10)
is.Equal(10, result1)
is.Equal(20, result2)
}
func TestReduceI(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ReduceI(values(1, 2, 3, 4), func(agg, item, _ int) int {
return agg + item
}, 0)
result2 := ReduceI(values(1, 2, 3, 4), func(agg, item, _ int) int {
return agg + item
}, 10)
is.Equal(10, result1)
is.Equal(20, result2)
}
func TestReduceLast(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ReduceLast(values([]int{0, 1}, []int{2, 3}, []int{4, 5}), func(agg, item []int) []int {
return append(agg, item...)
}, []int{})
is.Equal([]int{4, 5, 2, 3, 0, 1}, result1)
result2 := ReduceLast(values(1, 2, 3, 4), func(agg, item int) int {
return agg + item
}, 10)
is.Equal(20, result2)
}
func TestReduceLastI(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ReduceLastI(values([]int{0, 1}, []int{2, 3}, []int{4, 5}), func(agg, item []int, _ int) []int {
return append(agg, item...)
}, []int{})
is.Equal([]int{4, 5, 2, 3, 0, 1}, result1)
result2 := ReduceLastI(values(1, 2, 3, 4), func(agg, item, _ int) int {
return agg + item
}, 10)
is.Equal(20, result2)
}
func TestForEachI(t *testing.T) {
t.Parallel()
is := assert.New(t)
// check of callback is called for every element and in proper order
callParams1 := []string{}
callParams2 := []int{}
ForEachI(values("a", "b", "c"), func(item string, i int) {
callParams1 = append(callParams1, item)
callParams2 = append(callParams2, i)
})
is.Equal([]string{"a", "b", "c"}, callParams1)
is.Equal([]int{0, 1, 2}, callParams2)
is.IsIncreasing(callParams2)
}
func TestForEachWhileI(t *testing.T) {
t.Parallel()
is := assert.New(t)
// check of callback is called for every element and in proper order
var callParams1 []string
var callParams2 []int
ForEachWhileI(values("a", "b", "c"), func(item string, i int) bool {
if item == "c" {
return false
}
callParams1 = append(callParams1, item)
callParams2 = append(callParams2, i)
return true
})
is.Equal([]string{"a", "b"}, callParams1)
is.Equal([]int{0, 1}, callParams2)
is.IsIncreasing(callParams2)
}
func TestUniq(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Uniq(values(1, 2, 2, 1))
is.Equal([]int{1, 2}, slices.Collect(result1))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Uniq(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestUniqBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := UniqBy(values(0, 1, 2, 3, 4, 5), func(i int) int {
return i % 3
})
is.Equal([]int{0, 1, 2}, slices.Collect(result1))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := UniqBy(allStrings, func(i string) string {
return i
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestGroupBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := GroupBy(values(0, 1, 2, 3, 4, 5), func(i int) int {
return i % 3
})
is.Equal(map[int][]int{
0: {0, 3},
1: {1, 4},
2: {2, 5},
}, result1)
}
func TestGroupByMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := GroupByMap(values(0, 1, 2, 3, 4, 5), func(i int) (int, string) {
return i % 3, strconv.Itoa(i)
})
is.Equal(map[int][]string{
0: {"0", "3"},
1: {"1", "4"},
2: {"2", "5"},
}, result1)
type myInt int
result2 := GroupByMap(values[myInt](1, 0, 2, 3, 4, 5), func(i myInt) (int, string) {
return int(i % 3), strconv.Itoa(int(i))
})
is.Equal(map[int][]string{
0: {"0", "3"},
1: {"1", "4"},
2: {"2", "5"},
}, result2)
type product struct {
ID int64
CategoryID int64
}
products := values(
product{ID: 1, CategoryID: 1},
product{ID: 2, CategoryID: 1},
product{ID: 3, CategoryID: 2},
product{ID: 4, CategoryID: 3},
product{ID: 5, CategoryID: 3},
)
result3 := GroupByMap(products, func(item product) (int64, string) {
return item.CategoryID, "Product " + strconv.FormatInt(item.ID, 10)
})
is.Equal(map[int64][]string{
1: {"Product 1", "Product 2"},
2: {"Product 3"},
3: {"Product 4", "Product 5"},
}, result3)
}
func TestChunk(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Chunk(values(0, 1, 2, 3, 4, 5), 2)
result2 := Chunk(values(0, 1, 2, 3, 4, 5, 6), 2)
result3 := Chunk(values[int](), 2)
result4 := Chunk(values(0), 2)
is.Equal([][]int{{0, 1}, {2, 3}, {4, 5}}, slices.Collect(result1))
is.Equal([][]int{{0, 1}, {2, 3}, {4, 5}, {6}}, slices.Collect(result2))
is.Empty(slices.Collect(result3))
is.Equal([][]int{{0}}, slices.Collect(result4))
is.PanicsWithValue("it.Chunk: size must be greater than 0", func() {
Chunk(values(0), 0)
})
}
func TestWindow(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Window(values(1, 2, 3, 4, 5), 3)
result2 := Window(values(1, 2, 3, 4, 5, 6), 3)
result3 := Window(values(1, 2), 3)
result4 := Window(values(1, 2, 3), 3)
result5 := Window(values(1, 2, 3, 4), 1)
is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}}, slices.Collect(result1))
is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}, slices.Collect(result2))
is.Empty(slices.Collect(result3))
is.Equal([][]int{{1, 2, 3}}, slices.Collect(result4))
is.Equal([][]int{{1}, {2}, {3}, {4}}, slices.Collect(result5))
is.PanicsWithValue("it.Window: size must be greater than 0", func() {
Window(values(1, 2, 3), 0)
})
is.PanicsWithValue("it.Window: size must be greater than 0", func() {
Window(values(1, 2, 3), -1)
})
}
func TestSliding(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Sliding(values(1, 2, 3, 4, 5, 6), 3, 1)
is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}, slices.Collect(result1))
result2 := Sliding(values(1, 2, 3, 4, 5, 6), 3, 3)
is.Equal([][]int{{1, 2, 3}, {4, 5, 6}}, slices.Collect(result2))
result3 := Sliding(values(1, 2, 3, 4, 5, 6, 7, 8), 2, 3)
is.Equal([][]int{{1, 2}, {4, 5}, {7, 8}}, slices.Collect(result3))
result4 := Sliding(values(1, 2, 3, 4), 1, 1)
is.Equal([][]int{{1}, {2}, {3}, {4}}, slices.Collect(result4))
result5 := Sliding(values(1, 2), 3, 1)
is.Empty(slices.Collect(result5))
result6 := Sliding(values(1, 2, 3, 4, 5, 6), 2, 2)
is.Equal([][]int{{1, 2}, {3, 4}, {5, 6}}, slices.Collect(result6))
is.PanicsWithValue("it.Sliding: size must be greater than 0", func() {
Sliding(values(1, 2, 3), 0, 1)
})
is.PanicsWithValue("it.Sliding: step must be greater than 0", func() {
Sliding(values(1, 2, 3), 2, 0)
})
is.PanicsWithValue("it.Sliding: step must be greater than 0", func() {
Sliding(values(1, 2, 3), 2, -1)
})
// Test overlapping windows (step < size)
result7 := Sliding(values(1, 2, 3, 4, 5), 3, 2)
is.Equal([][]int{{1, 2, 3}, {3, 4, 5}}, slices.Collect(result7))
// Test with step > size (non-overlapping with gaps)
result8 := Sliding(values(1, 2, 3, 4, 5, 6, 7, 8), 2, 4)
is.Equal([][]int{{1, 2}, {5, 6}}, slices.Collect(result8))
// Test empty collection
result9 := Sliding(values[int](), 2, 1)
is.Empty(slices.Collect(result9))
// Test collection exactly equal to size (one window)
result10 := Sliding(values(1, 2, 3), 3, 1)
is.Equal([][]int{{1, 2, 3}}, slices.Collect(result10))
// Test collection exactly equal to size with step=3
result11 := Sliding(values(1, 2, 3), 3, 3)
is.Equal([][]int{{1, 2, 3}}, slices.Collect(result11))
// Test collection just larger than size (two windows)
result12 := Sliding(values(1, 2, 3, 4), 3, 1)
is.Equal([][]int{{1, 2, 3}, {2, 3, 4}}, slices.Collect(result12))
// Test size=1 with different steps
result13 := Sliding(values(1, 2, 3, 4, 5), 1, 2)
is.Equal([][]int{{1}, {3}, {5}}, slices.Collect(result13))
result14 := Sliding(values(1, 2, 3, 4, 5), 1, 3)
is.Equal([][]int{{1}, {4}}, slices.Collect(result14))
// Test very large step (only first window)
result15 := Sliding(values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 100)
is.Equal([][]int{{1, 2}}, slices.Collect(result15))
// Test step=1 with large size (maximum overlap)
result16 := Sliding(values(1, 2, 3, 4, 5), 4, 1)
is.Equal([][]int{{1, 2, 3, 4}, {2, 3, 4, 5}}, slices.Collect(result16))
// Test with strings
result17 := Sliding(values("a", "b", "c", "d"), 2, 1)
is.Equal([][]string{{"a", "b"}, {"b", "c"}, {"c", "d"}}, slices.Collect(result17))
// Test with structs
type Person struct {
Name string
Age int
}
people := values(
Person{"Alice", 25},
Person{"Bob", 30},
Person{"Charlie", 35},
Person{"Diana", 40},
)
result18 := Sliding(people, 2, 1)
collected := slices.Collect(result18)
is.Len(collected, 3)
is.Equal(Person{"Alice", 25}, collected[0][0])
is.Equal(Person{"Bob", 30}, collected[0][1])
is.Equal(Person{"Bob", 30}, collected[1][0])
is.Equal(Person{"Charlie", 35}, collected[1][1])
// Test size equals collection length with step > size (only one window)
result19 := Sliding(values(1, 2, 3), 3, 5)
is.Equal([][]int{{1, 2, 3}}, slices.Collect(result19))
// Test step=1 with size=2 on single element
result20 := Sliding(values(1), 2, 1)
is.Empty(slices.Collect(result20))
// Test large collection with small windows
result21 := Sliding(values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 2, 2)
is.Equal([][]int{{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}}, slices.Collect(result21))
// Test overlapping with step=2, size=4 (only full windows are returned)
result22 := Sliding(values(1, 2, 3, 4, 5, 6, 7), 4, 2)
is.Equal([][]int{{1, 2, 3, 4}, {3, 4, 5, 6}}, slices.Collect(result22))
// Test with float64
result23 := Sliding(values(1.1, 2.2, 3.3, 4.4), 2, 1)
is.Equal([][]float64{{1.1, 2.2}, {2.2, 3.3}, {3.3, 4.4}}, slices.Collect(result23))
// Test with bool
result24 := Sliding(values(true, false, true, false, true), 3, 2)
is.Equal([][]bool{{true, false, true}, {true, false, true}}, slices.Collect(result24))
// Test size=5, step=3 on collection of 7 elements (only full windows are returned)
result25 := Sliding(values(1, 2, 3, 4, 5, 6, 7), 5, 3)
is.Equal([][]int{{1, 2, 3, 4, 5}}, slices.Collect(result25))
// Test when collection size is exactly size + step - 1 (two windows with overlap)
result26 := Sliding(values(1, 2, 3, 4, 5), 3, 2)
is.Equal([][]int{{1, 2, 3}, {3, 4, 5}}, slices.Collect(result26))
// Test with negative size should panic
is.PanicsWithValue("it.Sliding: size must be greater than 0", func() {
Sliding(values(1, 2, 3), -1, 1)
})
// Test multiple early termination (stop yielding)
result27 := Sliding(values(1, 2, 3, 4, 5, 6), 2, 1)
count := 0
for window := range result27 {
count++
if count == 2 {
break
}
_ = window
}
is.Equal(2, count)
// Test with large size, small step, large collection (only full windows are returned)
result28 := Sliding(values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), 5, 3)
is.Equal([][]int{{1, 2, 3, 4, 5}, {4, 5, 6, 7, 8}, {7, 8, 9, 10, 11}}, slices.Collect(result28))
// Test size equals step equals 1
result29 := Sliding(values(1, 2, 3, 4, 5), 1, 1)
is.Equal([][]int{{1}, {2}, {3}, {4}, {5}}, slices.Collect(result29))
// Test with zero elements in collection
result30 := Sliding(values[int](), 1, 1)
is.Empty(slices.Collect(result30))
// Test when last window is exactly size elements
result31 := Sliding(values(1, 2, 3, 4, 5, 6), 3, 3)
is.Equal([][]int{{1, 2, 3}, {4, 5, 6}}, slices.Collect(result31))
// Test with pointers
x, y, z := 1, 2, 3
result32 := Sliding(values(&x, &y, &z), 2, 1)
collected32 := slices.Collect(result32)
is.Len(collected32, 2)
is.Equal(&x, collected32[0][0])
is.Equal(&y, collected32[0][1])
is.Equal(&y, collected32[1][0])
is.Equal(&z, collected32[1][1])
}
func TestPartitionBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
oddEven := func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
}
result1 := PartitionBy(values(-2, -1, 0, 1, 2, 3, 4, 5), oddEven)
result2 := PartitionBy(values[int](), oddEven)
is.Equal([][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}, result1)
is.Empty(result2)
}
func TestFlatten(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Flatten([]iter.Seq[int]{values(0, 1), values(2, 3, 4, 5)})
is.Equal([]int{0, 1, 2, 3, 4, 5}, slices.Collect(result1))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Flatten([]myStrings{allStrings})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestConcat(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Concat(values(0, 1), values(2, 3, 4, 5))
is.Equal([]int{0, 1, 2, 3, 4, 5}, slices.Collect(result1))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Concat(allStrings, allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestInterleave(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
in []iter.Seq[int]
want []int
}{
{
"empty",
[]iter.Seq[int]{},
nil,
},
{
"empties",
[]iter.Seq[int]{values[int](), values[int]()},
nil,
},
{
"same length",
[]iter.Seq[int]{values(1, 3, 5), values(2, 4, 6)},
[]int{1, 2, 3, 4, 5, 6},
},
{
"different length",
[]iter.Seq[int]{values(1, 3, 5, 6), values(2, 4)},
[]int{1, 2, 3, 4, 5, 6},
},
{
"many sequences",
[]iter.Seq[int]{values(1), values(2, 5, 8), values(3, 6), values(4, 7, 9, 10)},
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
},
}
for _, tc := range testCases {
tc := tc //nolint:modernize
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, slices.Collect(Interleave(tc.in...)))
})
}
}
func TestShuffle(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Shuffle(values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
result2 := Shuffle(values[int]())
slice1 := slices.Collect(result1)
is.NotEqual([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, slice1)
is.ElementsMatch([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, slice1)
is.Empty(slices.Collect(result2))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Shuffle(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestReverse(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Reverse(values(0, 1, 2, 3, 4, 5))
result2 := Reverse(values(0, 1, 2, 3, 4, 5, 6))
result3 := Reverse(values[int]())
is.Equal([]int{5, 4, 3, 2, 1, 0}, slices.Collect(result1))
is.Equal([]int{6, 5, 4, 3, 2, 1, 0}, slices.Collect(result2))
is.Empty(slices.Collect(result3))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Reverse(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFill(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Fill(values(foo{"a"}, foo{"a"}), foo{"b"})
result2 := Fill(values[foo](), foo{"a"})
is.Equal([]foo{{"b"}, {"b"}}, slices.Collect(result1))
is.Empty(slices.Collect(result2))
}
func TestRepeat(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Repeat(2, foo{"a"})
result2 := Repeat(0, foo{"a"})
is.Equal([]foo{{"a"}, {"a"}}, slices.Collect(result1))
is.Empty(slices.Collect(result2))
}
func TestRepeatBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
cb := func(i int) int {
return int(math.Pow(float64(i), 2))
}
result1 := RepeatBy(0, cb)
result2 := RepeatBy(2, cb)
result3 := RepeatBy(5, cb)
is.Empty(slices.Collect(result1))
is.Equal([]int{0, 1}, slices.Collect(result2))
is.Equal([]int{0, 1, 4, 9, 16}, slices.Collect(result3))
}
func TestKeyBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := KeyBy(values("a", "aa", "aaa"), func(str string) int {
return len(str)
})
is.Equal(map[int]string{1: "a", 2: "aa", 3: "aaa"}, result1)
}
func TestAssociate(t *testing.T) {
t.Parallel()
type foo struct {
baz string
bar int
}
transform := func(f *foo) (string, int) {
return f.baz, f.bar
}
testCases := []struct {
in []*foo
want map[string]int
}{
{
in: []*foo{{baz: "apple", bar: 1}},
want: map[string]int{"apple": 1},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}},
want: map[string]int{"apple": 1, "banana": 2},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}},
want: map[string]int{"apple": 2},
},
}
for i, tc := range testCases {
tc := tc //nolint:modernize
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, Associate(slices.Values(tc.in), transform))
})
}
}
func TestAssociateI(t *testing.T) {
t.Parallel()
transform := func(s string, i int) (int, string) {
return i % 2, s
}
testCases := []struct {
in []string
want map[int]string
}{
{
in: []string{"zero"},
want: map[int]string{0: "zero"},
},
{
in: []string{"zero", "one"},
want: map[int]string{0: "zero", 1: "one"},
},
{
in: []string{"two", "one", "zero"},
want: map[int]string{0: "zero", 1: "one"},
},
}
for i, tc := range testCases {
tc := tc //nolint:modernize
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, AssociateI(slices.Values(tc.in), transform))
})
}
}
func TestSeqToMap(t *testing.T) {
t.Parallel()
type foo struct {
baz string
bar int
}
transform := func(f *foo) (string, int) {
return f.baz, f.bar
}
testCases := []struct {
in []*foo
want map[string]int
}{
{
in: []*foo{{baz: "apple", bar: 1}},
want: map[string]int{"apple": 1},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}},
want: map[string]int{"apple": 1, "banana": 2},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}},
want: map[string]int{"apple": 2},
},
}
for i, tc := range testCases {
tc := tc //nolint:modernize
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, SeqToMap(slices.Values(tc.in), transform))
})
}
}
func TestSeqToMapI(t *testing.T) {
t.Parallel()
transform := func(s string, i int) (int, string) {
return i % 2, s
}
testCases := []struct {
in []string
want map[int]string
}{
{
in: []string{"zero"},
want: map[int]string{0: "zero"},
},
{
in: []string{"zero", "one"},
want: map[int]string{0: "zero", 1: "one"},
},
{
in: []string{"two", "one", "zero"},
want: map[int]string{0: "zero", 1: "one"},
},
}
for i, tc := range testCases {
tc := tc //nolint:modernize
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, SeqToMapI(slices.Values(tc.in), transform))
})
}
}
func TestFilterSeqToMap(t *testing.T) {
t.Parallel()
type foo struct {
baz string
bar int
}
transform := func(f *foo) (string, int, bool) {
return f.baz, f.bar, f.bar > 1
}
testCases := []struct {
in []*foo
want map[string]int
}{
{
in: []*foo{{baz: "apple", bar: 1}},
want: map[string]int{},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}},
want: map[string]int{"banana": 2},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}},
want: map[string]int{"apple": 2},
},
}
for i, tc := range testCases {
tc := tc //nolint:modernize
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, FilterSeqToMap(slices.Values(tc.in), transform))
})
}
}
func TestFilterSeqToMapI(t *testing.T) {
t.Parallel()
transform := func(s string, i int) (int, string, bool) {
return i % 5, s, i%2 == 0
}
testCases := []struct {
in []string
want map[int]string
}{
{
in: []string{"zero"},
want: map[int]string{0: "zero"},
},
{
in: []string{"zero", "one", "two", "three", "four"},
want: map[int]string{0: "zero", 2: "two", 4: "four"},
},
{
in: []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"},
want: map[int]string{0: "ten", 1: "six", 2: "two", 3: "eight", 4: "four"},
},
}
for i, tc := range testCases {
tc := tc //nolint:modernize
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, FilterSeqToMapI(slices.Values(tc.in), transform))
})
}
}
func TestKeyify(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Keyify(values(1, 2, 3, 4))
result2 := Keyify(values(1, 1, 1, 2))
result3 := Keyify(values[int]())
is.Equal(map[int]struct{}{1: {}, 2: {}, 3: {}, 4: {}}, result1)
is.Equal(map[int]struct{}{1: {}, 2: {}}, result2)
is.Empty(result3)
}
func TestDrop(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(Drop(values(0, 1, 2, 3, 4), 0)))
is.Equal([]int{1, 2, 3, 4}, slices.Collect(Drop(values(0, 1, 2, 3, 4), 1)))
is.Equal([]int{2, 3, 4}, slices.Collect(Drop(values(0, 1, 2, 3, 4), 2)))
is.Equal([]int{3, 4}, slices.Collect(Drop(values(0, 1, 2, 3, 4), 3)))
is.Equal([]int{4}, slices.Collect(Drop(values(0, 1, 2, 3, 4), 4)))
is.Empty(slices.Collect(Drop(values(0, 1, 2, 3, 4), 5)))
is.Empty(slices.Collect(Drop(values(0, 1, 2, 3, 4), 6)))
is.PanicsWithValue("it.Drop: n must not be negative", func() {
Drop(values(0, 1, 2, 3, 4), -1)
})
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Drop(allStrings, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestDropLast(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(DropLast(values(0, 1, 2, 3, 4), 0)))
is.Equal([]int{0, 1, 2, 3}, slices.Collect(DropLast(values(0, 1, 2, 3, 4), 1)))
is.Equal([]int{0, 1, 2}, slices.Collect(DropLast(values(0, 1, 2, 3, 4), 2)))
is.Equal([]int{0, 1}, slices.Collect(DropLast(values(0, 1, 2, 3, 4), 3)))
is.Equal([]int{0}, slices.Collect(DropLast(values(0, 1, 2, 3, 4), 4)))
is.Empty(slices.Collect(DropLast(values(0, 1, 2, 3, 4), 5)))
is.Empty(slices.Collect(DropLast(values(0, 1, 2, 3, 4), 6)))
is.PanicsWithValue("it.DropLast: n must not be negative", func() {
DropLast(values(0, 1, 2, 3, 4), -1)
})
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := DropLast(allStrings, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestDropWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{4, 5, 6}, slices.Collect(DropWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return t != 4
})))
is.Empty(slices.Collect(DropWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return true
})))
is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, slices.Collect(DropWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return t == 10
})))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := DropWhile(allStrings, func(t string) bool {
return t != "foo"
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestDropLastWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2, 3}, slices.Collect(DropLastWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return t != 3
})))
is.Equal([]int{0, 1}, slices.Collect(DropLastWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return t != 1
})))
is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, slices.Collect(DropLastWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return t == 10
})))
is.Empty(slices.Collect(DropLastWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return t != 10
})))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := DropLastWhile(allStrings, func(t string) bool {
return t != "foo"
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestTake(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2}, slices.Collect(Take(values(0, 1, 2, 3, 4), 3)))
is.Equal([]int{0, 1}, slices.Collect(Take(values(0, 1, 2, 3, 4), 2)))
is.Equal([]int{0}, slices.Collect(Take(values(0, 1, 2, 3, 4), 1)))
is.Empty(slices.Collect(Take(values(0, 1, 2, 3, 4), 0)))
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(Take(values(0, 1, 2, 3, 4), 5)))
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(Take(values(0, 1, 2, 3, 4), 10)))
is.PanicsWithValue("it.Take: n must not be negative", func() {
Take(values(0, 1, 2, 3, 4), -1)
})
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Take(allStrings, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestTakeWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2, 3}, slices.Collect(TakeWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return t != 4
})))
is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, slices.Collect(TakeWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return true
})))
is.Empty(slices.Collect(TakeWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return false
})))
is.Equal([]int{0, 1, 2}, slices.Collect(TakeWhile(values(0, 1, 2, 3, 4, 5, 6), func(t int) bool {
return t < 3
})))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := TakeWhile(allStrings, func(t string) bool {
return t != "bar"
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestTakeFilter(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{2, 4}, slices.Collect(TakeFilter(values(1, 2, 3, 4, 5, 6), 2, func(item int) bool {
return item%2 == 0
})))
is.Empty(slices.Collect(TakeFilter(values(1, 2, 3, 4, 5, 6), 0, func(item int) bool {
return item%2 == 0
})))
is.Empty(slices.Collect(TakeFilter(values(1, 3, 5), 2, func(item int) bool {
return item%2 == 0
})))
is.PanicsWithValue("it.TakeFilterI: n must not be negative", func() {
TakeFilter(values(1, 2, 3), -1, func(item int) bool { return true })
})
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar", "baz"))
nonempty := TakeFilter(allStrings, 2, func(item string) bool {
return item != ""
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestTakeFilterI(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{2, 4}, slices.Collect(TakeFilterI(values(1, 2, 3, 4, 5, 6), 2, func(item, index int) bool {
return item%2 == 0 && index < 4
})))
is.Equal([]int{2, 4, 6}, slices.Collect(TakeFilterI(values(1, 2, 3, 4, 5, 6), 10, func(item, _ int) bool {
return item%2 == 0
})))
is.Empty(slices.Collect(TakeFilterI(values(1, 2, 3, 4, 5, 6), 0, func(item, index int) bool {
return item%2 == 0 && index < 4
})))
is.Empty(slices.Collect(TakeFilterI(values(1, 3, 5), 2, func(item, _ int) bool {
return item%2 == 0
})))
is.Equal([]int{1}, slices.Collect(TakeFilterI(values(1, 2, 3, 4, 5), 1, func(item, _ int) bool {
return item%2 != 0
})))
is.PanicsWithValue("it.TakeFilterI: n must not be negative", func() {
TakeFilterI(values(1, 2, 3), -1, func(item, _ int) bool { return true })
})
}
func TestDropByIndex(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{1, 2, 3, 4}, slices.Collect(DropByIndex(values(0, 1, 2, 3, 4), 0)))
is.Equal([]int{3, 4}, slices.Collect(DropByIndex(values(0, 1, 2, 3, 4), 0, 1, 2)))
is.Equal([]int{2, 4}, slices.Collect(DropByIndex(values(0, 1, 2, 3, 4), 3, 1, 0)))
is.Equal([]int{0, 1, 3, 4}, slices.Collect(DropByIndex(values(0, 1, 2, 3, 4), 2)))
is.Equal([]int{0, 1, 2, 3}, slices.Collect(DropByIndex(values(0, 1, 2, 3, 4), 4)))
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(DropByIndex(values(0, 1, 2, 3, 4), 5)))
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(DropByIndex(values(0, 1, 2, 3, 4), 100)))
is.Empty(slices.Collect(DropByIndex(values[int](), 0, 1)))
is.Empty(slices.Collect(DropByIndex(values(42), 0, 1)))
is.Empty(slices.Collect(DropByIndex(values(42), 1, 0)))
is.Empty(slices.Collect(DropByIndex(values[int](), 1)))
is.Empty(slices.Collect(DropByIndex(values(1), 0)))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := DropByIndex(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestReject(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Reject(values(1, 2, 3, 4), func(x int) bool {
return x%2 == 0
})
is.Equal([]int{1, 3}, slices.Collect(r1))
r2 := Reject(values("Smith", "foo", "Domin", "bar", "Olivia"), func(x string) bool {
return len(x) > 3
})
is.Equal([]string{"foo", "bar"}, slices.Collect(r2))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Reject(allStrings, func(x string) bool {
return len(x) > 0
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestRejectI(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := RejectI(values(1, 2, 3, 4), func(x, _ int) bool {
return x%2 == 0
})
is.Equal([]int{1, 3}, slices.Collect(r1))
r2 := RejectI(values("Smith", "foo", "Domin", "bar", "Olivia"), func(x string, _ int) bool {
return len(x) > 3
})
is.Equal([]string{"foo", "bar"}, slices.Collect(r2))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := RejectI(allStrings, func(x string, _ int) bool {
return len(x) > 0
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestRejectMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := RejectMap(values[int64](1, 2, 3, 4), func(x int64) (string, bool) {
if x%2 == 0 {
return strconv.FormatInt(x, 10), false
}
return "", true
})
r2 := RejectMap(values("cpu", "gpu", "mouse", "keyboard"), func(x string) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", false
}
return "", true
})
is.Equal([]string{"2", "4"}, slices.Collect(r1))
is.Equal([]string{"xpu", "xpu"}, slices.Collect(r2))
}
func TestRejectMapI(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := RejectMapI(values[int64](1, 2, 3, 4), func(x int64, _ int) (string, bool) {
if x%2 == 0 {
return strconv.FormatInt(x, 10), false
}
return "", true
})
r2 := RejectMapI(values("cpu", "gpu", "mouse", "keyboard"), func(x string, _ int) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", false
}
return "", true
})
is.Equal([]string{"2", "4"}, slices.Collect(r1))
is.Equal([]string{"xpu", "xpu"}, slices.Collect(r2))
}
func TestCount(t *testing.T) {
t.Parallel()
is := assert.New(t)
count1 := Count(values(1, 2, 1), 1)
count2 := Count(values(1, 2, 1), 3)
count3 := Count(values[int](), 1)
is.Equal(2, count1)
is.Zero(count2)
is.Zero(count3)
}
func TestCountBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
count1 := CountBy(values(1, 2, 1), func(i int) bool {
return i < 2
})
count2 := CountBy(values(1, 2, 1), func(i int) bool {
return i > 2
})
count3 := CountBy(values[int](), func(i int) bool {
return i <= 2
})
is.Equal(2, count1)
is.Zero(count2)
is.Zero(count3)
}
func TestCountValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Empty(CountValues(values[int]()))
is.Equal(map[int]int{1: 1, 2: 1}, CountValues(values(1, 2)))
is.Equal(map[int]int{1: 1, 2: 2}, CountValues(values(1, 2, 2)))
is.Equal(map[string]int{"": 1, "foo": 1, "bar": 1}, CountValues(values("foo", "bar", "")))
is.Equal(map[string]int{"foo": 1, "bar": 2}, CountValues(values("foo", "bar", "bar")))
}
func TestCountValuesBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
oddEven := func(v int) bool {
return v%2 == 0
}
length := func(v string) int {
return len(v)
}
result1 := CountValuesBy(values[int](), oddEven)
result2 := CountValuesBy(values(1, 2), oddEven)
result3 := CountValuesBy(values(1, 2, 2), oddEven)
result4 := CountValuesBy(values("foo", "bar", ""), length)
result5 := CountValuesBy(values("foo", "bar", "bar"), length)
is.Empty(result1)
is.Equal(map[bool]int{true: 1, false: 1}, result2)
is.Equal(map[bool]int{true: 2, false: 1}, result3)
is.Equal(map[int]int{0: 1, 3: 2}, result4)
is.Equal(map[int]int{3: 3}, result5)
}
func TestSubset(t *testing.T) {
t.Parallel()
is := assert.New(t)
in := values(0, 1, 2, 3, 4)
out1 := Subset(in, 0, 0)
out2 := Subset(in, 10, 2)
out4 := Subset(in, 0, 10)
out5 := Subset(in, 0, 2)
out6 := Subset(in, 2, 2)
out7 := Subset(in, 2, 5)
out8 := Subset(in, 2, 3)
out9 := Subset(in, 2, 4)
is.Empty(slices.Collect(out1))
is.Empty(slices.Collect(out2))
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(out4))
is.Equal([]int{0, 1}, slices.Collect(out5))
is.Equal([]int{2, 3}, slices.Collect(out6))
is.Equal([]int{2, 3, 4}, slices.Collect(out7))
is.Equal([]int{2, 3, 4}, slices.Collect(out8))
is.Equal([]int{2, 3, 4}, slices.Collect(out9))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Subset(allStrings, 0, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestSlice(t *testing.T) {
t.Parallel()
is := assert.New(t)
in := values(0, 1, 2, 3, 4)
out1 := Slice(in, 0, 0)
out2 := Slice(in, 0, 1)
out3 := Slice(in, 0, 5)
out4 := Slice(in, 0, 6)
out5 := Slice(in, 1, 1)
out6 := Slice(in, 1, 5)
out7 := Slice(in, 1, 6)
out8 := Slice(in, 4, 5)
out9 := Slice(in, 5, 5)
out10 := Slice(in, 6, 5)
out11 := Slice(in, 6, 6)
out12 := Slice(in, 1, 0)
out13 := Slice(in, 5, 0)
out14 := Slice(in, 6, 4)
out15 := Slice(in, 6, 7)
is.Empty(slices.Collect(out1))
is.Equal([]int{0}, slices.Collect(out2))
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(out3))
is.Equal([]int{0, 1, 2, 3, 4}, slices.Collect(out4))
is.Empty(slices.Collect(out5))
is.Equal([]int{1, 2, 3, 4}, slices.Collect(out6))
is.Equal([]int{1, 2, 3, 4}, slices.Collect(out7))
is.Equal([]int{4}, slices.Collect(out8))
is.Empty(slices.Collect(out9))
is.Empty(slices.Collect(out10))
is.Empty(slices.Collect(out11))
is.Empty(slices.Collect(out12))
is.Empty(slices.Collect(out13))
is.Empty(slices.Collect(out14))
is.Empty(slices.Collect(out15))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Slice(allStrings, 0, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestReplace(t *testing.T) {
t.Parallel()
is := assert.New(t)
in := values(0, 1, 0, 1, 2, 3, 0)
out1 := Replace(in, 0, 42, 2)
out2 := Replace(in, 0, 42, 1)
out3 := Replace(in, 0, 42, 0)
out4 := Replace(in, 0, 42, -1)
out5 := Replace(in, 0, 42, -1)
out6 := Replace(in, -1, 42, 2)
out7 := Replace(in, -1, 42, 1)
out8 := Replace(in, -1, 42, 0)
out9 := Replace(in, -1, 42, -1)
out10 := Replace(in, -1, 42, -1)
is.Equal([]int{42, 1, 42, 1, 2, 3, 0}, slices.Collect(out1))
is.Equal([]int{42, 1, 42, 1, 2, 3, 0}, slices.Collect(out1)) // check no counter mutation
is.Equal([]int{42, 1, 0, 1, 2, 3, 0}, slices.Collect(out2))
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, slices.Collect(out3))
is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, slices.Collect(out4))
is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, slices.Collect(out5))
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, slices.Collect(out6))
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, slices.Collect(out7))
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, slices.Collect(out8))
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, slices.Collect(out9))
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, slices.Collect(out10))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Replace(allStrings, "0", "2", 1)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestReplaceAll(t *testing.T) {
t.Parallel()
is := assert.New(t)
in := values(0, 1, 0, 1, 2, 3, 0)
out1 := ReplaceAll(in, 0, 42)
out2 := ReplaceAll(in, -1, 42)
is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, slices.Collect(out1))
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, slices.Collect(out2))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := ReplaceAll(allStrings, "0", "2")
is.IsType(nonempty, allStrings, "type preserved")
}
func TestCompact(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Compact(values(2, 0, 4, 0))
is.Equal([]int{2, 4}, slices.Collect(r1))
r2 := Compact(values("", "foo", "", "bar", ""))
is.Equal([]string{"foo", "bar"}, slices.Collect(r2))
r3 := Compact(values(true, false, true, false))
is.Equal([]bool{true, true}, slices.Collect(r3))
type foo struct {
bar int
baz string
}
// sequence of structs
// If all fields of an element are zero values, Compact removes it.
r4 := Compact(values(
foo{bar: 1, baz: "a"}, // all fields are non-zero values
foo{bar: 0, baz: ""}, // all fields are zero values
foo{bar: 2, baz: ""}, // bar is non-zero
))
is.Equal([]foo{{bar: 1, baz: "a"}, {bar: 2, baz: ""}}, slices.Collect(r4))
// sequence of pointers to structs
// If an element is nil, Compact removes it.
e1, e2, e3 := foo{bar: 1, baz: "a"}, foo{bar: 0, baz: ""}, foo{bar: 2, baz: ""}
// NOTE: e2 is a zero value of foo, but its pointer &e2 is not a zero value of *foo.
r5 := Compact(values(&e1, &e2, nil, &e3))
is.Equal([]*foo{&e1, &e2, &e3}, slices.Collect(r5))
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Compact(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestIsSorted(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(IsSorted(values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)))
is.True(IsSorted(values("a", "b", "c", "d", "e", "f", "g", "h", "i", "j")))
is.False(IsSorted(values(0, 1, 4, 3, 2, 5, 6, 7, 8, 9, 10)))
is.False(IsSorted(values("a", "b", "d", "c", "e", "f", "g", "h", "i", "j")))
}
func TestIsSortedBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(IsSortedBy(values("a", "bb", "ccc"), func(s string) int {
return len(s)
}))
is.False(IsSortedBy(values("aa", "b", "ccc"), func(s string) int {
return len(s)
}))
is.True(IsSortedBy(values("1", "2", "3", "11"), func(s string) int {
ret, _ := strconv.Atoi(s)
return ret
}))
}
func TestSplice(t *testing.T) {
t.Parallel()
is := assert.New(t)
sample := values("a", "b", "c", "d", "e", "f", "g")
// normal case
results := slices.Collect(Splice(sample, 1, "1", "2"))
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(sample))
is.Equal([]string{"a", "1", "2", "b", "c", "d", "e", "f", "g"}, results)
// positive overflow
results = slices.Collect(Splice(sample, 42, "1", "2"))
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(sample))
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g", "1", "2"}, results)
// other
is.Equal([]string{"1", "2"}, slices.Collect(Splice(values[string](), 0, "1", "2")))
is.Equal([]string{"1", "2"}, slices.Collect(Splice(values[string](), 1, "1", "2")))
is.Equal([]string{"1", "2", "0"}, slices.Collect(Splice(values("0"), 0, "1", "2")))
is.Equal([]string{"0", "1", "2"}, slices.Collect(Splice(values("0"), 1, "1", "2")))
// type preserved
type myStrings iter.Seq[string]
allStrings := myStrings(values("", "foo", "bar"))
nonempty := Splice(allStrings, 1, "1", "2")
is.IsType(nonempty, allStrings, "type preserved")
}
func TestCutPrefix(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual, result := CutPrefix(values("a", "a", "b"), []string{"a"})
is.True(result)
is.Equal([]string{"a", "b"}, slices.Collect(actual))
actual, result = CutPrefix(values("a", "a", "b"), []string{"a"})
is.True(result)
is.Equal([]string{"a", "b"}, slices.Collect(actual))
actual, result = CutPrefix(values("a", "a", "b"), []string{"b"})
is.False(result)
is.Equal([]string{"a", "a", "b"}, slices.Collect(actual))
actual, result = CutPrefix(values[string](), []string{"b"})
is.False(result)
is.Empty(slices.Collect(actual))
actual, result = CutPrefix(values("a", "a", "b"), []string{})
is.True(result)
is.Equal([]string{"a", "a", "b"}, slices.Collect(actual))
actual, result = CutPrefix(values("a", "a", "b"), []string{"a", "a", "b", "b"})
is.False(result)
is.Equal([]string{"a", "a", "b"}, slices.Collect(actual))
actual, result = CutPrefix(values("a", "a", "b"), []string{"a", "b"})
is.False(result)
is.Equal([]string{"a", "a", "b"}, slices.Collect(actual))
}
func TestCutSuffix(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual, result := CutSuffix(values("a", "a", "b"), []string{"c"})
is.False(result)
is.Equal([]string{"a", "a", "b"}, slices.Collect(actual))
actual, result = CutSuffix(values("a", "a", "b"), []string{"b"})
is.True(result)
is.Equal([]string{"a", "a"}, slices.Collect(actual))
actual, result = CutSuffix(values("a", "a", "b"), []string{})
is.True(result)
is.Equal([]string{"a", "a", "b"}, slices.Collect(actual))
actual, result = CutSuffix(values("a", "a", "b"), []string{"a"})
is.False(result)
is.Equal([]string{"a", "a", "b"}, slices.Collect(actual))
actual, result = CutSuffix(values("a", "a", "b"), []string{})
is.True(result)
is.Equal([]string{"a", "a", "b"}, slices.Collect(actual))
}
func TestTrim(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := Trim(values("a", "b", "c", "d", "e", "f", "g"), "a", "b")
is.Equal([]string{"c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = Trim(values("a", "b", "c", "d", "e", "f", "g"), "g", "f")
is.Equal([]string{"a", "b", "c", "d", "e"}, slices.Collect(actual))
actual = Trim(values("a", "b", "c", "d", "e", "f", "g"), "a", "b", "c", "d", "e", "f", "g")
is.Empty(slices.Collect(actual))
actual = Trim(values("a", "b", "c", "d", "e", "f", "g"), "a", "b", "c", "d", "e", "f", "g", "h")
is.Empty(slices.Collect(actual))
actual = Trim(values("a", "b", "c", "d", "e", "f", "g"))
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
}
func TestTrimFirst(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := TrimFirst(values("a", "a", "b", "c", "d", "e", "f", "g"), "a", "b")
is.Equal([]string{"c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimFirst(values("a", "b", "c", "d", "e", "f", "g"), "b", "a")
is.Equal([]string{"c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimFirst(values("a", "b", "c", "d", "e", "f", "g"), "g", "f")
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimFirst(values("a", "b", "c", "d", "e", "f", "g"), "a", "b", "c", "d", "e", "f", "g")
is.Empty(slices.Collect(actual))
actual = TrimFirst(values("a", "b", "c", "d", "e", "f", "g"), "a", "b", "c", "d", "e", "f", "g", "h")
is.Empty(slices.Collect(actual))
actual = TrimFirst(values("a", "b", "c", "d", "e", "f", "g"))
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
}
func TestTrimPrefix(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := TrimPrefix(values("a", "b", "a", "b", "c", "d", "e", "f", "g"), []string{"a", "b"})
is.Equal([]string{"c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimPrefix(values("a", "b", "c", "d", "e", "f", "g"), []string{"b", "a"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimPrefix(values("a", "b", "c", "d", "e", "f", "g"), []string{"g", "f"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimPrefix(values("a", "b", "c", "d", "e", "f", "g"), []string{"a", "b", "c", "d", "e", "f", "g"})
is.Empty(slices.Collect(actual))
actual = TrimPrefix(values("a", "b", "c", "d", "e", "f", "g"), []string{"a", "b", "c", "d", "e", "f", "g", "h"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimPrefix(values("a", "b", "c", "d", "e", "f", "g"), []string{})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
}
func TestTrimLast(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := TrimLast(values("a", "b", "c", "d", "e", "f", "g"), "a", "b")
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimLast(values("a", "b", "c", "d", "e", "f", "g", "g"), "g", "f")
is.Equal([]string{"a", "b", "c", "d", "e"}, slices.Collect(actual))
actual = TrimLast(values("a", "b", "c", "d", "e", "f", "g"), "a", "b", "c", "d", "e", "f", "g")
is.Empty(slices.Collect(actual))
actual = TrimLast(values("a", "b", "c", "d", "e", "f", "g"), "a", "b", "c", "d", "e", "f", "g", "h")
is.Empty(slices.Collect(actual))
actual = TrimLast(values("a", "b", "c", "d", "e", "f", "g"))
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
}
func TestTrimSuffix(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := TrimSuffix(values("a", "b", "c", "d", "e", "f", "g"), []string{"a", "b"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimSuffix(values("a", "b", "c", "d", "e", "f", "g", "f", "g"), []string{"f", "g"})
is.Equal([]string{"a", "b", "c", "d", "e"}, slices.Collect(actual))
actual = TrimSuffix(values("a", "b", "c", "d", "e", "f", "g", "f", "g"), []string{"g", "f"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g", "f", "g"}, slices.Collect(actual))
actual = TrimSuffix(values("a", "b", "c", "d", "e", "f", "f", "g"), []string{"f", "g"})
is.Equal([]string{"a", "b", "c", "d", "e", "f"}, slices.Collect(actual))
actual = TrimSuffix(values("a", "b", "c", "d", "e", "f", "g"), []string{"a", "b", "c", "d", "e", "f", "g"})
is.Empty(slices.Collect(actual))
actual = TrimSuffix(values("a", "b", "c", "d", "e", "f", "g"), []string{"a", "b", "c", "d", "e", "f", "g", "h"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
actual = TrimSuffix(values("a", "b", "c", "d", "e", "f", "g"), []string{})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, slices.Collect(actual))
}
func TestBuffer(t *testing.T) {
t.Parallel()
is := assert.New(t)
// full batches
batches := slices.Collect(Buffer(RangeFrom(1, 6), 2))
is.Equal([][]int{{1, 2}, {3, 4}, {5, 6}}, batches)
// partial last batch
batches2 := slices.Collect(Buffer(RangeFrom(1, 5), 2))
is.Equal([][]int{{1, 2}, {3, 4}, {5}}, batches2)
// empty channel
batches3 := slices.Collect(Buffer(RangeFrom(1, 0), 2))
is.Empty(batches3)
// stop after first batch (early termination)
batches4 := slices.Collect(Take(Buffer(RangeFrom(1, 6), 2), 1))
is.Equal([][]int{{1, 2}}, batches4)
}
================================================
FILE: it/string.go
================================================
//go:build go1.23
package it
import "iter"
// ChunkString returns a sequence of strings split into groups of length size. If the string can't be split evenly,
// the final chunk will be the remaining characters.
// Play: https://go.dev/play/p/Bcc5ixTQQoQ
//
// Note: it.ChunkString and it.Chunk functions behave inconsistently for empty input: it.ChunkString("", n) returns [""] instead of [].
// See https://github.com/samber/lo/issues/788
func ChunkString[T ~string](str T, size int) iter.Seq[T] {
if size <= 0 {
panic("it.ChunkString: size must be greater than 0")
}
return func(yield func(T) bool) {
if len(str) == 0 || size >= len(str) {
yield(str)
return
}
currentLen := 0
currentStart := 0
for i := range str {
if currentLen == size {
if !yield(str[currentStart:i]) {
return
}
currentLen = 0
currentStart = i
}
currentLen++
}
yield(str[currentStart:])
}
}
================================================
FILE: it/string_example_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"slices"
)
func ExampleChunkString() {
result1 := ChunkString("123456", 2)
result2 := ChunkString("1234567", 2)
result3 := ChunkString("", 2)
result4 := ChunkString("1", 2)
fmt.Printf("%v\n", slices.Collect(result1))
fmt.Printf("%v\n", slices.Collect(result2))
fmt.Printf("%v\n", slices.Collect(result3))
fmt.Printf("%v\n", slices.Collect(result4))
// Output:
// [12 34 56]
// [12 34 56 7]
// []
// [1]
}
================================================
FILE: it/string_test.go
================================================
//go:build go1.23
package it
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
)
func TestChunkString(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ChunkString("12345", 2)
is.Equal([]string{"12", "34", "5"}, slices.Collect(result1))
result2 := ChunkString("123456", 2)
is.Equal([]string{"12", "34", "56"}, slices.Collect(result2))
result3 := ChunkString("123456", 6)
is.Equal([]string{"123456"}, slices.Collect(result3))
result4 := ChunkString("123456", 10)
is.Equal([]string{"123456"}, slices.Collect(result4))
result5 := ChunkString("", 2)
is.Equal([]string{""}, slices.Collect(result5)) // @TODO: should be [] - see https://github.com/samber/lo/issues/788
result6 := ChunkString("明1好休2林森", 2)
is.Equal([]string{"明1", "好休", "2林", "森"}, slices.Collect(result6))
is.PanicsWithValue("it.ChunkString: size must be greater than 0", func() {
ChunkString("12345", 0)
})
}
================================================
FILE: it/tuples.go
================================================
//go:build go1.23
package it
import (
"iter"
"github.com/samber/lo"
)
// Zip2 creates a sequence of grouped elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/U5nBWvR8eUZ
func Zip2[A, B any](a iter.Seq[A], b iter.Seq[B]) iter.Seq[lo.Tuple2[A, B]] {
return func(yield func(lo.Tuple2[A, B]) bool) {
nextA, stopA := iter.Pull(a)
defer stopA()
nextB, stopB := iter.Pull(b)
defer stopB()
for {
var item lo.Tuple2[A, B]
var ok [2]bool
item.A, ok[0] = nextA()
item.B, ok[1] = nextB()
if ok == [2]bool{} || !yield(item) {
return
}
}
}
}
// Zip3 creates a sequence of grouped elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/V5wL9xY8nQr
func Zip3[A, B, C any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C]) iter.Seq[lo.Tuple3[A, B, C]] {
return func(yield func(lo.Tuple3[A, B, C]) bool) {
nextA, stopA := iter.Pull(a)
defer stopA()
nextB, stopB := iter.Pull(b)
defer stopB()
nextC, stopC := iter.Pull(c)
defer stopC()
for {
var item lo.Tuple3[A, B, C]
var ok [3]bool
item.A, ok[0] = nextA()
item.B, ok[1] = nextB()
item.C, ok[2] = nextC()
if ok == [3]bool{} || !yield(item) {
return
}
}
}
}
// Zip4 creates a sequence of grouped elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/W6xM7zZ9oSt
func Zip4[A, B, C, D any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D]) iter.Seq[lo.Tuple4[A, B, C, D]] {
return func(yield func(lo.Tuple4[A, B, C, D]) bool) {
nextA, stopA := iter.Pull(a)
defer stopA()
nextB, stopB := iter.Pull(b)
defer stopB()
nextC, stopC := iter.Pull(c)
defer stopC()
nextD, stopD := iter.Pull(d)
defer stopD()
for {
var item lo.Tuple4[A, B, C, D]
var ok [4]bool
item.A, ok[0] = nextA()
item.B, ok[1] = nextB()
item.C, ok[2] = nextC()
item.D, ok[3] = nextD()
if ok == [4]bool{} || !yield(item) {
return
}
}
}
}
// Zip5 creates a sequence of grouped elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/X7yN8aA1pUv
func Zip5[A, B, C, D, E any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E]) iter.Seq[lo.Tuple5[A, B, C, D, E]] {
return func(yield func(lo.Tuple5[A, B, C, D, E]) bool) {
nextA, stopA := iter.Pull(a)
defer stopA()
nextB, stopB := iter.Pull(b)
defer stopB()
nextC, stopC := iter.Pull(c)
defer stopC()
nextD, stopD := iter.Pull(d)
defer stopD()
nextE, stopE := iter.Pull(e)
defer stopE()
for {
var item lo.Tuple5[A, B, C, D, E]
var ok [5]bool
item.A, ok[0] = nextA()
item.B, ok[1] = nextB()
item.C, ok[2] = nextC()
item.D, ok[3] = nextD()
item.E, ok[4] = nextE()
if ok == [5]bool{} || !yield(item) {
return
}
}
}
}
// Zip6 creates a sequence of grouped elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/Y4mN8bB2cXw
func Zip6[A, B, C, D, E, F any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], f iter.Seq[F]) iter.Seq[lo.Tuple6[A, B, C, D, E, F]] {
return func(yield func(lo.Tuple6[A, B, C, D, E, F]) bool) {
nextA, stopA := iter.Pull(a)
defer stopA()
nextB, stopB := iter.Pull(b)
defer stopB()
nextC, stopC := iter.Pull(c)
defer stopC()
nextD, stopD := iter.Pull(d)
defer stopD()
nextE, stopE := iter.Pull(e)
defer stopE()
nextF, stopF := iter.Pull(f)
defer stopF()
for {
var item lo.Tuple6[A, B, C, D, E, F]
var ok [6]bool
item.A, ok[0] = nextA()
item.B, ok[1] = nextB()
item.C, ok[2] = nextC()
item.D, ok[3] = nextD()
item.E, ok[4] = nextE()
item.F, ok[5] = nextF()
if ok == [6]bool{} || !yield(item) {
return
}
}
}
}
// Zip7 creates a sequence of grouped elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/Z9nA8cC3dXw
func Zip7[A, B, C, D, E, F, G any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], f iter.Seq[F], g iter.Seq[G]) iter.Seq[lo.Tuple7[A, B, C, D, E, F, G]] {
return func(yield func(lo.Tuple7[A, B, C, D, E, F, G]) bool) {
nextA, stopA := iter.Pull(a)
defer stopA()
nextB, stopB := iter.Pull(b)
defer stopB()
nextC, stopC := iter.Pull(c)
defer stopC()
nextD, stopD := iter.Pull(d)
defer stopD()
nextE, stopE := iter.Pull(e)
defer stopE()
nextF, stopF := iter.Pull(f)
defer stopF()
nextG, stopG := iter.Pull(g)
defer stopG()
for {
var item lo.Tuple7[A, B, C, D, E, F, G]
var ok [7]bool
item.A, ok[0] = nextA()
item.B, ok[1] = nextB()
item.C, ok[2] = nextC()
item.D, ok[3] = nextD()
item.E, ok[4] = nextE()
item.F, ok[5] = nextF()
item.G, ok[6] = nextG()
if ok == [7]bool{} || !yield(item) {
return
}
}
}
}
// Zip8 creates a sequence of grouped elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/0XrQKOk-vw
func Zip8[A, B, C, D, E, F, G, H any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], f iter.Seq[F], g iter.Seq[G], h iter.Seq[H]) iter.Seq[lo.Tuple8[A, B, C, D, E, F, G, H]] {
return func(yield func(lo.Tuple8[A, B, C, D, E, F, G, H]) bool) {
nextA, stopA := iter.Pull(a)
defer stopA()
nextB, stopB := iter.Pull(b)
defer stopB()
nextC, stopC := iter.Pull(c)
defer stopC()
nextD, stopD := iter.Pull(d)
defer stopD()
nextE, stopE := iter.Pull(e)
defer stopE()
nextF, stopF := iter.Pull(f)
defer stopF()
nextG, stopG := iter.Pull(g)
defer stopG()
nextH, stopH := iter.Pull(h)
defer stopH()
for {
var item lo.Tuple8[A, B, C, D, E, F, G, H]
var ok [8]bool
item.A, ok[0] = nextA()
item.B, ok[1] = nextB()
item.C, ok[2] = nextC()
item.D, ok[3] = nextD()
item.E, ok[4] = nextE()
item.F, ok[5] = nextF()
item.G, ok[6] = nextG()
item.H, ok[7] = nextH()
if ok == [8]bool{} || !yield(item) {
return
}
}
}
}
// Zip9 creates a sequence of grouped elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/1SmFJ5-zr
func Zip9[A, B, C, D, E, F, G, H, I any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], f iter.Seq[F], g iter.Seq[G], h iter.Seq[H], i iter.Seq[I]) iter.Seq[lo.Tuple9[A, B, C, D, E, F, G, H, I]] {
return func(yield func(lo.Tuple9[A, B, C, D, E, F, G, H, I]) bool) {
nextA, stopA := iter.Pull(a)
defer stopA()
nextB, stopB := iter.Pull(b)
defer stopB()
nextC, stopC := iter.Pull(c)
defer stopC()
nextD, stopD := iter.Pull(d)
defer stopD()
nextE, stopE := iter.Pull(e)
defer stopE()
nextF, stopF := iter.Pull(f)
defer stopF()
nextG, stopG := iter.Pull(g)
defer stopG()
nextH, stopH := iter.Pull(h)
defer stopH()
nextI, stopI := iter.Pull(i)
defer stopI()
for {
var item lo.Tuple9[A, B, C, D, E, F, G, H, I]
var ok [9]bool
item.A, ok[0] = nextA()
item.B, ok[1] = nextB()
item.C, ok[2] = nextC()
item.D, ok[3] = nextD()
item.E, ok[4] = nextE()
item.F, ok[5] = nextF()
item.G, ok[6] = nextG()
item.H, ok[7] = nextH()
item.I, ok[8] = nextI()
if ok == [9]bool{} || !yield(item) {
return
}
}
}
}
// ZipBy2 creates a sequence of transformed elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/y03uqMEAi1E
func ZipBy2[A, B, Out any](a iter.Seq[A], b iter.Seq[B], transform func(a A, b B) Out) iter.Seq[Out] {
return Map(Zip2(a, b), func(item lo.Tuple2[A, B]) Out {
return transform(item.A, item.B)
})
}
// ZipBy3 creates a sequence of transformed elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/3UoHL7-zt
func ZipBy3[A, B, C, Out any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], transform func(a A, b B, c C) Out) iter.Seq[Out] {
return Map(Zip3(a, b, c), func(item lo.Tuple3[A, B, C]) Out {
return transform(item.A, item.B, item.C)
})
}
// ZipBy4 creates a sequence of transformed elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/4VpIM8-zu
func ZipBy4[A, B, C, D, Out any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], transform func(a A, b B, c C, d D) Out) iter.Seq[Out] {
return Map(Zip4(a, b, c, d), func(item lo.Tuple4[A, B, C, D]) Out {
return transform(item.A, item.B, item.C, item.D)
})
}
// ZipBy5 creates a sequence of transformed elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/5WqJN9-zv
func ZipBy5[A, B, C, D, E, Out any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], transform func(a A, b B, c C, d D, e E) Out) iter.Seq[Out] {
return Map(Zip5(a, b, c, d, e), func(item lo.Tuple5[A, B, C, D, E]) Out {
return transform(item.A, item.B, item.C, item.D, item.E)
})
}
// ZipBy6 creates a sequence of transformed elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/6XrKO0-zw
func ZipBy6[A, B, C, D, E, F, Out any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], f iter.Seq[F], transform func(a A, b B, c C, d D, e E, f F) Out) iter.Seq[Out] {
return Map(Zip6(a, b, c, d, e, f), func(item lo.Tuple6[A, B, C, D, E, F]) Out {
return transform(item.A, item.B, item.C, item.D, item.E, item.F)
})
}
// ZipBy7 creates a sequence of transformed elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/7YsLP1-zx
func ZipBy7[A, B, C, D, E, F, G, Out any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], f iter.Seq[F], g iter.Seq[G], transform func(a A, b B, c C, d D, e E, f F, g G) Out) iter.Seq[Out] {
return Map(Zip7(a, b, c, d, e, f, g), func(item lo.Tuple7[A, B, C, D, E, F, G]) Out {
return transform(item.A, item.B, item.C, item.D, item.E, item.F, item.G)
})
}
// ZipBy8 creates a sequence of transformed elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/8isgTsyfL-t
func ZipBy8[A, B, C, D, E, F, G, H, Out any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], f iter.Seq[F], g iter.Seq[G], h iter.Seq[H], transform func(a A, b B, c C, d D, e E, f F, g G, h H) Out) iter.Seq[Out] {
return Map(Zip8(a, b, c, d, e, f, g, h), func(item lo.Tuple8[A, B, C, D, E, F, G, H]) Out {
return transform(item.A, item.B, item.C, item.D, item.E, item.F, item.G, item.H)
})
}
// ZipBy9 creates a sequence of transformed elements, the first of which contains the first elements
// of the given sequences, the second of which contains the second elements of the given sequences, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/9jthUzgF-u
func ZipBy9[A, B, C, D, E, F, G, H, I, Out any](a iter.Seq[A], b iter.Seq[B], c iter.Seq[C], d iter.Seq[D], e iter.Seq[E], f iter.Seq[F], g iter.Seq[G], h iter.Seq[H], i iter.Seq[I], transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) iter.Seq[Out] {
return Map(Zip9(a, b, c, d, e, f, g, h, i), func(item lo.Tuple9[A, B, C, D, E, F, G, H, I]) Out {
return transform(item.A, item.B, item.C, item.D, item.E, item.F, item.G, item.H, item.I)
})
}
// CrossJoin2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/OFe8xjZFjWU
func CrossJoin2[A, B any](listA iter.Seq[A], listB iter.Seq[B]) iter.Seq[lo.Tuple2[A, B]] {
return CrossJoinBy2(listA, listB, lo.T2[A, B])
}
// CrossJoin3 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/0XrQKOk-vw
func CrossJoin3[A, B, C any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C]) iter.Seq[lo.Tuple3[A, B, C]] {
return CrossJoinBy3(listA, listB, listC, lo.T3[A, B, C])
}
// CrossJoin4 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/1SmFJ5-zr
func CrossJoin4[A, B, C, D any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D]) iter.Seq[lo.Tuple4[A, B, C, D]] {
return CrossJoinBy4(listA, listB, listC, listD, lo.T4[A, B, C, D])
}
// CrossJoin5 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/2TnGK6-zs
func CrossJoin5[A, B, C, D, E any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E]) iter.Seq[lo.Tuple5[A, B, C, D, E]] {
return CrossJoinBy5(listA, listB, listC, listD, listE, lo.T5[A, B, C, D, E])
}
// CrossJoin6 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3UoHL7-zt
func CrossJoin6[A, B, C, D, E, F any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], listF iter.Seq[F]) iter.Seq[lo.Tuple6[A, B, C, D, E, F]] {
return CrossJoinBy6(listA, listB, listC, listD, listE, listF, lo.T6[A, B, C, D, E, F])
}
// CrossJoin7 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/4VpIM8-zu
func CrossJoin7[A, B, C, D, E, F, G any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], listF iter.Seq[F], listG iter.Seq[G]) iter.Seq[lo.Tuple7[A, B, C, D, E, F, G]] {
return CrossJoinBy7(listA, listB, listC, listD, listE, listF, listG, lo.T7[A, B, C, D, E, F, G])
}
// CrossJoin8 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/5WqJN9-zv
func CrossJoin8[A, B, C, D, E, F, G, H any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], listF iter.Seq[F], listG iter.Seq[G], listH iter.Seq[H]) iter.Seq[lo.Tuple8[A, B, C, D, E, F, G, H]] {
return CrossJoinBy8(listA, listB, listC, listD, listE, listF, listG, listH, lo.T8[A, B, C, D, E, F, G, H])
}
// CrossJoin9 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/6XrKO0-zw
func CrossJoin9[A, B, C, D, E, F, G, H, I any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], listF iter.Seq[F], listG iter.Seq[G], listH iter.Seq[H], listI iter.Seq[I]) iter.Seq[lo.Tuple9[A, B, C, D, E, F, G, H, I]] {
return CrossJoinBy9(listA, listB, listC, listD, listE, listF, listG, listH, listI, lo.T9[A, B, C, D, E, F, G, H, I])
}
// CrossJoinBy2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/6QGp3W-bQU1
func CrossJoinBy2[A, B, Out any](listA iter.Seq[A], listB iter.Seq[B], transform func(a A, b B) Out) iter.Seq[Out] {
return func(yield func(Out) bool) {
for a := range listA {
for b := range listB {
if !yield(transform(a, b)) {
return
}
}
}
}
}
// CrossJoinBy3 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/8isgTsyfL-t
func CrossJoinBy3[A, B, C, Out any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], transform func(a A, b B, c C) Out) iter.Seq[Out] {
return func(yield func(Out) bool) {
for a := range listA {
for b := range listB {
for c := range listC {
if !yield(transform(a, b, c)) {
return
}
}
}
}
}
}
// CrossJoinBy4 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/9jthUzgF-u
func CrossJoinBy4[A, B, C, D, Out any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], transform func(a A, b B, c C, d D) Out) iter.Seq[Out] {
return func(yield func(Out) bool) {
for a := range listA {
for b := range listB {
for c := range listC {
for d := range listD {
if !yield(transform(a, b, c, d)) {
return
}
}
}
}
}
}
}
// CrossJoinBy5 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/0XrQKOk-vw
func CrossJoinBy5[A, B, C, D, E, Out any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], transform func(a A, b B, c C, d D, e E) Out) iter.Seq[Out] {
return func(yield func(Out) bool) {
for a := range listA {
for b := range listB {
for c := range listC {
for d := range listD {
for e := range listE {
if !yield(transform(a, b, c, d, e)) {
return
}
}
}
}
}
}
}
}
// CrossJoinBy6 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/1SmFJ5-zr
func CrossJoinBy6[A, B, C, D, E, F, Out any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], listF iter.Seq[F], transform func(a A, b B, c C, d D, e E, f F) Out) iter.Seq[Out] {
return func(yield func(Out) bool) {
for a := range listA {
for b := range listB {
for c := range listC {
for d := range listD {
for e := range listE {
for f := range listF {
if !yield(transform(a, b, c, d, e, f)) {
return
}
}
}
}
}
}
}
}
}
// CrossJoinBy7 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/2TnGK6-zs
func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], listF iter.Seq[F], listG iter.Seq[G], transform func(a A, b B, c C, d D, e E, f F, g G) Out) iter.Seq[Out] {
return func(yield func(Out) bool) {
for a := range listA {
for b := range listB {
for c := range listC {
for d := range listD {
for e := range listE {
for f := range listF {
for g := range listG {
if !yield(transform(a, b, c, d, e, f, g)) {
return
}
}
}
}
}
}
}
}
}
}
// CrossJoinBy8 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3UoHL7-zt
func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], listF iter.Seq[F], listG iter.Seq[G], listH iter.Seq[H], transform func(a A, b B, c C, d D, e E, f F, g G, h H) Out) iter.Seq[Out] {
return func(yield func(Out) bool) {
for a := range listA {
for b := range listB {
for c := range listC {
for d := range listD {
for e := range listE {
for f := range listF {
for g := range listG {
for h := range listH {
if !yield(transform(a, b, c, d, e, f, g, h)) {
return
}
}
}
}
}
}
}
}
}
}
}
// CrossJoinBy9 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/4VpIM8-zu
func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA iter.Seq[A], listB iter.Seq[B], listC iter.Seq[C], listD iter.Seq[D], listE iter.Seq[E], listF iter.Seq[F], listG iter.Seq[G], listH iter.Seq[H], listI iter.Seq[I], transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) iter.Seq[Out] {
return func(yield func(Out) bool) {
for a := range listA {
for b := range listB {
for c := range listC {
for d := range listD {
for e := range listE {
for f := range listF {
for g := range listG {
for h := range listH {
for i := range listI {
if !yield(transform(a, b, c, d, e, f, g, h, i)) {
return
}
}
}
}
}
}
}
}
}
}
}
}
================================================
FILE: it/tuples_example_test.go
================================================
//go:build go1.23
package it
import (
"fmt"
"slices"
)
func ExampleZip2() {
result := Zip2(values("hello"), values(2))
fmt.Printf("%v", slices.Collect(result))
// Output: [{hello 2}]
}
func ExampleZip3() {
result := Zip3(values("hello"), values(2), values(true))
fmt.Printf("%v", slices.Collect(result))
// Output: [{hello 2 true}]
}
func ExampleZip4() {
result := Zip4(values("hello"), values(2), values(true), values(foo{bar: "bar"}))
fmt.Printf("%v", slices.Collect(result))
// Output: [{hello 2 true {bar}}]
}
func ExampleZip5() {
result := Zip5(values("hello"), values(2), values(true), values(foo{bar: "bar"}), values(4.2))
fmt.Printf("%v", slices.Collect(result))
// Output: [{hello 2 true {bar} 4.2}]
}
func ExampleZip6() {
result := Zip6(values("hello"), values(2), values(true), values(foo{bar: "bar"}), values(4.2), values("plop"))
fmt.Printf("%v", slices.Collect(result))
// Output: [{hello 2 true {bar} 4.2 plop}]
}
func ExampleZip7() {
result := Zip7(values("hello"), values(2), values(true), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false))
fmt.Printf("%v", slices.Collect(result))
// Output: [{hello 2 true {bar} 4.2 plop false}]
}
func ExampleZip8() {
result := Zip8(values("hello"), values(2), values(true), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false), values(42))
fmt.Printf("%v", slices.Collect(result))
// Output: [{hello 2 true {bar} 4.2 plop false 42}]
}
func ExampleZip9() {
result := Zip9(values("hello"), values(2), values(true), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false), values(42), values("hello world"))
fmt.Printf("%v", slices.Collect(result))
// Output: [{hello 2 true {bar} 4.2 plop false 42 hello world}]
}
func ExampleCrossJoin2() {
result := CrossJoin2(values("a", "b"), values(1, 2, 3, 4))
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1}
// {a 2}
// {a 3}
// {a 4}
// {b 1}
// {b 2}
// {b 3}
// {b 4}
}
func ExampleCrossJoin3() {
result := CrossJoin3(values("a", "b"), values(1, 2, 3, 4), values(true, false))
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true}
// {a 1 false}
// {a 2 true}
// {a 2 false}
// {a 3 true}
// {a 3 false}
// {a 4 true}
// {a 4 false}
// {b 1 true}
// {b 1 false}
// {b 2 true}
// {b 2 false}
// {b 3 true}
// {b 3 false}
// {b 4 true}
// {b 4 false}
}
func ExampleCrossJoin4() {
result := CrossJoin4(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}))
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar}}
// {a 1 false {bar}}
// {a 2 true {bar}}
// {a 2 false {bar}}
// {a 3 true {bar}}
// {a 3 false {bar}}
// {a 4 true {bar}}
// {a 4 false {bar}}
// {b 1 true {bar}}
// {b 1 false {bar}}
// {b 2 true {bar}}
// {b 2 false {bar}}
// {b 3 true {bar}}
// {b 3 false {bar}}
// {b 4 true {bar}}
// {b 4 false {bar}}
}
func ExampleCrossJoin5() {
result := CrossJoin5(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2))
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2}
// {a 1 false {bar} 4.2}
// {a 2 true {bar} 4.2}
// {a 2 false {bar} 4.2}
// {a 3 true {bar} 4.2}
// {a 3 false {bar} 4.2}
// {a 4 true {bar} 4.2}
// {a 4 false {bar} 4.2}
// {b 1 true {bar} 4.2}
// {b 1 false {bar} 4.2}
// {b 2 true {bar} 4.2}
// {b 2 false {bar} 4.2}
// {b 3 true {bar} 4.2}
// {b 3 false {bar} 4.2}
// {b 4 true {bar} 4.2}
// {b 4 false {bar} 4.2}
}
func ExampleCrossJoin6() {
result := CrossJoin6(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), values("plop"))
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2 plop}
// {a 1 false {bar} 4.2 plop}
// {a 2 true {bar} 4.2 plop}
// {a 2 false {bar} 4.2 plop}
// {a 3 true {bar} 4.2 plop}
// {a 3 false {bar} 4.2 plop}
// {a 4 true {bar} 4.2 plop}
// {a 4 false {bar} 4.2 plop}
// {b 1 true {bar} 4.2 plop}
// {b 1 false {bar} 4.2 plop}
// {b 2 true {bar} 4.2 plop}
// {b 2 false {bar} 4.2 plop}
// {b 3 true {bar} 4.2 plop}
// {b 3 false {bar} 4.2 plop}
// {b 4 true {bar} 4.2 plop}
// {b 4 false {bar} 4.2 plop}
}
func ExampleCrossJoin7() {
result := CrossJoin7(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false))
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2 plop false}
// {a 1 false {bar} 4.2 plop false}
// {a 2 true {bar} 4.2 plop false}
// {a 2 false {bar} 4.2 plop false}
// {a 3 true {bar} 4.2 plop false}
// {a 3 false {bar} 4.2 plop false}
// {a 4 true {bar} 4.2 plop false}
// {a 4 false {bar} 4.2 plop false}
// {b 1 true {bar} 4.2 plop false}
// {b 1 false {bar} 4.2 plop false}
// {b 2 true {bar} 4.2 plop false}
// {b 2 false {bar} 4.2 plop false}
// {b 3 true {bar} 4.2 plop false}
// {b 3 false {bar} 4.2 plop false}
// {b 4 true {bar} 4.2 plop false}
// {b 4 false {bar} 4.2 plop false}
}
func ExampleCrossJoin8() {
result := CrossJoin8(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false), values(42))
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2 plop false 42}
// {a 1 false {bar} 4.2 plop false 42}
// {a 2 true {bar} 4.2 plop false 42}
// {a 2 false {bar} 4.2 plop false 42}
// {a 3 true {bar} 4.2 plop false 42}
// {a 3 false {bar} 4.2 plop false 42}
// {a 4 true {bar} 4.2 plop false 42}
// {a 4 false {bar} 4.2 plop false 42}
// {b 1 true {bar} 4.2 plop false 42}
// {b 1 false {bar} 4.2 plop false 42}
// {b 2 true {bar} 4.2 plop false 42}
// {b 2 false {bar} 4.2 plop false 42}
// {b 3 true {bar} 4.2 plop false 42}
// {b 3 false {bar} 4.2 plop false 42}
// {b 4 true {bar} 4.2 plop false 42}
// {b 4 false {bar} 4.2 plop false 42}
}
func ExampleCrossJoin9() {
result := CrossJoin9(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false), values(42), values("hello world"))
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2 plop false 42 hello world}
// {a 1 false {bar} 4.2 plop false 42 hello world}
// {a 2 true {bar} 4.2 plop false 42 hello world}
// {a 2 false {bar} 4.2 plop false 42 hello world}
// {a 3 true {bar} 4.2 plop false 42 hello world}
// {a 3 false {bar} 4.2 plop false 42 hello world}
// {a 4 true {bar} 4.2 plop false 42 hello world}
// {a 4 false {bar} 4.2 plop false 42 hello world}
// {b 1 true {bar} 4.2 plop false 42 hello world}
// {b 1 false {bar} 4.2 plop false 42 hello world}
// {b 2 true {bar} 4.2 plop false 42 hello world}
// {b 2 false {bar} 4.2 plop false 42 hello world}
// {b 3 true {bar} 4.2 plop false 42 hello world}
// {b 3 false {bar} 4.2 plop false 42 hello world}
// {b 4 true {bar} 4.2 plop false 42 hello world}
// {b 4 false {bar} 4.2 plop false 42 hello world}
}
func ExampleCrossJoinBy2() {
result := CrossJoinBy2(values("a", "b"), values(1, 2, 3, 4), func(a string, b int) string {
return fmt.Sprintf("%v-%v", a, b)
})
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1
// a-2
// a-3
// a-4
// b-1
// b-2
// b-3
// b-4
}
func ExampleCrossJoinBy3() {
result := CrossJoinBy3(values("a", "b"), values(1, 2, 3, 4), values(true, false), func(a string, b int, c bool) string {
return fmt.Sprintf("%v-%v-%v", a, b, c)
})
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true
// a-1-false
// a-2-true
// a-2-false
// a-3-true
// a-3-false
// a-4-true
// a-4-false
// b-1-true
// b-1-false
// b-2-true
// b-2-false
// b-3-true
// b-3-false
// b-4-true
// b-4-false
}
func ExampleCrossJoinBy4() {
result := CrossJoinBy4(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), func(a string, b int, c bool, d foo) string {
return fmt.Sprintf("%v-%v-%v-%v", a, b, c, d)
})
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}
// a-1-false-{bar}
// a-2-true-{bar}
// a-2-false-{bar}
// a-3-true-{bar}
// a-3-false-{bar}
// a-4-true-{bar}
// a-4-false-{bar}
// b-1-true-{bar}
// b-1-false-{bar}
// b-2-true-{bar}
// b-2-false-{bar}
// b-3-true-{bar}
// b-3-false-{bar}
// b-4-true-{bar}
// b-4-false-{bar}
}
func ExampleCrossJoinBy5() {
result := CrossJoinBy5(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), func(a string, b int, c bool, d foo, e float64) string {
return fmt.Sprintf("%v-%v-%v-%v-%v", a, b, c, d, e)
})
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2
// a-1-false-{bar}-4.2
// a-2-true-{bar}-4.2
// a-2-false-{bar}-4.2
// a-3-true-{bar}-4.2
// a-3-false-{bar}-4.2
// a-4-true-{bar}-4.2
// a-4-false-{bar}-4.2
// b-1-true-{bar}-4.2
// b-1-false-{bar}-4.2
// b-2-true-{bar}-4.2
// b-2-false-{bar}-4.2
// b-3-true-{bar}-4.2
// b-3-false-{bar}-4.2
// b-4-true-{bar}-4.2
// b-4-false-{bar}-4.2
}
func ExampleCrossJoinBy6() {
result := CrossJoinBy6(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), values("plop"), func(a string, b int, c bool, d foo, e float64, f string) string {
return fmt.Sprintf("%v-%v-%v-%v-%v-%v", a, b, c, d, e, f)
})
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2-plop
// a-1-false-{bar}-4.2-plop
// a-2-true-{bar}-4.2-plop
// a-2-false-{bar}-4.2-plop
// a-3-true-{bar}-4.2-plop
// a-3-false-{bar}-4.2-plop
// a-4-true-{bar}-4.2-plop
// a-4-false-{bar}-4.2-plop
// b-1-true-{bar}-4.2-plop
// b-1-false-{bar}-4.2-plop
// b-2-true-{bar}-4.2-plop
// b-2-false-{bar}-4.2-plop
// b-3-true-{bar}-4.2-plop
// b-3-false-{bar}-4.2-plop
// b-4-true-{bar}-4.2-plop
// b-4-false-{bar}-4.2-plop
}
func ExampleCrossJoinBy7() {
result := CrossJoinBy7(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false), func(a string, b int, c bool, d foo, e float64, f string, g bool) string {
return fmt.Sprintf("%v-%v-%v-%v-%v-%v-%v", a, b, c, d, e, f, g)
})
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2-plop-false
// a-1-false-{bar}-4.2-plop-false
// a-2-true-{bar}-4.2-plop-false
// a-2-false-{bar}-4.2-plop-false
// a-3-true-{bar}-4.2-plop-false
// a-3-false-{bar}-4.2-plop-false
// a-4-true-{bar}-4.2-plop-false
// a-4-false-{bar}-4.2-plop-false
// b-1-true-{bar}-4.2-plop-false
// b-1-false-{bar}-4.2-plop-false
// b-2-true-{bar}-4.2-plop-false
// b-2-false-{bar}-4.2-plop-false
// b-3-true-{bar}-4.2-plop-false
// b-3-false-{bar}-4.2-plop-false
// b-4-true-{bar}-4.2-plop-false
// b-4-false-{bar}-4.2-plop-false
}
func ExampleCrossJoinBy8() {
result := CrossJoinBy8(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false), values(42), func(a string, b int, c bool, d foo, e float64, f string, g bool, h int) string {
return fmt.Sprintf("%v-%v-%v-%v-%v-%v-%v-%v", a, b, c, d, e, f, g, h)
})
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2-plop-false-42
// a-1-false-{bar}-4.2-plop-false-42
// a-2-true-{bar}-4.2-plop-false-42
// a-2-false-{bar}-4.2-plop-false-42
// a-3-true-{bar}-4.2-plop-false-42
// a-3-false-{bar}-4.2-plop-false-42
// a-4-true-{bar}-4.2-plop-false-42
// a-4-false-{bar}-4.2-plop-false-42
// b-1-true-{bar}-4.2-plop-false-42
// b-1-false-{bar}-4.2-plop-false-42
// b-2-true-{bar}-4.2-plop-false-42
// b-2-false-{bar}-4.2-plop-false-42
// b-3-true-{bar}-4.2-plop-false-42
// b-3-false-{bar}-4.2-plop-false-42
// b-4-true-{bar}-4.2-plop-false-42
// b-4-false-{bar}-4.2-plop-false-42
}
func ExampleCrossJoinBy9() {
result := CrossJoinBy9(values("a", "b"), values(1, 2, 3, 4), values(true, false), values(foo{bar: "bar"}), values(4.2), values("plop"), values(false), values(42), values("hello world"), func(a string, b int, c bool, d foo, e float64, f string, g bool, h int, i string) string {
return fmt.Sprintf("%v-%v-%v-%v-%v-%v-%v-%v-%v", a, b, c, d, e, f, g, h, i)
})
for r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2-plop-false-42-hello world
// a-1-false-{bar}-4.2-plop-false-42-hello world
// a-2-true-{bar}-4.2-plop-false-42-hello world
// a-2-false-{bar}-4.2-plop-false-42-hello world
// a-3-true-{bar}-4.2-plop-false-42-hello world
// a-3-false-{bar}-4.2-plop-false-42-hello world
// a-4-true-{bar}-4.2-plop-false-42-hello world
// a-4-false-{bar}-4.2-plop-false-42-hello world
// b-1-true-{bar}-4.2-plop-false-42-hello world
// b-1-false-{bar}-4.2-plop-false-42-hello world
// b-2-true-{bar}-4.2-plop-false-42-hello world
// b-2-false-{bar}-4.2-plop-false-42-hello world
// b-3-true-{bar}-4.2-plop-false-42-hello world
// b-3-false-{bar}-4.2-plop-false-42-hello world
// b-4-true-{bar}-4.2-plop-false-42-hello world
// b-4-false-{bar}-4.2-plop-false-42-hello world
}
================================================
FILE: it/tuples_test.go
================================================
//go:build go1.23
package it
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
"github.com/samber/lo"
)
func TestZip(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Zip2(
values("a", "b"),
values(1, 2),
)
r2 := Zip3(
values("a", "b", "c"),
values(1, 2, 3),
values(4, 5, 6),
)
r3 := Zip4(
values("a", "b", "c", "d"),
values(1, 2, 3, 4),
values(5, 6, 7, 8),
values(true, true, true, true),
)
r4 := Zip5(
values("a", "b", "c", "d", "e"),
values(1, 2, 3, 4, 5),
values(6, 7, 8, 9, 10),
values(true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5),
)
r5 := Zip6(
values("a", "b", "c", "d", "e", "f"),
values(1, 2, 3, 4, 5, 6),
values(7, 8, 9, 10, 11, 12),
values(true, true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5, 0.6),
values(0.01, 0.02, 0.03, 0.04, 0.05, 0.06),
)
r6 := Zip7(
values("a", "b", "c", "d", "e", "f", "g"),
values(1, 2, 3, 4, 5, 6, 7),
values(8, 9, 10, 11, 12, 13, 14),
values(true, true, true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7),
values(0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07),
values[int8](1, 2, 3, 4, 5, 6, 7),
)
r7 := Zip8(
values("a", "b", "c", "d", "e", "f", "g", "h"),
values(1, 2, 3, 4, 5, 6, 7, 8),
values(9, 10, 11, 12, 13, 14, 15, 16),
values(true, true, true, true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8),
values(0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08),
values[int8](1, 2, 3, 4, 5, 6, 7, 8),
values[int16](1, 2, 3, 4, 5, 6, 7, 8),
)
r8 := Zip9(
values("a", "b", "c", "d", "e", "f", "g", "h", "i"),
values(1, 2, 3, 4, 5, 6, 7, 8, 9),
values(10, 11, 12, 13, 14, 15, 16, 17, 18),
values(true, true, true, true, true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
values(0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09),
values[int8](1, 2, 3, 4, 5, 6, 7, 8, 9),
values[int16](1, 2, 3, 4, 5, 6, 7, 8, 9),
values[int32](1, 2, 3, 4, 5, 6, 7, 8, 9),
)
assertSeqSupportBreak(t, r1)
assertSeqSupportBreak(t, r2)
assertSeqSupportBreak(t, r3)
assertSeqSupportBreak(t, r4)
assertSeqSupportBreak(t, r5)
assertSeqSupportBreak(t, r6)
assertSeqSupportBreak(t, r7)
assertSeqSupportBreak(t, r8)
is.Equal([]lo.Tuple2[string, int]{
{A: "a", B: 1},
{A: "b", B: 2},
}, slices.Collect(r1))
is.Equal([]lo.Tuple3[string, int, int]{
{A: "a", B: 1, C: 4},
{A: "b", B: 2, C: 5},
{A: "c", B: 3, C: 6},
}, slices.Collect(r2))
is.Equal([]lo.Tuple4[string, int, int, bool]{
{A: "a", B: 1, C: 5, D: true},
{A: "b", B: 2, C: 6, D: true},
{A: "c", B: 3, C: 7, D: true},
{A: "d", B: 4, C: 8, D: true},
}, slices.Collect(r3))
is.Equal([]lo.Tuple5[string, int, int, bool, float32]{
{A: "a", B: 1, C: 6, D: true, E: 0.1},
{A: "b", B: 2, C: 7, D: true, E: 0.2},
{A: "c", B: 3, C: 8, D: true, E: 0.3},
{A: "d", B: 4, C: 9, D: true, E: 0.4},
{A: "e", B: 5, C: 10, D: true, E: 0.5},
}, slices.Collect(r4))
is.Equal([]lo.Tuple6[string, int, int, bool, float32, float64]{
{A: "a", B: 1, C: 7, D: true, E: 0.1, F: 0.01},
{A: "b", B: 2, C: 8, D: true, E: 0.2, F: 0.02},
{A: "c", B: 3, C: 9, D: true, E: 0.3, F: 0.03},
{A: "d", B: 4, C: 10, D: true, E: 0.4, F: 0.04},
{A: "e", B: 5, C: 11, D: true, E: 0.5, F: 0.05},
{A: "f", B: 6, C: 12, D: true, E: 0.6, F: 0.06},
}, slices.Collect(r5))
is.Equal([]lo.Tuple7[string, int, int, bool, float32, float64, int8]{
{A: "a", B: 1, C: 8, D: true, E: 0.1, F: 0.01, G: 1},
{A: "b", B: 2, C: 9, D: true, E: 0.2, F: 0.02, G: 2},
{A: "c", B: 3, C: 10, D: true, E: 0.3, F: 0.03, G: 3},
{A: "d", B: 4, C: 11, D: true, E: 0.4, F: 0.04, G: 4},
{A: "e", B: 5, C: 12, D: true, E: 0.5, F: 0.05, G: 5},
{A: "f", B: 6, C: 13, D: true, E: 0.6, F: 0.06, G: 6},
{A: "g", B: 7, C: 14, D: true, E: 0.7, F: 0.07, G: 7},
}, slices.Collect(r6))
is.Equal([]lo.Tuple8[string, int, int, bool, float32, float64, int8, int16]{
{A: "a", B: 1, C: 9, D: true, E: 0.1, F: 0.01, G: 1, H: 1},
{A: "b", B: 2, C: 10, D: true, E: 0.2, F: 0.02, G: 2, H: 2},
{A: "c", B: 3, C: 11, D: true, E: 0.3, F: 0.03, G: 3, H: 3},
{A: "d", B: 4, C: 12, D: true, E: 0.4, F: 0.04, G: 4, H: 4},
{A: "e", B: 5, C: 13, D: true, E: 0.5, F: 0.05, G: 5, H: 5},
{A: "f", B: 6, C: 14, D: true, E: 0.6, F: 0.06, G: 6, H: 6},
{A: "g", B: 7, C: 15, D: true, E: 0.7, F: 0.07, G: 7, H: 7},
{A: "h", B: 8, C: 16, D: true, E: 0.8, F: 0.08, G: 8, H: 8},
}, slices.Collect(r7))
is.Equal([]lo.Tuple9[string, int, int, bool, float32, float64, int8, int16, int32]{
{A: "a", B: 1, C: 10, D: true, E: 0.1, F: 0.01, G: 1, H: 1, I: 1},
{A: "b", B: 2, C: 11, D: true, E: 0.2, F: 0.02, G: 2, H: 2, I: 2},
{A: "c", B: 3, C: 12, D: true, E: 0.3, F: 0.03, G: 3, H: 3, I: 3},
{A: "d", B: 4, C: 13, D: true, E: 0.4, F: 0.04, G: 4, H: 4, I: 4},
{A: "e", B: 5, C: 14, D: true, E: 0.5, F: 0.05, G: 5, H: 5, I: 5},
{A: "f", B: 6, C: 15, D: true, E: 0.6, F: 0.06, G: 6, H: 6, I: 6},
{A: "g", B: 7, C: 16, D: true, E: 0.7, F: 0.07, G: 7, H: 7, I: 7},
{A: "h", B: 8, C: 17, D: true, E: 0.8, F: 0.08, G: 8, H: 8, I: 8},
{A: "i", B: 9, C: 18, D: true, E: 0.9, F: 0.09, G: 9, H: 9, I: 9},
}, slices.Collect(r8))
}
func TestZipBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := ZipBy2(
values("a", "b"),
values(1, 2),
lo.T2[string, int],
)
r2 := ZipBy3(
values("a", "b", "c"),
values(1, 2, 3),
values(4, 5, 6),
lo.T3[string, int, int],
)
r3 := ZipBy4(
values("a", "b", "c", "d"),
values(1, 2, 3, 4),
values(5, 6, 7, 8),
values(true, true, true, true),
lo.T4[string, int, int, bool],
)
r4 := ZipBy5(
values("a", "b", "c", "d", "e"),
values(1, 2, 3, 4, 5),
values(6, 7, 8, 9, 10),
values(true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5),
lo.T5[string, int, int, bool, float32],
)
r5 := ZipBy6(
values("a", "b", "c", "d", "e", "f"),
values(1, 2, 3, 4, 5, 6),
values(7, 8, 9, 10, 11, 12),
values(true, true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5, 0.6),
values(0.01, 0.02, 0.03, 0.04, 0.05, 0.06),
lo.T6[string, int, int, bool, float32, float64],
)
r6 := ZipBy7(
values("a", "b", "c", "d", "e", "f", "g"),
values(1, 2, 3, 4, 5, 6, 7),
values(8, 9, 10, 11, 12, 13, 14),
values(true, true, true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7),
values(0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07),
values[int8](1, 2, 3, 4, 5, 6, 7),
lo.T7[string, int, int, bool, float32, float64, int8],
)
r7 := ZipBy8(
values("a", "b", "c", "d", "e", "f", "g", "h"),
values(1, 2, 3, 4, 5, 6, 7, 8),
values(9, 10, 11, 12, 13, 14, 15, 16),
values(true, true, true, true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8),
values(0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08),
values[int8](1, 2, 3, 4, 5, 6, 7, 8),
values[int16](1, 2, 3, 4, 5, 6, 7, 8),
lo.T8[string, int, int, bool, float32, float64, int8, int16],
)
r8 := ZipBy9(
values("a", "b", "c", "d", "e", "f", "g", "h", "i"),
values(1, 2, 3, 4, 5, 6, 7, 8, 9),
values(10, 11, 12, 13, 14, 15, 16, 17, 18),
values(true, true, true, true, true, true, true, true, true),
values[float32](0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9),
values(0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09),
values[int8](1, 2, 3, 4, 5, 6, 7, 8, 9),
values[int16](1, 2, 3, 4, 5, 6, 7, 8, 9),
values[int32](1, 2, 3, 4, 5, 6, 7, 8, 9),
lo.T9[string, int, int, bool, float32, float64, int8, int16, int32],
)
assertSeqSupportBreak(t, r1)
assertSeqSupportBreak(t, r2)
assertSeqSupportBreak(t, r3)
assertSeqSupportBreak(t, r4)
assertSeqSupportBreak(t, r5)
assertSeqSupportBreak(t, r6)
assertSeqSupportBreak(t, r7)
assertSeqSupportBreak(t, r8)
is.Equal([]lo.Tuple2[string, int]{
{A: "a", B: 1},
{A: "b", B: 2},
}, slices.Collect(r1))
is.Equal([]lo.Tuple3[string, int, int]{
{A: "a", B: 1, C: 4},
{A: "b", B: 2, C: 5},
{A: "c", B: 3, C: 6},
}, slices.Collect(r2))
is.Equal([]lo.Tuple4[string, int, int, bool]{
{A: "a", B: 1, C: 5, D: true},
{A: "b", B: 2, C: 6, D: true},
{A: "c", B: 3, C: 7, D: true},
{A: "d", B: 4, C: 8, D: true},
}, slices.Collect(r3))
is.Equal([]lo.Tuple5[string, int, int, bool, float32]{
{A: "a", B: 1, C: 6, D: true, E: 0.1},
{A: "b", B: 2, C: 7, D: true, E: 0.2},
{A: "c", B: 3, C: 8, D: true, E: 0.3},
{A: "d", B: 4, C: 9, D: true, E: 0.4},
{A: "e", B: 5, C: 10, D: true, E: 0.5},
}, slices.Collect(r4))
is.Equal([]lo.Tuple6[string, int, int, bool, float32, float64]{
{A: "a", B: 1, C: 7, D: true, E: 0.1, F: 0.01},
{A: "b", B: 2, C: 8, D: true, E: 0.2, F: 0.02},
{A: "c", B: 3, C: 9, D: true, E: 0.3, F: 0.03},
{A: "d", B: 4, C: 10, D: true, E: 0.4, F: 0.04},
{A: "e", B: 5, C: 11, D: true, E: 0.5, F: 0.05},
{A: "f", B: 6, C: 12, D: true, E: 0.6, F: 0.06},
}, slices.Collect(r5))
is.Equal([]lo.Tuple7[string, int, int, bool, float32, float64, int8]{
{A: "a", B: 1, C: 8, D: true, E: 0.1, F: 0.01, G: 1},
{A: "b", B: 2, C: 9, D: true, E: 0.2, F: 0.02, G: 2},
{A: "c", B: 3, C: 10, D: true, E: 0.3, F: 0.03, G: 3},
{A: "d", B: 4, C: 11, D: true, E: 0.4, F: 0.04, G: 4},
{A: "e", B: 5, C: 12, D: true, E: 0.5, F: 0.05, G: 5},
{A: "f", B: 6, C: 13, D: true, E: 0.6, F: 0.06, G: 6},
{A: "g", B: 7, C: 14, D: true, E: 0.7, F: 0.07, G: 7},
}, slices.Collect(r6))
is.Equal([]lo.Tuple8[string, int, int, bool, float32, float64, int8, int16]{
{A: "a", B: 1, C: 9, D: true, E: 0.1, F: 0.01, G: 1, H: 1},
{A: "b", B: 2, C: 10, D: true, E: 0.2, F: 0.02, G: 2, H: 2},
{A: "c", B: 3, C: 11, D: true, E: 0.3, F: 0.03, G: 3, H: 3},
{A: "d", B: 4, C: 12, D: true, E: 0.4, F: 0.04, G: 4, H: 4},
{A: "e", B: 5, C: 13, D: true, E: 0.5, F: 0.05, G: 5, H: 5},
{A: "f", B: 6, C: 14, D: true, E: 0.6, F: 0.06, G: 6, H: 6},
{A: "g", B: 7, C: 15, D: true, E: 0.7, F: 0.07, G: 7, H: 7},
{A: "h", B: 8, C: 16, D: true, E: 0.8, F: 0.08, G: 8, H: 8},
}, slices.Collect(r7))
is.Equal([]lo.Tuple9[string, int, int, bool, float32, float64, int8, int16, int32]{
{A: "a", B: 1, C: 10, D: true, E: 0.1, F: 0.01, G: 1, H: 1, I: 1},
{A: "b", B: 2, C: 11, D: true, E: 0.2, F: 0.02, G: 2, H: 2, I: 2},
{A: "c", B: 3, C: 12, D: true, E: 0.3, F: 0.03, G: 3, H: 3, I: 3},
{A: "d", B: 4, C: 13, D: true, E: 0.4, F: 0.04, G: 4, H: 4, I: 4},
{A: "e", B: 5, C: 14, D: true, E: 0.5, F: 0.05, G: 5, H: 5, I: 5},
{A: "f", B: 6, C: 15, D: true, E: 0.6, F: 0.06, G: 6, H: 6, I: 6},
{A: "g", B: 7, C: 16, D: true, E: 0.7, F: 0.07, G: 7, H: 7, I: 7},
{A: "h", B: 8, C: 17, D: true, E: 0.8, F: 0.08, G: 8, H: 8, I: 8},
{A: "i", B: 9, C: 18, D: true, E: 0.9, F: 0.09, G: 9, H: 9, I: 9},
}, slices.Collect(r8))
}
func TestCrossJoin(t *testing.T) {
t.Parallel()
is := assert.New(t)
listOne := values("a", "b", "c")
listTwo := values(1, 2, 3)
emptyList := values[any]()
mixedList := values[any](9.6, 4, "foobar")
results1 := CrossJoin2(emptyList, listTwo)
is.Empty(slices.Collect(results1))
results2 := CrossJoin2(listOne, emptyList)
is.Empty(slices.Collect(results2))
results3 := CrossJoin2(emptyList, emptyList)
is.Empty(slices.Collect(results3))
results4 := CrossJoin2(values("a"), listTwo)
is.Equal([]lo.Tuple2[string, int]{lo.T2("a", 1), lo.T2("a", 2), lo.T2("a", 3)}, slices.Collect(results4))
results5 := CrossJoin2(listOne, values(1))
is.Equal([]lo.Tuple2[string, int]{lo.T2("a", 1), lo.T2("b", 1), lo.T2("c", 1)}, slices.Collect(results5))
results6 := CrossJoin2(listOne, listTwo)
is.Equal([]lo.Tuple2[string, int]{lo.T2("a", 1), lo.T2("a", 2), lo.T2("a", 3), lo.T2("b", 1), lo.T2("b", 2), lo.T2("b", 3), lo.T2("c", 1), lo.T2("c", 2), lo.T2("c", 3)}, slices.Collect(results6))
results7 := CrossJoin2(listOne, mixedList)
is.Equal([]lo.Tuple2[string, any]{lo.T2[string, any]("a", 9.6), lo.T2[string, any]("a", 4), lo.T2[string, any]("a", "foobar"), lo.T2[string, any]("b", 9.6), lo.T2[string, any]("b", 4), lo.T2[string, any]("b", "foobar"), lo.T2[string, any]("c", 9.6), lo.T2[string, any]("c", 4), lo.T2[string, any]("c", "foobar")}, slices.Collect(results7))
}
func TestCrossJoinBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
listOne := values("a", "b", "c")
listTwo := values(1, 2, 3)
emptyList := values[any]()
mixedList := values[any](9.6, 4, "foobar")
results1 := CrossJoinBy2(emptyList, listTwo, lo.T2[any, int])
is.Empty(slices.Collect(results1))
results2 := CrossJoinBy2(listOne, emptyList, lo.T2[string, any])
is.Empty(slices.Collect(results2))
results3 := CrossJoinBy2(emptyList, emptyList, lo.T2[any, any])
is.Empty(slices.Collect(results3))
results4 := CrossJoinBy2(values("a"), listTwo, lo.T2[string, int])
is.Equal([]lo.Tuple2[string, int]{lo.T2("a", 1), lo.T2("a", 2), lo.T2("a", 3)}, slices.Collect(results4))
results5 := CrossJoinBy2(listOne, values(1), lo.T2[string, int])
is.Equal([]lo.Tuple2[string, int]{lo.T2("a", 1), lo.T2("b", 1), lo.T2("c", 1)}, slices.Collect(results5))
results6 := CrossJoinBy2(listOne, listTwo, lo.T2[string, int])
is.Equal([]lo.Tuple2[string, int]{lo.T2("a", 1), lo.T2("a", 2), lo.T2("a", 3), lo.T2("b", 1), lo.T2("b", 2), lo.T2("b", 3), lo.T2("c", 1), lo.T2("c", 2), lo.T2("c", 3)}, slices.Collect(results6))
results7 := CrossJoinBy2(listOne, mixedList, lo.T2[string, any])
is.Equal([]lo.Tuple2[string, any]{lo.T2[string, any]("a", 9.6), lo.T2[string, any]("a", 4), lo.T2[string, any]("a", "foobar"), lo.T2[string, any]("b", 9.6), lo.T2[string, any]("b", 4), lo.T2[string, any]("b", "foobar"), lo.T2[string, any]("c", 9.6), lo.T2[string, any]("c", 4), lo.T2[string, any]("c", "foobar")}, slices.Collect(results7))
}
================================================
FILE: it/type_manipulation.go
================================================
//go:build go1.23
package it
import (
"iter"
"github.com/samber/lo"
)
// ToSeqPtr returns a sequence of pointers to each value.
// Play: https://go.dev/play/p/70BcKpDcOKm
func ToSeqPtr[T any](collection iter.Seq[T]) iter.Seq[*T] {
return Map(collection, lo.ToPtr)
}
// FromSeqPtr returns a sequence with the pointer values.
// Returns a zero value in case of a nil pointer element.
// Play: https://go.dev/play/p/_eO6scpLcBF
func FromSeqPtr[T any](collection iter.Seq[*T]) iter.Seq[T] {
return Map(collection, lo.FromPtr)
}
// FromSeqPtrOr returns a sequence with the pointer values or the fallback value.
// Play: https://go.dev/play/p/LJ17S4pvOjo
func FromSeqPtrOr[T any](collection iter.Seq[*T], fallback T) iter.Seq[T] {
return Map(collection, func(x *T) T { return lo.FromPtrOr(x, fallback) })
}
// ToAnySeq returns a sequence with all elements mapped to `any` type.
// Play: https://go.dev/play/p/ktE4IMXDMxv
func ToAnySeq[T any](collection iter.Seq[T]) iter.Seq[any] {
return Map(collection, func(x T) any { return x })
}
// FromAnySeq returns a sequence with all elements mapped to a type.
// Panics on type conversion failure.
// Play: https://go.dev/play/p/wnOma1j5Uzu
func FromAnySeq[T any](collection iter.Seq[any]) iter.Seq[T] {
return Map(collection, func(item any) T {
if t, ok := item.(T); ok {
return t
}
panic("it.FromAnySeq: type conversion failed")
})
}
// Empty returns an empty sequence.
// Play: https://go.dev/play/p/Y9yplM3vGFe
func Empty[T any]() iter.Seq[T] {
return func(yield func(T) bool) {}
}
// IsEmpty returns true if the sequence is empty.
// Will iterate at most once.
// Play: https://go.dev/play/p/krZ-laaVi2C
func IsEmpty[T any](collection iter.Seq[T]) bool {
for range collection {
return false
}
return true
}
// IsNotEmpty returns true if the sequence is not empty.
// Will iterate at most once.
// Play: https://go.dev/play/p/jMAjbDvrdPu
func IsNotEmpty[T any](collection iter.Seq[T]) bool {
return !IsEmpty(collection)
}
// CoalesceSeq returns the first non-empty sequence.
// Will iterate through each sub-sequence at most once.
// Play: https://go.dev/play/p/wep4z2KJLCO
func CoalesceSeq[T any](v ...iter.Seq[T]) (iter.Seq[T], bool) {
for i := range v {
for range v[i] {
return v[i], true
}
}
return Empty[T](), false
}
// CoalesceSeqOrEmpty returns the first non-empty sequence.
// Will iterate through each sub-sequence at most once.
// Play: https://go.dev/play/p/3KZPAFRQr8B
func CoalesceSeqOrEmpty[T any](v ...iter.Seq[T]) iter.Seq[T] {
result, _ := CoalesceSeq(v...)
return result
}
================================================
FILE: it/type_manipulation_test.go
================================================
//go:build go1.23
package it
import (
"slices"
"testing"
"github.com/stretchr/testify/assert"
)
func TestToSeqPtr(t *testing.T) {
t.Parallel()
is := assert.New(t)
str1 := "foo"
str2 := "bar"
result1 := ToSeqPtr(values(str1, str2))
is.Equal([]*string{&str1, &str2}, slices.Collect(result1))
}
func TestFromSeqPtr(t *testing.T) {
t.Parallel()
is := assert.New(t)
str1 := "foo"
str2 := "bar"
result1 := FromSeqPtr(values(&str1, &str2, nil))
is.Equal([]string{str1, str2, ""}, slices.Collect(result1))
}
func TestFromSeqPtrOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
str1 := "foo"
str2 := "bar"
result1 := FromSeqPtrOr(values(&str1, &str2, nil), "fallback")
is.Equal([]string{str1, str2, "fallback"}, slices.Collect(result1))
}
func TestToAnySeq(t *testing.T) {
t.Parallel()
is := assert.New(t)
in1 := values(0, 1, 2, 3)
in2 := values[int]()
out1 := ToAnySeq(in1)
out2 := ToAnySeq(in2)
is.Equal([]any{0, 1, 2, 3}, slices.Collect(out1))
is.Empty(slices.Collect(out2))
}
func TestFromAnySeq(t *testing.T) {
t.Parallel()
is := assert.New(t)
out1 := FromAnySeq[string](values[any]("foobar", 42))
out2 := FromAnySeq[string](values[any]("foobar", "42"))
is.PanicsWithValue("it.FromAnySeq: type conversion failed", func() { _ = slices.Collect(out1) })
is.Equal([]string{"foobar", "42"}, slices.Collect(out2))
}
func TestEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Empty(slices.Collect(Empty[string]()))
}
func TestIsEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(IsEmpty(values[string]()))
is.False(IsEmpty(values("foo")))
}
func TestIsNotEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.False(IsNotEmpty(values[string]()))
is.True(IsNotEmpty(values("foo")))
}
func TestCoalesceSeq(t *testing.T) {
t.Parallel()
is := assert.New(t)
seq0 := values[int]()
seq1 := values(1)
seq2 := values(1, 2)
result1, ok1 := CoalesceSeq[int]()
result4, ok4 := CoalesceSeq(seq0)
result6, ok6 := CoalesceSeq(seq2)
result7, ok7 := CoalesceSeq(seq1)
result8, ok8 := CoalesceSeq(seq1, seq2)
result9, ok9 := CoalesceSeq(seq2, seq1)
result10, ok10 := CoalesceSeq(seq0, seq1, seq2)
is.NotNil(result1)
is.Empty(slices.Collect(result1))
is.False(ok1)
is.NotNil(result4)
is.Empty(slices.Collect(result4))
is.False(ok4)
is.NotNil(result6)
is.Equal(slices.Collect(seq2), slices.Collect(result6))
is.True(ok6)
is.NotNil(result7)
is.Equal(slices.Collect(seq1), slices.Collect(result7))
is.True(ok7)
is.NotNil(result8)
is.Equal(slices.Collect(seq1), slices.Collect(result8))
is.True(ok8)
is.NotNil(result9)
is.Equal(slices.Collect(seq2), slices.Collect(result9))
is.True(ok9)
is.NotNil(result10)
is.Equal(slices.Collect(seq1), slices.Collect(result10))
is.True(ok10)
}
func TestCoalesceSeqOrEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
seq0 := values[int]()
seq1 := values(1)
seq2 := values(1, 2)
result1 := CoalesceSeqOrEmpty[int]()
result4 := CoalesceSeqOrEmpty(seq0)
result6 := CoalesceSeqOrEmpty(seq2)
result7 := CoalesceSeqOrEmpty(seq1)
result8 := CoalesceSeqOrEmpty(seq1, seq2)
result9 := CoalesceSeqOrEmpty(seq2, seq1)
result10 := CoalesceSeqOrEmpty(seq0, seq1, seq2)
is.NotNil(result1)
is.Empty(slices.Collect(result1))
is.NotNil(result4)
is.Empty(slices.Collect(result4))
is.NotNil(result6)
is.Equal(slices.Collect(seq2), slices.Collect(result6))
is.NotNil(result7)
is.Equal(slices.Collect(seq1), slices.Collect(result7))
is.NotNil(result8)
is.Equal(slices.Collect(seq1), slices.Collect(result8))
is.NotNil(result9)
is.Equal(slices.Collect(seq2), slices.Collect(result9))
is.NotNil(result10)
is.Equal(slices.Collect(seq1), slices.Collect(result10))
}
================================================
FILE: lo_example_test.go
================================================
package lo
import (
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
"time"
)
func ExampleTernary() {
result := Ternary(true, "a", "b")
fmt.Printf("%v", result)
// Output: a
}
func ExampleTernaryF() {
result := TernaryF(true, func() string { return "a" }, func() string { return "b" })
fmt.Printf("%v", result)
// Output: a
}
func ExampleIf() {
result1 := If(true, 1).
ElseIf(false, 2).
Else(3)
result2 := If(false, 1).
ElseIf(true, 2).
Else(3)
result3 := If(false, 1).
ElseIf(false, 2).
Else(3)
result4 := IfF(true, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
result5 := IfF(false, func() int { return 1 }).
ElseIfF(true, func() int { return 2 }).
ElseF(func() int { return 3 })
result6 := IfF(false, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func ExampleIfF() {
result1 := If(true, 1).
ElseIf(false, 2).
Else(3)
result2 := If(false, 1).
ElseIf(true, 2).
Else(3)
result3 := If(false, 1).
ElseIf(false, 2).
Else(3)
result4 := IfF(true, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
result5 := IfF(false, func() int { return 1 }).
ElseIfF(true, func() int { return 2 }).
ElseF(func() int { return 3 })
result6 := IfF(false, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func Example_ifElse_ElseIf() {
result1 := If(true, 1).
ElseIf(false, 2).
Else(3)
result2 := If(false, 1).
ElseIf(true, 2).
Else(3)
result3 := If(false, 1).
ElseIf(false, 2).
Else(3)
result4 := IfF(true, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
result5 := IfF(false, func() int { return 1 }).
ElseIfF(true, func() int { return 2 }).
ElseF(func() int { return 3 })
result6 := IfF(false, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func Example_ifElse_ElseIfF() {
result1 := If(true, 1).
ElseIf(false, 2).
Else(3)
result2 := If(false, 1).
ElseIf(true, 2).
Else(3)
result3 := If(false, 1).
ElseIf(false, 2).
Else(3)
result4 := IfF(true, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
result5 := IfF(false, func() int { return 1 }).
ElseIfF(true, func() int { return 2 }).
ElseF(func() int { return 3 })
result6 := IfF(false, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func Example_ifElse_Else() {
result1 := If(true, 1).
ElseIf(false, 2).
Else(3)
result2 := If(false, 1).
ElseIf(true, 2).
Else(3)
result3 := If(false, 1).
ElseIf(false, 2).
Else(3)
result4 := IfF(true, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
result5 := IfF(false, func() int { return 1 }).
ElseIfF(true, func() int { return 2 }).
ElseF(func() int { return 3 })
result6 := IfF(false, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func Example_ifElse_ElseF() {
result1 := If(true, 1).
ElseIf(false, 2).
Else(3)
result2 := If(false, 1).
ElseIf(true, 2).
Else(3)
result3 := If(false, 1).
ElseIf(false, 2).
Else(3)
result4 := IfF(true, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
result5 := IfF(false, func() int { return 1 }).
ElseIfF(true, func() int { return 2 }).
ElseF(func() int { return 3 })
result6 := IfF(false, func() int { return 1 }).
ElseIfF(false, func() int { return 2 }).
ElseF(func() int { return 3 })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func ExampleSwitch() {
result1 := Switch[int, string](1).
Case(1, "1").
Case(2, "2").
Default("3")
result2 := Switch[int, string](2).
Case(1, "1").
Case(2, "2").
Default("3")
result3 := Switch[int, string](42).
Case(1, "1").
Case(2, "2").
Default("3")
result4 := Switch[int, string](1).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result5 := Switch[int, string](2).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result6 := Switch[int, string](42).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func Example_switchCase_Case() {
result1 := Switch[int, string](1).
Case(1, "1").
Case(2, "2").
Default("3")
result2 := Switch[int, string](2).
Case(1, "1").
Case(2, "2").
Default("3")
result3 := Switch[int, string](42).
Case(1, "1").
Case(2, "2").
Default("3")
result4 := Switch[int, string](1).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result5 := Switch[int, string](2).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result6 := Switch[int, string](42).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func Example_switchCase_CaseF() {
result1 := Switch[int, string](1).
Case(1, "1").
Case(2, "2").
Default("3")
result2 := Switch[int, string](2).
Case(1, "1").
Case(2, "2").
Default("3")
result3 := Switch[int, string](42).
Case(1, "1").
Case(2, "2").
Default("3")
result4 := Switch[int, string](1).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result5 := Switch[int, string](2).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result6 := Switch[int, string](42).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func Example_switchCase_Default() {
result1 := Switch[int, string](1).
Case(1, "1").
Case(2, "2").
Default("3")
result2 := Switch[int, string](2).
Case(1, "1").
Case(2, "2").
Default("3")
result3 := Switch[int, string](42).
Case(1, "1").
Case(2, "2").
Default("3")
result4 := Switch[int, string](1).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result5 := Switch[int, string](2).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result6 := Switch[int, string](42).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func Example_switchCase_DefaultF() {
result1 := Switch[int, string](1).
Case(1, "1").
Case(2, "2").
Default("3")
result2 := Switch[int, string](2).
Case(1, "1").
Case(2, "2").
Default("3")
result3 := Switch[int, string](42).
Case(1, "1").
Case(2, "2").
Default("3")
result4 := Switch[int, string](1).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result5 := Switch[int, string](2).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
result6 := Switch[int, string](42).
CaseF(1, func() string { return "1" }).
CaseF(2, func() string { return "2" }).
DefaultF(func() string { return "3" })
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
// Output:
// 1
// 2
// 3
// 1
// 2
// 3
}
func ExampleValidate() {
i := 42
err1 := Validate(i < 0, "expected %d < 0", i)
err2 := Validate(i > 0, "expected %d > 0", i)
fmt.Printf("%v\n%v", err1, err2)
// Output:
// expected 42 < 0
//
}
func ExampleMust() {
defer func() {
_ = recover()
}()
// won't panic
Must(42, nil)
// won't panic
cb := func() (int, error) {
return 42, nil
}
Must(cb())
// will panic
Must(42, errors.New("my error"))
// will panic with error message
Must(42, errors.New("world"), "hello")
}
func ExampleMust0() {
defer func() {
_ = recover()
}()
// won't panic
Must0(nil)
// will panic
Must0(errors.New("my error"))
// will panic with error message
Must0(errors.New("world"), "hello")
}
func ExampleMust1() {
defer func() {
_ = recover()
}()
// won't panic
Must1(42, nil)
// won't panic
cb := func() (int, error) {
return 42, nil
}
Must1(cb())
// will panic
Must1(42, errors.New("my error"))
// will panic with error message
Must1(42, errors.New("world"), "hello")
}
func ExampleMust2() {
defer func() {
_ = recover()
}()
// won't panic
Must2(42, "hello", nil)
// will panic
Must2(42, "hello", errors.New("my error"))
// will panic with error message
Must2(42, "hello", errors.New("world"), "hello")
}
func ExampleMust3() {
defer func() {
_ = recover()
}()
// won't panic
Must3(42, "hello", 4.2, nil)
// will panic
Must3(42, "hello", 4.2, errors.New("my error"))
// will panic with error message
Must3(42, "hello", 4.2, errors.New("world"), "hello")
}
func ExampleMust4() {
defer func() {
_ = recover()
}()
// won't panic
Must4(42, "hello", 4.2, true, nil)
// will panic
Must4(42, "hello", 4.2, true, errors.New("my error"))
// will panic with error message
Must4(42, "hello", 4.2, true, errors.New("world"), "hello")
}
func ExampleMust5() {
defer func() {
_ = recover()
}()
// won't panic
Must5(42, "hello", 4.2, true, foo{}, nil)
// will panic
Must5(42, "hello", 4.2, true, foo{}, errors.New("my error"))
// will panic with error message
Must5(42, "hello", 4.2, true, foo{}, errors.New("world"), "hello")
}
func ExampleMust6() {
defer func() {
_ = recover()
}()
// won't panic
Must5(42, "hello", 4.2, true, foo{}, "foobar", nil)
// will panic
Must5(42, "hello", 4.2, true, foo{}, "foobar", errors.New("my error"))
// will panic with error message
Must5(42, "hello", 4.2, true, foo{}, "foobar", errors.New("world"), "hello")
}
func ExampleTry() {
ok1 := Try(func() error {
return nil
})
ok2 := Try(func() error {
return errors.New("my error")
})
ok3 := Try(func() error {
panic("my error")
})
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", ok3)
// Output:
// true
// false
// false
}
func ExampleTry1() {
ok1 := Try1(func() error {
return nil
})
ok2 := Try1(func() error {
return errors.New("my error")
})
ok3 := Try1(func() error {
panic("my error")
})
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", ok3)
// Output:
// true
// false
// false
}
func ExampleTry2() {
ok1 := Try2(func() (int, error) {
return 42, nil
})
ok2 := Try2(func() (int, error) {
return 42, errors.New("my error")
})
ok3 := Try2(func() (int, error) {
panic("my error")
})
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", ok3)
// Output:
// true
// false
// false
}
func ExampleTry3() {
ok1 := Try3(func() (int, string, error) {
return 42, "foobar", nil
})
ok2 := Try3(func() (int, string, error) {
return 42, "foobar", errors.New("my error")
})
ok3 := Try3(func() (int, string, error) {
panic("my error")
})
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", ok3)
// Output:
// true
// false
// false
}
func ExampleTry4() {
ok1 := Try4(func() (int, string, float64, error) {
return 42, "foobar", 4.2, nil
})
ok2 := Try4(func() (int, string, float64, error) {
return 42, "foobar", 4.2, errors.New("my error")
})
ok3 := Try4(func() (int, string, float64, error) {
panic("my error")
})
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", ok3)
// Output:
// true
// false
// false
}
func ExampleTry5() {
ok1 := Try5(func() (int, string, float64, bool, error) {
return 42, "foobar", 4.2, true, nil
})
ok2 := Try5(func() (int, string, float64, bool, error) {
return 42, "foobar", 4.2, true, errors.New("my error")
})
ok3 := Try5(func() (int, string, float64, bool, error) {
panic("my error")
})
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", ok3)
// Output:
// true
// false
// false
}
func ExampleTry6() {
ok1 := Try6(func() (int, string, float64, bool, foo, error) {
return 42, "foobar", 4.2, true, foo{}, nil
})
ok2 := Try6(func() (int, string, float64, bool, foo, error) {
return 42, "foobar", 4.2, true, foo{}, errors.New("my error")
})
ok3 := Try6(func() (int, string, float64, bool, foo, error) {
panic("my error")
})
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", ok3)
// Output:
// true
// false
// false
}
func ExampleTryOr() {
value1, ok1 := TryOr(func() (int, error) {
return 42, nil
}, 21)
value2, ok2 := TryOr(func() (int, error) {
return 42, errors.New("my error")
}, 21)
value3, ok3 := TryOr(func() (int, error) {
panic("my error")
}, 21)
fmt.Printf("%v %v\n", value1, ok1)
fmt.Printf("%v %v\n", value2, ok2)
fmt.Printf("%v %v\n", value3, ok3)
// Output:
// 42 true
// 21 false
// 21 false
}
func ExampleTryOr1() {
value1, ok1 := TryOr1(func() (int, error) {
return 42, nil
}, 21)
value2, ok2 := TryOr1(func() (int, error) {
return 42, errors.New("my error")
}, 21)
value3, ok3 := TryOr1(func() (int, error) {
panic("my error")
}, 21)
fmt.Printf("%v %v\n", value1, ok1)
fmt.Printf("%v %v\n", value2, ok2)
fmt.Printf("%v %v\n", value3, ok3)
// Output:
// 42 true
// 21 false
// 21 false
}
func ExampleTryOr2() {
value1, value2, ok3 := TryOr2(func() (int, string, error) {
panic("my error")
}, 21, "hello")
fmt.Printf("%v %v %v\n", value1, value2, ok3)
// Output: 21 hello false
}
func ExampleTryOr3() {
value1, value2, value3, ok3 := TryOr3(func() (int, string, bool, error) {
panic("my error")
}, 21, "hello", false)
fmt.Printf("%v %v %v %v\n", value1, value2, value3, ok3)
// Output: 21 hello false false
}
func ExampleTryOr4() {
value1, value2, value3, value4, ok3 := TryOr4(func() (int, string, bool, foo, error) {
panic("my error")
}, 21, "hello", false, foo{bar: "bar"})
fmt.Printf("%v %v %v %v %v\n", value1, value2, value3, value4, ok3)
// Output: 21 hello false {bar} false
}
func ExampleTryOr5() {
value1, value2, value3, value4, value5, ok3 := TryOr5(func() (int, string, bool, foo, float64, error) {
panic("my error")
}, 21, "hello", false, foo{bar: "bar"}, 4.2)
fmt.Printf("%v %v %v %v %v %v\n", value1, value2, value3, value4, value5, ok3)
// Output: 21 hello false {bar} 4.2 false
}
func ExampleTryOr6() {
value1, value2, value3, value4, value5, value6, ok3 := TryOr6(func() (int, string, bool, foo, float64, string, error) {
panic("my error")
}, 21, "hello", false, foo{bar: "bar"}, 4.2, "world")
fmt.Printf("%v %v %v %v %v %v %v\n", value1, value2, value3, value4, value5, value6, ok3)
// Output: 21 hello false {bar} 4.2 world false
}
func ExampleTryWithErrorValue() {
err1, ok1 := TryWithErrorValue(func() error {
return nil
})
err2, ok2 := TryWithErrorValue(func() error {
return errors.New("my error")
})
err3, ok3 := TryWithErrorValue(func() error {
panic("my error")
})
fmt.Printf("%v %v\n", err1, ok1)
fmt.Printf("%v %v\n", err2, ok2)
fmt.Printf("%v %v\n", err3, ok3)
// Output:
// true
// my error false
// my error false
}
func ExampleTryCatchWithErrorValue() {
TryCatchWithErrorValue(
func() error {
panic("trigger an error")
},
func(err any) {
fmt.Printf("catch: %s", err)
},
)
// Output: catch: trigger an error
}
type myError struct{}
func (e myError) Error() string {
return "my error"
}
func ExampleErrorsAs() {
doSomething := func() error {
return &myError{}
}
err := doSomething()
if rateLimitErr, ok := ErrorsAs[*myError](err); ok {
fmt.Printf("is type myError, err: %s", rateLimitErr.Error())
} else {
fmt.Printf("is not type myError")
}
// Output: is type myError, err: my error
}
func ExampleAssert() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
age := 20
// won't panic
Assert(age >= 18)
// won't panic
Assert(age >= 18, "age must be at least 18")
// will panic
Assert(age < 18)
// will panic
Assert(age < 18, "age must be less than 18")
// Output: assertion failed
}
func ExampleAssertf() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
}
}()
age := 20
// won't panic
Assertf(age >= 18, "age must be at least 18, got %d", age)
// will panic
Assertf(age < 18, "age must be less than 18, got %d", age)
// Output: assertion failed: age must be less than 18, got 20
}
func ExampleIndexOf() {
list := []string{"foo", "bar", "baz"}
result := IndexOf(list, "bar")
fmt.Printf("%d", result)
// Output: 1
}
func ExampleIndexOf_notFound() {
list := []string{"foo", "bar", "baz"}
result := IndexOf(list, "qux")
fmt.Printf("%d", result)
// Output: -1
}
func ExampleLastIndexOf() {
list := []string{"foo", "bar", "baz", "bar"}
result := LastIndexOf(list, "bar")
fmt.Printf("%d", result)
// Output: 3
}
func ExampleLastIndexOf_notFound() {
list := []string{"foo", "bar", "baz"}
result := LastIndexOf(list, "qux")
fmt.Printf("%d", result)
// Output: -1
}
func ExampleFind() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
result, found := Find(users, func(user User) bool {
return user.Age > 30
})
fmt.Printf("%s %t", result.Name, found)
// Output: Charlie true
}
func ExampleFind_notFound() {
list := []int{1, 2, 3, 4, 5}
result, found := Find(list, func(n int) bool {
return n > 10
})
fmt.Printf("%d %t", result, found)
// Output: 0 false
}
func ExampleFindErr() {
result, err := FindErr([]string{"a", "b", "c", "d"}, func(i string) (bool, error) {
return i == "b", nil
})
fmt.Printf("%v %v\n", result, err)
result, err = FindErr([]string{"foobar"}, func(i string) (bool, error) {
return i == "b", nil
})
fmt.Printf("%v %v\n", result, err)
result, err = FindErr([]string{"a", "b", "c"}, func(i string) (bool, error) {
if i == "b" {
return false, errors.New("b is not allowed")
}
return i == "b", nil
})
fmt.Printf("%v %v", result, err)
// Output:
// b
//
// b is not allowed
}
func ExampleFindIndexOf() {
list := []int{1, 2, 3, 4, 5}
result, index, found := FindIndexOf(list, func(n int) bool {
return n > 2
})
fmt.Printf("%d %d %t", result, index, found)
// Output: 3 2 true
}
func ExampleFindIndexOf_notFound() {
list := []int{1, 2, 3, 4, 5}
result, index, found := FindIndexOf(list, func(n int) bool {
return n > 10
})
fmt.Printf("%d %d %t", result, index, found)
// Output: 0 -1 false
}
func ExampleFindLastIndexOf() {
list := []int{1, 2, 3, 4, 3, 5}
result, index, found := FindLastIndexOf(list, func(n int) bool {
return n == 3
})
fmt.Printf("%d %d %t", result, index, found)
// Output: 3 4 true
}
func ExampleFindLastIndexOf_notFound() {
list := []int{1, 2, 3, 4, 5}
result, index, found := FindLastIndexOf(list, func(n int) bool {
return n > 10
})
fmt.Printf("%d %d %t", result, index, found)
// Output: 0 -1 false
}
func ExampleFindOrElse() {
list := []int{1, 2, 3, 4, 5}
result := FindOrElse(list, -1, func(n int) bool {
return n > 10
})
fmt.Printf("%d", result)
// Output: -1
}
func ExampleFindOrElse_found() {
list := []int{1, 2, 3, 4, 5}
result := FindOrElse(list, -1, func(n int) bool {
return n > 3
})
fmt.Printf("%d", result)
// Output: 4
}
func ExampleFindKey() {
users := map[string]int{
"Alice": 25,
"Bob": 30,
"Charlie": 35,
}
key, found := FindKey(users, 30)
fmt.Printf("%s %t", key, found)
// Output: Bob true
}
func ExampleFindKey_notFound() {
users := map[string]int{
"Alice": 25,
"Bob": 30,
"Charlie": 35,
}
key, found := FindKey(users, 40)
fmt.Printf("%s %t", key, found)
// Output: false
}
func ExampleFindKeyBy() {
users := map[string]int{
"Alice": 25,
"Bob": 30,
"Charlie": 35,
}
key, found := FindKeyBy(users, func(name string, age int) bool {
return age > 30
})
fmt.Printf("%s %t", key, found)
// Output: Charlie true
}
func ExampleFindKeyBy_notFound() {
users := map[string]int{
"Alice": 25,
"Bob": 30,
"Charlie": 35,
}
key, found := FindKeyBy(users, func(name string, age int) bool {
return age > 40
})
fmt.Printf("%s %t", key, found)
// Output: false
}
func ExampleFindUniques() {
list := []int{1, 2, 2, 3, 3, 3, 4, 5}
result := FindUniques(list)
fmt.Printf("%v", result)
// Output: [1 4 5]
}
func ExampleFindUniquesBy() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 25},
{Name: "David", Age: 30},
{Name: "Eve", Age: 35},
}
result := FindUniquesBy(users, func(user User) int {
return user.Age
})
fmt.Printf("%d", len(result))
// Output: 1
}
func ExampleFindDuplicates() {
list := []int{1, 2, 2, 3, 3, 3, 4, 5}
result := FindDuplicates(list)
fmt.Printf("%v", result)
// Output: [2 3]
}
func ExampleFindDuplicatesBy() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 25},
{Name: "David", Age: 30},
{Name: "Eve", Age: 35},
}
result := FindDuplicatesBy(users, func(user User) int {
return user.Age
})
fmt.Printf("%d", len(result))
// Output: 2
}
func ExampleFindDuplicatesByErr() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 25},
{Name: "David", Age: 30},
{Name: "Eve", Age: 35},
}
result, err := FindDuplicatesByErr(users, func(user User) (int, error) {
if user.Name == "Charlie" {
return 0, errors.New("charlie is not allowed")
}
return user.Age, nil
})
fmt.Printf("%d %v", len(result), err)
// Output: 0 charlie is not allowed
}
func ExampleMin() {
list := []int{3, 1, 4, 1, 5, 9, 2, 6}
result := Min(list)
fmt.Printf("%d", result)
// Output: 1
}
func ExampleMin_empty() {
list := []int{}
result := Min(list)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleMinIndex() {
list := []int{3, 1, 4, 1, 5, 9, 2, 6}
result, index := MinIndex(list)
fmt.Printf("%d %d", result, index)
// Output: 1 1
}
func ExampleMinIndex_empty() {
list := []int{}
result, index := MinIndex(list)
fmt.Printf("%d %d", result, index)
// Output: 0 -1
}
func ExampleMinBy() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
result := MinBy(users, func(a, b User) bool {
return a.Age < b.Age
})
fmt.Printf("%s", result.Name)
// Output: Alice
}
func ExampleMinByErr() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
result, err := MinByErr(users, func(a, b User) (bool, error) {
if a.Name == "Bob" {
return false, errors.New("bob is not allowed")
}
return a.Age < b.Age, nil
})
fmt.Printf("%v %v", result.Name, err)
// Output: bob is not allowed
}
func ExampleMinIndexBy() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
result, index := MinIndexBy(users, func(a, b User) bool {
return a.Age < b.Age
})
fmt.Printf("%s %d", result.Name, index)
// Output: Alice 0
}
func ExampleMinIndexByErr() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
result, _, err := MinIndexByErr(users, func(a, b User) (bool, error) {
if a.Name == "Bob" {
return false, errors.New("bob is not allowed")
}
return a.Age < b.Age, nil
})
fmt.Printf("%v %v", result.Name, err)
// Output: bob is not allowed
}
func ExampleEarliest() {
now := time.Now()
past := now.Add(-time.Hour)
future := now.Add(time.Hour)
result := Earliest(future, now, past)
fmt.Printf("%t", result.Equal(past))
// Output: true
}
func ExampleEarliestBy() {
type Event struct {
Name string
Time time.Time
}
now := time.Now()
events := []Event{
{Name: "Event A", Time: now.Add(time.Hour)},
{Name: "Event B", Time: now},
{Name: "Event C", Time: now.Add(-time.Hour)},
}
result := EarliestBy(events, func(event Event) time.Time {
return event.Time
})
fmt.Printf("%s", result.Name)
// Output: Event C
}
func ExampleEarliestByErr() {
type Event struct {
Name string
Time time.Time
}
now := time.Now()
events := []Event{
{Name: "Event A", Time: now.Add(time.Hour)},
{Name: "Event B", Time: now},
{Name: "Event C", Time: now.Add(-time.Hour)},
}
_, err := EarliestByErr(events, func(event Event) (time.Time, error) {
if event.Name == "Event B" {
return time.Time{}, errors.New("event b is not allowed")
}
return event.Time, nil
})
fmt.Printf("%v", err)
// Output: event b is not allowed
}
func ExampleMax() {
list := []int{3, 1, 4, 1, 5, 9, 2, 6}
result := Max(list)
fmt.Printf("%d", result)
// Output: 9
}
func ExampleMax_empty() {
list := []int{}
result := Max(list)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleMaxIndex() {
list := []int{3, 1, 4, 1, 5, 9, 2, 6}
result, index := MaxIndex(list)
fmt.Printf("%d %d", result, index)
// Output: 9 5
}
func ExampleMaxIndex_empty() {
list := []int{}
result, index := MaxIndex(list)
fmt.Printf("%d %d", result, index)
// Output: 0 -1
}
func ExampleMaxBy() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
result := MaxBy(users, func(a, b User) bool {
return a.Age > b.Age
})
fmt.Printf("%s", result.Name)
// Output: Charlie
}
func ExampleMaxByErr() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
_, err := MaxByErr(users, func(a, b User) (bool, error) {
if b.Name == "Bob" {
return false, errors.New("bob is not allowed")
}
return a.Age > b.Age, nil
})
fmt.Printf("%v", err)
// Output: bob is not allowed
}
func ExampleMaxIndexBy() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
result, index := MaxIndexBy(users, func(a, b User) bool {
return a.Age > b.Age
})
fmt.Printf("%s %d", result.Name, index)
// Output: Charlie 2
}
func ExampleMaxIndexByErr() {
type User struct {
Name string
Age int
}
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 30},
{Name: "Charlie", Age: 35},
}
_, _, err := MaxIndexByErr(users, func(a, b User) (bool, error) {
if b.Name == "Bob" {
return false, errors.New("bob is not allowed")
}
return a.Age > b.Age, nil
})
fmt.Printf("%v", err)
// Output: bob is not allowed
}
func ExampleLatest() {
now := time.Now()
past := now.Add(-time.Hour)
future := now.Add(time.Hour)
result := Latest(future, now, past)
fmt.Printf("%t", result.Equal(future))
// Output: true
}
func ExampleLatestBy() {
type Event struct {
Name string
Time time.Time
}
now := time.Now()
events := []Event{
{Name: "Event A", Time: now.Add(time.Hour)},
{Name: "Event B", Time: now},
{Name: "Event C", Time: now.Add(-time.Hour)},
}
result := LatestBy(events, func(event Event) time.Time {
return event.Time
})
fmt.Printf("%s", result.Name)
// Output: Event A
}
func ExampleLatestByErr() {
type Event struct {
Name string
Time time.Time
Err error // Simulates error condition
}
now := time.Now()
events := []Event{
{Name: "Event A", Time: now.Add(time.Hour), Err: nil},
{Name: "Event B", Time: now, Err: errors.New("event b error")},
{Name: "Event C", Time: now.Add(-time.Hour), Err: nil},
}
_, err := LatestByErr(events, func(event Event) (time.Time, error) {
if event.Err != nil {
return time.Time{}, event.Err
}
return event.Time, nil
})
fmt.Printf("%v", err)
// Output: event b error
}
func ExampleFirst() {
list := []int{1, 2, 3, 4, 5}
result, found := First(list)
fmt.Printf("%d %t", result, found)
// Output: 1 true
}
func ExampleFirst_empty() {
list := []int{}
result, found := First(list)
fmt.Printf("%d %t", result, found)
// Output: 0 false
}
func ExampleFirstOrEmpty() {
list := []int{1, 2, 3, 4, 5}
result := FirstOrEmpty(list)
fmt.Printf("%d", result)
// Output: 1
}
func ExampleFirstOrEmpty_empty() {
list := []int{}
result := FirstOrEmpty(list)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleFirstOr() {
list := []int{1, 2, 3, 4, 5}
result := FirstOr(list, -1)
fmt.Printf("%d", result)
// Output: 1
}
func ExampleFirstOr_empty() {
list := []int{}
result := FirstOr(list, -1)
fmt.Printf("%d", result)
// Output: -1
}
func ExampleLast() {
list := []int{1, 2, 3, 4, 5}
result, found := Last(list)
fmt.Printf("%d %t", result, found)
// Output: 5 true
}
func ExampleLast_empty() {
list := []int{}
result, found := Last(list)
fmt.Printf("%d %t", result, found)
// Output: 0 false
}
func ExampleLastOrEmpty() {
list := []int{1, 2, 3, 4, 5}
result := LastOrEmpty(list)
fmt.Printf("%d", result)
// Output: 5
}
func ExampleLastOrEmpty_empty() {
list := []int{}
result := LastOrEmpty(list)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleLastOr() {
list := []int{1, 2, 3, 4, 5}
result := LastOr(list, -1)
fmt.Printf("%d", result)
// Output: 5
}
func ExampleLastOr_empty() {
list := []int{}
result := LastOr(list, -1)
fmt.Printf("%d", result)
// Output: -1
}
func ExampleNth() {
list := []int{1, 2, 3, 4, 5}
result, err := Nth(list, 2)
fmt.Printf("%d %v", result, err)
// Output: 3
}
func ExampleNth_negative() {
list := []int{1, 2, 3, 4, 5}
result, err := Nth(list, -2)
fmt.Printf("%d %v", result, err)
// Output: 4
}
func ExampleNth_outOfBounds() {
list := []int{1, 2, 3, 4, 5}
result, err := Nth(list, 10)
fmt.Printf("%d %v", result, err)
// Output: 0 nth: 10 out of slice bounds
}
func ExampleNthOr() {
list := []int{1, 2, 3, 4, 5}
result := NthOr(list, 2, -1)
fmt.Printf("%d", result)
// Output: 3
}
func ExampleNthOr_outOfBounds() {
list := []int{1, 2, 3, 4, 5}
result := NthOr(list, 10, -1)
fmt.Printf("%d", result)
// Output: -1
}
func ExampleNthOrEmpty() {
list := []int{1, 2, 3, 4, 5}
result := NthOrEmpty(list, 2)
fmt.Printf("%d", result)
// Output: 3
}
func ExampleNthOrEmpty_outOfBounds() {
list := []int{1, 2, 3, 4, 5}
result := NthOrEmpty(list, 10)
fmt.Printf("%d", result)
// Output: 0
}
func ExampleWithoutBy() {
type User struct {
ID int
Name string
}
// original users
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}
// exclude users with IDs 2 and 3
excludedIDs := []int{2, 3}
// extract function to get the user ID
extractID := func(user User) int {
return user.ID
}
// filtering users
filteredUsers := WithoutBy(users, extractID, excludedIDs...)
// output the filtered users
fmt.Printf("%v", filteredUsers)
// Output:
// [{1 Alice}]
}
func ExampleWithoutByErr() {
type User struct {
ID int
Name string
}
// original users
users := []User{
{ID: 1, Name: "Alice"},
{ID: 2, Name: "Bob"},
{ID: 3, Name: "Charlie"},
}
// exclude users with IDs 2 and 3
excludedIDs := []int{2, 3}
// extract function to get the user ID
extractID := func(user User) (int, error) {
if user.ID == 2 {
return 0, errors.New("user 2 extraction failed")
}
return user.ID, nil
}
// filtering users
filteredUsers, err := WithoutByErr(users, extractID, excludedIDs...)
// output the filtered users
fmt.Printf("%v %v", filteredUsers, err)
// Output:
// [] user 2 extraction failed
}
func ExampleKeys() {
kv := map[string]int{"foo": 1, "bar": 2}
kv2 := map[string]int{"baz": 3}
result := Keys(kv, kv2)
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [bar baz foo]
}
func ExampleUniqKeys() {
kv := map[string]int{"foo": 1, "bar": 2}
kv2 := map[string]int{"bar": 3}
result := UniqKeys(kv, kv2)
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [bar foo]
}
func ExampleValues() {
kv := map[string]int{"foo": 1, "bar": 2}
kv2 := map[string]int{"baz": 3}
result := Values(kv, kv2)
sort.Ints(result)
fmt.Printf("%v", result)
// Output: [1 2 3]
}
func ExampleUniqValues() {
kv := map[string]int{"foo": 1, "bar": 2}
kv2 := map[string]int{"baz": 2}
result := UniqValues(kv, kv2)
sort.Ints(result)
fmt.Printf("%v", result)
// Output: [1 2]
}
func ExampleValueOr() {
kv := map[string]int{"foo": 1, "bar": 2}
result1 := ValueOr(kv, "foo", 42)
result2 := ValueOr(kv, "baz", 42)
fmt.Printf("%v %v", result1, result2)
// Output: 1 42
}
func ExamplePickBy() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := PickBy(kv, func(key string, value int) bool {
return value%2 == 1
})
fmt.Printf("%v %v %v", len(result), result["foo"], result["baz"])
// Output: 2 1 3
}
func ExamplePickByErr() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
_, err := PickByErr(kv, func(key string, value int) (bool, error) {
if key == "bar" {
return false, errors.New("key bar is not allowed")
}
return value%2 == 1, nil
})
fmt.Printf("%v", err)
// Output: key bar is not allowed
}
func ExamplePickByKeys() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := PickByKeys(kv, []string{"foo", "baz"})
fmt.Printf("%v %v %v", len(result), result["foo"], result["baz"])
// Output: 2 1 3
}
func ExamplePickByValues() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := PickByValues(kv, []int{1, 3})
fmt.Printf("%v %v %v", len(result), result["foo"], result["baz"])
// Output: 2 1 3
}
func ExampleOmitBy() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := OmitBy(kv, func(key string, value int) bool {
return value%2 == 1
})
fmt.Printf("%v", result)
// Output: map[bar:2]
}
func ExampleOmitByErr() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
_, err := OmitByErr(kv, func(key string, value int) (bool, error) {
if key == "bar" {
return false, errors.New("key bar is not allowed")
}
return value%2 == 1, nil
})
fmt.Printf("%v", err)
// Output: key bar is not allowed
}
func ExampleOmitByKeys() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := OmitByKeys(kv, []string{"foo", "baz"})
fmt.Printf("%v", result)
// Output: map[bar:2]
}
func ExampleOmitByValues() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := OmitByValues(kv, []int{1, 3})
fmt.Printf("%v", result)
// Output: map[bar:2]
}
func ExampleEntries() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := Entries(kv)
sort.Slice(result, func(i, j int) bool {
return strings.Compare(result[i].Key, result[j].Key) < 0
})
fmt.Printf("%v", result)
// Output: [{bar 2} {baz 3} {foo 1}]
}
func ExampleFromEntries() {
result := FromEntries([]Entry[string, int]{
{
Key: "foo",
Value: 1,
},
{
Key: "bar",
Value: 2,
},
{
Key: "baz",
Value: 3,
},
})
fmt.Printf("%v %v %v %v", len(result), result["foo"], result["bar"], result["baz"])
// Output: 3 1 2 3
}
func ExampleInvert() {
kv := map[string]int{"foo": 1, "bar": 2, "baz": 3}
result := Invert(kv)
fmt.Printf("%v %v %v %v", len(result), result[1], result[2], result[3])
// Output: 3 foo bar baz
}
func ExampleAssign() {
result := Assign(
map[string]int{"a": 1, "b": 2},
map[string]int{"b": 3, "c": 4},
)
fmt.Printf("%v %v %v %v", len(result), result["a"], result["b"], result["c"])
// Output: 3 1 3 4
}
func ExampleChunkEntries() {
result := ChunkEntries(
map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
},
3,
)
for i := range result {
fmt.Printf("%d\n", len(result[i]))
}
// Output:
// 3
// 2
}
func ExampleMapKeys() {
kv := map[int]int{1: 1, 2: 2, 3: 3, 4: 4}
result := MapKeys(kv, func(_, k int) string {
return strconv.FormatInt(int64(k), 10)
})
fmt.Printf("%v %v %v %v %v", len(result), result["1"], result["2"], result["3"], result["4"])
// Output: 4 1 2 3 4
}
func ExampleMapKeysErr() {
kv := map[int]int{1: 1, 2: 2, 3: 3, 4: 4}
_, err := MapKeysErr(kv, func(_, k int) (string, error) {
if k == 3 {
return "", errors.New("key 3 is not allowed")
}
return strconv.FormatInt(int64(k), 10), nil
})
fmt.Printf("%v", err)
// Output: key 3 is not allowed
}
func ExampleMapValues() {
kv := map[int]int{1: 1, 2: 2, 3: 3, 4: 4}
result := MapValues(kv, func(v, _ int) string {
return strconv.FormatInt(int64(v), 10)
})
fmt.Printf("%v %q %q %q %q", len(result), result[1], result[2], result[3], result[4])
// Output: 4 "1" "2" "3" "4"
}
func ExampleMapValuesErr() {
kv := map[int]int{1: 1, 2: 2, 3: 3, 4: 4}
_, err := MapValuesErr(kv, func(v, _ int) (string, error) {
if v == 3 {
return "", errors.New("value 3 is not allowed")
}
return strconv.FormatInt(int64(v), 10), nil
})
fmt.Printf("%v", err)
// Output: value 3 is not allowed
}
func ExampleMapEntries() {
kv := map[string]int{"foo": 1, "bar": 2}
result := MapEntries(kv, func(k string, v int) (int, string) {
return v, k
})
fmt.Printf("%v", result)
// Output: map[1:foo 2:bar]
}
func ExampleMapEntriesErr() {
kv := map[string]int{"foo": 1, "bar": 2}
_, err := MapEntriesErr(kv, func(k string, v int) (int, string, error) {
if k == "foo" {
return 0, "", errors.New("entry foo is not allowed")
}
return v, k, nil
})
fmt.Printf("%v", err)
// Output: entry foo is not allowed
}
func ExampleMapToSlice() {
kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
result := MapToSlice(kv, func(k int, v int64) string {
return fmt.Sprintf("%d_%d", k, v)
})
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [1_1 2_2 3_3 4_4]
}
func ExampleMapToSliceErr() {
kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
_, err := MapToSliceErr(kv, func(k int, v int64) (string, error) {
if k == 3 {
return "", errors.New("key 3 is not allowed")
}
return fmt.Sprintf("%d_%d", k, v), nil
})
fmt.Printf("%v", err)
// Output: key 3 is not allowed
}
func ExampleFilterMapToSlice() {
kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
result := FilterMapToSlice(kv, func(k int, v int64) (string, bool) {
return fmt.Sprintf("%d_%d", k, v), k%2 == 0
})
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [2_2 4_4]
}
func ExampleFilterMapToSliceErr() {
kv := map[int]int64{1: 1, 2: 2, 3: 3, 4: 4}
_, err := FilterMapToSliceErr(kv, func(k int, v int64) (string, bool, error) {
if k == 3 {
return "", false, errors.New("key 3 is not allowed")
}
return fmt.Sprintf("%d_%d", k, v), k%2 == 0, nil
})
fmt.Printf("%v", err)
// Output: key 3 is not allowed
}
func ExampleFilterKeys() {
kv := map[int]string{1: "foo", 2: "bar", 3: "baz"}
result := FilterKeys(kv, func(k int, v string) bool {
return v == "foo"
})
fmt.Printf("%v", result)
// Output: [1]
}
func ExampleFilterValues() {
kv := map[int]string{1: "foo", 2: "bar", 3: "baz"}
result := FilterValues(kv, func(k int, v string) bool {
return v == "foo"
})
fmt.Printf("%v", result)
// Output: [foo]
}
func ExampleFilterKeysErr() {
kv := map[int]string{1: "foo", 2: "bar", 3: "baz"}
result, err := FilterKeysErr(kv, func(k int, v string) (bool, error) {
if k == 3 {
return false, errors.New("key 3 not allowed")
}
return v == "foo", nil
})
fmt.Printf("%v, %v\n", result, err)
result, err = FilterKeysErr(kv, func(k int, v string) (bool, error) {
return v == "bar", nil
})
fmt.Printf("%v, %v\n", result, err)
// Output:
// [], key 3 not allowed
// [2],
}
func ExampleFilterValuesErr() {
kv := map[int]string{1: "foo", 2: "bar", 3: "baz"}
result, err := FilterValuesErr(kv, func(k int, v string) (bool, error) {
if k == 3 {
return false, errors.New("key 3 not allowed")
}
return v == "foo", nil
})
fmt.Printf("%v, %v\n", result, err)
result, err = FilterValuesErr(kv, func(k int, v string) (bool, error) {
return v == "bar", nil
})
fmt.Printf("%v, %v\n", result, err)
// Output:
// [], key 3 not allowed
// [bar],
}
func ExampleRange() {
result1 := Range(4)
result2 := Range(-4)
result3 := RangeFrom(1, 5)
result4 := RangeFrom(1.0, 5)
result5 := RangeWithSteps(0, 20, 5)
result6 := RangeWithSteps[float32](-1.0, -4.0, -1.0)
result7 := RangeWithSteps(1, 4, -1)
result8 := Range(0)
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
fmt.Printf("%v\n", result6)
fmt.Printf("%v\n", result7)
fmt.Printf("%v\n", result8)
// Output:
// [0 1 2 3]
// [0 -1 -2 -3]
// [1 2 3 4 5]
// [1 2 3 4 5]
// [0 5 10 15]
// [-1 -2 -3]
// []
// []
}
func ExampleClamp() {
result1 := Clamp(0, -10, 10)
result2 := Clamp(-42, -10, 10)
result3 := Clamp(42, -10, 10)
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
// Output:
// 0
// -10
// 10
}
func ExampleSum() {
list := []int{1, 2, 3, 4, 5}
sum := Sum(list)
fmt.Printf("%v", sum)
// Output: 15
}
func ExampleSumBy() {
list := []string{"foo", "bar"}
result := SumBy(list, func(item string) int {
return len(item)
})
fmt.Printf("%v", result)
// Output: 6
}
func ExampleSumByErr() {
list := []string{"foo", "bar", "baz"}
_, err := SumByErr(list, func(item string) (int, error) {
if item == "bar" {
return 0, errors.New("bar is not allowed")
}
return len(item), nil
})
fmt.Printf("%v", err)
// Output: bar is not allowed
}
func ExampleProduct() {
list := []int{1, 2, 3, 4, 5}
result := Product(list)
fmt.Printf("%v", result)
// Output: 120
}
func ExampleProductBy() {
list := []string{"foo", "bar"}
result := ProductBy(list, func(item string) int {
return len(item)
})
fmt.Printf("%v", result)
// Output: 9
}
func ExampleProductByErr() {
list := []string{"foo", "bar", "baz"}
_, err := ProductByErr(list, func(item string) (int, error) {
if item == "bar" {
return 0, errors.New("bar is not allowed")
}
return len(item), nil
})
fmt.Printf("%v", err)
// Output: bar is not allowed
}
func ExampleMean() {
list := []int{1, 2, 3, 4, 5}
result := Mean(list)
fmt.Printf("%v", result)
// Output: 3
}
func ExampleMeanBy() {
list := []string{"foo", "bar"}
result := MeanBy(list, func(item string) int {
return len(item)
})
fmt.Printf("%v", result)
// Output: 3
}
func ExampleMeanByErr() {
list := []string{"foo", "bar", "baz"}
_, err := MeanByErr(list, func(item string) (int, error) {
if item == "bar" {
return 0, errors.New("bar is not allowed")
}
return len(item), nil
})
fmt.Printf("%v", err)
// Output: bar is not allowed
}
func ExampleFilter() {
list := []int64{1, 2, 3, 4}
result := Filter(list, func(nbr int64, index int) bool {
return nbr%2 == 0
})
fmt.Printf("%v", result)
// Output: [2 4]
}
func ExampleFilterErr() {
list := []int64{1, 2, 3, 4}
result, err := FilterErr(list, func(nbr int64, index int) (bool, error) {
if nbr == 3 {
return false, errors.New("number 3 is not allowed")
}
return nbr%2 == 0, nil
})
fmt.Printf("%v, %v\n", result, err)
result, err = FilterErr([]int64{1, 2, 4, 6}, func(nbr int64, index int) (bool, error) {
return nbr%2 == 0, nil
})
fmt.Printf("%v, %v\n", result, err)
// Output:
// [], number 3 is not allowed
// [2 4 6],
}
func ExampleMap() {
list := []int64{1, 2, 3, 4}
result := Map(list, func(nbr int64, index int) string {
return strconv.FormatInt(nbr*2, 10)
})
fmt.Printf("%v", result)
// Output: [2 4 6 8]
}
func ExampleMapErr() {
list := []int64{1, 2, 3, 4}
_, err := MapErr(list, func(nbr int64, index int) (string, error) {
if nbr == 3 {
return "", errors.New("number 3 is not allowed")
}
return strconv.FormatInt(nbr*2, 10), nil
})
fmt.Printf("%v", err)
// Output: number 3 is not allowed
}
func ExampleUniqMap() {
type User struct {
Name string
Age int
}
users := []User{{Name: "Alex", Age: 10}, {Name: "Alex", Age: 12}, {Name: "Bob", Age: 11}, {Name: "Alice", Age: 20}}
result := UniqMap(users, func(u User, index int) string {
return u.Name
})
sort.Strings(result)
fmt.Printf("%v", result)
// Output: [Alex Alice Bob]
}
func ExampleFilterMap() {
list := []int64{1, 2, 3, 4}
result := FilterMap(list, func(nbr int64, index int) (string, bool) {
return strconv.FormatInt(nbr*2, 10), nbr%2 == 0
})
fmt.Printf("%v", result)
// Output: [4 8]
}
func ExampleFlatMap() {
list := []int64{1, 2, 3, 4}
result := FlatMap(list, func(nbr int64, index int) []string {
return []string{
strconv.FormatInt(nbr, 10), // base 10
strconv.FormatInt(nbr, 2), // base 2
}
})
fmt.Printf("%v", result)
// Output: [1 1 2 10 3 11 4 100]
}
func ExampleFlatMapErr() {
list := []int64{1, 2, 3, 4}
_, err := FlatMapErr(list, func(nbr int64, index int) ([]string, error) {
if nbr == 3 {
return nil, errors.New("number 3 is not allowed")
}
return []string{
strconv.FormatInt(nbr, 10), // base 10
strconv.FormatInt(nbr, 2), // base 2
}, nil
})
fmt.Printf("%v", err)
// Output: number 3 is not allowed
}
func ExampleReduce() {
list := []int64{1, 2, 3, 4}
result := Reduce(list, func(agg, item int64, index int) int64 {
return agg + item
}, 0)
fmt.Printf("%v", result)
// Output: 10
}
func ExampleReduceErr() {
list := []int64{1, 2, 3, 4}
_, err := ReduceErr(list, func(agg, item int64, index int) (int64, error) {
if item == 3 {
return 0, errors.New("number 3 is not allowed")
}
return agg + item, nil
}, 0)
fmt.Printf("%v", err)
// Output: number 3 is not allowed
}
func ExampleReduceRight() {
list := [][]int{{0, 1}, {2, 3}, {4, 5}}
result := ReduceRight(list, func(agg, item []int, index int) []int {
return append(agg, item...)
}, []int{})
fmt.Printf("%v", result)
// Output: [4 5 2 3 0 1]
}
func ExampleReduceRightErr() {
list := [][]int{{0, 1}, {2, 3}, {4, 5}}
_, err := ReduceRightErr(list, func(agg, item []int, index int) ([]int, error) {
if index == 0 {
return nil, errors.New("index 0 is not allowed")
}
return append(agg, item...), nil
}, []int{})
fmt.Printf("%v", err)
// Output: index 0 is not allowed
}
func ExampleForEach() {
list := []int64{1, 2, 3, 4}
ForEach(list, func(x int64, _ int) {
fmt.Println(x)
})
// Output:
// 1
// 2
// 3
// 4
}
func ExampleForEachWhile() {
list := []int64{1, 2, -math.MaxInt, 4}
ForEachWhile(list, func(x int64, _ int) bool {
if x < 0 {
return false
}
fmt.Println(x)
return true
})
// Output:
// 1
// 2
}
func ExampleTimes() {
result := Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
fmt.Printf("%v", result)
// Output: [0 1 2]
}
func ExampleUniq() {
list := []int{1, 2, 2, 1}
result := Uniq(list)
fmt.Printf("%v", result)
// Output: [1 2]
}
func ExampleUniqBy() {
list := []int{0, 1, 2, 3, 4, 5}
result := UniqBy(list, func(i int) int {
return i % 3
})
fmt.Printf("%v", result)
// Output: [0 1 2]
}
func ExampleUniqByErr() {
list := []int{0, 1, 2, 3, 4, 5}
_, err := UniqByErr(list, func(i int) (int, error) {
if i == 3 {
return 0, errors.New("number 3 is not allowed")
}
return i % 3, nil
})
fmt.Printf("%v", err)
// Output: number 3 is not allowed
}
func ExampleGroupBy() {
list := []int{0, 1, 2, 3, 4, 5}
result := GroupBy(list, func(i int) int {
return i % 3
})
fmt.Printf("%v\n", result[0])
fmt.Printf("%v\n", result[1])
fmt.Printf("%v\n", result[2])
// Output:
// [0 3]
// [1 4]
// [2 5]
}
func ExampleGroupByErr() {
list := []int{0, 1, 2, 3, 4, 5}
_, err := GroupByErr(list, func(i int) (int, error) {
if i == 3 {
return 0, errors.New("number 3 is not allowed")
}
return i % 3, nil
})
fmt.Printf("%v", err)
// Output: number 3 is not allowed
}
func ExampleGroupByMap() {
list := []int{0, 1, 2, 3, 4, 5}
result := GroupByMap(list, func(i int) (int, int) {
return i % 3, i * 2
})
fmt.Printf("%v\n", result[0])
fmt.Printf("%v\n", result[1])
fmt.Printf("%v\n", result[2])
// Output:
// [0 6]
// [2 8]
// [4 10]
}
func ExampleGroupByMapErr() {
list := []int{0, 1, 2, 3, 4, 5}
_, err := GroupByMapErr(list, func(i int) (int, int, error) {
if i == 3 {
return 0, 0, errors.New("number 3 is not allowed")
}
return i % 3, i * 2, nil
})
fmt.Printf("%v", err)
// Output: number 3 is not allowed
}
func ExampleChunk() {
list := []int{0, 1, 2, 3, 4}
result := Chunk(list, 2)
for _, item := range result {
fmt.Printf("%v\n", item)
}
// Output:
// [0 1]
// [2 3]
// [4]
}
func ExampleWindow() {
list := []int{1, 2, 3, 4, 5}
result := Window(list, 3)
for _, item := range result {
fmt.Printf("%v\n", item)
}
// Output:
// [1 2 3]
// [2 3 4]
// [3 4 5]
}
func ExampleSliding() {
list := []int{1, 2, 3, 4, 5, 6, 7, 8}
// Overlapping windows (step < size)
result1 := Sliding(list, 3, 1)
fmt.Printf("Overlapping: %v\n", result1)
// Non-overlapping windows (step == size, like Chunk)
result2 := Sliding(list, 3, 3)
fmt.Printf("Non-overlapping: %v\n", result2)
// Step > size (skipping elements)
result3 := Sliding(list, 2, 3)
fmt.Printf("With step: %v\n", result3)
// Output:
// Overlapping: [[1 2 3] [2 3 4] [3 4 5] [4 5 6] [5 6 7] [6 7 8]]
// Non-overlapping: [[1 2 3] [4 5 6]]
// With step: [[1 2] [4 5] [7 8]]
}
func ExamplePartitionBy() {
list := []int{-2, -1, 0, 1, 2, 3, 4}
result := PartitionBy(list, func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
})
for _, item := range result {
fmt.Printf("%v\n", item)
}
// Output:
// [-2 -1]
// [0 2 4]
// [1 3]
}
func ExamplePartitionByErr() {
list := []int{-2, -1, 0, 1, 2, 3, 4}
_, err := PartitionByErr(list, func(x int) (string, error) {
if x == 0 {
return "", errors.New("zero is not allowed")
}
if x < 0 {
return "negative", nil
} else if x%2 == 0 {
return "even", nil
}
return "odd", nil
})
fmt.Printf("%v", err)
// Output: zero is not allowed
}
func ExampleFlatten() {
list := [][]int{{0, 1, 2}, {3, 4, 5}}
result := Flatten(list)
fmt.Printf("%v", result)
// Output: [0 1 2 3 4 5]
}
func ExampleConcat() {
list := []int{0, 1, 2, 3, 4, 5}
result := Concat(list, list)
fmt.Printf("%v", result)
// Output: [0 1 2 3 4 5 0 1 2 3 4 5]
}
func ExampleInterleave() {
list1 := [][]int{{1, 4, 7}, {2, 5, 8}, {3, 6, 9}}
list2 := [][]int{{1}, {2, 5, 8}, {3, 6}, {4, 7, 9, 10}}
result1 := Interleave(list1...)
result2 := Interleave(list2...)
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
// Output:
// [1 2 3 4 5 6 7 8 9]
// [1 2 3 4 5 6 7 8 9 10]
}
func ExampleShuffle() {
list := []int{0, 1, 2, 3, 4, 5}
result := Shuffle(list)
fmt.Printf("%v", result)
}
func ExampleReverse() {
list := []int{0, 1, 2, 3, 4, 5}
result := Reverse(list)
fmt.Printf("%v", result)
// Output: [5 4 3 2 1 0]
}
func ExampleFill() {
list := []foo{{"a"}, {"a"}}
result := Fill(list, foo{"b"})
fmt.Printf("%v", result)
// Output: [{b} {b}]
}
func ExampleRepeat() {
result := Repeat(2, foo{"a"})
fmt.Printf("%v", result)
// Output: [{a} {a}]
}
func ExampleRepeatBy() {
result := RepeatBy(5, func(i int) string {
return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10)
})
fmt.Printf("%v", result)
// Output: [0 1 4 9 16]
}
func ExampleRepeatByErr() {
result, err := RepeatByErr(5, func(i int) (string, error) {
if i == 3 {
return "", errors.New("index 3 is not allowed")
}
return fmt.Sprintf("item-%d", i), nil
})
fmt.Printf("%d %v", len(result), err)
// Output: 0 index 3 is not allowed
}
func ExampleKeyBy() {
list := []string{"a", "aa", "aaa"}
result := KeyBy(list, func(str string) int {
return len(str)
})
fmt.Printf("%v", result)
// Output: map[1:a 2:aa 3:aaa]
}
func ExampleKeyByErr() {
list := []string{"a", "aa", "aaa"}
_, err := KeyByErr(list, func(str string) (int, error) {
if str == "aa" {
return 0, errors.New("aa is not allowed")
}
return len(str), nil
})
fmt.Printf("%v", err)
// Output: aa is not allowed
}
func ExampleSliceToMap() {
list := []string{"a", "aa", "aaa"}
result := SliceToMap(list, func(str string) (string, int) {
return str, len(str)
})
fmt.Printf("%v", result)
// Output: map[a:1 aa:2 aaa:3]
}
func ExampleFilterSliceToMap() {
list := []string{"a", "aa", "aaa"}
result := FilterSliceToMap(list, func(str string) (string, int, bool) {
return str, len(str), len(str) > 1
})
fmt.Printf("%v", result)
// Output: map[aa:2 aaa:3]
}
func ExampleKeyify() {
list := []string{"a", "a", "b", "b", "d"}
set := Keyify(list)
_, ok1 := set["a"]
_, ok2 := set["c"]
fmt.Printf("%v\n", ok1)
fmt.Printf("%v\n", ok2)
fmt.Printf("%v\n", set)
// Output:
// true
// false
// map[a:{} b:{} d:{}]
}
func ExampleDrop() {
list := []int{0, 1, 2, 3, 4, 5}
result := Drop(list, 2)
fmt.Printf("%v", result)
// Output: [2 3 4 5]
}
func ExampleDropRight() {
list := []int{0, 1, 2, 3, 4, 5}
result := DropRight(list, 2)
fmt.Printf("%v", result)
// Output: [0 1 2 3]
}
func ExampleDropWhile() {
list := []int{0, 1, 2, 3, 4, 5}
result := DropWhile(list, func(val int) bool {
return val < 2
})
fmt.Printf("%v", result)
// Output: [2 3 4 5]
}
func ExampleDropRightWhile() {
list := []int{0, 1, 2, 3, 4, 5}
result := DropRightWhile(list, func(val int) bool {
return val > 2
})
fmt.Printf("%v", result)
// Output: [0 1 2]
}
func ExampleTake() {
list := []int{0, 1, 2, 3, 4, 5}
result := Take(list, 3)
fmt.Printf("%v", result)
// Output: [0 1 2]
}
func ExampleTakeWhile() {
list := []int{0, 1, 2, 3, 4, 5}
result := TakeWhile(list, func(val int) bool {
return val < 3
})
fmt.Printf("%v", result)
// Output: [0 1 2]
}
func ExampleTakeFilter() {
list := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := TakeFilter(
list, 3, func(val, index int) bool {
return val%2 == 0
},
)
fmt.Printf("%v", result)
// Output: [2 4 6]
}
func ExampleDropByIndex() {
list := []int{0, 1, 2, 3, 4, 5}
result := DropByIndex(list, 2)
fmt.Printf("%v", result)
// Output: [0 1 3 4 5]
}
func ExampleReject() {
list := []int{0, 1, 2, 3, 4, 5}
result := Reject(list, func(x, _ int) bool {
return x%2 == 0
})
fmt.Printf("%v", result)
// Output: [1 3 5]
}
func ExampleRejectErr() {
list := []int64{0, 1, 2, 3, 4, 5}
result, err := RejectErr(list, func(x int64, index int) (bool, error) {
if x == 3 {
return false, errors.New("number 3 is not allowed")
}
return x%2 == 0, nil
})
fmt.Printf("%v, %v\n", result, err)
result, err = RejectErr([]int64{0, 1, 2, 4, 6}, func(x int64, index int) (bool, error) {
return x%2 == 0, nil
})
fmt.Printf("%v, %v\n", result, err)
// Output:
// [], number 3 is not allowed
// [1],
}
func ExampleCount() {
list := []int{0, 1, 2, 3, 4, 5, 0, 1, 2, 3}
result := Count(list, 2)
fmt.Printf("%v", result)
// Output: 2
}
func ExampleCountBy() {
list := []int{0, 1, 2, 3, 4, 5, 0, 1, 2, 3}
result := CountBy(list, func(i int) bool {
return i < 4
})
fmt.Printf("%v", result)
// Output: 8
}
func ExampleCountByErr() {
list := []int{0, 1, 2, 3, 4, 5, 0, 1, 2, 3}
_, err := CountByErr(list, func(i int) (bool, error) {
if i == 3 {
return false, errors.New("number 3 is not allowed")
}
return i < 4, nil
})
fmt.Printf("%v", err)
// Output: number 3 is not allowed
}
func ExampleCountValues() {
result1 := CountValues([]int{})
result2 := CountValues([]int{1, 2})
result3 := CountValues([]int{1, 2, 2})
result4 := CountValues([]string{"foo", "bar", ""})
result5 := CountValues([]string{"foo", "bar", "bar"})
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
// Output:
// map[]
// map[1:1 2:1]
// map[1:1 2:2]
// map[:1 bar:1 foo:1]
// map[bar:2 foo:1]
}
func ExampleCountValuesBy() {
isEven := func(v int) bool {
return v%2 == 0
}
result1 := CountValuesBy([]int{}, isEven)
result2 := CountValuesBy([]int{1, 2}, isEven)
result3 := CountValuesBy([]int{1, 2, 2}, isEven)
length := func(v string) int {
return len(v)
}
result4 := CountValuesBy([]string{"foo", "bar", ""}, length)
result5 := CountValuesBy([]string{"foo", "bar", "bar"}, length)
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
// Output:
// map[]
// map[false:1 true:1]
// map[false:1 true:2]
// map[0:1 3:2]
// map[3:3]
}
func ExampleSubset() {
list := []int{0, 1, 2, 3, 4, 5}
result := Subset(list, 2, 3)
fmt.Printf("%v", result)
// Output: [2 3 4]
}
func ExampleSlice() {
list := []int{0, 1, 2, 3, 4, 5}
result := Slice(list, 1, 4)
fmt.Printf("%v\n", result)
result = Slice(list, 4, 1)
fmt.Printf("%v\n", result)
result = Slice(list, 4, 5)
fmt.Printf("%v\n", result)
// Output:
// [1 2 3]
// []
// [4]
}
func ExampleReplace() {
list := []int{0, 1, 0, 1, 2, 3, 0}
result := Replace(list, 0, 42, 1)
fmt.Printf("%v\n", result)
result = Replace(list, -1, 42, 1)
fmt.Printf("%v\n", result)
result = Replace(list, 0, 42, 2)
fmt.Printf("%v\n", result)
result = Replace(list, 0, 42, -1)
fmt.Printf("%v\n", result)
// Output:
// [42 1 0 1 2 3 0]
// [0 1 0 1 2 3 0]
// [42 1 42 1 2 3 0]
// [42 1 42 1 2 3 42]
}
func ExampleClone() {
input := []int{1, 2, 3, 4, 5}
output := Clone(input)
input[0] = 42
fmt.Printf("%v", output)
// Output: [1 2 3 4 5]
}
func ExampleCompact() {
list := []string{"", "foo", "", "bar", ""}
result := Compact(list)
fmt.Printf("%v", result)
// Output: [foo bar]
}
func ExampleIsSorted() {
list := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
result := IsSorted(list)
fmt.Printf("%v", result)
// Output: true
}
func ExampleIsSortedBy() {
list := []string{"a", "bb", "ccc"}
result := IsSortedBy(list, func(s string) int {
return len(s)
})
fmt.Printf("%v", result)
// Output: true
}
func ExampleCut() {
collection := []string{"a", "b", "c", "d", "e", "f", "g"}
// Test with valid separator
before, after, found := Cut(collection, []string{"b", "c", "d"})
fmt.Printf("Before: %v, After: %v, Found: %t\n", before, after, found)
// Test with separator not found
before2, after2, found2 := Cut(collection, []string{"z"})
fmt.Printf("Before: %v, After: %v, Found: %t\n", before2, after2, found2)
// Test with separator at beginning
before3, after3, found3 := Cut(collection, []string{"a", "b"})
fmt.Printf("Before: %v, After: %v, Found: %t\n", before3, after3, found3)
// Output:
// Before: [a], After: [e f g], Found: true
// Before: [a b c d e f g], After: [], Found: false
// Before: [], After: [c d e f g], Found: true
}
func ExampleCutPrefix() {
collection := []string{"a", "b", "c", "d", "e", "f", "g"}
// Test with valid prefix
after, found := CutPrefix(collection, []string{"a", "b", "c"})
fmt.Printf("After: %v, Found: %t\n", after, found)
// Test with prefix not found
after2, found2 := CutPrefix(collection, []string{"b"})
fmt.Printf("After: %v, Found: %t\n", after2, found2)
// Test with empty prefix
after3, found3 := CutPrefix(collection, []string{})
fmt.Printf("After: %v, Found: %t\n", after3, found3)
// Output:
// After: [d e f g], Found: true
// After: [a b c d e f g], Found: false
// After: [a b c d e f g], Found: true
}
func ExampleCutSuffix() {
collection := []string{"a", "b", "c", "d", "e", "f", "g"}
// Test with valid suffix
before, found := CutSuffix(collection, []string{"f", "g"})
fmt.Printf("Before: %v, Found: %t\n", before, found)
// Test with suffix not found
before2, found2 := CutSuffix(collection, []string{"b"})
fmt.Printf("Before: %v, Found: %t\n", before2, found2)
// Test with empty suffix
before3, found3 := CutSuffix(collection, []string{})
fmt.Printf("Before: %v, Found: %t\n", before3, found3)
// Output:
// Before: [a b c d e], Found: true
// Before: [a b c d e f g], Found: false
// Before: [a b c d e f g], Found: true
}
func ExampleTrim() {
collection := []int{0, 1, 2, 0, 3, 0}
// Test with valid cutset
result := Trim(collection, []int{0})
fmt.Printf("Trim with cutset {0}: %v\n", result)
// Test with string collection
words := []string{" hello ", "world", " "}
result2 := Trim(words, []string{" "})
fmt.Printf("Trim with string cutset: %v\n", result2)
// Test with no cutset elements
result3 := Trim(collection, []int{5})
fmt.Printf("Trim with cutset {5} (not present): %v\n", result3)
// Output:
// Trim with cutset {0}: [1 2 0 3]
// Trim with string cutset: [ hello world ]
// Trim with cutset {5} (not present): [0 1 2 0 3 0]
}
func ExampleTrimLeft() {
collection := []int{0, 1, 2, 0, 3, 0}
// Test with valid cutset
result := TrimLeft(collection, []int{0})
fmt.Printf("TrimLeft with cutset {0}: %v\n", result)
// Test with string collection
words := []string{" hello ", "world", " "}
result2 := TrimLeft(words, []string{" "})
fmt.Printf("TrimLeft with string cutset: %v\n", result2)
// Test with no cutset elements
result3 := TrimLeft(collection, []int{5})
fmt.Printf("TrimLeft with cutset {5} (not present): %v\n", result3)
// Output:
// TrimLeft with cutset {0}: [1 2 0 3 0]
// TrimLeft with string cutset: [ hello world ]
// TrimLeft with cutset {5} (not present): [0 1 2 0 3 0]
}
func ExampleTrimPrefix() {
collection := []int{1, 2, 1, 2, 3}
// Test with valid prefix
result := TrimPrefix(collection, []int{1, 2})
fmt.Printf("TrimPrefix with prefix {1,2}: %v\n", result)
// Test with string collection
words := []string{"hello", "hello", "world"}
result2 := TrimPrefix(words, []string{"hello"})
fmt.Printf("TrimPrefix with string prefix: %v\n", result2)
// Test with prefix not present
result3 := TrimPrefix(collection, []int{5, 6})
fmt.Printf("TrimPrefix with prefix {5,6} (not present): %v\n", result3)
// Output:
// TrimPrefix with prefix {1,2}: [3]
// TrimPrefix with string prefix: [world]
// TrimPrefix with prefix {5,6} (not present): [1 2 1 2 3]
}
func ExampleTrimRight() {
collection := []int{0, 1, 2, 0, 3, 0}
// Test with valid cutset
result := TrimRight(collection, []int{0})
fmt.Printf("TrimRight with cutset {0}: %v\n", result)
// Test with string collection
words := []string{" hello ", "world", " "}
result2 := TrimRight(words, []string{" "})
fmt.Printf("TrimRight with string cutset: %v\n", result2)
// Test with no cutset elements
result3 := TrimRight(collection, []int{5})
fmt.Printf("TrimRight with cutset {5} (not present): %v\n", result3)
// Output:
// TrimRight with cutset {0}: [0 1 2 0 3]
// TrimRight with string cutset: [ hello world ]
// TrimRight with cutset {5} (not present): [0 1 2 0 3 0]
}
func ExampleTrimSuffix() {
collection := []int{1, 2, 1, 2, 3}
// Test with valid suffix
result := TrimSuffix(collection, []int{1, 2})
fmt.Printf("TrimSuffix with suffix {1,2}: %v\n", result)
// Test with string collection
words := []string{"hello", "world", "test"}
result2 := TrimSuffix(words, []string{"test"})
fmt.Printf("TrimSuffix with string suffix: %v\n", result2)
// Test with suffix not present
result3 := TrimSuffix(collection, []int{5, 6})
fmt.Printf("TrimSuffix with suffix {5,6} (not present): %v\n", result3)
// Output:
// TrimSuffix with suffix {1,2}: [1 2 1 2 3]
// TrimSuffix with string suffix: [hello world]
// TrimSuffix with suffix {5,6} (not present): [1 2 1 2 3]
}
func ExampleSubstring() {
result1 := Substring("hello", 2, 3)
result2 := Substring("hello", -4, 3)
result3 := Substring("hello", -2, math.MaxUint)
result4 := Substring("🏠🐶🐱", 0, 2)
result5 := Substring("你好,世界", 0, 3)
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
fmt.Printf("%v\n", result5)
// Output:
// llo
// ell
// lo
// 🏠🐶
// 你好,
}
func ExampleChunkString() {
result1 := ChunkString("123456", 2)
result2 := ChunkString("1234567", 2)
result3 := ChunkString("", 2)
result4 := ChunkString("1", 2)
fmt.Printf("%v\n", result1)
fmt.Printf("%v\n", result2)
fmt.Printf("%v\n", result3)
fmt.Printf("%v\n", result4)
// Output:
// [12 34 56]
// [12 34 56 7]
// []
// [1]
}
func ExampleRuneLength() {
result1, chars1 := RuneLength("hellô"), len("hellô")
result2, chars2 := RuneLength("🤘"), len("🤘")
fmt.Printf("%v %v\n", result1, chars1)
fmt.Printf("%v %v\n", result2, chars2)
// Output:
// 5 6
// 1 4
}
func ExampleT2() {
result := T2("hello", 2)
fmt.Printf("%v %v", result.A, result.B)
// Output: hello 2
}
func ExampleT3() {
result := T3("hello", 2, true)
fmt.Printf("%v %v %v", result.A, result.B, result.C)
// Output: hello 2 true
}
func ExampleT4() {
result := T4("hello", 2, true, foo{bar: "bar"})
fmt.Printf("%v %v %v %v", result.A, result.B, result.C, result.D)
// Output: hello 2 true {bar}
}
func ExampleT5() {
result := T5("hello", 2, true, foo{bar: "bar"}, 4.2)
fmt.Printf("%v %v %v %v %v", result.A, result.B, result.C, result.D, result.E)
// Output: hello 2 true {bar} 4.2
}
func ExampleT6() {
result := T6("hello", 2, true, foo{bar: "bar"}, 4.2, "plop")
fmt.Printf("%v %v %v %v %v %v", result.A, result.B, result.C, result.D, result.E, result.F)
// Output: hello 2 true {bar} 4.2 plop
}
func ExampleT7() {
result := T7("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false)
fmt.Printf("%v %v %v %v %v %v %v", result.A, result.B, result.C, result.D, result.E, result.F, result.G)
// Output: hello 2 true {bar} 4.2 plop false
}
func ExampleT8() {
result := T8("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42)
fmt.Printf("%v %v %v %v %v %v %v %v", result.A, result.B, result.C, result.D, result.E, result.F, result.G, result.H)
// Output: hello 2 true {bar} 4.2 plop false 42
}
func ExampleT9() {
result := T9("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42, "hello world")
fmt.Printf("%v %v %v %v %v %v %v %v %v", result.A, result.B, result.C, result.D, result.E, result.F, result.G, result.H, result.I)
// Output: hello 2 true {bar} 4.2 plop false 42 hello world
}
func ExampleUnpack2() {
a, b := Unpack2(T2("hello", 2))
fmt.Printf("%v %v", a, b)
// Output: hello 2
}
func ExampleUnpack3() {
a, b, c := Unpack3(T3("hello", 2, true))
fmt.Printf("%v %v %v", a, b, c)
// Output: hello 2 true
}
func ExampleUnpack4() {
a, b, c, d := Unpack4(T4("hello", 2, true, foo{bar: "bar"}))
fmt.Printf("%v %v %v %v", a, b, c, d)
// Output: hello 2 true {bar}
}
func ExampleUnpack5() {
a, b, c, d, e := Unpack5(T5("hello", 2, true, foo{bar: "bar"}, 4.2))
fmt.Printf("%v %v %v %v %v", a, b, c, d, e)
// Output: hello 2 true {bar} 4.2
}
func ExampleUnpack6() {
a, b, c, d, e, f := Unpack6(T6("hello", 2, true, foo{bar: "bar"}, 4.2, "plop"))
fmt.Printf("%v %v %v %v %v %v", a, b, c, d, e, f)
// Output: hello 2 true {bar} 4.2 plop
}
func ExampleUnpack7() {
a, b, c, d, e, f, g := Unpack7(T7("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false))
fmt.Printf("%v %v %v %v %v %v %v", a, b, c, d, e, f, g)
// Output: hello 2 true {bar} 4.2 plop false
}
func ExampleUnpack8() {
a, b, c, d, e, f, g, h := Unpack8(T8("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42))
fmt.Printf("%v %v %v %v %v %v %v %v", a, b, c, d, e, f, g, h)
// Output: hello 2 true {bar} 4.2 plop false 42
}
func ExampleUnpack9() {
a, b, c, d, e, f, g, h, i := Unpack9(T9("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42, "hello world"))
fmt.Printf("%v %v %v %v %v %v %v %v %v", a, b, c, d, e, f, g, h, i)
// Output: hello 2 true {bar} 4.2 plop false 42 hello world
}
func ExampleZip2() {
result := Zip2([]string{"hello"}, []int{2})
fmt.Printf("%v", result)
// Output: [{hello 2}]
}
func ExampleZip3() {
result := Zip3([]string{"hello"}, []int{2}, []bool{true})
fmt.Printf("%v", result)
// Output: [{hello 2 true}]
}
func ExampleZip4() {
result := Zip4([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}})
fmt.Printf("%v", result)
// Output: [{hello 2 true {bar}}]
}
func ExampleZip5() {
result := Zip5([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2})
fmt.Printf("%v", result)
// Output: [{hello 2 true {bar} 4.2}]
}
func ExampleZip6() {
result := Zip6([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"})
fmt.Printf("%v", result)
// Output: [{hello 2 true {bar} 4.2 plop}]
}
func ExampleZip7() {
result := Zip7([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false})
fmt.Printf("%v", result)
// Output: [{hello 2 true {bar} 4.2 plop false}]
}
func ExampleZip8() {
result := Zip8([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, []int{42})
fmt.Printf("%v", result)
// Output: [{hello 2 true {bar} 4.2 plop false 42}]
}
func ExampleZip9() {
result := Zip9([]string{"hello"}, []int{2}, []bool{true}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, []int{42}, []string{"hello world"})
fmt.Printf("%v", result)
// Output: [{hello 2 true {bar} 4.2 plop false 42 hello world}]
}
func ExampleUnzip2() {
a, b := Unzip2([]Tuple2[string, int]{T2("hello", 2)})
fmt.Printf("%v %v", a, b)
// Output: [hello] [2]
}
func ExampleUnzip3() {
a, b, c := Unzip3([]Tuple3[string, int, bool]{T3("hello", 2, true)})
fmt.Printf("%v %v %v", a, b, c)
// Output: [hello] [2] [true]
}
func ExampleUnzip4() {
a, b, c, d := Unzip4([]Tuple4[string, int, bool, foo]{T4("hello", 2, true, foo{bar: "bar"})})
fmt.Printf("%v %v %v %v", a, b, c, d)
// Output: [hello] [2] [true] [{bar}]
}
func ExampleUnzip5() {
a, b, c, d, e := Unzip5([]Tuple5[string, int, bool, foo, float64]{T5("hello", 2, true, foo{bar: "bar"}, 4.2)})
fmt.Printf("%v %v %v %v %v", a, b, c, d, e)
// Output: [hello] [2] [true] [{bar}] [4.2]
}
func ExampleUnzip6() {
a, b, c, d, e, f := Unzip6([]Tuple6[string, int, bool, foo, float64, string]{T6("hello", 2, true, foo{bar: "bar"}, 4.2, "plop")})
fmt.Printf("%v %v %v %v %v %v", a, b, c, d, e, f)
// Output: [hello] [2] [true] [{bar}] [4.2] [plop]
}
func ExampleUnzip7() {
a, b, c, d, e, f, g := Unzip7([]Tuple7[string, int, bool, foo, float64, string, bool]{T7("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false)})
fmt.Printf("%v %v %v %v %v %v %v", a, b, c, d, e, f, g)
// Output: [hello] [2] [true] [{bar}] [4.2] [plop] [false]
}
func ExampleUnzip8() {
a, b, c, d, e, f, g, h := Unzip8([]Tuple8[string, int, bool, foo, float64, string, bool, int]{T8("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42)})
fmt.Printf("%v %v %v %v %v %v %v %v", a, b, c, d, e, f, g, h)
// Output: [hello] [2] [true] [{bar}] [4.2] [plop] [false] [42]
}
func ExampleUnzip9() {
a, b, c, d, e, f, g, h, i := Unzip9([]Tuple9[string, int, bool, foo, float64, string, bool, int, string]{T9("hello", 2, true, foo{bar: "bar"}, 4.2, "plop", false, 42, "hello world")})
fmt.Printf("%v %v %v %v %v %v %v %v %v", a, b, c, d, e, f, g, h, i)
// Output: [hello] [2] [true] [{bar}] [4.2] [plop] [false] [42] [hello world]
}
func ExampleUnzipByErr2() {
a, b, err := UnzipByErr2([]string{"hello", "error", "world"}, func(str string) (string, int, error) {
if str == "error" {
return "", 0, errors.New("error string not allowed")
}
return str, len(str), nil
})
fmt.Printf("%v %v %v", len(a), len(b), err)
// Output: 0 0 error string not allowed
}
func ExampleUnzipByErr3() {
a, b, c, err := UnzipByErr3([]string{"hello", "error", "world"}, func(str string) (string, int, bool, error) {
if str == "error" {
return "", 0, false, errors.New("error string not allowed")
}
return str, len(str), len(str) > 4, nil
})
fmt.Printf("%v %v %v %v", len(a), len(b), len(c), err)
// Output: 0 0 0 error string not allowed
}
func ExampleUnzipByErr4() {
a, b, c, d, err := UnzipByErr4([]string{"hello", "error", "world"}, func(str string) (string, int, bool, float32, error) {
if str == "error" {
return "", 0, false, 0, errors.New("error string not allowed")
}
return str, len(str), len(str) > 4, float32(len(str)), nil
})
fmt.Printf("%v %v %v %v %v", len(a), len(b), len(c), len(d), err)
// Output: 0 0 0 0 error string not allowed
}
func ExampleUnzipByErr5() {
a, b, c, d, e, err := UnzipByErr5([]string{"hello", "error", "world"}, func(str string) (string, int, bool, float32, float64, error) {
if str == "error" {
return "", 0, false, 0, 0, errors.New("error string not allowed")
}
return str, len(str), len(str) > 4, float32(len(str)), float64(len(str)), nil
})
fmt.Printf("%v %v %v %v %v %v", len(a), len(b), len(c), len(d), len(e), err)
// Output: 0 0 0 0 0 error string not allowed
}
func ExampleUnzipByErr6() {
a, b, c, d, e, f, err := UnzipByErr6([]string{"hello", "error", "world"}, func(str string) (string, int, bool, float32, float64, int8, error) {
if str == "error" {
return "", 0, false, 0, 0, 0, errors.New("error string not allowed")
}
return str, len(str), len(str) > 4, float32(len(str)), float64(len(str)), int8(len(str)), nil
})
fmt.Printf("%v %v %v %v %v %v %v", len(a), len(b), len(c), len(d), len(e), len(f), err)
// Output: 0 0 0 0 0 0 error string not allowed
}
func ExampleUnzipByErr7() {
a, b, c, d, e, f, g, err := UnzipByErr7([]string{"hello", "error", "world"}, func(str string) (string, int, bool, float32, float64, int8, int16, error) {
if str == "error" {
return "", 0, false, 0, 0, 0, 0, errors.New("error string not allowed")
}
return str, len(str), len(str) > 4, float32(len(str)), float64(len(str)), int8(len(str)), int16(len(str)), nil
})
fmt.Printf("%v %v %v %v %v %v %v %v", len(a), len(b), len(c), len(d), len(e), len(f), len(g), err)
// Output: 0 0 0 0 0 0 0 error string not allowed
}
func ExampleUnzipByErr8() {
a, b, c, d, e, f, g, h, err := UnzipByErr8([]string{"hello", "error", "world"}, func(str string) (string, int, bool, float32, float64, int8, int16, int32, error) {
if str == "error" {
return "", 0, false, 0, 0, 0, 0, 0, errors.New("error string not allowed")
}
return str, len(str), len(str) > 4, float32(len(str)), float64(len(str)), int8(len(str)), int16(len(str)), int32(len(str)), nil
})
fmt.Printf("%v %v %v %v %v %v %v %v %v", len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), err)
// Output: 0 0 0 0 0 0 0 0 error string not allowed
}
func ExampleUnzipByErr9() {
a, b, c, d, e, f, g, h, i, err := UnzipByErr9([]string{"hello", "error", "world"}, func(str string) (string, int, bool, float32, float64, int8, int16, int32, int64, error) {
if str == "error" {
return "", 0, false, 0, 0, 0, 0, 0, 0, errors.New("error string not allowed")
}
return str, len(str), len(str) > 4, float32(len(str)), float64(len(str)), int8(len(str)), int16(len(str)), int32(len(str)), int64(len(str)), nil
})
fmt.Printf("%v %v %v %v %v %v %v %v %v %v", len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i), err)
// Output: 0 0 0 0 0 0 0 0 0 error string not allowed
}
func ExampleCrossJoin2() {
result := CrossJoin2([]string{"a", "b"}, []int{1, 2, 3, 4})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1}
// {a 2}
// {a 3}
// {a 4}
// {b 1}
// {b 2}
// {b 3}
// {b 4}
}
func ExampleCrossJoin3() {
result := CrossJoin3([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true}
// {a 1 false}
// {a 2 true}
// {a 2 false}
// {a 3 true}
// {a 3 false}
// {a 4 true}
// {a 4 false}
// {b 1 true}
// {b 1 false}
// {b 2 true}
// {b 2 false}
// {b 3 true}
// {b 3 false}
// {b 4 true}
// {b 4 false}
}
func ExampleCrossJoin4() {
result := CrossJoin4([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar}}
// {a 1 false {bar}}
// {a 2 true {bar}}
// {a 2 false {bar}}
// {a 3 true {bar}}
// {a 3 false {bar}}
// {a 4 true {bar}}
// {a 4 false {bar}}
// {b 1 true {bar}}
// {b 1 false {bar}}
// {b 2 true {bar}}
// {b 2 false {bar}}
// {b 3 true {bar}}
// {b 3 false {bar}}
// {b 4 true {bar}}
// {b 4 false {bar}}
}
func ExampleCrossJoin5() {
result := CrossJoin5([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2}
// {a 1 false {bar} 4.2}
// {a 2 true {bar} 4.2}
// {a 2 false {bar} 4.2}
// {a 3 true {bar} 4.2}
// {a 3 false {bar} 4.2}
// {a 4 true {bar} 4.2}
// {a 4 false {bar} 4.2}
// {b 1 true {bar} 4.2}
// {b 1 false {bar} 4.2}
// {b 2 true {bar} 4.2}
// {b 2 false {bar} 4.2}
// {b 3 true {bar} 4.2}
// {b 3 false {bar} 4.2}
// {b 4 true {bar} 4.2}
// {b 4 false {bar} 4.2}
}
func ExampleCrossJoin6() {
result := CrossJoin6([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2 plop}
// {a 1 false {bar} 4.2 plop}
// {a 2 true {bar} 4.2 plop}
// {a 2 false {bar} 4.2 plop}
// {a 3 true {bar} 4.2 plop}
// {a 3 false {bar} 4.2 plop}
// {a 4 true {bar} 4.2 plop}
// {a 4 false {bar} 4.2 plop}
// {b 1 true {bar} 4.2 plop}
// {b 1 false {bar} 4.2 plop}
// {b 2 true {bar} 4.2 plop}
// {b 2 false {bar} 4.2 plop}
// {b 3 true {bar} 4.2 plop}
// {b 3 false {bar} 4.2 plop}
// {b 4 true {bar} 4.2 plop}
// {b 4 false {bar} 4.2 plop}
}
func ExampleCrossJoin7() {
result := CrossJoin7([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2 plop false}
// {a 1 false {bar} 4.2 plop false}
// {a 2 true {bar} 4.2 plop false}
// {a 2 false {bar} 4.2 plop false}
// {a 3 true {bar} 4.2 plop false}
// {a 3 false {bar} 4.2 plop false}
// {a 4 true {bar} 4.2 plop false}
// {a 4 false {bar} 4.2 plop false}
// {b 1 true {bar} 4.2 plop false}
// {b 1 false {bar} 4.2 plop false}
// {b 2 true {bar} 4.2 plop false}
// {b 2 false {bar} 4.2 plop false}
// {b 3 true {bar} 4.2 plop false}
// {b 3 false {bar} 4.2 plop false}
// {b 4 true {bar} 4.2 plop false}
// {b 4 false {bar} 4.2 plop false}
}
func ExampleCrossJoin8() {
result := CrossJoin8([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, []int{42})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2 plop false 42}
// {a 1 false {bar} 4.2 plop false 42}
// {a 2 true {bar} 4.2 plop false 42}
// {a 2 false {bar} 4.2 plop false 42}
// {a 3 true {bar} 4.2 plop false 42}
// {a 3 false {bar} 4.2 plop false 42}
// {a 4 true {bar} 4.2 plop false 42}
// {a 4 false {bar} 4.2 plop false 42}
// {b 1 true {bar} 4.2 plop false 42}
// {b 1 false {bar} 4.2 plop false 42}
// {b 2 true {bar} 4.2 plop false 42}
// {b 2 false {bar} 4.2 plop false 42}
// {b 3 true {bar} 4.2 plop false 42}
// {b 3 false {bar} 4.2 plop false 42}
// {b 4 true {bar} 4.2 plop false 42}
// {b 4 false {bar} 4.2 plop false 42}
}
func ExampleCrossJoin9() {
result := CrossJoin9([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, []int{42}, []string{"hello world"})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// {a 1 true {bar} 4.2 plop false 42 hello world}
// {a 1 false {bar} 4.2 plop false 42 hello world}
// {a 2 true {bar} 4.2 plop false 42 hello world}
// {a 2 false {bar} 4.2 plop false 42 hello world}
// {a 3 true {bar} 4.2 plop false 42 hello world}
// {a 3 false {bar} 4.2 plop false 42 hello world}
// {a 4 true {bar} 4.2 plop false 42 hello world}
// {a 4 false {bar} 4.2 plop false 42 hello world}
// {b 1 true {bar} 4.2 plop false 42 hello world}
// {b 1 false {bar} 4.2 plop false 42 hello world}
// {b 2 true {bar} 4.2 plop false 42 hello world}
// {b 2 false {bar} 4.2 plop false 42 hello world}
// {b 3 true {bar} 4.2 plop false 42 hello world}
// {b 3 false {bar} 4.2 plop false 42 hello world}
// {b 4 true {bar} 4.2 plop false 42 hello world}
// {b 4 false {bar} 4.2 plop false 42 hello world}
}
func ExampleCrossJoinBy2() {
result := CrossJoinBy2([]string{"a", "b"}, []int{1, 2, 3, 4}, func(a string, b int) string {
return fmt.Sprintf("%v-%v", a, b)
})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1
// a-2
// a-3
// a-4
// b-1
// b-2
// b-3
// b-4
}
func ExampleCrossJoinBy3() {
result := CrossJoinBy3([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, func(a string, b int, c bool) string {
return fmt.Sprintf("%v-%v-%v", a, b, c)
})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true
// a-1-false
// a-2-true
// a-2-false
// a-3-true
// a-3-false
// a-4-true
// a-4-false
// b-1-true
// b-1-false
// b-2-true
// b-2-false
// b-3-true
// b-3-false
// b-4-true
// b-4-false
}
func ExampleCrossJoinBy4() {
result := CrossJoinBy4([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, func(a string, b int, c bool, d foo) string {
return fmt.Sprintf("%v-%v-%v-%v", a, b, c, d)
})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}
// a-1-false-{bar}
// a-2-true-{bar}
// a-2-false-{bar}
// a-3-true-{bar}
// a-3-false-{bar}
// a-4-true-{bar}
// a-4-false-{bar}
// b-1-true-{bar}
// b-1-false-{bar}
// b-2-true-{bar}
// b-2-false-{bar}
// b-3-true-{bar}
// b-3-false-{bar}
// b-4-true-{bar}
// b-4-false-{bar}
}
func ExampleCrossJoinBy5() {
result := CrossJoinBy5([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, func(a string, b int, c bool, d foo, e float64) string {
return fmt.Sprintf("%v-%v-%v-%v-%v", a, b, c, d, e)
})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2
// a-1-false-{bar}-4.2
// a-2-true-{bar}-4.2
// a-2-false-{bar}-4.2
// a-3-true-{bar}-4.2
// a-3-false-{bar}-4.2
// a-4-true-{bar}-4.2
// a-4-false-{bar}-4.2
// b-1-true-{bar}-4.2
// b-1-false-{bar}-4.2
// b-2-true-{bar}-4.2
// b-2-false-{bar}-4.2
// b-3-true-{bar}-4.2
// b-3-false-{bar}-4.2
// b-4-true-{bar}-4.2
// b-4-false-{bar}-4.2
}
func ExampleCrossJoinBy6() {
result := CrossJoinBy6([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, func(a string, b int, c bool, d foo, e float64, f string) string {
return fmt.Sprintf("%v-%v-%v-%v-%v-%v", a, b, c, d, e, f)
})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2-plop
// a-1-false-{bar}-4.2-plop
// a-2-true-{bar}-4.2-plop
// a-2-false-{bar}-4.2-plop
// a-3-true-{bar}-4.2-plop
// a-3-false-{bar}-4.2-plop
// a-4-true-{bar}-4.2-plop
// a-4-false-{bar}-4.2-plop
// b-1-true-{bar}-4.2-plop
// b-1-false-{bar}-4.2-plop
// b-2-true-{bar}-4.2-plop
// b-2-false-{bar}-4.2-plop
// b-3-true-{bar}-4.2-plop
// b-3-false-{bar}-4.2-plop
// b-4-true-{bar}-4.2-plop
// b-4-false-{bar}-4.2-plop
}
func ExampleCrossJoinBy7() {
result := CrossJoinBy7([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, func(a string, b int, c bool, d foo, e float64, f string, g bool) string {
return fmt.Sprintf("%v-%v-%v-%v-%v-%v-%v", a, b, c, d, e, f, g)
})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2-plop-false
// a-1-false-{bar}-4.2-plop-false
// a-2-true-{bar}-4.2-plop-false
// a-2-false-{bar}-4.2-plop-false
// a-3-true-{bar}-4.2-plop-false
// a-3-false-{bar}-4.2-plop-false
// a-4-true-{bar}-4.2-plop-false
// a-4-false-{bar}-4.2-plop-false
// b-1-true-{bar}-4.2-plop-false
// b-1-false-{bar}-4.2-plop-false
// b-2-true-{bar}-4.2-plop-false
// b-2-false-{bar}-4.2-plop-false
// b-3-true-{bar}-4.2-plop-false
// b-3-false-{bar}-4.2-plop-false
// b-4-true-{bar}-4.2-plop-false
// b-4-false-{bar}-4.2-plop-false
}
func ExampleCrossJoinBy8() {
result := CrossJoinBy8([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, []int{42}, func(a string, b int, c bool, d foo, e float64, f string, g bool, h int) string {
return fmt.Sprintf("%v-%v-%v-%v-%v-%v-%v-%v", a, b, c, d, e, f, g, h)
})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2-plop-false-42
// a-1-false-{bar}-4.2-plop-false-42
// a-2-true-{bar}-4.2-plop-false-42
// a-2-false-{bar}-4.2-plop-false-42
// a-3-true-{bar}-4.2-plop-false-42
// a-3-false-{bar}-4.2-plop-false-42
// a-4-true-{bar}-4.2-plop-false-42
// a-4-false-{bar}-4.2-plop-false-42
// b-1-true-{bar}-4.2-plop-false-42
// b-1-false-{bar}-4.2-plop-false-42
// b-2-true-{bar}-4.2-plop-false-42
// b-2-false-{bar}-4.2-plop-false-42
// b-3-true-{bar}-4.2-plop-false-42
// b-3-false-{bar}-4.2-plop-false-42
// b-4-true-{bar}-4.2-plop-false-42
// b-4-false-{bar}-4.2-plop-false-42
}
func ExampleCrossJoinBy9() {
result := CrossJoinBy9([]string{"a", "b"}, []int{1, 2, 3, 4}, []bool{true, false}, []foo{{bar: "bar"}}, []float64{4.2}, []string{"plop"}, []bool{false}, []int{42}, []string{"hello world"}, func(a string, b int, c bool, d foo, e float64, f string, g bool, h int, i string) string {
return fmt.Sprintf("%v-%v-%v-%v-%v-%v-%v-%v-%v", a, b, c, d, e, f, g, h, i)
})
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// a-1-true-{bar}-4.2-plop-false-42-hello world
// a-1-false-{bar}-4.2-plop-false-42-hello world
// a-2-true-{bar}-4.2-plop-false-42-hello world
// a-2-false-{bar}-4.2-plop-false-42-hello world
// a-3-true-{bar}-4.2-plop-false-42-hello world
// a-3-false-{bar}-4.2-plop-false-42-hello world
// a-4-true-{bar}-4.2-plop-false-42-hello world
// a-4-false-{bar}-4.2-plop-false-42-hello world
// b-1-true-{bar}-4.2-plop-false-42-hello world
// b-1-false-{bar}-4.2-plop-false-42-hello world
// b-2-true-{bar}-4.2-plop-false-42-hello world
// b-2-false-{bar}-4.2-plop-false-42-hello world
// b-3-true-{bar}-4.2-plop-false-42-hello world
// b-3-false-{bar}-4.2-plop-false-42-hello world
// b-4-true-{bar}-4.2-plop-false-42-hello world
// b-4-false-{bar}-4.2-plop-false-42-hello world
}
func ExampleCrossJoinByErr2() {
result, err := CrossJoinByErr2([]string{"a", "b"}, []int{1, 2}, func(a string, b int) (string, error) {
if a == "b" {
return "", errors.New("b not allowed")
}
return fmt.Sprintf("%v-%v", a, b), nil
})
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// error: b not allowed
}
func ExampleZipByErr2() {
result, err := ZipByErr2([]string{"a", "b", "c"}, []int{1, 2, 3}, func(a string, b int) (string, error) {
if a == "b" {
return "", errors.New("b is not allowed")
}
return fmt.Sprintf("%v-%v", a, b), nil
})
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
for _, r := range result {
fmt.Printf("%v\n", r)
}
// Output:
// error: b is not allowed
}
func ExampleIntersect() {
result := Intersect([]int{0, 3, 5, 7}, []int{3, 5}, []int{0, 1, 2, 0, 3, 0})
fmt.Printf("%v", result)
// Output:
// [3]
}
func ExampleIntersectBy() {
result := IntersectBy(strconv.Itoa, []int{0, 6, 0, 3}, []int{0, 1, 2, 3, 4, 5}, []int{0, 6})
fmt.Printf("%v", result)
// Output:
// [0]
}
================================================
FILE: lo_test.go
================================================
package lo
import (
"fmt"
"runtime"
"strconv"
"testing"
"time"
"github.com/samber/lo/internal/xtime"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
xtime.SetClock(xtime.NewFakeClock())
goleak.VerifyTestMain(m)
}
// https://github.com/stretchr/testify/issues/1101
func testWithTimeout(t *testing.T, timeout time.Duration) {
t.Helper()
testFinished := make(chan struct{})
t.Cleanup(func() {
close(testFinished)
})
line := ""
funcName := ""
var pc [1]uintptr
n := runtime.Callers(2, pc[:])
if n > 0 {
frames := runtime.CallersFrames(pc[:])
frame, _ := frames.Next()
line = frame.File + ":" + strconv.Itoa(frame.Line)
funcName = frame.Function
}
go func() {
select {
case <-testFinished:
case <-time.After(timeout):
if line == "" || funcName == "" {
panic(fmt.Sprintf("Test timed out after: %v", timeout))
}
panic(fmt.Sprintf("%s: Test timed out after: %v\n%s", funcName, timeout, line))
// t.Errorf("Test timed out after: %v", timeout)
// os.Exit(1)
}
}()
}
type foo struct {
bar string
}
func (f foo) Clone() foo {
return foo{f.bar}
}
================================================
FILE: map.go
================================================
package lo
// Keys creates a slice of the map keys.
// Play: https://go.dev/play/p/Uu11fHASqrU
func Keys[K comparable, V any](in ...map[K]V) []K {
size := 0
for i := range in {
size += len(in[i])
}
result := make([]K, 0, size)
for i := range in {
for k := range in[i] {
result = append(result, k)
}
}
return result
}
// UniqKeys creates a slice of unique keys in the map.
// Play: https://go.dev/play/p/TPKAb6ILdHk
func UniqKeys[K comparable, V any](in ...map[K]V) []K {
size := 0
for i := range in {
size += len(in[i])
}
seen := make(map[K]struct{}, size)
result := make([]K, 0, size)
for i := range in {
for k := range in[i] {
if _, exists := seen[k]; exists {
continue
}
seen[k] = struct{}{}
result = append(result, k)
}
}
return result
}
// HasKey returns whether the given key exists.
// Play: https://go.dev/play/p/aVwubIvECqS
func HasKey[K comparable, V any](in map[K]V, key K) bool {
_, ok := in[key]
return ok
}
// Values creates a slice of the map values.
// Play: https://go.dev/play/p/nnRTQkzQfF6
func Values[K comparable, V any](in ...map[K]V) []V {
size := 0
for i := range in {
size += len(in[i])
}
result := make([]V, 0, size)
for i := range in {
for _, v := range in[i] {
result = append(result, v)
}
}
return result
}
// UniqValues creates a slice of unique values in the map.
// Play: https://go.dev/play/p/nf6bXMh7rM3
func UniqValues[K, V comparable](in ...map[K]V) []V {
size := 0
for i := range in {
size += len(in[i])
}
seen := make(map[V]struct{}, size)
result := make([]V, 0, size)
for i := range in {
for _, v := range in[i] {
if _, exists := seen[v]; exists {
continue
}
seen[v] = struct{}{}
result = append(result, v)
}
}
return result
}
// ValueOr returns the value of the given key or the fallback value if the key is not present.
// Play: https://go.dev/play/p/bAq9mHErB4V
func ValueOr[K comparable, V any](in map[K]V, key K, fallback V) V {
if v, ok := in[key]; ok {
return v
}
return fallback
}
// PickBy returns same map type filtered by given predicate.
// Play: https://go.dev/play/p/kdg8GR_QMmf
func PickBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map {
r := make(Map, len(in))
for k, v := range in {
if predicate(k, v) {
r[k] = v
}
}
return r
}
// PickByErr returns same map type filtered by given predicate.
// It returns the first error returned by the predicate.
func PickByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error) {
r := make(Map, len(in))
for k, v := range in {
ok, err := predicate(k, v)
if err != nil {
return nil, err
}
if ok {
r[k] = v
}
}
return r, nil
}
// PickByKeys returns same map type filtered by given keys.
// Play: https://go.dev/play/p/R1imbuci9qU
func PickByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
r := make(Map, len(keys))
for i := range keys {
if v, ok := in[keys[i]]; ok {
r[keys[i]] = v
}
}
return r
}
// PickByValues returns same map type filtered by given values.
// Play: https://go.dev/play/p/-_PPkSbO1Kc
func PickByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map {
r := make(Map, len(values))
seen := Keyify(values)
for k, v := range in {
if _, ok := seen[v]; ok {
r[k] = v
}
}
return r
}
// OmitBy returns same map type filtered by given predicate.
// Play: https://go.dev/play/p/EtBsR43bdsd
func OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map {
r := make(Map, len(in))
for k, v := range in {
if !predicate(k, v) {
r[k] = v
}
}
return r
}
// OmitByErr returns same map type filtered by given predicate.
// It returns the first error returned by the predicate.
func OmitByErr[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) (bool, error)) (Map, error) {
r := make(Map, len(in))
for k, v := range in {
ok, err := predicate(k, v)
if err != nil {
return nil, err
}
if !ok {
r[k] = v
}
}
return r, nil
}
// OmitByKeys returns same map type filtered by given keys.
// Play: https://go.dev/play/p/t1QjCrs-ysk
func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map {
r := make(Map, len(in))
for k, v := range in {
r[k] = v
}
for i := range keys {
delete(r, keys[i])
}
return r
}
// OmitByValues returns same map type filtered by given values.
// Play: https://go.dev/play/p/9UYZi-hrs8j
func OmitByValues[K, V comparable, Map ~map[K]V](in Map, values []V) Map {
r := make(Map, len(in))
seen := Keyify(values)
for k, v := range in {
if _, ok := seen[v]; !ok {
r[k] = v
}
}
return r
}
// Entries transforms a map into a slice of key/value pairs.
// Play: https://go.dev/play/p/_t4Xe34-Nl5
func Entries[K comparable, V any](in map[K]V) []Entry[K, V] {
entries := make([]Entry[K, V], 0, len(in))
for k, v := range in {
entries = append(entries, Entry[K, V]{
Key: k,
Value: v,
})
}
return entries
}
// ToPairs transforms a map into a slice of key/value pairs.
// Alias of Entries().
// Play: https://go.dev/play/p/3Dhgx46gawJ
func ToPairs[K comparable, V any](in map[K]V) []Entry[K, V] {
return Entries(in)
}
// FromEntries transforms a slice of key/value pairs into a map.
// Play: https://go.dev/play/p/oIr5KHFGCEN
func FromEntries[K comparable, V any](entries []Entry[K, V]) map[K]V {
out := make(map[K]V, len(entries))
for i := range entries {
out[entries[i].Key] = entries[i].Value
}
return out
}
// FromPairs transforms a slice of key/value pairs into a map.
// Alias of FromEntries().
// Play: https://go.dev/play/p/oIr5KHFGCEN
func FromPairs[K comparable, V any](entries []Entry[K, V]) map[K]V {
return FromEntries(entries)
}
// Invert creates a map composed of the inverted keys and values. If map
// contains duplicate values, subsequent values overwrite property assignments
// of previous values.
// Play: https://go.dev/play/p/rFQ4rak6iA1
func Invert[K, V comparable](in map[K]V) map[V]K {
out := make(map[V]K, len(in))
for k, v := range in {
out[v] = k
}
return out
}
// Assign merges multiple maps from left to right.
// Play: https://go.dev/play/p/VhwfJOyxf5o
func Assign[K comparable, V any, Map ~map[K]V](maps ...Map) Map {
count := 0
for i := range maps {
count += len(maps[i])
}
out := make(Map, count)
for i := range maps {
for k, v := range maps[i] {
out[k] = v
}
}
return out
}
// ChunkEntries splits a map into a slice of elements in groups of length equal to its size. If the map cannot be split evenly,
// the final chunk will contain the remaining elements.
// Play: https://go.dev/play/p/X_YQL6mmoD-
func ChunkEntries[K comparable, V any](m map[K]V, size int) []map[K]V {
if size <= 0 {
panic("lo.ChunkEntries: size must be greater than 0")
}
count := len(m)
if count == 0 {
return []map[K]V{}
}
result := make([]map[K]V, 0, ((count-1)/size)+1)
for k, v := range m {
if len(result) == 0 || len(result[len(result)-1]) == size {
result = append(result, make(map[K]V, size))
}
result[len(result)-1][k] = v
}
return result
}
// MapKeys manipulates map keys and transforms it to a map of another type.
// Play: https://go.dev/play/p/9_4WPIqOetJ
func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(value V, key K) R) map[R]V {
result := make(map[R]V, len(in))
for k, v := range in {
result[iteratee(v, k)] = v
}
return result
}
// MapKeysErr manipulates map keys and transforms it to a map of another type.
// It returns the first error returned by the iteratee.
func MapKeysErr[K comparable, V any, R comparable](in map[K]V, iteratee func(value V, key K) (R, error)) (map[R]V, error) {
result := make(map[R]V, len(in))
for k, v := range in {
r, err := iteratee(v, k)
if err != nil {
return nil, err
}
result[r] = v
}
return result, nil
}
// MapValues manipulates map values and transforms it to a map of another type.
// Play: https://go.dev/play/p/T_8xAfvcf0W
func MapValues[K comparable, V, R any](in map[K]V, iteratee func(value V, key K) R) map[K]R {
result := make(map[K]R, len(in))
for k, v := range in {
result[k] = iteratee(v, k)
}
return result
}
// MapValuesErr manipulates map values and transforms it to a map of another type.
// It returns the first error returned by the iteratee.
func MapValuesErr[K comparable, V, R any](in map[K]V, iteratee func(value V, key K) (R, error)) (map[K]R, error) {
result := make(map[K]R, len(in))
for k, v := range in {
r, err := iteratee(v, k)
if err != nil {
return nil, err
}
result[k] = r
}
return result, nil
}
// MapEntries manipulates map entries and transforms it to a map of another type.
// Play: https://go.dev/play/p/VuvNQzxKimT
func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2)) map[K2]V2 {
result := make(map[K2]V2, len(in))
for k1 := range in {
k2, v2 := iteratee(k1, in[k1])
result[k2] = v2
}
return result
}
// MapEntriesErr manipulates map entries and transforms it to a map of another type.
// It returns the first error returned by the iteratee.
func MapEntriesErr[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2, error)) (map[K2]V2, error) {
result := make(map[K2]V2, len(in))
for k1 := range in {
k2, v2, err := iteratee(k1, in[k1])
if err != nil {
return nil, err
}
result[k2] = v2
}
return result, nil
}
// MapToSlice transforms a map into a slice based on specified iteratee.
// Play: https://go.dev/play/p/4f5hbHyMf5h
func MapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) R) []R {
result := make([]R, 0, len(in))
for k, v := range in {
result = append(result, iteratee(k, v))
}
return result
}
// MapToSliceErr transforms a map into a slice based on specified iteratee.
// It returns the first error returned by the iteratee.
func MapToSliceErr[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) (R, error)) ([]R, error) {
result := make([]R, 0, len(in))
for k, v := range in {
r, err := iteratee(k, v)
if err != nil {
return nil, err
}
result = append(result, r)
}
return result, nil
}
// FilterMapToSlice transforms a map into a slice based on specified iteratee.
// The iteratee returns a value and a boolean. If the boolean is true, the value is added to the result slice.
// If the boolean is false, the value is not added to the result slice.
// The order of the keys in the input map is not specified and the order of the keys in the output slice is not guaranteed.
// Play: https://go.dev/play/p/jgsD_Kil9pV
func FilterMapToSlice[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) (R, bool)) []R {
result := make([]R, 0, len(in))
for k, v := range in {
if v, ok := iteratee(k, v); ok {
result = append(result, v)
}
}
return result
}
// FilterMapToSliceErr transforms a map into a slice based on specified iteratee.
// The iteratee returns a value, a boolean, and an error. If the boolean is true, the value is added to the result slice.
// If the boolean is false, the value is not added to the result slice.
// If an error is returned, iteration stops immediately and returns the error.
// The order of the keys in the input map is not specified and the order of the keys in the output slice is not guaranteed.
// Play: https://go.dev/play/p/YjFEORLBWvk
func FilterMapToSliceErr[K comparable, V, R any](in map[K]V, iteratee func(key K, value V) (R, bool, error)) ([]R, error) {
result := make([]R, 0, len(in))
for k, v := range in {
r, ok, err := iteratee(k, v)
if err != nil {
return nil, err
}
if ok {
result = append(result, r)
}
}
return result, nil
}
// FilterKeys transforms a map into a slice based on predicate returns true for specific elements.
// It is a mix of lo.Filter() and lo.Keys().
// Play: https://go.dev/play/p/OFlKXlPrBAe
func FilterKeys[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []K {
result := make([]K, 0, len(in))
for k, v := range in {
if predicate(k, v) {
result = append(result, k)
}
}
return result
}
// FilterValues transforms a map into a slice based on predicate returns true for specific elements.
// It is a mix of lo.Filter() and lo.Values().
// Play: https://go.dev/play/p/YVD5r_h-LX-
func FilterValues[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) []V {
result := make([]V, 0, len(in))
for k, v := range in {
if predicate(k, v) {
result = append(result, v)
}
}
return result
}
// FilterKeysErr transforms a map into a slice of keys based on predicate that can return an error.
// It is a mix of lo.Filter() and lo.Keys() with error handling.
// If the predicate returns true, the key is added to the result slice.
// If the predicate returns an error, iteration stops immediately and returns the error.
// The order of the keys in the input map is not specified.
// Play: https://go.dev/play/p/j2gUQzCTu4t
func FilterKeysErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]K, error) {
result := make([]K, 0, len(in))
for k, v := range in {
ok, err := predicate(k, v)
if err != nil {
return nil, err
}
if ok {
result = append(result, k)
}
}
return result, nil
}
// FilterValuesErr transforms a map into a slice of values based on predicate that can return an error.
// It is a mix of lo.Filter() and lo.Values() with error handling.
// If the predicate returns true, the value is added to the result slice.
// If the predicate returns an error, iteration stops immediately and returns the error.
// The order of the keys in the input map is not specified.
// Play: https://go.dev/play/p/hKvHlqLzbdE
func FilterValuesErr[K comparable, V any](in map[K]V, predicate func(key K, value V) (bool, error)) ([]V, error) {
result := make([]V, 0, len(in))
for k, v := range in {
ok, err := predicate(k, v)
if err != nil {
return nil, err
}
if ok {
result = append(result, v)
}
}
return result, nil
}
================================================
FILE: map_test.go
================================================
package lo
import (
"errors"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Keys(map[string]int{"foo": 1, "bar": 2})
is.ElementsMatch(r1, []string{"bar", "foo"})
r2 := Keys(map[string]int{})
is.Empty(r2)
r3 := Keys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
is.ElementsMatch(r3, []string{"bar", "baz", "foo"})
r4 := Keys[string, int]()
is.Empty(r4)
r5 := Keys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"bar": 3})
is.ElementsMatch(r5, []string{"bar", "bar", "foo"})
}
func TestUniqKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := UniqKeys(map[string]int{"foo": 1, "bar": 2})
is.ElementsMatch(r1, []string{"bar", "foo"})
r2 := UniqKeys(map[string]int{})
is.Empty(r2)
r3 := UniqKeys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
is.ElementsMatch(r3, []string{"bar", "baz", "foo"})
r4 := UniqKeys[string, int]()
is.Empty(r4)
r5 := UniqKeys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"foo": 1, "bar": 3})
is.ElementsMatch(r5, []string{"bar", "foo"})
// check order
r6 := UniqKeys(map[string]int{"foo": 1}, map[string]int{"bar": 3})
is.Equal([]string{"foo", "bar"}, r6)
}
func TestHasKey(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := HasKey(map[string]int{"foo": 1}, "bar")
is.False(r1)
r2 := HasKey(map[string]int{"foo": 1}, "foo")
is.True(r2)
}
func TestValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Values(map[string]int{"foo": 1, "bar": 2})
is.ElementsMatch(r1, []int{1, 2})
r2 := Values(map[string]int{})
is.Empty(r2)
r3 := Values(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
is.ElementsMatch(r3, []int{1, 2, 3})
r4 := Values[string, int]()
is.Empty(r4)
r5 := Values(map[string]int{"foo": 1, "bar": 2}, map[string]int{"foo": 1, "bar": 3})
is.ElementsMatch(r5, []int{1, 1, 2, 3})
}
func TestUniqValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := UniqValues(map[string]int{"foo": 1, "bar": 2})
is.ElementsMatch(r1, []int{1, 2})
r2 := UniqValues(map[string]int{})
is.Empty(r2)
r3 := UniqValues(map[string]int{"foo": 1, "bar": 2}, map[string]int{"baz": 3})
is.ElementsMatch(r3, []int{1, 2, 3})
r4 := UniqValues[string, int]()
is.Empty(r4)
r5 := UniqValues(map[string]int{"foo": 1, "bar": 2}, map[string]int{"foo": 1, "bar": 3})
is.ElementsMatch(r5, []int{1, 2, 3})
r6 := UniqValues(map[string]int{"foo": 1, "bar": 1}, map[string]int{"foo": 1, "bar": 3})
is.ElementsMatch(r6, []int{1, 3})
// check order
r7 := UniqValues(map[string]int{"foo": 1}, map[string]int{"bar": 3})
is.Equal([]int{1, 3}, r7)
}
func TestValueOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := ValueOr(map[string]int{"foo": 1}, "bar", 2)
is.Equal(2, r1)
r2 := ValueOr(map[string]int{"foo": 1}, "foo", 2)
is.Equal(1, r2)
}
func TestPickBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := PickBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) bool {
return value%2 == 1
})
is.Equal(map[string]int{"foo": 1, "baz": 3}, r1)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after := PickBy(before, func(key string, value int) bool { return true })
is.IsType(after, before, "type preserved")
}
func TestPickByErr(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input map[string]int
predicate func(string, int) (bool, error)
want map[string]int
wantErr string
wantCallCount int // 0 means don't check (maps have no order)
}{
{
name: "filter odd values",
input: map[string]int{"foo": 1, "bar": 2, "baz": 3},
predicate: func(key string, value int) (bool, error) {
return value%2 == 1, nil
},
want: map[string]int{"foo": 1, "baz": 3},
wantErr: "",
wantCallCount: 0, // map iteration order is not deterministic
},
{
name: "empty map",
input: map[string]int{},
predicate: func(key string, value int) (bool, error) {
return true, nil
},
want: map[string]int{},
wantErr: "",
wantCallCount: 0,
},
{
name: "error on specific key",
input: map[string]int{"foo": 1, "bar": 2, "baz": 3},
predicate: func(key string, value int) (bool, error) {
if key == "bar" {
return false, errors.New("bar not allowed")
}
return true, nil
},
want: nil,
wantErr: "bar not allowed",
wantCallCount: 0, // map iteration order is not deterministic
},
{
name: "filter all out",
input: map[string]int{"foo": 2, "bar": 4, "baz": 6},
predicate: func(key string, value int) (bool, error) {
return value%2 == 1, nil
},
want: map[string]int{},
wantErr: "",
wantCallCount: 0,
},
{
name: "filter all in",
input: map[string]int{"foo": 1, "bar": 3, "baz": 5},
predicate: func(key string, value int) (bool, error) {
return value%2 == 1, nil
},
want: map[string]int{"foo": 1, "bar": 3, "baz": 5},
wantErr: "",
wantCallCount: 0,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
got, err := PickByErr(tt.input, tt.predicate)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.Equal(tt.want, got)
}
})
}
t.Run("type preserved", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after, err := PickByErr(before, func(key string, value int) (bool, error) {
return true, nil
})
is.NoError(err)
is.IsType(after, before, "type preserved")
})
}
func TestPickByKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := PickByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz", "qux"})
is.Equal(map[string]int{"foo": 1, "baz": 3}, r1)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after := PickByKeys(before, []string{"foobar", "baz"})
is.IsType(after, before, "type preserved")
}
func TestPickByValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := PickByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3})
is.Equal(map[string]int{"foo": 1, "baz": 3}, r1)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after := PickByValues(before, []int{0, 3})
is.IsType(after, before, "type preserved")
}
func TestOmitBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := OmitBy(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(key string, value int) bool {
return value%2 == 1
})
is.Equal(map[string]int{"bar": 2}, r1)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after := PickBy(before, func(key string, value int) bool { return true })
is.IsType(after, before, "type preserved")
}
func TestOmitByErr(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input map[string]int
predicate func(string, int) (bool, error)
want map[string]int
wantErr string
wantCallCount int // 0 means don't check (maps have no order)
}{
{
name: "omit odd values",
input: map[string]int{"foo": 1, "bar": 2, "baz": 3},
predicate: func(key string, value int) (bool, error) {
return value%2 == 1, nil
},
want: map[string]int{"bar": 2},
wantErr: "",
wantCallCount: 0, // map iteration order is not deterministic
},
{
name: "empty map",
input: map[string]int{},
predicate: func(key string, value int) (bool, error) {
return true, nil
},
want: map[string]int{},
wantErr: "",
wantCallCount: 0,
},
{
name: "error on specific key",
input: map[string]int{"foo": 1, "bar": 2, "baz": 3},
predicate: func(key string, value int) (bool, error) {
if key == "bar" {
return false, errors.New("bar not allowed")
}
return true, nil
},
want: nil,
wantErr: "bar not allowed",
wantCallCount: 0, // map iteration order is not deterministic
},
{
name: "omit all",
input: map[string]int{"foo": 1, "bar": 3, "baz": 5},
predicate: func(key string, value int) (bool, error) {
return value%2 == 1, nil
},
want: map[string]int{},
wantErr: "",
wantCallCount: 0,
},
{
name: "omit none",
input: map[string]int{"foo": 2, "bar": 4, "baz": 6},
predicate: func(key string, value int) (bool, error) {
return value%2 == 1, nil
},
want: map[string]int{"foo": 2, "bar": 4, "baz": 6},
wantErr: "",
wantCallCount: 0,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
got, err := OmitByErr(tt.input, tt.predicate)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.Equal(tt.want, got)
}
})
}
t.Run("type preserved", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after, err := OmitByErr(before, func(key string, value int) (bool, error) {
return false, nil
})
is.NoError(err)
is.IsType(after, before, "type preserved")
})
}
func TestOmitByKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := OmitByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz", "qux"})
is.Equal(map[string]int{"bar": 2}, r1)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after := OmitByKeys(before, []string{"foobar", "baz"})
is.IsType(after, before, "type preserved")
}
func TestOmitByValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := OmitByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3})
is.Equal(map[string]int{"bar": 2}, r1)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after := OmitByValues(before, []int{0, 3})
is.IsType(after, before, "type preserved")
}
func TestEntries(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Entries(map[string]int{"foo": 1, "bar": 2})
is.ElementsMatch(r1, []Entry[string, int]{
{
Key: "foo",
Value: 1,
},
{
Key: "bar",
Value: 2,
},
})
}
func TestToPairs(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := ToPairs(map[string]int{"baz": 3, "qux": 4})
is.ElementsMatch(r1, []Entry[string, int]{
{
Key: "baz",
Value: 3,
},
{
Key: "qux",
Value: 4,
},
})
}
func TestFromEntries(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FromEntries([]Entry[string, int]{
{
Key: "foo",
Value: 1,
},
{
Key: "bar",
Value: 2,
},
})
is.Len(r1, 2)
is.Equal(1, r1["foo"])
is.Equal(2, r1["bar"])
}
func TestFromPairs(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FromPairs([]Entry[string, int]{
{
Key: "baz",
Value: 3,
},
{
Key: "qux",
Value: 4,
},
})
is.Len(r1, 2)
is.Equal(3, r1["baz"])
is.Equal(4, r1["qux"])
}
func TestInvert(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Invert(map[string]int{"a": 1, "b": 2})
r2 := Invert(map[string]int{"a": 1, "b": 2, "c": 1})
is.Equal(map[int]string{1: "a", 2: "b"}, r1)
is.Len(r2, 2)
}
func TestAssign(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Assign(map[string]int{"a": 1, "b": 2}, map[string]int{"b": 3, "c": 4})
is.Equal(map[string]int{"a": 1, "b": 3, "c": 4}, result1)
type myMap map[string]int
before := myMap{"": 0, "foobar": 6, "baz": 3}
after := Assign(before, before)
is.IsType(after, before, "type preserved")
}
func TestChunkEntries(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ChunkEntries(map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}, 2)
result2 := ChunkEntries(map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}, 3)
result3 := ChunkEntries(map[string]int{}, 2)
result4 := ChunkEntries(map[string]int{"a": 1}, 2)
result5 := ChunkEntries(map[string]int{"a": 1, "b": 2}, 1)
is.Len(result1, 3)
is.Len(result2, 2)
is.Empty(result3)
is.Len(result4, 1)
is.Len(result5, 2)
is.PanicsWithValue("lo.ChunkEntries: size must be greater than 0", func() {
ChunkEntries(map[string]int{"a": 1}, 0)
})
is.PanicsWithValue("lo.ChunkEntries: size must be greater than 0", func() {
ChunkEntries(map[string]int{"a": 1}, -1)
})
type myStruct struct {
Name string
Value int
}
allStructs := []myStruct{{"one", 1}, {"two", 2}, {"three", 3}}
nonempty := ChunkEntries(map[string]myStruct{"a": allStructs[0], "b": allStructs[1], "c": allStructs[2]}, 2)
is.Len(nonempty, 2)
originalMap := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
result6 := ChunkEntries(originalMap, 2)
for k := range result6[0] {
result6[0][k] = 10
}
is.Equal(map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}, originalMap)
}
func TestMapKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MapKeys(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(x, _ int) string {
return "Hello"
})
result2 := MapKeys(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(_, v int) string {
return strconv.FormatInt(int64(v), 10)
})
is.Len(result1, 1)
is.Equal(map[string]int{"1": 1, "2": 2, "3": 3, "4": 4}, result2)
}
func TestMapKeysErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Test successful case
result1, err := MapKeysErr(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(x, _ int) (string, error) {
return "Hello", nil
})
is.NoError(err)
is.Len(result1, 1)
result2, err := MapKeysErr(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(_, v int) (string, error) {
return strconv.FormatInt(int64(v), 10), nil
})
is.NoError(err)
is.Equal(map[string]int{"1": 1, "2": 2, "3": 3, "4": 4}, result2)
// Test error case - returns first error
_, err = MapKeysErr(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(v, _ int) (string, error) {
if v == 3 {
return "", fmt.Errorf("error at %d", v)
}
return strconv.FormatInt(int64(v), 10), nil
})
is.Error(err)
is.Equal("error at 3", err.Error())
// Test empty map
result3, err := MapKeysErr(map[int]int{}, func(v, _ int) (string, error) {
return strconv.FormatInt(int64(v), 10), nil
})
is.NoError(err)
is.Empty(result3)
// Test all keys collide - iteration order is non-deterministic, so check that value is one of expected values
result4, err := MapKeysErr(map[int]int{1: 1, 2: 2, 3: 3}, func(_, _ int) (string, error) {
return "same", nil
})
is.NoError(err)
is.Len(result4, 1)
is.Contains(result4, "same")
is.Contains([]int{1, 2, 3}, result4["same"])
}
func TestMapValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MapValues(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(x, _ int) string {
return "Hello"
})
result2 := MapValues(map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func(x, _ int) string {
return strconv.FormatInt(int64(x), 10)
})
is.Equal(map[int]string{1: "Hello", 2: "Hello", 3: "Hello", 4: "Hello"}, result1)
is.Equal(map[int]string{1: "1", 2: "2", 3: "3", 4: "4"}, result2)
}
func TestMapValuesErr(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input map[int]int
iteratee func(int, int) (string, error)
want map[int]string
wantErr string
}{
{
name: "successful transformation",
input: map[int]int{1: 1, 2: 2, 3: 3},
iteratee: func(x, _ int) (string, error) {
return strconv.FormatInt(int64(x), 10), nil
},
want: map[int]string{1: "1", 2: "2", 3: "3"},
wantErr: "",
},
{
name: "constant value transformation",
input: map[int]int{1: 1, 2: 2, 3: 3},
iteratee: func(_, _ int) (string, error) {
return "Hello", nil
},
want: map[int]string{1: "Hello", 2: "Hello", 3: "Hello"},
wantErr: "",
},
{
name: "error on specific value",
input: map[int]int{1: 1, 2: 2, 3: 3, 4: 4},
iteratee: func(x, _ int) (string, error) {
if x == 3 {
return "", fmt.Errorf("error at %d", x)
}
return strconv.FormatInt(int64(x), 10), nil
},
want: nil,
wantErr: "error at 3",
},
{
name: "error on first value",
input: map[int]int{1: 1, 2: 2},
iteratee: func(x, _ int) (string, error) {
if x == 1 {
return "", errors.New("cannot process 1")
}
return strconv.FormatInt(int64(x), 10), nil
},
want: nil,
wantErr: "cannot process 1",
},
{
name: "empty map",
input: map[int]int{},
iteratee: func(x, _ int) (string, error) { return strconv.FormatInt(int64(x), 10), nil },
want: map[int]string{},
wantErr: "",
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
got, err := MapValuesErr(tt.input, tt.iteratee)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.Equal(tt.want, got)
}
})
}
}
func TestMapEntries(t *testing.T) {
t.Parallel()
t.Run("Normal", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := MapEntries(map[string]int{"foo": 1, "bar": 2},
func(k string, v int) (string, int) {
return k, v + 1
})
is.Equal(map[string]int{"foo": 2, "bar": 3}, r1)
r2 := MapEntries(map[string]int{"foo": 1, "bar": 2},
func(k string, v int) (string, string) {
return k, k + strconv.Itoa(v)
})
is.Equal(map[string]string{"foo": "foo1", "bar": "bar2"}, r2)
r3 := MapEntries(map[string]int{"foo": 1, "bar": 2},
func(k string, v int) (string, string) {
return k, strconv.Itoa(v) + k
})
is.Equal(map[string]string{"foo": "1foo", "bar": "2bar"}, r3)
})
t.Run("NoMutation", func(t *testing.T) {
t.Parallel()
r1 := map[string]int{"foo": 1, "bar": 2}
MapEntries(r1, func(k string, v int) (string, string) {
return k, strconv.Itoa(v) + "!!"
})
assert.Equal(t, map[string]int{"foo": 1, "bar": 2}, r1)
})
t.Run("EmptyInput", func(t *testing.T) {
t.Parallel()
r1 := MapEntries(map[string]int{},
func(k string, v int) (string, string) {
return k, strconv.Itoa(v) + "!!"
})
assert.Empty(t, r1)
r2 := MapEntries(map[string]any{},
func(k string, v any) (string, any) {
return k, v
})
assert.Empty(t, r2)
})
t.Run("Identity", func(t *testing.T) {
t.Parallel()
r1 := MapEntries(map[string]int{"foo": 1, "bar": 2},
func(k string, v int) (string, int) {
return k, v
})
assert.Equal(t, map[string]int{"foo": 1, "bar": 2}, r1)
r2 := MapEntries(map[string]any{"foo": 1, "bar": "2", "ccc": true},
func(k string, v any) (string, any) {
return k, v
})
assert.Equal(t, map[string]any{"foo": 1, "bar": "2", "ccc": true}, r2)
})
t.Run("ToConstantEntry", func(t *testing.T) {
t.Parallel()
r1 := MapEntries(map[string]any{"foo": 1, "bar": "2", "ccc": true},
func(k string, v any) (string, any) {
return "key", "value"
})
assert.Equal(t, map[string]any{"key": "value"}, r1)
r2 := MapEntries(map[string]any{"foo": 1, "bar": "2", "ccc": true},
func(k string, v any) (string, any) {
return "b", 5
})
assert.Equal(t, map[string]any{"b": 5}, r2)
})
// // because using range over map, the order is not guaranteed
// // this test is not deterministic
// t.Run("OverlappingKeys", func(t *testing.T) {
// t.Parallel()
//
// r1 := MapEntries(map[string]any{"foo": 1, "foo2": 2, "Foo": 2, "Foo2": "2", "bar": "2", "ccc": true},
// func(k string, v any) (string, any) {
// return string(k[0]), v
// })
// assert.Equal(t, map[string]any{"F": "2", "b": "2", "c": true, "f": 1}, r1)
//
// r2 := MapEntries(map[string]string{"foo": "1", "foo2": "2", "Foo": "2", "Foo2": "2", "bar": "2", "ccc": "true"},
// func(k, v string) (string, string) {
// return v, k
// })
// assert.Equal(t, map[string]string{"1": "foo", "2": "bar", "true": "ccc"}, r2)
// })
t.Run("NormalMappers", func(t *testing.T) {
t.Parallel()
r1 := MapEntries(map[string]string{"foo": "1", "foo2": "2", "Foo": "2", "Foo2": "2", "bar": "2", "ccc": "true"},
func(k, v string) (string, string) {
return k, k + v
})
assert.Equal(t, map[string]string{"Foo": "Foo2", "Foo2": "Foo22", "bar": "bar2", "ccc": "ccctrue", "foo": "foo1", "foo2": "foo22"}, r1)
type myStruct struct {
name string
age int
}
r2 := MapEntries(map[string]myStruct{"1-11-1": {name: "foo", age: 1}, "2-22-2": {name: "bar", age: 2}},
func(k string, v myStruct) (string, string) {
return v.name, k
})
assert.Equal(t, map[string]string{"bar": "2-22-2", "foo": "1-11-1"}, r2)
})
}
func TestMapEntriesErr(t *testing.T) {
t.Parallel()
t.Run("same types", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input map[string]int
iteratee func(string, int) (string, int, error)
want map[string]int
wantErr string
}{
{
name: "increment values",
input: map[string]int{"foo": 1, "bar": 2},
iteratee: func(k string, v int) (string, int, error) {
return k, v + 1, nil
},
want: map[string]int{"foo": 2, "bar": 3},
},
{
name: "error on specific key",
input: map[string]int{"foo": 1, "bar": 2, "baz": 3},
iteratee: func(k string, v int) (string, int, error) {
if k == "bar" {
return "", 0, errors.New("bar not allowed")
}
return k, v, nil
},
wantErr: "bar not allowed",
},
{
name: "empty map",
input: map[string]int{},
iteratee: func(k string, v int) (string, int, error) {
return k, v, nil
},
want: map[string]int{},
},
{
name: "to constant entry",
input: map[string]int{"foo": 1, "bar": 2, "baz": 3},
iteratee: func(k string, v int) (string, int, error) {
return "key", 0, nil
},
want: map[string]int{"key": 0},
},
{
name: "identity",
input: map[string]int{"foo": 1, "bar": 2},
iteratee: func(k string, v int) (string, int, error) {
return k, v, nil
},
want: map[string]int{"foo": 1, "bar": 2},
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
got, err := MapEntriesErr(tt.input, tt.iteratee)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.Equal(tt.want, got)
}
})
}
})
t.Run("different value type", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input map[string]int
iteratee func(string, int) (string, string, error)
want map[string]string
wantErr string
}{
{
name: "transform both key and value",
input: map[string]int{"foo": 1, "bar": 2},
iteratee: func(k string, v int) (string, string, error) {
return k, k + strconv.Itoa(v), nil
},
want: map[string]string{"foo": "foo1", "bar": "bar2"},
},
{
name: "error on specific value",
input: map[string]int{"foo": 1, "bar": 2, "baz": 3},
iteratee: func(k string, v int) (string, string, error) {
if v == 2 {
return "", "", fmt.Errorf("even value not allowed: %d", v)
}
return k, strconv.Itoa(v), nil
},
wantErr: "even value not allowed: 2",
},
{
name: "empty map",
input: map[string]int{},
iteratee: func(k string, v int) (string, string, error) {
return k, strconv.Itoa(v), nil
},
want: map[string]string{},
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
got, err := MapEntriesErr(tt.input, tt.iteratee)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.Equal(tt.want, got)
}
})
}
})
t.Run("invert map", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input map[string]int
iteratee func(string, int) (int, string, error)
want map[int]string
wantErr string
}{
{
name: "successful invert",
input: map[string]int{"a": 1, "b": 2},
iteratee: func(k string, v int) (int, string, error) {
return v, k, nil
},
want: map[int]string{1: "a", 2: "b"},
},
{
name: "error on specific key",
input: map[string]int{"a": 1, "b": 2},
iteratee: func(k string, v int) (int, string, error) {
if k == "b" {
return 0, "", errors.New("cannot invert b")
}
return v, k, nil
},
wantErr: "cannot invert b",
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
got, err := MapEntriesErr(tt.input, tt.iteratee)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.Equal(tt.want, got)
}
})
}
})
}
func TestMapToSlice(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MapToSlice(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k, v int) string {
return fmt.Sprintf("%d_%d", k, v)
})
result2 := MapToSlice(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k, _ int) string {
return strconv.FormatInt(int64(k), 10)
})
is.ElementsMatch(result1, []string{"1_5", "2_6", "3_7", "4_8"})
is.ElementsMatch(result2, []string{"1", "2", "3", "4"})
}
func TestMapToSliceErr(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input map[int]int64
iteratee func(int, int64) (string, error)
want []string
wantErr string
wantCallCount int // 0 means don't check (maps have no order)
}{
{
name: "successful transformation",
input: map[int]int64{1: 5, 2: 6, 3: 7},
iteratee: func(k int, v int64) (string, error) {
return fmt.Sprintf("%d_%d", k, v), nil
},
want: []string{"1_5", "2_6", "3_7"},
wantErr: "",
wantCallCount: 0, // map iteration order is not deterministic
},
{
name: "empty map",
input: map[int]int64{},
iteratee: func(k int, v int64) (string, error) {
return fmt.Sprintf("%d_%d", k, v), nil
},
want: []string{},
wantErr: "",
wantCallCount: 0,
},
{
name: "error on specific key",
input: map[int]int64{1: 5, 2: 6, 3: 7},
iteratee: func(k int, v int64) (string, error) {
if k == 2 {
return "", errors.New("key 2 not allowed")
}
return fmt.Sprintf("%d_%d", k, v), nil
},
want: nil,
wantErr: "key 2 not allowed",
wantCallCount: 0, // map iteration order is not deterministic
},
{
name: "constant value",
input: map[int]int64{1: 5, 2: 6, 3: 7},
iteratee: func(k int, v int64) (string, error) {
return "constant", nil
},
want: []string{"constant", "constant", "constant"},
wantErr: "",
wantCallCount: 0,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
got, err := MapToSliceErr(tt.input, tt.iteratee)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.ElementsMatch(tt.want, got)
}
})
}
}
func TestFilterMapToSlice(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FilterMapToSlice(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k, v int) (string, bool) {
return fmt.Sprintf("%d_%d", k, v), k%2 == 0
})
result2 := FilterMapToSlice(map[int]int{1: 5, 2: 6, 3: 7, 4: 8}, func(k, _ int) (string, bool) {
return strconv.FormatInt(int64(k), 10), k%2 == 0
})
is.ElementsMatch(result1, []string{"2_6", "4_8"})
is.ElementsMatch(result2, []string{"2", "4"})
}
func TestFilterMapToSliceErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input map[int]int64
iteratee func(int, int64) (string, bool, error)
want []string
wantErr string
}{
{
name: "filter even keys and transform",
input: map[int]int64{1: 5, 2: 6, 3: 7, 4: 8},
iteratee: func(k int, v int64) (string, bool, error) {
return fmt.Sprintf("%d_%d", k, v), k%2 == 0, nil
},
want: []string{"2_6", "4_8"},
},
{
name: "empty map",
input: map[int]int64{},
iteratee: func(k int, v int64) (string, bool, error) {
return fmt.Sprintf("%d_%d", k, v), true, nil
},
want: []string{},
},
{
name: "filter all out",
input: map[int]int64{1: 2, 2: 4, 3: 6},
iteratee: func(k int, v int64) (string, bool, error) {
return fmt.Sprintf("%d_%d", k, v), false, nil
},
want: []string{},
},
{
name: "filter all in",
input: map[int]int64{1: 5, 2: 6, 3: 7},
iteratee: func(k int, v int64) (string, bool, error) {
return fmt.Sprintf("%d_%d", k, v), true, nil
},
want: []string{"1_5", "2_6", "3_7"},
},
{
name: "constant value",
input: map[int]int64{1: 5, 2: 6, 3: 7},
iteratee: func(k int, v int64) (string, bool, error) {
return "constant", true, nil
},
want: []string{"constant", "constant", "constant"},
},
{
name: "error on specific key",
input: map[int]int64{1: 5, 2: 6, 3: 7},
iteratee: func(k int, v int64) (string, bool, error) {
if k == 2 {
return "", false, errors.New("key 2 not allowed")
}
return fmt.Sprintf("%d_%d", k, v), true, nil
},
wantErr: "key 2 not allowed",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := FilterMapToSliceErr(tt.input, tt.iteratee)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.ElementsMatch(tt.want, got)
}
})
}
}
func TestFilterKeys(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FilterKeys(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) bool {
return v == "foo"
})
is.Equal([]int{1}, result1)
result2 := FilterKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool {
return false
})
is.Empty(result2)
}
func TestFilterValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FilterValues(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) bool {
return v == "foo"
})
is.Equal([]string{"foo"}, result1)
result2 := FilterValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool {
return false
})
is.Empty(result2)
}
func TestFilterKeysErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input map[int]string
predicate func(int, string) (bool, error)
want []int
wantErr string
}{
{
name: "filter by value",
input: map[int]string{1: "foo", 2: "bar", 3: "baz"},
predicate: func(k int, v string) (bool, error) {
return v == "foo", nil
},
want: []int{1},
},
{
name: "empty map",
input: map[int]string{},
predicate: func(k int, v string) (bool, error) {
return true, nil
},
want: []int{},
},
{
name: "filter all out",
input: map[int]string{1: "foo", 2: "bar", 3: "baz"},
predicate: func(k int, v string) (bool, error) {
return false, nil
},
want: []int{},
},
{
name: "filter all in",
input: map[int]string{1: "foo", 2: "bar", 3: "baz"},
predicate: func(k int, v string) (bool, error) {
return true, nil
},
want: []int{1, 2, 3},
},
{
name: "error on specific key",
input: map[int]string{1: "foo", 2: "bar", 3: "baz"},
predicate: func(k int, v string) (bool, error) {
if k == 2 {
return false, errors.New("key 2 not allowed")
}
return true, nil
},
wantErr: "key 2 not allowed",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := FilterKeysErr(tt.input, tt.predicate)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.ElementsMatch(tt.want, got)
}
})
}
}
func TestFilterValuesErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input map[int]string
predicate func(int, string) (bool, error)
want []string
wantErr string
}{
{
name: "filter by value",
input: map[int]string{1: "foo", 2: "bar", 3: "baz"},
predicate: func(k int, v string) (bool, error) {
return v == "foo", nil
},
want: []string{"foo"},
},
{
name: "empty map",
input: map[int]string{},
predicate: func(k int, v string) (bool, error) {
return true, nil
},
want: []string{},
},
{
name: "filter all out",
input: map[int]string{1: "foo", 2: "bar", 3: "baz"},
predicate: func(k int, v string) (bool, error) {
return false, nil
},
want: []string{},
},
{
name: "filter all in",
input: map[int]string{1: "foo", 2: "bar", 3: "baz"},
predicate: func(k int, v string) (bool, error) {
return true, nil
},
want: []string{"foo", "bar", "baz"},
},
{
name: "error on specific key",
input: map[int]string{1: "foo", 2: "bar", 3: "baz"},
predicate: func(k int, v string) (bool, error) {
if k == 2 {
return false, errors.New("key 2 not allowed")
}
return true, nil
},
wantErr: "key 2 not allowed",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := FilterValuesErr(tt.input, tt.predicate)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
} else {
is.NoError(err)
is.ElementsMatch(tt.want, got)
}
})
}
}
func BenchmarkAssign(b *testing.B) {
counts := []int{32768, 1024, 128, 32, 2}
allDifferentMap := func(b *testing.B, n int) []map[string]int {
b.Helper()
defer b.ResetTimer()
m := make([]map[string]int, 0)
for i := 0; i < n; i++ {
m = append(m, map[string]int{
strconv.Itoa(i): i,
strconv.Itoa(i): i,
strconv.Itoa(i): i,
strconv.Itoa(i): i,
strconv.Itoa(i): i,
strconv.Itoa(i): i,
},
)
}
return m
}
allTheSameMap := func(b *testing.B, n int) []map[string]int {
b.Helper()
defer b.ResetTimer()
m := make([]map[string]int, 0)
for i := 0; i < n; i++ {
m = append(m, map[string]int{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
},
)
}
return m
}
for _, count := range counts {
differentMap := allDifferentMap(b, count)
sameMap := allTheSameMap(b, count)
b.Run(strconv.Itoa(count), func(b *testing.B) {
testCases := []struct {
name string
in []map[string]int
}{
{"different", differentMap},
{"same", sameMap},
}
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
result := Assign(tc.in...)
_ = result
}
})
}
})
}
}
================================================
FILE: math.go
================================================
package lo
import (
"math"
"github.com/samber/lo/internal/constraints"
)
// Range creates a slice of numbers (positive and/or negative) with given length.
// Play: https://go.dev/play/p/rho00R0WuHs
func Range(elementNum int) []int {
step := Ternary(elementNum < 0, -1, 1)
length := elementNum * step
result := make([]int, length)
for i, j := 0, 0; i < length; i, j = i+1, j+step {
result[i] = j
}
return result
}
// RangeFrom creates a slice of numbers from start with specified length.
// Play: https://go.dev/play/p/0r6VimXAi9H
func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) []T {
step := Ternary(elementNum < 0, -1, 1)
length := elementNum * step
result := make([]T, length)
for i, j := 0, start; i < length; i, j = i+1, j+T(step) {
result[i] = j
}
return result
}
// RangeWithSteps creates a slice of numbers (positive and/or negative) progressing from start up to, but not including end.
// step set to zero will return an empty slice.
// Play: https://go.dev/play/p/0r6VimXAi9H
func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) []T {
if start == end || step == 0 {
return []T{}
}
capacity := func(count, delta T) int {
// Use math.Ceil instead of (count-1)/delta+1 because integer division
// fails for floats (e.g., 5.5/2.5=2.2 → ceil=3, not 2).
return int(math.Ceil(float64(count) / float64(delta)))
}
if start < end {
if step < 0 {
return []T{}
}
result := make([]T, 0, capacity(end-start, step))
for i := start; i < end; i += step {
result = append(result, i)
}
return result
}
if step > 0 {
return []T{}
}
result := make([]T, 0, capacity(start-end, -step))
for i := start; i > end; i += step {
result = append(result, i)
}
return result
}
// Clamp clamps number within the inclusive lower and upper bounds.
// Play: https://go.dev/play/p/RU4lJNC2hlI
func Clamp[T constraints.Ordered](value, mIn, mAx T) T {
if value < mIn {
return mIn
} else if value > mAx {
return mAx
}
return value
}
// Sum sums the values in a collection. If collection is empty 0 is returned.
// Play: https://go.dev/play/p/upfeJVqs4Bt
func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T {
var sum T
for i := range collection {
sum += collection[i]
}
return sum
}
// SumBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 0 is returned.
// Play: https://go.dev/play/p/Dz_a_7jN_ca
func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R {
var sum R
for i := range collection {
sum += iteratee(collection[i])
}
return sum
}
// SumByErr summarizes the values in a collection using the given return value from the iteration function.
// If the iteratee returns an error, iteration stops and the error is returned.
// If collection is empty 0 and nil error are returned.
func SumByErr[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) (R, error)) (R, error) {
var sum R
for i := range collection {
v, err := iteratee(collection[i])
if err != nil {
return sum, err
}
sum += v
}
return sum, nil
}
// Product gets the product of the values in a collection. If collection is empty 1 is returned.
// Play: https://go.dev/play/p/2_kjM_smtAH
func Product[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T {
var product T = 1
for i := range collection {
product *= collection[i]
}
return product
}
// ProductBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 1 is returned.
// Play: https://go.dev/play/p/wadzrWr9Aer
func ProductBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R {
var product R = 1
for i := range collection {
product *= iteratee(collection[i])
}
return product
}
// ProductByErr summarizes the values in a collection using the given return value from the iteration function.
// If the iteratee returns an error, iteration stops and the error is returned.
// If collection is empty 1 and nil error are returned.
func ProductByErr[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) (R, error)) (R, error) {
var product R = 1
for i := range collection {
v, err := iteratee(collection[i])
if err != nil {
return product, err
}
product *= v
}
return product, nil
}
// Mean calculates the mean of a collection of numbers.
// Play: https://go.dev/play/p/tPURSuteUsP
func Mean[T constraints.Float | constraints.Integer](collection []T) T {
length := T(len(collection))
if length == 0 {
return 0
}
sum := Sum(collection)
return sum / length
}
// MeanBy calculates the mean of a collection of numbers using the given return value from the iteration function.
// Play: https://go.dev/play/p/j7TsVwBOZ7P
func MeanBy[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) R) R {
length := R(len(collection))
if length == 0 {
return 0
}
sum := SumBy(collection, iteratee)
return sum / length
}
// MeanByErr calculates the mean of a collection of numbers using the given return value from the iteration function.
// If the iteratee returns an error, iteration stops and the error is returned.
// If collection is empty 0 and nil error are returned.
func MeanByErr[T any, R constraints.Float | constraints.Integer](collection []T, iteratee func(item T) (R, error)) (R, error) {
length := R(len(collection))
if length == 0 {
return 0, nil
}
sum, err := SumByErr(collection, iteratee)
if err != nil {
return 0, err
}
return sum / length, nil
}
// Mode returns the mode (most frequent value) of a collection.
// If multiple values have the same highest frequency, then multiple values are returned.
// If the collection is empty, then the zero value of T is returned.
// Play: https://go.dev/play/p/PbiviqnV5zX
func Mode[T constraints.Integer | constraints.Float](collection []T) []T {
length := T(len(collection))
if length == 0 {
return []T{}
}
mode := make([]T, 0)
maxFreq := 0
frequency := make(map[T]int, len(collection))
for _, item := range collection {
frequency[item]++
count := frequency[item]
if count > maxFreq {
maxFreq = count
mode = []T{item}
} else if count == maxFreq {
mode = append(mode, item)
}
}
return mode
}
================================================
FILE: math_test.go
================================================
package lo
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRange(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Range(4)
result2 := Range(-4)
result3 := Range(0)
is.Equal([]int{0, 1, 2, 3}, result1)
is.Equal([]int{0, -1, -2, -3}, result2)
is.Empty(result3)
}
func TestRangeFrom(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := RangeFrom(1, 5)
result2 := RangeFrom(-1, -5)
result3 := RangeFrom(10, 0)
result4 := RangeFrom(2.0, 3)
result5 := RangeFrom(-2.0, -3)
result6 := RangeFrom(2.5, 3)
result7 := RangeFrom(-2.5, -3)
is.Equal([]int{1, 2, 3, 4, 5}, result1)
is.Equal([]int{-1, -2, -3, -4, -5}, result2)
is.Empty(result3)
is.Equal([]float64{2.0, 3.0, 4.0}, result4)
is.Equal([]float64{-2.0, -3.0, -4.0}, result5)
is.Equal([]float64{2.5, 3.5, 4.5}, result6)
is.Equal([]float64{-2.5, -3.5, -4.5}, result7)
}
func TestRangeWithSteps(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := RangeWithSteps(0, 20, 6)
result2 := RangeWithSteps(0, 3, -5)
result3 := RangeWithSteps(1, 1, 0)
result4 := RangeWithSteps(3, 2, 1)
result5 := RangeWithSteps(1.0, 4.0, 2.0)
result6 := RangeWithSteps[float32](-1.0, -4.0, -1.0)
result7 := RangeWithSteps(0.0, 0.5, 1.0)
result8 := RangeWithSteps(0.0, 0.3, 0.1)
result9 := RangeWithSteps(0.0, 5.5, 2.5)
type f64 float64
result10 := RangeWithSteps[f64](0.0, 0.3, 0.1)
is.Equal([]int{0, 6, 12, 18}, result1)
is.Empty(result2)
is.Empty(result3)
is.Empty(result4)
is.Equal([]float64{1.0, 3.0}, result5)
is.Equal([]float32{-1.0, -2.0, -3.0}, result6)
is.Equal([]float64{0.0}, result7)
is.Equal([]float64{0.0, 0.1, 0.2}, result8)
is.Equal([]float64{0.0, 2.5, 5.0}, result9)
is.Equal([]f64{0.0, 0.1, 0.2}, result10)
}
func TestClamp(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Clamp(0, -10, 10)
result2 := Clamp(-42, -10, 10)
result3 := Clamp(42, -10, 10)
is.Zero(result1)
is.Equal(-10, result2)
is.Equal(10, result3)
}
func TestSum(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Sum([]float32{2.3, 3.3, 4, 5.3})
result2 := Sum([]int32{2, 3, 4, 5})
result3 := Sum([]uint32{2, 3, 4, 5})
result4 := Sum([]uint32{})
result5 := Sum([]complex128{4_4, 2_2})
is.InEpsilon(14.9, result1, 1e-7)
is.Equal(int32(14), result2)
is.Equal(uint32(14), result3)
is.Equal(uint32(0), result4)
is.Equal(complex128(6_6), result5)
}
func TestSumBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := SumBy([]float32{2.3, 3.3, 4, 5.3}, func(n float32) float32 { return n })
result2 := SumBy([]int32{2, 3, 4, 5}, func(n int32) int32 { return n })
result3 := SumBy([]uint32{2, 3, 4, 5}, func(n uint32) uint32 { return n })
result4 := SumBy([]uint32{}, func(n uint32) uint32 { return n })
result5 := SumBy([]complex128{4_4, 2_2}, func(n complex128) complex128 { return n })
is.InEpsilon(14.9, result1, 1e-7)
is.Equal(int32(14), result2)
is.Equal(uint32(14), result3)
is.Equal(uint32(0), result4)
is.Equal(complex128(6_6), result5)
}
func TestSumByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Test normal operation (no error)
result1, err1 := SumByErr([]float32{2.3, 3.3, 4, 5.3}, func(n float32) (float32, error) { return n, nil })
result2, err2 := SumByErr([]int32{2, 3, 4, 5}, func(n int32) (int32, error) { return n, nil })
result3, err3 := SumByErr([]uint32{2, 3, 4, 5}, func(n uint32) (uint32, error) { return n, nil })
result4, err4 := SumByErr([]complex128{4_4, 2_2}, func(n complex128) (complex128, error) { return n, nil })
is.NoError(err1)
is.InEpsilon(14.9, result1, 1e-7)
is.NoError(err2)
is.Equal(int32(14), result2)
is.NoError(err3)
is.Equal(uint32(14), result3)
is.NoError(err4)
is.Equal(complex128(6_6), result4)
// Test empty collection
result5, err5 := SumByErr([]uint32{}, func(n uint32) (uint32, error) { return n, nil })
is.NoError(err5)
is.Equal(uint32(0), result5)
// Test error - iteratee returns error
testErr := assert.AnError
result6, err6 := SumByErr([]int32{1, 2, 3, 4, 5}, func(n int32) (int32, error) {
if n == 3 {
return 0, testErr
}
return n, nil
})
is.ErrorIs(err6, testErr)
// Early return: sum up to 1+2 = 3
is.Equal(int32(3), result6)
// Test early return - callback count verification
// With 5 elements and error at 3rd, only 3 callbacks should be made
items := []int32{1, 2, 3, 4, 5}
callbackCount := 0
_, err7 := SumByErr(items, func(n int32) (int32, error) {
callbackCount++
if n == 3 {
return 0, testErr
}
return n, nil
})
is.ErrorIs(err7, testErr)
is.Equal(3, callbackCount) // Only 3 callbacks before error
// Test error at first element
result8, err8 := SumByErr([]int32{1, 2, 3}, func(n int32) (int32, error) {
return 0, testErr
})
is.ErrorIs(err8, testErr)
is.Equal(int32(0), result8)
// Test error at last element
callbackCount2 := 0
result9, err9 := SumByErr([]int32{1, 2, 3}, func(n int32) (int32, error) {
callbackCount2++
if n == 3 {
return 0, testErr
}
return n, nil
})
_ = result9 // unused due to error
is.ErrorIs(err9, testErr)
is.Equal(3, callbackCount2) // All 3 callbacks before error at last element
}
func TestProduct(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Product([]float32{2.3, 3.3, 4, 5.3})
result2 := Product([]int32{2, 3, 4, 5})
result3 := Product([]int32{7, 8, 9, 0})
result4 := Product([]int32{7, -1, 9, 2})
result5 := Product([]uint32{2, 3, 4, 5})
result6 := Product([]uint32{})
result7 := Product([]complex128{4_4, 2_2})
result8 := Product[uint32](nil)
is.InEpsilon(160.908, result1, 1e-7)
is.Equal(int32(120), result2)
is.Equal(int32(0), result3)
is.Equal(int32(-126), result4)
is.Equal(uint32(120), result5)
is.Equal(uint32(1), result6)
is.Equal(complex128(96_8), result7)
is.Equal(uint32(1), result8)
}
func TestProductBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ProductBy([]float32{2.3, 3.3, 4, 5.3}, func(n float32) float32 { return n })
result2 := ProductBy([]int32{2, 3, 4, 5}, func(n int32) int32 { return n })
result3 := ProductBy([]int32{7, 8, 9, 0}, func(n int32) int32 { return n })
result4 := ProductBy([]int32{7, -1, 9, 2}, func(n int32) int32 { return n })
result5 := ProductBy([]uint32{2, 3, 4, 5}, func(n uint32) uint32 { return n })
result6 := ProductBy([]uint32{}, func(n uint32) uint32 { return n })
result7 := ProductBy([]complex128{4_4, 2_2}, func(n complex128) complex128 { return n })
result8 := ProductBy(nil, func(n uint32) uint32 { return n })
is.InEpsilon(160.908, result1, 1e-7)
is.Equal(int32(120), result2)
is.Equal(int32(0), result3)
is.Equal(int32(-126), result4)
is.Equal(uint32(120), result5)
is.Equal(uint32(1), result6)
is.Equal(complex128(96_8), result7)
is.Equal(uint32(1), result8)
}
//nolint:errcheck,forcetypeassert
func TestProductByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
// Test normal operation (no error) - table driven
tests := []struct {
name string
input any
expected any
}{
{
name: "float32 slice",
input: []float32{2.3, 3.3, 4, 5.3},
expected: float32(160.908),
},
{
name: "int32 slice",
input: []int32{2, 3, 4, 5},
expected: int32(120),
},
{
name: "int32 slice with zero",
input: []int32{7, 8, 9, 0},
expected: int32(0),
},
{
name: "int32 slice with negative",
input: []int32{7, -1, 9, 2},
expected: int32(-126),
},
{
name: "uint32 slice",
input: []uint32{2, 3, 4, 5},
expected: uint32(120),
},
{
name: "empty uint32 slice",
input: []uint32{},
expected: uint32(1),
},
{
name: "complex128 slice",
input: []complex128{4 + 4i, 2 + 2i},
expected: complex128(0 + 16i),
},
{
name: "nil int32 slice",
input: ([]int32)(nil),
expected: int32(1),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
switch input := tt.input.(type) {
case []float32:
result, err := ProductByErr(input, func(n float32) (float32, error) { return n, nil })
is.NoError(err)
is.InEpsilon(tt.expected.(float32), result, 1e-7)
case []int32:
result, err := ProductByErr(input, func(n int32) (int32, error) { return n, nil })
is.NoError(err)
is.Equal(tt.expected.(int32), result)
case []uint32:
result, err := ProductByErr(input, func(n uint32) (uint32, error) { return n, nil })
is.NoError(err)
is.Equal(tt.expected.(uint32), result)
case []complex128:
result, err := ProductByErr(input, func(n complex128) (complex128, error) { return n, nil })
is.NoError(err)
is.Equal(tt.expected.(complex128), result)
}
})
}
// Test error cases - table driven
errorTests := []struct {
name string
input []int32
errorAt int32
expectedProd int32
expectedCalls int
}{
{
name: "error at third element",
input: []int32{1, 2, 3, 4, 5},
errorAt: 3,
expectedProd: 2, // 1 * 2
expectedCalls: 3,
},
{
name: "error at first element",
input: []int32{1, 2, 3},
errorAt: 1,
expectedProd: 1, // initial value
expectedCalls: 1,
},
{
name: "error at last element",
input: []int32{1, 2, 3},
errorAt: 3,
expectedProd: 2, // 1 * 2
expectedCalls: 3,
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
result, err := ProductByErr(tt.input, func(n int32) (int32, error) {
callbackCount++
if n == tt.errorAt {
return 0, testErr
}
return n, nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedProd, result)
is.Equal(tt.expectedCalls, callbackCount)
})
}
}
func TestMean(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Mean([]float32{2.3, 3.3, 4, 5.3})
result2 := Mean([]int32{2, 3, 4, 5})
result3 := Mean([]uint32{2, 3, 4, 5})
result4 := Mean([]uint32{})
is.InEpsilon(3.725, result1, 1e-7)
is.Equal(int32(3), result2)
is.Equal(uint32(3), result3)
is.Equal(uint32(0), result4)
}
func TestMeanBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := MeanBy([]float32{2.3, 3.3, 4, 5.3}, func(n float32) float32 { return n })
result2 := MeanBy([]int32{2, 3, 4, 5}, func(n int32) int32 { return n })
result3 := MeanBy([]uint32{2, 3, 4, 5}, func(n uint32) uint32 { return n })
result4 := MeanBy([]uint32{}, func(n uint32) uint32 { return n })
is.InEpsilon(3.725, result1, 1e-7)
is.Equal(int32(3), result2)
is.Equal(uint32(3), result3)
is.Equal(uint32(0), result4)
}
//nolint:errcheck,forcetypeassert
func TestMeanByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := assert.AnError
// Test normal operation (no error) - table driven
tests := []struct {
name string
input any
expected any
}{
{
name: "float32 slice",
input: []float32{2.3, 3.3, 4, 5.3},
expected: float32(3.725),
},
{
name: "int32 slice",
input: []int32{2, 3, 4, 5},
expected: int32(3),
},
{
name: "uint32 slice",
input: []uint32{2, 3, 4, 5},
expected: uint32(3),
},
{
name: "empty uint32 slice",
input: []uint32{},
expected: uint32(0),
},
{
name: "nil int32 slice",
input: ([]int32)(nil),
expected: int32(0),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
switch input := tt.input.(type) {
case []float32:
result, err := MeanByErr(input, func(n float32) (float32, error) { return n, nil })
is.NoError(err)
is.InEpsilon(tt.expected.(float32), result, 1e-7)
case []int32:
result, err := MeanByErr(input, func(n int32) (int32, error) { return n, nil })
is.NoError(err)
is.Equal(tt.expected.(int32), result)
case []uint32:
result, err := MeanByErr(input, func(n uint32) (uint32, error) { return n, nil })
is.NoError(err)
is.Equal(tt.expected.(uint32), result)
}
})
}
// Test error cases - table driven
errorTests := []struct {
name string
input []int32
errorAt int32
expectedCalls int
}{
{
name: "error at third element",
input: []int32{1, 2, 3, 4, 5},
errorAt: 3,
expectedCalls: 3,
},
{
name: "error at first element",
input: []int32{1, 2, 3},
errorAt: 1,
expectedCalls: 1,
},
{
name: "error at last element",
input: []int32{1, 2, 3},
errorAt: 3,
expectedCalls: 3,
},
}
for _, tt := range errorTests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
_, err := MeanByErr(tt.input, func(n int32) (int32, error) {
callbackCount++
if n == tt.errorAt {
return 0, testErr
}
return n, nil
})
is.ErrorIs(err, testErr)
is.Equal(tt.expectedCalls, callbackCount)
})
}
}
func TestMode(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Mode([]float32{2.3, 3.3, 3.3, 5.3})
result2 := Mode([]int32{2, 2, 3, 4})
result3 := Mode([]uint32{2, 2, 3, 3})
result4 := Mode([]uint32{})
result5 := Mode([]int{1, 2, 3, 4, 5, 6, 7, 8, 9})
is.Equal([]float32{3.3}, result1)
is.Equal([]int32{2}, result2)
is.Equal([]uint32{2, 3}, result3)
is.Empty(result4)
is.Equal([]int{1, 2, 3, 4, 5, 6, 7, 8, 9}, result5)
}
func TestModeCapacityConsistency(t *testing.T) {
t.Parallel()
is := assert.New(t)
arr := []int{1, 1, 2, 2, 3, 3, 3}
result := Mode(arr)
is.Equal([]int{3}, result, "Mode should return correct mode value")
is.Equal(len(result), cap(result), "Mode slice capacity should match its length")
}
================================================
FILE: mutable/slice.go
================================================
package mutable
import "github.com/samber/lo/internal/xrand"
// Filter is a generic function that modifies the input slice in-place to contain only the elements
// that satisfy the provided predicate function. The predicate function takes an element of the slice and its index,
// and should return true for elements that should be kept and false for elements that should be removed.
// The function returns the modified slice, which may be shorter than the original if some elements were removed.
// Note that the order of elements in the original slice is preserved in the output.
// Play: https://go.dev/play/p/0jY3Z0B7O_5
func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
j := 0
for i := range collection {
if predicate(collection[i]) {
collection[j] = collection[i]
j++
}
}
return collection[:j]
}
// FilterI is a generic function that modifies the input slice in-place to contain only the elements
// that satisfy the provided predicate function. The predicate function takes an element of the slice and its index,
// and should return true for elements that should be kept and false for elements that should be removed.
// The function returns the modified slice, which may be shorter than the original if some elements were removed.
// Note that the order of elements in the original slice is preserved in the output.
func FilterI[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice {
j := 0
for i := range collection {
if predicate(collection[i], i) {
collection[j] = collection[i]
j++
}
}
return collection[:j]
}
// Map is a generic function that modifies the input slice in-place to contain the result of applying the provided
// function to each element of the slice. The function returns the modified slice, which has the same length as the original.
// Play: https://go.dev/play/p/0jY3Z0B7O_5
func Map[T any, Slice ~[]T](collection Slice, transform func(item T) T) {
for i := range collection {
collection[i] = transform(collection[i])
}
}
// MapI is a generic function that modifies the input slice in-place to contain the result of applying the provided
// function to each element of the slice. The function returns the modified slice, which has the same length as the original.
func MapI[T any, Slice ~[]T](collection Slice, transform func(item T, index int) T) {
for i := range collection {
collection[i] = transform(collection[i], i)
}
}
// Shuffle returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
// Play: https://go.dev/play/p/2xb3WdLjeSJ
func Shuffle[T any, Slice ~[]T](collection Slice) {
xrand.Shuffle(len(collection), func(i, j int) {
collection[i], collection[j] = collection[j], collection[i]
})
}
// Reverse reverses a slice so that the first element becomes the last, the second element becomes the second to last, and so on.
// Play: https://go.dev/play/p/O-M5pmCRgzV
func Reverse[T any, Slice ~[]T](collection Slice) {
length := len(collection)
half := length / 2
for i := 0; i < half; i++ {
j := length - 1 - i
collection[i], collection[j] = collection[j], collection[i]
}
}
================================================
FILE: mutable/slice_example_test.go
================================================
package mutable
import "fmt"
func ExampleFilter() {
list := []int{1, 2, 3, 4}
newList := Filter(list, func(nbr int) bool {
return nbr%2 == 0
})
fmt.Printf("%v\n%v", list, newList)
// Output:
// [2 4 3 4]
// [2 4]
}
func ExampleFilterI() {
list := []int{1, 2, 3, 4}
newList := Filter(list, func(nbr int) bool {
return nbr%2 == 0
})
fmt.Printf("%v\n%v", list, newList)
// Output:
// [2 4 3 4]
// [2 4]
}
func ExampleMap() {
list := []int{1, 2, 3, 4}
Map(list, func(nbr int) int {
return nbr * 2
})
fmt.Printf("%v", list)
// Output: [2 4 6 8]
}
func ExampleMapI() {
list := []int{1, 2, 3, 4}
MapI(list, func(nbr, index int) int {
return nbr * index
})
fmt.Printf("%v", list)
// Output: [0 2 6 12]
}
func ExampleShuffle() {
list := []int{0, 1, 2, 3, 4, 5}
Shuffle(list)
fmt.Printf("%v", list)
}
func ExampleReverse() {
list := []int{0, 1, 2, 3, 4, 5}
Reverse(list)
fmt.Printf("%v", list)
// Output: [5 4 3 2 1 0]
}
// Fill fills elements of a slice with `initial` value.
// Play: https://go.dev/play/p/VwR34GzqEub
func Fill[T any, Slice ~[]T](collection Slice, initial T) {
for i := range collection {
collection[i] = initial
}
}
================================================
FILE: mutable/slice_test.go
================================================
package mutable
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFilter(t *testing.T) {
t.Parallel()
is := assert.New(t)
input1 := []int{1, 2, 3, 4}
r1 := Filter(input1, func(x int) bool {
return x%2 == 0
})
is.Equal([]int{2, 4, 3, 4}, input1)
is.Equal([]int{2, 4}, r1)
input2 := []string{"", "foo", "", "bar", ""}
r2 := Filter(input2, func(x string) bool {
return len(x) > 0
})
is.Equal([]string{"foo", "bar", "", "bar", ""}, input2)
is.Equal([]string{"foo", "bar"}, r2)
}
func TestFilterI(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FilterI([]int{1, 2, 3, 4}, func(x, i int) bool {
is.Equal(i, x-1)
return x%2 == 0
})
is.Equal([]int{2, 4}, r1)
}
func TestMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
list := []int{1, 2, 3, 4}
Map(list, func(x int) int {
return x * 2
})
is.Equal([]int{2, 4, 6, 8}, list)
list = []int{1, 2, 3, 4}
Map(list, func(x int) int {
return x * 4
})
is.Equal([]int{4, 8, 12, 16}, list)
}
func TestMapI(t *testing.T) {
t.Parallel()
is := assert.New(t)
list := []int{1, 2, 3, 4}
MapI(list, func(x, index int) int {
is.Equal(index, x-1)
return x * 2
})
is.Equal([]int{2, 4, 6, 8}, list)
list = []int{1, 2, 3, 4}
MapI(list, func(x, index int) int {
is.Equal(index, x-1)
return x * 4
})
is.Equal([]int{4, 8, 12, 16}, list)
}
func TestShuffle(t *testing.T) {
t.Parallel()
is := assert.New(t)
list := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
Shuffle(list)
is.NotEqual([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, list)
list = []int{}
Shuffle(list)
is.Empty(list)
}
func TestReverse(t *testing.T) {
t.Parallel()
is := assert.New(t)
list := []int{0, 1, 2, 3, 4, 5}
Reverse(list)
is.Equal([]int{5, 4, 3, 2, 1, 0}, list)
list = []int{0, 1, 2, 3, 4, 5, 6}
Reverse(list)
is.Equal([]int{6, 5, 4, 3, 2, 1, 0}, list)
list = []int{}
Reverse(list)
is.Empty(list)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
Reverse(allStrings)
is.IsType(myStrings{"", "foo", "bar"}, allStrings, "type preserved")
}
func TestFill(t *testing.T) {
t.Parallel()
is := assert.New(t)
list1 := []string{"a", "0"}
Fill(list1, "b")
is.Equal([]string{"b", "b"}, list1)
list2 := []string{}
Fill(list2, "b")
is.Empty(list2)
}
================================================
FILE: parallel/slice.go
================================================
package parallel
import "sync"
// Map manipulates a slice and transforms it to a slice of another type.
// `transform` is called in parallel. Result keep the same order.
// Play: https://go.dev/play/p/sCJaB3quRMC
func Map[T, R any](collection []T, transform func(item T, index int) R) []R {
result := make([]R, len(collection))
var wg sync.WaitGroup
wg.Add(len(collection))
for i, item := range collection {
go func(_item T, _i int) {
res := transform(_item, _i)
result[_i] = res
wg.Done()
}(item, i)
}
wg.Wait()
return result
}
// ForEach iterates over elements of collection and invokes callback for each element.
// `iteratee` is called in parallel.
// Play: https://go.dev/play/p/sCJaB3quRMC
func ForEach[T any](collection []T, callback func(item T, index int)) {
var wg sync.WaitGroup
wg.Add(len(collection))
for i, item := range collection {
go func(_item T, _i int) {
callback(_item, _i)
wg.Done()
}(item, i)
}
wg.Wait()
}
// Times invokes the iteratee n times, returning a slice of the results of each invocation.
// The iteratee is invoked with index as argument.
// `iteratee` is called in parallel.
// Play: https://go.dev/play/p/ZNnWNcJ4Au-
func Times[T any](count int, iteratee func(index int) T) []T {
result := make([]T, count)
var wg sync.WaitGroup
wg.Add(count)
for i := 0; i < count; i++ {
go func(_i int) {
item := iteratee(_i)
result[_i] = item
wg.Done()
}(i)
}
wg.Wait()
return result
}
// GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee.
// The order of grouped values is determined by the order they occur in the collection.
// `iteratee` is called in parallel.
// Play: https://go.dev/play/p/EkyvA0gw4dj
func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) map[U]Slice {
result := map[U]Slice{}
keys := Map(collection, func(item T, _ int) U {
return iteratee(item)
})
for i, item := range collection {
result[keys[i]] = append(result[keys[i]], item)
}
return result
}
// PartitionBy returns a slice of elements split into groups. The order of grouped values is
// determined by the order they occur in collection. The grouping is generated from the results
// of running each element of collection through iteratee.
// The order of groups is determined by their first appearance in the collection.
// `iteratee` is called in parallel.
// Play: https://go.dev/play/p/GwBQdMgx2nC
func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K) []Slice {
result := []Slice{}
seen := map[K]int{}
keys := Map(collection, func(item T, _ int) K {
return iteratee(item)
})
for i := range collection {
resultIndex, ok := seen[keys[i]]
if ok {
result[resultIndex] = append(result[resultIndex], collection[i])
} else {
seen[keys[i]] = len(result)
result = append(result, Slice{collection[i]})
}
}
return result
}
================================================
FILE: parallel/slice_test.go
================================================
package parallel
import (
"sort"
"strconv"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Map([]int{1, 2, 3, 4}, func(x, _ int) string {
return "Hello"
})
result2 := Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string {
return strconv.FormatInt(x, 10)
})
is.Equal([]string{"Hello", "Hello", "Hello", "Hello"}, result1)
is.Equal([]string{"1", "2", "3", "4"}, result2)
}
func TestForEach(t *testing.T) {
t.Parallel()
is := assert.New(t)
var counter uint64
collection := []int{1, 2, 3, 4}
ForEach(collection, func(x, i int) {
atomic.AddUint64(&counter, 1)
})
is.Equal(uint64(4), atomic.LoadUint64(&counter))
}
func TestTimes(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
is.Equal([]string{"0", "1", "2"}, result1)
}
func TestGroupBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i % 3
})
// order
for x := range result1 {
sort.Ints(result1[x])
}
is.Equal(map[int][]int{
0: {0, 3},
1: {1, 4},
2: {2, 5},
}, result1)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := GroupBy(allStrings, func(i string) int {
return 42
})
is.IsType(nonempty[42], allStrings, "type preserved")
}
func TestPartitionBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
oddEven := func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
}
result1 := PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, oddEven)
result2 := PartitionBy([]int{}, oddEven)
// order
sort.Slice(result1, func(i, j int) bool {
return result1[i][0] < result1[j][0]
})
for x := range result1 {
sort.Ints(result1[x])
}
is.ElementsMatch(result1, [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}})
is.Empty(result2)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := PartitionBy(allStrings, func(item string) int {
return len(item)
})
is.IsType(nonempty[0], allStrings, "type preserved")
}
================================================
FILE: retry.go
================================================
package lo
import (
"sync"
"time"
"github.com/samber/lo/internal/xtime"
)
type debounce struct {
after time.Duration
mu *sync.Mutex
timer *time.Timer
done bool
callbacks []func()
}
func (d *debounce) reset() {
d.mu.Lock()
defer d.mu.Unlock()
if d.done {
return
}
if d.timer != nil {
d.timer.Stop()
}
d.timer = time.AfterFunc(d.after, func() {
// We need to lock the mutex here to avoid race conditions with 2 concurrent calls to reset()
d.mu.Lock()
callbacks := append([]func(){}, d.callbacks...)
d.mu.Unlock()
for i := range callbacks {
callbacks[i]()
}
})
}
func (d *debounce) cancel() {
d.mu.Lock()
defer d.mu.Unlock()
if d.timer != nil {
d.timer.Stop()
d.timer = nil
}
d.done = true
}
// NewDebounce creates a debounced instance that delays invoking functions given until after wait milliseconds have elapsed.
// Play: https://go.dev/play/p/_IPY7ROzbMk
func NewDebounce(duration time.Duration, f ...func()) (func(), func()) {
d := &debounce{
after: duration,
mu: new(sync.Mutex),
timer: nil,
done: false,
callbacks: f,
}
return func() {
d.reset()
}, d.cancel
}
type debounceByItem struct {
mu *sync.Mutex
timer *time.Timer
count int
}
type debounceBy[T comparable] struct {
after time.Duration
mu *sync.Mutex
items map[T]*debounceByItem
callbacks []func(key T, count int)
}
func (d *debounceBy[T]) reset(key T) {
d.mu.Lock()
if _, ok := d.items[key]; !ok {
d.items[key] = &debounceByItem{
mu: new(sync.Mutex),
timer: nil,
}
}
item := d.items[key]
d.mu.Unlock()
item.mu.Lock()
defer item.mu.Unlock()
item.count++
if item.timer != nil {
item.timer.Stop()
}
item.timer = time.AfterFunc(d.after, func() {
// We need to lock the mutex here to avoid race conditions with 2 concurrent calls to reset()
item.mu.Lock()
count := item.count
item.count = 0
callbacks := append([]func(key T, count int){}, d.callbacks...)
item.mu.Unlock()
for i := range callbacks {
callbacks[i](key, count)
}
})
}
func (d *debounceBy[T]) cancel(key T) {
d.mu.Lock()
defer d.mu.Unlock()
if item, ok := d.items[key]; ok {
item.mu.Lock()
if item.timer != nil {
item.timer.Stop()
item.timer = nil
}
item.mu.Unlock()
delete(d.items, key)
}
}
// NewDebounceBy creates a debounced instance for each distinct key, that delays invoking functions given until after wait milliseconds have elapsed.
// Play: https://go.dev/play/p/Izk7GEzZm2Q
func NewDebounceBy[T comparable](duration time.Duration, f ...func(key T, count int)) (func(key T), func(key T)) {
d := &debounceBy[T]{
after: duration,
mu: new(sync.Mutex),
items: map[T]*debounceByItem{},
callbacks: f,
}
return func(key T) {
d.reset(key)
}, d.cancel
}
// Attempt invokes a function N times until it returns valid output. Returns either the caught error or nil.
// When the first argument is less than `1`, the function runs until a successful response is returned.
// Play: https://go.dev/play/p/3ggJZ2ZKcMj
func Attempt(maxIteration int, f func(index int) error) (int, error) {
var err error
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
// for retries >= 0 {
err = f(i)
if err == nil {
return i + 1, nil
}
}
return maxIteration, err
}
// AttemptWithDelay invokes a function N times until it returns valid output,
// with a pause between each call. Returns either the caught error or nil.
// When the first argument is less than `1`, the function runs until a successful
// response is returned.
// Play: https://go.dev/play/p/tVs6CygC7m1
func AttemptWithDelay(maxIteration int, delay time.Duration, f func(index int, duration time.Duration) error) (int, time.Duration, error) {
var err error
start := xtime.Now()
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
err = f(i, xtime.Since(start))
if err == nil {
return i + 1, xtime.Since(start), nil
}
if maxIteration <= 0 || i+1 < maxIteration {
xtime.Sleep(delay)
}
}
return maxIteration, xtime.Since(start), err
}
// AttemptWhile invokes a function N times until it returns valid output.
// Returns either the caught error or nil, along with a bool value to determine
// whether the function should be invoked again. It will terminate the invoke
// immediately if the second return value is false. When the first
// argument is less than `1`, the function runs until a successful response is
// returned.
// Play: https://go.dev/play/p/1VS7HxlYMOG
func AttemptWhile(maxIteration int, f func(int) (error, bool)) (int, error) {
var err error
var shouldContinueInvoke bool
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
// for retries >= 0 {
err, shouldContinueInvoke = f(i)
if !shouldContinueInvoke { // if shouldContinueInvoke is false, then return immediately
return i + 1, err
}
if err == nil {
return i + 1, nil
}
}
return maxIteration, err
}
// AttemptWhileWithDelay invokes a function N times until it returns valid output,
// with a pause between each call. Returns either the caught error or nil, along
// with a bool value to determine whether the function should be invoked again.
// It will terminate the invoke immediately if the second return value is false.
// When the first argument is less than `1`, the function runs until a successful
// response is returned.
// Play: https://go.dev/play/p/mhufUjJfLEF
func AttemptWhileWithDelay(maxIteration int, delay time.Duration, f func(int, time.Duration) (error, bool)) (int, time.Duration, error) {
var err error
var shouldContinueInvoke bool
start := xtime.Now()
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
err, shouldContinueInvoke = f(i, xtime.Since(start))
if !shouldContinueInvoke { // if shouldContinueInvoke is false, then return immediately
return i + 1, xtime.Since(start), err
}
if err == nil {
return i + 1, xtime.Since(start), nil
}
if maxIteration <= 0 || i+1 < maxIteration {
xtime.Sleep(delay)
}
}
return maxIteration, xtime.Since(start), err
}
type transactionStep[T any] struct {
exec func(T) (T, error)
onRollback func(T) T
}
// NewTransaction instantiate a new transaction.
// Play: https://go.dev/play/p/7B2o52wEQbj
func NewTransaction[T any]() *Transaction[T] {
return &Transaction[T]{
steps: []transactionStep[T]{},
}
}
// Transaction implements a Saga pattern.
type Transaction[T any] struct {
steps []transactionStep[T]
}
// Then adds a step to the chain of callbacks. Returns the same Transaction.
// Play: https://go.dev/play/p/Qxrd7MGQGh1 https://go.dev/play/p/xrHb2_kMvTY
func (t *Transaction[T]) Then(exec func(T) (T, error), onRollback func(T) T) *Transaction[T] {
t.steps = append(t.steps, transactionStep[T]{
exec: exec,
onRollback: onRollback,
})
return t
}
// Process runs the Transaction steps and rollbacks in case of errors.
// Play: https://go.dev/play/p/Qxrd7MGQGh1 https://go.dev/play/p/xrHb2_kMvTY
func (t *Transaction[T]) Process(state T) (T, error) {
var i int
var err error
for i < len(t.steps) {
state, err = t.steps[i].exec(state)
if err != nil {
break
}
i++
}
if err == nil {
return state, nil
}
for i > 0 {
i--
state = t.steps[i].onRollback(state)
}
return state, err
}
// @TODO: single mutex per key?
type throttleBy[T comparable] struct {
mu *sync.Mutex
timer *time.Timer
interval time.Duration
callbacks []func(key T)
countLimit int
count map[T]int
}
func (th *throttleBy[T]) throttledFunc(key T) {
th.mu.Lock()
defer th.mu.Unlock()
if th.count[key] < th.countLimit {
th.count[key]++
for _, f := range th.callbacks {
f(key)
}
}
if th.timer == nil {
th.timer = time.AfterFunc(th.interval, func() {
th.reset()
})
}
}
func (th *throttleBy[T]) reset() {
th.mu.Lock()
defer th.mu.Unlock()
if th.timer != nil {
th.timer.Stop()
}
th.count = map[T]int{}
th.timer = nil
}
// NewThrottle creates a throttled instance that invokes given functions only once in every interval.
// This returns 2 functions, First one is throttled function and Second one is a function to reset interval.
// Play: https://go.dev/play/p/qQn3fm8Z7jS
func NewThrottle(interval time.Duration, f ...func()) (throttle, reset func()) {
return NewThrottleWithCount(interval, 1, f...)
}
// NewThrottleWithCount is NewThrottle with count limit, throttled function will be invoked count times in every interval.
// Play: https://go.dev/play/p/w5nc0MgWtjC
func NewThrottleWithCount(interval time.Duration, count int, f ...func()) (throttle, reset func()) {
callbacks := Map(f, func(item func(), _ int) func(struct{}) {
return func(struct{}) {
item()
}
})
throttleFn, reset := NewThrottleByWithCount(interval, count, callbacks...)
return func() {
throttleFn(struct{}{})
}, reset
}
// NewThrottleBy creates a throttled instance that invokes given functions only once in every interval.
// This returns 2 functions, First one is throttled function and Second one is a function to reset interval.
// Play: https://go.dev/play/p/0Wv6oX7dHdC
func NewThrottleBy[T comparable](interval time.Duration, f ...func(key T)) (throttle func(key T), reset func()) {
return NewThrottleByWithCount(interval, 1, f...)
}
// NewThrottleByWithCount is NewThrottleBy with count limit, throttled function will be invoked count times in every interval.
// Play: https://go.dev/play/p/vQk3ECH7_EW
func NewThrottleByWithCount[T comparable](interval time.Duration, count int, f ...func(key T)) (throttle func(key T), reset func()) {
if count <= 0 {
count = 1
}
th := &throttleBy[T]{
mu: new(sync.Mutex),
interval: interval,
callbacks: f,
countLimit: count,
count: map[T]int{},
}
return th.throttledFunc, th.reset
}
================================================
FILE: retry_example_test.go
================================================
//go:build !race
package lo
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
)
func ExampleNewDebounce() {
i := int32(0)
calls := []int32{}
mu := sync.Mutex{}
debounce, cancel := NewDebounce(time.Millisecond, func() {
mu.Lock()
defer mu.Unlock()
calls = append(calls, atomic.LoadInt32(&i))
})
debounce()
atomic.AddInt32(&i, 1)
time.Sleep(5 * time.Millisecond)
debounce()
atomic.AddInt32(&i, 1)
debounce()
atomic.AddInt32(&i, 1)
debounce()
atomic.AddInt32(&i, 1)
time.Sleep(5 * time.Millisecond)
cancel()
mu.Lock()
fmt.Printf("%v", calls)
mu.Unlock()
// Output: [1 4]
}
func ExampleNewDebounceBy() {
calls := map[string][]int{}
mu := sync.Mutex{}
debounce, cancel := NewDebounceBy(time.Millisecond, func(userID string, count int) {
mu.Lock()
defer mu.Unlock()
if _, ok := calls[userID]; !ok {
calls[userID] = []int{}
}
calls[userID] = append(calls[userID], count)
})
debounce("samuel")
debounce("john")
time.Sleep(5 * time.Millisecond)
debounce("john")
debounce("john")
debounce("samuel")
debounce("john")
time.Sleep(5 * time.Millisecond)
cancel("samuel")
cancel("john")
mu.Lock()
fmt.Printf("samuel: %v\n", calls["samuel"])
fmt.Printf("john: %v\n", calls["john"])
mu.Unlock()
// Output:
// samuel: [1 1]
// john: [1 3]
}
func ExampleAttempt() {
count1, err1 := Attempt(2, func(i int) error {
if i == 0 {
return errors.New("error")
}
return nil
})
count2, err2 := Attempt(2, func(i int) error {
if i < 10 {
return errors.New("error")
}
return nil
})
fmt.Printf("%v %v\n", count1, err1)
fmt.Printf("%v %v\n", count2, err2)
// Output:
// 2
// 2 error
}
func ExampleAttemptWithDelay() {
count1, time1, err1 := AttemptWithDelay(2, time.Millisecond, func(i int, _ time.Duration) error {
if i == 0 {
return errors.New("error")
}
return nil
})
count2, time2, err2 := AttemptWithDelay(2, time.Millisecond, func(i int, _ time.Duration) error {
if i < 10 {
return errors.New("error")
}
return nil
})
fmt.Printf("%v %v %v\n", count1, time1.Truncate(time.Millisecond), err1)
fmt.Printf("%v %v %v\n", count2, time2.Truncate(time.Millisecond), err2)
// Output:
// 2 1ms
// 2 1ms error
}
func ExampleTransaction() {
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
fmt.Println("step 1")
return state + 10, nil
},
func(state int) int {
fmt.Println("rollback 1")
return state - 10
},
).
Then(
func(state int) (int, error) {
fmt.Println("step 2")
return state + 15, nil
},
func(state int) int {
fmt.Println("rollback 2")
return state - 15
},
).
Then(
func(state int) (int, error) {
fmt.Println("step 3")
if true {
return state, errors.New("error")
}
return state + 42, nil
},
func(state int) int {
fmt.Println("rollback 3")
return state - 42
},
)
_, _ = transaction.Process(-5)
// Output:
// step 1
// step 2
// step 3
// rollback 2
// rollback 1
}
func ExampleTransaction_ok() {
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 10, nil
},
func(state int) int {
return state - 10
},
).
Then(
func(state int) (int, error) {
return state + 15, nil
},
func(state int) int {
return state - 15
},
).
Then(
func(state int) (int, error) {
return state + 42, nil
},
func(state int) int {
return state - 42
},
)
state, err := transaction.Process(-5)
fmt.Println(state)
fmt.Println(err)
// Output:
// 62
//
}
func ExampleTransaction_error() {
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 10, nil
},
func(state int) int {
return state - 10
},
).
Then(
func(state int) (int, error) {
return state, errors.New("error")
},
func(state int) int {
return state - 15
},
).
Then(
func(state int) (int, error) {
return state + 42, nil
},
func(state int) int {
return state - 42
},
)
state, err := transaction.Process(-5)
fmt.Println(state)
fmt.Println(err)
// Output:
// -5
// error
}
func ExampleNewThrottle() {
throttle, reset := NewThrottle(100*time.Millisecond, func() {
fmt.Println("Called once in every 100ms")
})
for j := 0; j < 10; j++ {
throttle()
time.Sleep(30 * time.Millisecond)
}
reset()
// Output:
// Called once in every 100ms
// Called once in every 100ms
// Called once in every 100ms
}
func ExampleNewThrottleWithCount() {
throttle, reset := NewThrottleWithCount(100*time.Millisecond, 2, func() {
fmt.Println("Called once in every 100ms")
})
for j := 0; j < 10; j++ {
throttle()
time.Sleep(30 * time.Millisecond)
}
reset()
// Output:
// Called once in every 100ms
// Called once in every 100ms
// Called once in every 100ms
// Called once in every 100ms
// Called once in every 100ms
// Called once in every 100ms
}
func ExampleNewThrottleBy() {
throttle, reset := NewThrottleBy(100*time.Millisecond, func(key string) {
fmt.Println(key, "Called once in every 100ms")
})
for j := 0; j < 10; j++ {
throttle("foo")
throttle("bar")
time.Sleep(30 * time.Millisecond)
}
reset()
// Output:
// foo Called once in every 100ms
// bar Called once in every 100ms
// foo Called once in every 100ms
// bar Called once in every 100ms
// foo Called once in every 100ms
// bar Called once in every 100ms
}
func ExampleNewThrottleByWithCount() {
throttle, reset := NewThrottleByWithCount(100*time.Millisecond, 2, func(key string) {
fmt.Println(key, "Called once in every 100ms")
})
for j := 0; j < 10; j++ {
throttle("foo")
throttle("bar")
time.Sleep(30 * time.Millisecond)
}
reset()
// Output:
// foo Called once in every 100ms
// bar Called once in every 100ms
// foo Called once in every 100ms
// bar Called once in every 100ms
// foo Called once in every 100ms
// bar Called once in every 100ms
// foo Called once in every 100ms
// bar Called once in every 100ms
// foo Called once in every 100ms
// bar Called once in every 100ms
// foo Called once in every 100ms
// bar Called once in every 100ms
}
================================================
FILE: retry_test.go
================================================
package lo
import (
"errors"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestAttempt(t *testing.T) {
t.Parallel()
is := assert.New(t)
err := errors.New("failed")
iter1, err1 := Attempt(42, func(i int) error {
return nil
})
iter2, err2 := Attempt(42, func(i int) error {
if i == 5 {
return nil
}
return err
})
iter3, err3 := Attempt(2, func(i int) error {
if i == 5 {
return nil
}
return err
})
iter4, err4 := Attempt(0, func(i int) error {
if i < 42 {
return err
}
return nil
})
is.Equal(1, iter1)
is.NoError(err1)
is.Equal(6, iter2)
is.NoError(err2)
is.Equal(2, iter3)
is.ErrorIs(err3, err)
is.Equal(43, iter4)
is.NoError(err4)
}
func TestAttemptWithDelay(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
err := errors.New("failed")
iter1, dur1, err1 := AttemptWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) error {
return nil
})
iter2, dur2, err2 := AttemptWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) error {
if i == 3 {
return nil
}
return err
})
iter3, dur3, err3 := AttemptWithDelay(2, 10*time.Millisecond, func(i int, d time.Duration) error {
if i == 3 {
return nil
}
return err
})
iter4, dur4, err4 := AttemptWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) error {
if i < 10 {
return err
}
return nil
})
is.Equal(1, iter1)
is.InDelta(1*time.Microsecond, dur1, float64(1*time.Millisecond))
is.NoError(err1)
is.Equal(4, iter2)
is.InDelta(30*time.Millisecond, dur2, float64(5*time.Millisecond))
is.NoError(err2)
is.Equal(2, iter3)
is.InDelta(10*time.Millisecond, dur3, float64(5*time.Millisecond))
is.ErrorIs(err3, err)
is.Equal(11, iter4)
is.InDelta(100*time.Millisecond, dur4, float64(5*time.Millisecond))
is.NoError(err4)
}
func TestAttemptWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)
err := errors.New("failed")
iter1, err1 := AttemptWhile(42, func(i int) (error, bool) {
return nil, true
})
is.Equal(1, iter1)
is.NoError(err1)
iter2, err2 := AttemptWhile(42, func(i int) (error, bool) {
if i == 5 {
return nil, true
}
return err, true
})
is.Equal(6, iter2)
is.NoError(err2)
iter3, err3 := AttemptWhile(2, func(i int) (error, bool) {
if i == 5 {
return nil, true
}
return err, true
})
is.Equal(2, iter3)
is.ErrorIs(err3, err)
iter4, err4 := AttemptWhile(0, func(i int) (error, bool) {
if i < 42 {
return err, true
}
return nil, true
})
is.Equal(43, iter4)
is.NoError(err4)
iter5, err5 := AttemptWhile(0, func(i int) (error, bool) {
if i == 5 {
return nil, false
}
return err, true
})
is.Equal(6, iter5)
is.NoError(err5)
iter6, err6 := AttemptWhile(0, func(i int) (error, bool) {
return nil, false
})
is.Equal(1, iter6)
is.NoError(err6)
iter7, err7 := AttemptWhile(42, func(i int) (error, bool) {
if i == 42 {
return nil, false
}
if i < 41 {
return err, true
}
return nil, true
})
is.Equal(42, iter7)
is.NoError(err7)
}
func TestAttemptWhileWithDelay(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
err := errors.New("failed")
iter1, dur1, err1 := AttemptWhileWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) {
return nil, true
})
is.Equal(1, iter1)
is.InDelta(1*time.Microsecond, dur1, float64(3*time.Millisecond))
is.NoError(err1)
iter2, dur2, err2 := AttemptWhileWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) {
if i == 3 {
return nil, true
}
return err, true
})
is.Equal(4, iter2)
is.InDelta(30*time.Millisecond, dur2, float64(5*time.Millisecond))
is.NoError(err2)
iter3, dur3, err3 := AttemptWhileWithDelay(2, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) {
if i == 5 {
return nil, true
}
return err, true
})
is.Equal(2, iter3)
is.InDelta(10*time.Millisecond, dur3, float64(5*time.Millisecond))
is.ErrorIs(err3, err)
iter4, dur4, err4 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) {
if i < 10 {
return err, true
}
return nil, true
})
is.Equal(11, iter4)
is.InDelta(100*time.Millisecond, dur4, float64(5*time.Millisecond))
is.NoError(err4)
iter5, dur5, err5 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) {
if i == 3 {
return nil, false
}
return err, true
})
is.Equal(4, iter5)
is.InDelta(30*time.Millisecond, dur5, float64(5*time.Millisecond))
is.NoError(err5)
iter6, dur6, err6 := AttemptWhileWithDelay(0, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) {
return nil, false
})
is.Equal(1, iter6)
is.InDelta(1*time.Microsecond, dur6, float64(5*time.Millisecond))
is.NoError(err6)
iter7, dur7, err7 := AttemptWhileWithDelay(42, 10*time.Millisecond, func(i int, d time.Duration) (error, bool) {
if i == 42 {
return nil, false
}
if i < 41 {
return err, true
}
return nil, true
})
is.Equal(42, iter7)
is.InDelta(410*time.Millisecond, dur7, float64(5*time.Millisecond))
is.NoError(err7)
}
func TestDebounce(t *testing.T) { //nolint:paralleltest
// t.Parallel()
f1 := func() {
println("1. Called once after 10ms when func stopped invoking!")
}
f2 := func() {
println("2. Called once after 10ms when func stopped invoking!")
}
f3 := func() {
println("3. Called once after 10ms when func stopped invoking!")
}
d1, _ := NewDebounce(100*time.Millisecond, f1)
// execute 3 times
for i := 0; i < 3; i++ {
for j := 0; j < 10; j++ {
d1()
}
time.Sleep(200 * time.Millisecond)
}
d2, _ := NewDebounce(100*time.Millisecond, f2)
// execute once because it is always invoked and only last invoke is worked after 100ms
for i := 0; i < 3; i++ {
for j := 0; j < 5; j++ {
d2()
}
time.Sleep(50 * time.Millisecond)
}
time.Sleep(100 * time.Millisecond)
// execute once because it is canceled after 200ms.
d3, cancel := NewDebounce(100*time.Millisecond, f3)
for i := 0; i < 3; i++ {
for j := 0; j < 10; j++ {
d3()
}
time.Sleep(200 * time.Millisecond)
if i == 0 {
cancel()
}
}
}
func TestDebounceBy(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
mu := sync.Mutex{}
output := map[int]int{0: 0, 1: 0, 2: 0}
f1 := func(key, count int) {
mu.Lock()
output[key] += count
mu.Unlock()
// fmt.Printf("[key=%d] 1. Called once after 10ms when func stopped invoking!\n", key)
}
f2 := func(key, count int) {
mu.Lock()
output[key] += count
mu.Unlock()
// fmt.Printf("[key=%d] 2. Called once after 10ms when func stopped invoking!\n", key)
}
f3 := func(key, count int) {
mu.Lock()
output[key] += count
mu.Unlock()
// fmt.Printf("[key=%d] 3. Called once after 10ms when func stopped invoking!\n", key)
}
d1, _ := NewDebounceBy(100*time.Millisecond, f1)
// execute 3 times
for i := 0; i < 3; i++ {
for j := 0; j < 10; j++ {
for k := 0; k < 3; k++ {
d1(k)
}
}
time.Sleep(200 * time.Millisecond)
}
// Wait for debounced calls to complete
time.Sleep(150 * time.Millisecond)
mu.Lock()
is.Equal(30, output[0])
is.Equal(30, output[1])
is.Equal(30, output[2])
mu.Unlock()
d2, _ := NewDebounceBy(100*time.Millisecond, f2)
// execute once because it is always invoked and only last invoke is worked after 100ms
for i := 0; i < 3; i++ {
for j := 0; j < 5; j++ {
for k := 0; k < 3; k++ {
d2(k)
}
}
time.Sleep(50 * time.Millisecond)
}
// Wait for debounced calls to complete
time.Sleep(150 * time.Millisecond)
mu.Lock()
is.Equal(45, output[0])
is.Equal(45, output[1])
is.Equal(45, output[2])
mu.Unlock()
// execute once because it is canceled after 200ms.
d3, cancel := NewDebounceBy(100*time.Millisecond, f3)
for i := 0; i < 3; i++ {
for j := 0; j < 10; j++ {
for k := 0; k < 3; k++ {
d3(k)
}
}
time.Sleep(200 * time.Millisecond)
if i == 0 {
for k := 0; k < 3; k++ {
cancel(k)
}
}
}
// Wait for debounced calls to complete
time.Sleep(150 * time.Millisecond)
mu.Lock()
is.Equal(75, output[0])
is.Equal(75, output[1])
is.Equal(75, output[2])
mu.Unlock()
}
func TestTransaction(t *testing.T) {
t.Parallel()
is := assert.New(t)
// no error
{
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 100, nil
},
func(state int) int {
return state - 100
},
).
Then(
func(state int) (int, error) {
return state + 21, nil
},
func(state int) int {
return state - 21
},
)
state, err := transaction.Process(21)
is.Equal(142, state)
is.NoError(err)
}
// with error
{
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 100, nil
},
func(state int) int {
return state - 100
},
).
Then(
func(state int) (int, error) {
return state, assert.AnError
},
func(state int) int {
return state - 21
},
).
Then(
func(state int) (int, error) {
return state + 42, nil
},
func(state int) int {
return state - 42
},
)
state, err := transaction.Process(21)
is.Equal(21, state)
is.ErrorIs(err, assert.AnError)
}
// with error + update value
{
transaction := NewTransaction[int]().
Then(
func(state int) (int, error) {
return state + 100, nil
},
func(state int) int {
return state - 100
},
).
Then(
func(state int) (int, error) {
return state + 21, assert.AnError
},
func(state int) int {
return state - 21
},
).
Then(
func(state int) (int, error) {
return state + 42, nil
},
func(state int) int {
return state - 42
},
)
state, err := transaction.Process(21)
is.Equal(42, state)
is.ErrorIs(err, assert.AnError)
}
}
func TestNewThrottle(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
callCount := 0
f1 := func() {
callCount++
}
th, reset := NewThrottle(100*time.Millisecond, f1)
is.Zero(callCount)
for j := 0; j < 100; j++ {
th()
}
is.Equal(1, callCount)
time.Sleep(150 * time.Millisecond)
for j := 0; j < 100; j++ {
th()
}
is.Equal(2, callCount)
// reset counter
reset()
th()
is.Equal(3, callCount)
}
func TestNewThrottleWithCount(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
callCount := 0
f1 := func() {
callCount++
}
th, reset := NewThrottleWithCount(100*time.Millisecond, 3, f1)
// the function does not throttle for initial count number
for i := 0; i < 20; i++ {
th()
}
is.Equal(3, callCount)
time.Sleep(150 * time.Millisecond)
for i := 0; i < 20; i++ {
th()
}
is.Equal(6, callCount)
reset()
for i := 0; i < 20; i++ {
th()
}
is.Equal(9, callCount)
}
func TestNewThrottleBy(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
callCountA := 0
callCountB := 0
f1 := func(key string) {
if key == "a" {
callCountA++
} else {
callCountB++
}
}
th, reset := NewThrottleBy(100*time.Millisecond, f1)
is.Zero(callCountA)
is.Zero(callCountB)
for j := 0; j < 100; j++ {
th("a")
th("b")
}
is.Equal(1, callCountA)
is.Equal(1, callCountB)
time.Sleep(150 * time.Millisecond)
for j := 0; j < 100; j++ {
th("a")
th("b")
}
is.Equal(2, callCountA)
is.Equal(2, callCountB)
// reset counter
reset()
th("a")
is.Equal(3, callCountA)
is.Equal(2, callCountB)
}
func TestNewThrottleByWithCount(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
callCountA := 0
callCountB := 0
f1 := func(key string) {
if key == "a" {
callCountA++
} else {
callCountB++
}
}
th, reset := NewThrottleByWithCount(100*time.Millisecond, 3, f1)
// the function does not throttle for initial count number
for i := 0; i < 20; i++ {
th("a")
th("b")
}
is.Equal(3, callCountA)
is.Equal(3, callCountB)
time.Sleep(150 * time.Millisecond)
for i := 0; i < 20; i++ {
th("a")
th("b")
}
is.Equal(6, callCountA)
is.Equal(6, callCountB)
reset()
for i := 0; i < 20; i++ {
th("a")
}
is.Equal(9, callCountA)
is.Equal(6, callCountB)
}
================================================
FILE: slice.go
================================================
package lo
import (
"sort"
"github.com/samber/lo/internal/constraints"
"github.com/samber/lo/mutable"
)
// Filter iterates over elements of collection, returning a slice of all elements predicate returns true for.
// Play: https://go.dev/play/p/Apjg3WeSi7K
func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice {
result := make(Slice, 0, len(collection))
for i := range collection {
if predicate(collection[i], i) {
result = append(result, collection[i])
}
}
return result
}
// FilterErr iterates over elements of collection, returning a slice of all elements predicate returns true for.
// If the predicate returns an error, iteration stops immediately and returns the error.
// Play: https://go.dev/play/p/Apjg3WeSi7K
func FilterErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error) {
result := make(Slice, 0, len(collection))
for i := range collection {
ok, err := predicate(collection[i], i)
if err != nil {
return nil, err
}
if ok {
result = append(result, collection[i])
}
}
return result, nil
}
// Map manipulates a slice and transforms it to a slice of another type.
// Play: https://go.dev/play/p/OkPcYAhBo0D
func Map[T, R any](collection []T, transform func(item T, index int) R) []R {
result := make([]R, len(collection))
for i := range collection {
result[i] = transform(collection[i], i)
}
return result
}
// MapErr manipulates a slice and transforms it to a slice of another type.
// It returns the first error returned by the transform function.
func MapErr[T, R any](collection []T, transform func(item T, index int) (R, error)) ([]R, error) {
result := make([]R, len(collection))
for i := range collection {
r, err := transform(collection[i], i)
if err != nil {
return nil, err
}
result[i] = r
}
return result, nil
}
// UniqMap manipulates a slice and transforms it to a slice of another type with unique values.
// Play: https://go.dev/play/p/fygzLBhvUdB
func UniqMap[T any, R comparable](collection []T, transform func(item T, index int) R) []R {
result := make([]R, 0, len(collection))
seen := make(map[R]struct{}, len(collection))
for i := range collection {
r := transform(collection[i], i)
if _, ok := seen[r]; !ok {
seen[r] = struct{}{}
result = append(result, r)
}
}
return result
}
// FilterMap returns a slice obtained after both filtering and mapping using the given callback function.
// The callback function should return two values:
// - the result of the mapping operation and
// - whether the result element should be included or not.
//
// Play: https://go.dev/play/p/CgHYNUpOd1I
func FilterMap[T, R any](collection []T, callback func(item T, index int) (R, bool)) []R {
result := make([]R, 0, len(collection))
for i := range collection {
if r, ok := callback(collection[i], i); ok {
result = append(result, r)
}
}
return result
}
// FlatMap manipulates a slice and transforms and flattens it to a slice of another type.
// The transform function can either return a slice or a `nil`, and in the `nil` case
// no value is added to the final slice.
// Play: https://go.dev/play/p/pFCF5WVB225
func FlatMap[T, R any](collection []T, transform func(item T, index int) []R) []R {
result := make([]R, 0, len(collection))
for i := range collection {
result = append(result, transform(collection[i], i)...)
}
return result
}
// FlatMapErr manipulates a slice and transforms and flattens it to a slice of another type.
// The transform function can either return a slice or a `nil`, and in the `nil` case
// no value is added to the final slice.
// It returns the first error returned by the transform function.
func FlatMapErr[T, R any](collection []T, transform func(item T, index int) ([]R, error)) ([]R, error) {
result := make([]R, 0, len(collection))
for i := range collection {
r, err := transform(collection[i], i)
if err != nil {
return nil, err
}
result = append(result, r...)
}
return result, nil
}
// Reduce reduces collection to a value which is the accumulated result of running each element in collection
// through accumulator, where each successive invocation is supplied the return value of the previous.
// Play: https://go.dev/play/p/CgHYNUpOd1I
func Reduce[T, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R {
for i := range collection {
initial = accumulator(initial, collection[i], i)
}
return initial
}
// ReduceErr reduces collection to a value which is the accumulated result of running each element in collection
// through accumulator, where each successive invocation is supplied the return value of the previous.
// It returns the first error returned by the accumulator function.
func ReduceErr[T, R any](collection []T, accumulator func(agg R, item T, index int) (R, error), initial R) (R, error) {
for i := range collection {
result, err := accumulator(initial, collection[i], i)
if err != nil {
var zero R
return zero, err
}
initial = result
}
return initial, nil
}
// ReduceRight is like Reduce except that it iterates over elements of collection from right to left.
// Play: https://go.dev/play/p/Fq3W70l7wXF
func ReduceRight[T, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R {
for i := len(collection) - 1; i >= 0; i-- {
initial = accumulator(initial, collection[i], i)
}
return initial
}
// ReduceRightErr is like ReduceRight except that the accumulator function can return an error.
// It returns the first error returned by the accumulator function.
func ReduceRightErr[T, R any](collection []T, accumulator func(agg R, item T, index int) (R, error), initial R) (R, error) {
for i := len(collection) - 1; i >= 0; i-- {
result, err := accumulator(initial, collection[i], i)
if err != nil {
var zero R
return zero, err
}
initial = result
}
return initial, nil
}
// ForEach iterates over elements of collection and invokes callback for each element.
// Play: https://go.dev/play/p/oofyiUPRf8t
func ForEach[T any](collection []T, callback func(item T, index int)) {
for i := range collection {
callback(collection[i], i)
}
}
// ForEachWhile iterates over elements of collection and invokes predicate for each element
// collection return value decide to continue or break, like do while().
// Play: https://go.dev/play/p/QnLGt35tnow
func ForEachWhile[T any](collection []T, predicate func(item T, index int) bool) {
for i := range collection {
if !predicate(collection[i], i) {
break
}
}
}
// Times invokes the iteratee n times, returning a slice of the results of each invocation.
// The iteratee is invoked with index as argument.
// Play: https://go.dev/play/p/vgQj3Glr6lT
func Times[T any](count int, iteratee func(index int) T) []T {
result := make([]T, count)
for i := 0; i < count; i++ {
result[i] = iteratee(i)
}
return result
}
// Uniq returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept.
// The order of result values is determined by the order they occur in the slice.
// Play: https://go.dev/play/p/DTzbeXZ6iEN
func Uniq[T comparable, Slice ~[]T](collection Slice) Slice {
result := make(Slice, 0, len(collection))
seen := make(map[T]struct{}, len(collection))
for i := range collection {
if _, ok := seen[collection[i]]; ok {
continue
}
seen[collection[i]] = struct{}{}
result = append(result, collection[i])
}
return result
}
// UniqBy returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept.
// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
// Play: https://go.dev/play/p/g42Z3QSb53u
func UniqBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice {
result := make(Slice, 0, len(collection))
seen := make(map[U]struct{}, len(collection))
for i := range collection {
key := iteratee(collection[i])
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
result = append(result, collection[i])
}
return result
}
// UniqByErr returns a duplicate-free version of a slice, in which only the first occurrence of each element is kept.
// The order of result values is determined by the order they occur in the slice. It accepts `iteratee` which is
// invoked for each element in the slice to generate the criterion by which uniqueness is computed.
// It returns the first error returned by the iteratee function.
func UniqByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (Slice, error) {
result := make(Slice, 0, len(collection))
seen := make(map[U]struct{}, len(collection))
for i := range collection {
key, err := iteratee(collection[i])
if err != nil {
return nil, err
}
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
result = append(result, collection[i])
}
return result, nil
}
// GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee.
// Play: https://go.dev/play/p/XnQBd_v6brd
func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) map[U]Slice {
result := map[U]Slice{}
for i := range collection {
key := iteratee(collection[i])
result[key] = append(result[key], collection[i])
}
return result
}
// GroupByErr returns an object composed of keys generated from the results of running each element of collection through iteratee.
// It returns the first error returned by the iteratee function.
// Play: https://go.dev/play/p/BzKPcY3AdX2
func GroupByErr[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) (U, error)) (map[U]Slice, error) {
result := map[U]Slice{}
for i := range collection {
key, err := iteratee(collection[i])
if err != nil {
return nil, err
}
result[key] = append(result[key], collection[i])
}
return result, nil
}
// GroupByMap returns an object composed of keys generated from the results of running each element of collection through transform.
// Play: https://go.dev/play/p/iMeruQ3_W80
func GroupByMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K][]V {
result := map[K][]V{}
for i := range collection {
k, v := transform(collection[i])
result[k] = append(result[k], v)
}
return result
}
// GroupByMapErr returns an object composed of keys generated from the results of running each element of collection through transform.
// It returns the first error returned by the transform function.
func GroupByMapErr[T any, K comparable, V any](collection []T, transform func(item T) (K, V, error)) (map[K][]V, error) {
result := map[K][]V{}
for i := range collection {
k, v, err := transform(collection[i])
if err != nil {
return nil, err
}
result[k] = append(result[k], v)
}
return result, nil
}
// Chunk returns a slice of elements split into groups of length size. If the slice can't be split evenly,
// the final chunk will be the remaining elements.
// Play: https://go.dev/play/p/kEMkFbdu85g
func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice {
if size <= 0 {
panic("lo.Chunk: size must be greater than 0")
}
chunksNum := len(collection) / size
if len(collection)%size != 0 {
chunksNum++
}
result := make([]Slice, 0, chunksNum)
for i := 0; i < chunksNum; i++ {
last := (i + 1) * size
if last > len(collection) {
last = len(collection)
}
// Copy chunk in a new slice, to prevent memory leak and free memory from initial collection.
newSlice := make(Slice, last-i*size)
copy(newSlice, collection[i*size:last])
result = append(result, newSlice)
}
return result
}
// PartitionBy returns a slice of elements split into groups. The order of grouped values is
// determined by the order they occur in collection. The grouping is generated from the results
// of running each element of collection through iteratee.
// Play: https://go.dev/play/p/NfQ_nGjkgXW
func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K) []Slice {
result := []Slice{}
seen := map[K]int{}
for i := range collection {
key := iteratee(collection[i])
resultIndex, ok := seen[key]
if ok {
result[resultIndex] = append(result[resultIndex], collection[i])
} else {
seen[key] = len(result)
result = append(result, Slice{collection[i]})
}
}
return result
// unordered:
// groups := GroupBy[T, K](collection, iteratee)
// return Values[K, []T](groups)
}
// PartitionByErr partitions a slice into groups determined by a key computed from each element.
// The order of the partitions is determined by the order they occur in collection. The grouping
// is generated from the results of running each element of collection through iteratee.
// It returns the first error returned by the iteratee function.
func PartitionByErr[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) (K, error)) ([]Slice, error) {
result := []Slice{}
seen := map[K]int{}
for i := range collection {
key, err := iteratee(collection[i])
if err != nil {
return nil, err
}
resultIndex, ok := seen[key]
if ok {
result[resultIndex] = append(result[resultIndex], collection[i])
} else {
seen[key] = len(result)
result = append(result, Slice{collection[i]})
}
}
return result, nil
}
// Flatten returns a slice a single level deep.
// See also: Concat
// Play: https://go.dev/play/p/rbp9ORaMpjw
func Flatten[T any, Slice ~[]T](collection []Slice) Slice {
totalLen := 0
for i := range collection {
totalLen += len(collection[i])
}
result := make(Slice, 0, totalLen)
for i := range collection {
result = append(result, collection[i]...)
}
return result
}
// Concat returns a new slice containing all the elements in collections. Concat conserves the order of the elements.
// See also: Flatten, Union.
// Play: https://go.dev/play/p/Ux2UuR2xpRK
func Concat[T any, Slice ~[]T](collections ...Slice) Slice {
return Flatten(collections)
}
// Window creates a slice of sliding windows of a given size.
// Each window overlaps with the previous one by size-1 elements.
// This is equivalent to Sliding(collection, size, 1).
func Window[T any, Slice ~[]T](collection Slice, size int) []Slice {
if size <= 0 {
panic("lo.Window: size must be greater than 0")
}
return Sliding(collection, size, 1)
}
// Sliding creates a slice of sliding windows of a given size with a given step.
// If step is equal to size, windows don't overlap (similar to Chunk).
// If step is less than size, windows overlap.
// Play: https://go.dev/play/p/aIIU6gWxl2T
func Sliding[T any, Slice ~[]T](collection Slice, size, step int) []Slice {
if size <= 0 {
panic("lo.Sliding: size must be greater than 0")
}
if step <= 0 {
panic("lo.Sliding: step must be greater than 0")
}
n := len(collection) - size
if n < 0 {
return []Slice{}
}
result := make([]Slice, 0, n/step+1)
for i := 0; i <= n; i += step {
window := make(Slice, size)
copy(window, collection[i:i+size])
result = append(result, window)
}
return result
}
// Interleave round-robin alternating input slices and sequentially appending value at index into result.
// Play: https://go.dev/play/p/KOVtGUt-tdI
func Interleave[T any, Slice ~[]T](collections ...Slice) Slice {
if len(collections) == 0 {
return Slice{}
}
maxSize := 0
totalSize := 0
for i := range collections {
size := len(collections[i])
totalSize += size
if size > maxSize {
maxSize = size
}
}
if maxSize == 0 {
return Slice{}
}
result := make(Slice, totalSize)
resultIdx := 0
for i := 0; i < maxSize; i++ {
for j := range collections {
if len(collections[j])-1 < i {
continue
}
result[resultIdx] = collections[j][i]
resultIdx++
}
}
return result
}
// Shuffle returns a slice of shuffled values. Uses the Fisher-Yates shuffle algorithm.
// Play: https://go.dev/play/p/ZTGG7OUCdnp
//
// Deprecated: use mutable.Shuffle() instead.
func Shuffle[T any, Slice ~[]T](collection Slice) Slice {
mutable.Shuffle(collection)
return collection
}
// Reverse reverses a slice so that the first element becomes the last, the second element becomes the second to last, and so on.
// Play: https://go.dev/play/p/iv2e9jslfBM
//
// Deprecated: use mutable.Reverse() instead.
func Reverse[T any, Slice ~[]T](collection Slice) Slice {
mutable.Reverse(collection)
return collection
}
// Fill fills elements of a slice with `initial` value.
// Play: https://go.dev/play/p/VwR34GzqEub
func Fill[T Clonable[T], Slice ~[]T](collection Slice, initial T) Slice {
result := make(Slice, len(collection))
for i := range collection {
result[i] = initial.Clone()
}
return result
}
// Repeat builds a slice with N copies of initial value.
// Play: https://go.dev/play/p/g3uHXbmc3b6
func Repeat[T Clonable[T]](count int, initial T) []T {
result := make([]T, count)
for i := 0; i < count; i++ {
result[i] = initial.Clone()
}
return result
}
// RepeatBy builds a slice with values returned by N calls of callback.
// Play: https://go.dev/play/p/ozZLCtX_hNU
func RepeatBy[T any](count int, callback func(index int) T) []T {
result := make([]T, count)
for i := 0; i < count; i++ {
result[i] = callback(i)
}
return result
}
// RepeatByErr builds a slice with values returned by N calls of callback.
// It returns the first error returned by the callback function.
func RepeatByErr[T any](count int, callback func(index int) (T, error)) ([]T, error) {
result := make([]T, 0, count)
for i := 0; i < count; i++ {
r, err := callback(i)
if err != nil {
return nil, err
}
result = append(result, r)
}
return result, nil
}
// KeyBy transforms a slice or a slice of structs to a map based on a pivot callback.
// Play: https://go.dev/play/p/ccUiUL_Lnel
func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V {
result := make(map[K]V, len(collection))
for i := range collection {
k := iteratee(collection[i])
result[k] = collection[i]
}
return result
}
// KeyByErr transforms a slice or a slice of structs to a map based on a pivot callback to compute keys.
// Iteratee can return an error to stop iteration immediately.
// Play: https://go.dev/play/p/ccUiUL_Lnel
func KeyByErr[K comparable, V any](collection []V, iteratee func(item V) (K, error)) (map[K]V, error) {
result := make(map[K]V, len(collection))
for i := range collection {
k, err := iteratee(collection[i])
if err != nil {
return nil, err
}
result[k] = collection[i]
}
return result, nil
}
// Associate returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
// Play: https://go.dev/play/p/WHa2CfMO3Lr
func Associate[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V {
return AssociateI(collection, func(item T, _ int) (K, V) {
return transform(item)
})
}
// AssociateI returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
// Play: https://go.dev/play/p/Ugmz6S22rRO
func AssociateI[T any, K comparable, V any](collection []T, transform func(item T, index int) (K, V)) map[K]V {
result := make(map[K]V, len(collection))
for index, item := range collection {
k, v := transform(item, index)
result[k] = v
}
return result
}
// SliceToMap returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
// Alias of Associate().
// Play: https://go.dev/play/p/WHa2CfMO3Lr
func SliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V {
return Associate(collection, transform)
}
// SliceToMapI returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
// Alias of AssociateI().
// Play: https://go.dev/play/p/mMBm5GV3_eq
func SliceToMapI[T any, K comparable, V any](collection []T, transform func(item T, index int) (K, V)) map[K]V {
return AssociateI(collection, transform)
}
// FilterSliceToMap returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
// The third return value of the transform function is a boolean that indicates whether the key-value pair should be included in the map.
// Play: https://go.dev/play/p/eurMiQEqey2
func FilterSliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V, bool)) map[K]V {
return FilterSliceToMapI(collection, func(item T, _ int) (K, V, bool) {
return transform(item)
})
}
// FilterSliceToMapI returns a map containing key-value pairs provided by transform function applied to elements of the given slice.
// If any of two pairs have the same key the last one gets added to the map.
// The order of keys in returned map is not specified and is not guaranteed to be the same from the original slice.
// The third return value of the transform function is a boolean that indicates whether the key-value pair should be included in the map.
// Play: https://go.dev/play/p/mSz_bUIk9aJ
func FilterSliceToMapI[T any, K comparable, V any](collection []T, transform func(item T, index int) (K, V, bool)) map[K]V {
result := make(map[K]V, len(collection))
for index, item := range collection {
k, v, ok := transform(item, index)
if ok {
result[k] = v
}
}
return result
}
// Keyify returns a map with each unique element of the slice as a key.
// Play: https://go.dev/play/p/_d5lXdzfw32
func Keyify[T comparable, Slice ~[]T](collection Slice) map[T]struct{} {
result := make(map[T]struct{}, len(collection))
for i := range collection {
result[collection[i]] = struct{}{}
}
return result
}
// Drop drops n elements from the beginning of a slice.
// Play: https://go.dev/play/p/JswS7vXRJP2
func Drop[T any, Slice ~[]T](collection Slice, n int) Slice {
if n < 0 {
panic("lo.Drop: n must not be negative")
}
if len(collection) <= n {
return make(Slice, 0)
}
result := make(Slice, 0, len(collection)-n)
return append(result, collection[n:]...)
}
// DropRight drops n elements from the end of a slice.
// Play: https://go.dev/play/p/GG0nXkSJJa3
func DropRight[T any, Slice ~[]T](collection Slice, n int) Slice {
if n < 0 {
panic("lo.DropRight: n must not be negative")
}
if len(collection) <= n {
return Slice{}
}
result := make(Slice, 0, len(collection)-n)
return append(result, collection[:len(collection)-n]...)
}
// DropWhile drops elements from the beginning of a slice while the predicate returns true.
// Play: https://go.dev/play/p/b_PYomVQLGy
func DropWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
i := 0
for ; i < len(collection); i++ {
if !predicate(collection[i]) {
break
}
}
result := make(Slice, 0, len(collection)-i)
return append(result, collection[i:]...)
}
// DropRightWhile drops elements from the end of a slice while the predicate returns true.
// Play: https://go.dev/play/p/HBh8pHl-ZZz
func DropRightWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
i := len(collection) - 1
for ; i >= 0; i-- {
if !predicate(collection[i]) {
break
}
}
result := make(Slice, 0, i+1)
return append(result, collection[:i+1]...)
}
// Take takes the first n elements from a slice.
func Take[T any, Slice ~[]T](collection Slice, n int) Slice {
if n < 0 {
panic("lo.Take: n must not be negative")
}
if n == 0 {
return make(Slice, 0)
}
size := len(collection)
if size == 0 {
return make(Slice, 0)
}
if n >= size {
result := make(Slice, size)
copy(result, collection)
return result
}
result := make(Slice, n)
copy(result, collection)
return result
}
// TakeWhile takes elements from the beginning of a slice while the predicate returns true.
// Play: https://go.dev/play/p/NJkLGvyRWm4
func TakeWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice {
i := 0
for ; i < len(collection); i++ {
if !predicate(collection[i]) {
break
}
}
result := make(Slice, i)
copy(result, collection[:i])
return result
}
// DropByIndex drops elements from a slice by the index.
// A negative index will drop elements from the end of the slice.
// Play: https://go.dev/play/p/bPIH4npZRxS
func DropByIndex[T any, Slice ~[]T](collection Slice, indexes ...int) Slice {
initialSize := len(collection)
if initialSize == 0 {
return Slice{}
}
// do not change the input
indexes = append(make([]int, 0, len(indexes)), indexes...)
for i, index := range indexes {
if index < 0 {
indexes[i] += initialSize
}
}
sort.Ints(indexes)
prev := -1
indexes = mutable.Filter(indexes, func(index int) bool {
ok := index != prev && // uniq
uint(index) < uint(initialSize) // in range
prev = index
return ok
})
result := make(Slice, 0, initialSize-len(indexes))
i := 0
for _, index := range indexes {
result = append(result, collection[i:index]...)
i = index + 1
}
return append(result, collection[i:]...)
}
// TakeFilter filters elements and takes the first n elements that match the predicate.
// Equivalent to calling Take(Filter(...)), but more efficient as it stops after finding n matches.
// Play: https://go.dev/play/p/l99lvN4gReF
func TakeFilter[T any, Slice ~[]T](collection Slice, n int, predicate func(item T, index int) bool) Slice {
if n < 0 {
panic("lo.TakeFilter: n must not be negative")
}
if n == 0 {
return make(Slice, 0)
}
result := make(Slice, 0, n)
count := 0
for i := range collection {
if predicate(collection[i], i) {
result = append(result, collection[i])
count++
if count >= n {
break
}
}
}
return result
}
// Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return true for.
// Play: https://go.dev/play/p/7Pl-34c19Va
func Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice {
result := make(Slice, 0, len(collection))
for i := range collection {
if !predicate(collection[i], i) {
result = append(result, collection[i])
}
}
return result
}
// RejectErr is the opposite of FilterErr, this method returns the elements of collection that predicate does not return true for.
// If the predicate returns an error, iteration stops immediately and returns the error.
// Play: https://go.dev/play/p/pFCF5WVB225
func RejectErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error) {
result := make(Slice, 0, len(collection))
for i := range collection {
match, err := predicate(collection[i], i)
if err != nil {
return nil, err
}
if !match {
result = append(result, collection[i])
}
}
return result, nil
}
// RejectMap is the opposite of FilterMap, this method returns a slice obtained after both filtering and mapping using the given callback function.
// The callback function should return two values:
// - the result of the mapping operation and
// - whether the result element should be included or not.
//
// Play: https://go.dev/play/p/W9Ug9r0QFkL
func RejectMap[T, R any](collection []T, callback func(item T, index int) (R, bool)) []R {
result := make([]R, 0, len(collection))
for i := range collection {
if r, ok := callback(collection[i], i); !ok {
result = append(result, r)
}
}
return result
}
// FilterReject mixes Filter and Reject, this method returns two slices, one for the elements of collection that
// predicate returns true for and one for the elements that predicate does not return true for.
// Play: https://go.dev/play/p/lHSEGSznJjB
func FilterReject[T any, Slice ~[]T](collection Slice, predicate func(T, int) bool) (kept, rejected Slice) {
kept = make(Slice, 0, len(collection))
rejected = make(Slice, 0, len(collection))
for i := range collection {
if predicate(collection[i], i) {
kept = append(kept, collection[i])
} else {
rejected = append(rejected, collection[i])
}
}
return kept, rejected
}
// Count counts the number of elements in the collection that equal value.
// Play: https://go.dev/play/p/Y3FlK54yveC
func Count[T comparable](collection []T, value T) int {
var count int
for i := range collection {
if collection[i] == value {
count++
}
}
return count
}
// CountBy counts the number of elements in the collection for which predicate is true.
// Play: https://go.dev/play/p/5GMQP5vNT4q
func CountBy[T any](collection []T, predicate func(item T) bool) int {
var count int
for i := range collection {
if predicate(collection[i]) {
count++
}
}
return count
}
// CountByErr counts the number of elements in the collection for which predicate is true.
// It returns the first error returned by the predicate.
// Play: https://go.dev/play/p/7BnyPhpG6lW
func CountByErr[T any](collection []T, predicate func(item T) (bool, error)) (int, error) {
var count int
for i := range collection {
ok, err := predicate(collection[i])
if err != nil {
return 0, err
}
if ok {
count++
}
}
return count, nil
}
// CountValues counts the number of each element in the collection.
// Play: https://go.dev/play/p/-p-PyLT4dfy
func CountValues[T comparable](collection []T) map[T]int {
result := make(map[T]int, len(collection))
for i := range collection {
result[collection[i]]++
}
return result
}
// CountValuesBy counts the number of each element returned from transform function.
// Is equivalent to chaining lo.Map and lo.CountValues.
// Play: https://go.dev/play/p/2U0dG1SnOmS
func CountValuesBy[T any, U comparable](collection []T, transform func(item T) U) map[U]int {
result := make(map[U]int)
for i := range collection {
result[transform(collection[i])]++
}
return result
}
// Subset returns a copy of a slice from `offset` up to `length` elements. Like `slice[start:start+length]`, but does not panic on overflow.
// Play: https://go.dev/play/p/tOQu1GhFcog
func Subset[T any, Slice ~[]T](collection Slice, offset int, length uint) Slice {
size := len(collection)
if offset < 0 {
offset = size + offset
if offset < 0 {
offset = 0
}
}
if offset > size {
return Slice{}
}
if length > uint(size)-uint(offset) {
length = uint(size - offset)
}
return collection[offset : offset+int(length)]
}
// Slice returns a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow.
// Play: https://go.dev/play/p/8XWYhfMMA1h
func Slice[T any, Slice ~[]T](collection Slice, start, end int) Slice {
if start >= end {
return Slice{}
}
size := len(collection)
if start < 0 {
start = 0
} else if start > size {
start = size
}
if end < 0 {
end = 0
} else if end > size {
end = size
}
return collection[start:end]
}
// Replace returns a copy of the slice with the first n non-overlapping instances of old replaced by new.
// Play: https://go.dev/play/p/XfPzmf9gql6
func Replace[T comparable, Slice ~[]T](collection Slice, old, nEw T, n int) Slice {
result := make(Slice, len(collection))
copy(result, collection)
for i := range result {
if result[i] == old && n != 0 {
result[i] = nEw
n--
}
}
return result
}
// ReplaceAll returns a copy of the slice with all non-overlapping instances of old replaced by new.
// Play: https://go.dev/play/p/a9xZFUHfYcV
func ReplaceAll[T comparable, Slice ~[]T](collection Slice, old, nEw T) Slice {
return Replace(collection, old, nEw, -1)
}
// Clone returns a shallow copy of the collection.
// Play: https://go.dev/play/p/66LJ2wAF0rN
func Clone[T any, Slice ~[]T](collection Slice) Slice {
// backporting from slices.Clone in Go 1.21
// when we drop support for Go 1.20, this can be replaced with: return slices.Clone(collection)
// Preserve nilness in case it matters.
if collection == nil {
return nil
}
// Avoid s[:0:0] as it leads to unwanted liveness when cloning a
// zero-length slice of a large array; see https://go.dev/issue/68488.
return append(Slice{}, collection...)
}
// Compact returns a slice of all non-zero elements.
// Play: https://go.dev/play/p/tXiy-iK6PAc
func Compact[T comparable, Slice ~[]T](collection Slice) Slice {
var zero T
result := make(Slice, 0, len(collection))
for i := range collection {
if collection[i] != zero {
result = append(result, collection[i])
}
}
return result
}
// IsSorted checks if a slice is sorted.
// Play: https://go.dev/play/p/mc3qR-t4mcx
func IsSorted[T constraints.Ordered](collection []T) bool {
for i := 1; i < len(collection); i++ {
if collection[i-1] > collection[i] {
return false
}
}
return true
}
// IsSortedBy checks if a slice is sorted by iteratee.
func IsSortedBy[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool {
size := len(collection)
for i := 0; i < size-1; i++ {
if iteratee(collection[i]) > iteratee(collection[i+1]) {
return false
}
}
return true
}
// IsSortedByKey checks if a slice is sorted by iteratee.
//
// Deprecated: Use lo.IsSortedBy instead.
func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool {
return IsSortedBy(collection, iteratee)
}
// Splice inserts multiple elements at index i. A negative index counts back
// from the end of the slice. The helper is protected against overflow errors.
// Play: https://go.dev/play/p/G5_GhkeSUBA
func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice {
sizeCollection := len(collection)
sizeElements := len(elements)
output := make(Slice, 0, sizeCollection+sizeElements) // preallocate memory for the output slice
switch {
case sizeElements == 0:
return append(output, collection...) // simple copy
case i > sizeCollection:
// positive overflow
return append(append(output, collection...), elements...)
case i < -sizeCollection:
// negative overflow
return append(append(output, elements...), collection...)
case i < 0:
// backward
i = sizeCollection + i
}
return append(append(append(output, collection[:i]...), elements...), collection[i:]...)
}
// Cut slices collection around the first instance of separator, returning the part of collection
// before and after separator. The found result reports whether separator appears in collection.
// If separator does not appear in s, cut returns collection, empty slice of []T, false.
// Play: https://go.dev/play/p/GiL3qhpIP3f
func Cut[T comparable, Slice ~[]T](collection, separator Slice) (before, after Slice, found bool) {
if len(separator) == 0 {
return make(Slice, 0), collection, true
}
for i := 0; i+len(separator) <= len(collection); i++ {
match := true
for j := 0; j < len(separator); j++ {
if collection[i+j] != separator[j] {
match = false
break
}
}
if match {
return collection[:i], collection[i+len(separator):], true
}
}
return collection, make(Slice, 0), false
}
// CutPrefix returns collection without the provided leading prefix []T
// and reports whether it found the prefix.
// If s doesn't start with prefix, CutPrefix returns collection, false.
// If prefix is the empty []T, CutPrefix returns collection, true.
// Play: https://go.dev/play/p/P1scQj53aFa
func CutPrefix[T comparable, Slice ~[]T](collection, separator Slice) (after Slice, found bool) {
if HasPrefix(collection, separator) {
return collection[len(separator):], true
}
return collection, false
}
// CutSuffix returns collection without the provided ending suffix []T and reports
// whether it found the suffix. If s doesn't end with suffix, CutSuffix returns collection, false.
// If suffix is the empty []T, CutSuffix returns collection, true.
// Play: https://go.dev/play/p/7FKfBFvPTaT
func CutSuffix[T comparable, Slice ~[]T](collection, separator Slice) (before Slice, found bool) {
if HasSuffix(collection, separator) {
return collection[:len(collection)-len(separator)], true
}
return collection, false
}
// Trim removes all the leading and trailing cutset from the collection.
// Play: https://go.dev/play/p/LZLfLj5C8Lg
func Trim[T comparable, Slice ~[]T](collection, cutset Slice) Slice {
set := Keyify(cutset)
i := 0
for ; i < len(collection); i++ {
if _, ok := set[collection[i]]; !ok {
break
}
}
if i >= len(collection) {
return Slice{}
}
j := len(collection) - 1
for ; j >= 0; j-- {
if _, ok := set[collection[j]]; !ok {
break
}
}
result := make(Slice, 0, j+1-i)
return append(result, collection[i:j+1]...)
}
// TrimLeft removes all the leading cutset from the collection.
// Play: https://go.dev/play/p/fJK-AhROy9w
func TrimLeft[T comparable, Slice ~[]T](collection, cutset Slice) Slice {
set := Keyify(cutset)
return DropWhile(collection, func(item T) bool {
_, ok := set[item]
return ok
})
}
// TrimPrefix removes all the leading prefix from the collection.
// Play: https://go.dev/play/p/8O2RoWYzi8J
func TrimPrefix[T comparable, Slice ~[]T](collection, prefix Slice) Slice {
if len(prefix) == 0 {
return collection
}
for HasPrefix(collection, prefix) {
collection = collection[len(prefix):]
}
return collection
}
// TrimRight removes all the trailing cutset from the collection.
// Play: https://go.dev/play/p/9V_N8sRyyiZ
func TrimRight[T comparable, Slice ~[]T](collection, cutset Slice) Slice {
set := Keyify(cutset)
return DropRightWhile(collection, func(item T) bool {
_, ok := set[item]
return ok
})
}
// TrimSuffix removes all the trailing suffix from the collection.
// Play: https://go.dev/play/p/IjEUrV0iofq
func TrimSuffix[T comparable, Slice ~[]T](collection, suffix Slice) Slice {
if len(suffix) == 0 {
return collection
}
for HasSuffix(collection, suffix) {
collection = collection[:len(collection)-len(suffix)]
}
return collection
}
================================================
FILE: slice_test.go
================================================
package lo
import (
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFilter(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Filter([]int{1, 2, 3, 4}, func(x, _ int) bool {
return x%2 == 0
})
is.Equal([]int{2, 4}, r1)
r2 := Filter([]string{"", "foo", "", "bar", ""}, func(x string, _ int) bool {
return len(x) > 0
})
is.Equal([]string{"foo", "bar"}, r2)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Filter(allStrings, func(x string, _ int) bool {
return len(x) > 0
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFilterErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
predicate func(item, index int) (bool, error)
want []int
wantErr string
callbacks int // Number of predicates called before error/finish
}{
{
name: "filter even numbers",
input: []int{1, 2, 3, 4},
predicate: func(x, _ int) (bool, error) {
return x%2 == 0, nil
},
want: []int{2, 4},
callbacks: 4,
},
{
name: "empty slice",
input: []int{},
predicate: func(x, _ int) (bool, error) {
return true, nil
},
want: []int{},
callbacks: 0,
},
{
name: "filter all out",
input: []int{1, 2, 3, 4},
predicate: func(x, _ int) (bool, error) {
return false, nil
},
want: []int{},
callbacks: 4,
},
{
name: "filter all in",
input: []int{1, 2, 3, 4},
predicate: func(x, _ int) (bool, error) {
return true, nil
},
want: []int{1, 2, 3, 4},
callbacks: 4,
},
{
name: "error on specific index",
input: []int{1, 2, 3, 4},
predicate: func(x, _ int) (bool, error) {
if x == 3 {
return false, errors.New("number 3 is not allowed")
}
return x%2 == 0, nil
},
callbacks: 3,
wantErr: "number 3 is not allowed",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var callbacks int
wrappedPredicate := func(item, index int) (bool, error) {
callbacks++
return tt.predicate(item, index)
}
got, err := FilterErr(tt.input, wrappedPredicate)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
is.Equal(tt.callbacks, callbacks, "callback count should match expected early return")
} else {
is.NoError(err)
is.Equal(tt.want, got)
is.Equal(tt.callbacks, callbacks)
}
})
}
// Test type preservation
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty, err := FilterErr(allStrings, func(x string, _ int) (bool, error) {
return len(x) > 0, nil
})
is.NoError(err)
is.IsType(nonempty, allStrings, "type preserved")
is.Equal(myStrings{"foo", "bar"}, nonempty)
}
func TestMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Map([]int{1, 2, 3, 4}, func(x, _ int) string {
return "Hello"
})
result2 := Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string {
return strconv.FormatInt(x, 10)
})
is.Equal([]string{"Hello", "Hello", "Hello", "Hello"}, result1)
is.Equal([]string{"1", "2", "3", "4"}, result2)
}
func TestMapErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
transform func(item, index int) (string, error)
wantResult []string
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful transformation",
input: []int{1, 2, 3, 4},
transform: func(x, _ int) (string, error) {
return strconv.Itoa(x), nil
},
wantResult: []string{"1", "2", "3", "4"},
wantErr: false,
expectedCallbackCount: 4,
},
{
name: "error at third element stops iteration",
input: []int{1, 2, 3, 4},
transform: func(x, _ int) (string, error) {
if x == 3 {
return "", errors.New("number 3 is not allowed")
}
return strconv.Itoa(x), nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 3 is not allowed",
expectedCallbackCount: 3,
},
{
name: "error at first element stops iteration immediately",
input: []int{1, 2, 3, 4},
transform: func(x, _ int) (string, error) {
if x == 1 {
return "", errors.New("number 1 is not allowed")
}
return strconv.Itoa(x), nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 1 is not allowed",
expectedCallbackCount: 1,
},
{
name: "error at last element",
input: []int{1, 2, 3, 4},
transform: func(x, _ int) (string, error) {
if x == 4 {
return "", errors.New("number 4 is not allowed")
}
return strconv.Itoa(x), nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 4 is not allowed",
expectedCallbackCount: 4,
},
{
name: "empty input slice",
input: []int{},
transform: func(x, _ int) (string, error) {
return strconv.Itoa(x), nil
},
wantResult: []string{},
wantErr: false,
expectedCallbackCount: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to test early return
callbackCount := 0
wrappedTransform := func(item, index int) (string, error) {
callbackCount++
return tt.transform(item, index)
}
result, err := MapErr(tt.input, wrappedTransform)
if tt.wantErr {
is.Error(err)
is.Equal(tt.errMsg, err.Error())
is.Nil(result)
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestUniqMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
type User struct {
Name string
age int
}
users := []User{{Name: "Alice", age: 20}, {Name: "Alex", age: 21}, {Name: "Alex", age: 22}}
result := UniqMap(users, func(item User, index int) string {
return item.Name
})
sort.Strings(result)
is.Equal([]string{"Alex", "Alice"}, result)
}
func TestFilterMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := FilterMap([]int64{1, 2, 3, 4}, func(x int64, _ int) (string, bool) {
if x%2 == 0 {
return strconv.FormatInt(x, 10), true
}
return "", false
})
r2 := FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", true
}
return "", false
})
is.Equal([]string{"2", "4"}, r1)
is.Equal([]string{"xpu", "xpu"}, r2)
}
func TestFlatMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := FlatMap([]int{0, 1, 2, 3, 4}, func(x, _ int) []string {
return []string{"Hello"}
})
result2 := FlatMap([]int64{0, 1, 2, 3, 4}, func(x int64, _ int) []string {
result := make([]string, 0, x)
for i := int64(0); i < x; i++ {
result = append(result, strconv.FormatInt(x, 10))
}
return result
})
is.Equal([]string{"Hello", "Hello", "Hello", "Hello", "Hello"}, result1)
is.Equal([]string{"1", "2", "2", "3", "3", "3", "4", "4", "4", "4"}, result2)
}
func TestFlatMapErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int64
transform func(item int64, index int) ([]string, error)
wantResult []string
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful transformation",
input: []int64{0, 1, 2},
transform: func(x int64, _ int) ([]string, error) {
return []string{strconv.FormatInt(x, 10), strconv.FormatInt(x, 10)}, nil
},
wantResult: []string{"0", "0", "1", "1", "2", "2"},
wantErr: false,
expectedCallbackCount: 3,
},
{
name: "error at second element stops iteration",
input: []int64{0, 1, 2, 3},
transform: func(x int64, _ int) ([]string, error) {
if x == 1 {
return nil, errors.New("number 1 is not allowed")
}
return []string{strconv.FormatInt(x, 10)}, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 1 is not allowed",
expectedCallbackCount: 2,
},
{
name: "error at first element stops iteration immediately",
input: []int64{0, 1, 2, 3},
transform: func(x int64, _ int) ([]string, error) {
if x == 0 {
return nil, errors.New("number 0 is not allowed")
}
return []string{strconv.FormatInt(x, 10)}, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 0 is not allowed",
expectedCallbackCount: 1,
},
{
name: "error at last element",
input: []int64{0, 1, 2},
transform: func(x int64, _ int) ([]string, error) {
if x == 2 {
return nil, errors.New("number 2 is not allowed")
}
return []string{strconv.FormatInt(x, 10)}, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 2 is not allowed",
expectedCallbackCount: 3,
},
{
name: "empty input slice",
input: []int64{},
transform: func(x int64, _ int) ([]string, error) {
return []string{strconv.FormatInt(x, 10)}, nil
},
wantResult: []string{},
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "returns empty slice for each element",
input: []int64{1, 2, 3},
transform: func(x int64, _ int) ([]string, error) {
return []string{}, nil
},
wantResult: []string{},
wantErr: false,
expectedCallbackCount: 3,
},
{
name: "returns nil for some elements",
input: []int64{1, 2, 3},
transform: func(x int64, _ int) ([]string, error) {
if x == 2 {
return nil, nil
}
return []string{strconv.FormatInt(x, 10)}, nil
},
wantResult: []string{"1", "3"},
wantErr: false,
expectedCallbackCount: 3,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to test early return
callbackCount := 0
wrappedTransform := func(item int64, index int) ([]string, error) {
callbackCount++
return tt.transform(item, index)
}
result, err := FlatMapErr(tt.input, wrappedTransform)
if tt.wantErr {
is.Error(err)
is.Equal(tt.errMsg, err.Error())
is.Nil(result)
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestTimes(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
is.Equal([]string{"0", "1", "2"}, result1)
}
func TestReduce(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Reduce([]int{1, 2, 3, 4}, func(agg, item, _ int) int {
return agg + item
}, 0)
result2 := Reduce([]int{1, 2, 3, 4}, func(agg, item, _ int) int {
return agg + item
}, 10)
is.Equal(10, result1)
is.Equal(20, result2)
}
func TestReduceErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
accumulator func(agg, item, index int) (int, error)
initial int
wantResult int
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful reduction",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
return agg + item, nil
},
initial: 0,
wantResult: 10,
wantErr: false,
expectedCallbackCount: 4,
},
{
name: "error at third element stops iteration",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
if item == 3 {
return 0, errors.New("number 3 is not allowed")
}
return agg + item, nil
},
initial: 0,
wantResult: 0,
wantErr: true,
errMsg: "number 3 is not allowed",
expectedCallbackCount: 3,
},
{
name: "error at first element stops iteration immediately",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
if item == 1 {
return 0, errors.New("number 1 is not allowed")
}
return agg + item, nil
},
initial: 0,
wantResult: 0,
wantErr: true,
errMsg: "number 1 is not allowed",
expectedCallbackCount: 1,
},
{
name: "error at last element",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
if item == 4 {
return 0, errors.New("number 4 is not allowed")
}
return agg + item, nil
},
initial: 0,
wantResult: 0,
wantErr: true,
errMsg: "number 4 is not allowed",
expectedCallbackCount: 4,
},
{
name: "empty input slice",
input: []int{},
accumulator: func(agg, item, _ int) (int, error) {
return agg + item, nil
},
initial: 10,
wantResult: 10,
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "with non-zero initial value",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
return agg + item, nil
},
initial: 10,
wantResult: 20,
wantErr: false,
expectedCallbackCount: 4,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to test early return
callbackCount := 0
wrappedAccumulator := func(agg, item, index int) (int, error) {
callbackCount++
return tt.accumulator(agg, item, index)
}
result, err := ReduceErr(tt.input, wrappedAccumulator, tt.initial)
if tt.wantErr {
is.Error(err)
is.Equal(tt.errMsg, err.Error())
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestReduceRight(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg, item []int, _ int) []int {
return append(agg, item...)
}, []int{})
is.Equal([]int{4, 5, 2, 3, 0, 1}, result1)
type collection []int
result3 := ReduceRight(collection{1, 2, 3, 4}, func(agg, item, _ int) int {
return agg + item
}, 10)
is.Equal(20, result3)
}
func TestReduceRightErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
accumulator func(agg, item, index int) (int, error)
initial int
wantResult int
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful reduction",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
return agg + item, nil
},
initial: 0,
wantResult: 10,
wantErr: false,
expectedCallbackCount: 4,
},
{
name: "error at second element from right stops iteration",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
if item == 3 {
return 0, errors.New("number 3 is not allowed")
}
return agg + item, nil
},
initial: 0,
wantResult: 0,
wantErr: true,
errMsg: "number 3 is not allowed",
expectedCallbackCount: 2,
},
{
name: "error at first element from right stops iteration immediately",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
if item == 4 {
return 0, errors.New("number 4 is not allowed")
}
return agg + item, nil
},
initial: 0,
wantResult: 0,
wantErr: true,
errMsg: "number 4 is not allowed",
expectedCallbackCount: 1,
},
{
name: "error at last element from left",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
if item == 1 {
return 0, errors.New("number 1 is not allowed")
}
return agg + item, nil
},
initial: 0,
wantResult: 0,
wantErr: true,
errMsg: "number 1 is not allowed",
expectedCallbackCount: 4,
},
{
name: "empty input slice",
input: []int{},
accumulator: func(agg, item, _ int) (int, error) {
return agg + item, nil
},
initial: 10,
wantResult: 10,
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "with non-zero initial value",
input: []int{1, 2, 3, 4},
accumulator: func(agg, item, _ int) (int, error) {
return agg + item, nil
},
initial: 10,
wantResult: 20,
wantErr: false,
expectedCallbackCount: 4,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to test early return
callbackCount := 0
wrappedAccumulator := func(agg, item, index int) (int, error) {
callbackCount++
return tt.accumulator(agg, item, index)
}
result, err := ReduceRightErr(tt.input, wrappedAccumulator, tt.initial)
if tt.wantErr {
is.Error(err)
is.Equal(tt.errMsg, err.Error())
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestForEach(t *testing.T) {
t.Parallel()
is := assert.New(t)
// check of callback is called for every element and in proper order
callParams1 := []string{}
callParams2 := []int{}
ForEach([]string{"a", "b", "c"}, func(item string, i int) {
callParams1 = append(callParams1, item)
callParams2 = append(callParams2, i)
})
is.Equal([]string{"a", "b", "c"}, callParams1)
is.Equal([]int{0, 1, 2}, callParams2)
is.IsIncreasing(callParams2)
}
func TestForEachWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)
// check of callback is called for every element and in proper order
var callParams1 []string
var callParams2 []int
ForEachWhile([]string{"a", "b", "c"}, func(item string, i int) bool {
if item == "c" {
return false
}
callParams1 = append(callParams1, item)
callParams2 = append(callParams2, i)
return true
})
is.Equal([]string{"a", "b"}, callParams1)
is.Equal([]int{0, 1}, callParams2)
is.IsIncreasing(callParams2)
}
func TestUniq(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Uniq([]int{1, 2, 2, 1})
is.Equal([]int{1, 2}, result1)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Uniq(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestUniqBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i % 3
})
is.Equal([]int{0, 1, 2}, result1)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := UniqBy(allStrings, func(i string) string {
return i
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestUniqByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
iteratee func(item int) (int, error)
wantResult []int
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful uniq",
input: []int{0, 1, 2, 3, 4, 5},
iteratee: func(i int) (int, error) {
return i % 3, nil
},
wantResult: []int{0, 1, 2},
wantErr: false,
expectedCallbackCount: 6,
},
{
name: "error at fourth element stops iteration",
input: []int{0, 1, 2, 3, 4, 5},
iteratee: func(i int) (int, error) {
if i == 3 {
return 0, errors.New("number 3 is not allowed")
}
return i % 3, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 3 is not allowed",
expectedCallbackCount: 4,
},
{
name: "error at first element stops iteration immediately",
input: []int{0, 1, 2, 3, 4, 5},
iteratee: func(i int) (int, error) {
if i == 0 {
return 0, errors.New("number 0 is not allowed")
}
return i % 3, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 0 is not allowed",
expectedCallbackCount: 1,
},
{
name: "error at last element",
input: []int{0, 1, 2, 3, 4, 5},
iteratee: func(i int) (int, error) {
if i == 5 {
return 0, errors.New("number 5 is not allowed")
}
return i % 3, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 5 is not allowed",
expectedCallbackCount: 6,
},
{
name: "empty input slice",
input: []int{},
iteratee: func(i int) (int, error) {
return i % 3, nil
},
wantResult: []int{},
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "all duplicates",
input: []int{1, 1, 1, 1},
iteratee: func(i int) (int, error) {
return i % 3, nil
},
wantResult: []int{1},
wantErr: false,
expectedCallbackCount: 4,
},
{
name: "no duplicates",
input: []int{0, 1, 2, 3},
iteratee: func(i int) (int, error) {
return i, nil
},
wantResult: []int{0, 1, 2, 3},
wantErr: false,
expectedCallbackCount: 4,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to test early return
callbackCount := 0
wrappedIteratee := func(item int) (int, error) {
callbackCount++
return tt.iteratee(item)
}
result, err := UniqByErr(tt.input, wrappedIteratee)
if tt.wantErr {
is.Error(err)
is.Equal(tt.errMsg, err.Error())
is.Nil(result)
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestGroupBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i % 3
})
is.Equal(map[int][]int{
0: {0, 3},
1: {1, 4},
2: {2, 5},
}, result1)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := GroupBy(allStrings, func(i string) int {
return 42
})
is.IsType(nonempty[42], allStrings, "type preserved")
}
func TestGroupByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
iteratee func(item int) (int, error)
wantResult map[int][]int
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful grouping",
input: []int{0, 1, 2, 3, 4, 5},
iteratee: func(i int) (int, error) {
return i % 3, nil
},
wantResult: map[int][]int{
0: {0, 3},
1: {1, 4},
2: {2, 5},
},
wantErr: false,
expectedCallbackCount: 6,
},
{
name: "error at fourth element stops iteration",
input: []int{0, 1, 2, 3, 4, 5},
iteratee: func(i int) (int, error) {
if i == 3 {
return 0, errors.New("number 3 is not allowed")
}
return i % 3, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 3 is not allowed",
expectedCallbackCount: 4,
},
{
name: "error at first element stops iteration immediately",
input: []int{0, 1, 2, 3, 4, 5},
iteratee: func(i int) (int, error) {
if i == 0 {
return 0, errors.New("number 0 is not allowed")
}
return i % 3, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 0 is not allowed",
expectedCallbackCount: 1,
},
{
name: "error at last element",
input: []int{0, 1, 2, 3, 4, 5},
iteratee: func(i int) (int, error) {
if i == 5 {
return 0, errors.New("number 5 is not allowed")
}
return i % 3, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 5 is not allowed",
expectedCallbackCount: 6,
},
{
name: "empty input slice",
input: []int{},
iteratee: func(i int) (int, error) {
return i % 3, nil
},
wantResult: map[int][]int{},
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "all elements in same group",
input: []int{3, 6, 9, 12},
iteratee: func(i int) (int, error) {
return 0, nil
},
wantResult: map[int][]int{
0: {3, 6, 9, 12},
},
wantErr: false,
expectedCallbackCount: 4,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to test early return
callbackCount := 0
wrappedIteratee := func(item int) (int, error) {
callbackCount++
return tt.iteratee(item)
}
result, err := GroupByErr(tt.input, wrappedIteratee)
if tt.wantErr {
is.Error(err)
is.Equal(tt.errMsg, err.Error())
is.Nil(result)
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestGroupByMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := GroupByMap([]int{0, 1, 2, 3, 4, 5}, func(i int) (int, string) {
return i % 3, strconv.Itoa(i)
})
is.Equal(map[int][]string{
0: {"0", "3"},
1: {"1", "4"},
2: {"2", "5"},
}, result1)
type myInt int
type myInts []myInt
result2 := GroupByMap(myInts{1, 0, 2, 3, 4, 5}, func(i myInt) (int, string) {
return int(i % 3), strconv.Itoa(int(i))
})
is.Equal(map[int][]string{
0: {"0", "3"},
1: {"1", "4"},
2: {"2", "5"},
}, result2)
type product struct {
ID int64
CategoryID int64
}
products := []product{
{ID: 1, CategoryID: 1},
{ID: 2, CategoryID: 1},
{ID: 3, CategoryID: 2},
{ID: 4, CategoryID: 3},
{ID: 5, CategoryID: 3},
}
result3 := GroupByMap(products, func(item product) (int64, string) {
return item.CategoryID, "Product " + strconv.FormatInt(item.ID, 10)
})
is.Equal(map[int64][]string{
1: {"Product 1", "Product 2"},
2: {"Product 3"},
3: {"Product 4", "Product 5"},
}, result3)
}
func TestGroupByMapErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
transform func(item int) (int, int, error)
wantResult map[int][]int
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful grouping",
input: []int{0, 1, 2, 3, 4, 5},
transform: func(i int) (int, int, error) {
return i % 3, i * 2, nil
},
wantResult: map[int][]int{
0: {0, 6},
1: {2, 8},
2: {4, 10},
},
wantErr: false,
expectedCallbackCount: 6,
},
{
name: "error at fourth element stops iteration",
input: []int{0, 1, 2, 3, 4, 5},
transform: func(i int) (int, int, error) {
if i == 3 {
return 0, 0, errors.New("number 3 is not allowed")
}
return i % 3, i * 2, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 3 is not allowed",
expectedCallbackCount: 4,
},
{
name: "error at first element stops iteration immediately",
input: []int{0, 1, 2, 3, 4, 5},
transform: func(i int) (int, int, error) {
if i == 0 {
return 0, 0, errors.New("number 0 is not allowed")
}
return i % 3, i * 2, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 0 is not allowed",
expectedCallbackCount: 1,
},
{
name: "error at last element",
input: []int{0, 1, 2, 3, 4, 5},
transform: func(i int) (int, int, error) {
if i == 5 {
return 0, 0, errors.New("number 5 is not allowed")
}
return i % 3, i * 2, nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 5 is not allowed",
expectedCallbackCount: 6,
},
{
name: "empty input slice",
input: []int{},
transform: func(i int) (int, int, error) {
return i % 3, i * 2, nil
},
wantResult: map[int][]int{},
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "all elements in same group",
input: []int{3, 6, 9, 12},
transform: func(i int) (int, int, error) {
return 0, i, nil
},
wantResult: map[int][]int{
0: {3, 6, 9, 12},
},
wantErr: false,
expectedCallbackCount: 4,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to test early return
callbackCount := 0
wrappedTransform := func(item int) (int, int, error) {
callbackCount++
return tt.transform(item)
}
result, err := GroupByMapErr(tt.input, wrappedTransform)
if tt.wantErr {
is.Error(err)
is.Equal(tt.errMsg, err.Error())
is.Nil(result)
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestChunk(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Chunk([]int{0, 1, 2, 3, 4, 5}, 2)
result2 := Chunk([]int{0, 1, 2, 3, 4, 5, 6}, 2)
result3 := Chunk([]int{}, 2)
result4 := Chunk([]int{0}, 2)
is.Equal([][]int{{0, 1}, {2, 3}, {4, 5}}, result1)
is.Equal([][]int{{0, 1}, {2, 3}, {4, 5}, {6}}, result2)
is.Empty(result3)
is.Equal([][]int{{0}}, result4)
is.PanicsWithValue("lo.Chunk: size must be greater than 0", func() {
Chunk([]int{0}, 0)
})
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Chunk(allStrings, 2)
is.IsType(nonempty[0], allStrings, "type preserved")
// appending to a chunk should not affect original slice
original := []int{0, 1, 2, 3, 4, 5}
result5 := Chunk(original, 2)
result5[0] = append(result5[0], 6)
is.Equal([]int{0, 1, 2, 3, 4, 5}, original)
}
func TestWindow(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Window([]int{1, 2, 3, 4, 5}, 3)
result2 := Window([]int{1, 2, 3, 4, 5, 6}, 3)
result3 := Window([]int{1, 2}, 3)
result4 := Window([]int{1, 2, 3}, 3)
result5 := Window([]int{1, 2, 3, 4}, 1)
is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}}, result1)
is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}, result2)
is.Empty(result3)
is.Equal([][]int{{1, 2, 3}}, result4)
is.Equal([][]int{{1}, {2}, {3}, {4}}, result5)
is.PanicsWithValue("lo.Window: size must be greater than 0", func() {
Window([]int{1, 2, 3}, 0)
})
is.PanicsWithValue("lo.Window: size must be greater than 0", func() {
Window([]int{1, 2, 3}, -1)
})
type myStrings []string
allStrings := myStrings{"a", "b", "c", "d"}
windows := Window(allStrings, 2)
is.IsType(windows[0], allStrings, "type preserved")
is.Equal(myStrings{"a", "b"}, windows[0])
// appending to a window should not affect original slice
original := []int{1, 2, 3, 4, 5}
windows2 := Window(original, 3)
windows2[0] = append(windows2[0], 6)
is.Equal([]int{1, 2, 3, 4, 5}, original)
}
func TestSliding(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Overlapping windows (step < size)
result1 := Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 1)
is.Equal([][]int{{1, 2, 3}, {2, 3, 4}, {3, 4, 5}, {4, 5, 6}}, result1)
// Non-overlapping windows (step == size, like Chunk)
result2 := Sliding([]int{1, 2, 3, 4, 5, 6}, 3, 3)
is.Equal([][]int{{1, 2, 3}, {4, 5, 6}}, result2)
// Step > size (skipping elements)
result3 := Sliding([]int{1, 2, 3, 4, 5, 6, 7, 8}, 2, 3)
is.Equal([][]int{{1, 2}, {4, 5}, {7, 8}}, result3)
// Single element windows
result4 := Sliding([]int{1, 2, 3, 4}, 1, 1)
is.Equal([][]int{{1}, {2}, {3}, {4}}, result4)
// Empty result when collection is too small
result5 := Sliding([]int{1, 2}, 3, 1)
is.Empty(result5)
// Step 2, size 2
result6 := Sliding([]int{1, 2, 3, 4, 5, 6}, 2, 2)
is.Equal([][]int{{1, 2}, {3, 4}, {5, 6}}, result6)
is.PanicsWithValue("lo.Sliding: size must be greater than 0", func() {
Sliding([]int{1, 2, 3}, 0, 1)
})
is.PanicsWithValue("lo.Sliding: step must be greater than 0", func() {
Sliding([]int{1, 2, 3}, 2, 0)
})
is.PanicsWithValue("lo.Sliding: step must be greater than 0", func() {
Sliding([]int{1, 2, 3}, 2, -1)
})
type myStrings []string
allStrings := myStrings{"a", "b", "c", "d", "e"}
windows := Sliding(allStrings, 2, 2)
is.IsType(windows[0], allStrings, "type preserved")
is.Equal(myStrings{"a", "b"}, windows[0])
// appending to a window should not affect original slice
original := []int{1, 2, 3, 4, 5, 6}
windows2 := Sliding(original, 2, 2)
windows2[0] = append(windows2[0], 7)
is.Equal([]int{1, 2, 3, 4, 5, 6}, original)
}
func TestPartitionBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
oddEven := func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
}
result1 := PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, oddEven)
result2 := PartitionBy([]int{}, oddEven)
is.Equal([][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}, result1)
is.Empty(result2)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := PartitionBy(allStrings, func(item string) int {
return len(item)
})
is.IsType(nonempty[0], allStrings, "type preserved")
}
func TestPartitionByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
oddEven := func(x int) (string, error) {
if x < 0 {
return "negative", nil
} else if x%2 == 0 {
return "even", nil
}
return "odd", nil
}
tests := []struct {
name string
input []int
iteratee func(item int) (string, error)
wantResult [][]int
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful partition",
input: []int{-2, -1, 0, 1, 2, 3, 4, 5},
iteratee: oddEven,
wantResult: [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}},
wantErr: false,
expectedCallbackCount: 8,
},
{
name: "error at fifth element stops iteration",
input: []int{-2, -1, 0, 1, 2, 3},
iteratee: func(x int) (string, error) {
if x == 2 {
return "", errors.New("number 2 is not allowed")
}
return oddEven(x)
},
wantResult: nil,
wantErr: true,
errMsg: "number 2 is not allowed",
expectedCallbackCount: 5,
},
{
name: "error at first element stops iteration immediately",
input: []int{-2, -1, 0, 1},
iteratee: func(x int) (string, error) {
if x == -2 {
return "", errors.New("number -2 is not allowed")
}
return oddEven(x)
},
wantResult: nil,
wantErr: true,
errMsg: "number -2 is not allowed",
expectedCallbackCount: 1,
},
{
name: "error at last element",
input: []int{-2, -1, 0, 1, 2},
iteratee: func(x int) (string, error) {
if x == 2 {
return "", errors.New("number 2 is not allowed")
}
return oddEven(x)
},
wantResult: nil,
wantErr: true,
errMsg: "number 2 is not allowed",
expectedCallbackCount: 5,
},
{
name: "empty input slice",
input: []int{},
iteratee: oddEven,
wantResult: [][]int{},
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "all elements in same partition",
input: []int{1, 3, 5},
iteratee: func(x int) (string, error) {
return "odd", nil
},
wantResult: [][]int{{1, 3, 5}},
wantErr: false,
expectedCallbackCount: 3,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to test early return
callbackCount := 0
wrappedIteratee := func(item int) (string, error) {
callbackCount++
return tt.iteratee(item)
}
result, err := PartitionByErr(tt.input, wrappedIteratee)
if tt.wantErr {
is.Error(err)
is.Equal(tt.errMsg, err.Error())
is.Nil(result)
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestFlatten(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Flatten([][]int{{0, 1}, {2, 3, 4, 5}})
is.Equal([]int{0, 1, 2, 3, 4, 5}, result1)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Flatten([]myStrings{allStrings})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestConcat(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Concat([][]int{{0, 1}, {2, 3, 4, 5}}...)
is.Equal([]int{0, 1, 2, 3, 4, 5}, result1)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Concat([]myStrings{allStrings}...)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestInterleave(t *testing.T) {
t.Parallel()
is := assert.New(t)
testCases := []struct {
name string
in [][]int
want []int
}{
{
"nil",
[][]int{nil},
[]int{},
},
{
"empty",
[][]int{},
[]int{},
},
{
"empties",
[][]int{{}, {}},
[]int{},
},
{
"same length",
[][]int{{1, 3, 5}, {2, 4, 6}},
[]int{1, 2, 3, 4, 5, 6},
},
{
"different length",
[][]int{{1, 3, 5, 6}, {2, 4}},
[]int{1, 2, 3, 4, 5, 6},
},
{
"many slices",
[][]int{{1}, {2, 5, 8}, {3, 6}, {4, 7, 9, 10}},
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, Interleave(tc.in...))
})
}
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Interleave(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestShuffle(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Shuffle([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
result2 := Shuffle([]int{})
is.NotEqual([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, result1)
is.ElementsMatch([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, result1)
is.Empty(result2)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Shuffle(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestReverse(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Reverse([]int{0, 1, 2, 3, 4, 5})
result2 := Reverse([]int{0, 1, 2, 3, 4, 5, 6})
result3 := Reverse([]int{})
is.Equal([]int{5, 4, 3, 2, 1, 0}, result1)
is.Equal([]int{6, 5, 4, 3, 2, 1, 0}, result2)
is.Empty(result3)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Reverse(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFill(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Fill([]foo{{"a"}, {"a"}}, foo{"b"})
result2 := Fill([]foo{}, foo{"a"})
is.Equal([]foo{{"b"}, {"b"}}, result1)
is.Empty(result2)
}
func TestRepeat(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Repeat(2, foo{"a"})
result2 := Repeat(0, foo{"a"})
is.Equal([]foo{{"a"}, {"a"}}, result1)
is.Empty(result2)
}
func TestRepeatBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
cb := func(i int) int {
return int(math.Pow(float64(i), 2))
}
result1 := RepeatBy(0, cb)
result2 := RepeatBy(2, cb)
result3 := RepeatBy(5, cb)
is.Empty(result1)
is.Equal([]int{0, 1}, result2)
is.Equal([]int{0, 1, 4, 9, 16}, result3)
}
func TestRepeatByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
testErr := errors.New("test error")
// Table-driven tests
tests := []struct {
name string
count int
callback func(index int) (int, error)
wantResult []int
wantErr bool
expectedCallbackCount int
}{
{
name: "successful completion",
count: 5,
callback: func(i int) (int, error) {
return i * i, nil
},
wantResult: []int{0, 1, 4, 9, 16},
wantErr: false,
expectedCallbackCount: 5,
},
{
name: "error at first iteration",
count: 5,
callback: func(i int) (int, error) {
if i == 0 {
return 0, testErr
}
return i * i, nil
},
wantResult: nil,
wantErr: true,
expectedCallbackCount: 1,
},
{
name: "error at third iteration",
count: 5,
callback: func(i int) (int, error) {
if i == 2 {
return 0, testErr
}
return i * i, nil
},
wantResult: nil,
wantErr: true,
expectedCallbackCount: 3,
},
{
name: "error at last iteration",
count: 5,
callback: func(i int) (int, error) {
if i == 4 {
return 0, testErr
}
return i * i, nil
},
wantResult: nil,
wantErr: true,
expectedCallbackCount: 5,
},
{
name: "zero count - empty result",
count: 0,
callback: func(i int) (int, error) {
return i * i, nil
},
wantResult: []int{},
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "single item success",
count: 1,
callback: func(i int) (int, error) {
return 42, nil
},
wantResult: []int{42},
wantErr: false,
expectedCallbackCount: 1,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Track callback count to verify early return
callbackCount := 0
wrappedCallback := func(i int) (int, error) {
callbackCount++
return tt.callback(i)
}
result, err := RepeatByErr(tt.count, wrappedCallback)
if tt.wantErr {
is.ErrorIs(err, testErr)
is.Nil(result)
} else {
is.NoError(err)
is.Equal(tt.wantResult, result)
}
// Verify callback count matches expected (tests early return)
is.Equal(tt.expectedCallbackCount, callbackCount, "callback count should match expected")
})
}
}
func TestKeyBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := KeyBy([]string{"a", "aa", "aaa"}, func(str string) int {
return len(str)
})
is.Equal(map[int]string{1: "a", 2: "aa", 3: "aaa"}, result1)
}
func TestKeyByErr(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input []string
iteratee func(item string) (int, error)
wantResult map[int]string
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "empty slice",
input: []string{},
iteratee: func(s string) (int, error) { return len(s), nil },
wantResult: map[int]string{},
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "success case",
input: []string{"a", "aa", "aaa"},
iteratee: func(s string) (int, error) { return len(s), nil },
wantResult: map[int]string{1: "a", 2: "aa", 3: "aaa"},
wantErr: false,
expectedCallbackCount: 3,
},
{
name: "error stops iteration - first item",
input: []string{"a", "aa", "aaa"},
iteratee: func(s string) (int, error) {
return 0, fmt.Errorf("error on %s", s)
},
wantResult: nil,
wantErr: true,
errMsg: "error on a",
expectedCallbackCount: 1,
},
{
name: "error stops iteration - middle item",
input: []string{"a", "aa", "aaa"},
iteratee: func(s string) (int, error) {
if s == "aa" {
return 0, errors.New("middle error")
}
return len(s), nil
},
wantResult: nil,
wantErr: true,
errMsg: "middle error",
expectedCallbackCount: 2,
},
{
name: "error stops iteration - last item",
input: []string{"a", "aa", "aaa"},
iteratee: func(s string) (int, error) {
if s == "aaa" {
return 0, errors.New("last error")
}
return len(s), nil
},
wantResult: nil,
wantErr: true,
errMsg: "last error",
expectedCallbackCount: 3,
},
{
name: "duplicate keys",
input: []string{"a", "b", "c"},
iteratee: func(s string) (int, error) { return 1, nil },
wantResult: map[int]string{1: "c"},
wantErr: false,
expectedCallbackCount: 3,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
wrappedIteratee := func(s string) (int, error) {
callbackCount++
return tt.iteratee(s)
}
result, err := KeyByErr(tt.input, wrappedIteratee)
if tt.wantErr {
assert.Error(t, err)
if tt.errMsg != "" {
assert.Equal(t, tt.errMsg, err.Error())
}
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.wantResult, result)
}
assert.Equal(t, tt.expectedCallbackCount, callbackCount, "callback count mismatch")
})
}
}
func TestAssociate(t *testing.T) {
t.Parallel()
type foo struct {
baz string
bar int
}
transform := func(f *foo) (string, int) {
return f.baz, f.bar
}
testCases := []struct {
in []*foo
want map[string]int
}{
{
in: []*foo{{baz: "apple", bar: 1}},
want: map[string]int{"apple": 1},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}},
want: map[string]int{"apple": 1, "banana": 2},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}},
want: map[string]int{"apple": 2},
},
}
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, Associate(tc.in, transform))
})
}
}
func TestAssociateI(t *testing.T) {
t.Parallel()
transform := func(s string, i int) (int, string) {
return i % 2, s
}
testCases := []struct {
in []string
want map[int]string
}{
{
in: []string{"zero"},
want: map[int]string{0: "zero"},
},
{
in: []string{"zero", "one"},
want: map[int]string{0: "zero", 1: "one"},
},
{
in: []string{"two", "one", "zero"},
want: map[int]string{0: "zero", 1: "one"},
},
}
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, AssociateI(tc.in, transform))
})
}
}
func TestSliceToMap(t *testing.T) {
t.Parallel()
type foo struct {
baz string
bar int
}
transform := func(f *foo) (string, int) {
return f.baz, f.bar
}
testCases := []struct {
in []*foo
want map[string]int
}{
{
in: []*foo{{baz: "apple", bar: 1}},
want: map[string]int{"apple": 1},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}},
want: map[string]int{"apple": 1, "banana": 2},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}},
want: map[string]int{"apple": 2},
},
}
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, SliceToMap(tc.in, transform))
})
}
}
func TestSliceToMapI(t *testing.T) {
t.Parallel()
transform := func(s string, i int) (int, string) {
return i % 2, s
}
testCases := []struct {
in []string
want map[int]string
}{
{
in: []string{"zero"},
want: map[int]string{0: "zero"},
},
{
in: []string{"zero", "one"},
want: map[int]string{0: "zero", 1: "one"},
},
{
in: []string{"two", "one", "zero"},
want: map[int]string{0: "zero", 1: "one"},
},
}
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, SliceToMapI(tc.in, transform))
})
}
}
func TestFilterSliceToMap(t *testing.T) {
t.Parallel()
type foo struct {
baz string
bar int
}
transform := func(f *foo) (string, int, bool) {
return f.baz, f.bar, f.bar > 1
}
testCases := []struct {
in []*foo
want map[string]int
}{
{
in: []*foo{{baz: "apple", bar: 1}},
want: map[string]int{},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}},
want: map[string]int{"banana": 2},
},
{
in: []*foo{{baz: "apple", bar: 1}, {baz: "apple", bar: 2}},
want: map[string]int{"apple": 2},
},
}
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, FilterSliceToMap(tc.in, transform))
})
}
}
func TestFilterSliceToMapI(t *testing.T) {
t.Parallel()
transform := func(s string, i int) (int, string, bool) {
return i % 5, s, i%2 == 0
}
testCases := []struct {
in []string
want map[int]string
}{
{
in: []string{"zero"},
want: map[int]string{0: "zero"},
},
{
in: []string{"zero", "one", "two", "three", "four"},
want: map[int]string{0: "zero", 2: "two", 4: "four"},
},
{
in: []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"},
want: map[int]string{0: "ten", 1: "six", 2: "two", 3: "eight", 4: "four"},
},
}
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, FilterSliceToMapI(tc.in, transform))
})
}
}
func TestKeyify(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := Keyify([]int{1, 2, 3, 4})
result2 := Keyify([]int{1, 1, 1, 2})
result3 := Keyify([]int{})
is.Equal(map[int]struct{}{1: {}, 2: {}, 3: {}, 4: {}}, result1)
is.Equal(map[int]struct{}{1: {}, 2: {}}, result2)
is.Empty(result3)
}
func TestDrop(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2, 3, 4}, Drop([]int{0, 1, 2, 3, 4}, 0))
is.Equal([]int{1, 2, 3, 4}, Drop([]int{0, 1, 2, 3, 4}, 1))
is.Equal([]int{2, 3, 4}, Drop([]int{0, 1, 2, 3, 4}, 2))
is.Equal([]int{3, 4}, Drop([]int{0, 1, 2, 3, 4}, 3))
is.Equal([]int{4}, Drop([]int{0, 1, 2, 3, 4}, 4))
is.Empty(Drop([]int{0, 1, 2, 3, 4}, 5))
is.Empty(Drop([]int{0, 1, 2, 3, 4}, 6))
is.PanicsWithValue("lo.Drop: n must not be negative", func() {
Drop([]int{0, 1, 2, 3, 4}, -1)
})
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Drop(allStrings, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestDropRight(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2, 3, 4}, DropRight([]int{0, 1, 2, 3, 4}, 0))
is.Equal([]int{0, 1, 2, 3}, DropRight([]int{0, 1, 2, 3, 4}, 1))
is.Equal([]int{0, 1, 2}, DropRight([]int{0, 1, 2, 3, 4}, 2))
is.Equal([]int{0, 1}, DropRight([]int{0, 1, 2, 3, 4}, 3))
is.Equal([]int{0}, DropRight([]int{0, 1, 2, 3, 4}, 4))
is.Empty(DropRight([]int{0, 1, 2, 3, 4}, 5))
is.Empty(DropRight([]int{0, 1, 2, 3, 4}, 6))
is.PanicsWithValue("lo.DropRight: n must not be negative", func() {
DropRight([]int{0, 1, 2, 3, 4}, -1)
})
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := DropRight(allStrings, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestDropWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{4, 5, 6}, DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t != 4
}))
is.Empty(DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return true
}))
is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t == 10
}))
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := DropWhile(allStrings, func(t string) bool {
return t != "foo"
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestDropRightWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2, 3}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t != 3
}))
is.Equal([]int{0, 1}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t != 1
}))
is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t == 10
}))
is.Empty(DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t != 10
}))
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := DropRightWhile(allStrings, func(t string) bool {
return t != "foo"
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestTake(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2}, Take([]int{0, 1, 2, 3, 4}, 3))
is.Equal([]int{0, 1}, Take([]int{0, 1, 2, 3, 4}, 2))
is.Equal([]int{0}, Take([]int{0, 1, 2, 3, 4}, 1))
is.Empty(Take([]int{0, 1, 2, 3, 4}, 0))
is.Equal([]int{0, 1, 2, 3, 4}, Take([]int{0, 1, 2, 3, 4}, 5))
is.Equal([]int{0, 1, 2, 3, 4}, Take([]int{0, 1, 2, 3, 4}, 10))
is.PanicsWithValue("lo.Take: n must not be negative", func() {
Take([]int{0, 1, 2, 3, 4}, -1)
})
type myStrings []string
allStrings := myStrings{"foo", "bar", "baz"}
taken := Take(allStrings, 2)
is.IsType(taken, allStrings, "type preserved")
is.Equal(myStrings{"foo", "bar"}, taken)
}
func TestTakeWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{0, 1, 2, 3}, TakeWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t < 4
}))
is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, TakeWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t < 10
}))
is.Empty(TakeWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t < 0
}))
is.Equal([]int{0, 1, 2}, TakeWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool {
return t != 3
}))
type myStrings []string
allStrings := myStrings{"foo", "bar", "baz", "qux"}
taken := TakeWhile(allStrings, func(t string) bool {
return t != "baz"
})
is.IsType(taken, allStrings, "type preserved")
is.Equal(myStrings{"foo", "bar"}, taken)
}
func TestTakeFilter(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal(
[]int{2, 4}, TakeFilter([]int{1, 2, 3, 4, 5, 6}, 2, func(item, index int) bool {
return item%2 == 0
}),
)
is.Equal([]int{2, 4, 6}, TakeFilter([]int{1, 2, 3, 4, 5, 6}, 10, func(item, index int) bool {
return item%2 == 0
}))
is.Empty(TakeFilter([]int{1, 2, 3, 4, 5, 6}, 0, func(item, index int) bool {
return item%2 == 0
}))
is.Empty(TakeFilter([]int{1, 3, 5}, 2, func(item, index int) bool {
return item%2 == 0
}))
is.Equal([]int{1}, TakeFilter([]int{1, 2, 3, 4, 5}, 1, func(item, index int) bool {
return item%2 != 0
}))
is.PanicsWithValue("lo.TakeFilter: n must not be negative", func() {
TakeFilter([]int{1, 2, 3}, -1, func(item, index int) bool { return true })
})
type myStrings []string
allStrings := myStrings{"foo", "bar", "baz", "qux"}
filtered := TakeFilter(allStrings, 2, func(item string, index int) bool {
return len(item) == 3
})
is.IsType(filtered, allStrings, "type preserved")
is.Equal(myStrings{"foo", "bar"}, filtered)
}
func TestDropByIndex(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal([]int{1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 0))
is.Equal([]int{3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 0, 1, 2))
is.Equal([]int{0, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, -4, -2, -3))
is.Equal([]int{0, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, -4, -4))
is.Equal([]int{2, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 3, 1, 0))
is.Equal([]int{0, 1, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 2))
is.Equal([]int{0, 1, 2, 3}, DropByIndex([]int{0, 1, 2, 3, 4}, 4))
is.Equal([]int{0, 1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}))
is.Equal([]int{0, 1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 5))
is.Equal([]int{0, 1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, 100))
is.Equal([]int{0, 1, 2, 3, 4}, DropByIndex([]int{0, 1, 2, 3, 4}, -100))
is.Equal([]int{0, 1, 2, 3}, DropByIndex([]int{0, 1, 2, 3, 4}, -1))
is.Equal([]int{0, 1, 2, 3}, DropByIndex([]int{0, 1, 2, 3, 4}, -1, 4))
is.Equal([]int{0, 1, 2, 3}, DropByIndex([]int{0, 1, 2, 3, 4}, -100, 4))
is.Empty(DropByIndex([]int{}, 0, 1))
is.Empty(DropByIndex([]int{42}, 0, 1))
is.Empty(DropByIndex([]int{42}, 1, 0))
is.Empty(DropByIndex([]int{}, 1))
is.Empty(DropByIndex([]int{1}, 0))
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := DropByIndex(allStrings, 0)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestReject(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Reject([]int{1, 2, 3, 4}, func(x, _ int) bool {
return x%2 == 0
})
is.Equal([]int{1, 3}, r1)
r2 := Reject([]string{"Smith", "foo", "Domin", "bar", "Olivia"}, func(x string, _ int) bool {
return len(x) > 3
})
is.Equal([]string{"foo", "bar"}, r2)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Reject(allStrings, func(x string, _ int) bool {
return len(x) > 0
})
is.IsType(nonempty, allStrings, "type preserved")
}
func TestRejectErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
predicate func(item, index int) (bool, error)
want []int
wantErr string
callbacks int // Number of predicates called before error/finish
}{
{
name: "reject even numbers",
input: []int{1, 2, 3, 4},
predicate: func(x, _ int) (bool, error) {
return x%2 == 0, nil
},
want: []int{1, 3},
callbacks: 4,
},
{
name: "empty slice",
input: []int{},
predicate: func(x, _ int) (bool, error) {
return true, nil
},
want: []int{},
callbacks: 0,
},
{
name: "reject all out",
input: []int{1, 2, 3, 4},
predicate: func(x, _ int) (bool, error) {
return false, nil
},
want: []int{1, 2, 3, 4},
callbacks: 4,
},
{
name: "reject all in",
input: []int{1, 2, 3, 4},
predicate: func(x, _ int) (bool, error) {
return true, nil
},
want: []int{},
callbacks: 4,
},
{
name: "error on specific index",
input: []int{1, 2, 3, 4},
predicate: func(x, _ int) (bool, error) {
if x == 3 {
return false, errors.New("number 3 is not allowed")
}
return x%2 == 0, nil
},
callbacks: 3,
wantErr: "number 3 is not allowed",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var callbacks int
wrappedPredicate := func(item, index int) (bool, error) {
callbacks++
return tt.predicate(item, index)
}
got, err := RejectErr(tt.input, wrappedPredicate)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
is.Equal(tt.callbacks, callbacks, "callback count should match expected early return")
} else {
is.NoError(err)
is.Equal(tt.want, got)
is.Equal(tt.callbacks, callbacks)
}
})
}
// Test type preservation
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty, err := RejectErr(allStrings, func(x string, _ int) (bool, error) {
return len(x) > 0, nil
})
is.NoError(err)
is.IsType(nonempty, allStrings, "type preserved")
is.Equal(myStrings{""}, nonempty)
}
func TestRejectMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := RejectMap([]int64{1, 2, 3, 4}, func(x int64, _ int) (string, bool) {
if x%2 == 0 {
return strconv.FormatInt(x, 10), false
}
return "", true
})
r2 := RejectMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", false
}
return "", true
})
is.Equal([]string{"2", "4"}, r1)
is.Equal([]string{"xpu", "xpu"}, r2)
}
func TestFilterReject(t *testing.T) {
t.Parallel()
is := assert.New(t)
left1, right1 := FilterReject([]int{1, 2, 3, 4}, func(x, _ int) bool {
return x%2 == 0
})
is.Equal([]int{2, 4}, left1)
is.Equal([]int{1, 3}, right1)
left2, right2 := FilterReject([]string{"Smith", "foo", "Domin", "bar", "Olivia"}, func(x string, _ int) bool {
return len(x) > 3
})
is.Equal([]string{"Smith", "Domin", "Olivia"}, left2)
is.Equal([]string{"foo", "bar"}, right2)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
a, b := FilterReject(allStrings, func(x string, _ int) bool {
return len(x) > 0
})
is.IsType(a, allStrings, "type preserved")
is.IsType(b, allStrings, "type preserved")
}
func TestCount(t *testing.T) {
t.Parallel()
is := assert.New(t)
count1 := Count([]int{1, 2, 1}, 1)
count2 := Count([]int{1, 2, 1}, 3)
count3 := Count([]int{}, 1)
is.Equal(2, count1)
is.Zero(count2)
is.Zero(count3)
}
func TestCountBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
count1 := CountBy([]int{1, 2, 1}, func(i int) bool {
return i < 2
})
count2 := CountBy([]int{1, 2, 1}, func(i int) bool {
return i > 2
})
count3 := CountBy([]int{}, func(i int) bool {
return i <= 2
})
is.Equal(2, count1)
is.Zero(count2)
is.Zero(count3)
}
func TestCountByErr(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input []int
predicate func(int) (bool, error)
want int
wantErr string
wantCallCount int
}{
{
name: "count elements less than 2",
input: []int{1, 2, 1},
predicate: func(i int) (bool, error) {
return i < 2, nil
},
want: 2,
wantErr: "",
wantCallCount: 3,
},
{
name: "count elements greater than 2",
input: []int{1, 2, 1},
predicate: func(i int) (bool, error) {
return i > 2, nil
},
want: 0,
wantErr: "",
wantCallCount: 3,
},
{
name: "empty slice",
input: []int{},
predicate: func(i int) (bool, error) {
return i <= 2, nil
},
want: 0,
wantErr: "",
wantCallCount: 0,
},
{
name: "error on third element",
input: []int{1, 2, 3, 4, 5},
predicate: func(i int) (bool, error) {
if i == 3 {
return false, fmt.Errorf("error at %d", i)
}
return i < 3, nil
},
want: 0,
wantErr: "error at 3",
wantCallCount: 3, // stops early at error
},
{
name: "error on first element",
input: []int{1, 2, 3},
predicate: func(i int) (bool, error) {
return false, errors.New("first element error")
},
want: 0,
wantErr: "first element error",
wantCallCount: 1,
},
{
name: "all match",
input: []int{1, 2, 3},
predicate: func(i int) (bool, error) {
return i > 0, nil
},
want: 3,
wantErr: "",
wantCallCount: 3,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callCount := 0
wrappedPredicate := func(i int) (bool, error) {
callCount++
return tt.predicate(i)
}
got, err := CountByErr(tt.input, wrappedPredicate)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Equal(tt.want, got)
if tt.wantCallCount > 0 {
is.Equal(tt.wantCallCount, callCount, "should stop early on error")
}
} else {
is.NoError(err)
is.Equal(tt.want, got)
is.Equal(tt.wantCallCount, callCount)
}
})
}
}
func TestCountValues(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Empty(CountValues([]int{}))
is.Equal(map[int]int{1: 1, 2: 1}, CountValues([]int{1, 2}))
is.Equal(map[int]int{1: 1, 2: 2}, CountValues([]int{1, 2, 2}))
is.Equal(map[string]int{"": 1, "foo": 1, "bar": 1}, CountValues([]string{"foo", "bar", ""}))
is.Equal(map[string]int{"foo": 1, "bar": 2}, CountValues([]string{"foo", "bar", "bar"}))
}
func TestCountValuesBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
oddEven := func(v int) bool {
return v%2 == 0
}
length := func(v string) int {
return len(v)
}
result1 := CountValuesBy([]int{}, oddEven)
result2 := CountValuesBy([]int{1, 2}, oddEven)
result3 := CountValuesBy([]int{1, 2, 2}, oddEven)
result4 := CountValuesBy([]string{"foo", "bar", ""}, length)
result5 := CountValuesBy([]string{"foo", "bar", "bar"}, length)
is.Empty(result1)
is.Equal(map[bool]int{true: 1, false: 1}, result2)
is.Equal(map[bool]int{true: 2, false: 1}, result3)
is.Equal(map[int]int{0: 1, 3: 2}, result4)
is.Equal(map[int]int{3: 3}, result5)
}
func TestSubset(t *testing.T) {
t.Parallel()
is := assert.New(t)
in := []int{0, 1, 2, 3, 4}
out1 := Subset(in, 0, 0)
out2 := Subset(in, 10, 2)
out3 := Subset(in, -10, 2)
out4 := Subset(in, 0, 10)
out5 := Subset(in, 0, 2)
out6 := Subset(in, 2, 2)
out7 := Subset(in, 2, 5)
out8 := Subset(in, 2, 3)
out9 := Subset(in, 2, 4)
out10 := Subset(in, -2, 4)
out11 := Subset(in, -4, 1)
out12 := Subset(in, -4, math.MaxUint)
is.Empty(out1)
is.Empty(out2)
is.Equal([]int{0, 1}, out3)
is.Equal([]int{0, 1, 2, 3, 4}, out4)
is.Equal([]int{0, 1}, out5)
is.Equal([]int{2, 3}, out6)
is.Equal([]int{2, 3, 4}, out7)
is.Equal([]int{2, 3, 4}, out8)
is.Equal([]int{2, 3, 4}, out9)
is.Equal([]int{3, 4}, out10)
is.Equal([]int{1}, out11)
is.Equal([]int{1, 2, 3, 4}, out12)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Subset(allStrings, 0, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestSlice(t *testing.T) {
t.Parallel()
is := assert.New(t)
in := []int{0, 1, 2, 3, 4}
out1 := Slice(in, 0, 0)
out2 := Slice(in, 0, 1)
out3 := Slice(in, 0, 5)
out4 := Slice(in, 0, 6)
out5 := Slice(in, 1, 1)
out6 := Slice(in, 1, 5)
out7 := Slice(in, 1, 6)
out8 := Slice(in, 4, 5)
out9 := Slice(in, 5, 5)
out10 := Slice(in, 6, 5)
out11 := Slice(in, 6, 6)
out12 := Slice(in, 1, 0)
out13 := Slice(in, 5, 0)
out14 := Slice(in, 6, 4)
out15 := Slice(in, 6, 7)
out16 := Slice(in, -10, 1)
out17 := Slice(in, -1, 3)
out18 := Slice(in, -10, 7)
out19 := Slice(in, -10, -1)
is.Empty(out1)
is.Equal([]int{0}, out2)
is.Equal([]int{0, 1, 2, 3, 4}, out3)
is.Equal([]int{0, 1, 2, 3, 4}, out4)
is.Empty(out5)
is.Equal([]int{1, 2, 3, 4}, out6)
is.Equal([]int{1, 2, 3, 4}, out7)
is.Equal([]int{4}, out8)
is.Empty(out9)
is.Empty(out10)
is.Empty(out11)
is.Empty(out12)
is.Empty(out13)
is.Empty(out14)
is.Empty(out15)
is.Equal([]int{0}, out16)
is.Equal([]int{0, 1, 2}, out17)
is.Equal([]int{0, 1, 2, 3, 4}, out18)
is.Empty(out19)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Slice(allStrings, 0, 2)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestReplace(t *testing.T) {
t.Parallel()
is := assert.New(t)
in := []int{0, 1, 0, 1, 2, 3, 0}
out1 := Replace(in, 0, 42, 2)
out2 := Replace(in, 0, 42, 1)
out3 := Replace(in, 0, 42, 0)
out4 := Replace(in, 0, 42, -1)
out5 := Replace(in, 0, 42, -1)
out6 := Replace(in, -1, 42, 2)
out7 := Replace(in, -1, 42, 1)
out8 := Replace(in, -1, 42, 0)
out9 := Replace(in, -1, 42, -1)
out10 := Replace(in, -1, 42, -1)
is.Equal([]int{42, 1, 42, 1, 2, 3, 0}, out1)
is.Equal([]int{42, 1, 0, 1, 2, 3, 0}, out2)
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out3)
is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out4)
is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out5)
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out6)
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out7)
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out8)
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out9)
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out10)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Replace(allStrings, "0", "2", 1)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestReplaceAll(t *testing.T) {
t.Parallel()
is := assert.New(t)
in := []int{0, 1, 0, 1, 2, 3, 0}
out1 := ReplaceAll(in, 0, 42)
out2 := ReplaceAll(in, -1, 42)
is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out1)
is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out2)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := ReplaceAll(allStrings, "0", "2")
is.IsType(nonempty, allStrings, "type preserved")
}
func TestClone(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Test with int slice
original1 := []int{1, 2, 3, 4, 5}
result1 := Clone(original1)
is.Equal([]int{1, 2, 3, 4, 5}, result1)
// Verify it's a different slice by checking that modifying one doesn't affect the other
original1[0] = 99
is.Equal([]int{99, 2, 3, 4, 5}, original1)
is.Equal([]int{1, 2, 3, 4, 5}, result1)
// Test with string slice
original2 := []string{"a", "b", "c"}
result2 := Clone(original2)
is.Equal([]string{"a", "b", "c"}, result2)
// Test with empty slice
original3 := []int{}
result3 := Clone(original3)
is.Equal([]int{}, result3)
is.Empty(result3)
// Test with nil slice
var original4 []int
result4 := Clone(original4)
is.Nil(result4)
// Verify shallow copy behavior - modifying clone doesn't affect original
original5 := []int{1, 2, 3}
result5 := Clone(original5)
result5[0] = 99
is.Equal([]int{1, 2, 3}, original5) // Original unchanged
is.Equal([]int{99, 2, 3}, result5) // Clone changed
type myStrings []string
original6 := myStrings{"", "foo", "bar"}
result6 := Clone(original6)
result6[0] = "baz"
is.Equal(myStrings{"", "foo", "bar"}, original6) // Original unchanged
is.Equal(myStrings{"baz", "foo", "bar"}, result6) // Clone changed
}
func TestCompact(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Compact([]int{2, 0, 4, 0})
is.Equal([]int{2, 4}, r1)
r2 := Compact([]string{"", "foo", "", "bar", ""})
is.Equal([]string{"foo", "bar"}, r2)
r3 := Compact([]bool{true, false, true, false})
is.Equal([]bool{true, true}, r3)
type foo struct {
bar int
baz string
}
// slice of structs
// If all fields of an element are zero values, Compact removes it.
r4 := Compact([]foo{
{bar: 1, baz: "a"}, // all fields are non-zero values
{bar: 0, baz: ""}, // all fields are zero values
{bar: 2, baz: ""}, // bar is non-zero
})
is.Equal([]foo{{bar: 1, baz: "a"}, {bar: 2, baz: ""}}, r4)
// slice of pointers to structs
// If an element is nil, Compact removes it.
e1, e2, e3 := foo{bar: 1, baz: "a"}, foo{bar: 0, baz: ""}, foo{bar: 2, baz: ""}
// NOTE: e2 is a zero value of foo, but its pointer &e2 is not a zero value of *foo.
r5 := Compact([]*foo{&e1, &e2, nil, &e3})
is.Equal([]*foo{&e1, &e2, &e3}, r5)
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Compact(allStrings)
is.IsType(nonempty, allStrings, "type preserved")
}
func TestIsSorted(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(IsSorted([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}))
is.True(IsSorted([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}))
is.False(IsSorted([]int{0, 1, 4, 3, 2, 5, 6, 7, 8, 9, 10}))
is.False(IsSorted([]string{"a", "b", "d", "c", "e", "f", "g", "h", "i", "j"}))
}
func TestIsSortedBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.True(IsSortedBy([]string{"a", "bb", "ccc"}, func(s string) int {
return len(s)
}))
is.False(IsSortedBy([]string{"aa", "b", "ccc"}, func(s string) int {
return len(s)
}))
is.True(IsSortedBy([]string{"1", "2", "3", "11"}, func(s string) int {
ret, _ := strconv.Atoi(s)
return ret
}))
}
func TestSplice(t *testing.T) {
t.Parallel()
is := assert.New(t)
sample := []string{"a", "b", "c", "d", "e", "f", "g"}
// normal case
results := Splice(sample, 1, "1", "2")
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample)
is.Equal([]string{"a", "1", "2", "b", "c", "d", "e", "f", "g"}, results)
// check there is no side effect
results = Splice(sample, 1)
results[0] = "b"
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample)
// positive overflow
results = Splice(sample, 42, "1", "2")
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample)
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g", "1", "2"}, results)
// negative overflow
results = Splice(sample, -42, "1", "2")
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample)
is.Equal([]string{"1", "2", "a", "b", "c", "d", "e", "f", "g"}, results)
// backward
results = Splice(sample, -2, "1", "2")
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample)
is.Equal([]string{"a", "b", "c", "d", "e", "1", "2", "f", "g"}, results)
results = Splice(sample, -7, "1", "2")
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, sample)
is.Equal([]string{"1", "2", "a", "b", "c", "d", "e", "f", "g"}, results)
// other
is.Equal([]string{"1", "2"}, Splice([]string{}, 0, "1", "2"))
is.Equal([]string{"1", "2"}, Splice([]string{}, 1, "1", "2"))
is.Equal([]string{"1", "2"}, Splice([]string{}, -1, "1", "2"))
is.Equal([]string{"1", "2", "0"}, Splice([]string{"0"}, 0, "1", "2"))
is.Equal([]string{"0", "1", "2"}, Splice([]string{"0"}, 1, "1", "2"))
is.Equal([]string{"1", "2", "0"}, Splice([]string{"0"}, -1, "1", "2"))
// type preserved
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty := Splice(allStrings, 1, "1", "2")
is.IsType(nonempty, allStrings, "type preserved")
}
func TestCutSuccess(t *testing.T) {
t.Parallel()
is := assert.New(t)
// case 1
actualLeft, actualRight, result := Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"})
is.True(result)
is.Equal([]string{}, actualLeft)
is.Equal([]string{"c", "d", "e", "f", "g"}, actualRight)
// case 2
actualLeft, actualRight, result = Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"f", "g"})
is.True(result)
is.Equal([]string{"a", "b", "c", "d", "e"}, actualLeft)
is.Equal([]string{}, actualRight)
// case 3
actualLeft, actualRight, result = Cut([]string{"g"}, []string{"g"})
is.True(result)
is.Equal([]string{}, actualLeft)
is.Equal([]string{}, actualRight)
// case 4
actualLeft, actualRight, result = Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b", "c"})
is.True(result)
is.Equal([]string{"a"}, actualLeft)
is.Equal([]string{"d", "e", "f", "g"}, actualRight)
// case 5
actualLeft, actualRight, result = Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"e", "f"})
is.True(result)
is.Equal([]string{"a", "b", "c", "d"}, actualLeft)
is.Equal([]string{"g"}, actualRight)
// case 6
actualLeft, actualRight, result = Cut([]string{"a", "b"}, []string{"b"})
is.True(result)
is.Equal([]string{"a"}, actualLeft)
is.Equal([]string{}, actualRight)
// case 7
actualLeft, actualRight, result = Cut([]string{"a", "b"}, []string{"a"})
is.True(result)
is.Equal([]string{}, actualLeft)
is.Equal([]string{"b"}, actualRight)
}
func TestCutFail(t *testing.T) {
t.Parallel()
is := assert.New(t)
// case 1
actualLeft, actualRight, result := Cut([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"z"})
is.False(result)
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actualLeft)
is.Equal([]string{}, actualRight)
// case 2
actualLeft, actualRight, result = Cut([]string{}, []string{"z"})
is.False(result)
is.Equal([]string{}, actualLeft)
is.Equal([]string{}, actualRight)
// case 3
actualLeft, actualRight, result = Cut([]string{"a"}, []string{"z"})
is.False(result)
is.Equal([]string{"a"}, actualLeft)
is.Equal([]string{}, actualRight)
}
type TestCutStruct struct {
id int
data string
}
func TestCutPrefix(t *testing.T) {
t.Parallel()
is := assert.New(t)
// case 1
actualAfter, result := CutPrefix(
[]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}},
[]TestCutStruct{{id: 1, data: "a"}},
)
is.True(result)
is.Equal([]TestCutStruct{{id: 2, data: "a"}, {id: 2, data: "b"}}, actualAfter)
// case 2
actualAfter, result = CutPrefix(
[]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}},
[]TestCutStruct{},
)
is.True(result)
is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualAfter)
// case 3
actualAfter, result = CutPrefix(
[]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}},
[]TestCutStruct{{id: 2, data: "b"}},
)
is.False(result)
is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualAfter)
// case 4
actualAfter, result = CutPrefix(
[]TestCutStruct{},
[]TestCutStruct{{id: 2, data: "b"}},
)
is.False(result)
is.Equal([]TestCutStruct{}, actualAfter)
// case 5
actualAfterS, result := CutPrefix([]string{"a", "a", "b"}, []string{})
is.True(result)
is.Equal([]string{"a", "a", "b"}, actualAfterS)
}
func TestCutSuffix(t *testing.T) {
t.Parallel()
is := assert.New(t)
// case 1
actualBefore, result := CutSuffix(
[]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}},
[]TestCutStruct{{id: 3, data: "b"}},
)
is.False(result)
is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualBefore)
// case 2
actualBefore, result = CutSuffix(
[]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}},
[]TestCutStruct{{id: 2, data: "b"}},
)
is.True(result)
is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}}, actualBefore)
// case 3
actualBefore, result = CutSuffix(
[]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}},
[]TestCutStruct{},
)
is.True(result)
is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualBefore)
// case 4
actualBefore, result = CutSuffix(
[]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}},
[]TestCutStruct{{id: 2, data: "a"}},
)
is.False(result)
is.Equal([]TestCutStruct{{id: 1, data: "a"}, {id: 2, data: "a"}, {id: 2, data: "b"}}, actualBefore)
// case 5
actualAfterS, result := CutSuffix([]string{"a", "a", "b"}, []string{})
is.True(result)
is.Equal([]string{"a", "a", "b"}, actualAfterS)
}
func TestTrim(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"})
is.Equal([]string{"c", "d", "e", "f", "g"}, actual)
actual = Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"g", "f"})
is.Equal([]string{"a", "b", "c", "d", "e"}, actual)
actual = Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"})
is.Equal([]string{}, actual)
actual = Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"})
is.Equal([]string{}, actual)
actual = Trim([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
}
func TestTrimLeft(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := TrimLeft([]string{"a", "a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"})
is.Equal([]string{"c", "d", "e", "f", "g"}, actual)
actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b", "a"})
is.Equal([]string{"c", "d", "e", "f", "g"}, actual)
actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"g", "f"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"})
is.Equal([]string{}, actual)
actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"})
is.Equal([]string{}, actual)
actual = TrimLeft([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
}
func TestTrimPrefix(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := TrimPrefix([]string{"a", "b", "a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"})
is.Equal([]string{"c", "d", "e", "f", "g"}, actual)
actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"b", "a"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"g", "f"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"})
is.Equal([]string{}, actual)
actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
actual = TrimPrefix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
}
func TestTrimRight(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := TrimRight([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
actual = TrimRight([]string{"a", "b", "c", "d", "e", "f", "g", "g"}, []string{"g", "f"})
is.Equal([]string{"a", "b", "c", "d", "e"}, actual)
actual = TrimRight([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"})
is.Equal([]string{}, actual)
actual = TrimRight([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"})
is.Equal([]string{}, actual)
actual = TrimRight([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
}
func TestTrimSuffix(t *testing.T) {
t.Parallel()
is := assert.New(t)
actual := TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g", "f", "g"}, []string{"f", "g"})
is.Equal([]string{"a", "b", "c", "d", "e"}, actual)
actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g", "f", "g"}, []string{"g", "f"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g", "f", "g"}, actual)
actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g"})
is.Equal([]string{}, actual)
actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{"a", "b", "c", "d", "e", "f", "g", "h"})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
actual = TrimSuffix([]string{"a", "b", "c", "d", "e", "f", "g"}, []string{})
is.Equal([]string{"a", "b", "c", "d", "e", "f", "g"}, actual)
}
================================================
FILE: string.go
================================================
package lo
import (
"math"
"regexp"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/samber/lo/internal/xrand"
)
var (
//nolint:revive
LowerCaseLettersCharset = []rune("abcdefghijklmnopqrstuvwxyz")
UpperCaseLettersCharset = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
LettersCharset = append(LowerCaseLettersCharset, UpperCaseLettersCharset...)
NumbersCharset = []rune("0123456789")
AlphanumericCharset = append(LettersCharset, NumbersCharset...)
SpecialCharset = []rune("!@#$%^&*()_+-=[]{}|;':\",./<>?")
AllCharset = append(AlphanumericCharset, SpecialCharset...)
// bearer:disable go_lang_permissive_regex_validation
splitWordReg = regexp.MustCompile(`([a-z])([A-Z0-9])|([a-zA-Z])([0-9])|([0-9])([a-zA-Z])|([A-Z])([A-Z])([a-z])`)
// bearer:disable go_lang_permissive_regex_validation
splitNumberLetterReg = regexp.MustCompile(`([0-9])([a-zA-Z])`)
maximumCapacity = math.MaxInt>>1 + 1
)
// RandomString return a random string.
// Play: https://go.dev/play/p/rRseOQVVum4
func RandomString(size int, charset []rune) string {
if size <= 0 {
panic("lo.RandomString: size must be greater than 0")
}
if len(charset) == 0 {
panic("lo.RandomString: charset must not be empty")
}
// see https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-go
var sb strings.Builder
sb.Grow(size)
if len(charset) == 1 {
// Edge case, because if the charset is a single character,
// it will panic below (divide by zero).
// -> https://github.com/samber/lo/issues/679
for i := 0; i < size; i++ {
sb.WriteRune(charset[0])
}
return sb.String()
}
// Calculate the number of bits required to represent the charset,
// e.g., for 62 characters, it would need 6 bits (since 62 -> 64 = 2^6)
letterIDBits := int(math.Log2(float64(nearestPowerOfTwo(len(charset)))))
// Determine the corresponding bitmask,
// e.g., for 62 characters, the bitmask would be 111111.
var letterIDMask int64 = 1<= 0; {
// Regenerate the random number if all available bits have been used
if remain == 0 {
cache, remain = xrand.Int64(), letterIDMax
}
// Select a character from the charset
if idx := int(cache & letterIDMask); idx < len(charset) {
sb.WriteRune(charset[idx])
i--
}
// Shift the bits to the right to prepare for the next character selection,
// e.g., for 62 characters, shift by 6 bits.
cache >>= letterIDBits
// Decrease the remaining number of uses for the current random number.
remain--
}
return sb.String()
}
// nearestPowerOfTwo returns the nearest power of two.
func nearestPowerOfTwo(capacity int) int {
n := capacity - 1
n |= n >> 1
n |= n >> 2
n |= n >> 4
n |= n >> 8
n |= n >> 16
if n < 0 {
return 1
}
if n >= maximumCapacity {
return maximumCapacity
}
return n + 1
}
// Substring extracts a substring from a string with Unicode character (rune) awareness.
// offset - starting position of the substring (can be positive, negative, or zero)
// length - number of characters to extract
// With positive offset, counting starts from the beginning of the string
// With negative offset, counting starts from the end of the string
// Play: https://go.dev/play/p/emzCC9zBjHu
func Substring[T ~string](str T, offset int, length uint) T {
str = substring(str, offset, length)
// Validate UTF-8 and fix invalid sequences
if !utf8.ValidString(string(str)) {
// Convert to []rune to replicate behavior with duplicated �
str = T([]rune(str))
}
// Remove null bytes from result
return T(strings.ReplaceAll(string(str), "\x00", ""))
}
func substring[T ~string](str T, offset int, length uint) T {
switch {
// Empty length or offset beyond string bounds - return empty string
case length == 0, offset >= len(str):
return ""
// Positive offset - count from the beginning
case offset > 0:
// Skip offset runes from the start
for i, r := range str {
if offset--; offset == 0 {
str = str[i+utf8.RuneLen(r):]
break
}
}
// If couldn't skip enough runes - string is shorter than offset
if offset != 0 {
return ""
}
// If remaining string is shorter than or equal to length - return it entirely
if uint(len(str)) <= length {
return str
}
// Otherwise proceed to trimming by length
fallthrough
// Zero offset or offset less than minus string length - start from beginning
case offset < -len(str), offset == 0:
// Count length runes from the start
for i := range str {
if length == 0 {
return str[:i]
}
length--
}
return str
// Negative offset - count from the end of string
default: // -len(str) < offset < 0
// Helper function to move backward through runes
backwardPos := func(end int, count uint) (start int) {
for {
_, i := utf8.DecodeLastRuneInString(string(str[:end]))
end -= i
if count--; count == 0 || end == 0 {
return end
}
}
}
offset := uint(-offset)
// If offset is less than or equal to length - take from position to end
if offset <= length {
start := backwardPos(len(str), offset)
return str[start:]
}
// Otherwise calculate start and end positions
end := backwardPos(len(str), offset-length)
start := backwardPos(end, length)
return str[start:end]
}
}
// ChunkString returns a slice of strings split into groups of length size. If the string can't be split evenly,
// the final chunk will be the remaining characters.
// Play: https://go.dev/play/p/__FLTuJVz54
//
// Note: lo.ChunkString and lo.Chunk functions behave inconsistently for empty input: lo.ChunkString("", n) returns [""] instead of [].
// See https://github.com/samber/lo/issues/788
func ChunkString[T ~string](str T, size int) []T {
if size <= 0 {
panic("lo.ChunkString: size must be greater than 0")
}
if size >= len(str) {
return []T{str}
}
chunks := make([]T, 0, ((len(str)-1)/size)+1)
currentLen := 0
currentStart := 0
for i := range str {
if currentLen == size {
chunks = append(chunks, str[currentStart:i])
currentLen = 0
currentStart = i
}
currentLen++
}
chunks = append(chunks, str[currentStart:])
return chunks
}
// RuneLength is an alias to utf8.RuneCountInString which returns the number of runes in string.
// Play: https://go.dev/play/p/BXT52mBk0zO
func RuneLength(str string) int {
return utf8.RuneCountInString(str)
}
// PascalCase converts string to pascal case.
// Play: https://go.dev/play/p/uxER7XpRHLB
func PascalCase(str string) string {
items := Words(str)
for i := range items {
items[i] = Capitalize(items[i])
}
return strings.Join(items, "")
}
// CamelCase converts string to camel case.
// Play: https://go.dev/play/p/4JNDzaMwXkm
func CamelCase(str string) string {
items := Words(str)
for i, item := range items {
item = strings.ToLower(item)
if i > 0 {
item = Capitalize(item)
}
items[i] = item
}
return strings.Join(items, "")
}
// KebabCase converts string to kebab case.
// Play: https://go.dev/play/p/ZBeMB4-pq45
func KebabCase(str string) string {
items := Words(str)
for i := range items {
items[i] = strings.ToLower(items[i])
}
return strings.Join(items, "-")
}
// SnakeCase converts string to snake case.
// Play: https://go.dev/play/p/ziB0V89IeVH
func SnakeCase(str string) string {
items := Words(str)
for i := range items {
items[i] = strings.ToLower(items[i])
}
return strings.Join(items, "_")
}
// Words splits string into a slice of its words.
// Play: https://go.dev/play/p/-f3VIQqiaVw
func Words(str string) []string {
str = splitWordReg.ReplaceAllString(str, `$1$3$5$7 $2$4$6$8$9`)
// example: Int8Value => Int 8Value => Int 8 Value
str = splitNumberLetterReg.ReplaceAllString(str, "$1 $2")
var result strings.Builder
result.Grow(len(str))
for _, r := range str {
if unicode.IsLetter(r) || unicode.IsDigit(r) {
result.WriteRune(r)
} else {
result.WriteRune(' ')
}
}
return strings.Fields(result.String())
}
// Capitalize converts the first character of string to upper case and the remaining to lower case.
// Play: https://go.dev/play/p/uLTZZQXqnsa
func Capitalize(str string) string {
return cases.Title(language.English).String(str)
}
// Ellipsis trims and truncates a string to a specified length in runes and appends an ellipsis
// if truncated. The length parameter counts Unicode code points (runes), not bytes, so multi-byte
// characters such as emoji or CJK ideographs are never split in the middle.
// Play: https://go.dev/play/p/qE93rgqe1TW
func Ellipsis(str string, length int) string {
str = strings.TrimSpace(str)
const ellipsis = "..."
cutPosition := 0
for i := range str {
if length == len(ellipsis) {
cutPosition = i
}
if length--; length < 0 {
return strings.TrimSpace(str[:cutPosition]) + ellipsis
}
}
return str
}
================================================
FILE: string_test.go
================================================
package lo
import (
"fmt"
"math"
"strings"
"testing"
"unicode/utf8"
"github.com/stretchr/testify/assert"
)
func TestRandomString(t *testing.T) {
t.Parallel()
is := assert.New(t)
str1 := RandomString(100, LowerCaseLettersCharset)
is.Equal(100, RuneLength(str1))
is.Subset(LowerCaseLettersCharset, []rune(str1))
str2 := RandomString(100, LowerCaseLettersCharset)
is.NotEqual(str1, str2)
noneUtf8Charset := []rune("明1好休2林森")
str3 := RandomString(100, noneUtf8Charset)
is.Equal(100, RuneLength(str3))
is.Subset(noneUtf8Charset, []rune(str3))
is.PanicsWithValue("lo.RandomString: charset must not be empty", func() { RandomString(100, []rune{}) })
is.PanicsWithValue("lo.RandomString: size must be greater than 0", func() { RandomString(0, LowerCaseLettersCharset) })
str4 := RandomString(10, []rune{65})
is.Equal(10, RuneLength(str4))
is.Subset([]rune{65, 65, 65, 65, 65, 65, 65, 65, 65, 65}, []rune(str4))
}
func TestChunkString(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ChunkString("12345", 2)
is.Equal([]string{"12", "34", "5"}, result1)
result2 := ChunkString("123456", 2)
is.Equal([]string{"12", "34", "56"}, result2)
result3 := ChunkString("123456", 6)
is.Equal([]string{"123456"}, result3)
result4 := ChunkString("123456", 10)
is.Equal([]string{"123456"}, result4)
result5 := ChunkString("", 2)
is.Equal([]string{""}, result5) // @TODO: should be [] - see https://github.com/samber/lo/issues/788
result6 := ChunkString("明1好休2林森", 2)
is.Equal([]string{"明1", "好休", "2林", "森"}, result6)
is.PanicsWithValue("lo.ChunkString: size must be greater than 0", func() {
ChunkString("12345", 0)
})
}
func TestSubstring(t *testing.T) {
t.Parallel()
is := assert.New(t)
str0 := Substring("hello", 5, 10)
str1 := Substring("hello", 0, 0)
str2 := Substring("hello", 10, 2)
str3 := Substring("hello", -10, 2)
str4 := Substring("hello", 0, 10)
str5 := Substring("hello", 0, 2)
str6 := Substring("hello", 2, 2)
str7 := Substring("hello", 2, 5)
str8 := Substring("hello", 2, 3)
str9 := Substring("hello", 2, 4)
str10 := Substring("hello", -2, 4)
str11 := Substring("hello", -4, 1)
str12 := Substring("hello", -4, math.MaxUint)
str13 := Substring("🏠🐶🐱", 0, 2)
str14 := Substring("你好,世界", 0, 3)
str15 := Substring("🏠🐶🐱", 1, 2)
str16 := Substring("🏠🐶🐱", -2, 2)
str17 := Substring("🏠🐶🐱", 3, 3)
str18 := Substring("🏠🐶🐱", 4, 3)
str19 := Substring("hello", 5, 1)
str20 := Substring("hello", -5, 5)
str21 := Substring("hello", -5, 4)
str22 := Substring("hello", -5, math.MaxUint)
str23 := Substring("\x00\x00\x00", 0, math.MaxUint)
str24 := Substring(string(utf8.RuneError), 0, math.MaxUint)
str25 := Substring("привет"[1:], 0, 6)
str26 := Substring("привет"[:2*5+1], 0, 6)
str27 := Substring("привет"[:2*5+1], -2, math.MaxUint)
str28 := Substring("🏠🐶🐱"[1:], 0, math.MaxUint)
str29 := Substring("🏠🐶🐱"[1:], 0, 2)
str30 := Substring("привет", 6, math.MaxUint)
str31 := Substring("привет", 6+1, math.MaxUint)
is.Empty(str0)
is.Empty(str1)
is.Empty(str2)
is.Equal("he", str3)
is.Equal("hello", str4)
is.Equal("he", str5)
is.Equal("ll", str6)
is.Equal("llo", str7)
is.Equal("llo", str8)
is.Equal("llo", str9)
is.Equal("lo", str10)
is.Equal("e", str11)
is.Equal("ello", str12)
is.Equal("🏠🐶", str13)
is.Equal("你好,", str14)
is.Equal("🐶🐱", str15)
is.Equal("🐶🐱", str16)
is.Empty(str17)
is.Empty(str18)
is.Empty(str19)
is.Equal("hello", str20)
is.Equal("hell", str21)
is.Equal("hello", str22)
is.Empty(str23)
is.Equal("�", str24)
is.Equal("�ривет", str25)
is.Equal("приве�", str26)
is.Equal("е�", str27)
is.Equal("���🐶🐱", str28)
is.Equal("��", str29)
is.Empty(str30)
is.Empty(str31)
}
func BenchmarkSubstring(b *testing.B) {
str := strings.Repeat("1", 100)
for _, test := range []struct {
offset int
length uint
}{
{10, 10},
{50, 50},
{50, 45},
{-50, 50},
{-10, 10},
} {
fmt.Println(test)
b.Run(fmt.Sprint(test), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Substring(str, test.offset, test.length)
}
})
}
}
func TestRuneLength(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal(5, RuneLength("hellô"))
is.Len("hellô", 6)
}
func TestAllCase(t *testing.T) {
t.Parallel()
type output struct {
PascalCase string
CamelCase string
KebabCase string
SnakeCase string
}
testCases := []struct {
name string
in string
want output
}{
{want: output{}},
{in: ".", want: output{}},
{in: "Hello world!", want: output{
PascalCase: "HelloWorld",
CamelCase: "helloWorld",
KebabCase: "hello-world",
SnakeCase: "hello_world",
}},
{in: "A", want: output{
PascalCase: "A",
CamelCase: "a",
KebabCase: "a",
SnakeCase: "a",
}},
{in: "a", want: output{
PascalCase: "A",
CamelCase: "a",
KebabCase: "a",
SnakeCase: "a",
}},
{in: "foo", want: output{
PascalCase: "Foo",
CamelCase: "foo",
KebabCase: "foo",
SnakeCase: "foo",
}},
{in: "snake_case", want: output{
PascalCase: "SnakeCase",
CamelCase: "snakeCase",
KebabCase: "snake-case",
SnakeCase: "snake_case",
}},
{in: "SNAKE_CASE", want: output{
PascalCase: "SnakeCase",
CamelCase: "snakeCase",
KebabCase: "snake-case",
SnakeCase: "snake_case",
}},
{in: "kebab-case", want: output{
PascalCase: "KebabCase",
CamelCase: "kebabCase",
KebabCase: "kebab-case",
SnakeCase: "kebab_case",
}},
{in: "PascalCase", want: output{
PascalCase: "PascalCase",
CamelCase: "pascalCase",
KebabCase: "pascal-case",
SnakeCase: "pascal_case",
}},
{in: "camelCase", want: output{
PascalCase: "CamelCase",
CamelCase: "camelCase",
KebabCase: `camel-case`,
SnakeCase: "camel_case",
}},
{in: "Title Case", want: output{
PascalCase: "TitleCase",
CamelCase: "titleCase",
KebabCase: "title-case",
SnakeCase: "title_case",
}},
{in: "point.case", want: output{
PascalCase: "PointCase",
CamelCase: "pointCase",
KebabCase: "point-case",
SnakeCase: "point_case",
}},
{in: "snake_case_with_more_words", want: output{
PascalCase: "SnakeCaseWithMoreWords",
CamelCase: "snakeCaseWithMoreWords",
KebabCase: "snake-case-with-more-words",
SnakeCase: "snake_case_with_more_words",
}},
{in: "SNAKE_CASE_WITH_MORE_WORDS", want: output{
PascalCase: "SnakeCaseWithMoreWords",
CamelCase: "snakeCaseWithMoreWords",
KebabCase: "snake-case-with-more-words",
SnakeCase: "snake_case_with_more_words",
}},
{in: "kebab-case-with-more-words", want: output{
PascalCase: "KebabCaseWithMoreWords",
CamelCase: "kebabCaseWithMoreWords",
KebabCase: "kebab-case-with-more-words",
SnakeCase: "kebab_case_with_more_words",
}},
{in: "PascalCaseWithMoreWords", want: output{
PascalCase: "PascalCaseWithMoreWords",
CamelCase: "pascalCaseWithMoreWords",
KebabCase: "pascal-case-with-more-words",
SnakeCase: "pascal_case_with_more_words",
}},
{in: "camelCaseWithMoreWords", want: output{
PascalCase: "CamelCaseWithMoreWords",
CamelCase: "camelCaseWithMoreWords",
KebabCase: "camel-case-with-more-words",
SnakeCase: "camel_case_with_more_words",
}},
{in: "Title Case With More Words", want: output{
PascalCase: "TitleCaseWithMoreWords",
CamelCase: "titleCaseWithMoreWords",
KebabCase: "title-case-with-more-words",
SnakeCase: "title_case_with_more_words",
}},
{in: "point.case.with.more.words", want: output{
PascalCase: "PointCaseWithMoreWords",
CamelCase: "pointCaseWithMoreWords",
KebabCase: "point-case-with-more-words",
SnakeCase: "point_case_with_more_words",
}},
{in: "snake_case__with___multiple____delimiters", want: output{
PascalCase: "SnakeCaseWithMultipleDelimiters",
CamelCase: "snakeCaseWithMultipleDelimiters",
KebabCase: "snake-case-with-multiple-delimiters",
SnakeCase: "snake_case_with_multiple_delimiters",
}},
{in: "SNAKE_CASE__WITH___multiple____DELIMITERS", want: output{
PascalCase: "SnakeCaseWithMultipleDelimiters",
CamelCase: "snakeCaseWithMultipleDelimiters",
KebabCase: "snake-case-with-multiple-delimiters",
SnakeCase: "snake_case_with_multiple_delimiters",
}},
{in: "kebab-case--with---multiple----delimiters", want: output{
PascalCase: "KebabCaseWithMultipleDelimiters",
CamelCase: "kebabCaseWithMultipleDelimiters",
KebabCase: "kebab-case-with-multiple-delimiters",
SnakeCase: "kebab_case_with_multiple_delimiters",
}},
{in: "Title Case With Multiple Delimiters", want: output{
PascalCase: "TitleCaseWithMultipleDelimiters",
CamelCase: "titleCaseWithMultipleDelimiters",
KebabCase: "title-case-with-multiple-delimiters",
SnakeCase: "title_case_with_multiple_delimiters",
}},
{in: "point.case..with...multiple....delimiters", want: output{
PascalCase: "PointCaseWithMultipleDelimiters",
CamelCase: "pointCaseWithMultipleDelimiters",
KebabCase: "point-case-with-multiple-delimiters",
SnakeCase: "point_case_with_multiple_delimiters",
}},
{in: " leading space", want: output{
PascalCase: "LeadingSpace",
CamelCase: "leadingSpace",
KebabCase: "leading-space",
SnakeCase: "leading_space",
}},
{in: " leading spaces", want: output{
PascalCase: "LeadingSpaces",
CamelCase: "leadingSpaces",
KebabCase: "leading-spaces",
SnakeCase: "leading_spaces",
}},
{in: "\t\t\r\n leading whitespaces", want: output{
PascalCase: "LeadingWhitespaces",
CamelCase: "leadingWhitespaces",
KebabCase: "leading-whitespaces",
SnakeCase: "leading_whitespaces",
}},
{in: "trailing space ", want: output{
PascalCase: "TrailingSpace",
CamelCase: "trailingSpace",
KebabCase: "trailing-space",
SnakeCase: "trailing_space",
}},
{in: "trailing spaces ", want: output{
PascalCase: "TrailingSpaces",
CamelCase: "trailingSpaces",
KebabCase: "trailing-spaces",
SnakeCase: "trailing_spaces",
}},
{in: "trailing whitespaces\t\t\r\n", want: output{
PascalCase: "TrailingWhitespaces",
CamelCase: "trailingWhitespaces",
KebabCase: "trailing-whitespaces",
SnakeCase: "trailing_whitespaces",
}},
{in: " on both sides ", want: output{
PascalCase: "OnBothSides",
CamelCase: "onBothSides",
KebabCase: "on-both-sides",
SnakeCase: "on_both_sides",
}},
{in: " many on both sides ", want: output{
PascalCase: "ManyOnBothSides",
CamelCase: "manyOnBothSides",
KebabCase: "many-on-both-sides",
SnakeCase: "many_on_both_sides",
}},
{in: "\r whitespaces on both sides\t\t\r\n", want: output{
PascalCase: "WhitespacesOnBothSides",
CamelCase: "whitespacesOnBothSides",
KebabCase: "whitespaces-on-both-sides",
SnakeCase: "whitespaces_on_both_sides",
}},
{in: " extraSpaces in_This TestCase Of MIXED_CASES\t", want: output{
PascalCase: "ExtraSpacesInThisTestCaseOfMixedCases",
CamelCase: "extraSpacesInThisTestCaseOfMixedCases",
KebabCase: "extra-spaces-in-this-test-case-of-mixed-cases",
SnakeCase: "extra_spaces_in_this_test_case_of_mixed_cases",
}},
{in: "CASEBreak", want: output{
PascalCase: "CaseBreak",
CamelCase: "caseBreak",
KebabCase: "case-break",
SnakeCase: "case_break",
}},
{in: "ID", want: output{
PascalCase: "Id",
CamelCase: "id",
KebabCase: "id",
SnakeCase: "id",
}},
{in: "userID", want: output{
PascalCase: "UserId",
CamelCase: "userId",
KebabCase: "user-id",
SnakeCase: "user_id",
}},
{in: "JSON_blob", want: output{
PascalCase: "JsonBlob",
CamelCase: "jsonBlob",
KebabCase: "json-blob",
SnakeCase: "json_blob",
}},
{in: "HTTPStatusCode", want: output{
PascalCase: "HttpStatusCode",
CamelCase: "httpStatusCode",
KebabCase: "http-status-code",
SnakeCase: "http_status_code",
}},
{in: "FreeBSD and SSLError are not golang initialisms", want: output{
PascalCase: "FreeBsdAndSslErrorAreNotGolangInitialisms",
CamelCase: "freeBsdAndSslErrorAreNotGolangInitialisms",
KebabCase: "free-bsd-and-ssl-error-are-not-golang-initialisms",
SnakeCase: "free_bsd_and_ssl_error_are_not_golang_initialisms",
}},
{in: "David's Computer", want: output{
PascalCase: "DavidSComputer",
CamelCase: "davidSComputer",
KebabCase: "david-s-computer",
SnakeCase: "david_s_computer",
}},
{in: "http200", want: output{
PascalCase: "Http200",
CamelCase: "http200",
KebabCase: "http-200",
SnakeCase: "http_200",
}},
{in: "NumberSplittingVersion1.0r3", want: output{
PascalCase: "NumberSplittingVersion10R3",
CamelCase: "numberSplittingVersion10R3",
KebabCase: "number-splitting-version-1-0-r3",
SnakeCase: "number_splitting_version_1_0_r3",
}},
{in: "When you have a comma, odd results", want: output{
PascalCase: "WhenYouHaveACommaOddResults",
CamelCase: "whenYouHaveACommaOddResults",
KebabCase: "when-you-have-a-comma-odd-results",
SnakeCase: "when_you_have_a_comma_odd_results",
}},
{in: "Ordinal numbers work: 1st 2nd and 3rd place", want: output{
PascalCase: "OrdinalNumbersWork1St2NdAnd3RdPlace",
CamelCase: "ordinalNumbersWork1St2NdAnd3RdPlace",
KebabCase: "ordinal-numbers-work-1-st-2-nd-and-3-rd-place",
SnakeCase: "ordinal_numbers_work_1_st_2_nd_and_3_rd_place",
}},
{in: "BadUTF8\xe2\xe2\xa1", want: output{
PascalCase: "BadUtf8",
CamelCase: "badUtf8",
KebabCase: "bad-utf-8",
SnakeCase: "bad_utf_8",
}},
{in: "IDENT3", want: output{
PascalCase: "Ident3",
CamelCase: "ident3",
KebabCase: "ident-3",
SnakeCase: "ident_3",
}},
{in: "LogRouterS3BucketName", want: output{
PascalCase: "LogRouterS3BucketName",
CamelCase: "logRouterS3BucketName",
KebabCase: "log-router-s3-bucket-name",
SnakeCase: "log_router_s3_bucket_name",
}},
{in: "PINEAPPLE", want: output{
PascalCase: "Pineapple",
CamelCase: "pineapple",
KebabCase: "pineapple",
SnakeCase: "pineapple",
}},
{in: "Int8Value", want: output{
PascalCase: "Int8Value",
CamelCase: "int8Value",
KebabCase: "int-8-value",
SnakeCase: "int_8_value",
}},
{in: "first.last", want: output{
PascalCase: "FirstLast",
CamelCase: "firstLast",
KebabCase: "first-last",
SnakeCase: "first_last",
}},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.in, func(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equalf(tc.want.PascalCase, PascalCase(tc.in), "PascalCase(%v)", tc.in)
is.Equalf(tc.want.CamelCase, CamelCase(tc.in), "CamelCase(%v)", tc.in)
is.Equalf(tc.want.KebabCase, KebabCase(tc.in), "KebabCase(%v)", tc.in)
is.Equalf(tc.want.SnakeCase, SnakeCase(tc.in), "SnakeCase(%v)", tc.in)
})
}
}
func TestWords(t *testing.T) {
t.Parallel()
testCases := []struct {
in string
want []string
}{
{"PascalCase", []string{"Pascal", "Case"}},
{"camelCase", []string{"camel", "Case"}},
{"snake_case", []string{"snake", "case"}},
{"kebab_case", []string{"kebab", "case"}},
{"_test text_", []string{"test", "text"}},
{"UPPERCASE", []string{"UPPERCASE"}},
{"HTTPCode", []string{"HTTP", "Code"}},
{"Int8Value", []string{"Int", "8", "Value"}},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.in, func(t *testing.T) {
t.Parallel()
assert.Equalf(t, tc.want, Words(tc.in), "Words(%v)", tc.in)
})
}
}
func TestCapitalize(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
in string
want string
}{
{"lower case", "hello", "Hello"},
{"mixed case", "heLLO", "Hello"},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equalf(t, tc.want, Capitalize(tc.in), "Capitalize(%v)", tc.in)
})
}
}
func TestEllipsis(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Equal("...", Ellipsis("12", 0))
is.Equal("...", Ellipsis("12", 1))
is.Equal("12", Ellipsis("12", 2))
is.Equal("12", Ellipsis("12", 3))
is.Equal("12345", Ellipsis("12345", 5))
is.Equal("1...", Ellipsis("12345", 4))
is.Equal("1...", Ellipsis(" 12345 ", 4))
is.Equal("12345", Ellipsis("12345", 6))
is.Equal("12345", Ellipsis("12345", 10))
is.Equal("12345", Ellipsis(" 12345 ", 10))
is.Equal("...", Ellipsis("12345", 3))
is.Equal("...", Ellipsis("12345", 2))
is.Equal("...", Ellipsis("12345", -1))
is.Equal("hello...", Ellipsis(" hello world ", 9))
// Unicode: rune-based truncation (not byte-based)
is.Equal("hello...", Ellipsis("hello 世界! 你好", 8)) // CJK characters: "hello" (5 runes) + "..." = 8 runes
is.Equal("hello 世界...", Ellipsis("hello 世界! 你好", 11)) // truncate within CJK text
is.Equal("hello 世界! 你好", Ellipsis("hello 世界! 你好", 12)) // exact length, no truncation
is.Equal("hello 世界! 你好", Ellipsis("hello 世界! 你好", 20)) // length exceeds string, no truncation
is.Equal("🏠🐶🐱🌟", Ellipsis("🏠🐶🐱🌟", 5)) // length > rune count, no truncation
is.Equal("🏠🐶🐱🌟", Ellipsis("🏠🐶🐱🌟", 4)) // exact length, no truncation
is.Equal("...", Ellipsis("🏠🐶🐱🌟", 3)) // length == 3, returns "..."
is.Equal("...", Ellipsis("🏠🐶🐱🌟", 2)) // length < 3, returns "..."
is.Equal("🏠🐶...", Ellipsis("🏠🐶🐱🌟🎉🌈", 5)) // 6 emoji, truncate to 2 + "..."
is.Equal("café", Ellipsis("café", 4)) // accented char counts as 1 rune
is.Equal("...", Ellipsis("café", 3)) // length == 3, returns "..."
is.Equal("ca...", Ellipsis("café au lait", 5)) // mixed ASCII and accented
// Combining emoji (Rainbow Flag is 4 runes: U+1F3F3 + U+FE0F + U+200D + U+1F308)
// "aà😁🏳️🌈pabc" = 1 + 1 + 1 + 4 + 1 + 1 + 1 + 1 = 11 runes total
is.Equal("...", Ellipsis("aà😁🏳️🌈pabc", 2)) // only "..."
is.Equal("...", Ellipsis("aà😁🏳️🌈pabc", 3)) // only "..."
is.Equal("a...", Ellipsis("aà😁🏳️🌈pabc", 4)) // 1 rune + "..."
is.Equal("aà...", Ellipsis("aà😁🏳️🌈pabc", 5)) // 2 runes + "..."
is.Equal("aà😁...", Ellipsis("aà😁🏳️🌈pabc", 6)) // 3 runes + "..."
// @TODO: fix these tests
// is.Equal("aà😁🏳️🌈...", Ellipsis("aà😁🏳️🌈pabc", 7)) // 4 runes + "..."
// is.Equal("aà😁🏳️🌈p...", Ellipsis("aà😁🏳️🌈pabc", 8)) // 5 runes + "..."
// is.Equal("aà😁🏳️🌈pabc", Ellipsis("aà😁🏳️🌈pabc", 9)) // exact length, no truncation
// is.Equal("aà😁🏳️🌈pabc", Ellipsis("aà😁🏳️🌈pabc", 10)) // length exceeds string, no truncation
}
================================================
FILE: time.go
================================================
package lo
import (
"time"
)
// Duration returns the time taken to execute a function.
// Play: https://go.dev/play/p/LFhKq2vY9Ty
func Duration(callback func()) time.Duration {
return Duration0(callback)
}
// Duration0 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
func Duration0(callback func()) time.Duration {
start := time.Now()
callback()
return time.Since(start)
}
// Duration1 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
func Duration1[A any](callback func() A) (A, time.Duration) {
start := time.Now()
a := callback()
return a, time.Since(start)
}
// Duration2 returns the time taken to execute a function.
// Play: https://go.dev/play/p/HQfbBbAXaFP
func Duration2[A, B any](callback func() (A, B)) (A, B, time.Duration) {
start := time.Now()
a, b := callback()
return a, b, time.Since(start)
}
// Duration3 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
func Duration3[A, B, C any](callback func() (A, B, C)) (A, B, C, time.Duration) {
start := time.Now()
a, b, c := callback()
return a, b, c, time.Since(start)
}
// Duration4 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
func Duration4[A, B, C, D any](callback func() (A, B, C, D)) (A, B, C, D, time.Duration) {
start := time.Now()
a, b, c, d := callback()
return a, b, c, d, time.Since(start)
}
// Duration5 returns the time taken to execute a function.
// Play: https://go.dev/play/p/xr863iwkAxQ
func Duration5[A, B, C, D, E any](callback func() (A, B, C, D, E)) (A, B, C, D, E, time.Duration) {
start := time.Now()
a, b, c, d, e := callback()
return a, b, c, d, e, time.Since(start)
}
// Duration6 returns the time taken to execute a function.
// Play: https://go.dev/play/p/mR4bTQKO-Tf
func Duration6[A, B, C, D, E, F any](callback func() (A, B, C, D, E, F)) (A, B, C, D, E, F, time.Duration) {
start := time.Now()
a, b, c, d, e, f := callback()
return a, b, c, d, e, f, time.Since(start)
}
// Duration7 returns the time taken to execute a function.
// Play: https://go.dev/play/p/jgIAcBWWInS
func Duration7[A, B, C, D, E, F, G any](callback func() (A, B, C, D, E, F, G)) (A, B, C, D, E, F, G, time.Duration) {
start := time.Now()
a, b, c, d, e, f, g := callback()
return a, b, c, d, e, f, g, time.Since(start)
}
// Duration8 returns the time taken to execute a function.
// Play: https://go.dev/play/p/T8kxpG1c5Na
func Duration8[A, B, C, D, E, F, G, H any](callback func() (A, B, C, D, E, F, G, H)) (A, B, C, D, E, F, G, H, time.Duration) {
start := time.Now()
a, b, c, d, e, f, g, h := callback()
return a, b, c, d, e, f, g, h, time.Since(start)
}
// Duration9 returns the time taken to execute a function.
// Play: https://go.dev/play/p/bg9ix2VrZ0j
func Duration9[A, B, C, D, E, F, G, H, I any](callback func() (A, B, C, D, E, F, G, H, I)) (A, B, C, D, E, F, G, H, I, time.Duration) {
start := time.Now()
a, b, c, d, e, f, g, h, i := callback()
return a, b, c, d, e, f, g, h, i, time.Since(start)
}
// Duration10 returns the time taken to execute a function.
// Play: https://go.dev/play/p/Y3n7oJXqJbk
func Duration10[A, B, C, D, E, F, G, H, I, J any](callback func() (A, B, C, D, E, F, G, H, I, J)) (A, B, C, D, E, F, G, H, I, J, time.Duration) {
start := time.Now()
a, b, c, d, e, f, g, h, i, j := callback()
return a, b, c, d, e, f, g, h, i, j, time.Since(start)
}
================================================
FILE: time_test.go
================================================
package lo
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestDuration(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
testWithTimeout(t, 200*time.Millisecond)
result := Duration(func() { time.Sleep(100 * time.Millisecond) })
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
}
func TestDurationX(t *testing.T) { //nolint:paralleltest
// t.Parallel()
is := assert.New(t)
testWithTimeout(t, 1500*time.Millisecond)
{
result := Duration0(func() { time.Sleep(100 * time.Millisecond) })
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
}
{
a, result := Duration1(func() string { time.Sleep(100 * time.Millisecond); return "a" })
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
}
{
a, b, result := Duration2(func() (string, string) { time.Sleep(100 * time.Millisecond); return "a", "b" })
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
}
{
a, b, c, result := Duration3(func() (string, string, string) { time.Sleep(100 * time.Millisecond); return "a", "b", "c" })
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
is.Equal("c", c)
}
{
a, b, c, d, result := Duration4(func() (string, string, string, string) {
time.Sleep(100 * time.Millisecond)
return "a", "b", "c", "d"
})
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
is.Equal("c", c)
is.Equal("d", d)
}
{
a, b, c, d, e, result := Duration5(func() (string, string, string, string, string) {
time.Sleep(100 * time.Millisecond)
return "a", "b", "c", "d", "e"
})
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
is.Equal("c", c)
is.Equal("d", d)
is.Equal("e", e)
}
{
a, b, c, d, e, f, result := Duration6(func() (string, string, string, string, string, string) {
time.Sleep(100 * time.Millisecond)
return "a", "b", "c", "d", "e", "f"
})
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
is.Equal("c", c)
is.Equal("d", d)
is.Equal("e", e)
is.Equal("f", f)
}
{
a, b, c, d, e, f, g, result := Duration7(func() (string, string, string, string, string, string, string) {
time.Sleep(100 * time.Millisecond)
return "a", "b", "c", "d", "e", "f", "g"
})
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
is.Equal("c", c)
is.Equal("d", d)
is.Equal("e", e)
is.Equal("f", f)
is.Equal("g", g)
}
{
a, b, c, d, e, f, g, h, result := Duration8(func() (string, string, string, string, string, string, string, string) {
time.Sleep(100 * time.Millisecond)
return "a", "b", "c", "d", "e", "f", "g", "h"
})
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
is.Equal("c", c)
is.Equal("d", d)
is.Equal("e", e)
is.Equal("f", f)
is.Equal("g", g)
is.Equal("h", h)
}
{
a, b, c, d, e, f, g, h, i, result := Duration9(func() (string, string, string, string, string, string, string, string, string) {
time.Sleep(100 * time.Millisecond)
return "a", "b", "c", "d", "e", "f", "g", "h", "i"
})
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
is.Equal("c", c)
is.Equal("d", d)
is.Equal("e", e)
is.Equal("f", f)
is.Equal("g", g)
is.Equal("h", h)
is.Equal("i", i)
}
{
a, b, c, d, e, f, g, h, i, j, result := Duration10(func() (string, string, string, string, string, string, string, string, string, string) {
time.Sleep(100 * time.Millisecond)
return "a", "b", "c", "d", "e", "f", "g", "h", "i", "j"
})
is.InDelta(100*time.Millisecond, result, float64(20*time.Millisecond))
is.Equal("a", a)
is.Equal("b", b)
is.Equal("c", c)
is.Equal("d", d)
is.Equal("e", e)
is.Equal("f", f)
is.Equal("g", g)
is.Equal("h", h)
is.Equal("i", i)
is.Equal("j", j)
}
}
================================================
FILE: tuples.go
================================================
package lo
// T2 creates a tuple from a list of values.
// Play: https://go.dev/play/p/IllL3ZO4BQm
func T2[A, B any](a A, b B) Tuple2[A, B] {
return Tuple2[A, B]{A: a, B: b}
}
// T3 creates a tuple from a list of values.
// Play: https://go.dev/play/p/IllL3ZO4BQm
func T3[A, B, C any](a A, b B, c C) Tuple3[A, B, C] {
return Tuple3[A, B, C]{A: a, B: b, C: c}
}
// T4 creates a tuple from a list of values.
// Play: https://go.dev/play/p/IllL3ZO4BQm
func T4[A, B, C, D any](a A, b B, c C, d D) Tuple4[A, B, C, D] {
return Tuple4[A, B, C, D]{A: a, B: b, C: c, D: d}
}
// T5 creates a tuple from a list of values.
// Play: https://go.dev/play/p/IllL3ZO4BQm
func T5[A, B, C, D, E any](a A, b B, c C, d D, e E) Tuple5[A, B, C, D, E] {
return Tuple5[A, B, C, D, E]{A: a, B: b, C: c, D: d, E: e}
}
// T6 creates a tuple from a list of values.
// Play: https://go.dev/play/p/IllL3ZO4BQm
func T6[A, B, C, D, E, F any](a A, b B, c C, d D, e E, f F) Tuple6[A, B, C, D, E, F] {
return Tuple6[A, B, C, D, E, F]{A: a, B: b, C: c, D: d, E: e, F: f}
}
// T7 creates a tuple from a list of values.
// Play: https://go.dev/play/p/IllL3ZO4BQm
func T7[A, B, C, D, E, F, G any](a A, b B, c C, d D, e E, f F, g G) Tuple7[A, B, C, D, E, F, G] {
return Tuple7[A, B, C, D, E, F, G]{A: a, B: b, C: c, D: d, E: e, F: f, G: g}
}
// T8 creates a tuple from a list of values.
// Play: https://go.dev/play/p/IllL3ZO4BQm
func T8[A, B, C, D, E, F, G, H any](a A, b B, c C, d D, e E, f F, g G, h H) Tuple8[A, B, C, D, E, F, G, H] {
return Tuple8[A, B, C, D, E, F, G, H]{A: a, B: b, C: c, D: d, E: e, F: f, G: g, H: h}
}
// T9 creates a tuple from a list of values.
// Play: https://go.dev/play/p/IllL3ZO4BQm
func T9[A, B, C, D, E, F, G, H, I any](a A, b B, c C, d D, e E, f F, g G, h H, i I) Tuple9[A, B, C, D, E, F, G, H, I] {
return Tuple9[A, B, C, D, E, F, G, H, I]{A: a, B: b, C: c, D: d, E: e, F: f, G: g, H: h, I: i}
}
// Unpack2 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack2[A, B any](tuple Tuple2[A, B]) (A, B) {
return tuple.A, tuple.B
}
// Unpack3 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack3[A, B, C any](tuple Tuple3[A, B, C]) (A, B, C) {
return tuple.A, tuple.B, tuple.C
}
// Unpack4 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack4[A, B, C, D any](tuple Tuple4[A, B, C, D]) (A, B, C, D) {
return tuple.A, tuple.B, tuple.C, tuple.D
}
// Unpack5 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack5[A, B, C, D, E any](tuple Tuple5[A, B, C, D, E]) (A, B, C, D, E) {
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E
}
// Unpack6 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack6[A, B, C, D, E, F any](tuple Tuple6[A, B, C, D, E, F]) (A, B, C, D, E, F) {
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F
}
// Unpack7 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack7[A, B, C, D, E, F, G any](tuple Tuple7[A, B, C, D, E, F, G]) (A, B, C, D, E, F, G) {
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G
}
// Unpack8 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack8[A, B, C, D, E, F, G, H any](tuple Tuple8[A, B, C, D, E, F, G, H]) (A, B, C, D, E, F, G, H) {
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G, tuple.H
}
// Unpack9 returns values contained in a tuple.
// Play: https://go.dev/play/p/xVP_k0kJ96W
func Unpack9[A, B, C, D, E, F, G, H, I any](tuple Tuple9[A, B, C, D, E, F, G, H, I]) (A, B, C, D, E, F, G, H, I) {
return tuple.A, tuple.B, tuple.C, tuple.D, tuple.E, tuple.F, tuple.G, tuple.H, tuple.I
}
// Zip2 creates a slice of grouped elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip2[A, B any](a []A, b []B) []Tuple2[A, B] {
size := Max([]int{len(a), len(b)})
result := make([]Tuple2[A, B], size)
// Perf: separate loops per input slice improve CPU cache locality (each loop reads
// one contiguous memory region) and enable bounds-check elimination by the compiler.
for i := range a {
result[i].A = a[i]
}
for i := range b {
result[i].B = b[i]
}
return result
}
// Zip3 creates a slice of grouped elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip3[A, B, C any](a []A, b []B, c []C) []Tuple3[A, B, C] {
size := Max([]int{len(a), len(b), len(c)})
result := make([]Tuple3[A, B, C], size)
for i := range a {
result[i].A = a[i]
}
for i := range b {
result[i].B = b[i]
}
for i := range c {
result[i].C = c[i]
}
return result
}
// Zip4 creates a slice of grouped elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip4[A, B, C, D any](a []A, b []B, c []C, d []D) []Tuple4[A, B, C, D] {
size := Max([]int{len(a), len(b), len(c), len(d)})
result := make([]Tuple4[A, B, C, D], size)
for i := range a {
result[i].A = a[i]
}
for i := range b {
result[i].B = b[i]
}
for i := range c {
result[i].C = c[i]
}
for i := range d {
result[i].D = d[i]
}
return result
}
// Zip5 creates a slice of grouped elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip5[A, B, C, D, E any](a []A, b []B, c []C, d []D, e []E) []Tuple5[A, B, C, D, E] {
size := Max([]int{len(a), len(b), len(c), len(d), len(e)})
result := make([]Tuple5[A, B, C, D, E], size)
for i := range a {
result[i].A = a[i]
}
for i := range b {
result[i].B = b[i]
}
for i := range c {
result[i].C = c[i]
}
for i := range d {
result[i].D = d[i]
}
for i := range e {
result[i].E = e[i]
}
return result
}
// Zip6 creates a slice of grouped elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip6[A, B, C, D, E, F any](a []A, b []B, c []C, d []D, e []E, f []F) []Tuple6[A, B, C, D, E, F] {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)})
result := make([]Tuple6[A, B, C, D, E, F], size)
for i := range a {
result[i].A = a[i]
}
for i := range b {
result[i].B = b[i]
}
for i := range c {
result[i].C = c[i]
}
for i := range d {
result[i].D = d[i]
}
for i := range e {
result[i].E = e[i]
}
for i := range f {
result[i].F = f[i]
}
return result
}
// Zip7 creates a slice of grouped elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip7[A, B, C, D, E, F, G any](a []A, b []B, c []C, d []D, e []E, f []F, g []G) []Tuple7[A, B, C, D, E, F, G] {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)})
result := make([]Tuple7[A, B, C, D, E, F, G], size)
for i := range a {
result[i].A = a[i]
}
for i := range b {
result[i].B = b[i]
}
for i := range c {
result[i].C = c[i]
}
for i := range d {
result[i].D = d[i]
}
for i := range e {
result[i].E = e[i]
}
for i := range f {
result[i].F = f[i]
}
for i := range g {
result[i].G = g[i]
}
return result
}
// Zip8 creates a slice of grouped elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip8[A, B, C, D, E, F, G, H any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H) []Tuple8[A, B, C, D, E, F, G, H] {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)})
result := make([]Tuple8[A, B, C, D, E, F, G, H], size)
for i := range a {
result[i].A = a[i]
}
for i := range b {
result[i].B = b[i]
}
for i := range c {
result[i].C = c[i]
}
for i := range d {
result[i].D = d[i]
}
for i := range e {
result[i].E = e[i]
}
for i := range f {
result[i].F = f[i]
}
for i := range g {
result[i].G = g[i]
}
for i := range h {
result[i].H = h[i]
}
return result
}
// Zip9 creates a slice of grouped elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/jujaA6GaJTp
func Zip9[A, B, C, D, E, F, G, H, I any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, in []I) []Tuple9[A, B, C, D, E, F, G, H, I] {
size := Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(in)})
result := make([]Tuple9[A, B, C, D, E, F, G, H, I], size)
for i := range a {
result[i].A = a[i]
}
for i := range b {
result[i].B = b[i]
}
for i := range c {
result[i].C = c[i]
}
for i := range d {
result[i].D = d[i]
}
for i := range e {
result[i].E = e[i]
}
for i := range f {
result[i].F = f[i]
}
for i := range g {
result[i].G = g[i]
}
for i := range h {
result[i].H = h[i]
}
for i := range in {
result[i].I = in[i]
}
return result
}
// ZipBy2 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/wlHur6yO8rR
func ZipBy2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) Out) []Out {
size := uint(Max([]int{len(a), len(b)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
result[index] = iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
)
}
return result
}
// ZipBy3 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/j9maveOnSQX
func ZipBy3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) Out) []Out {
size := uint(Max([]int{len(a), len(b), len(c)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
result[index] = iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
)
}
return result
}
// ZipBy4 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/Y1eF2Ke0Ayz
func ZipBy4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) Out) []Out {
size := uint(Max([]int{len(a), len(b), len(c), len(d)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
result[index] = iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
)
}
return result
}
// ZipBy5 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/SLynyalh5Oa
func ZipBy5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) Out) []Out {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
result[index] = iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
)
}
return result
}
// ZipBy6 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/IK6KVgw9e-S
func ZipBy6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) Out) []Out {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
result[index] = iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
NthOrEmpty(f, index),
)
}
return result
}
// ZipBy7 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/4uW6a2vXh8w
func ZipBy7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) Out) []Out {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
result[index] = iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
NthOrEmpty(f, index),
NthOrEmpty(g, index),
)
}
return result
}
// ZipBy8 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/tk8xW7XzY4v
func ZipBy8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
result[index] = iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
NthOrEmpty(f, index),
NthOrEmpty(g, index),
NthOrEmpty(h, index),
)
}
return result
}
// ZipBy9 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// Play: https://go.dev/play/p/VGqjDmQ9YqX
func ZipBy9[A, B, C, D, E, F, G, H, I, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
result[index] = iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
NthOrEmpty(f, index),
NthOrEmpty(g, index),
NthOrEmpty(h, index),
NthOrEmpty(i, index),
)
}
return result
}
// ZipByErr2 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// It returns the first error returned by the iteratee.
func ZipByErr2[A, B, Out any](a []A, b []B, iteratee func(a A, b B) (Out, error)) ([]Out, error) {
size := uint(Max([]int{len(a), len(b)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
r, err := iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
)
if err != nil {
return nil, err
}
result[index] = r
}
return result, nil
}
// ZipByErr3 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// It returns the first error returned by the iteratee.
func ZipByErr3[A, B, C, Out any](a []A, b []B, c []C, iteratee func(a A, b B, c C) (Out, error)) ([]Out, error) {
size := uint(Max([]int{len(a), len(b), len(c)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
r, err := iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
)
if err != nil {
return nil, err
}
result[index] = r
}
return result, nil
}
// ZipByErr4 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// It returns the first error returned by the iteratee.
func ZipByErr4[A, B, C, D, Out any](a []A, b []B, c []C, d []D, iteratee func(a A, b B, c C, d D) (Out, error)) ([]Out, error) {
size := uint(Max([]int{len(a), len(b), len(c), len(d)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
r, err := iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
)
if err != nil {
return nil, err
}
result[index] = r
}
return result, nil
}
// ZipByErr5 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// It returns the first error returned by the iteratee.
func ZipByErr5[A, B, C, D, E, Out any](a []A, b []B, c []C, d []D, e []E, iteratee func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error) {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
r, err := iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
)
if err != nil {
return nil, err
}
result[index] = r
}
return result, nil
}
// ZipByErr6 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// It returns the first error returned by the iteratee.
func ZipByErr6[A, B, C, D, E, F, Out any](a []A, b []B, c []C, d []D, e []E, f []F, iteratee func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error) {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
r, err := iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
NthOrEmpty(f, index),
)
if err != nil {
return nil, err
}
result[index] = r
}
return result, nil
}
// ZipByErr7 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// It returns the first error returned by the iteratee.
func ZipByErr7[A, B, C, D, E, F, G, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, iteratee func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error) {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
r, err := iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
NthOrEmpty(f, index),
NthOrEmpty(g, index),
)
if err != nil {
return nil, err
}
result[index] = r
}
return result, nil
}
// ZipByErr8 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// It returns the first error returned by the iteratee.
func ZipByErr8[A, B, C, D, E, F, G, H, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error) {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
r, err := iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
NthOrEmpty(f, index),
NthOrEmpty(g, index),
NthOrEmpty(h, index),
)
if err != nil {
return nil, err
}
result[index] = r
}
return result, nil
}
// ZipByErr9 creates a slice of transformed elements, the first of which contains the first elements
// of the given slices, the second of which contains the second elements of the given slices, and so on.
// When collections are different sizes, the Tuple attributes are filled with zero value.
// It returns the first error returned by the iteratee.
func ZipByErr9[A, B, C, D, E, F, G, H, I, Out any](a []A, b []B, c []C, d []D, e []E, f []F, g []G, h []H, i []I, iteratee func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error) {
size := uint(Max([]int{len(a), len(b), len(c), len(d), len(e), len(f), len(g), len(h), len(i)}))
result := make([]Out, size)
for index := uint(0); index < size; index++ {
r, err := iteratee(
NthOrEmpty(a, index),
NthOrEmpty(b, index),
NthOrEmpty(c, index),
NthOrEmpty(d, index),
NthOrEmpty(e, index),
NthOrEmpty(f, index),
NthOrEmpty(g, index),
NthOrEmpty(h, index),
NthOrEmpty(i, index),
)
if err != nil {
return nil, err
}
result[index] = r
}
return result, nil
}
// Unzip2 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/K-vG9tyD3Kf
func Unzip2[A, B any](tuples []Tuple2[A, B]) ([]A, []B) {
size := len(tuples)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
for i := range tuples {
r1 = append(r1, tuples[i].A)
r2 = append(r2, tuples[i].B)
}
return r1, r2
}
// Unzip3 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip3[A, B, C any](tuples []Tuple3[A, B, C]) ([]A, []B, []C) {
size := len(tuples)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
for i := range tuples {
r1 = append(r1, tuples[i].A)
r2 = append(r2, tuples[i].B)
r3 = append(r3, tuples[i].C)
}
return r1, r2, r3
}
// Unzip4 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip4[A, B, C, D any](tuples []Tuple4[A, B, C, D]) ([]A, []B, []C, []D) {
size := len(tuples)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
for i := range tuples {
r1 = append(r1, tuples[i].A)
r2 = append(r2, tuples[i].B)
r3 = append(r3, tuples[i].C)
r4 = append(r4, tuples[i].D)
}
return r1, r2, r3, r4
}
// Unzip5 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip5[A, B, C, D, E any](tuples []Tuple5[A, B, C, D, E]) ([]A, []B, []C, []D, []E) {
size := len(tuples)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
for i := range tuples {
r1 = append(r1, tuples[i].A)
r2 = append(r2, tuples[i].B)
r3 = append(r3, tuples[i].C)
r4 = append(r4, tuples[i].D)
r5 = append(r5, tuples[i].E)
}
return r1, r2, r3, r4, r5
}
// Unzip6 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip6[A, B, C, D, E, F any](tuples []Tuple6[A, B, C, D, E, F]) ([]A, []B, []C, []D, []E, []F) {
size := len(tuples)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
for i := range tuples {
r1 = append(r1, tuples[i].A)
r2 = append(r2, tuples[i].B)
r3 = append(r3, tuples[i].C)
r4 = append(r4, tuples[i].D)
r5 = append(r5, tuples[i].E)
r6 = append(r6, tuples[i].F)
}
return r1, r2, r3, r4, r5, r6
}
// Unzip7 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip7[A, B, C, D, E, F, G any](tuples []Tuple7[A, B, C, D, E, F, G]) ([]A, []B, []C, []D, []E, []F, []G) {
size := len(tuples)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
for i := range tuples {
r1 = append(r1, tuples[i].A)
r2 = append(r2, tuples[i].B)
r3 = append(r3, tuples[i].C)
r4 = append(r4, tuples[i].D)
r5 = append(r5, tuples[i].E)
r6 = append(r6, tuples[i].F)
r7 = append(r7, tuples[i].G)
}
return r1, r2, r3, r4, r5, r6, r7
}
// Unzip8 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip8[A, B, C, D, E, F, G, H any](tuples []Tuple8[A, B, C, D, E, F, G, H]) ([]A, []B, []C, []D, []E, []F, []G, []H) {
size := len(tuples)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
r8 := make([]H, 0, size)
for i := range tuples {
r1 = append(r1, tuples[i].A)
r2 = append(r2, tuples[i].B)
r3 = append(r3, tuples[i].C)
r4 = append(r4, tuples[i].D)
r5 = append(r5, tuples[i].E)
r6 = append(r6, tuples[i].F)
r7 = append(r7, tuples[i].G)
r8 = append(r8, tuples[i].H)
}
return r1, r2, r3, r4, r5, r6, r7, r8
}
// Unzip9 accepts a slice of grouped elements and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/ciHugugvaAW
func Unzip9[A, B, C, D, E, F, G, H, I any](tuples []Tuple9[A, B, C, D, E, F, G, H, I]) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) {
size := len(tuples)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
r8 := make([]H, 0, size)
r9 := make([]I, 0, size)
for i := range tuples {
r1 = append(r1, tuples[i].A)
r2 = append(r2, tuples[i].B)
r3 = append(r3, tuples[i].C)
r4 = append(r4, tuples[i].D)
r5 = append(r5, tuples[i].E)
r6 = append(r6, tuples[i].F)
r7 = append(r7, tuples[i].G)
r8 = append(r8, tuples[i].H)
r9 = append(r9, tuples[i].I)
}
return r1, r2, r3, r4, r5, r6, r7, r8, r9
}
// UnzipBy2 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/tN8yqaRZz0r
func UnzipBy2[In, A, B any](items []In, iteratee func(In) (a A, b B)) ([]A, []B) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
for i := range items {
a, b := iteratee(items[i])
r1 = append(r1, a)
r2 = append(r2, b)
}
return r1, r2
}
// UnzipBy3 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/36ITO2DlQq1
func UnzipBy3[In, A, B, C any](items []In, iteratee func(In) (a A, b B, c C)) ([]A, []B, []C) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
for i := range items {
a, b, c := iteratee(items[i])
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
}
return r1, r2, r3
}
// UnzipBy4 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/zJ6qY1dD1rL
func UnzipBy4[In, A, B, C, D any](items []In, iteratee func(In) (a A, b B, c C, d D)) ([]A, []B, []C, []D) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
for i := range items {
a, b, c, d := iteratee(items[i])
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
}
return r1, r2, r3, r4
}
// UnzipBy5 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/3f7jKkV9xZt
func UnzipBy5[In, A, B, C, D, E any](items []In, iteratee func(In) (a A, b B, c C, d D, e E)) ([]A, []B, []C, []D, []E) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
for i := range items {
a, b, c, d, e := iteratee(items[i])
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
}
return r1, r2, r3, r4, r5
}
// UnzipBy6 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/8Y1b7tKu2pL
func UnzipBy6[In, A, B, C, D, E, F any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F)) ([]A, []B, []C, []D, []E, []F) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
for i := range items {
a, b, c, d, e, f := iteratee(items[i])
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
r6 = append(r6, f)
}
return r1, r2, r3, r4, r5, r6
}
// UnzipBy7 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/7j1kLmVn3pM
func UnzipBy7[In, A, B, C, D, E, F, G any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G)) ([]A, []B, []C, []D, []E, []F, []G) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
for i := range items {
a, b, c, d, e, f, g := iteratee(items[i])
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
r6 = append(r6, f)
r7 = append(r7, g)
}
return r1, r2, r3, r4, r5, r6, r7
}
// UnzipBy8 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/1n2k3L4m5N6
func UnzipBy8[In, A, B, C, D, E, F, G, H any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H)) ([]A, []B, []C, []D, []E, []F, []G, []H) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
r8 := make([]H, 0, size)
for i := range items {
a, b, c, d, e, f, g, h := iteratee(items[i])
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
r6 = append(r6, f)
r7 = append(r7, g)
r8 = append(r8, h)
}
return r1, r2, r3, r4, r5, r6, r7, r8
}
// UnzipBy9 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// Play: https://go.dev/play/p/7o8p9q0r1s2
func UnzipBy9[In, A, B, C, D, E, F, G, H, I any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
r8 := make([]H, 0, size)
r9 := make([]I, 0, size)
for i := range items {
a, b, c, d, e, f, g, h, i := iteratee(items[i])
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
r6 = append(r6, f)
r7 = append(r7, g)
r8 = append(r8, h)
r9 = append(r9, i)
}
return r1, r2, r3, r4, r5, r6, r7, r8, r9
}
// UnzipByErr2 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// It returns the first error returned by the iteratee.
// Play: https://go.dev/play/p/G2pyXQa1SUD
func UnzipByErr2[In, A, B any](items []In, iteratee func(In) (a A, b B, err error)) ([]A, []B, error) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
for i := range items {
a, b, err := iteratee(items[i])
if err != nil {
return nil, nil, err
}
r1 = append(r1, a)
r2 = append(r2, b)
}
return r1, r2, nil
}
// UnzipByErr3 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// It returns the first error returned by the iteratee.
func UnzipByErr3[In, A, B, C any](items []In, iteratee func(In) (a A, b B, c C, err error)) ([]A, []B, []C, error) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
for i := range items {
a, b, c, err := iteratee(items[i])
if err != nil {
return nil, nil, nil, err
}
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
}
return r1, r2, r3, nil
}
// UnzipByErr4 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// It returns the first error returned by the iteratee.
func UnzipByErr4[In, A, B, C, D any](items []In, iteratee func(In) (a A, b B, c C, d D, err error)) ([]A, []B, []C, []D, error) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
for i := range items {
a, b, c, d, err := iteratee(items[i])
if err != nil {
return nil, nil, nil, nil, err
}
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
}
return r1, r2, r3, r4, nil
}
// UnzipByErr5 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// It returns the first error returned by the iteratee.
func UnzipByErr5[In, A, B, C, D, E any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, err error)) ([]A, []B, []C, []D, []E, error) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
for i := range items {
a, b, c, d, e, err := iteratee(items[i])
if err != nil {
return nil, nil, nil, nil, nil, err
}
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
}
return r1, r2, r3, r4, r5, nil
}
// UnzipByErr6 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// It returns the first error returned by the iteratee.
func UnzipByErr6[In, A, B, C, D, E, F any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, err error)) ([]A, []B, []C, []D, []E, []F, error) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
for i := range items {
a, b, c, d, e, f, err := iteratee(items[i])
if err != nil {
return nil, nil, nil, nil, nil, nil, err
}
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
r6 = append(r6, f)
}
return r1, r2, r3, r4, r5, r6, nil
}
// UnzipByErr7 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// It returns the first error returned by the iteratee.
func UnzipByErr7[In, A, B, C, D, E, F, G any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, err error)) ([]A, []B, []C, []D, []E, []F, []G, error) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
for i := range items {
a, b, c, d, e, f, g, err := iteratee(items[i])
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, err
}
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
r6 = append(r6, f)
r7 = append(r7, g)
}
return r1, r2, r3, r4, r5, r6, r7, nil
}
// UnzipByErr8 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// It returns the first error returned by the iteratee.
func UnzipByErr8[In, A, B, C, D, E, F, G, H any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, err error)) ([]A, []B, []C, []D, []E, []F, []G, []H, error) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
r8 := make([]H, 0, size)
for i := range items {
a, b, c, d, e, f, g, h, err := iteratee(items[i])
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, nil, err
}
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
r6 = append(r6, f)
r7 = append(r7, g)
r8 = append(r8, h)
}
return r1, r2, r3, r4, r5, r6, r7, r8, nil
}
// UnzipByErr9 iterates over a collection and creates a slice regrouping the elements
// to their pre-zip configuration.
// It returns the first error returned by the iteratee.
func UnzipByErr9[In, A, B, C, D, E, F, G, H, I any](items []In, iteratee func(In) (a A, b B, c C, d D, e E, f F, g G, h H, i I, err error)) ([]A, []B, []C, []D, []E, []F, []G, []H, []I, error) {
size := len(items)
r1 := make([]A, 0, size)
r2 := make([]B, 0, size)
r3 := make([]C, 0, size)
r4 := make([]D, 0, size)
r5 := make([]E, 0, size)
r6 := make([]F, 0, size)
r7 := make([]G, 0, size)
r8 := make([]H, 0, size)
r9 := make([]I, 0, size)
for i := range items {
a, b, c, d, e, f, g, h, i, err := iteratee(items[i])
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
}
r1 = append(r1, a)
r2 = append(r2, b)
r3 = append(r3, c)
r4 = append(r4, d)
r5 = append(r5, e)
r6 = append(r6, f)
r7 = append(r7, g)
r8 = append(r8, h)
r9 = append(r9, i)
}
return r1, r2, r3, r4, r5, r6, r7, r8, r9, nil
}
// CrossJoin2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/LSRL5DmdPag
func CrossJoin2[A, B any](listA []A, listB []B) []Tuple2[A, B] {
return CrossJoinBy2(listA, listB, T2[A, B])
}
// CrossJoin3 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/2WGeHyJj4fK
func CrossJoin3[A, B, C any](listA []A, listB []B, listC []C) []Tuple3[A, B, C] {
return CrossJoinBy3(listA, listB, listC, T3[A, B, C])
}
// CrossJoin4 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/6XhKjLmMnNp
func CrossJoin4[A, B, C, D any](listA []A, listB []B, listC []C, listD []D) []Tuple4[A, B, C, D] {
return CrossJoinBy4(listA, listB, listC, listD, T4[A, B, C, D])
}
// CrossJoin5 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/7oPqRsTuVwX
func CrossJoin5[A, B, C, D, E any](listA []A, listB []B, listC []C, listD []D, listE []E) []Tuple5[A, B, C, D, E] {
return CrossJoinBy5(listA, listB, listC, listD, listE, T5[A, B, C, D, E])
}
// CrossJoin6 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/8yZ1aB2cD3e
func CrossJoin6[A, B, C, D, E, F any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F) []Tuple6[A, B, C, D, E, F] {
return CrossJoinBy6(listA, listB, listC, listD, listE, listF, T6[A, B, C, D, E, F])
}
// CrossJoin7 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/9f4g5h6i7j8
func CrossJoin7[A, B, C, D, E, F, G any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G) []Tuple7[A, B, C, D, E, F, G] {
return CrossJoinBy7(listA, listB, listC, listD, listE, listF, listG, T7[A, B, C, D, E, F, G])
}
// CrossJoin8 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/0k1l2m3n4o5
func CrossJoin8[A, B, C, D, E, F, G, H any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H) []Tuple8[A, B, C, D, E, F, G, H] {
return CrossJoinBy8(listA, listB, listC, listD, listE, listF, listG, listH, T8[A, B, C, D, E, F, G, H])
}
// CrossJoin9 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/6p7q8r9s0t1
func CrossJoin9[A, B, C, D, E, F, G, H, I any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I) []Tuple9[A, B, C, D, E, F, G, H, I] {
return CrossJoinBy9(listA, listB, listC, listD, listE, listF, listG, listH, listI, T9[A, B, C, D, E, F, G, H, I])
}
// CrossJoinBy2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/VLy8iyrPN8X
func CrossJoinBy2[A, B, Out any](listA []A, listB []B, transform func(a A, b B) Out) []Out {
size := len(listA) * len(listB)
if size == 0 {
return []Out{}
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
result = append(result, transform(a, b))
}
}
return result
}
// CrossJoinBy3 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3z4y5x6w7v8
func CrossJoinBy3[A, B, C, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) Out) []Out {
size := len(listA) * len(listB) * len(listC)
if size == 0 {
return []Out{}
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
result = append(result, transform(a, b, c))
}
}
}
return result
}
// CrossJoinBy4 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/8b9c0d1e2f3
func CrossJoinBy4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD)
if size == 0 {
return []Out{}
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
result = append(result, transform(a, b, c, d))
}
}
}
}
return result
}
// CrossJoinBy5 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/4g5h6i7j8k9
func CrossJoinBy5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE)
if size == 0 {
return []Out{}
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
result = append(result, transform(a, b, c, d, e))
}
}
}
}
}
return result
}
// CrossJoinBy6 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/1l2m3n4o5p6
func CrossJoinBy6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF)
if size == 0 {
return []Out{}
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
result = append(result, transform(a, b, c, d, e, f))
}
}
}
}
}
}
return result
}
// CrossJoinBy7 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/7q8r9s0t1u2
func CrossJoinBy7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG)
if size == 0 {
return []Out{}
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
result = append(result, transform(a, b, c, d, e, f, g))
}
}
}
}
}
}
}
return result
}
// CrossJoinBy8 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/3v4w5x6y7z8
func CrossJoinBy8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH)
if size == 0 {
return []Out{}
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
for _, h := range listH {
result = append(result, transform(a, b, c, d, e, f, g, h))
}
}
}
}
}
}
}
}
return result
}
// CrossJoinBy9 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// Play: https://go.dev/play/p/9a0b1c2d3e4
func CrossJoinBy9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) Out) []Out {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) * len(listI)
if size == 0 {
return []Out{}
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
for _, h := range listH {
for _, i := range listI {
result = append(result, transform(a, b, c, d, e, f, g, h, i))
}
}
}
}
}
}
}
}
}
return result
}
// CrossJoinByErr2 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr2[A, B, Out any](listA []A, listB []B, transform func(a A, b B) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
r, err := transform(a, b)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
return result, nil
}
// CrossJoinByErr3 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr3[A, B, C, Out any](listA []A, listB []B, listC []C, transform func(a A, b B, c C) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
r, err := transform(a, b, c)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
return result, nil
}
// CrossJoinByErr4 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr4[A, B, C, D, Out any](listA []A, listB []B, listC []C, listD []D, transform func(a A, b B, c C, d D) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
r, err := transform(a, b, c, d)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
return result, nil
}
// CrossJoinByErr5 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr5[A, B, C, D, E, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, transform func(a A, b B, c C, d D, e E) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
r, err := transform(a, b, c, d, e)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
return result, nil
}
// CrossJoinByErr6 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr6[A, B, C, D, E, F, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, transform func(a A, b B, c C, d D, e E, f F) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
r, err := transform(a, b, c, d, e, f)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
}
return result, nil
}
// CrossJoinByErr7 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr7[A, B, C, D, E, F, G, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, transform func(a A, b B, c C, d D, e E, f F, g G) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
r, err := transform(a, b, c, d, e, f, g)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
}
}
return result, nil
}
// CrossJoinByErr8 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr8[A, B, C, D, E, F, G, H, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, transform func(a A, b B, c C, d D, e E, f F, g G, h H) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
for _, h := range listH {
r, err := transform(a, b, c, d, e, f, g, h)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
}
}
}
return result, nil
}
// CrossJoinByErr9 combines every item from one list with every item from others.
// It is the cartesian product of lists received as arguments. The transform function
// is used to create the output values.
// Returns an empty list if a list is empty.
// It returns the first error returned by the transform function.
func CrossJoinByErr9[A, B, C, D, E, F, G, H, I, Out any](listA []A, listB []B, listC []C, listD []D, listE []E, listF []F, listG []G, listH []H, listI []I, transform func(a A, b B, c C, d D, e E, f F, g G, h H, i I) (Out, error)) ([]Out, error) {
size := len(listA) * len(listB) * len(listC) * len(listD) * len(listE) * len(listF) * len(listG) * len(listH) * len(listI)
if size == 0 {
return []Out{}, nil
}
result := make([]Out, 0, size)
for _, a := range listA {
for _, b := range listB {
for _, c := range listC {
for _, d := range listD {
for _, e := range listE {
for _, f := range listF {
for _, g := range listG {
for _, h := range listH {
for _, i := range listI {
r, err := transform(a, b, c, d, e, f, g, h, i)
if err != nil {
return nil, err
}
result = append(result, r)
}
}
}
}
}
}
}
}
}
return result, nil
}
================================================
FILE: tuples_test.go
================================================
package lo
import (
"errors"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestT(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := T2("a", 1)
r2 := T3[string, int, float32]("b", 2, 3.0)
r3 := T4[string, int, float32]("c", 3, 4.0, true)
r4 := T5[string, int, float32]("d", 4, 5.0, false, "e")
r5 := T6[string, int, float32]("f", 5, 6.0, true, "g", 7)
r6 := T7[string, int, float32]("h", 6, 7.0, false, "i", 8, 9.0)
r7 := T8[string, int, float32]("j", 7, 8.0, true, "k", 9, 10.0, false)
r8 := T9[string, int, float32]("l", 8, 9.0, false, "m", 10, 11.0, true, "n")
is.Equal(Tuple2[string, int]{A: "a", B: 1}, r1)
is.Equal(Tuple3[string, int, float32]{A: "b", B: 2, C: 3.0}, r2)
is.Equal(Tuple4[string, int, float32, bool]{A: "c", B: 3, C: 4.0, D: true}, r3)
is.Equal(Tuple5[string, int, float32, bool, string]{A: "d", B: 4, C: 5.0, D: false, E: "e"}, r4)
is.Equal(Tuple6[string, int, float32, bool, string, int]{A: "f", B: 5, C: 6.0, D: true, E: "g", F: 7}, r5)
is.Equal(Tuple7[string, int, float32, bool, string, int, float64]{A: "h", B: 6, C: 7.0, D: false, E: "i", F: 8, G: 9.0}, r6)
is.Equal(Tuple8[string, int, float32, bool, string, int, float64, bool]{A: "j", B: 7, C: 8.0, D: true, E: "k", F: 9, G: 10.0, H: false}, r7)
is.Equal(Tuple9[string, int, float32, bool, string, int, float64, bool, string]{A: "l", B: 8, C: 9.0, D: false, E: "m", F: 10, G: 11.0, H: true, I: "n"}, r8)
}
func TestUnpack(t *testing.T) {
t.Parallel()
is := assert.New(t)
{
tuple := Tuple2[string, int]{"a", 1}
r1, r2 := Unpack2(tuple)
is.Equal("a", r1)
is.Equal(1, r2)
r1, r2 = tuple.Unpack()
is.Equal("a", r1)
is.Equal(1, r2)
}
{
tuple := Tuple3[string, int, float64]{"a", 1, 1.0}
r1, r2, r3 := Unpack3(tuple)
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
r1, r2, r3 = tuple.Unpack()
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
}
{
tuple := Tuple4[string, int, float64, bool]{"a", 1, 1.0, true}
r1, r2, r3, r4 := Unpack4(tuple)
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
r1, r2, r3, r4 = tuple.Unpack()
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
}
{
tuple := Tuple5[string, int, float64, bool, string]{"a", 1, 1.0, true, "b"}
r1, r2, r3, r4, r5 := Unpack5(tuple)
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
r1, r2, r3, r4, r5 = tuple.Unpack()
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
}
{
tuple := Tuple6[string, int, float64, bool, string, int]{"a", 1, 1.0, true, "b", 2}
r1, r2, r3, r4, r5, r6 := Unpack6(tuple)
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
is.Equal(2, r6)
r1, r2, r3, r4, r5, r6 = tuple.Unpack()
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
is.Equal(2, r6)
}
{
tuple := Tuple7[string, int, float64, bool, string, int, float64]{"a", 1, 1.0, true, "b", 2, 3.0}
r1, r2, r3, r4, r5, r6, r7 := Unpack7(tuple)
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
is.Equal(2, r6)
is.Equal(3.0, r7)
r1, r2, r3, r4, r5, r6, r7 = tuple.Unpack()
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
is.Equal(2, r6)
is.Equal(3.0, r7)
}
{
tuple := Tuple8[string, int, float64, bool, string, int, float64, bool]{"a", 1, 1.0, true, "b", 2, 3.0, true}
r1, r2, r3, r4, r5, r6, r7, r8 := Unpack8(tuple)
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
is.Equal(2, r6)
is.Equal(3.0, r7)
is.True(r8)
r1, r2, r3, r4, r5, r6, r7, r8 = tuple.Unpack()
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
is.Equal(2, r6)
is.Equal(3.0, r7)
is.True(r8)
}
{
tuple := Tuple9[string, int, float64, bool, string, int, float64, bool, string]{"a", 1, 1.0, true, "b", 2, 3.0, true, "c"}
r1, r2, r3, r4, r5, r6, r7, r8, r9 := Unpack9(tuple)
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
is.Equal(2, r6)
is.Equal(3.0, r7)
is.True(r8)
is.Equal("c", r9)
r1, r2, r3, r4, r5, r6, r7, r8, r9 = tuple.Unpack()
is.Equal("a", r1)
is.Equal(1, r2)
is.Equal(1.0, r3)
is.True(r4)
is.Equal("b", r5)
is.Equal(2, r6)
is.Equal(3.0, r7)
is.True(r8)
is.Equal("c", r9)
}
}
func TestZip(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := Zip2(
[]string{"a", "b"},
[]int{1, 2},
)
r2 := Zip3(
[]string{"a", "b", "c"},
[]int{1, 2, 3},
[]int{4, 5, 6},
)
r3 := Zip4(
[]string{"a", "b", "c", "d"},
[]int{1, 2, 3, 4},
[]int{5, 6, 7, 8},
[]bool{true, true, true, true},
)
r4 := Zip5(
[]string{"a", "b", "c", "d", "e"},
[]int{1, 2, 3, 4, 5},
[]int{6, 7, 8, 9, 10},
[]bool{true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5},
)
r5 := Zip6(
[]string{"a", "b", "c", "d", "e", "f"},
[]int{1, 2, 3, 4, 5, 6},
[]int{7, 8, 9, 10, 11, 12},
[]bool{true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06},
)
r6 := Zip7(
[]string{"a", "b", "c", "d", "e", "f", "g"},
[]int{1, 2, 3, 4, 5, 6, 7},
[]int{8, 9, 10, 11, 12, 13, 14},
[]bool{true, true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07},
[]int8{1, 2, 3, 4, 5, 6, 7},
)
r7 := Zip8(
[]string{"a", "b", "c", "d", "e", "f", "g", "h"},
[]int{1, 2, 3, 4, 5, 6, 7, 8},
[]int{9, 10, 11, 12, 13, 14, 15, 16},
[]bool{true, true, true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08},
[]int8{1, 2, 3, 4, 5, 6, 7, 8},
[]int16{1, 2, 3, 4, 5, 6, 7, 8},
)
r8 := Zip9(
[]string{"a", "b", "c", "d", "e", "f", "g", "h", "i"},
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
[]int{10, 11, 12, 13, 14, 15, 16, 17, 18},
[]bool{true, true, true, true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09},
[]int8{1, 2, 3, 4, 5, 6, 7, 8, 9},
[]int16{1, 2, 3, 4, 5, 6, 7, 8, 9},
[]int32{1, 2, 3, 4, 5, 6, 7, 8, 9},
)
is.Equal([]Tuple2[string, int]{
{A: "a", B: 1},
{A: "b", B: 2},
}, r1)
is.Equal([]Tuple3[string, int, int]{
{A: "a", B: 1, C: 4},
{A: "b", B: 2, C: 5},
{A: "c", B: 3, C: 6},
}, r2)
is.Equal([]Tuple4[string, int, int, bool]{
{A: "a", B: 1, C: 5, D: true},
{A: "b", B: 2, C: 6, D: true},
{A: "c", B: 3, C: 7, D: true},
{A: "d", B: 4, C: 8, D: true},
}, r3)
is.Equal([]Tuple5[string, int, int, bool, float32]{
{A: "a", B: 1, C: 6, D: true, E: 0.1},
{A: "b", B: 2, C: 7, D: true, E: 0.2},
{A: "c", B: 3, C: 8, D: true, E: 0.3},
{A: "d", B: 4, C: 9, D: true, E: 0.4},
{A: "e", B: 5, C: 10, D: true, E: 0.5},
}, r4)
is.Equal([]Tuple6[string, int, int, bool, float32, float64]{
{A: "a", B: 1, C: 7, D: true, E: 0.1, F: 0.01},
{A: "b", B: 2, C: 8, D: true, E: 0.2, F: 0.02},
{A: "c", B: 3, C: 9, D: true, E: 0.3, F: 0.03},
{A: "d", B: 4, C: 10, D: true, E: 0.4, F: 0.04},
{A: "e", B: 5, C: 11, D: true, E: 0.5, F: 0.05},
{A: "f", B: 6, C: 12, D: true, E: 0.6, F: 0.06},
}, r5)
is.Equal([]Tuple7[string, int, int, bool, float32, float64, int8]{
{A: "a", B: 1, C: 8, D: true, E: 0.1, F: 0.01, G: 1},
{A: "b", B: 2, C: 9, D: true, E: 0.2, F: 0.02, G: 2},
{A: "c", B: 3, C: 10, D: true, E: 0.3, F: 0.03, G: 3},
{A: "d", B: 4, C: 11, D: true, E: 0.4, F: 0.04, G: 4},
{A: "e", B: 5, C: 12, D: true, E: 0.5, F: 0.05, G: 5},
{A: "f", B: 6, C: 13, D: true, E: 0.6, F: 0.06, G: 6},
{A: "g", B: 7, C: 14, D: true, E: 0.7, F: 0.07, G: 7},
}, r6)
is.Equal([]Tuple8[string, int, int, bool, float32, float64, int8, int16]{
{A: "a", B: 1, C: 9, D: true, E: 0.1, F: 0.01, G: 1, H: 1},
{A: "b", B: 2, C: 10, D: true, E: 0.2, F: 0.02, G: 2, H: 2},
{A: "c", B: 3, C: 11, D: true, E: 0.3, F: 0.03, G: 3, H: 3},
{A: "d", B: 4, C: 12, D: true, E: 0.4, F: 0.04, G: 4, H: 4},
{A: "e", B: 5, C: 13, D: true, E: 0.5, F: 0.05, G: 5, H: 5},
{A: "f", B: 6, C: 14, D: true, E: 0.6, F: 0.06, G: 6, H: 6},
{A: "g", B: 7, C: 15, D: true, E: 0.7, F: 0.07, G: 7, H: 7},
{A: "h", B: 8, C: 16, D: true, E: 0.8, F: 0.08, G: 8, H: 8},
}, r7)
is.Equal([]Tuple9[string, int, int, bool, float32, float64, int8, int16, int32]{
{A: "a", B: 1, C: 10, D: true, E: 0.1, F: 0.01, G: 1, H: 1, I: 1},
{A: "b", B: 2, C: 11, D: true, E: 0.2, F: 0.02, G: 2, H: 2, I: 2},
{A: "c", B: 3, C: 12, D: true, E: 0.3, F: 0.03, G: 3, H: 3, I: 3},
{A: "d", B: 4, C: 13, D: true, E: 0.4, F: 0.04, G: 4, H: 4, I: 4},
{A: "e", B: 5, C: 14, D: true, E: 0.5, F: 0.05, G: 5, H: 5, I: 5},
{A: "f", B: 6, C: 15, D: true, E: 0.6, F: 0.06, G: 6, H: 6, I: 6},
{A: "g", B: 7, C: 16, D: true, E: 0.7, F: 0.07, G: 7, H: 7, I: 7},
{A: "h", B: 8, C: 17, D: true, E: 0.8, F: 0.08, G: 8, H: 8, I: 8},
{A: "i", B: 9, C: 18, D: true, E: 0.9, F: 0.09, G: 9, H: 9, I: 9},
}, r8)
}
func TestZipBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1 := ZipBy2(
[]string{"a", "b"},
[]int{1, 2},
T2[string, int],
)
r2 := ZipBy3(
[]string{"a", "b", "c"},
[]int{1, 2, 3},
[]int{4, 5, 6},
T3[string, int, int],
)
r3 := ZipBy4(
[]string{"a", "b", "c", "d"},
[]int{1, 2, 3, 4},
[]int{5, 6, 7, 8},
[]bool{true, true, true, true},
T4[string, int, int, bool],
)
r4 := ZipBy5(
[]string{"a", "b", "c", "d", "e"},
[]int{1, 2, 3, 4, 5},
[]int{6, 7, 8, 9, 10},
[]bool{true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5},
T5[string, int, int, bool, float32],
)
r5 := ZipBy6(
[]string{"a", "b", "c", "d", "e", "f"},
[]int{1, 2, 3, 4, 5, 6},
[]int{7, 8, 9, 10, 11, 12},
[]bool{true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06},
T6[string, int, int, bool, float32, float64],
)
r6 := ZipBy7(
[]string{"a", "b", "c", "d", "e", "f", "g"},
[]int{1, 2, 3, 4, 5, 6, 7},
[]int{8, 9, 10, 11, 12, 13, 14},
[]bool{true, true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07},
[]int8{1, 2, 3, 4, 5, 6, 7},
T7[string, int, int, bool, float32, float64, int8],
)
r7 := ZipBy8(
[]string{"a", "b", "c", "d", "e", "f", "g", "h"},
[]int{1, 2, 3, 4, 5, 6, 7, 8},
[]int{9, 10, 11, 12, 13, 14, 15, 16},
[]bool{true, true, true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08},
[]int8{1, 2, 3, 4, 5, 6, 7, 8},
[]int16{1, 2, 3, 4, 5, 6, 7, 8},
T8[string, int, int, bool, float32, float64, int8, int16],
)
r8 := ZipBy9(
[]string{"a", "b", "c", "d", "e", "f", "g", "h", "i"},
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9},
[]int{10, 11, 12, 13, 14, 15, 16, 17, 18},
[]bool{true, true, true, true, true, true, true, true, true},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9},
[]float64{0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09},
[]int8{1, 2, 3, 4, 5, 6, 7, 8, 9},
[]int16{1, 2, 3, 4, 5, 6, 7, 8, 9},
[]int32{1, 2, 3, 4, 5, 6, 7, 8, 9},
T9[string, int, int, bool, float32, float64, int8, int16, int32],
)
is.Equal([]Tuple2[string, int]{
{A: "a", B: 1},
{A: "b", B: 2},
}, r1)
is.Equal([]Tuple3[string, int, int]{
{A: "a", B: 1, C: 4},
{A: "b", B: 2, C: 5},
{A: "c", B: 3, C: 6},
}, r2)
is.Equal([]Tuple4[string, int, int, bool]{
{A: "a", B: 1, C: 5, D: true},
{A: "b", B: 2, C: 6, D: true},
{A: "c", B: 3, C: 7, D: true},
{A: "d", B: 4, C: 8, D: true},
}, r3)
is.Equal([]Tuple5[string, int, int, bool, float32]{
{A: "a", B: 1, C: 6, D: true, E: 0.1},
{A: "b", B: 2, C: 7, D: true, E: 0.2},
{A: "c", B: 3, C: 8, D: true, E: 0.3},
{A: "d", B: 4, C: 9, D: true, E: 0.4},
{A: "e", B: 5, C: 10, D: true, E: 0.5},
}, r4)
is.Equal([]Tuple6[string, int, int, bool, float32, float64]{
{A: "a", B: 1, C: 7, D: true, E: 0.1, F: 0.01},
{A: "b", B: 2, C: 8, D: true, E: 0.2, F: 0.02},
{A: "c", B: 3, C: 9, D: true, E: 0.3, F: 0.03},
{A: "d", B: 4, C: 10, D: true, E: 0.4, F: 0.04},
{A: "e", B: 5, C: 11, D: true, E: 0.5, F: 0.05},
{A: "f", B: 6, C: 12, D: true, E: 0.6, F: 0.06},
}, r5)
is.Equal([]Tuple7[string, int, int, bool, float32, float64, int8]{
{A: "a", B: 1, C: 8, D: true, E: 0.1, F: 0.01, G: 1},
{A: "b", B: 2, C: 9, D: true, E: 0.2, F: 0.02, G: 2},
{A: "c", B: 3, C: 10, D: true, E: 0.3, F: 0.03, G: 3},
{A: "d", B: 4, C: 11, D: true, E: 0.4, F: 0.04, G: 4},
{A: "e", B: 5, C: 12, D: true, E: 0.5, F: 0.05, G: 5},
{A: "f", B: 6, C: 13, D: true, E: 0.6, F: 0.06, G: 6},
{A: "g", B: 7, C: 14, D: true, E: 0.7, F: 0.07, G: 7},
}, r6)
is.Equal([]Tuple8[string, int, int, bool, float32, float64, int8, int16]{
{A: "a", B: 1, C: 9, D: true, E: 0.1, F: 0.01, G: 1, H: 1},
{A: "b", B: 2, C: 10, D: true, E: 0.2, F: 0.02, G: 2, H: 2},
{A: "c", B: 3, C: 11, D: true, E: 0.3, F: 0.03, G: 3, H: 3},
{A: "d", B: 4, C: 12, D: true, E: 0.4, F: 0.04, G: 4, H: 4},
{A: "e", B: 5, C: 13, D: true, E: 0.5, F: 0.05, G: 5, H: 5},
{A: "f", B: 6, C: 14, D: true, E: 0.6, F: 0.06, G: 6, H: 6},
{A: "g", B: 7, C: 15, D: true, E: 0.7, F: 0.07, G: 7, H: 7},
{A: "h", B: 8, C: 16, D: true, E: 0.8, F: 0.08, G: 8, H: 8},
}, r7)
is.Equal([]Tuple9[string, int, int, bool, float32, float64, int8, int16, int32]{
{A: "a", B: 1, C: 10, D: true, E: 0.1, F: 0.01, G: 1, H: 1, I: 1},
{A: "b", B: 2, C: 11, D: true, E: 0.2, F: 0.02, G: 2, H: 2, I: 2},
{A: "c", B: 3, C: 12, D: true, E: 0.3, F: 0.03, G: 3, H: 3, I: 3},
{A: "d", B: 4, C: 13, D: true, E: 0.4, F: 0.04, G: 4, H: 4, I: 4},
{A: "e", B: 5, C: 14, D: true, E: 0.5, F: 0.05, G: 5, H: 5, I: 5},
{A: "f", B: 6, C: 15, D: true, E: 0.6, F: 0.06, G: 6, H: 6, I: 6},
{A: "g", B: 7, C: 16, D: true, E: 0.7, F: 0.07, G: 7, H: 7, I: 7},
{A: "h", B: 8, C: 17, D: true, E: 0.8, F: 0.08, G: 8, H: 8, I: 8},
{A: "i", B: 9, C: 18, D: true, E: 0.9, F: 0.09, G: 9, H: 9, I: 9},
}, r8)
}
func TestUnzip(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1, r2 := Unzip2([]Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}})
is.Equal([]string{"a", "b"}, r1)
is.Equal([]int{1, 2}, r2)
}
func TestUnzipBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
r1, r2 := UnzipBy2([]Tuple2[string, int]{{A: "a", B: 1}, {A: "b", B: 2}}, func(i Tuple2[string, int]) (string, int) {
return i.A + i.A, i.B + i.B
})
is.Equal([]string{"aa", "bb"}, r1)
is.Equal([]int{2, 4}, r2)
}
func TestZipByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Test ZipByErr2
t.Run("ZipByErr2", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
a []string
b []int
iteratee func(a string, b int) (string, error)
wantResult []string
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful transformation",
a: []string{"a", "b"},
b: []int{1, 2},
iteratee: func(a string, b int) (string, error) {
return a + "-" + strconv.Itoa(b), nil
},
wantResult: []string{"a-1", "b-2"},
wantErr: false,
expectedCallbackCount: 2,
},
{
name: "error at second element stops iteration",
a: []string{"a", "b"},
b: []int{1, 2},
iteratee: func(a string, b int) (string, error) {
if b == 2 {
return "", errors.New("number 2 is not allowed")
}
return a + "-" + strconv.Itoa(b), nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 2 is not allowed",
expectedCallbackCount: 2,
},
{
name: "error at first element stops iteration immediately",
a: []string{"a", "b"},
b: []int{1, 2},
iteratee: func(a string, b int) (string, error) {
return "", errors.New("first error")
},
wantResult: nil,
wantErr: true,
errMsg: "first error",
expectedCallbackCount: 1,
},
{
name: "empty input slices",
a: []string{},
b: []int{},
iteratee: func(a string, b int) (string, error) {
return a + "-" + strconv.Itoa(b), nil
},
wantResult: []string{},
wantErr: false,
expectedCallbackCount: 0,
},
{
name: "unequal slice lengths - zero value filled",
a: []string{"a", "b", "c"},
b: []int{1},
iteratee: func(a string, b int) (string, error) {
if b == 0 {
return "", errors.New("zero value not allowed")
}
return a + "-" + strconv.Itoa(b), nil
},
wantResult: nil,
wantErr: true,
errMsg: "zero value not allowed",
expectedCallbackCount: 2,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
iteratee := func(a string, b int) (string, error) {
callbackCount++
return tt.iteratee(a, b)
}
result, err := ZipByErr2(tt.a, tt.b, iteratee)
is.Equal(tt.wantResult, result)
if tt.wantErr {
is.Error(err)
if tt.errMsg != "" {
is.ErrorContains(err, tt.errMsg)
}
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test ZipByErr3
t.Run("ZipByErr3", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
a []string
b []int
c []bool
iteratee func(a string, b int, c bool) (string, error)
wantResult []string
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful transformation",
a: []string{"a", "b"},
b: []int{1, 2},
c: []bool{true, false},
iteratee: func(a string, b int, c bool) (string, error) {
return a + "-" + strconv.Itoa(b) + "-" + strconv.FormatBool(c), nil
},
wantResult: []string{"a-1-true", "b-2-false"},
wantErr: false,
expectedCallbackCount: 2,
},
{
name: "error at second element stops iteration",
a: []string{"a", "b"},
b: []int{1, 2},
c: []bool{true, false},
iteratee: func(a string, b int, c bool) (string, error) {
if b == 2 {
return "", errors.New("number 2 is not allowed")
}
return a + "-" + strconv.Itoa(b) + "-" + strconv.FormatBool(c), nil
},
wantResult: nil,
wantErr: true,
errMsg: "number 2 is not allowed",
expectedCallbackCount: 2,
},
{
name: "error at first element",
a: []string{"a", "b"},
b: []int{1, 2},
c: []bool{true, false},
iteratee: func(a string, b int, c bool) (string, error) {
return "", errors.New("first error")
},
wantResult: nil,
wantErr: true,
errMsg: "first error",
expectedCallbackCount: 1,
},
{
name: "empty input slices",
a: []string{},
b: []int{},
c: []bool{},
iteratee: func(a string, b int, c bool) (string, error) {
return a + "-" + strconv.Itoa(b) + "-" + strconv.FormatBool(c), nil
},
wantResult: []string{},
wantErr: false,
expectedCallbackCount: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
iteratee := func(a string, b int, c bool) (string, error) {
callbackCount++
return tt.iteratee(a, b, c)
}
result, err := ZipByErr3(tt.a, tt.b, tt.c, iteratee)
is.Equal(tt.wantResult, result)
if tt.wantErr {
is.Error(err)
if tt.errMsg != "" {
is.ErrorContains(err, tt.errMsg)
}
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test ZipByErr4
t.Run("ZipByErr4", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
a []string
b []int
c []bool
d []float32
iteratee func(a string, b int, c bool, d float32) (string, error)
wantResult []string
wantErr bool
expectedCallbackCount int
}{
{
name: "successful transformation",
a: []string{"a", "b"},
b: []int{1, 2},
c: []bool{true, false},
d: []float32{1.1, 2.2},
iteratee: func(a string, b int, c bool, d float32) (string, error) {
return a + "-" + strconv.Itoa(b), nil
},
wantResult: []string{"a-1", "b-2"},
wantErr: false,
expectedCallbackCount: 2,
},
{
name: "error stops iteration",
a: []string{"a", "b"},
b: []int{1, 2},
c: []bool{true, false},
d: []float32{1.1, 2.2},
iteratee: func(a string, b int, c bool, d float32) (string, error) {
if b == 2 {
return "", errors.New("error")
}
return a + "-" + strconv.Itoa(b), nil
},
wantResult: nil,
wantErr: true,
expectedCallbackCount: 2,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
iteratee := func(a string, b int, c bool, d float32) (string, error) {
callbackCount++
return tt.iteratee(a, b, c, d)
}
result, err := ZipByErr4(tt.a, tt.b, tt.c, tt.d, iteratee)
is.Equal(tt.wantResult, result)
if tt.wantErr {
is.Error(err)
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test ZipByErr5
t.Run("ZipByErr5", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := ZipByErr5(
[]string{"a", "b"},
[]int{1, 2},
[]bool{true, false},
[]float32{1.1, 2.2},
[]float64{0.1, 0.2},
func(a string, b int, c bool, d float32, e float64) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal([]string{"a-1", "b-2"}, result)
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test ZipByErr6
t.Run("ZipByErr6", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := ZipByErr6(
[]string{"a", "b"},
[]int{1, 2},
[]bool{true, false},
[]float32{1.1, 2.2},
[]float64{0.1, 0.2},
[]int8{1, 2},
func(a string, b int, c bool, d float32, e float64, f int8) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal([]string{"a-1", "b-2"}, result)
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test ZipByErr7
t.Run("ZipByErr7", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := ZipByErr7(
[]string{"a", "b"},
[]int{1, 2},
[]bool{true, false},
[]float32{1.1, 2.2},
[]float64{0.1, 0.2},
[]int8{1, 2},
[]int16{3, 4},
func(a string, b int, c bool, d float32, e float64, f int8, g int16) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal([]string{"a-1", "b-2"}, result)
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test ZipByErr8
t.Run("ZipByErr8", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := ZipByErr8(
[]string{"a", "b"},
[]int{1, 2},
[]bool{true, false},
[]float32{1.1, 2.2},
[]float64{0.1, 0.2},
[]int8{1, 2},
[]int16{3, 4},
[]int32{5, 6},
func(a string, b int, c bool, d float32, e float64, f int8, g int16, h int32) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal([]string{"a-1", "b-2"}, result)
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test ZipByErr9
t.Run("ZipByErr9", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := ZipByErr9(
[]string{"a", "b"},
[]int{1, 2},
[]bool{true, false},
[]float32{1.1, 2.2},
[]float64{0.1, 0.2},
[]int8{1, 2},
[]int16{3, 4},
[]int32{5, 6},
[]int64{7, 8},
func(a string, b int, c bool, d float32, e float64, f int8, g int16, h int32, i int64) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Equal([]string{"a-1", "b-2"}, result)
is.NoError(err)
is.Equal(2, callbackCount)
})
}
func TestCrossJoin(t *testing.T) {
t.Parallel()
is := assert.New(t)
listOne := []string{"a", "b", "c"}
listTwo := []int{1, 2, 3}
emptyList := []any{}
mixedList := []any{9.6, 4, "foobar"}
results1 := CrossJoin2(emptyList, listTwo)
is.Empty(results1)
results2 := CrossJoin2(listOne, emptyList)
is.Empty(results2)
results3 := CrossJoin2(emptyList, emptyList)
is.Empty(results3)
results4 := CrossJoin2([]string{"a"}, listTwo)
is.Equal([]Tuple2[string, int]{T2("a", 1), T2("a", 2), T2("a", 3)}, results4)
results5 := CrossJoin2(listOne, []int{1})
is.Equal([]Tuple2[string, int]{T2("a", 1), T2("b", 1), T2("c", 1)}, results5)
results6 := CrossJoin2(listOne, listTwo)
is.Equal([]Tuple2[string, int]{T2("a", 1), T2("a", 2), T2("a", 3), T2("b", 1), T2("b", 2), T2("b", 3), T2("c", 1), T2("c", 2), T2("c", 3)}, results6)
results7 := CrossJoin2(listOne, mixedList)
is.Equal([]Tuple2[string, any]{T2[string, any]("a", 9.6), T2[string, any]("a", 4), T2[string, any]("a", "foobar"), T2[string, any]("b", 9.6), T2[string, any]("b", 4), T2[string, any]("b", "foobar"), T2[string, any]("c", 9.6), T2[string, any]("c", 4), T2[string, any]("c", "foobar")}, results7)
}
func TestCrossJoinBy(t *testing.T) {
t.Parallel()
is := assert.New(t)
listOne := []string{"a", "b", "c"}
listTwo := []int{1, 2, 3}
emptyList := []any{}
mixedList := []any{9.6, 4, "foobar"}
results1 := CrossJoinBy2(emptyList, listTwo, T2[any, int])
is.Empty(results1)
results2 := CrossJoinBy2(listOne, emptyList, T2[string, any])
is.Empty(results2)
results3 := CrossJoinBy2(emptyList, emptyList, T2[any, any])
is.Empty(results3)
results4 := CrossJoinBy2([]string{"a"}, listTwo, T2[string, int])
is.Equal([]Tuple2[string, int]{T2("a", 1), T2("a", 2), T2("a", 3)}, results4)
results5 := CrossJoinBy2(listOne, []int{1}, T2[string, int])
is.Equal([]Tuple2[string, int]{T2("a", 1), T2("b", 1), T2("c", 1)}, results5)
results6 := CrossJoinBy2(listOne, listTwo, T2[string, int])
is.Equal([]Tuple2[string, int]{T2("a", 1), T2("a", 2), T2("a", 3), T2("b", 1), T2("b", 2), T2("b", 3), T2("c", 1), T2("c", 2), T2("c", 3)}, results6)
results7 := CrossJoinBy2(listOne, mixedList, T2[string, any])
is.Equal([]Tuple2[string, any]{T2[string, any]("a", 9.6), T2[string, any]("a", 4), T2[string, any]("a", "foobar"), T2[string, any]("b", 9.6), T2[string, any]("b", 4), T2[string, any]("b", "foobar"), T2[string, any]("c", 9.6), T2[string, any]("c", 4), T2[string, any]("c", "foobar")}, results7)
}
func TestCrossJoinByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Test CrossJoinByErr2
t.Run("CrossJoinByErr2", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
listA []string
listB []int
transform func(a string, b int) (string, error)
wantResult []string
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful transformation",
listA: []string{"a", "b"},
listB: []int{1, 2},
transform: func(a string, b int) (string, error) {
return a + "-" + strconv.Itoa(b), nil
},
wantResult: []string{"a-1", "a-2", "b-1", "b-2"},
wantErr: false,
expectedCallbackCount: 4,
},
{
name: "error stops iteration early",
listA: []string{"a", "b"},
listB: []int{1, 2},
transform: func(a string, b int) (string, error) {
if a == "b" {
return "", errors.New("b not allowed")
}
return a + "-" + strconv.Itoa(b), nil
},
wantResult: nil,
wantErr: true,
errMsg: "b not allowed",
expectedCallbackCount: 3, // a-1, a-2, then b-1 errors
},
{
name: "empty list returns empty result",
listA: []string{},
listB: []int{1, 2},
transform: func(a string, b int) (string, error) { return a + "-" + strconv.Itoa(b), nil },
wantResult: []string{},
wantErr: false,
expectedCallbackCount: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
transform := func(a string, b int) (string, error) {
callbackCount++
return tt.transform(a, b)
}
result, err := CrossJoinByErr2(tt.listA, tt.listB, transform)
is.Equal(tt.wantResult, result)
if tt.wantErr {
is.Error(err)
if tt.errMsg != "" {
is.ErrorContains(err, tt.errMsg)
}
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test CrossJoinByErr3
t.Run("CrossJoinByErr3", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
listA []string
listB []int
listC []bool
transform func(a string, b int, c bool) (string, error)
wantResult []string
wantErr bool
expectedCallbackCount int
}{
{
name: "successful transformation",
listA: []string{"a", "b"},
listB: []int{1, 2},
listC: []bool{true, false},
transform: func(a string, b int, c bool) (string, error) {
return a + "-" + strconv.Itoa(b), nil
},
wantResult: []string{"a-1", "a-1", "a-2", "a-2", "b-1", "b-1", "b-2", "b-2"},
wantErr: false,
expectedCallbackCount: 8,
},
{
name: "error stops iteration early",
listA: []string{"a", "b"},
listB: []int{1, 2},
listC: []bool{true, false},
transform: func(a string, b int, c bool) (string, error) {
if a == "b" && b == 2 {
return "", errors.New("error")
}
return a + "-" + strconv.Itoa(b), nil
},
wantResult: nil,
wantErr: true,
expectedCallbackCount: 7,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
transform := func(a string, b int, c bool) (string, error) {
callbackCount++
return tt.transform(a, b, c)
}
result, err := CrossJoinByErr3(tt.listA, tt.listB, tt.listC, transform)
is.Equal(tt.wantResult, result)
if tt.wantErr {
is.Error(err)
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test CrossJoinByErr4
t.Run("CrossJoinByErr4", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr4(
[]string{"a", "b"},
[]int{1, 2},
[]bool{true},
[]float32{1.1},
func(a string, b int, c bool, d float32) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Len(result, 4)
is.NoError(err)
is.Equal(4, callbackCount)
})
// Test CrossJoinByErr5
t.Run("CrossJoinByErr5", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr5(
[]string{"a", "b"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
func(a string, b int, c bool, d float32, e float64) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Len(result, 2)
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test CrossJoinByErr6
t.Run("CrossJoinByErr6", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr6(
[]string{"a"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
[]int8{3},
func(a string, b int, c bool, d float32, e float64, f int8) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Len(result, 1)
is.NoError(err)
is.Equal(1, callbackCount)
})
// Test CrossJoinByErr7
t.Run("CrossJoinByErr7", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr7(
[]string{"a"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
[]int8{3},
[]int16{4},
func(a string, b int, c bool, d float32, e float64, f int8, g int16) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Len(result, 1)
is.NoError(err)
is.Equal(1, callbackCount)
})
// Test CrossJoinByErr8
t.Run("CrossJoinByErr8", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr8(
[]string{"a"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
[]int8{3},
[]int16{4},
[]int32{5},
func(a string, b int, c bool, d float32, e float64, f int8, g int16, h int32) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Len(result, 1)
is.NoError(err)
is.Equal(1, callbackCount)
})
// Test CrossJoinByErr9
t.Run("CrossJoinByErr9", func(t *testing.T) {
t.Parallel()
is := assert.New(t)
callbackCount := 0
result, err := CrossJoinByErr9(
[]string{"a"},
[]int{1},
[]bool{true},
[]float32{1.1},
[]float64{2.2},
[]int8{3},
[]int16{4},
[]int32{5},
[]int64{6},
func(a string, b int, c bool, d float32, e float64, f int8, g int16, h int32, i int64) (string, error) {
callbackCount++
return a + "-" + strconv.Itoa(b), nil
},
)
is.Len(result, 1)
is.NoError(err)
is.Equal(1, callbackCount)
})
}
func TestUnzipByErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
// Test UnzipByErr2
t.Run("UnzipByErr2", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
items []string
iteratee func(str string) (string, int, error)
wantR1 []string
wantR2 []int
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful transformation",
items: []string{"hello", "world"},
iteratee: func(str string) (string, int, error) {
return strings.ToUpper(str), len(str), nil
},
wantR1: []string{"HELLO", "WORLD"},
wantR2: []int{5, 5},
wantErr: false,
expectedCallbackCount: 2,
},
{
name: "error at second element stops iteration",
items: []string{"hello", "error", "world"},
iteratee: func(str string) (string, int, error) {
if str == "error" {
return "", 0, errors.New("error string not allowed")
}
return strings.ToUpper(str), len(str), nil
},
wantR1: nil,
wantR2: nil,
wantErr: true,
errMsg: "error string not allowed",
expectedCallbackCount: 2,
},
{
name: "error at first element stops immediately",
items: []string{"error", "hello", "world"},
iteratee: func(str string) (string, int, error) {
return "", 0, errors.New("first error")
},
wantR1: nil,
wantR2: nil,
wantErr: true,
errMsg: "first error",
expectedCallbackCount: 1,
},
{
name: "empty input slices",
items: []string{},
iteratee: func(str string) (string, int, error) {
return strings.ToUpper(str), len(str), nil
},
wantR1: []string{},
wantR2: []int{},
wantErr: false,
expectedCallbackCount: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
iteratee := func(str string) (string, int, error) {
callbackCount++
return tt.iteratee(str)
}
r1, r2, err := UnzipByErr2(tt.items, iteratee)
is.Equal(tt.wantR1, r1)
is.Equal(tt.wantR2, r2)
if tt.wantErr {
is.Error(err)
if tt.errMsg != "" {
is.ErrorContains(err, tt.errMsg)
}
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test UnzipByErr3
t.Run("UnzipByErr3", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
items []string
iteratee func(str string) (string, int, bool, error)
wantR1 []string
wantR2 []int
wantR3 []bool
wantErr bool
errMsg string
expectedCallbackCount int
}{
{
name: "successful transformation",
items: []string{"hello", "world"},
iteratee: func(str string) (string, int, bool, error) {
return strings.ToUpper(str), len(str), len(str) > 4, nil
},
wantR1: []string{"HELLO", "WORLD"},
wantR2: []int{5, 5},
wantR3: []bool{true, true},
wantErr: false,
expectedCallbackCount: 2,
},
{
name: "error at second element stops iteration",
items: []string{"hello", "error", "world"},
iteratee: func(str string) (string, int, bool, error) {
if str == "error" {
return "", 0, false, errors.New("error string not allowed")
}
return strings.ToUpper(str), len(str), len(str) > 4, nil
},
wantR1: nil,
wantR2: nil,
wantR3: nil,
wantErr: true,
errMsg: "error string not allowed",
expectedCallbackCount: 2,
},
{
name: "error at first element",
items: []string{"error", "hello", "world"},
iteratee: func(str string) (string, int, bool, error) {
return "", 0, false, errors.New("first error")
},
wantR1: nil,
wantR2: nil,
wantR3: nil,
wantErr: true,
errMsg: "first error",
expectedCallbackCount: 1,
},
{
name: "empty input slices",
items: []string{},
iteratee: func(str string) (string, int, bool, error) {
return strings.ToUpper(str), len(str), len(str) > 4, nil
},
wantR1: []string{},
wantR2: []int{},
wantR3: []bool{},
wantErr: false,
expectedCallbackCount: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
iteratee := func(str string) (string, int, bool, error) {
callbackCount++
return tt.iteratee(str)
}
r1, r2, r3, err := UnzipByErr3(tt.items, iteratee)
is.Equal(tt.wantR1, r1)
is.Equal(tt.wantR2, r2)
is.Equal(tt.wantR3, r3)
if tt.wantErr {
is.Error(err)
if tt.errMsg != "" {
is.ErrorContains(err, tt.errMsg)
}
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test UnzipByErr4
t.Run("UnzipByErr4", func(t *testing.T) {
t.Parallel()
tests := []struct {
name string
items []string
iteratee func(str string) (string, int, bool, float32, error)
wantErr bool
expectedCallbackCount int
}{
{
name: "successful transformation",
items: []string{"hello", "world"},
iteratee: func(str string) (string, int, bool, float32, error) {
return strings.ToUpper(str), len(str), len(str) > 4, float32(len(str)), nil
},
wantErr: false,
expectedCallbackCount: 2,
},
{
name: "error at second element stops iteration",
items: []string{"hello", "error", "world"},
iteratee: func(str string) (string, int, bool, float32, error) {
if str == "error" {
return "", 0, false, 0, errors.New("error string not allowed")
}
return strings.ToUpper(str), len(str), len(str) > 4, float32(len(str)), nil
},
wantErr: true,
expectedCallbackCount: 2,
},
{
name: "empty input slices",
items: []string{},
iteratee: func(str string) (string, int, bool, float32, error) {
return strings.ToUpper(str), len(str), len(str) > 4, float32(len(str)), nil
},
wantErr: false,
expectedCallbackCount: 0,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
callbackCount := 0
iteratee := func(str string) (string, int, bool, float32, error) {
callbackCount++
return tt.iteratee(str)
}
_, _, _, _, err := UnzipByErr4(tt.items, iteratee)
if tt.wantErr {
is.Error(err)
} else {
is.NoError(err)
}
is.Equal(tt.expectedCallbackCount, callbackCount)
})
}
})
// Test UnzipByErr5
t.Run("UnzipByErr5", func(t *testing.T) {
t.Parallel()
callbackCount := 0
_, _, _, _, _, err := UnzipByErr5([]string{"hello", "world"}, func(str string) (string, int, bool, float32, float64, error) {
callbackCount++
return strings.ToUpper(str), len(str), len(str) > 4, float32(len(str)), float64(len(str)), nil
})
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test UnzipByErr6
t.Run("UnzipByErr6", func(t *testing.T) {
t.Parallel()
callbackCount := 0
_, _, _, _, _, _, err := UnzipByErr6([]string{"hello", "world"}, func(str string) (string, int, bool, float32, float64, int8, error) {
callbackCount++
return strings.ToUpper(str), len(str), len(str) > 4, float32(len(str)), float64(len(str)), int8(len(str)), nil
})
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test UnzipByErr7
t.Run("UnzipByErr7", func(t *testing.T) {
t.Parallel()
callbackCount := 0
_, _, _, _, _, _, _, err := UnzipByErr7([]string{"hello", "world"}, func(str string) (string, int, bool, float32, float64, int8, int16, error) {
callbackCount++
return strings.ToUpper(str), len(str), len(str) > 4, float32(len(str)), float64(len(str)), int8(len(str)), int16(len(str)), nil
})
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test UnzipByErr8
t.Run("UnzipByErr8", func(t *testing.T) {
t.Parallel()
callbackCount := 0
_, _, _, _, _, _, _, _, err := UnzipByErr8([]string{"hello", "world"}, func(str string) (string, int, bool, float32, float64, int8, int16, int32, error) {
callbackCount++
return strings.ToUpper(str), len(str), len(str) > 4, float32(len(str)), float64(len(str)), int8(len(str)), int16(len(str)), int32(len(str)), nil
})
is.NoError(err)
is.Equal(2, callbackCount)
})
// Test UnzipByErr9
t.Run("UnzipByErr9", func(t *testing.T) {
t.Parallel()
callbackCount := 0
_, _, _, _, _, _, _, _, _, err := UnzipByErr9([]string{"hello", "world"}, func(str string) (string, int, bool, float32, float64, int8, int16, int32, int64, error) {
callbackCount++
return strings.ToUpper(str), len(str), len(str) > 4, float32(len(str)), float64(len(str)), int8(len(str)), int16(len(str)), int32(len(str)), int64(len(str)), nil
})
is.NoError(err)
is.Equal(2, callbackCount)
})
}
================================================
FILE: type_manipulation.go
================================================
package lo
import "reflect"
// IsNil checks if a value is nil or if it's a reference type with a nil underlying value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func IsNil(x any) bool {
if x == nil {
return true
}
v := reflect.ValueOf(x)
switch v.Kind() { //nolint:exhaustive
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
return v.IsNil()
default:
return false
}
}
// IsNotNil checks if a value is not nil or if it's not a reference type with a nil underlying value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func IsNotNil(x any) bool {
return !IsNil(x)
}
// ToPtr returns a pointer copy of value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func ToPtr[T any](x T) *T {
return &x
}
// Nil returns a nil pointer of type.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func Nil[T any]() *T {
return nil
}
// EmptyableToPtr returns a pointer copy of value if it's nonzero.
// Otherwise, returns nil pointer.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func EmptyableToPtr[T any](x T) *T {
// 🤮
isZero := reflect.ValueOf(&x).Elem().IsZero()
if isZero {
return nil
}
return &x
}
// FromPtr returns the pointer value or empty.
// Play: https://go.dev/play/p/mhD9CwO3X0m
func FromPtr[T any](x *T) T {
if x == nil {
return Empty[T]()
}
return *x
}
// FromPtrOr returns the pointer value or the fallback value.
// Play: https://go.dev/play/p/mhD9CwO3X0m
func FromPtrOr[T any](x *T, fallback T) T {
if x == nil {
return fallback
}
return *x
}
// ToSlicePtr returns a slice of pointers to each value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func ToSlicePtr[T any](collection []T) []*T {
result := make([]*T, len(collection))
for i := range collection {
result[i] = &collection[i]
}
return result
}
// FromSlicePtr returns a slice with the pointer values.
// Returns a zero value in case of a nil pointer element.
// Play: https://go.dev/play/p/lbunFvzlUDX
func FromSlicePtr[T any](collection []*T) []T {
result := make([]T, len(collection))
for i := range collection {
if collection[i] != nil {
result[i] = *collection[i]
}
}
return result
}
// FromSlicePtrOr returns a slice with the pointer values or the fallback value.
// Play: https://go.dev/play/p/lbunFvzlUDX
func FromSlicePtrOr[T any](collection []*T, fallback T) []T {
result := make([]T, len(collection))
for i := range collection {
if collection[i] != nil {
result[i] = *collection[i]
} else {
result[i] = fallback
}
}
return result
}
// ToAnySlice returns a slice with all elements mapped to `any` type.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func ToAnySlice[T any](collection []T) []any {
result := make([]any, len(collection))
for i := range collection {
result[i] = collection[i]
}
return result
}
// FromAnySlice returns a slice with all elements mapped to a type.
// Returns false in case of type conversion failure.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func FromAnySlice[T any](in []any) ([]T, bool) {
out := make([]T, len(in))
for i := range in {
t, ok := in[i].(T)
if !ok {
return []T{}, false
}
out[i] = t
}
return out, true
}
// Empty returns the zero value (https://go.dev/ref/spec#The_zero_value).
// Play: https://go.dev/play/p/P2sD0PMXw4F
func Empty[T any]() T {
var zero T
return zero
}
// IsEmpty returns true if argument is a zero value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func IsEmpty[T comparable](v T) bool {
var zero T
return zero == v
}
// IsNotEmpty returns true if argument is not a zero value.
// Play: https://go.dev/play/p/P2sD0PMXw4F
func IsNotEmpty[T comparable](v T) bool {
var zero T
return zero != v
}
// Coalesce returns the first non-empty arguments. Arguments must be comparable.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func Coalesce[T comparable](values ...T) (T, bool) {
var zero T
for i := range values {
if values[i] != zero {
return values[i], true
}
}
return zero, false
}
// CoalesceOrEmpty returns the first non-empty arguments. Arguments must be comparable.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceOrEmpty[T comparable](v ...T) T {
result, _ := Coalesce(v...)
return result
}
// CoalesceSlice returns the first non-zero slice.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceSlice[T any](v ...[]T) ([]T, bool) {
for i := range v {
if len(v[i]) > 0 {
return v[i], true
}
}
return []T{}, false
}
// CoalesceSliceOrEmpty returns the first non-zero slice.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceSliceOrEmpty[T any](v ...[]T) []T {
for i := range v {
if len(v[i]) > 0 {
return v[i]
}
}
return []T{}
}
// CoalesceMap returns the first non-zero map.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceMap[K comparable, V any](v ...map[K]V) (map[K]V, bool) {
for i := range v {
if len(v[i]) > 0 {
return v[i], true
}
}
return map[K]V{}, false
}
// CoalesceMapOrEmpty returns the first non-zero map.
// Play: https://go.dev/play/p/Gyo9otyvFHH
func CoalesceMapOrEmpty[K comparable, V any](v ...map[K]V) map[K]V {
for i := range v {
if len(v[i]) > 0 {
return v[i]
}
}
return map[K]V{}
}
================================================
FILE: type_manipulation_test.go
================================================
package lo
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsNil(t *testing.T) {
t.Parallel()
is := assert.New(t)
var x int
is.False(IsNil(x))
var k struct{}
is.False(IsNil(k))
var s *string
is.True(IsNil(s))
var i *int
is.True(IsNil(i))
var b *bool
is.True(IsNil(b))
var ifaceWithNilValue any = (*string)(nil) //nolint:staticcheck
is.True(IsNil(ifaceWithNilValue))
is.False(ifaceWithNilValue == nil) //nolint:staticcheck,testifylint
}
func TestIsNotNil(t *testing.T) {
t.Parallel()
is := assert.New(t)
var x int
is.True(IsNotNil(x))
var k struct{}
is.True(IsNotNil(k))
var s *string
is.False(IsNotNil(s))
var i *int
is.False(IsNotNil(i))
var b *bool
is.False(IsNotNil(b))
var ifaceWithNilValue any = (*string)(nil) //nolint:staticcheck
is.False(IsNotNil(ifaceWithNilValue))
is.True(ifaceWithNilValue != nil) //nolint:staticcheck,testifylint
}
func TestToPtr(t *testing.T) {
t.Parallel()
is := assert.New(t)
result1 := ToPtr([]int{1, 2})
is.Equal([]int{1, 2}, *result1)
}
func TestNil(t *testing.T) {
t.Parallel()
is := assert.New(t)
nilFloat64 := Nil[float64]()
var expNilFloat64 *float64
nilString := Nil[string]()
var expNilString *string
is.Equal(expNilFloat64, nilFloat64)
is.Nil(nilFloat64)
is.NotEqual(nil, nilFloat64) //nolint:testifylint
is.Equal(expNilString, nilString)
is.Nil(nilString)
is.NotEqual(nil, nilString) //nolint:testifylint
is.NotEqual(nilString, nilFloat64)
}
func TestEmptyableToPtr(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.Nil(EmptyableToPtr(0))
is.Nil(EmptyableToPtr(""))
is.Nil(EmptyableToPtr[[]int](nil))
is.Nil(EmptyableToPtr[map[int]int](nil))
is.Nil(EmptyableToPtr[error](nil))
is.Equal(42, *EmptyableToPtr(42))
is.Equal("nonempty", *EmptyableToPtr("nonempty"))
is.Empty(*EmptyableToPtr([]int{}))
is.Equal([]int{1, 2}, *EmptyableToPtr([]int{1, 2}))
is.Empty(*EmptyableToPtr(map[int]int{}))
is.Equal(assert.AnError, *EmptyableToPtr(assert.AnError))
}
func TestFromPtr(t *testing.T) {
t.Parallel()
is := assert.New(t)
str1 := "foo"
ptr := &str1
is.Equal("foo", FromPtr(ptr))
is.Empty(FromPtr[string](nil))
is.Zero(FromPtr[int](nil))
is.Nil(FromPtr[*string](nil))
is.Equal(ptr, FromPtr(&ptr))
}
func TestFromPtrOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
const fallbackStr = "fallback"
str := "foo"
ptrStr := &str
const fallbackInt = -1
i := 9
ptrInt := &i
is.Equal(str, FromPtrOr(ptrStr, fallbackStr))
is.Equal(fallbackStr, FromPtrOr(nil, fallbackStr))
is.Equal(i, FromPtrOr(ptrInt, fallbackInt))
is.Equal(fallbackInt, FromPtrOr(nil, fallbackInt))
}
func TestToSlicePtr(t *testing.T) {
t.Parallel()
is := assert.New(t)
str1 := "foo"
str2 := "bar"
result1 := ToSlicePtr([]string{str1, str2})
is.Equal([]*string{&str1, &str2}, result1)
}
func TestFromSlicePtr(t *testing.T) {
t.Parallel()
is := assert.New(t)
str1 := "foo"
str2 := "bar"
result1 := FromSlicePtr([]*string{&str1, &str2, nil})
is.Equal([]string{str1, str2, ""}, result1)
}
func TestFromSlicePtrOr(t *testing.T) {
t.Parallel()
is := assert.New(t)
str1 := "foo"
str2 := "bar"
result1 := FromSlicePtrOr([]*string{&str1, &str2, nil}, "fallback")
is.Equal([]string{str1, str2, "fallback"}, result1)
}
func TestToAnySlice(t *testing.T) {
t.Parallel()
is := assert.New(t)
in1 := []int{0, 1, 2, 3}
in2 := []int{}
out1 := ToAnySlice(in1)
out2 := ToAnySlice(in2)
is.Equal([]any{0, 1, 2, 3}, out1)
is.Empty(out2)
}
func TestFromAnySlice(t *testing.T) {
t.Parallel()
is := assert.New(t)
is.NotPanics(func() {
out1, ok1 := FromAnySlice[string]([]any{"foobar", 42})
out2, ok2 := FromAnySlice[string]([]any{"foobar", "42"})
is.Empty(out1)
is.False(ok1)
is.Equal([]string{"foobar", "42"}, out2)
is.True(ok2)
})
}
func TestEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
type test struct{}
is.Empty(Empty[string]())
is.Empty(Empty[int64]())
is.Empty(Empty[test]())
is.Empty(Empty[chan string]())
is.Nil(Empty[[]int]())
is.Nil(Empty[map[string]int]())
}
func TestIsEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
type test struct {
foobar string
}
is.True(IsEmpty(""))
is.False(IsEmpty("foo"))
is.True(IsEmpty[int64](0))
is.False(IsEmpty[int64](42))
is.True(IsEmpty(test{foobar: ""}))
is.False(IsEmpty(test{foobar: "foo"}))
}
func TestIsNotEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
type test struct {
foobar string
}
is.False(IsNotEmpty(""))
is.True(IsNotEmpty("foo"))
is.False(IsNotEmpty[int64](0))
is.True(IsNotEmpty[int64](42))
is.False(IsNotEmpty(test{foobar: ""}))
is.True(IsNotEmpty(test{foobar: "foo"}))
}
func TestCoalesce(t *testing.T) {
t.Parallel()
is := assert.New(t)
newStr := func(v string) *string { return &v }
var nilStr *string
str1 := newStr("str1")
str2 := newStr("str2")
type structType struct {
field1 int
field2 float64
}
var zeroStruct structType
struct1 := structType{1, 1.0}
struct2 := structType{2, 2.0}
result1, ok1 := Coalesce[int]()
result2, ok2 := Coalesce(3)
result3, ok3 := Coalesce(nil, nilStr)
result4, ok4 := Coalesce(nilStr, str1)
result5, ok5 := Coalesce(nilStr, str1, str2)
result6, ok6 := Coalesce(str1, str2, nilStr)
result7, ok7 := Coalesce(0, 1, 2, 3)
result8, ok8 := Coalesce(zeroStruct)
result9, ok9 := Coalesce(zeroStruct, struct1)
result10, ok10 := Coalesce(zeroStruct, struct1, struct2)
is.Zero(result1)
is.False(ok1)
is.Equal(3, result2)
is.True(ok2)
is.Nil(result3)
is.False(ok3)
is.Equal(str1, result4)
is.True(ok4)
is.Equal(str1, result5)
is.True(ok5)
is.Equal(str1, result6)
is.True(ok6)
is.Equal(1, result7)
is.True(ok7)
is.Zero(result8)
is.False(ok8)
is.Equal(struct1, result9)
is.True(ok9)
is.Equal(struct1, result10)
is.True(ok10)
}
func TestCoalesceOrEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
newStr := func(v string) *string { return &v }
var nilStr *string
str1 := newStr("str1")
str2 := newStr("str2")
type structType struct {
field1 int
field2 float64
}
var zeroStruct structType
struct1 := structType{1, 1.0}
struct2 := structType{2, 2.0}
result1 := CoalesceOrEmpty[int]()
result2 := CoalesceOrEmpty(3)
result3 := CoalesceOrEmpty(nil, nilStr)
result4 := CoalesceOrEmpty(nilStr, str1)
result5 := CoalesceOrEmpty(nilStr, str1, str2)
result6 := CoalesceOrEmpty(str1, str2, nilStr)
result7 := CoalesceOrEmpty(0, 1, 2, 3)
result8 := CoalesceOrEmpty(zeroStruct)
result9 := CoalesceOrEmpty(zeroStruct, struct1)
result10 := CoalesceOrEmpty(zeroStruct, struct1, struct2)
is.Zero(result1)
is.Equal(3, result2)
is.Nil(result3)
is.Equal(str1, result4)
is.Equal(str1, result5)
is.Equal(str1, result6)
is.Equal(1, result7)
is.Zero(result8)
is.Equal(struct1, result9)
is.Equal(struct1, result10)
}
func TestCoalesceSlice(t *testing.T) {
t.Parallel()
is := assert.New(t)
var sliceNil []int
slice0 := []int{}
slice1 := []int{1}
slice2 := []int{1, 2}
result1, ok1 := CoalesceSlice[int]()
result2, ok2 := CoalesceSlice[int](nil)
result3, ok3 := CoalesceSlice(sliceNil)
result4, ok4 := CoalesceSlice(slice0)
result5, ok5 := CoalesceSlice(nil, sliceNil, slice0)
result6, ok6 := CoalesceSlice(slice2)
result7, ok7 := CoalesceSlice(slice1)
result8, ok8 := CoalesceSlice(slice1, slice2)
result9, ok9 := CoalesceSlice(slice2, slice1)
result10, ok10 := CoalesceSlice(sliceNil, slice0, slice1, slice2)
is.NotNil(result1)
is.Empty(result1)
is.False(ok1)
is.NotNil(result2)
is.Empty(result2)
is.False(ok2)
is.NotNil(result3)
is.Empty(result3)
is.False(ok3)
is.NotNil(result4)
is.Empty(result4)
is.False(ok4)
is.NotNil(result5)
is.Empty(result5)
is.False(ok5)
is.NotNil(result6)
is.Equal(slice2, result6)
is.True(ok6)
is.NotNil(result7)
is.Equal(slice1, result7)
is.True(ok7)
is.NotNil(result8)
is.Equal(slice1, result8)
is.True(ok8)
is.NotNil(result9)
is.Equal(slice2, result9)
is.True(ok9)
is.NotNil(result10)
is.Equal(slice1, result10)
is.True(ok10)
}
func TestCoalesceSliceOrEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
var sliceNil []int
slice0 := []int{}
slice1 := []int{1}
slice2 := []int{1, 2}
result1 := CoalesceSliceOrEmpty[int]()
result2 := CoalesceSliceOrEmpty[int](nil)
result3 := CoalesceSliceOrEmpty(sliceNil)
result4 := CoalesceSliceOrEmpty(slice0)
result5 := CoalesceSliceOrEmpty(nil, sliceNil, slice0)
result6 := CoalesceSliceOrEmpty(slice2)
result7 := CoalesceSliceOrEmpty(slice1)
result8 := CoalesceSliceOrEmpty(slice1, slice2)
result9 := CoalesceSliceOrEmpty(slice2, slice1)
result10 := CoalesceSliceOrEmpty(sliceNil, slice0, slice1, slice2)
is.NotNil(result1)
is.Empty(result1)
is.NotNil(result2)
is.Empty(result2)
is.NotNil(result3)
is.Empty(result3)
is.NotNil(result4)
is.Empty(result4)
is.NotNil(result5)
is.Empty(result5)
is.NotNil(result6)
is.Equal(slice2, result6)
is.NotNil(result7)
is.Equal(slice1, result7)
is.NotNil(result8)
is.Equal(slice1, result8)
is.NotNil(result9)
is.Equal(slice2, result9)
is.NotNil(result10)
is.Equal(slice1, result10)
}
func TestCoalesceMap(t *testing.T) {
t.Parallel()
is := assert.New(t)
var mapNil map[int]int
map0 := map[int]int{}
map1 := map[int]int{1: 1}
map2 := map[int]int{1: 1, 2: 2}
result1, ok1 := CoalesceMap[int, int]()
result2, ok2 := CoalesceMap[int, int](nil)
result3, ok3 := CoalesceMap(mapNil)
result4, ok4 := CoalesceMap(map0)
result5, ok5 := CoalesceMap(nil, mapNil, map0)
result6, ok6 := CoalesceMap(map2)
result7, ok7 := CoalesceMap(map1)
result8, ok8 := CoalesceMap(map1, map2)
result9, ok9 := CoalesceMap(map2, map1)
result10, ok10 := CoalesceMap(mapNil, map0, map1, map2)
is.NotNil(result1)
is.Empty(result1)
is.False(ok1)
is.NotNil(result2)
is.Empty(result2)
is.False(ok2)
is.NotNil(result3)
is.Empty(result3)
is.False(ok3)
is.NotNil(result4)
is.Empty(result4)
is.False(ok4)
is.NotNil(result5)
is.Empty(result5)
is.False(ok5)
is.NotNil(result6)
is.Equal(map2, result6)
is.True(ok6)
is.NotNil(result7)
is.Equal(map1, result7)
is.True(ok7)
is.NotNil(result8)
is.Equal(map1, result8)
is.True(ok8)
is.NotNil(result9)
is.Equal(map2, result9)
is.True(ok9)
is.NotNil(result10)
is.Equal(map1, result10)
is.True(ok10)
}
func TestCoalesceMapOrEmpty(t *testing.T) {
t.Parallel()
is := assert.New(t)
var mapNil map[int]int
map0 := map[int]int{}
map1 := map[int]int{1: 1}
map2 := map[int]int{1: 1, 2: 2}
result1 := CoalesceMapOrEmpty[int, int]()
result2 := CoalesceMapOrEmpty[int, int](nil)
result3 := CoalesceMapOrEmpty(mapNil)
result4 := CoalesceMapOrEmpty(map0)
result5 := CoalesceMapOrEmpty(nil, mapNil, map0)
result6 := CoalesceMapOrEmpty(map2)
result7 := CoalesceMapOrEmpty(map1)
result8 := CoalesceMapOrEmpty(map1, map2)
result9 := CoalesceMapOrEmpty(map2, map1)
result10 := CoalesceMapOrEmpty(mapNil, map0, map1, map2)
is.NotNil(result1)
is.Empty(result1)
is.NotNil(result2)
is.Empty(result2)
is.NotNil(result3)
is.Empty(result3)
is.NotNil(result4)
is.Empty(result4)
is.NotNil(result5)
is.Empty(result5)
is.NotNil(result6)
is.Equal(map2, result6)
is.NotNil(result7)
is.Equal(map1, result7)
is.NotNil(result8)
is.Equal(map1, result8)
is.NotNil(result9)
is.Equal(map2, result9)
is.NotNil(result10)
is.Equal(map1, result10)
}
================================================
FILE: types.go
================================================
package lo
// Entry defines a key/value pairs.
type Entry[K comparable, V any] struct {
Key K
Value V
}
// Tuple2 is a group of 2 elements (pair).
type Tuple2[A, B any] struct {
A A
B B
}
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/yrtn7QJTmL_E
func (t Tuple2[A, B]) Unpack() (A, B) {
return t.A, t.B
}
// Tuple3 is a group of 3 elements.
type Tuple3[A, B, C any] struct {
A A
B B
C C
}
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/yrtn7QJTmL_E
func (t Tuple3[A, B, C]) Unpack() (A, B, C) {
return t.A, t.B, t.C
}
// Tuple4 is a group of 4 elements.
type Tuple4[A, B, C, D any] struct {
A A
B B
C C
D D
}
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/yrtn7QJTmL_E
func (t Tuple4[A, B, C, D]) Unpack() (A, B, C, D) {
return t.A, t.B, t.C, t.D
}
// Tuple5 is a group of 5 elements.
type Tuple5[A, B, C, D, E any] struct {
A A
B B
C C
D D
E E
}
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/7J4KrtgtK3M
func (t Tuple5[A, B, C, D, E]) Unpack() (A, B, C, D, E) {
return t.A, t.B, t.C, t.D, t.E
}
// Tuple6 is a group of 6 elements.
type Tuple6[A, B, C, D, E, F any] struct {
A A
B B
C C
D D
E E
F F
}
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/7J4KrtgtK3M
func (t Tuple6[A, B, C, D, E, F]) Unpack() (A, B, C, D, E, F) {
return t.A, t.B, t.C, t.D, t.E, t.F
}
// Tuple7 is a group of 7 elements.
type Tuple7[A, B, C, D, E, F, G any] struct {
A A
B B
C C
D D
E E
F F
G G
}
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/Ow9Zgf_zeiA
func (t Tuple7[A, B, C, D, E, F, G]) Unpack() (A, B, C, D, E, F, G) {
return t.A, t.B, t.C, t.D, t.E, t.F, t.G
}
// Tuple8 is a group of 8 elements.
type Tuple8[A, B, C, D, E, F, G, H any] struct {
A A
B B
C C
D D
E E
F F
G G
H H
}
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/Ow9Zgf_zeiA
func (t Tuple8[A, B, C, D, E, F, G, H]) Unpack() (A, B, C, D, E, F, G, H) {
return t.A, t.B, t.C, t.D, t.E, t.F, t.G, t.H
}
// Tuple9 is a group of 9 elements.
type Tuple9[A, B, C, D, E, F, G, H, I any] struct {
A A
B B
C C
D D
E E
F F
G G
H H
I I
}
// Unpack returns values contained in a tuple.
// Play: https://go.dev/play/p/Ow9Zgf_zeiA
func (t Tuple9[A, B, C, D, E, F, G, H, I]) Unpack() (A, B, C, D, E, F, G, H, I) {
return t.A, t.B, t.C, t.D, t.E, t.F, t.G, t.H, t.I
}