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... [![tag](https://img.shields.io/github/tag/samber/lo.svg)](https://github.com/samber/lo/releases) ![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.18-%23007d9c) [![GoDoc](https://godoc.org/github.com/samber/lo?status.svg)](https://pkg.go.dev/github.com/samber/lo) ![Build Status](https://github.com/samber/lo/actions/workflows/test.yml/badge.svg) [![Go report](https://goreportcard.com/badge/github.com/samber/lo)](https://goreportcard.com/report/github.com/samber/lo) [![Coverage](https://img.shields.io/codecov/c/github/samber/lo)](https://codecov.io/gh/samber/lo) [![Contributors](https://img.shields.io/github/contributors/samber/lo)](https://github.com/samber/lo/graphs/contributors) [![License](https://img.shields.io/github/license/samber/lo)](./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 ----
💖 Sponsored by:
dbos
DBOS - Durable workflow orchestration library for Go
---- **Why this name?** I wanted a **short name**, similar to "Lodash", and no Go package uses this name. ![lo](docs/static/img/logo-full.png) ## 🚀 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 ![Contributors](https://contrib.rocks/image?repo=samber/lo) ## 💫 Show your support Give a ⭐️ if this project helped you! [![GitHub Sponsors](https://img.shields.io/github/sponsors/samber?style=for-the-badge)](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. ![logo](../static/img/functional-gopher.png) **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} {/*

{helper.name}

*/}
{helper.subCategory} {helper.category}
{sourceRef && ( 🧩 Source )} {godocUrl && ( 📚 GoDoc )} {helper.playUrl && ( 🎮 Try on Go Playground )}
{renderedNodes}
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 ( ); } ================================================ 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 (

Community

These are places where you can ask questions and find your soulmate (no promises).
"If you want to go fast, go alone. If you want to go far, go together."

Report bugs or suggest improvements

Open new issue

You like this project?

Start contributing!

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 (
{title}

{description}

); } function HomepageFeatures(): ReactNode { return (
{FeatureList.map((props, idx) => ( ))}
); } function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); return (
{siteConfig.title}

{siteConfig.tagline}

Intro
Getting started - 5min ⏱️
); } 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 && (
💖 Sponsored by
{sponsors.map((sponsor) => (
{sponsor.name}
{sponsor.title}
))}
)}
); } ================================================ 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 }