├── types.go ├── pointer_examples_test.go ├── other_examples_test.go ├── pointer.go ├── pool_examples_test.go ├── chan_examples_test.go ├── math_examples_test.go ├── go.mod ├── json.go ├── map_examples_test.go ├── pool.go ├── context.go ├── errors_examples_test.go ├── chan.go ├── errors.go ├── chan_test.go ├── string.go ├── slice_iter_test.go ├── .github └── workflows │ └── tests.yml ├── LICENSE ├── pointer_test.go ├── pool_test.go ├── json_test.go ├── context_test.go ├── other.go ├── math.go ├── slice_examples_test.go ├── README.md ├── string_test.go ├── errors_test.go ├── null.go ├── .gitignore ├── math_test.go ├── other_test.go ├── map.go ├── go.sum ├── null_test.go ├── map_bench_test.go ├── slice.go ├── map_test.go ├── slice_bench_test.go └── slice_test.go /types.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import "golang.org/x/exp/constraints" 4 | 5 | type number interface { 6 | constraints.Float | constraints.Signed | constraints.Unsigned 7 | } 8 | 9 | type builtin interface { 10 | number | ~bool | ~string 11 | } 12 | 13 | const zero = 0 14 | -------------------------------------------------------------------------------- /pointer_examples_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kazhuravlev/just" 6 | ) 7 | 8 | func ExamplePointerUnwrapDefault() { 9 | someFuncThatReturnsNil := func() *int { return nil } 10 | 11 | value := just.PointerUnwrapDefault(someFuncThatReturnsNil(), 42) 12 | fmt.Println(value) 13 | // Output: 42 14 | } 15 | -------------------------------------------------------------------------------- /other_examples_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/kazhuravlev/just" 7 | "io" 8 | ) 9 | 10 | func ExampleBool() { 11 | fmt.Println(just.Bool(0), just.Bool(1), just.Bool(-1)) 12 | // Output: false true false 13 | } 14 | 15 | func ExampleMust() { 16 | val := just.Must(io.ReadAll(bytes.NewBufferString("this is body!"))) 17 | fmt.Println(string(val)) 18 | // Output: 19 | // this is body! 20 | } 21 | -------------------------------------------------------------------------------- /pointer.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | // Pointer returns a pointer to `v`. 4 | func Pointer[T any](v T) *T { 5 | return &v 6 | } 7 | 8 | // PointerUnwrap returns the value from the pointer. 9 | func PointerUnwrap[T any](in *T) T { 10 | return *in 11 | } 12 | 13 | // PointerUnwrapDefault returns a value from pointer or defaultVal when input 14 | // is an empty pointer. 15 | func PointerUnwrapDefault[T builtin | any](in *T, defaultVal T) T { 16 | if in == nil { 17 | return defaultVal 18 | } 19 | 20 | return *in 21 | } 22 | -------------------------------------------------------------------------------- /pool_examples_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/kazhuravlev/just" 7 | ) 8 | 9 | func ExampleNewPool() { 10 | p := just.NewPool( 11 | func() *bytes.Buffer { return bytes.NewBuffer(nil) }, 12 | func(buf *bytes.Buffer) { fmt.Println(buf.String()); buf.Reset() }, 13 | ) 14 | 15 | buf := p.Get() 16 | buf.WriteString("Some data") 17 | // This example showing that Pool will call reset callback 18 | // on each Pool.Put call. 19 | p.Put(buf) 20 | // Output: Some data 21 | } 22 | -------------------------------------------------------------------------------- /chan_examples_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kazhuravlev/just" 6 | "strconv" 7 | ) 8 | 9 | func ExampleChanPut() { 10 | ch := make(chan int, 4) 11 | just.ChanPut(ch, []int{1, 2, 3, 4}) 12 | 13 | resultSlice := just.ChanReadN(ch, 4) 14 | fmt.Println(resultSlice) 15 | // Output: [1 2 3 4] 16 | } 17 | 18 | func ExampleChanAdapt() { 19 | intCh := make(chan int, 4) 20 | just.ChanPut(intCh, []int{1, 2, 3, 4}) 21 | 22 | strCh := just.ChanAdapt(intCh, strconv.Itoa) 23 | fmt.Println(just.ChanReadN(strCh, 4)) 24 | // Output: [1 2 3 4] 25 | } 26 | -------------------------------------------------------------------------------- /math_examples_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kazhuravlev/just" 6 | ) 7 | 8 | func ExampleMaxOr() { 9 | values := []int{10, 20, 30} 10 | maxValue := just.MaxOr(999, values...) 11 | // This will print 30, because you have non-empty slice. 12 | fmt.Println(maxValue) 13 | // Output: 30 14 | } 15 | 16 | func ExampleMin() { 17 | values := []int{10, 20, 30} 18 | minValue := just.Min(values...) 19 | fmt.Println(minValue) 20 | // Output: 10 21 | } 22 | 23 | func ExampleSum() { 24 | values := []int{10, 20, 30} 25 | minValue := just.Sum(values...) 26 | fmt.Println(minValue) 27 | // Output: 60 28 | } 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kazhuravlev/just 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/goccy/go-yaml v1.12.0 7 | github.com/stretchr/testify v1.8.0 8 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/fatih/color v1.17.0 // indirect 14 | github.com/mattn/go-colorable v0.1.13 // indirect 15 | github.com/mattn/go-isatty v0.0.20 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | golang.org/x/sys v0.24.0 // indirect 18 | golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // JsonParseType parse byte slice to specific type. 10 | func JsonParseType[T any](bb []byte) (*T, error) { 11 | var target T 12 | if err := json.Unmarshal(bb, &target); err != nil { 13 | return nil, fmt.Errorf("unmarshal type: %w", err) 14 | } 15 | 16 | return &target, nil 17 | } 18 | 19 | // JsonParseTypeF parse json file into specific T. 20 | func JsonParseTypeF[T any](filename string) (*T, error) { 21 | bb, err := os.ReadFile(filename) 22 | if err != nil { 23 | return nil, fmt.Errorf("read file: %w", err) 24 | } 25 | 26 | return JsonParseType[T](bb) 27 | } 28 | -------------------------------------------------------------------------------- /map_examples_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kazhuravlev/just" 6 | ) 7 | 8 | func ExampleMapGetDefault() { 9 | m := map[int]int{ 10 | 1: 10, 11 | 2: 20, 12 | } 13 | val := just.MapGetDefault(m, 3, 42) 14 | fmt.Println(val) 15 | // Output: 42 16 | } 17 | 18 | func ExampleMapDropKeys() { 19 | m := map[int]int{ 20 | 1: 10, 21 | 2: 20, 22 | } 23 | just.MapDropKeys(m, 1, 3) 24 | 25 | fmt.Printf("%#v", m) 26 | // Output: map[int]int{2:20} 27 | } 28 | 29 | func ExampleMapContainsKeysAll() { 30 | m := map[int]int{ 31 | 1: 10, 32 | 2: 20, 33 | } 34 | containsAllKeys := just.MapContainsKeysAll(m, []int{1, 2}) 35 | 36 | fmt.Println(containsAllKeys) 37 | // Output: true 38 | } 39 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Pool[T any] struct { 8 | pool sync.Pool 9 | reset func(T) 10 | } 11 | 12 | // NewPool returns a new pool with concrete type and resets fn. 13 | func NewPool[T any](constructor func() T, reset func(T)) *Pool[T] { 14 | if reset == nil { 15 | reset = func(T) {} 16 | } 17 | 18 | return &Pool[T]{ 19 | pool: sync.Pool{ 20 | New: func() any { return constructor() }, 21 | }, 22 | reset: reset, 23 | } 24 | } 25 | 26 | // Get return another object from the pool. 27 | func (p *Pool[T]) Get() T { 28 | return p.pool.Get().(T) 29 | } 30 | 31 | // Put will reset the object and put this object to the pool. 32 | func (p *Pool[T]) Put(obj T) { 33 | p.reset(obj) 34 | p.pool.Put(obj) 35 | } 36 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // ContextWithTimeout will create a new context with a specified timeout and 9 | // call the function with this context. 10 | func ContextWithTimeout(ctx context.Context, d time.Duration, fn func(context.Context) error) error { 11 | ctx2, cancel := context.WithTimeout(ctx, d) 12 | defer cancel() 13 | 14 | return fn(ctx2) 15 | } 16 | 17 | // ContextWithTimeout2 will do the same as ContextWithTimeout but returns 18 | // 2 arguments from the function callback. 19 | func ContextWithTimeout2[T any](ctx context.Context, d time.Duration, fn func(context.Context) (T, error)) (T, error) { 20 | ctx2, cancel := context.WithTimeout(ctx, d) 21 | defer cancel() 22 | 23 | return fn(ctx2) 24 | } 25 | -------------------------------------------------------------------------------- /errors_examples_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kazhuravlev/just" 6 | "io" 7 | "math/rand" 8 | ) 9 | 10 | func ExampleErrIsAnyOf() { 11 | funcWithRandomError := func() error { 12 | errs := []error{ 13 | io.EOF, 14 | io.ErrNoProgress, 15 | io.ErrClosedPipe, 16 | } 17 | 18 | return errs[rand.Intn(len(errs))] 19 | } 20 | 21 | err := funcWithRandomError() 22 | // Instead of switch/case you can use: 23 | fmt.Println(just.ErrIsAnyOf(err, io.EOF, io.ErrClosedPipe, io.ErrNoProgress)) 24 | // Output: true 25 | } 26 | 27 | func ExampleErrAs() { 28 | err := fmt.Errorf("problem: %w", customErr{reason: 13}) 29 | 30 | e, ok := just.ErrAs[customErr](err) 31 | fmt.Printf("%#v, %t", e, ok) 32 | // Output: just_test.customErr{reason:13}, true 33 | } 34 | -------------------------------------------------------------------------------- /chan.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | // ChanAdapt returns a channel, which will contain adapted messages from 4 | // the source channel. The resulting channel will be closed after the source 5 | // channel is closed. 6 | func ChanAdapt[T, D any](in <-chan T, fn func(T) D) <-chan D { 7 | ch := make(chan D) 8 | go func() { 9 | defer close(ch) 10 | 11 | for elem := range in { 12 | ch <- fn(elem) 13 | } 14 | }() 15 | 16 | return ch 17 | } 18 | 19 | // ChanPut will put all elements into the channel synchronously. 20 | func ChanPut[T any](ch chan T, elems []T) { 21 | for i := range elems { 22 | ch <- elems[i] 23 | } 24 | } 25 | 26 | // ChanReadN will read N messages from the channel and return the resulting 27 | // slice. 28 | func ChanReadN[T any](ch <-chan T, n int) []T { 29 | res := make([]T, n) 30 | for i := 0; i < n; i++ { 31 | res[i] = <-ch 32 | } 33 | 34 | return res 35 | } 36 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import "errors" 4 | 5 | // ErrIsAnyOf returns true when at least one expression 6 | // `errors.Is(err, errSlice[N])` return true. 7 | func ErrIsAnyOf(err error, errSlice ...error) bool { 8 | for i := range errSlice { 9 | if errors.Is(err, errSlice[i]) { 10 | return true 11 | } 12 | } 13 | 14 | return false 15 | } 16 | 17 | // ErrIsNotAnyOf returns true when all errors from errSlice are not 18 | // `errors.Is(err, errSlice[N])`. 19 | func ErrIsNotAnyOf(err error, errSlice ...error) bool { 20 | for i := range errSlice { 21 | if errors.Is(err, errSlice[i]) { 22 | return false 23 | } 24 | } 25 | 26 | return true 27 | } 28 | 29 | // ErrAs provide a more handful way to match the error. 30 | func ErrAs[T any](err error) (T, bool) { 31 | var target T 32 | if errors.As(err, &target) { 33 | return target, true 34 | } 35 | 36 | return target, false 37 | } 38 | -------------------------------------------------------------------------------- /chan_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "github.com/kazhuravlev/just" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/stretchr/testify/require" 7 | "strconv" 8 | "testing" 9 | ) 10 | 11 | func TestChanAdapt(t *testing.T) { 12 | inCh := make(chan int, 3) 13 | just.ChanPut(inCh, []int{1, 2, 3}) 14 | 15 | outCh := just.ChanAdapt(inCh, strconv.Itoa) 16 | assert.Equal(t, "1", <-outCh) 17 | assert.Equal(t, "2", <-outCh) 18 | assert.Equal(t, "3", <-outCh) 19 | 20 | close(inCh) 21 | 22 | _, ok := <-outCh 23 | assert.False(t, ok, "out channel should be closed at this moment") 24 | } 25 | 26 | func TestChanReadN(t *testing.T) { 27 | const chLen = 10 28 | const n = chLen / 2 29 | const msg = "hi" 30 | 31 | messages := just.SliceFillElem(n, msg) 32 | 33 | in := make(chan string, chLen) 34 | just.ChanPut(in, messages) 35 | 36 | res := just.ChanReadN(in, n) 37 | require.Equal(t, messages, res) 38 | } 39 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import ( 4 | "unicode/utf8" 5 | ) 6 | 7 | // StrCharCount returns rune count in string. 8 | func StrCharCount(s string) int { 9 | return utf8.RuneCountInString(s) 10 | } 11 | 12 | // StrSplitByChars returns slice of runes in string. 13 | func StrSplitByChars(s string) []rune { 14 | chars := make([]rune, 0, len(s)) 15 | var idx int 16 | buf := []byte(s) 17 | for { 18 | char, size := utf8.DecodeRune(buf[idx:]) 19 | if size == 0 { 20 | break 21 | } 22 | 23 | chars = append(chars, char) 24 | idx += size 25 | } 26 | 27 | return chars 28 | } 29 | 30 | // StrGetFirst return string contains first N valid chars. 31 | func StrGetFirst(s string, n int) string { 32 | if n <= 0 { 33 | return "" 34 | } 35 | 36 | var idx int 37 | for n > 0 { 38 | _, size := utf8.DecodeRune([]byte(s)[idx:]) 39 | if size == 0 { 40 | break 41 | } 42 | 43 | idx += size 44 | n-- 45 | } 46 | 47 | return s[:idx] 48 | } 49 | -------------------------------------------------------------------------------- /slice_iter_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.23 2 | 3 | package just_test 4 | 5 | import ( 6 | "iter" 7 | "testing" 8 | 9 | "github.com/kazhuravlev/just" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestSliceIter(t *testing.T) { 15 | t.Parallel() 16 | 17 | f := func(next func() (just.IterContext, int, bool), val, idx, revIdx int, isFirst, isLast bool) { 18 | t.Helper() 19 | 20 | iterCtx, elem, valid := next() 21 | require.True(t, valid) 22 | assert.Equal(t, val, elem) 23 | assert.Equal(t, idx, iterCtx.Idx()) 24 | assert.Equal(t, revIdx, iterCtx.RevIdx()) 25 | assert.Equal(t, isFirst, iterCtx.IsFirst()) 26 | assert.Equal(t, isLast, iterCtx.IsLast()) 27 | } 28 | 29 | in := []int{10, 20, 30, 40} 30 | iterator := just.SliceIter(in) 31 | next, _ := iter.Pull2(iterator) 32 | 33 | f(next, 10, 0, 3, true, false) 34 | f(next, 20, 1, 2, false, false) 35 | f(next, 30, 2, 1, false, false) 36 | f(next, 40, 3, 0, false, true) 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: 3 | pull_request: 4 | branches: 5 | - 'master' 6 | push: 7 | tags: 8 | - '*' 9 | jobs: 10 | build: 11 | name: Build on golang ${{ matrix.go_version }} and ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | go_version: 16 | - "1.18" 17 | - "1.19" 18 | - "1.20" 19 | - "1.21" 20 | os: 21 | - "ubuntu-latest" 22 | - "macOS-latest" 23 | 24 | steps: 25 | - name: Set up Go ${{ matrix.go_version }} 26 | uses: actions/setup-go@v4 27 | with: 28 | go-version: ${{ matrix.go_version }} 29 | id: go 30 | 31 | - name: Check out code into the Go module directory 32 | uses: actions/checkout@v4 33 | 34 | - name: Get dependencies 35 | run: | 36 | go mod download 37 | 38 | - name: Test 39 | run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 40 | 41 | - name: Upload coverage to Codecov 42 | uses: codecov/codecov-action@v3 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kirill Zhuravlev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pointer_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "github.com/kazhuravlev/just" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestPointer(t *testing.T) { 10 | t.Parallel() 11 | 12 | a := just.Pointer(10) 13 | assert.Equal(t, 10, *a) 14 | } 15 | 16 | func TestPointerUnwrap(t *testing.T) { 17 | t.Parallel() 18 | 19 | n := 10 20 | a := just.PointerUnwrap(&n) 21 | assert.Equal(t, n, a) 22 | } 23 | 24 | func TestPointerUnwrapDefault(t *testing.T) { 25 | t.Parallel() 26 | 27 | table := []struct { 28 | in *int 29 | defaultVal int 30 | exp int 31 | }{ 32 | { 33 | in: nil, 34 | defaultVal: 10, 35 | exp: 10, 36 | }, 37 | { 38 | in: just.Pointer(6), 39 | defaultVal: 10, 40 | exp: 6, 41 | }, 42 | } 43 | 44 | for _, row := range table { 45 | t.Run("", func(t *testing.T) { 46 | res := just.PointerUnwrapDefault(row.in, row.defaultVal) 47 | assert.Equal(t, row.exp, res) 48 | }) 49 | } 50 | 51 | type Data struct { 52 | Value int 53 | } 54 | 55 | res := just.PointerUnwrapDefault((*Data)(nil), Data{42}) 56 | assert.Equal(t, Data{42}, res) 57 | } 58 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "github.com/kazhuravlev/just" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestPool(t *testing.T) { 10 | t.Run("with_reset", func(t *testing.T) { 11 | var isCalled bool 12 | p := just.NewPool( 13 | func() *[]byte { return just.Pointer(make([]byte, 8)) }, 14 | func(b *[]byte) { isCalled = true }, 15 | ) 16 | 17 | assert.False(t, isCalled) 18 | 19 | bb := p.Get() 20 | assert.Equal(t, 8, len(*bb)) 21 | assert.False(t, isCalled) 22 | 23 | p.Put(bb) 24 | assert.True(t, isCalled) 25 | }) 26 | 27 | t.Run("without_reset", func(t *testing.T) { 28 | p := just.NewPool( 29 | func() *[]byte { return just.Pointer(make([]byte, 8)) }, 30 | nil, 31 | ) 32 | 33 | bb := p.Get() 34 | assert.Equal(t, 8, len(*bb)) 35 | 36 | p.Put(bb) 37 | }) 38 | } 39 | 40 | func BenchmarkPoolReset(b *testing.B) { 41 | // BenchmarkPoolReset-8 130386861 8.977 ns/op 0 B/op 0 allocs/op 42 | p := just.NewPool( 43 | func() *[]byte { return just.Pointer(make([]byte, 0)) }, 44 | func(b *[]byte) { *b = (*b)[:0] }, 45 | ) 46 | 47 | b.ResetTimer() 48 | for i := 0; i < b.N; i++ { 49 | bb := p.Get() 50 | _ = bb 51 | p.Put(bb) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /json_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "github.com/kazhuravlev/just" 5 | "github.com/stretchr/testify/require" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | type SomeType struct { 11 | ID int `json:"id"` 12 | } 13 | 14 | func TestJsonParseType(t *testing.T) { 15 | t.Run("valid_type", func(t *testing.T) { 16 | res, err := just.JsonParseType[SomeType]([]byte(`{"id":42}`)) 17 | require.NoError(t, err) 18 | require.Equal(t, SomeType{ID: 42}, *res) 19 | }) 20 | 21 | t.Run("invalid_type", func(t *testing.T) { 22 | res, err := just.JsonParseType[SomeType]([]byte(`42`)) 23 | require.Error(t, err) 24 | require.Nil(t, res) 25 | }) 26 | } 27 | 28 | func TestJsonParseTypeF(t *testing.T) { 29 | t.Run("valid_type", func(t *testing.T) { 30 | tmpFile, err := os.CreateTemp(os.TempDir(), "test-json-file") 31 | require.NoError(t, err) 32 | { 33 | _, err := tmpFile.Write([]byte(`{"id":42}`)) 34 | require.NoError(t, err) 35 | 36 | tmpFile.Close() 37 | } 38 | 39 | res, err := just.JsonParseTypeF[SomeType](tmpFile.Name()) 40 | require.NoError(t, err) 41 | require.Equal(t, SomeType{ID: 42}, *res) 42 | }) 43 | 44 | t.Run("invalid_type", func(t *testing.T) { 45 | res, err := just.JsonParseTypeF[SomeType]("/path/not-exists/png") 46 | require.Error(t, err) 47 | require.Nil(t, res) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /context_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/kazhuravlev/just" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestContextWithTimeout(t *testing.T) { 13 | t.Parallel() 14 | 15 | fn := func(ctx context.Context) error { 16 | select { 17 | case <-ctx.Done(): 18 | return ctx.Err() 19 | case <-time.After(time.Second): 20 | return nil 21 | } 22 | } 23 | 24 | err := just.ContextWithTimeout(context.Background(), time.Millisecond, fn) 25 | assert.ErrorIs(t, context.DeadlineExceeded, err) 26 | 27 | fn2 := func(ctx context.Context) error { 28 | return ctx.Err() 29 | } 30 | 31 | err2 := just.ContextWithTimeout(context.Background(), time.Millisecond, fn2) 32 | assert.NoError(t, err2) 33 | } 34 | 35 | func TestContextWithTimeout2(t *testing.T) { 36 | t.Parallel() 37 | 38 | fn := func(ctx context.Context) (int, error) { 39 | select { 40 | case <-ctx.Done(): 41 | return 10, ctx.Err() 42 | case <-time.After(time.Second): 43 | return 20, nil 44 | } 45 | } 46 | 47 | r, err := just.ContextWithTimeout2(context.Background(), time.Millisecond, fn) 48 | assert.ErrorIs(t, context.DeadlineExceeded, err) 49 | assert.Equal(t, 10, r) 50 | 51 | fn2 := func(ctx context.Context) (int, error) { 52 | return 42, ctx.Err() 53 | } 54 | 55 | r, err2 := just.ContextWithTimeout2(context.Background(), time.Millisecond, fn2) 56 | assert.NoError(t, err2) 57 | 58 | assert.Equal(t, 42, r) 59 | } 60 | -------------------------------------------------------------------------------- /other.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // Bool returns true if the element is not equal to the default value 9 | // for this type. 10 | func Bool[T builtin](v T) bool { 11 | switch x := any(v).(type) { 12 | case bool: 13 | return x 14 | case uint8: 15 | return x > zero 16 | case uint16: 17 | return x > zero 18 | case uint32: 19 | return x > zero 20 | case uint64: 21 | return x > zero 22 | case int8: 23 | return x > zero 24 | case int16: 25 | return x > zero 26 | case int32: 27 | return x > zero 28 | case int64: 29 | return x > zero 30 | case float32: 31 | return x > zero 32 | case float64: 33 | return x > zero 34 | case int: 35 | return x > zero 36 | case uint: 37 | return x > zero 38 | case uintptr: 39 | return x > zero 40 | case string: 41 | return x != "" 42 | } 43 | 44 | return false 45 | } 46 | 47 | // Must will panic on an error after calling typical function. 48 | func Must[T any](val T, err error) T { 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | return val 54 | } 55 | 56 | func RunAfter(ctx context.Context, ticker <-chan time.Time, runNow bool, fn func(ctx context.Context) error) error { 57 | if runNow { 58 | if err := fn(ctx); err != nil { 59 | return err 60 | } 61 | } 62 | 63 | for { 64 | select { 65 | case <-ctx.Done(): 66 | return ctx.Err() 67 | case <-ticker: 68 | if err := fn(ctx); err != nil { 69 | return err 70 | } 71 | } 72 | } 73 | } 74 | 75 | // If return val1 when condition is true; val2 in other case. 76 | // 77 | // Usable to simplify constructions like: 78 | // 79 | // var username string 80 | // if request.Username != "" { 81 | // username = request.Username 82 | // } else { 83 | // username = "__unknown__" 84 | // } 85 | func If[T any](condition bool, val1, val2 T) T { 86 | if condition { 87 | return val1 88 | } 89 | 90 | return val2 91 | } 92 | -------------------------------------------------------------------------------- /math.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | // Max returns the max number from `in`. 4 | func Max[T number](in ...T) T { 5 | if len(in) == 0 { 6 | panic("cannot find max of nothing") 7 | } 8 | 9 | if len(in) == 1 { 10 | return in[0] 11 | } 12 | 13 | res := in[0] 14 | for i := 1; i < len(in); i++ { 15 | if in[i] > res { 16 | res = in[i] 17 | } 18 | } 19 | 20 | return res 21 | } 22 | 23 | // MaxOr returns the max element from `in` or defaultVal when `in` is empty. 24 | func MaxOr[T number](defaultVal T, in ...T) T { 25 | if len(in) == 0 { 26 | return defaultVal 27 | } 28 | 29 | return Max(in...) 30 | } 31 | 32 | // MaxDefault returns the max element from `in` or the default value for 33 | // a specified type when `in` is empty. 34 | func MaxDefault[T number](in ...T) T { 35 | return MaxOr(0, in...) 36 | } 37 | 38 | // Min returns the min number from `in`. 39 | func Min[T number](in ...T) T { 40 | if len(in) == 0 { 41 | panic("cannot find min of nothing") 42 | } 43 | 44 | if len(in) == 1 { 45 | return in[0] 46 | } 47 | 48 | res := in[0] 49 | for i := 1; i < len(in); i++ { 50 | if in[i] < res { 51 | res = in[i] 52 | } 53 | } 54 | 55 | return res 56 | } 57 | 58 | // MinOr returns the min element from `in` or defaultVal when `in` is empty. 59 | func MinOr[T number](defaultVal T, in ...T) T { 60 | if len(in) == 0 { 61 | return defaultVal 62 | } 63 | 64 | return Min(in...) 65 | } 66 | 67 | // MinDefault returns the min element from `in` or the default value for 68 | // a specified type when `in` is empty. 69 | func MinDefault[T number](in ...T) T { 70 | return MinOr(0, in...) 71 | } 72 | 73 | // Sum returns the sum of numbers from `in`. 74 | func Sum[T number](in ...T) T { 75 | var acc T 76 | for i := range in { 77 | acc += in[i] 78 | } 79 | 80 | return acc 81 | } 82 | 83 | // Abs returns the abs value of v. 84 | func Abs[T number](v T) T { 85 | if v < 0 { 86 | return -v 87 | } 88 | 89 | return v 90 | } 91 | -------------------------------------------------------------------------------- /slice_examples_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kazhuravlev/just" 6 | "sort" 7 | "strconv" 8 | ) 9 | 10 | func ExampleSliceMap() { 11 | unsignedIntegers := []uint{1, 2, 3, 4} 12 | multiply := func(i uint) int { return int(i) * 10 } 13 | integers := just.SliceMap(unsignedIntegers, multiply) 14 | fmt.Println(integers) 15 | // Output: [10 20 30 40] 16 | } 17 | 18 | func ExampleSliceUniq() { 19 | data := []int{1, 1, 2, 2, 3, 3} 20 | uniqData := just.SliceUniq(data) 21 | sort.Ints(uniqData) 22 | fmt.Println(uniqData) 23 | // Output: [1 2 3] 24 | } 25 | 26 | func ExampleSliceFlatMap() { 27 | input := []int{1, 2, 3} 28 | result := just.SliceFlatMap(input, func(i int) []string { 29 | return []string{strconv.Itoa(i), strconv.Itoa(i)} 30 | }) 31 | fmt.Println(result) 32 | // Output: [1 1 2 2 3 3] 33 | } 34 | 35 | func ExampleSliceApply() { 36 | input := []int{10, 20, 30} 37 | just.SliceApply(input, func(i int, v int) { 38 | fmt.Println(i, v) 39 | }) 40 | // Output: 41 | // 0 10 42 | // 1 20 43 | // 2 30 44 | } 45 | 46 | func ExampleSliceFilter() { 47 | input := []int{10, 20, 30} 48 | filtered := just.SliceFilter(input, func(v int) bool { 49 | return v > 15 50 | }) 51 | fmt.Println(filtered) 52 | // Output: [20 30] 53 | } 54 | 55 | func ExampleSliceContainsElem() { 56 | input := []int{10, 20, 30} 57 | contains20 := just.SliceContainsElem(input, 20) 58 | fmt.Println(contains20) 59 | // Output: true 60 | } 61 | 62 | func ExampleSliceAddNotExists() { 63 | input := []int{10, 20, 30} 64 | result := just.SliceAddNotExists(input, 42) 65 | fmt.Println(result) 66 | // Output: [10 20 30 42] 67 | } 68 | 69 | func ExampleSlice2Map() { 70 | input := []int{10, 20, 30, 30} 71 | result := just.Slice2Map(input) 72 | fmt.Println(result) 73 | // Output: map[10:{} 20:{} 30:{}] 74 | } 75 | 76 | func ExampleSliceChunkEvery() { 77 | input := []int{10, 20, 30, 40, 50} 78 | result := just.SliceChunkEvery(input, 2) 79 | fmt.Println(result) 80 | // Output: [[10 20] [30 40] [50]] 81 | } 82 | 83 | func ExampleSlice2Chan() { 84 | input := []int{10, 20, 30, 40, 50} 85 | ch := just.Slice2Chan(input, 0) 86 | result := just.ChanReadN(ch, 5) 87 | fmt.Println(result) 88 | // Output: [10 20 30 40 50] 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # just 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/kazhuravlev/just.svg)](https://pkg.go.dev/github.com/kazhuravlev/just) 4 | [![License](https://img.shields.io/github/license/kazhuravlev/just?color=blue)](https://github.com/kazhuravlev/just/blob/master/LICENSE) 5 | [![Build Status](https://github.com/kazhuravlev/just/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/kazhuravlev/just/actions/workflows/tests.yml?query=branch%3Amaster) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/kazhuravlev/just)](https://goreportcard.com/report/github.com/kazhuravlev/just) 7 | [![CodeCov](https://codecov.io/gh/kazhuravlev/just/branch/master/graph/badge.svg?token=tNKcOjlxLo)](https://codecov.io/gh/kazhuravlev/just) 8 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#utilities) 9 | 10 | This project contains features that help make the noisy stuff in every project. 11 | 12 | - Filter slices, maps. 13 | - Applying functions to collections. 14 | - Null[any] for optional fields in API and sql.DB queries. 15 | 16 | ## Most helpful functions 17 | 18 | - [`SliceIter`](https://pkg.go.dev/github.com/kazhuravlev/just#SliceIter) allows you to iterate over slice with 19 | special `IterContext` that provide some methods like `IsFirst`, `IsLast` 20 | - [`SliceMap`](https://pkg.go.dev/github.com/kazhuravlev/just#SliceMap) and 21 | [`SliceMapErr`](https://pkg.go.dev/github.com/kazhuravlev/just#SliceMapErr) allow you to map a slice to 22 | another one. Useful for adapters. 23 | - [`NewPool`](https://pkg.go.dev/github.com/kazhuravlev/just#NewPool) `sync.Pool` with generics 24 | - [`ChanAdapt`](https://pkg.go.dev/github.com/kazhuravlev/just#ChanAdapt) allows to create an adapted version of channel 25 | - [`ContextWithTimeout`](https://pkg.go.dev/github.com/kazhuravlev/just#ContextWithTimeout) runs a function with context 26 | and timeout 27 | - [`ErrAs`](https://pkg.go.dev/github.com/kazhuravlev/just#ErrAs) helps to handle an errors 28 | - [`SliceChunk`](https://pkg.go.dev/github.com/kazhuravlev/just#SliceChunk) iterates over chunks 29 | - [`SliceChain`](https://pkg.go.dev/github.com/kazhuravlev/just#SliceChain) joins slices to one 30 | - [`StrSplitByChars`](https://pkg.go.dev/github.com/kazhuravlev/just#StrSplitByChars) splits the string by symbols 31 | 32 | ## Examples 33 | 34 | This library contains a bunch of functions. Please 35 | see [pkg.go.dev](https://pkg.go.dev/github.com/kazhuravlev/just#pkg-examples) 36 | for examples. 37 | -------------------------------------------------------------------------------- /string_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "github.com/kazhuravlev/just" 5 | "github.com/stretchr/testify/require" 6 | "testing" 7 | ) 8 | 9 | func TestStrSplitByChars(t *testing.T) { 10 | t.Parallel() 11 | 12 | table := []struct { 13 | name string 14 | in string 15 | exp []rune 16 | }{ 17 | { 18 | name: "empty_input_empty_output", 19 | in: "", 20 | exp: []rune{}, 21 | }, 22 | { 23 | name: "latin_chars", 24 | in: "hello", 25 | exp: []rune{'h', 'e', 'l', 'l', 'o'}, 26 | }, 27 | { 28 | name: "cyrillic_chars", 29 | in: "тест", 30 | exp: []rune{'т', 'е', 'с', 'т'}, 31 | }, 32 | { 33 | name: "mixed_chars", 34 | in: "QЙ", 35 | exp: []rune{'Q', 'Й'}, 36 | }, 37 | { 38 | name: "japanese_chars", 39 | in: "空母", 40 | exp: []rune{'空', '母'}, 41 | }, 42 | } 43 | for i := range table { 44 | row := table[i] 45 | t.Run(row.name, func(t *testing.T) { 46 | t.Parallel() 47 | 48 | require.Equal(t, row.exp, just.StrSplitByChars(row.in)) 49 | require.Equal(t, len(row.exp), just.StrCharCount(row.in)) 50 | }) 51 | } 52 | } 53 | 54 | func TestStrGetFirst(t *testing.T) { 55 | t.Parallel() 56 | 57 | table := []struct { 58 | name string 59 | in string 60 | inN int 61 | exp string 62 | }{ 63 | { 64 | name: "empty_input_empty_output", 65 | in: "", 66 | inN: 0, 67 | exp: "", 68 | }, 69 | { 70 | name: "empty_input_empty_output2", 71 | in: "hello", 72 | inN: 0, 73 | exp: "", 74 | }, 75 | { 76 | name: "negative_in", 77 | in: "hello", 78 | inN: -10, 79 | exp: "", 80 | }, 81 | { 82 | name: "overflow_in", 83 | in: "hello", 84 | inN: 10, 85 | exp: "hello", 86 | }, 87 | { 88 | name: "latin_chars", 89 | in: "hello", 90 | inN: 4, 91 | exp: "hell", 92 | }, 93 | { 94 | name: "cyrillic_chars", 95 | in: "тесто", 96 | inN: 4, 97 | exp: "тест", 98 | }, 99 | { 100 | name: "emoji", 101 | in: "🍕", 102 | inN: 1, 103 | exp: "🍕", 104 | }, 105 | { 106 | name: "japanese_chars", 107 | in: "空母", 108 | inN: 2, 109 | exp: "空母", 110 | }, 111 | } 112 | for i := range table { 113 | row := table[i] 114 | t.Run(row.name, func(t *testing.T) { 115 | t.Parallel() 116 | 117 | require.Equal(t, row.exp, just.StrGetFirst(row.in, row.inN)) 118 | }) 119 | } 120 | } 121 | 122 | func BenchmarkStrGetFirst(b *testing.B) { 123 | str := "Hey, this is a test string for experiment. It length 64 symbols." 124 | 125 | for i := 0; i < b.N; i++ { 126 | _ = just.StrGetFirst(str, 32) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /errors_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/kazhuravlev/just" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestErrIsAnyOf(t *testing.T) { 14 | t.Parallel() 15 | 16 | wrapped := func(e error) error { return fmt.Errorf("wrapped %w", e) } 17 | 18 | table := []struct { 19 | err error 20 | errSlice []error 21 | exp bool 22 | }{ 23 | { 24 | err: nil, 25 | errSlice: nil, 26 | exp: false, 27 | }, 28 | { 29 | err: io.EOF, 30 | errSlice: []error{io.EOF, io.ErrClosedPipe}, 31 | exp: true, 32 | }, 33 | { 34 | err: wrapped(io.EOF), 35 | errSlice: []error{io.EOF, io.ErrClosedPipe}, 36 | exp: true, 37 | }, 38 | { 39 | err: wrapped(io.EOF), 40 | errSlice: []error{io.ErrClosedPipe}, 41 | exp: false, 42 | }, 43 | { 44 | err: wrapped(io.EOF), 45 | errSlice: nil, 46 | exp: false, 47 | }, 48 | } 49 | 50 | for _, row := range table { 51 | t.Run("", func(t *testing.T) { 52 | res := just.ErrIsAnyOf(row.err, row.errSlice...) 53 | assert.Equal(t, row.exp, res) 54 | }) 55 | } 56 | } 57 | 58 | func TestErrIsNotAnyOf(t *testing.T) { 59 | t.Parallel() 60 | 61 | wrapped := func(e error) error { return fmt.Errorf("wrapped %w", e) } 62 | 63 | table := []struct { 64 | err error 65 | errSlice []error 66 | exp bool 67 | }{ 68 | { 69 | err: nil, 70 | errSlice: nil, 71 | exp: true, 72 | }, 73 | { 74 | err: io.EOF, 75 | errSlice: []error{io.EOF, io.ErrClosedPipe}, 76 | exp: false, 77 | }, 78 | { 79 | err: wrapped(io.EOF), 80 | errSlice: []error{io.EOF, io.ErrClosedPipe}, 81 | exp: false, 82 | }, 83 | { 84 | err: wrapped(io.EOF), 85 | errSlice: []error{io.ErrClosedPipe}, 86 | exp: true, 87 | }, 88 | { 89 | err: wrapped(io.EOF), 90 | errSlice: nil, 91 | exp: true, 92 | }, 93 | } 94 | 95 | for _, row := range table { 96 | t.Run("", func(t *testing.T) { 97 | res := just.ErrIsNotAnyOf(row.err, row.errSlice...) 98 | assert.Equal(t, row.exp, res) 99 | }) 100 | } 101 | } 102 | 103 | type customErr struct { 104 | reason int 105 | } 106 | 107 | func (c customErr) Error() string { 108 | return strconv.Itoa(c.reason) 109 | } 110 | 111 | func TestAsTestErrAs(t *testing.T) { 112 | t.Parallel() 113 | 114 | err := fmt.Errorf("problem: %w", customErr{reason: 13}) 115 | 116 | e, ok := just.ErrAs[customErr](err) 117 | assert.True(t, ok) 118 | assert.Equal(t, customErr{reason: 13}, e) 119 | } 120 | -------------------------------------------------------------------------------- /null.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "errors" 7 | 8 | "github.com/goccy/go-yaml" 9 | ) 10 | 11 | // NullVal represents the nullable value for this type. 12 | // Deprecated: Use github.com/kazhuravlev/optional. 13 | type NullVal[T any] struct { 14 | Val T `json:"v"` 15 | Valid bool `json:"ok"` 16 | } 17 | 18 | // UnmarshalYAML implements the interface for unmarshalling yaml. 19 | func (nv *NullVal[T]) UnmarshalYAML(bb []byte) error { 20 | if len(bb) == 0 { 21 | nv.Valid = false 22 | nv.Val = *new(T) 23 | return nil 24 | } 25 | 26 | if err := yaml.Unmarshal(bb, &nv.Val); err != nil { 27 | return err 28 | } 29 | 30 | nv.Valid = true 31 | 32 | return nil 33 | } 34 | 35 | // MarshalYAML implements the interface for marshaling yaml. 36 | func (nv NullVal[T]) MarshalYAML() ([]byte, error) { 37 | if !nv.Valid { 38 | return []byte("null"), nil 39 | } 40 | 41 | return yaml.Marshal(nv.Val) 42 | } 43 | 44 | // Scan implements the Scanner interface. 45 | func (nv *NullVal[T]) Scan(value any) error { 46 | if v, ok := any(&nv.Val).(sql.Scanner); ok { 47 | if err := v.Scan(value); err != nil { 48 | nv.Val, nv.Valid = *new(T), false 49 | return err 50 | } 51 | 52 | nv.Valid = true 53 | return nil 54 | } 55 | 56 | if value == nil { 57 | nv.Val, nv.Valid = *new(T), true 58 | return nil 59 | } 60 | 61 | v, ok := value.(T) 62 | if !ok { 63 | return errors.New("unexpected value type") 64 | } 65 | 66 | nv.Valid = true 67 | nv.Val = v 68 | return nil 69 | } 70 | 71 | // Value implements the driver Valuer interface. 72 | func (nv NullVal[T]) Value() (driver.Value, error) { 73 | if !nv.Valid { 74 | return nil, nil 75 | } 76 | 77 | if v, ok := any(nv.Val).(driver.Valuer); ok { 78 | return v.Value() 79 | } 80 | 81 | return nv.Val, nil 82 | } 83 | 84 | // ValueOk returns the NullVal.Val and NullVal.Valid. 85 | func (nv NullVal[T]) ValueOk() (T, bool) { 86 | return nv.Val, nv.Valid 87 | } 88 | 89 | // SetDefault set value `val` if NullVal.Valid == false. 90 | func (nv *NullVal[T]) SetDefault(val T) bool { 91 | if nv.Valid { 92 | return false 93 | } 94 | 95 | nv.Val = val 96 | 97 | return true 98 | } 99 | 100 | // Null returns NullVal for `val` type, which are `NullVal.Valid == true`. 101 | func Null[T any](val T) NullVal[T] { 102 | return NullVal[T]{ 103 | Val: val, 104 | Valid: true, 105 | } 106 | } 107 | 108 | // NullNull returns NullVal, which are `NullVal.Valid == false`. 109 | func NullNull[T any]() NullVal[T] { 110 | return NullVal[T]{ 111 | Val: *new(T), 112 | Valid: false, 113 | } 114 | } 115 | 116 | // NullDefaultFalse returns NullVal for this type with Valid=true only when 117 | // `val` is not equal to default value of type T. 118 | func NullDefaultFalse[T comparable](val T) NullVal[T] { 119 | return NullVal[T]{ 120 | Val: val, 121 | Valid: val != *new(T), 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### JetBrains template 2 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 3 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 4 | 5 | # User-specific stuff 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | # Generated files 13 | .idea/**/contentModel.xml 14 | 15 | # Sensitive or high-churn files 16 | .idea/**/dataSources/ 17 | .idea/**/dataSources.ids 18 | .idea/**/dataSources.local.xml 19 | .idea/**/sqlDataSources.xml 20 | .idea/**/dynamic.xml 21 | .idea/**/uiDesigner.xml 22 | .idea/**/dbnavigator.xml 23 | 24 | # Gradle 25 | .idea/**/gradle.xml 26 | .idea/**/libraries 27 | 28 | # Gradle and Maven with auto-import 29 | # When using Gradle or Maven with auto-import, you should exclude module files, 30 | # since they will be recreated, and may cause churn. Uncomment if using 31 | # auto-import. 32 | # .idea/artifacts 33 | # .idea/compiler.xml 34 | # .idea/jarRepositories.xml 35 | # .idea/modules.xml 36 | # .idea/*.iml 37 | # .idea/modules 38 | # *.iml 39 | # *.ipr 40 | 41 | # CMake 42 | cmake-build-*/ 43 | 44 | # Mongo Explorer plugin 45 | .idea/**/mongoSettings.xml 46 | 47 | # File-based project format 48 | *.iws 49 | 50 | # IntelliJ 51 | out/ 52 | 53 | # mpeltonen/sbt-idea plugin 54 | .idea_modules/ 55 | 56 | # JIRA plugin 57 | atlassian-ide-plugin.xml 58 | 59 | # Cursive Clojure plugin 60 | .idea/replstate.xml 61 | 62 | # Crashlytics plugin (for Android Studio and IntelliJ) 63 | com_crashlytics_export_strings.xml 64 | crashlytics.properties 65 | crashlytics-build.properties 66 | fabric.properties 67 | 68 | # Editor-based Rest Client 69 | .idea/httpRequests 70 | 71 | # Android studio 3.1+ serialized cache file 72 | .idea/caches/build_file_checksums.ser 73 | 74 | ### Linux template 75 | *~ 76 | 77 | # temporary files which can be created if a process still has a handle open of a deleted file 78 | .fuse_hidden* 79 | 80 | # KDE directory preferences 81 | .directory 82 | 83 | # Linux trash folder which might appear on any partition or disk 84 | .Trash-* 85 | 86 | # .nfs files are created when an open file is removed but is still being accessed 87 | .nfs* 88 | 89 | ### Go template 90 | # Binaries for programs and plugins 91 | *.exe 92 | *.exe~ 93 | *.dll 94 | *.so 95 | *.dylib 96 | 97 | # Test binary, built with `go test -c` 98 | *.test 99 | 100 | # Output of the go coverage tool, specifically when used with LiteIDE 101 | *.out 102 | 103 | # Dependency directories (remove the comment below to include it) 104 | # vendor/ 105 | 106 | ### macOS template 107 | # General 108 | .DS_Store 109 | .AppleDouble 110 | .LSOverride 111 | 112 | # Icon must end with two \r 113 | Icon 114 | 115 | # Thumbnails 116 | ._* 117 | 118 | # Files that might appear in the root of a volume 119 | .DocumentRevisions-V100 120 | .fseventsd 121 | .Spotlight-V100 122 | .TemporaryItems 123 | .Trashes 124 | .VolumeIcon.icns 125 | .com.apple.timemachine.donotpresent 126 | 127 | # Directories potentially created on remote AFP share 128 | .AppleDB 129 | .AppleDesktop 130 | Network Trash Folder 131 | Temporary Items 132 | .apdisk 133 | 134 | ### Custom 135 | .idea/ 136 | -------------------------------------------------------------------------------- /math_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "github.com/kazhuravlev/just" 5 | "github.com/stretchr/testify/assert" 6 | "math" 7 | "testing" 8 | ) 9 | 10 | func TestMax(t *testing.T) { 11 | t.Parallel() 12 | 13 | t.Run("empty_should_be_panicked", func(t *testing.T) { 14 | assert.Panics(t, func() { 15 | just.Max[int]() 16 | }) 17 | }) 18 | 19 | table := []struct { 20 | name string 21 | in []int 22 | exp int 23 | }{ 24 | { 25 | name: "case1", 26 | in: []int{1}, 27 | exp: 1, 28 | }, 29 | { 30 | name: "case2", 31 | in: []int{-1, 0, 1}, 32 | exp: 1, 33 | }, 34 | { 35 | name: "case3", 36 | in: []int{-1, -1, -10}, 37 | exp: -1, 38 | }, 39 | } 40 | 41 | for _, row := range table { 42 | row := row 43 | t.Run(row.name, func(t *testing.T) { 44 | t.Parallel() 45 | 46 | res := just.Max(row.in...) 47 | assert.Equal(t, row.exp, res) 48 | }) 49 | } 50 | 51 | } 52 | 53 | func TestMaxOr(t *testing.T) { 54 | t.Parallel() 55 | 56 | table := []struct { 57 | name string 58 | defaultVal int 59 | in []int 60 | exp int 61 | }{ 62 | { 63 | name: "empty_in", 64 | defaultVal: -1, 65 | in: []int{}, 66 | exp: -1, 67 | }, 68 | { 69 | name: "case1", 70 | defaultVal: -1, 71 | in: []int{1}, 72 | exp: 1, 73 | }, 74 | { 75 | name: "case2", 76 | defaultVal: -100, 77 | in: []int{-1, 0, 1}, 78 | exp: 1, 79 | }, 80 | { 81 | name: "case3", 82 | defaultVal: -100, 83 | in: []int{-1, -1, -10}, 84 | exp: -1, 85 | }, 86 | } 87 | 88 | for _, row := range table { 89 | row := row 90 | t.Run(row.name, func(t *testing.T) { 91 | t.Parallel() 92 | 93 | res := just.MaxOr(row.defaultVal, row.in...) 94 | assert.Equal(t, row.exp, res) 95 | }) 96 | } 97 | } 98 | 99 | func TestMaxDefault(t *testing.T) { 100 | assert.Equal(t, 3, just.MaxDefault(1, 2, 3)) 101 | assert.Equal(t, 0, just.MaxDefault[int]()) 102 | } 103 | 104 | func TestMin(t *testing.T) { 105 | t.Parallel() 106 | 107 | t.Run("empty_should_be_panicked", func(t *testing.T) { 108 | assert.Panics(t, func() { 109 | just.Min[int]() 110 | }) 111 | }) 112 | 113 | table := []struct { 114 | name string 115 | in []int 116 | exp int 117 | }{ 118 | { 119 | name: "case1", 120 | in: []int{1}, 121 | exp: 1, 122 | }, 123 | { 124 | name: "case2", 125 | in: []int{-1, 0, 1}, 126 | exp: -1, 127 | }, 128 | { 129 | name: "case3", 130 | in: []int{-1, -1, -10}, 131 | exp: -10, 132 | }, 133 | } 134 | 135 | for _, row := range table { 136 | row := row 137 | t.Run(row.name, func(t *testing.T) { 138 | t.Parallel() 139 | 140 | res := just.Min(row.in...) 141 | assert.Equal(t, row.exp, res) 142 | }) 143 | } 144 | } 145 | 146 | func TestMinOr(t *testing.T) { 147 | t.Parallel() 148 | 149 | table := []struct { 150 | name string 151 | defaultVal int 152 | in []int 153 | exp int 154 | }{ 155 | { 156 | name: "empty_in", 157 | defaultVal: -1, 158 | in: []int{}, 159 | exp: -1, 160 | }, 161 | { 162 | name: "case1", 163 | defaultVal: -1, 164 | in: []int{1}, 165 | exp: 1, 166 | }, 167 | { 168 | name: "case2", 169 | defaultVal: -100, 170 | in: []int{-1, 0, 1}, 171 | exp: -1, 172 | }, 173 | { 174 | name: "case3", 175 | defaultVal: -100, 176 | in: []int{-1, -1, -10}, 177 | exp: -10, 178 | }, 179 | } 180 | 181 | for _, row := range table { 182 | row := row 183 | t.Run(row.name, func(t *testing.T) { 184 | t.Parallel() 185 | 186 | res := just.MinOr(row.defaultVal, row.in...) 187 | assert.Equal(t, row.exp, res) 188 | }) 189 | } 190 | } 191 | 192 | func TestMinDefault(t *testing.T) { 193 | assert.Equal(t, 1, just.MinDefault(1, 2, 3)) 194 | assert.Equal(t, 0, just.MinDefault[int]()) 195 | } 196 | 197 | func TestSum(t *testing.T) { 198 | t.Parallel() 199 | 200 | table := []struct { 201 | name string 202 | in []int 203 | exp int 204 | }{ 205 | { 206 | name: "empty", 207 | in: []int{}, 208 | exp: 0, 209 | }, 210 | { 211 | name: "case1", 212 | in: []int{1}, 213 | exp: 1, 214 | }, 215 | { 216 | name: "case2", 217 | in: []int{1, -1}, 218 | exp: 0, 219 | }, 220 | { 221 | name: "case3", 222 | in: []int{10, -1, 0}, 223 | exp: 9, 224 | }, 225 | } 226 | 227 | for _, row := range table { 228 | row := row 229 | t.Run(row.name, func(t *testing.T) { 230 | t.Parallel() 231 | 232 | res := just.Sum(row.in...) 233 | assert.Equal(t, row.exp, res) 234 | }) 235 | } 236 | } 237 | 238 | func TestAbs(t *testing.T) { 239 | t.Parallel() 240 | 241 | assert.Equal(t, 1, just.Abs(1)) 242 | assert.Equal(t, 1, just.Abs(-1)) 243 | assert.Equal(t, 0, just.Abs(0)) 244 | 245 | a := math.Copysign(0, -1) 246 | assert.Equal(t, float64(0), just.Abs(a)) 247 | } 248 | -------------------------------------------------------------------------------- /other_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "github.com/kazhuravlev/just" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | "io" 10 | "regexp" 11 | "sync" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | func TestBool(t *testing.T) { 17 | t.Parallel() 18 | 19 | t.Run("bool", func(t *testing.T) { 20 | assert.True(t, just.Bool(true)) 21 | assert.False(t, just.Bool(false)) 22 | }) 23 | 24 | t.Run("int", func(t *testing.T) { 25 | assert.True(t, just.Bool(int(1))) 26 | assert.True(t, just.Bool(int8(1))) 27 | assert.True(t, just.Bool(int16(1))) 28 | assert.True(t, just.Bool(int32(1))) 29 | assert.True(t, just.Bool(int64(1))) 30 | 31 | assert.False(t, just.Bool(int(0))) 32 | assert.False(t, just.Bool(int8(0))) 33 | assert.False(t, just.Bool(int16(0))) 34 | assert.False(t, just.Bool(int32(0))) 35 | assert.False(t, just.Bool(int64(0))) 36 | }) 37 | 38 | t.Run("uint", func(t *testing.T) { 39 | assert.True(t, just.Bool(uint(1))) 40 | assert.True(t, just.Bool(uint8(1))) 41 | assert.True(t, just.Bool(uint16(1))) 42 | assert.True(t, just.Bool(uint32(1))) 43 | assert.True(t, just.Bool(uint64(1))) 44 | 45 | assert.False(t, just.Bool(uint(0))) 46 | assert.False(t, just.Bool(uint8(0))) 47 | assert.False(t, just.Bool(uint16(0))) 48 | assert.False(t, just.Bool(uint32(0))) 49 | assert.False(t, just.Bool(uint64(0))) 50 | }) 51 | 52 | t.Run("float", func(t *testing.T) { 53 | assert.True(t, just.Bool(float32(1))) 54 | assert.True(t, just.Bool(float64(1))) 55 | 56 | assert.False(t, just.Bool(float32(0))) 57 | assert.False(t, just.Bool(float64(0))) 58 | }) 59 | 60 | t.Run("uintptr", func(t *testing.T) { 61 | assert.True(t, just.Bool(uintptr(1))) 62 | 63 | assert.False(t, just.Bool(uintptr(0))) 64 | }) 65 | 66 | t.Run("string", func(t *testing.T) { 67 | assert.True(t, just.Bool("1")) 68 | 69 | assert.False(t, just.Bool("")) 70 | }) 71 | } 72 | 73 | func TestMust(t *testing.T) { 74 | t.Run("success", func(t *testing.T) { 75 | const str = "this is body!" 76 | 77 | val := just.Must(io.ReadAll(bytes.NewBufferString(str))) 78 | assert.Equal(t, str, string(val)) 79 | }) 80 | 81 | t.Run("panic", func(t *testing.T) { 82 | require.Panics(t, func() { 83 | just.Must(regexp.Compile("[")) 84 | }) 85 | }) 86 | } 87 | 88 | func TestRunAfter(t *testing.T) { 89 | t.Parallel() 90 | 91 | t.Run("runs immediately when runNow is true", func(t *testing.T) { 92 | ctx, cancel := context.WithCancel(context.Background()) 93 | defer cancel() 94 | ticker := make(chan time.Time) 95 | 96 | var called bool 97 | done := make(chan error, 1) 98 | go func() { 99 | done <- just.RunAfter(ctx, ticker, true, func(ctx context.Context) error { 100 | called = true 101 | return assert.AnError // Return error to exit the loop 102 | }) 103 | }() 104 | 105 | err := <-done 106 | assert.Equal(t, assert.AnError, err) 107 | assert.True(t, called) 108 | }) 109 | 110 | t.Run("does not run immediately when runNow is false", func(t *testing.T) { 111 | ctx, cancel := context.WithCancel(context.Background()) 112 | defer cancel() 113 | ticker := make(chan time.Time) 114 | 115 | var called bool 116 | done := make(chan struct{}) 117 | go func() { 118 | _ = just.RunAfter(ctx, ticker, false, func(ctx context.Context) error { 119 | called = true 120 | return nil 121 | }) 122 | close(done) 123 | }() 124 | 125 | time.Sleep(10 * time.Millisecond) 126 | assert.False(t, called) 127 | cancel() 128 | <-done 129 | }) 130 | 131 | t.Run("runs on ticker", func(t *testing.T) { 132 | ctx, cancel := context.WithCancel(context.Background()) 133 | defer cancel() 134 | ticker := make(chan time.Time) 135 | 136 | var count int 137 | var mu sync.Mutex 138 | done := make(chan struct{}) 139 | go func() { 140 | _ = just.RunAfter(ctx, ticker, false, func(ctx context.Context) error { 141 | mu.Lock() 142 | count++ 143 | mu.Unlock() 144 | return nil 145 | }) 146 | close(done) 147 | }() 148 | 149 | ticker <- time.Now() 150 | time.Sleep(10 * time.Millisecond) 151 | mu.Lock() 152 | assert.Equal(t, 1, count) 153 | mu.Unlock() 154 | 155 | ticker <- time.Now() 156 | time.Sleep(10 * time.Millisecond) 157 | mu.Lock() 158 | assert.Equal(t, 2, count) 159 | mu.Unlock() 160 | 161 | cancel() 162 | <-done 163 | }) 164 | 165 | t.Run("returns error from function", func(t *testing.T) { 166 | ctx := context.Background() 167 | ticker := make(chan time.Time) 168 | defer close(ticker) 169 | 170 | expectedErr := assert.AnError 171 | err := just.RunAfter(ctx, ticker, true, func(ctx context.Context) error { 172 | return expectedErr 173 | }) 174 | 175 | assert.Equal(t, expectedErr, err) 176 | }) 177 | 178 | t.Run("stops on context cancellation", func(t *testing.T) { 179 | ctx, cancel := context.WithCancel(context.Background()) 180 | ticker := make(chan time.Time) 181 | defer close(ticker) 182 | 183 | errChan := make(chan error, 1) 184 | go func() { 185 | errChan <- just.RunAfter(ctx, ticker, false, func(ctx context.Context) error { 186 | return nil 187 | }) 188 | }() 189 | 190 | cancel() 191 | err := <-errChan 192 | assert.Equal(t, context.Canceled, err) 193 | }) 194 | } 195 | 196 | func TestIf(t *testing.T) { 197 | t.Parallel() 198 | 199 | t.Run("returns first value when condition is true", func(t *testing.T) { 200 | result := just.If(true, "yes", "no") 201 | assert.Equal(t, "yes", result) 202 | }) 203 | 204 | t.Run("returns second value when condition is false", func(t *testing.T) { 205 | result := just.If(false, "yes", "no") 206 | assert.Equal(t, "no", result) 207 | }) 208 | 209 | t.Run("works with different types", func(t *testing.T) { 210 | // int 211 | intResult := just.If(true, 42, 0) 212 | assert.Equal(t, 42, intResult) 213 | 214 | // struct 215 | type person struct { 216 | name string 217 | age int 218 | } 219 | alice := person{name: "Alice", age: 30} 220 | bob := person{name: "Bob", age: 25} 221 | personResult := just.If(true, alice, bob) 222 | assert.Equal(t, alice, personResult) 223 | 224 | // pointers 225 | x, y := 1, 2 226 | ptrResult := just.If(false, &x, &y) 227 | assert.Equal(t, &y, ptrResult) 228 | }) 229 | } 230 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import "golang.org/x/exp/maps" 4 | 5 | // MapMerge returns the map which contains all keys from m1, m2, and values 6 | // from `fn(key, m1Value, m2Value)`. 7 | func MapMerge[M ~map[K]V, K comparable, V any](m1, m2 M, fn func(k K, v1, v2 V) V) M { 8 | m := make(M, len(m1)) 9 | for k, v := range m1 { 10 | m[k] = fn(k, v, m2[k]) 11 | } 12 | 13 | var emptyVal V 14 | for k, v := range m2 { 15 | if _, ok := m[k]; ok { 16 | continue 17 | } 18 | 19 | m[k] = fn(k, emptyVal, v) 20 | } 21 | 22 | return m 23 | } 24 | 25 | // MapFilter returns the map which contains elements that 26 | // `fn(key, value) == true`. 27 | func MapFilter[M ~map[K]V, K comparable, V any](in M, fn func(k K, v V) bool) M { 28 | m := make(M, len(in)) 29 | for k, v := range in { 30 | if !fn(k, v) { 31 | continue 32 | } 33 | 34 | m[k] = v 35 | } 36 | 37 | return m 38 | } 39 | 40 | // MapFilterKeys returns the map which contains elements that 41 | // `fn(key) == true`. That is a simplified version of MapFilter. 42 | func MapFilterKeys[M ~map[K]V, K comparable, V any](in M, fn func(k K) bool) M { 43 | return MapFilter(in, func(k K, _ V) bool { 44 | return fn(k) 45 | }) 46 | } 47 | 48 | // MapFilterValues returns the map which contains elements that 49 | // `fn(value) == true`. That is a simplified version of MapFilter. 50 | func MapFilterValues[M ~map[K]V, K comparable, V any](in M, fn func(v V) bool) M { 51 | return MapFilter(in, func(_ K, v V) bool { 52 | return fn(v) 53 | }) 54 | } 55 | 56 | // MapGetKeys returns all keys of the map. 57 | func MapGetKeys[M ~map[K]V, K comparable, V any](m M) []K { 58 | return maps.Keys(m) 59 | } 60 | 61 | // MapGetValues returns all values of the map. Not Uniq, unordered. 62 | func MapGetValues[M ~map[K]V, K comparable, V any](m M) []V { 63 | return maps.Values(m) 64 | } 65 | 66 | // KV represents the key-value of the map. 67 | type KV[K comparable, V any] struct { 68 | Key K 69 | Val V 70 | } 71 | 72 | // MapPairs returns a slice of KV structs that contains key-value pairs. 73 | func MapPairs[M ~map[K]V, K comparable, V any](m M) []KV[K, V] { 74 | if len(m) == 0 { 75 | return nil 76 | } 77 | 78 | res := make([]KV[K, V], len(m)) 79 | var i int 80 | for k, v := range m { 81 | res[i] = KV[K, V]{ 82 | Key: k, 83 | Val: v, 84 | } 85 | i++ 86 | } 87 | 88 | return res 89 | } 90 | 91 | // MapDefaults returns the map `m` after filling in its non-exists keys by 92 | // `defaults`. 93 | // Example: {1:1}, {1:0, 2:2} => {1:1, 2:2} 94 | func MapDefaults[M ~map[K]V, K comparable, V any](m, defaults M) M { 95 | res := MapCopy(m) 96 | for k, v := range defaults { 97 | if _, ok := res[k]; !ok { 98 | res[k] = v 99 | } 100 | } 101 | 102 | return res 103 | } 104 | 105 | // MapCopy returns a shallow copy of the map. 106 | func MapCopy[M ~map[K]V, K comparable, V any](m M) M { 107 | return maps.Clone(m) 108 | } 109 | 110 | // MapMap applies fn to all kv pairs from in. 111 | func MapMap[M ~map[K]V, K, K1 comparable, V, V1 any](in M, fn func(K, V) (K1, V1)) map[K1]V1 { 112 | res := make(map[K1]V1, len(in)) 113 | for k, v := range in { 114 | k1, v1 := fn(k, v) 115 | res[k1] = v1 116 | } 117 | 118 | return res 119 | } 120 | 121 | // MapMapErr applies fn to all kv pairs from in. 122 | func MapMapErr[M ~map[K]V, K, K1 comparable, V, V1 any](in M, fn func(K, V) (K1, V1, error)) (map[K1]V1, error) { 123 | res := make(map[K1]V1, len(in)) 124 | for k, v := range in { 125 | k1, v1, err := fn(k, v) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | res[k1] = v1 131 | } 132 | 133 | return res, nil 134 | } 135 | 136 | // MapContainsKey returns true if key is exists in the map. 137 | func MapContainsKey[M ~map[K]V, K comparable, V any](m M, key K) bool { 138 | _, ok := m[key] 139 | 140 | return ok 141 | } 142 | 143 | // MapContainsKeysAny returns true when at least one key exists in the map. 144 | func MapContainsKeysAny[M ~map[K]V, K comparable, V any](m M, keys []K) bool { 145 | if len(keys) == 0 { 146 | return false 147 | } 148 | 149 | if len(m) == 0 { 150 | return false 151 | } 152 | 153 | for i := range keys { 154 | if MapContainsKey(m, keys[i]) { 155 | return true 156 | } 157 | } 158 | 159 | return false 160 | } 161 | 162 | // MapContainsKeysAll returns true when at all keys exist in the map. 163 | func MapContainsKeysAll[M ~map[K]V, K comparable, V any](m M, keys []K) bool { 164 | if len(keys) == 0 { 165 | return false 166 | } 167 | 168 | if len(m) == 0 { 169 | return false 170 | } 171 | 172 | for i := range keys { 173 | if !MapContainsKey(m, keys[i]) { 174 | return false 175 | } 176 | } 177 | 178 | return true 179 | } 180 | 181 | // MapApply applies fn to each kv pair 182 | func MapApply[M ~map[K]V, K comparable, V any](in M, fn func(k K, v V)) { 183 | for k, v := range in { 184 | fn(k, v) 185 | } 186 | } 187 | 188 | // MapJoin will create a new map containing all key-value pairs from app input 189 | // maps. If several maps have duplicate keys - the last write wins. 190 | func MapJoin[M ~map[K]V, K comparable, V any](maps ...M) M { 191 | res := make(M) 192 | for i := range maps { 193 | for k, v := range maps[i] { 194 | res[k] = v 195 | } 196 | } 197 | 198 | return res 199 | } 200 | 201 | // MapGetDefault returns a value for a given key or default value if the key 202 | // is not present in the source map. 203 | func MapGetDefault[M ~map[K]V, K comparable, V any](in M, key K, defaultVal V) V { 204 | v, ok := in[key] 205 | if !ok { 206 | return defaultVal 207 | } 208 | 209 | return v 210 | } 211 | 212 | // MapNotNil returns the source map when it is not nil or creates an empty 213 | // instance of this type. 214 | func MapNotNil[T ~map[K]V, K comparable, V any](in T) T { 215 | if in == nil { 216 | return make(T, 0) 217 | } 218 | 219 | return in 220 | } 221 | 222 | // MapDropKeys remove all keys from the source map. Map will change in place. 223 | func MapDropKeys[M ~map[K]V, K comparable, V any](in M, keys ...K) { 224 | if len(keys) == 0 || len(in) == 0 { 225 | return 226 | } 227 | 228 | for i := range keys { 229 | delete(in, keys[i]) 230 | } 231 | } 232 | 233 | // MapPopKeyDefault will return value for given key and delete this key from source map. 234 | // In case of key do not presented in map - returns default value. 235 | func MapPopKeyDefault[M ~map[K]V, K comparable, V any](in M, key K, def V) V { 236 | val, ok := in[key] 237 | if ok { 238 | delete(in, key) 239 | return val 240 | } 241 | 242 | return def 243 | } 244 | 245 | // MapSetVal update key with value for given map and return updated map. 246 | func MapSetVal[M ~map[K]V, K comparable, V any](in M, key K, val V) M { 247 | in[key] = val 248 | return in 249 | } 250 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= 5 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 6 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 7 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= 8 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= 9 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 10 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 11 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 12 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 13 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 14 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 15 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 16 | github.com/goccy/go-yaml v1.9.6 h1:KhAu1zf9JXnm3vbG49aDE0E5uEBUsM4uwD31/58ZWyI= 17 | github.com/goccy/go-yaml v1.9.6/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= 18 | github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= 19 | github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= 20 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 21 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 22 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 23 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 24 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 25 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 26 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 27 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 28 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 29 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 30 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 31 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 32 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 34 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 35 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 36 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 37 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 38 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 39 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 40 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 42 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 43 | golang.org/x/exp v0.0.0-20221109134031-9ce248df8de5 h1:pug8He0YPdPwDXZN4B/1sJ/8d4YsZqwVZEzg/Xi5w3Y= 44 | golang.org/x/exp v0.0.0-20221109134031-9ce248df8de5/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 45 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= 46 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= 47 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 48 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 49 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 53 | golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 | golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= 56 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= 59 | golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 61 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 62 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 63 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 64 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= 65 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 66 | golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= 67 | golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 68 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 71 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 72 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 73 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 74 | -------------------------------------------------------------------------------- /null_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "database/sql" 5 | "github.com/kazhuravlev/just" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestNullConstructor(t *testing.T) { 11 | t.Parallel() 12 | 13 | t.Run("null_null", func(t *testing.T) { 14 | v1 := just.NullNull[int]() 15 | assert.Equal(t, just.NullVal[int]{Val: 0, Valid: false}, v1) 16 | 17 | v2 := just.NullNull[string]() 18 | assert.Equal(t, just.NullVal[string]{Val: "", Valid: false}, v2) 19 | 20 | type user struct { 21 | Name string 22 | Email string 23 | } 24 | 25 | v3 := just.NullNull[user]() 26 | assert.Equal(t, just.NullVal[user]{Val: user{Name: "", Email: ""}, Valid: false}, v3) 27 | }) 28 | 29 | t.Run("null", func(t *testing.T) { 30 | v1 := just.Null[int](10) 31 | assert.Equal(t, just.NullVal[int]{Val: 10, Valid: true}, v1) 32 | 33 | v2 := just.Null[string]("Hello") 34 | assert.Equal(t, just.NullVal[string]{Val: "Hello", Valid: true}, v2) 35 | 36 | type user struct { 37 | Name string 38 | Email string 39 | } 40 | 41 | v3 := just.Null[user](user{Name: "Joe", Email: "joe@example.com"}) 42 | assert.Equal(t, just.NullVal[user]{Val: user{Name: "Joe", Email: "joe@example.com"}, Valid: true}, v3) 43 | }) 44 | 45 | t.Run("set_default", func(t *testing.T) { 46 | v := just.NullNull[int]() 47 | assert.True(t, v.SetDefault(10)) 48 | assert.Equal(t, 10, v.Val) 49 | 50 | v2 := just.Null[int](10) 51 | assert.False(t, v2.SetDefault(42)) 52 | assert.Equal(t, 10, v2.Val) 53 | }) 54 | 55 | t.Run("value_ok", func(t *testing.T) { 56 | v1, ok := just.NullNull[int]().ValueOk() 57 | assert.Equal(t, 0, v1) 58 | assert.False(t, ok) 59 | 60 | v2, ok := just.Null[int](10).ValueOk() 61 | assert.Equal(t, 10, v2) 62 | assert.True(t, ok) 63 | }) 64 | 65 | t.Run("value", func(t *testing.T) { 66 | v1, err := just.NullNull[int]().Value() 67 | assert.Equal(t, nil, v1) 68 | assert.NoError(t, err) 69 | 70 | v2, err := just.NullNull[sql.NullBool]().Value() 71 | assert.Equal(t, nil, v2) 72 | assert.NoError(t, err) 73 | 74 | v3, err := just.Null[int](10).Value() 75 | assert.Equal(t, 10, v3) 76 | assert.NoError(t, err) 77 | 78 | v4, err := just.Null[sql.NullBool](sql.NullBool{Bool: true, Valid: true}).Value() 79 | assert.Equal(t, true, v4) 80 | assert.NoError(t, err) 81 | }) 82 | 83 | t.Run("scan", func(t *testing.T) { 84 | v1 := just.NullNull[int]() 85 | assert.NoError(t, v1.Scan(10)) 86 | assert.Equal(t, 10, v1.Val) 87 | assert.True(t, v1.Valid) 88 | 89 | v2 := just.NullNull[sql.NullString]() 90 | assert.NoError(t, v2.Scan("hi")) 91 | assert.Equal(t, sql.NullString{String: "hi", Valid: true}, v2.Val) 92 | assert.True(t, v2.Valid) 93 | 94 | v3 := just.NullNull[int]() 95 | assert.NoError(t, v3.Scan(nil)) 96 | assert.Equal(t, 0, v3.Val) 97 | assert.True(t, v3.Valid) 98 | 99 | v4 := just.NullNull[int]() 100 | assert.Error(t, v4.Scan("this is not integer")) 101 | assert.Equal(t, 0, v4.Val) 102 | assert.False(t, v4.Valid) 103 | 104 | v5 := just.NullNull[sql.NullInt64]() 105 | assert.Error(t, v5.Scan("this is not int64")) 106 | assert.Equal(t, sql.NullInt64{Int64: 0, Valid: false}, v5.Val) 107 | assert.False(t, v5.Valid) 108 | }) 109 | } 110 | 111 | func TestNullDefaultFalse(t *testing.T) { 112 | t.Parallel() 113 | 114 | tableWithValidFalse := []bool{ 115 | just.NullDefaultFalse(0).Valid, 116 | just.NullDefaultFalse("").Valid, 117 | just.NullDefaultFalse(false).Valid, 118 | } 119 | 120 | for _, row := range tableWithValidFalse { 121 | t.Run("", func(t *testing.T) { 122 | assert.False(t, row) 123 | }) 124 | } 125 | 126 | tableWithValidTrue := []bool{ 127 | just.NullDefaultFalse(1).Valid, 128 | just.NullDefaultFalse("1").Valid, 129 | just.NullDefaultFalse(true).Valid, 130 | } 131 | 132 | for _, row := range tableWithValidTrue { 133 | t.Run("", func(t *testing.T) { 134 | assert.True(t, row) 135 | }) 136 | } 137 | } 138 | 139 | func TestNullValYAML(t *testing.T) { 140 | t.Parallel() 141 | 142 | t.Run("UnmarshalYAML", func(t *testing.T) { 143 | t.Run("empty bytes results in invalid null", func(t *testing.T) { 144 | var nv just.NullVal[string] 145 | err := nv.UnmarshalYAML([]byte{}) 146 | assert.NoError(t, err) 147 | assert.False(t, nv.Valid) 148 | assert.Equal(t, "", nv.Val) 149 | }) 150 | 151 | t.Run("null string results in invalid null", func(t *testing.T) { 152 | var nv just.NullVal[int] 153 | err := nv.UnmarshalYAML([]byte("null")) 154 | assert.NoError(t, err) 155 | assert.True(t, nv.Valid) 156 | assert.Equal(t, 0, nv.Val) 157 | }) 158 | 159 | t.Run("valid string value", func(t *testing.T) { 160 | var nv just.NullVal[string] 161 | err := nv.UnmarshalYAML([]byte("hello world")) 162 | assert.NoError(t, err) 163 | assert.True(t, nv.Valid) 164 | assert.Equal(t, "hello world", nv.Val) 165 | }) 166 | 167 | t.Run("valid int value", func(t *testing.T) { 168 | var nv just.NullVal[int] 169 | err := nv.UnmarshalYAML([]byte("42")) 170 | assert.NoError(t, err) 171 | assert.True(t, nv.Valid) 172 | assert.Equal(t, 42, nv.Val) 173 | }) 174 | 175 | t.Run("valid bool value", func(t *testing.T) { 176 | var nv just.NullVal[bool] 177 | err := nv.UnmarshalYAML([]byte("true")) 178 | assert.NoError(t, err) 179 | assert.True(t, nv.Valid) 180 | assert.Equal(t, true, nv.Val) 181 | }) 182 | 183 | t.Run("valid struct value", func(t *testing.T) { 184 | type person struct { 185 | Name string `yaml:"name"` 186 | Age int `yaml:"age"` 187 | } 188 | 189 | var nv just.NullVal[person] 190 | yamlData := []byte("name: Alice\nage: 30") 191 | err := nv.UnmarshalYAML(yamlData) 192 | assert.NoError(t, err) 193 | assert.True(t, nv.Valid) 194 | assert.Equal(t, person{Name: "Alice", Age: 30}, nv.Val) 195 | }) 196 | 197 | t.Run("invalid yaml returns error", func(t *testing.T) { 198 | var nv just.NullVal[int] 199 | err := nv.UnmarshalYAML([]byte("not a number")) 200 | assert.Error(t, err) 201 | }) 202 | }) 203 | 204 | t.Run("MarshalYAML", func(t *testing.T) { 205 | t.Run("invalid null marshals to null", func(t *testing.T) { 206 | nv := just.NullNull[string]() 207 | data, err := nv.MarshalYAML() 208 | assert.NoError(t, err) 209 | assert.Equal(t, []byte("null"), data) 210 | }) 211 | 212 | t.Run("valid string value", func(t *testing.T) { 213 | nv := just.Null("hello world") 214 | data, err := nv.MarshalYAML() 215 | assert.NoError(t, err) 216 | assert.Equal(t, "hello world\n", string(data)) 217 | }) 218 | 219 | t.Run("valid int value", func(t *testing.T) { 220 | nv := just.Null(42) 221 | data, err := nv.MarshalYAML() 222 | assert.NoError(t, err) 223 | assert.Equal(t, "42\n", string(data)) 224 | }) 225 | 226 | t.Run("valid bool value", func(t *testing.T) { 227 | nv := just.Null(true) 228 | data, err := nv.MarshalYAML() 229 | assert.NoError(t, err) 230 | assert.Equal(t, "true\n", string(data)) 231 | }) 232 | 233 | t.Run("valid struct value", func(t *testing.T) { 234 | type person struct { 235 | Name string `yaml:"name"` 236 | Age int `yaml:"age"` 237 | } 238 | 239 | nv := just.Null(person{Name: "Bob", Age: 25}) 240 | data, err := nv.MarshalYAML() 241 | assert.NoError(t, err) 242 | assert.Contains(t, string(data), "name: Bob") 243 | assert.Contains(t, string(data), "age: 25") 244 | }) 245 | 246 | t.Run("zero value but valid", func(t *testing.T) { 247 | nv := just.Null(0) 248 | data, err := nv.MarshalYAML() 249 | assert.NoError(t, err) 250 | assert.Equal(t, "0\n", string(data)) 251 | }) 252 | }) 253 | 254 | t.Run("Round trip marshaling", func(t *testing.T) { 255 | t.Run("valid value", func(t *testing.T) { 256 | original := just.Null("test string") 257 | 258 | // Marshal 259 | data, err := original.MarshalYAML() 260 | assert.NoError(t, err) 261 | 262 | // Unmarshal 263 | var result just.NullVal[string] 264 | err = result.UnmarshalYAML(data) 265 | assert.NoError(t, err) 266 | 267 | assert.Equal(t, original, result) 268 | }) 269 | 270 | t.Run("invalid value", func(t *testing.T) { 271 | original := just.NullNull[int]() 272 | 273 | // Marshal 274 | data, err := original.MarshalYAML() 275 | assert.NoError(t, err) 276 | assert.Equal(t, []byte("null"), data) 277 | 278 | // Note: UnmarshalYAML treats "null" as a valid value with zero value 279 | // This is a limitation of the current implementation 280 | }) 281 | }) 282 | } 283 | -------------------------------------------------------------------------------- /map_bench_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/kazhuravlev/just" 10 | ) 11 | 12 | // Helper functions to create test maps of various sizes 13 | func createIntMap(size int) map[int]int { 14 | m := make(map[int]int, size) 15 | for i := 0; i < size; i++ { 16 | m[i] = i * 2 17 | } 18 | return m 19 | } 20 | 21 | func createStringMap(size int) map[string]string { 22 | m := make(map[string]string, size) 23 | for i := 0; i < size; i++ { 24 | m[strconv.Itoa(i)] = fmt.Sprintf("value_%d", i) 25 | } 26 | return m 27 | } 28 | 29 | // BenchmarkMapMerge benchmarks the MapMerge function 30 | func BenchmarkMapMerge(b *testing.B) { 31 | sizes := []int{10, 1000} 32 | 33 | for _, size := range sizes { 34 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 35 | m1 := createIntMap(size) 36 | m2 := createIntMap(size) 37 | // Half overlapping keys 38 | for i := size / 2; i < size+size/2; i++ { 39 | m2[i] = i * 3 40 | } 41 | 42 | b.ResetTimer() 43 | for i := 0; i < b.N; i++ { 44 | _ = just.MapMerge(m1, m2, func(k, v1, v2 int) int { 45 | return v1 + v2 46 | }) 47 | } 48 | }) 49 | } 50 | } 51 | 52 | // BenchmarkMapFilter benchmarks the MapFilter function 53 | func BenchmarkMapFilter(b *testing.B) { 54 | sizes := []int{10, 1000} 55 | 56 | for _, size := range sizes { 57 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 58 | m := createIntMap(size) 59 | 60 | b.ResetTimer() 61 | for i := 0; i < b.N; i++ { 62 | _ = just.MapFilter(m, func(k, v int) bool { 63 | return v%2 == 0 && k < size/2 64 | }) 65 | } 66 | }) 67 | } 68 | } 69 | 70 | // BenchmarkMapFilterKeys benchmarks the MapFilterKeys function 71 | func BenchmarkMapFilterKeys(b *testing.B) { 72 | sizes := []int{10, 1000} 73 | 74 | for _, size := range sizes { 75 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 76 | m := createIntMap(size) 77 | 78 | b.ResetTimer() 79 | for i := 0; i < b.N; i++ { 80 | _ = just.MapFilterKeys(m, func(k int) bool { 81 | return k%2 == 0 82 | }) 83 | } 84 | }) 85 | } 86 | } 87 | 88 | // BenchmarkMapFilterValues benchmarks the MapFilterValues function 89 | func BenchmarkMapFilterValues(b *testing.B) { 90 | sizes := []int{10, 1000} 91 | 92 | for _, size := range sizes { 93 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 94 | m := createIntMap(size) 95 | 96 | b.ResetTimer() 97 | for i := 0; i < b.N; i++ { 98 | _ = just.MapFilterValues(m, func(v int) bool { 99 | return v > size/2 100 | }) 101 | } 102 | }) 103 | } 104 | } 105 | 106 | // BenchmarkMapGetKeys benchmarks the MapGetKeys function 107 | func BenchmarkMapGetKeys(b *testing.B) { 108 | sizes := []int{10, 1000} 109 | 110 | for _, size := range sizes { 111 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 112 | m := createIntMap(size) 113 | 114 | b.ResetTimer() 115 | for i := 0; i < b.N; i++ { 116 | _ = just.MapGetKeys(m) 117 | } 118 | }) 119 | } 120 | } 121 | 122 | // BenchmarkMapGetValues benchmarks the MapGetValues function 123 | func BenchmarkMapGetValues(b *testing.B) { 124 | sizes := []int{10, 1000} 125 | 126 | for _, size := range sizes { 127 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 128 | m := createIntMap(size) 129 | 130 | b.ResetTimer() 131 | for i := 0; i < b.N; i++ { 132 | _ = just.MapGetValues(m) 133 | } 134 | }) 135 | } 136 | } 137 | 138 | // BenchmarkMapPairs benchmarks the MapPairs function 139 | func BenchmarkMapPairs(b *testing.B) { 140 | sizes := []int{10, 1000} 141 | 142 | for _, size := range sizes { 143 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 144 | m := createIntMap(size) 145 | 146 | b.ResetTimer() 147 | for i := 0; i < b.N; i++ { 148 | _ = just.MapPairs(m) 149 | } 150 | }) 151 | } 152 | } 153 | 154 | // BenchmarkMapDefaults benchmarks the MapDefaults function 155 | func BenchmarkMapDefaults(b *testing.B) { 156 | sizes := []int{10, 1000} 157 | 158 | for _, size := range sizes { 159 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 160 | m := createIntMap(size / 2) // Half filled 161 | defaults := createIntMap(size) 162 | 163 | b.ResetTimer() 164 | for i := 0; i < b.N; i++ { 165 | _ = just.MapDefaults(m, defaults) 166 | } 167 | }) 168 | } 169 | } 170 | 171 | // BenchmarkMapCopy benchmarks the MapCopy function 172 | func BenchmarkMapCopy(b *testing.B) { 173 | sizes := []int{10, 1000} 174 | 175 | for _, size := range sizes { 176 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 177 | m := createIntMap(size) 178 | 179 | b.ResetTimer() 180 | for i := 0; i < b.N; i++ { 181 | _ = just.MapCopy(m) 182 | } 183 | }) 184 | } 185 | } 186 | 187 | // BenchmarkMapMap benchmarks the MapMap function 188 | func BenchmarkMapMap(b *testing.B) { 189 | sizes := []int{10, 1000} 190 | 191 | for _, size := range sizes { 192 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 193 | m := createIntMap(size) 194 | 195 | b.ResetTimer() 196 | for i := 0; i < b.N; i++ { 197 | _ = just.MapMap(m, func(k, v int) (string, string) { 198 | return strconv.Itoa(k), strconv.Itoa(v) 199 | }) 200 | } 201 | }) 202 | } 203 | } 204 | 205 | // BenchmarkMapMapErr benchmarks the MapMapErr function 206 | func BenchmarkMapMapErr(b *testing.B) { 207 | sizes := []int{10, 1000} 208 | 209 | for _, size := range sizes { 210 | b.Run(fmt.Sprintf("size_%d_success", size), func(b *testing.B) { 211 | m := createIntMap(size) 212 | 213 | b.ResetTimer() 214 | for i := 0; i < b.N; i++ { 215 | _, _ = just.MapMapErr(m, func(k, v int) (string, string, error) { 216 | return strconv.Itoa(k), strconv.Itoa(v), nil 217 | }) 218 | } 219 | }) 220 | 221 | b.Run(fmt.Sprintf("size_%d_error_early", size), func(b *testing.B) { 222 | m := createIntMap(size) 223 | 224 | b.ResetTimer() 225 | for i := 0; i < b.N; i++ { 226 | _, _ = just.MapMapErr(m, func(k, v int) (string, string, error) { 227 | if k == 0 { 228 | return "", "", errors.New("error") 229 | } 230 | return strconv.Itoa(k), strconv.Itoa(v), nil 231 | }) 232 | } 233 | }) 234 | } 235 | } 236 | 237 | // BenchmarkMapContainsKey benchmarks the MapContainsKey function 238 | func BenchmarkMapContainsKey(b *testing.B) { 239 | sizes := []int{10, 1000} 240 | 241 | for _, size := range sizes { 242 | b.Run(fmt.Sprintf("size_%d_exists", size), func(b *testing.B) { 243 | m := createIntMap(size) 244 | key := size / 2 245 | 246 | b.ResetTimer() 247 | for i := 0; i < b.N; i++ { 248 | _ = just.MapContainsKey(m, key) 249 | } 250 | }) 251 | 252 | b.Run(fmt.Sprintf("size_%d_not_exists", size), func(b *testing.B) { 253 | m := createIntMap(size) 254 | key := size + 1 255 | 256 | b.ResetTimer() 257 | for i := 0; i < b.N; i++ { 258 | _ = just.MapContainsKey(m, key) 259 | } 260 | }) 261 | } 262 | } 263 | 264 | // BenchmarkMapContainsKeysAny benchmarks the MapContainsKeysAny function 265 | func BenchmarkMapContainsKeysAny(b *testing.B) { 266 | sizes := []int{10, 1000} 267 | keyCounts := []int{1, 10, 100} 268 | 269 | for _, size := range sizes { 270 | for _, keyCount := range keyCounts { 271 | if keyCount > size { 272 | continue 273 | } 274 | 275 | b.Run(fmt.Sprintf("size_%d_keys_%d_found", size, keyCount), func(b *testing.B) { 276 | m := createIntMap(size) 277 | keys := make([]int, keyCount) 278 | for i := 0; i < keyCount; i++ { 279 | keys[i] = i 280 | } 281 | 282 | b.ResetTimer() 283 | for i := 0; i < b.N; i++ { 284 | _ = just.MapContainsKeysAny(m, keys) 285 | } 286 | }) 287 | 288 | b.Run(fmt.Sprintf("size_%d_keys_%d_not_found", size, keyCount), func(b *testing.B) { 289 | m := createIntMap(size) 290 | keys := make([]int, keyCount) 291 | for i := 0; i < keyCount; i++ { 292 | keys[i] = size + i + 1 293 | } 294 | 295 | b.ResetTimer() 296 | for i := 0; i < b.N; i++ { 297 | _ = just.MapContainsKeysAny(m, keys) 298 | } 299 | }) 300 | } 301 | } 302 | } 303 | 304 | // BenchmarkMapContainsKeysAll benchmarks the MapContainsKeysAll function 305 | func BenchmarkMapContainsKeysAll(b *testing.B) { 306 | sizes := []int{10, 1000} 307 | keyCounts := []int{1, 10, 100} 308 | 309 | for _, size := range sizes { 310 | for _, keyCount := range keyCounts { 311 | if keyCount > size { 312 | continue 313 | } 314 | 315 | b.Run(fmt.Sprintf("size_%d_keys_%d_all_found", size, keyCount), func(b *testing.B) { 316 | m := createIntMap(size) 317 | keys := make([]int, keyCount) 318 | for i := 0; i < keyCount; i++ { 319 | keys[i] = i 320 | } 321 | 322 | b.ResetTimer() 323 | for i := 0; i < b.N; i++ { 324 | _ = just.MapContainsKeysAll(m, keys) 325 | } 326 | }) 327 | 328 | b.Run(fmt.Sprintf("size_%d_keys_%d_one_missing", size, keyCount), func(b *testing.B) { 329 | m := createIntMap(size) 330 | keys := make([]int, keyCount) 331 | for i := 0; i < keyCount-1; i++ { 332 | keys[i] = i 333 | } 334 | keys[keyCount-1] = size + 1 // One key that doesn't exist 335 | 336 | b.ResetTimer() 337 | for i := 0; i < b.N; i++ { 338 | _ = just.MapContainsKeysAll(m, keys) 339 | } 340 | }) 341 | } 342 | } 343 | } 344 | 345 | // BenchmarkMapApply benchmarks the MapApply function 346 | func BenchmarkMapApply(b *testing.B) { 347 | sizes := []int{10, 1000} 348 | 349 | for _, size := range sizes { 350 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 351 | m := createIntMap(size) 352 | sum := 0 353 | 354 | b.ResetTimer() 355 | for i := 0; i < b.N; i++ { 356 | just.MapApply(m, func(k, v int) { 357 | sum += v // Simple operation 358 | }) 359 | } 360 | }) 361 | } 362 | } 363 | 364 | // BenchmarkMapJoin benchmarks the MapJoin function 365 | func BenchmarkMapJoin(b *testing.B) { 366 | mapCounts := []int{2, 5, 10} 367 | sizes := []int{10, 100, 1000} 368 | 369 | for _, mapCount := range mapCounts { 370 | for _, size := range sizes { 371 | b.Run(fmt.Sprintf("maps_%d_size_%d", mapCount, size), func(b *testing.B) { 372 | maps := make([]map[int]int, mapCount) 373 | for i := 0; i < mapCount; i++ { 374 | m := make(map[int]int, size) 375 | for j := 0; j < size; j++ { 376 | m[i*size+j] = j 377 | } 378 | maps[i] = m 379 | } 380 | 381 | b.ResetTimer() 382 | for i := 0; i < b.N; i++ { 383 | _ = just.MapJoin(maps...) 384 | } 385 | }) 386 | } 387 | } 388 | } 389 | 390 | // BenchmarkMapGetDefault benchmarks the MapGetDefault function 391 | func BenchmarkMapGetDefault(b *testing.B) { 392 | sizes := []int{10, 1000} 393 | 394 | for _, size := range sizes { 395 | b.Run(fmt.Sprintf("size_%d_exists", size), func(b *testing.B) { 396 | m := createIntMap(size) 397 | key := size / 2 398 | defaultVal := -1 399 | 400 | b.ResetTimer() 401 | for i := 0; i < b.N; i++ { 402 | _ = just.MapGetDefault(m, key, defaultVal) 403 | } 404 | }) 405 | 406 | b.Run(fmt.Sprintf("size_%d_not_exists", size), func(b *testing.B) { 407 | m := createIntMap(size) 408 | key := size + 1 409 | defaultVal := -1 410 | 411 | b.ResetTimer() 412 | for i := 0; i < b.N; i++ { 413 | _ = just.MapGetDefault(m, key, defaultVal) 414 | } 415 | }) 416 | } 417 | } 418 | 419 | // BenchmarkMapNotNil benchmarks the MapNotNil function 420 | func BenchmarkMapNotNil(b *testing.B) { 421 | b.Run("nil_map", func(b *testing.B) { 422 | var m map[int]int 423 | 424 | b.ResetTimer() 425 | for i := 0; i < b.N; i++ { 426 | _ = just.MapNotNil(m) 427 | } 428 | }) 429 | 430 | b.Run("non_nil_map", func(b *testing.B) { 431 | m := createIntMap(100) 432 | 433 | b.ResetTimer() 434 | for i := 0; i < b.N; i++ { 435 | _ = just.MapNotNil(m) 436 | } 437 | }) 438 | } 439 | 440 | // BenchmarkMapDropKeys benchmarks the MapDropKeys function 441 | func BenchmarkMapDropKeys(b *testing.B) { 442 | sizes := []int{10, 1000} 443 | dropCounts := []int{1, 10, 100} 444 | 445 | for _, size := range sizes { 446 | for _, dropCount := range dropCounts { 447 | if dropCount > size { 448 | continue 449 | } 450 | 451 | b.Run(fmt.Sprintf("size_%d_drop_%d", size, dropCount), func(b *testing.B) { 452 | keys := make([]int, dropCount) 453 | for i := 0; i < dropCount; i++ { 454 | keys[i] = i 455 | } 456 | 457 | b.ResetTimer() 458 | for i := 0; i < b.N; i++ { 459 | m := createIntMap(size) 460 | just.MapDropKeys(m, keys...) 461 | } 462 | }) 463 | } 464 | } 465 | } 466 | 467 | // BenchmarkMapPopKeyDefault benchmarks the MapPopKeyDefault function 468 | func BenchmarkMapPopKeyDefault(b *testing.B) { 469 | sizes := []int{10, 1000} 470 | 471 | for _, size := range sizes { 472 | b.Run(fmt.Sprintf("size_%d_exists", size), func(b *testing.B) { 473 | defaultVal := -1 474 | 475 | b.ResetTimer() 476 | for i := 0; i < b.N; i++ { 477 | m := createIntMap(size) 478 | key := size / 2 479 | _ = just.MapPopKeyDefault(m, key, defaultVal) 480 | } 481 | }) 482 | 483 | b.Run(fmt.Sprintf("size_%d_not_exists", size), func(b *testing.B) { 484 | m := createIntMap(size) 485 | key := size + 1 486 | defaultVal := -1 487 | 488 | b.ResetTimer() 489 | for i := 0; i < b.N; i++ { 490 | _ = just.MapPopKeyDefault(m, key, defaultVal) 491 | } 492 | }) 493 | } 494 | } 495 | 496 | // BenchmarkMapSetVal benchmarks the MapSetVal function 497 | func BenchmarkMapSetVal(b *testing.B) { 498 | sizes := []int{10, 1000} 499 | 500 | for _, size := range sizes { 501 | b.Run(fmt.Sprintf("size_%d_update", size), func(b *testing.B) { 502 | key := size / 2 503 | val := 999 504 | 505 | b.ResetTimer() 506 | for i := 0; i < b.N; i++ { 507 | m := createIntMap(size) 508 | _ = just.MapSetVal(m, key, val) 509 | } 510 | }) 511 | 512 | b.Run(fmt.Sprintf("size_%d_add", size), func(b *testing.B) { 513 | key := size + 1 514 | val := 999 515 | 516 | b.ResetTimer() 517 | for i := 0; i < b.N; i++ { 518 | m := createIntMap(size) 519 | _ = just.MapSetVal(m, key, val) 520 | } 521 | }) 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /slice.go: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import ( 4 | "math/rand" 5 | "sort" 6 | ) 7 | 8 | // SliceUniq returns unique values from `in`. 9 | func SliceUniq[T comparable](in []T) []T { 10 | index := Slice2Map(in) 11 | 12 | res := make([]T, 0, len(index)) 13 | for k := range index { 14 | res = append(res, k) 15 | } 16 | 17 | return res 18 | } 19 | 20 | // SliceUniqStable returns unique values from `in`. Keep original ordering. 21 | func SliceUniqStable[T comparable](in []T) []T { 22 | index := make(map[T]struct{}, len(in)) 23 | 24 | res := make([]T, 0, len(in)) 25 | for i := range in { 26 | if MapContainsKey(index, in[i]) { 27 | continue 28 | } 29 | 30 | index[in[i]] = struct{}{} 31 | 32 | res = append(res, in[i]) 33 | } 34 | 35 | return res 36 | } 37 | 38 | // SliceMap returns the slice where each element of `in` was handled by `fn`. 39 | func SliceMap[T any, V any](in []T, fn func(T) V) []V { 40 | if len(in) == 0 { 41 | return make([]V, 0) 42 | } 43 | 44 | res := make([]V, len(in)) 45 | for i := range in { 46 | res[i] = fn(in[i]) 47 | } 48 | 49 | return res 50 | } 51 | 52 | // SliceFlatMap applies `fn` to each element of `in` and join all output slices. 53 | func SliceFlatMap[T, V any](in []T, fn func(val T) []V) []V { 54 | if len(in) == 0 { 55 | return make([]V, 0) 56 | } 57 | 58 | res := make([]V, 0, len(in)) 59 | for i := range in { 60 | slice := fn(in[i]) 61 | res = append(res, slice...) 62 | } 63 | 64 | return res 65 | } 66 | 67 | // SliceFlatMap2 does the same as SliceFlatMap but receives the index 68 | // of the element. 69 | func SliceFlatMap2[T, V any](in []T, fn func(i int, val T) []V) []V { 70 | if len(in) == 0 { 71 | return make([]V, 0) 72 | } 73 | 74 | var res []V 75 | for i := range in { 76 | slice := fn(i, in[i]) 77 | res = append(res, slice...) 78 | } 79 | 80 | return res 81 | } 82 | 83 | // SliceApply handles all elements from `in` by function `fn`. Function 84 | // applies sequentially. 85 | func SliceApply[T any](in []T, fn func(int, T)) { 86 | if len(in) == 0 { 87 | return 88 | } 89 | 90 | for i := range in { 91 | fn(i, in[i]) 92 | } 93 | } 94 | 95 | // SliceMapErr does the same thing as SliceMap but returns an error when 96 | // an error occurs in fn. 97 | func SliceMapErr[T any, V any](in []T, fn func(T) (V, error)) ([]V, error) { 98 | if len(in) == 0 { 99 | return make([]V, 0), nil 100 | } 101 | 102 | res := make([]V, len(in)) 103 | var err error 104 | for i := range in { 105 | res[i], err = fn(in[i]) 106 | if err != nil { 107 | return nil, err 108 | } 109 | } 110 | 111 | return res, nil 112 | } 113 | 114 | // SliceFilter returns a slice of values from `in` where `fn(elem) == true`. 115 | func SliceFilter[T any](in []T, fn func(T) bool) []T { 116 | if len(in) == 0 { 117 | return make([]T, 0) 118 | } 119 | 120 | res := make([]T, 0, len(in)) 121 | for i := range in { 122 | if !fn(in[i]) { 123 | continue 124 | } 125 | 126 | res = append(res, in[i]) 127 | } 128 | 129 | return res 130 | } 131 | 132 | // SliceReverse reverse the slice. 133 | func SliceReverse[T any](in []T) []T { 134 | if len(in) == 0 { 135 | return make([]T, 0) 136 | } 137 | 138 | res := make([]T, len(in)) 139 | for i := range in { 140 | res[i] = in[len(in)-i-1] 141 | } 142 | 143 | return res 144 | } 145 | 146 | // SliceAny returns true when `fn` returns true for at least one element 147 | // from `in`. 148 | func SliceAny[T any](in []T, fn func(T) bool) bool { 149 | for i := range in { 150 | if fn(in[i]) { 151 | return true 152 | } 153 | } 154 | 155 | return false 156 | } 157 | 158 | // SliceAll returns true when `fn` returns true for all elements from `in`. 159 | // Returns true when in is empty. 160 | func SliceAll[T any](in []T, fn func(T) bool) bool { 161 | for i := range in { 162 | if !fn(in[i]) { 163 | return false 164 | } 165 | } 166 | 167 | return true 168 | } 169 | 170 | // SliceContainsElem returns true when `in` contains elem. 171 | func SliceContainsElem[T comparable](in []T, elem T) bool { 172 | return SliceAny(in, func(v T) bool { return v == elem }) 173 | } 174 | 175 | // SliceAddNotExists return `in` with `elem` inside when `elem` not exists in 176 | // `in`. 177 | func SliceAddNotExists[T comparable](in []T, elem T) []T { 178 | for i := range in { 179 | if in[i] == elem { 180 | return in 181 | } 182 | } 183 | 184 | return append(in, elem) 185 | } 186 | 187 | // SliceUnion returns only uniq items from all slices. 188 | func SliceUnion[T comparable](in ...[]T) []T { 189 | var res []T 190 | for i := range in { 191 | res = append(res, in[i]...) 192 | } 193 | 194 | return SliceUniq[T](res) 195 | } 196 | 197 | // Slice2Map make map from slice, which contains all values from `in` as map 198 | // keys. 199 | func Slice2Map[T comparable](in []T) map[T]struct{} { 200 | res := make(map[T]struct{}, len(in)) 201 | for i := range in { 202 | res[in[i]] = struct{}{} 203 | } 204 | 205 | return res 206 | } 207 | 208 | // SliceDifference returns the difference between `oldSlice` and `newSlice`. 209 | // Returns only elements presented in `newSlice` but not presented 210 | // in `oldSlice`. 211 | // Example: [1,2,3], [3,4,5,5,5] => [4,5,5,5] 212 | func SliceDifference[T comparable](oldSlice, newSlice []T) []T { 213 | if len(oldSlice) == 0 { 214 | return newSlice 215 | } 216 | 217 | if len(newSlice) == 0 { 218 | return make([]T, 0) 219 | } 220 | 221 | index := Slice2Map(oldSlice) 222 | res := make([]T, 0, len(newSlice)) 223 | for i := range newSlice { 224 | if _, ok := index[newSlice[i]]; ok { 225 | continue 226 | } 227 | 228 | res = append(res, newSlice[i]) 229 | } 230 | 231 | return SliceUniq(res) 232 | } 233 | 234 | // SliceIntersection returns elements that are presented in both slices. 235 | // Example: [1,2,3], [2,4,3,3,3] => [2, 3] 236 | func SliceIntersection[T comparable](oldSlice, newSlice []T) []T { 237 | if len(oldSlice) == 0 { 238 | return make([]T, 0) 239 | } 240 | 241 | if len(newSlice) == 0 { 242 | return make([]T, 0) 243 | } 244 | 245 | index := Slice2Map(oldSlice) 246 | res := make([]T, 0, len(newSlice)) 247 | for i := range newSlice { 248 | if _, ok := index[newSlice[i]]; !ok { 249 | continue 250 | } 251 | 252 | res = append(res, newSlice[i]) 253 | } 254 | 255 | return SliceUniq(res) 256 | } 257 | 258 | // SliceWithoutElem returns the slice `in` that not contains `elem`. 259 | func SliceWithoutElem[T comparable](in []T, elem T) []T { 260 | return SliceWithout(in, func(v T) bool { 261 | return v == elem 262 | }) 263 | } 264 | 265 | // SliceWithout returns the slice `in` where fn(elem) == true. 266 | func SliceWithout[T any](in []T, fn func(T) bool) []T { 267 | return SliceFilter(in, func(elem T) bool { 268 | return !fn(elem) 269 | }) 270 | } 271 | 272 | // SliceZip returns merged together the values of each of the arrays with the 273 | // values at the corresponding position. If the len of `in` is different - will 274 | // use smaller one. 275 | func SliceZip[T any](in ...[]T) [][]T { 276 | if len(in) == 0 { 277 | return make([][]T, 0) 278 | } 279 | 280 | maxLen := len(in[0]) 281 | for i := range in { 282 | if len(in[i]) < maxLen { 283 | maxLen = len(in[i]) 284 | } 285 | } 286 | 287 | if maxLen == 0 { 288 | return make([][]T, 0) 289 | } 290 | 291 | res := make([][]T, maxLen) 292 | for i := 0; i < maxLen; i++ { 293 | row := make([]T, len(in)) 294 | for j := range in { 295 | row[j] = in[j][i] 296 | } 297 | 298 | res[i] = row 299 | } 300 | 301 | return res 302 | } 303 | 304 | // SliceFillElem returns the slice with len `l` where all elements are equal to 305 | // `elem`. 306 | func SliceFillElem[T any](l int, elem T) []T { 307 | res := make([]T, l) 308 | for i := 0; i < l; i++ { 309 | res[i] = elem 310 | } 311 | 312 | return res 313 | } 314 | 315 | // SliceNotNil return source slice when it is not nil or create empty 316 | // instance of this type. 317 | func SliceNotNil[T any](in []T) []T { 318 | if in == nil { 319 | return make([]T, 0) 320 | } 321 | 322 | return in 323 | } 324 | 325 | // SliceChunk split `in` into chunks by fn(index, elem) == true. 326 | func SliceChunk[T any](in []T, fn func(i int, elem T) bool) [][]T { 327 | if len(in) == 0 { 328 | return make([][]T, 0) 329 | } 330 | 331 | res := make([][]T, 0, len(in)) 332 | var chunk []T 333 | for i := range in { 334 | if fn(i, in[i]) && len(chunk) != 0 { 335 | res = append(res, chunk) 336 | chunk = make([]T, 0) 337 | } 338 | 339 | chunk = append(chunk, in[i]) 340 | } 341 | 342 | if len(chunk) != 0 { 343 | res = append(res, chunk) 344 | } 345 | 346 | return res 347 | } 348 | 349 | // SliceChunkEvery split `in` into chunks by size `every` 350 | func SliceChunkEvery[T any](in []T, every int) [][]T { 351 | if every == 0 { 352 | panic("invalid arg") 353 | } 354 | 355 | return SliceChunk(in, func(i int, elem T) bool { 356 | return i%every == 0 357 | }) 358 | } 359 | 360 | // SliceElem represent element of slice. 361 | type SliceElem[T any] struct { 362 | // Idx is index of element in slice. 363 | Idx int 364 | // Val is value on slice by Idx index. 365 | Val T 366 | } 367 | 368 | // ValueOk returns the value and true (when element is exists in slice) or false in other case. 369 | // Useful for cases like: 370 | // 371 | // if elem, ok := SliceFindFirstElem([]int{1,2,3}, 2); ok{ 372 | // fmt.Println(elem) 373 | // } 374 | func (e SliceElem[T]) ValueOk() (T, bool) { 375 | return e.Val, e.Idx != -1 376 | } 377 | 378 | // Ok returns true if Idx is valid. 379 | func (e SliceElem[T]) Ok() bool { 380 | return e.Idx != -1 381 | } 382 | 383 | // ValueIdx returns value and index as is. 384 | // Useful for this: 385 | // 386 | // elem, idx := SliceFindFirstElem([]int{1,2,3}, 2).ValueIdx() 387 | func (e SliceElem[T]) ValueIdx() (T, int) { 388 | return e.Val, e.Idx 389 | } 390 | 391 | // SliceFindFirst return first elem from `in` that fn(index, elem) == true. 392 | // returns index of found elem or -1 if elem not found. 393 | func SliceFindFirst[T any](in []T, fn func(i int, elem T) bool) SliceElem[T] { 394 | for i := range in { 395 | if fn(i, in[i]) { 396 | return SliceElem[T]{ 397 | Idx: i, 398 | Val: in[i], 399 | } 400 | } 401 | } 402 | 403 | return SliceElem[T]{ 404 | Idx: -1, 405 | } 406 | } 407 | 408 | // SliceFindFirstElem return first elem from `in` that equals to `elem`. 409 | func SliceFindFirstElem[T comparable](in []T, elem T) SliceElem[T] { 410 | return SliceFindFirst(in, func(_ int, e T) bool { 411 | return e == elem 412 | }) 413 | } 414 | 415 | // SliceFindLast return last elem from `in` that fn(index, elem) == true. 416 | // returns index of found elem or -1 if elem not found. 417 | func SliceFindLast[T any](in []T, fn func(i int, elem T) bool) SliceElem[T] { 418 | for i := len(in) - 1; i != -1; i-- { 419 | if fn(i, in[i]) { 420 | return SliceElem[T]{ 421 | Idx: i, 422 | Val: in[i], 423 | } 424 | } 425 | } 426 | 427 | return SliceElem[T]{ 428 | Idx: -1, 429 | } 430 | } 431 | 432 | // SliceFindLastElem return last elem from `in` that equals to `elem`. 433 | func SliceFindLastElem[T comparable](in []T, elem T) SliceElem[T] { 434 | return SliceFindLast(in, func(_ int, e T) bool { 435 | return e == elem 436 | }) 437 | } 438 | 439 | // SliceFindAll return all elem and index from `in` that fn(index, elem) == true. 440 | func SliceFindAll[T any](in []T, fn func(i int, elem T) bool) []SliceElem[T] { 441 | res := make([]SliceElem[T], 0, len(in)) 442 | for i := range in { 443 | if !fn(i, in[i]) { 444 | continue 445 | } 446 | 447 | res = append(res, SliceElem[T]{ 448 | Idx: i, 449 | Val: in[i], 450 | }) 451 | } 452 | 453 | return res 454 | } 455 | 456 | // SliceFindAllElements return all elem from `in` that fn(index, elem) == true. 457 | func SliceFindAllElements[T any](in []T, fn func(i int, elem T) bool) []T { 458 | res := make([]T, 0, len(in)) 459 | for i := range in { 460 | if !fn(i, in[i]) { 461 | continue 462 | } 463 | 464 | res = append(res, in[i]) 465 | } 466 | 467 | return res 468 | } 469 | 470 | // SliceFindAllIndexes return all indexes from `in` that fn(index, elem) == true. 471 | func SliceFindAllIndexes[T any](in []T, fn func(i int, elem T) bool) []int { 472 | res := make([]int, 0, len(in)) 473 | for i := range in { 474 | if !fn(i, in[i]) { 475 | continue 476 | } 477 | 478 | res = append(res, i) 479 | } 480 | 481 | return res 482 | } 483 | 484 | // SliceRange produces a sequence of integers from start (inclusive) 485 | // to stop (exclusive) by step. 486 | func SliceRange[T number](start, stop, step T) []T { 487 | if start == stop { 488 | return make([]T, 0) 489 | } 490 | 491 | if step == 0 { 492 | return make([]T, 0) 493 | } 494 | 495 | isIncr := start < stop 496 | if isIncr && step < 0 { 497 | return make([]T, 0) 498 | } 499 | 500 | if !isIncr && step > 0 { 501 | return make([]T, 0) 502 | } 503 | 504 | res := make([]T, 0, int(Abs((start-stop)/step))) 505 | e := start 506 | for { 507 | if isIncr && e >= stop { 508 | break 509 | } 510 | 511 | if !isIncr && e <= stop { 512 | break 513 | } 514 | 515 | res = append(res, e) 516 | e += step 517 | } 518 | 519 | return res 520 | } 521 | 522 | // SliceEqualUnordered returns true when all uniq values from `in1` contains in `in2`. 523 | // Useful in tests for comparing expected and actual slices. 524 | // Examples: 525 | // - [1,2,3], [2,3,3,3,1,1] => true 526 | // - [1], [1,1,1] => true 527 | // - [1], [1] => true 528 | // - [1], [2] => false 529 | func SliceEqualUnordered[T comparable](in1, in2 []T) bool { 530 | m1 := Slice2Map(in1) 531 | m2 := Slice2Map(in2) 532 | 533 | if len(m1) != len(m2) { 534 | return false 535 | } 536 | 537 | for k := range m1 { 538 | if _, ok := m2[k]; !ok { 539 | return false 540 | } 541 | } 542 | 543 | return true 544 | } 545 | 546 | // SliceChain returns a slice where all `in` slices id appended to the end. Like 547 | // append(append(in[0], in[1]...), in[2]...). 548 | func SliceChain[T any](in ...[]T) []T { 549 | if len(in) == 0 { 550 | return make([]T, 0) 551 | } 552 | 553 | var l int 554 | for i := range in { 555 | l += len(in[i]) 556 | } 557 | 558 | res := make([]T, l) 559 | var x int 560 | for i := range in { 561 | copy(res[x:x+len(in[i])], in[i]) 562 | x += len(in[i]) 563 | } 564 | 565 | return res 566 | } 567 | 568 | // SliceSort sort slice inplace. 569 | func SliceSort[T any](in []T, less func(a, b T) bool) { 570 | sort.SliceStable(in, func(i, j int) bool { 571 | return less(in[i], in[j]) 572 | }) 573 | } 574 | 575 | // SliceSortCopy copy and sort slice. 576 | func SliceSortCopy[T any](in []T, less func(a, b T) bool) []T { 577 | res := make([]T, len(in)) 578 | copy(res, in) 579 | sort.SliceStable(res, func(i, j int) bool { 580 | return less(res[i], res[j]) 581 | }) 582 | 583 | return res 584 | } 585 | 586 | // SliceGroupBy will group all 587 | func SliceGroupBy[K comparable, V any](in []V, fn func(V) K) map[K][]V { 588 | if len(in) == 0 { 589 | return make(map[K][]V, 0) 590 | } 591 | 592 | res := make(map[K][]V, len(in)) 593 | for i := range in { 594 | key := fn(in[i]) 595 | res[key] = append(res[key], in[i]) 596 | } 597 | 598 | return res 599 | } 600 | 601 | // Slice2MapFn apply fn to every elem. fn should return key and value, which 602 | // will be applied to result map. 603 | func Slice2MapFn[T any, K comparable, V any](in []T, fn func(idx int, elem T) (K, V)) map[K]V { 604 | m := make(map[K]V, len(in)) 605 | for i := range in { 606 | k, v := fn(i, in[i]) 607 | m[k] = v 608 | } 609 | 610 | return m 611 | } 612 | 613 | // Slice2MapFnErr apply fn to every elem. fn should return key and value, which 614 | // will be applied to result map. return error when at least one fn returns an 615 | // error. 616 | func Slice2MapFnErr[T any, K comparable, V any](in []T, fn func(idx int, elem T) (K, V, error)) (map[K]V, error) { 617 | m := make(map[K]V, len(in)) 618 | for i := range in { 619 | k, v, err := fn(i, in[i]) 620 | if err != nil { 621 | return nil, err 622 | } 623 | 624 | m[k] = v 625 | } 626 | 627 | return m, nil 628 | } 629 | 630 | // Slice2Chan make chan with specified capacity from source slice. 631 | func Slice2Chan[T any](in []T, capacity int) chan T { 632 | if len(in) == capacity { 633 | return Slice2ChanFill(in) 634 | } 635 | 636 | ch := make(chan T, capacity) 637 | go func() { 638 | for i := range in { 639 | ch <- in[i] 640 | } 641 | }() 642 | 643 | return ch 644 | } 645 | 646 | // Slice2ChanFill make chan from source slice with will already filled by all 647 | // elements from source slice. 648 | func Slice2ChanFill[T any](in []T) chan T { 649 | ch := make(chan T, len(in)) 650 | ChanPut(ch, in) 651 | 652 | return ch 653 | } 654 | 655 | // SliceFromElem return a slice which contains only one element `elem`. 656 | func SliceFromElem[T any](elem T) []T { 657 | return []T{elem} 658 | } 659 | 660 | // SliceGetFirstN return a subslice of source slice, which will contains not 661 | // more than `maxElems` items. 662 | func SliceGetFirstN[T any](in []T, maxElems int) []T { 663 | if maxElems < 0 { 664 | panic("maxElems should be >= 0") 665 | } 666 | 667 | if len(in) < maxElems { 668 | return in 669 | } 670 | 671 | return in[:maxElems] 672 | } 673 | 674 | // SliceCopy return a copy of source slice. 675 | func SliceCopy[T any](in []T) []T { 676 | res := make([]T, len(in)) 677 | copy(res, in) 678 | 679 | return res 680 | } 681 | 682 | // SliceReplaceFirst will replace the first element in `in` such 683 | // that fn(index, elem) == true. 684 | // Will do nothing if the element is not found. 685 | func SliceReplaceFirst[T any](in []T, fn func(i int, elem T) bool, newElem T) { 686 | e := SliceFindFirst(in, fn) 687 | if e.Idx == -1 { 688 | return 689 | } 690 | 691 | in[e.Idx] = newElem 692 | } 693 | 694 | // SliceReplaceFirstOrAdd will replace the first element in `in` such 695 | // that fn(index, elem) == true. 696 | // Will add a `newElem` if the element is not found. 697 | func SliceReplaceFirstOrAdd[T any](in []T, fn func(i int, elem T) bool, newElem T) []T { 698 | e := SliceFindFirst(in, fn) 699 | if e.Idx == -1 { 700 | return append(in, newElem) 701 | } 702 | 703 | in[e.Idx] = newElem 704 | 705 | return in 706 | } 707 | 708 | // SliceLastDefault return a last elem from slice or default value in case of zero slice. 709 | func SliceLastDefault[T any](in []T, defaultVal T) T { 710 | if len(in) == 0 { 711 | return defaultVal 712 | } 713 | 714 | return in[len(in)-1] 715 | } 716 | 717 | // Slice2Iter create an iterator from slice. Check this docs https://go.dev/ref/spec#For_range. 718 | func Slice2Iter[T any](in []T) func(func(int, T) bool) { 719 | return func(yield func(int, T) bool) { 720 | for i := range in { 721 | if !yield(i, in[i]) { 722 | return 723 | } 724 | } 725 | } 726 | } 727 | 728 | type IterContext interface { 729 | // Idx returns an element index 730 | Idx() int 731 | RevIdx() int 732 | IsFirst() bool 733 | IsLast() bool 734 | } 735 | 736 | type iterContext struct { 737 | idx int 738 | inLen int 739 | } 740 | 741 | func (i iterContext) Idx() int { 742 | return i.idx 743 | } 744 | 745 | func (i iterContext) RevIdx() int { 746 | return i.inLen - i.idx - 1 747 | } 748 | 749 | func (i iterContext) IsFirst() bool { 750 | return i.idx == 0 751 | } 752 | 753 | func (i iterContext) IsLast() bool { 754 | return i.idx == i.inLen-1 755 | } 756 | 757 | var _ IterContext = (*iterContext)(nil) 758 | 759 | // SliceIter create an iterator from slice. The first argument will contain a useful context struct. 760 | func SliceIter[T any](in []T) func(func(IterContext, T) bool) { 761 | return func(yield func(loop IterContext, elem T) bool) { 762 | inLen := len(in) 763 | for i := range in { 764 | ctx := iterContext{ 765 | idx: i, 766 | inLen: inLen, 767 | } 768 | if !yield(ctx, in[i]) { 769 | return 770 | } 771 | } 772 | } 773 | } 774 | 775 | // SliceShuffle will shuffle the slice in-place. 776 | func SliceShuffle[T any](in []T) { 777 | for i := range in { 778 | j := rand.Intn(i + 1) 779 | in[i], in[j] = in[j], in[i] 780 | } 781 | } 782 | 783 | // SliceShuffleCopy will make a copy and shuffle slice. 784 | func SliceShuffleCopy[T any](in []T) []T { 785 | res := make([]T, len(in)) 786 | copy(res, in) 787 | 788 | SliceShuffle(res) 789 | 790 | return res 791 | } 792 | 793 | // SliceLastN return up to last n elements from input slice in original order. 794 | func SliceLastN[T any](in []T, n int) []T { 795 | if n < 0 { 796 | panic("n should be greater than 0") 797 | } 798 | 799 | inLen := len(in) 800 | 801 | if inLen == 0 || n == 0 { 802 | return make([]T, 0) 803 | } 804 | 805 | if inLen == n { 806 | res := make([]T, n) 807 | copy(res, in) 808 | 809 | return res 810 | } 811 | 812 | n = Min(inLen, n) 813 | res := make([]T, n) 814 | for i := n - 1; i >= 0; i-- { 815 | res[i] = in[inLen-n+i] 816 | } 817 | 818 | return res 819 | } 820 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | 8 | "github.com/kazhuravlev/just" 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestMapMerge(t *testing.T) { 14 | t.Parallel() 15 | 16 | alwaysTen := func(...int) int { return 10 } 17 | 18 | table := []struct { 19 | name string 20 | m1 map[int]int 21 | m2 map[int]int 22 | fn func(...int) int 23 | exp map[int]int 24 | }{ 25 | { 26 | name: "empty_nil", 27 | m1: nil, 28 | m2: nil, 29 | fn: alwaysTen, 30 | exp: map[int]int{}, 31 | }, 32 | { 33 | name: "empty_len0", 34 | m1: map[int]int{}, 35 | m2: map[int]int{}, 36 | fn: alwaysTen, 37 | exp: map[int]int{}, 38 | }, 39 | { 40 | name: "merge_all_keys", 41 | m1: map[int]int{1: 1}, 42 | m2: map[int]int{2: 2}, 43 | fn: alwaysTen, 44 | exp: map[int]int{1: 10, 2: 10}, 45 | }, 46 | { 47 | name: "merge_all_keys_duplicated", 48 | m1: map[int]int{1: 1, 2: 2}, 49 | m2: map[int]int{2: 2, 1: 1}, 50 | fn: alwaysTen, 51 | exp: map[int]int{1: 10, 2: 10}, 52 | }, 53 | { 54 | name: "merge_all_keys_m1_empty", 55 | m1: map[int]int{}, 56 | m2: map[int]int{2: 2, 1: 1}, 57 | fn: alwaysTen, 58 | exp: map[int]int{1: 10, 2: 10}, 59 | }, 60 | { 61 | name: "merge_all_keys_m2_empty", 62 | m1: map[int]int{2: 2, 1: 1}, 63 | m2: map[int]int{}, 64 | fn: alwaysTen, 65 | exp: map[int]int{1: 10, 2: 10}, 66 | }, 67 | { 68 | name: "merge_all_keys_get_biggest", 69 | m1: map[int]int{1: 10, 2: 11}, 70 | m2: map[int]int{1: 11, 2: 10}, 71 | fn: just.Max[int], 72 | exp: map[int]int{1: 11, 2: 11}, 73 | }, 74 | } 75 | 76 | for _, row := range table { 77 | row := row 78 | t.Run(row.name, func(t *testing.T) { 79 | t.Parallel() 80 | 81 | res := just.MapMerge(row.m1, row.m2, func(k, a, b int) int { return row.fn(a, b) }) 82 | require.EqualValues(t, row.exp, res) 83 | }) 84 | } 85 | } 86 | 87 | func TestMapFilterKeys(t *testing.T) { 88 | t.Parallel() 89 | 90 | alwaysTrue := func(_ int) bool { return true } 91 | alwaysFalse := func(_ int) bool { return false } 92 | 93 | table := []struct { 94 | name string 95 | m map[int]int 96 | fn func(int) bool 97 | exp map[int]int 98 | }{ 99 | { 100 | name: "empty_nil", 101 | m: nil, 102 | fn: alwaysTrue, 103 | exp: map[int]int{}, 104 | }, 105 | { 106 | name: "empty_len0", 107 | m: map[int]int{}, 108 | fn: alwaysTrue, 109 | exp: map[int]int{}, 110 | }, 111 | { 112 | name: "should_copy_all_kv", 113 | m: map[int]int{1: 1, 2: 2}, 114 | fn: alwaysTrue, 115 | exp: map[int]int{1: 1, 2: 2}, 116 | }, 117 | { 118 | name: "should_ignore_all", 119 | m: map[int]int{1: 1, 2: 2}, 120 | fn: alwaysFalse, 121 | exp: map[int]int{}, 122 | }, 123 | { 124 | name: "keep_only_key_1_or_2", 125 | m: map[int]int{1: 10, 2: 20, 3: 100, 4: -1}, 126 | fn: func(k int) bool { 127 | return k == 1 || k == 2 128 | }, 129 | exp: map[int]int{1: 10, 2: 20}, 130 | }, 131 | { 132 | name: "keep_only_even_keys", 133 | m: map[int]int{1: 10, 2: 2, 3: 100, 4: -1}, 134 | fn: func(k int) bool { 135 | return k%2 == 0 136 | }, 137 | exp: map[int]int{2: 2, 4: -1}, 138 | }, 139 | } 140 | 141 | for _, row := range table { 142 | row := row 143 | t.Run(row.name, func(t *testing.T) { 144 | t.Parallel() 145 | 146 | res := just.MapFilterKeys(row.m, row.fn) 147 | require.EqualValues(t, row.exp, res) 148 | }) 149 | } 150 | } 151 | 152 | func TestMapFilterValues(t *testing.T) { 153 | t.Parallel() 154 | 155 | alwaysTrue := func(_ int) bool { return true } 156 | alwaysFalse := func(_ int) bool { return false } 157 | 158 | table := []struct { 159 | name string 160 | m map[int]int 161 | fn func(int) bool 162 | exp map[int]int 163 | }{ 164 | { 165 | name: "empty_nil", 166 | m: nil, 167 | fn: alwaysTrue, 168 | exp: map[int]int{}, 169 | }, 170 | { 171 | name: "empty_len0", 172 | m: map[int]int{}, 173 | fn: alwaysTrue, 174 | exp: map[int]int{}, 175 | }, 176 | { 177 | name: "should_copy_all_kv", 178 | m: map[int]int{1: 1, 2: 2}, 179 | fn: alwaysTrue, 180 | exp: map[int]int{1: 1, 2: 2}, 181 | }, 182 | { 183 | name: "should_ignore_all", 184 | m: map[int]int{1: 1, 2: 2}, 185 | fn: alwaysFalse, 186 | exp: map[int]int{}, 187 | }, 188 | { 189 | name: "keep_only_values_gte_20", 190 | m: map[int]int{1: 10, 2: 20, 3: 100, 4: -1}, 191 | fn: func(v int) bool { 192 | return v >= 20 193 | }, 194 | exp: map[int]int{2: 20, 3: 100}, 195 | }, 196 | { 197 | name: "keep_only_even_values", 198 | m: map[int]int{1: 10, 2: 2, 3: 100, 4: -1}, 199 | fn: func(v int) bool { 200 | return v%2 == 0 201 | }, 202 | exp: map[int]int{2: 2, 3: 100, 1: 10}, 203 | }, 204 | } 205 | 206 | for _, row := range table { 207 | row := row 208 | t.Run(row.name, func(t *testing.T) { 209 | t.Parallel() 210 | 211 | res := just.MapFilterValues(row.m, row.fn) 212 | require.EqualValues(t, row.exp, res) 213 | }) 214 | } 215 | } 216 | 217 | func TestMapGetKeys(t *testing.T) { 218 | t.Parallel() 219 | 220 | table := []struct { 221 | name string 222 | m map[int]int 223 | exp []int 224 | }{ 225 | { 226 | name: "empty_nil", 227 | m: nil, 228 | exp: []int{}, 229 | }, 230 | { 231 | name: "empty_len0", 232 | m: map[int]int{}, 233 | exp: []int{}, 234 | }, 235 | { 236 | name: "case1", 237 | m: map[int]int{1: 11, 2: 22, 3: 33}, 238 | exp: []int{1, 2, 3}, 239 | }, 240 | } 241 | 242 | for _, row := range table { 243 | row := row 244 | t.Run(row.name, func(t *testing.T) { 245 | t.Parallel() 246 | 247 | res := just.MapGetKeys(row.m) 248 | sort.Ints(res) 249 | require.EqualValues(t, row.exp, res) 250 | }) 251 | } 252 | } 253 | 254 | func TestMapGetValues(t *testing.T) { 255 | t.Parallel() 256 | 257 | table := []struct { 258 | name string 259 | m map[int]int 260 | exp []int 261 | }{ 262 | { 263 | name: "empty_nil", 264 | m: nil, 265 | exp: []int{}, 266 | }, 267 | { 268 | name: "empty_len0", 269 | m: map[int]int{}, 270 | exp: []int{}, 271 | }, 272 | { 273 | name: "case1", 274 | m: map[int]int{1: 11, 2: 22, 3: 33}, 275 | exp: []int{11, 22, 33}, 276 | }, 277 | } 278 | 279 | for _, row := range table { 280 | row := row 281 | t.Run(row.name, func(t *testing.T) { 282 | t.Parallel() 283 | 284 | res := just.MapGetValues(row.m) 285 | sort.Ints(res) 286 | require.EqualValues(t, row.exp, res) 287 | }) 288 | } 289 | } 290 | 291 | func TestMapPairs(t *testing.T) { 292 | t.Parallel() 293 | 294 | table := []struct { 295 | name string 296 | m map[int]int 297 | exp []just.KV[int, int] 298 | }{ 299 | { 300 | name: "empty_nil", 301 | m: nil, 302 | exp: []just.KV[int, int]{}, 303 | }, 304 | { 305 | name: "empty_len0", 306 | m: map[int]int{}, 307 | exp: []just.KV[int, int]{}, 308 | }, 309 | { 310 | name: "case1", 311 | m: map[int]int{1: 11, 2: 22, 3: 33}, 312 | exp: []just.KV[int, int]{ 313 | {Key: 1, Val: 11}, 314 | {Key: 2, Val: 22}, 315 | {Key: 3, Val: 33}, 316 | }, 317 | }, 318 | } 319 | 320 | for _, row := range table { 321 | row := row 322 | t.Run(row.name, func(t *testing.T) { 323 | t.Parallel() 324 | 325 | res := just.MapPairs(row.m) 326 | require.EqualValues(t, row.exp, just.SliceSortCopy(res, func(a, b just.KV[int, int]) bool { return a.Key < b.Key })) 327 | }) 328 | } 329 | } 330 | 331 | func TestMapDefaults(t *testing.T) { 332 | t.Parallel() 333 | 334 | table := []struct { 335 | name string 336 | in, defaults, exp map[int]int 337 | }{ 338 | { 339 | name: "empty", 340 | in: nil, 341 | defaults: nil, 342 | exp: nil, 343 | }, 344 | { 345 | name: "empty_defaults", 346 | in: map[int]int{1: 1, 2: 2}, 347 | defaults: nil, 348 | exp: map[int]int{1: 1, 2: 2}, 349 | }, 350 | { 351 | name: "defaults_will_not_rewrite_src", 352 | in: map[int]int{1: 1, 2: 2}, 353 | defaults: map[int]int{1: 11, 2: 22}, 354 | exp: map[int]int{1: 1, 2: 2}, 355 | }, 356 | { 357 | name: "defaults_will_extend_non_exists_keys", 358 | in: map[int]int{1: 1, 2: 2}, 359 | defaults: map[int]int{2: 22, 3: 33}, 360 | exp: map[int]int{1: 1, 2: 2, 3: 33}, 361 | }, 362 | } 363 | 364 | for _, row := range table { 365 | row := row 366 | t.Run(row.name, func(t *testing.T) { 367 | t.Parallel() 368 | 369 | res := just.MapDefaults(row.in, row.defaults) 370 | assert.Equal(t, row.exp, res) 371 | }) 372 | } 373 | } 374 | 375 | func TestMapContainsKeysAny(t *testing.T) { 376 | t.Parallel() 377 | 378 | table := []struct { 379 | name string 380 | in map[int]int 381 | keys []int 382 | exp bool 383 | }{ 384 | { 385 | name: "empty_both", 386 | in: nil, 387 | keys: nil, 388 | exp: false, 389 | }, 390 | { 391 | name: "empty_keys", 392 | in: map[int]int{1: 1, 2: 2}, 393 | keys: nil, 394 | exp: false, 395 | }, 396 | { 397 | name: "empty_in", 398 | in: nil, 399 | keys: []int{1, 2, 3}, 400 | exp: false, 401 | }, 402 | { 403 | name: "one_key_is_exists", 404 | in: map[int]int{1: 1, 2: 2}, 405 | keys: []int{1, 100}, 406 | exp: true, 407 | }, 408 | { 409 | name: "all_keys_not_exists", 410 | in: map[int]int{1: 1, 2: 2}, 411 | keys: []int{100, 200, 300}, 412 | exp: false, 413 | }, 414 | } 415 | 416 | for _, row := range table { 417 | row := row 418 | t.Run(row.name, func(t *testing.T) { 419 | t.Parallel() 420 | 421 | res := just.MapContainsKeysAny(row.in, row.keys) 422 | assert.Equal(t, row.exp, res) 423 | }) 424 | } 425 | } 426 | 427 | func TestMapContainsKeysAll(t *testing.T) { 428 | t.Parallel() 429 | 430 | table := []struct { 431 | name string 432 | in map[int]int 433 | keys []int 434 | exp bool 435 | }{ 436 | { 437 | name: "empty_both", 438 | in: nil, 439 | keys: nil, 440 | exp: false, 441 | }, 442 | { 443 | name: "empty_keys", 444 | in: map[int]int{1: 1, 2: 2}, 445 | keys: nil, 446 | exp: false, 447 | }, 448 | { 449 | name: "empty_in", 450 | in: nil, 451 | keys: []int{1, 2, 3}, 452 | exp: false, 453 | }, 454 | { 455 | name: "one_key_is_exists", 456 | in: map[int]int{1: 1, 2: 2}, 457 | keys: []int{1, 100}, 458 | exp: false, 459 | }, 460 | { 461 | name: "all_keys_not_exists", 462 | in: map[int]int{1: 1, 2: 2}, 463 | keys: []int{100, 200, 300}, 464 | exp: false, 465 | }, 466 | { 467 | name: "all_keys_is_exists", 468 | in: map[int]int{1: 1, 2: 2}, 469 | keys: []int{1, 2}, 470 | exp: true, 471 | }, 472 | } 473 | 474 | for _, row := range table { 475 | row := row 476 | t.Run(row.name, func(t *testing.T) { 477 | t.Parallel() 478 | 479 | res := just.MapContainsKeysAll(row.in, row.keys) 480 | assert.Equal(t, row.exp, res) 481 | }) 482 | } 483 | } 484 | 485 | func TestMapMap(t *testing.T) { 486 | in := map[int]int{ 487 | 1: 11, 488 | 2: 22, 489 | } 490 | res := just.MapMap(in, func(k, v int) (string, string) { 491 | return strconv.Itoa(k), strconv.Itoa(v) 492 | }) 493 | exp := map[string]string{ 494 | "1": "11", 495 | "2": "22", 496 | } 497 | 498 | require.Equal(t, exp, res) 499 | } 500 | 501 | func TestMapMapErr(t *testing.T) { 502 | t.Run("success", func(t *testing.T) { 503 | in := map[int]string{ 504 | 1: "11", 505 | 2: "22", 506 | } 507 | res, err := just.MapMapErr(in, func(k int, v string) (int, int, error) { 508 | vInt, err := strconv.Atoi(v) 509 | return k, vInt, err 510 | }) 511 | exp := map[int]int{ 512 | 1: 11, 513 | 2: 22, 514 | } 515 | 516 | require.NoError(t, err) 517 | require.Equal(t, exp, res) 518 | }) 519 | 520 | t.Run("fail", func(t *testing.T) { 521 | in := map[int]string{ 522 | 1: "11", 523 | 2: "not-a-number", 524 | } 525 | res, err := just.MapMapErr(in, func(k int, v string) (int, int, error) { 526 | vInt, err := strconv.Atoi(v) 527 | return k, vInt, err 528 | }) 529 | 530 | require.Error(t, err) 531 | require.Empty(t, res) 532 | }) 533 | } 534 | 535 | func TestMapApply(t *testing.T) { 536 | var callCounter int 537 | just.MapApply(map[int]int{ 538 | 1: 1, 539 | 2: 2, 540 | }, func(k, v int) { 541 | callCounter += 1 542 | }) 543 | 544 | require.Equal(t, 2, callCounter) 545 | } 546 | 547 | func TestMapJoin(t *testing.T) { 548 | table := []struct { 549 | maps []map[int]int 550 | exp map[int]int 551 | }{ 552 | { 553 | maps: nil, 554 | exp: map[int]int{}, 555 | }, 556 | { 557 | maps: []map[int]int{}, 558 | exp: map[int]int{}, 559 | }, 560 | { 561 | maps: []map[int]int{ 562 | {1: 1}, 563 | {2: 2}, 564 | {3: 3}, 565 | }, 566 | exp: map[int]int{1: 1, 2: 2, 3: 3}, 567 | }, 568 | { 569 | maps: []map[int]int{ 570 | {1: 1}, 571 | {1: 2}, 572 | {1: 3}, 573 | }, 574 | exp: map[int]int{1: 3}, 575 | }, 576 | { 577 | maps: []map[int]int{ 578 | {1: 1}, 579 | {}, 580 | nil, 581 | }, 582 | exp: map[int]int{1: 1}, 583 | }, 584 | } 585 | 586 | for _, row := range table { 587 | t.Run("", func(t *testing.T) { 588 | res := just.MapJoin(row.maps...) 589 | require.Equal(t, row.exp, res) 590 | }) 591 | } 592 | } 593 | 594 | func TestMapGetDefault(t *testing.T) { 595 | table := []struct { 596 | in map[int]int 597 | key int 598 | defVal int 599 | exp int 600 | }{ 601 | { 602 | in: nil, 603 | key: 10, 604 | defVal: 7, 605 | exp: 7, 606 | }, 607 | { 608 | in: map[int]int{1: 1}, 609 | key: 1, 610 | defVal: 2, 611 | exp: 1, 612 | }, 613 | { 614 | in: map[int]int{1: 1}, 615 | key: 10, 616 | defVal: 7, 617 | exp: 7, 618 | }, 619 | } 620 | 621 | for _, row := range table { 622 | t.Run("", func(t *testing.T) { 623 | res := just.MapGetDefault(row.in, row.key, row.defVal) 624 | assert.Equal(t, row.exp, res) 625 | }) 626 | } 627 | } 628 | 629 | func TestMapNotNil(t *testing.T) { 630 | t.Parallel() 631 | 632 | table := []struct { 633 | in map[int]int 634 | exp map[int]int 635 | }{ 636 | { 637 | in: nil, 638 | exp: map[int]int{}, 639 | }, 640 | { 641 | in: map[int]int{}, 642 | exp: map[int]int{}, 643 | }, 644 | { 645 | in: map[int]int{1: 2}, 646 | exp: map[int]int{1: 2}, 647 | }, 648 | } 649 | 650 | for _, row := range table { 651 | t.Run("", func(t *testing.T) { 652 | res := just.MapNotNil(row.in) 653 | assert.Equal(t, row.exp, res) 654 | }) 655 | } 656 | } 657 | 658 | func TestMapDropKeys(t *testing.T) { 659 | t.Parallel() 660 | 661 | table := []struct { 662 | in map[int]int 663 | keys []int 664 | exp map[int]int 665 | }{ 666 | { 667 | in: nil, 668 | keys: nil, 669 | exp: nil, 670 | }, 671 | { 672 | in: map[int]int{}, 673 | keys: []int{1, 2, 3}, 674 | exp: map[int]int{}, 675 | }, 676 | { 677 | in: map[int]int{1: 1}, 678 | keys: []int{2, 3, 4}, 679 | exp: map[int]int{1: 1}, 680 | }, 681 | { 682 | in: map[int]int{1: 1, 2: 2}, 683 | keys: []int{1, 2, 3, 4}, 684 | exp: map[int]int{}, 685 | }, 686 | { 687 | in: map[int]int{1: 1, 2: 2}, 688 | keys: []int{1, 1, 1, 1, 1, 1}, 689 | exp: map[int]int{2: 2}, 690 | }, 691 | } 692 | 693 | for _, row := range table { 694 | t.Run("", func(t *testing.T) { 695 | just.MapDropKeys(row.in, row.keys...) 696 | assert.Equal(t, row.exp, row.in) 697 | }) 698 | } 699 | } 700 | 701 | func TestMapPopKeyDefault(t *testing.T) { 702 | t.Parallel() 703 | 704 | f := func(in map[int]int, k, def, exp int) { 705 | t.Helper() 706 | t.Run("", func(t *testing.T) { 707 | t.Parallel() 708 | 709 | res := just.MapPopKeyDefault(in, k, def) 710 | require.Equal(t, exp, res) 711 | require.False(t, just.MapContainsKey(in, k)) 712 | }) 713 | } 714 | 715 | type m map[int]int 716 | const ne = -1 717 | 718 | f(nil, 1, ne, ne) 719 | f(m{}, 1, ne, ne) 720 | f(m{1: 11}, 1, ne, 11) 721 | f(m{2: 22}, 1, ne, ne) 722 | } 723 | 724 | func TestMapSetVal(t *testing.T) { 725 | t.Parallel() 726 | 727 | type m map[int]int 728 | 729 | f := func(in m, k, v int, exp m) { 730 | t.Helper() 731 | 732 | t.Run("", func(t *testing.T) { 733 | t.Parallel() 734 | 735 | require.Equal(t, exp, just.MapSetVal(in, k, v)) 736 | }) 737 | } 738 | 739 | require.Panics(t, func() { 740 | just.MapSetVal((m)(nil), 1, 1) 741 | }) 742 | f(m{}, 1, 1, m{1: 1}) 743 | f(m{1: 1}, 1, 111, m{1: 111}) 744 | f(m{1: 1}, 2, 2, m{1: 1, 2: 2}) 745 | } 746 | 747 | func TestMapFilter(t *testing.T) { 748 | t.Parallel() 749 | 750 | t.Run("empty map", func(t *testing.T) { 751 | result := just.MapFilter(map[string]int{}, func(k string, v int) bool { 752 | return v > 0 753 | }) 754 | assert.Equal(t, map[string]int{}, result) 755 | }) 756 | 757 | t.Run("filters by value", func(t *testing.T) { 758 | input := map[string]int{ 759 | "a": 1, 760 | "b": -2, 761 | "c": 3, 762 | "d": -4, 763 | "e": 5, 764 | } 765 | result := just.MapFilter(input, func(k string, v int) bool { 766 | return v > 0 767 | }) 768 | expected := map[string]int{ 769 | "a": 1, 770 | "c": 3, 771 | "e": 5, 772 | } 773 | assert.Equal(t, expected, result) 774 | }) 775 | 776 | t.Run("filters by key", func(t *testing.T) { 777 | input := map[string]int{ 778 | "apple": 1, 779 | "banana": 2, 780 | "apricot": 3, 781 | "cherry": 4, 782 | } 783 | result := just.MapFilter(input, func(k string, v int) bool { 784 | return len(k) > 5 785 | }) 786 | expected := map[string]int{ 787 | "banana": 2, 788 | "apricot": 3, 789 | "cherry": 4, 790 | } 791 | assert.Equal(t, expected, result) 792 | }) 793 | 794 | t.Run("filters by key and value", func(t *testing.T) { 795 | input := map[string]int{ 796 | "a": 10, 797 | "bb": 20, 798 | "ccc": 30, 799 | "dddd": 40, 800 | } 801 | result := just.MapFilter(input, func(k string, v int) bool { 802 | return len(k) >= 2 && v <= 30 803 | }) 804 | expected := map[string]int{ 805 | "bb": 20, 806 | "ccc": 30, 807 | } 808 | assert.Equal(t, expected, result) 809 | }) 810 | 811 | t.Run("no elements match", func(t *testing.T) { 812 | input := map[string]int{ 813 | "a": 1, 814 | "b": 2, 815 | "c": 3, 816 | } 817 | result := just.MapFilter(input, func(k string, v int) bool { 818 | return v > 10 819 | }) 820 | assert.Equal(t, map[string]int{}, result) 821 | }) 822 | 823 | t.Run("all elements match", func(t *testing.T) { 824 | input := map[string]int{ 825 | "a": 1, 826 | "b": 2, 827 | "c": 3, 828 | } 829 | result := just.MapFilter(input, func(k string, v int) bool { 830 | return v > 0 831 | }) 832 | assert.Equal(t, input, result) 833 | }) 834 | } 835 | 836 | func TestMapContainsKey(t *testing.T) { 837 | t.Parallel() 838 | 839 | t.Run("empty map", func(t *testing.T) { 840 | result := just.MapContainsKey(map[string]int{}, "key") 841 | assert.False(t, result) 842 | }) 843 | 844 | t.Run("key exists", func(t *testing.T) { 845 | m := map[string]int{ 846 | "a": 1, 847 | "b": 2, 848 | "c": 3, 849 | } 850 | assert.True(t, just.MapContainsKey(m, "b")) 851 | assert.True(t, just.MapContainsKey(m, "a")) 852 | assert.True(t, just.MapContainsKey(m, "c")) 853 | }) 854 | 855 | t.Run("key does not exist", func(t *testing.T) { 856 | m := map[string]int{ 857 | "a": 1, 858 | "b": 2, 859 | "c": 3, 860 | } 861 | assert.False(t, just.MapContainsKey(m, "d")) 862 | assert.False(t, just.MapContainsKey(m, "")) 863 | assert.False(t, just.MapContainsKey(m, "aa")) 864 | }) 865 | 866 | t.Run("zero value exists", func(t *testing.T) { 867 | m := map[string]int{ 868 | "a": 0, 869 | "b": 1, 870 | } 871 | assert.True(t, just.MapContainsKey(m, "a")) 872 | }) 873 | 874 | t.Run("nil map", func(t *testing.T) { 875 | var m map[string]int 876 | assert.False(t, just.MapContainsKey(m, "key")) 877 | }) 878 | } 879 | 880 | func TestMapCopy(t *testing.T) { 881 | t.Parallel() 882 | 883 | t.Run("empty map", func(t *testing.T) { 884 | original := map[string]int{} 885 | copied := just.MapCopy(original) 886 | assert.Equal(t, original, copied) 887 | assert.NotSame(t, original, copied) 888 | }) 889 | 890 | t.Run("nil map", func(t *testing.T) { 891 | var original map[string]int 892 | copied := just.MapCopy(original) 893 | assert.Nil(t, copied) 894 | }) 895 | 896 | t.Run("copies map content", func(t *testing.T) { 897 | original := map[string]int{ 898 | "a": 1, 899 | "b": 2, 900 | "c": 3, 901 | } 902 | copied := just.MapCopy(original) 903 | assert.Equal(t, original, copied) 904 | 905 | // Verify it's a different map 906 | original["d"] = 4 907 | assert.NotEqual(t, original, copied) 908 | assert.Equal(t, 3, len(copied)) 909 | assert.Equal(t, 4, len(original)) 910 | }) 911 | 912 | t.Run("shallow copy with struct values", func(t *testing.T) { 913 | type person struct { 914 | name string 915 | age int 916 | } 917 | 918 | original := map[string]person{ 919 | "alice": {name: "Alice", age: 30}, 920 | "bob": {name: "Bob", age: 25}, 921 | } 922 | copied := just.MapCopy(original) 923 | 924 | assert.Equal(t, original, copied) 925 | 926 | // Modify original 927 | original["charlie"] = person{name: "Charlie", age: 35} 928 | assert.NotEqual(t, original, copied) 929 | assert.Equal(t, 2, len(copied)) 930 | assert.Equal(t, 3, len(original)) 931 | }) 932 | 933 | t.Run("shallow copy with pointer values", func(t *testing.T) { 934 | a, b, c := 1, 2, 3 935 | original := map[string]*int{ 936 | "a": &a, 937 | "b": &b, 938 | "c": &c, 939 | } 940 | copied := just.MapCopy(original) 941 | 942 | // Same pointers (shallow copy) 943 | assert.Same(t, original["a"], copied["a"]) 944 | assert.Same(t, original["b"], copied["b"]) 945 | assert.Same(t, original["c"], copied["c"]) 946 | 947 | // But different maps 948 | d := 4 949 | original["d"] = &d 950 | assert.Equal(t, 3, len(copied)) 951 | assert.Equal(t, 4, len(original)) 952 | }) 953 | } 954 | -------------------------------------------------------------------------------- /slice_bench_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/kazhuravlev/just" 11 | ) 12 | 13 | // Helper functions to create test slices 14 | func createIntSlice(size int) []int { 15 | s := make([]int, size) 16 | for i := 0; i < size; i++ { 17 | s[i] = i 18 | } 19 | return s 20 | } 21 | 22 | func createIntSliceWithDuplicates(size int, dupFactor int) []int { 23 | s := make([]int, size) 24 | for i := 0; i < size; i++ { 25 | s[i] = i / dupFactor 26 | } 27 | return s 28 | } 29 | 30 | // BenchmarkSliceUniq benchmarks the SliceUniq function 31 | func BenchmarkSliceUniq(b *testing.B) { 32 | sizes := []int{10, 1000} 33 | dupFactors := []int{1, 2, 10} // 1 = no dups, 2 = 50% dups, 10 = 90% dups 34 | 35 | for _, size := range sizes { 36 | for _, dupFactor := range dupFactors { 37 | b.Run(fmt.Sprintf("size_%d_dupFactor_%d", size, dupFactor), func(b *testing.B) { 38 | slice := createIntSliceWithDuplicates(size, dupFactor) 39 | 40 | b.ResetTimer() 41 | for i := 0; i < b.N; i++ { 42 | _ = just.SliceUniq(slice) 43 | } 44 | }) 45 | } 46 | } 47 | } 48 | 49 | // BenchmarkSliceUniqStable benchmarks the SliceUniqStable function 50 | func BenchmarkSliceUniqStable(b *testing.B) { 51 | sizes := []int{10, 1000} 52 | 53 | for _, size := range sizes { 54 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 55 | slice := createIntSliceWithDuplicates(size, 2) 56 | 57 | b.ResetTimer() 58 | for i := 0; i < b.N; i++ { 59 | _ = just.SliceUniqStable(slice) 60 | } 61 | }) 62 | } 63 | } 64 | 65 | // BenchmarkSliceMap benchmarks the SliceMap function 66 | func BenchmarkSliceMap(b *testing.B) { 67 | sizes := []int{10, 1000} 68 | 69 | for _, size := range sizes { 70 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 71 | slice := createIntSlice(size) 72 | 73 | b.ResetTimer() 74 | for i := 0; i < b.N; i++ { 75 | _ = just.SliceMap(slice, func(v int) string { 76 | return strconv.Itoa(v) 77 | }) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | // BenchmarkSliceFlatMap benchmarks the SliceFlatMap function 84 | func BenchmarkSliceFlatMap(b *testing.B) { 85 | sizes := []int{10, 1000} 86 | 87 | for _, size := range sizes { 88 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 89 | slice := createIntSlice(size) 90 | 91 | b.ResetTimer() 92 | for i := 0; i < b.N; i++ { 93 | _ = just.SliceFlatMap(slice, func(v int) []int { 94 | return []int{v, v * 2, v * 3} 95 | }) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | // BenchmarkSliceFlatMap2 benchmarks the SliceFlatMap2 function 102 | func BenchmarkSliceFlatMap2(b *testing.B) { 103 | sizes := []int{10, 1000} 104 | 105 | for _, size := range sizes { 106 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 107 | slice := createIntSlice(size) 108 | 109 | b.ResetTimer() 110 | for i := 0; i < b.N; i++ { 111 | _ = just.SliceFlatMap2(slice, func(idx int, v int) []int { 112 | return []int{idx, v} 113 | }) 114 | } 115 | }) 116 | } 117 | } 118 | 119 | // BenchmarkSliceApply benchmarks the SliceApply function 120 | func BenchmarkSliceApply(b *testing.B) { 121 | sizes := []int{10, 1000} 122 | 123 | for _, size := range sizes { 124 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 125 | slice := createIntSlice(size) 126 | sum := 0 127 | 128 | b.ResetTimer() 129 | for i := 0; i < b.N; i++ { 130 | just.SliceApply(slice, func(idx int, v int) { 131 | sum += v 132 | }) 133 | } 134 | }) 135 | } 136 | } 137 | 138 | // BenchmarkSliceMapErr benchmarks the SliceMapErr function 139 | func BenchmarkSliceMapErr(b *testing.B) { 140 | sizes := []int{10, 1000} 141 | 142 | for _, size := range sizes { 143 | b.Run(fmt.Sprintf("size_%d_success", size), func(b *testing.B) { 144 | slice := createIntSlice(size) 145 | 146 | b.ResetTimer() 147 | for i := 0; i < b.N; i++ { 148 | _, _ = just.SliceMapErr(slice, func(v int) (string, error) { 149 | return strconv.Itoa(v), nil 150 | }) 151 | } 152 | }) 153 | 154 | b.Run(fmt.Sprintf("size_%d_error_early", size), func(b *testing.B) { 155 | slice := createIntSlice(size) 156 | 157 | b.ResetTimer() 158 | for i := 0; i < b.N; i++ { 159 | _, _ = just.SliceMapErr(slice, func(v int) (string, error) { 160 | if v == 0 { 161 | return "", errors.New("error") 162 | } 163 | return strconv.Itoa(v), nil 164 | }) 165 | } 166 | }) 167 | } 168 | } 169 | 170 | // BenchmarkSliceFilter benchmarks the SliceFilter function 171 | func BenchmarkSliceFilter(b *testing.B) { 172 | sizes := []int{10, 1000} 173 | filterRatios := []int{2, 10, 100} // Keep 1/2, 1/10, 1/100 of elements 174 | 175 | for _, size := range sizes { 176 | for _, ratio := range filterRatios { 177 | b.Run(fmt.Sprintf("size_%d_keep_1/%d", size, ratio), func(b *testing.B) { 178 | slice := createIntSlice(size) 179 | 180 | b.ResetTimer() 181 | for i := 0; i < b.N; i++ { 182 | _ = just.SliceFilter(slice, func(v int) bool { 183 | return v%ratio == 0 184 | }) 185 | } 186 | }) 187 | } 188 | } 189 | } 190 | 191 | // BenchmarkSliceReverse benchmarks the SliceReverse function 192 | func BenchmarkSliceReverse(b *testing.B) { 193 | sizes := []int{10, 1000} 194 | 195 | for _, size := range sizes { 196 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 197 | slice := createIntSlice(size) 198 | 199 | b.ResetTimer() 200 | for i := 0; i < b.N; i++ { 201 | _ = just.SliceReverse(slice) 202 | } 203 | }) 204 | } 205 | } 206 | 207 | // BenchmarkSliceAny benchmarks the SliceAny function 208 | func BenchmarkSliceAny(b *testing.B) { 209 | sizes := []int{10, 1000} 210 | 211 | for _, size := range sizes { 212 | b.Run(fmt.Sprintf("size_%d_found_first", size), func(b *testing.B) { 213 | slice := createIntSlice(size) 214 | 215 | b.ResetTimer() 216 | for i := 0; i < b.N; i++ { 217 | _ = just.SliceAny(slice, func(v int) bool { 218 | return v == 0 219 | }) 220 | } 221 | }) 222 | 223 | b.Run(fmt.Sprintf("size_%d_found_last", size), func(b *testing.B) { 224 | slice := createIntSlice(size) 225 | 226 | b.ResetTimer() 227 | for i := 0; i < b.N; i++ { 228 | _ = just.SliceAny(slice, func(v int) bool { 229 | return v == size-1 230 | }) 231 | } 232 | }) 233 | 234 | b.Run(fmt.Sprintf("size_%d_not_found", size), func(b *testing.B) { 235 | slice := createIntSlice(size) 236 | 237 | b.ResetTimer() 238 | for i := 0; i < b.N; i++ { 239 | _ = just.SliceAny(slice, func(v int) bool { 240 | return v < 0 241 | }) 242 | } 243 | }) 244 | } 245 | } 246 | 247 | // BenchmarkSliceAll benchmarks the SliceAll function 248 | func BenchmarkSliceAll(b *testing.B) { 249 | sizes := []int{10, 1000} 250 | 251 | for _, size := range sizes { 252 | b.Run(fmt.Sprintf("size_%d_all_true", size), func(b *testing.B) { 253 | slice := createIntSlice(size) 254 | 255 | b.ResetTimer() 256 | for i := 0; i < b.N; i++ { 257 | _ = just.SliceAll(slice, func(v int) bool { 258 | return v >= 0 259 | }) 260 | } 261 | }) 262 | 263 | b.Run(fmt.Sprintf("size_%d_false_at_end", size), func(b *testing.B) { 264 | slice := createIntSlice(size) 265 | 266 | b.ResetTimer() 267 | for i := 0; i < b.N; i++ { 268 | _ = just.SliceAll(slice, func(v int) bool { 269 | return v < size-1 270 | }) 271 | } 272 | }) 273 | } 274 | } 275 | 276 | // BenchmarkSliceContainsElem benchmarks the SliceContainsElem function 277 | func BenchmarkSliceContainsElem(b *testing.B) { 278 | sizes := []int{10, 1000} 279 | 280 | for _, size := range sizes { 281 | b.Run(fmt.Sprintf("size_%d_found", size), func(b *testing.B) { 282 | slice := createIntSlice(size) 283 | elem := size / 2 284 | 285 | b.ResetTimer() 286 | for i := 0; i < b.N; i++ { 287 | _ = just.SliceContainsElem(slice, elem) 288 | } 289 | }) 290 | 291 | b.Run(fmt.Sprintf("size_%d_not_found", size), func(b *testing.B) { 292 | slice := createIntSlice(size) 293 | elem := size + 1 294 | 295 | b.ResetTimer() 296 | for i := 0; i < b.N; i++ { 297 | _ = just.SliceContainsElem(slice, elem) 298 | } 299 | }) 300 | } 301 | } 302 | 303 | // BenchmarkSliceAddNotExists benchmarks the SliceAddNotExists function 304 | func BenchmarkSliceAddNotExists(b *testing.B) { 305 | sizes := []int{10, 1000} 306 | 307 | for _, size := range sizes { 308 | b.Run(fmt.Sprintf("size_%d_exists", size), func(b *testing.B) { 309 | slice := createIntSlice(size) 310 | elem := size / 2 311 | 312 | b.ResetTimer() 313 | for i := 0; i < b.N; i++ { 314 | _ = just.SliceAddNotExists(slice, elem) 315 | } 316 | }) 317 | 318 | b.Run(fmt.Sprintf("size_%d_not_exists", size), func(b *testing.B) { 319 | slice := createIntSlice(size) 320 | elem := size + 1 321 | 322 | b.ResetTimer() 323 | for i := 0; i < b.N; i++ { 324 | _ = just.SliceAddNotExists(slice, elem) 325 | } 326 | }) 327 | } 328 | } 329 | 330 | // BenchmarkSliceUnion benchmarks the SliceUnion function 331 | func BenchmarkSliceUnion(b *testing.B) { 332 | sizes := []int{10, 100, 1000} 333 | sliceCounts := []int{2, 5, 10} 334 | 335 | for _, count := range sliceCounts { 336 | for _, size := range sizes { 337 | b.Run(fmt.Sprintf("slices_%d_size_%d", count, size), func(b *testing.B) { 338 | slices := make([][]int, count) 339 | for i := 0; i < count; i++ { 340 | slices[i] = createIntSlice(size) 341 | } 342 | 343 | b.ResetTimer() 344 | for i := 0; i < b.N; i++ { 345 | _ = just.SliceUnion(slices...) 346 | } 347 | }) 348 | } 349 | } 350 | } 351 | 352 | // BenchmarkSlice2Map benchmarks the Slice2Map function 353 | func BenchmarkSlice2Map(b *testing.B) { 354 | sizes := []int{10, 1000} 355 | 356 | for _, size := range sizes { 357 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 358 | slice := createIntSlice(size) 359 | 360 | b.ResetTimer() 361 | for i := 0; i < b.N; i++ { 362 | _ = just.Slice2Map(slice) 363 | } 364 | }) 365 | } 366 | } 367 | 368 | // BenchmarkSliceDifference benchmarks the SliceDifference function 369 | func BenchmarkSliceDifference(b *testing.B) { 370 | sizes := []int{10, 1000} 371 | 372 | for _, size := range sizes { 373 | b.Run(fmt.Sprintf("size_%d_no_overlap", size), func(b *testing.B) { 374 | old := createIntSlice(size) 375 | new := make([]int, size) 376 | for i := 0; i < size; i++ { 377 | new[i] = i + size 378 | } 379 | 380 | b.ResetTimer() 381 | for i := 0; i < b.N; i++ { 382 | _ = just.SliceDifference(old, new) 383 | } 384 | }) 385 | 386 | b.Run(fmt.Sprintf("size_%d_half_overlap", size), func(b *testing.B) { 387 | old := createIntSlice(size) 388 | new := make([]int, size) 389 | for i := 0; i < size; i++ { 390 | new[i] = i + size/2 391 | } 392 | 393 | b.ResetTimer() 394 | for i := 0; i < b.N; i++ { 395 | _ = just.SliceDifference(old, new) 396 | } 397 | }) 398 | } 399 | } 400 | 401 | // BenchmarkSliceIntersection benchmarks the SliceIntersection function 402 | func BenchmarkSliceIntersection(b *testing.B) { 403 | sizes := []int{10, 1000} 404 | 405 | for _, size := range sizes { 406 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 407 | slice1 := createIntSlice(size) 408 | slice2 := make([]int, size) 409 | for i := 0; i < size; i++ { 410 | slice2[i] = i + size/2 411 | } 412 | 413 | b.ResetTimer() 414 | for i := 0; i < b.N; i++ { 415 | _ = just.SliceIntersection(slice1, slice2) 416 | } 417 | }) 418 | } 419 | } 420 | 421 | // BenchmarkSliceWithoutElem benchmarks the SliceWithoutElem function 422 | func BenchmarkSliceWithoutElem(b *testing.B) { 423 | sizes := []int{10, 1000} 424 | 425 | for _, size := range sizes { 426 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 427 | slice := createIntSliceWithDuplicates(size, 10) 428 | elem := size / 20 // Will have ~10 occurrences 429 | 430 | b.ResetTimer() 431 | for i := 0; i < b.N; i++ { 432 | _ = just.SliceWithoutElem(slice, elem) 433 | } 434 | }) 435 | } 436 | } 437 | 438 | // BenchmarkSliceWithout benchmarks the SliceWithout function 439 | func BenchmarkSliceWithout(b *testing.B) { 440 | sizes := []int{10, 1000} 441 | 442 | for _, size := range sizes { 443 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 444 | slice := createIntSlice(size) 445 | 446 | b.ResetTimer() 447 | for i := 0; i < b.N; i++ { 448 | _ = just.SliceWithout(slice, func(v int) bool { 449 | return v%2 == 0 450 | }) 451 | } 452 | }) 453 | } 454 | } 455 | 456 | // BenchmarkSliceZip benchmarks the SliceZip function 457 | func BenchmarkSliceZip(b *testing.B) { 458 | sizes := []int{10, 1000} 459 | sliceCounts := []int{2, 5, 10} 460 | 461 | for _, count := range sliceCounts { 462 | for _, size := range sizes { 463 | b.Run(fmt.Sprintf("slices_%d_size_%d", count, size), func(b *testing.B) { 464 | slices := make([][]int, count) 465 | for i := 0; i < count; i++ { 466 | slices[i] = createIntSlice(size) 467 | } 468 | 469 | b.ResetTimer() 470 | for i := 0; i < b.N; i++ { 471 | _ = just.SliceZip(slices...) 472 | } 473 | }) 474 | } 475 | } 476 | } 477 | 478 | // BenchmarkSliceFillElem benchmarks the SliceFillElem function 479 | func BenchmarkSliceFillElem(b *testing.B) { 480 | sizes := []int{10, 1000} 481 | 482 | for _, size := range sizes { 483 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 484 | elem := 42 485 | 486 | b.ResetTimer() 487 | for i := 0; i < b.N; i++ { 488 | _ = just.SliceFillElem(size, elem) 489 | } 490 | }) 491 | } 492 | } 493 | 494 | // BenchmarkSliceNotNil benchmarks the SliceNotNil function 495 | func BenchmarkSliceNotNil(b *testing.B) { 496 | b.Run("nil_slice", func(b *testing.B) { 497 | var slice []int 498 | 499 | b.ResetTimer() 500 | for i := 0; i < b.N; i++ { 501 | _ = just.SliceNotNil(slice) 502 | } 503 | }) 504 | 505 | b.Run("non_nil_slice", func(b *testing.B) { 506 | slice := createIntSlice(100) 507 | 508 | b.ResetTimer() 509 | for i := 0; i < b.N; i++ { 510 | _ = just.SliceNotNil(slice) 511 | } 512 | }) 513 | } 514 | 515 | // BenchmarkSliceChunk benchmarks the SliceChunk function 516 | func BenchmarkSliceChunk(b *testing.B) { 517 | sizes := []int{10, 1000} 518 | 519 | for _, size := range sizes { 520 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 521 | slice := createIntSlice(size) 522 | 523 | b.ResetTimer() 524 | for i := 0; i < b.N; i++ { 525 | _ = just.SliceChunk(slice, func(idx int, v int) bool { 526 | return v%10 == 0 527 | }) 528 | } 529 | }) 530 | } 531 | } 532 | 533 | // BenchmarkSliceChunkEvery benchmarks the SliceChunkEvery function 534 | func BenchmarkSliceChunkEvery(b *testing.B) { 535 | sizes := []int{10, 1000} 536 | chunkSizes := []int{1, 10, 100} 537 | 538 | for _, size := range sizes { 539 | for _, chunkSize := range chunkSizes { 540 | if chunkSize > size { 541 | continue 542 | } 543 | b.Run(fmt.Sprintf("size_%d_chunk_%d", size, chunkSize), func(b *testing.B) { 544 | slice := createIntSlice(size) 545 | 546 | b.ResetTimer() 547 | for i := 0; i < b.N; i++ { 548 | _ = just.SliceChunkEvery(slice, chunkSize) 549 | } 550 | }) 551 | } 552 | } 553 | } 554 | 555 | // BenchmarkSliceFindFirst benchmarks the SliceFindFirst function 556 | func BenchmarkSliceFindFirst(b *testing.B) { 557 | sizes := []int{10, 1000} 558 | 559 | for _, size := range sizes { 560 | b.Run(fmt.Sprintf("size_%d_found_early", size), func(b *testing.B) { 561 | slice := createIntSlice(size) 562 | 563 | b.ResetTimer() 564 | for i := 0; i < b.N; i++ { 565 | _ = just.SliceFindFirst(slice, func(idx int, v int) bool { 566 | return v == 5 567 | }) 568 | } 569 | }) 570 | 571 | b.Run(fmt.Sprintf("size_%d_found_late", size), func(b *testing.B) { 572 | slice := createIntSlice(size) 573 | 574 | b.ResetTimer() 575 | for i := 0; i < b.N; i++ { 576 | _ = just.SliceFindFirst(slice, func(idx int, v int) bool { 577 | return v == size-5 578 | }) 579 | } 580 | }) 581 | } 582 | } 583 | 584 | // BenchmarkSliceFindFirstElem benchmarks the SliceFindFirstElem function 585 | func BenchmarkSliceFindFirstElem(b *testing.B) { 586 | sizes := []int{10, 1000} 587 | 588 | for _, size := range sizes { 589 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 590 | slice := createIntSlice(size) 591 | elem := size / 2 592 | 593 | b.ResetTimer() 594 | for i := 0; i < b.N; i++ { 595 | _ = just.SliceFindFirstElem(slice, elem) 596 | } 597 | }) 598 | } 599 | } 600 | 601 | // BenchmarkSliceFindLast benchmarks the SliceFindLast function 602 | func BenchmarkSliceFindLast(b *testing.B) { 603 | sizes := []int{10, 1000} 604 | 605 | for _, size := range sizes { 606 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 607 | slice := createIntSliceWithDuplicates(size, 10) 608 | 609 | b.ResetTimer() 610 | for i := 0; i < b.N; i++ { 611 | _ = just.SliceFindLast(slice, func(idx int, v int) bool { 612 | return v == size/20 613 | }) 614 | } 615 | }) 616 | } 617 | } 618 | 619 | // BenchmarkSliceFindLastElem benchmarks the SliceFindLastElem function 620 | func BenchmarkSliceFindLastElem(b *testing.B) { 621 | sizes := []int{10, 1000} 622 | 623 | for _, size := range sizes { 624 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 625 | slice := createIntSliceWithDuplicates(size, 10) 626 | elem := size / 20 627 | 628 | b.ResetTimer() 629 | for i := 0; i < b.N; i++ { 630 | _ = just.SliceFindLastElem(slice, elem) 631 | } 632 | }) 633 | } 634 | } 635 | 636 | // BenchmarkSliceFindAll benchmarks the SliceFindAll function 637 | func BenchmarkSliceFindAll(b *testing.B) { 638 | sizes := []int{10, 1000} 639 | 640 | for _, size := range sizes { 641 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 642 | slice := createIntSlice(size) 643 | 644 | b.ResetTimer() 645 | for i := 0; i < b.N; i++ { 646 | _ = just.SliceFindAll(slice, func(idx int, v int) bool { 647 | return v%10 == 0 648 | }) 649 | } 650 | }) 651 | } 652 | } 653 | 654 | // BenchmarkSliceFindAllElements benchmarks the SliceFindAllElements function 655 | func BenchmarkSliceFindAllElements(b *testing.B) { 656 | sizes := []int{10, 1000} 657 | 658 | for _, size := range sizes { 659 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 660 | slice := createIntSlice(size) 661 | 662 | b.ResetTimer() 663 | for i := 0; i < b.N; i++ { 664 | _ = just.SliceFindAllElements(slice, func(idx int, v int) bool { 665 | return v%10 == 0 666 | }) 667 | } 668 | }) 669 | } 670 | } 671 | 672 | // BenchmarkSliceFindAllIndexes benchmarks the SliceFindAllIndexes function 673 | func BenchmarkSliceFindAllIndexes(b *testing.B) { 674 | sizes := []int{10, 1000} 675 | 676 | for _, size := range sizes { 677 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 678 | slice := createIntSlice(size) 679 | 680 | b.ResetTimer() 681 | for i := 0; i < b.N; i++ { 682 | _ = just.SliceFindAllIndexes(slice, func(idx int, v int) bool { 683 | return v%10 == 0 684 | }) 685 | } 686 | }) 687 | } 688 | } 689 | 690 | // BenchmarkSliceRange benchmarks the SliceRange function 691 | func BenchmarkSliceRange(b *testing.B) { 692 | ranges := []struct { 693 | start, stop, step int 694 | }{ 695 | {0, 10, 1}, 696 | {0, 100, 1}, 697 | {0, 1000, 1}, 698 | {0, 10000, 1}, 699 | {0, 10000, 10}, 700 | {0, 10000, 100}, 701 | } 702 | 703 | for _, r := range ranges { 704 | b.Run(fmt.Sprintf("range_%d_%d_%d", r.start, r.stop, r.step), func(b *testing.B) { 705 | b.ResetTimer() 706 | for i := 0; i < b.N; i++ { 707 | _ = just.SliceRange(r.start, r.stop, r.step) 708 | } 709 | }) 710 | } 711 | } 712 | 713 | // BenchmarkSliceEqualUnordered benchmarks the SliceEqualUnordered function 714 | func BenchmarkSliceEqualUnordered(b *testing.B) { 715 | sizes := []int{10, 1000} 716 | 717 | for _, size := range sizes { 718 | b.Run(fmt.Sprintf("size_%d_equal", size), func(b *testing.B) { 719 | slice1 := createIntSlice(size) 720 | slice2 := make([]int, size) 721 | copy(slice2, slice1) 722 | // Shuffle slice2 723 | rand.Shuffle(len(slice2), func(i, j int) { 724 | slice2[i], slice2[j] = slice2[j], slice2[i] 725 | }) 726 | 727 | b.ResetTimer() 728 | for i := 0; i < b.N; i++ { 729 | _ = just.SliceEqualUnordered(slice1, slice2) 730 | } 731 | }) 732 | 733 | b.Run(fmt.Sprintf("size_%d_not_equal", size), func(b *testing.B) { 734 | slice1 := createIntSlice(size) 735 | slice2 := createIntSlice(size) 736 | slice2[0] = size + 1 737 | 738 | b.ResetTimer() 739 | for i := 0; i < b.N; i++ { 740 | _ = just.SliceEqualUnordered(slice1, slice2) 741 | } 742 | }) 743 | } 744 | } 745 | 746 | // BenchmarkSliceChain benchmarks the SliceChain function 747 | func BenchmarkSliceChain(b *testing.B) { 748 | sliceCounts := []int{2, 5, 10} 749 | sizes := []int{10, 100, 1000} 750 | 751 | for _, count := range sliceCounts { 752 | for _, size := range sizes { 753 | b.Run(fmt.Sprintf("slices_%d_size_%d", count, size), func(b *testing.B) { 754 | slices := make([][]int, count) 755 | for i := 0; i < count; i++ { 756 | slices[i] = createIntSlice(size) 757 | } 758 | 759 | b.ResetTimer() 760 | for i := 0; i < b.N; i++ { 761 | _ = just.SliceChain(slices...) 762 | } 763 | }) 764 | } 765 | } 766 | } 767 | 768 | // BenchmarkSliceSort benchmarks the SliceSort function 769 | func BenchmarkSliceSort(b *testing.B) { 770 | sizes := []int{10, 1000} 771 | 772 | for _, size := range sizes { 773 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 774 | less := func(a, b int) bool { return a < b } 775 | 776 | b.ResetTimer() 777 | for i := 0; i < b.N; i++ { 778 | slice := make([]int, size) 779 | for j := 0; j < size; j++ { 780 | slice[j] = rand.Intn(size) 781 | } 782 | just.SliceSort(slice, less) 783 | } 784 | }) 785 | } 786 | } 787 | 788 | // BenchmarkSliceSortCopy benchmarks the SliceSortCopy function 789 | func BenchmarkSliceSortCopy(b *testing.B) { 790 | sizes := []int{10, 1000} 791 | 792 | for _, size := range sizes { 793 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 794 | slice := make([]int, size) 795 | for i := 0; i < size; i++ { 796 | slice[i] = rand.Intn(size) 797 | } 798 | less := func(a, b int) bool { return a < b } 799 | 800 | b.ResetTimer() 801 | for i := 0; i < b.N; i++ { 802 | _ = just.SliceSortCopy(slice, less) 803 | } 804 | }) 805 | } 806 | } 807 | 808 | // BenchmarkSliceGroupBy benchmarks the SliceGroupBy function 809 | func BenchmarkSliceGroupBy(b *testing.B) { 810 | sizes := []int{10, 1000} 811 | groupCounts := []int{2, 10, 100} 812 | 813 | for _, size := range sizes { 814 | for _, groups := range groupCounts { 815 | if groups > size { 816 | continue 817 | } 818 | b.Run(fmt.Sprintf("size_%d_groups_%d", size, groups), func(b *testing.B) { 819 | slice := createIntSlice(size) 820 | 821 | b.ResetTimer() 822 | for i := 0; i < b.N; i++ { 823 | _ = just.SliceGroupBy(slice, func(v int) int { 824 | return v % groups 825 | }) 826 | } 827 | }) 828 | } 829 | } 830 | } 831 | 832 | // BenchmarkSlice2MapFn benchmarks the Slice2MapFn function 833 | func BenchmarkSlice2MapFn(b *testing.B) { 834 | sizes := []int{10, 1000} 835 | 836 | for _, size := range sizes { 837 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 838 | slice := createIntSlice(size) 839 | 840 | b.ResetTimer() 841 | for i := 0; i < b.N; i++ { 842 | _ = just.Slice2MapFn(slice, func(idx int, v int) (string, int) { 843 | return strconv.Itoa(idx), v 844 | }) 845 | } 846 | }) 847 | } 848 | } 849 | 850 | // BenchmarkSlice2MapFnErr benchmarks the Slice2MapFnErr function 851 | func BenchmarkSlice2MapFnErr(b *testing.B) { 852 | sizes := []int{10, 1000} 853 | 854 | for _, size := range sizes { 855 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 856 | slice := createIntSlice(size) 857 | 858 | b.ResetTimer() 859 | for i := 0; i < b.N; i++ { 860 | _, _ = just.Slice2MapFnErr(slice, func(idx int, v int) (string, int, error) { 861 | return strconv.Itoa(idx), v, nil 862 | }) 863 | } 864 | }) 865 | } 866 | } 867 | 868 | // BenchmarkSliceFromElem benchmarks the SliceFromElem function 869 | func BenchmarkSliceFromElem(b *testing.B) { 870 | b.Run("int", func(b *testing.B) { 871 | elem := 42 872 | 873 | b.ResetTimer() 874 | for i := 0; i < b.N; i++ { 875 | _ = just.SliceFromElem(elem) 876 | } 877 | }) 878 | 879 | b.Run("string", func(b *testing.B) { 880 | elem := "test" 881 | 882 | b.ResetTimer() 883 | for i := 0; i < b.N; i++ { 884 | _ = just.SliceFromElem(elem) 885 | } 886 | }) 887 | } 888 | 889 | // BenchmarkSliceGetFirstN benchmarks the SliceGetFirstN function 890 | func BenchmarkSliceGetFirstN(b *testing.B) { 891 | sizes := []int{10, 1000} 892 | nValues := []int{1, 10, 100} 893 | 894 | for _, size := range sizes { 895 | for _, n := range nValues { 896 | if n > size { 897 | continue 898 | } 899 | b.Run(fmt.Sprintf("size_%d_n_%d", size, n), func(b *testing.B) { 900 | slice := createIntSlice(size) 901 | 902 | b.ResetTimer() 903 | for i := 0; i < b.N; i++ { 904 | _ = just.SliceGetFirstN(slice, n) 905 | } 906 | }) 907 | } 908 | } 909 | } 910 | 911 | // BenchmarkSliceCopy benchmarks the SliceCopy function 912 | func BenchmarkSliceCopy(b *testing.B) { 913 | sizes := []int{10, 1000} 914 | 915 | for _, size := range sizes { 916 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 917 | slice := createIntSlice(size) 918 | 919 | b.ResetTimer() 920 | for i := 0; i < b.N; i++ { 921 | _ = just.SliceCopy(slice) 922 | } 923 | }) 924 | } 925 | } 926 | 927 | // BenchmarkSliceReplaceFirst benchmarks the SliceReplaceFirst function 928 | func BenchmarkSliceReplaceFirst(b *testing.B) { 929 | sizes := []int{10, 1000} 930 | 931 | for _, size := range sizes { 932 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 933 | newElem := -1 934 | 935 | b.ResetTimer() 936 | for i := 0; i < b.N; i++ { 937 | slice := createIntSlice(size) 938 | just.SliceReplaceFirst(slice, func(idx int, v int) bool { 939 | return v == size/2 940 | }, newElem) 941 | } 942 | }) 943 | } 944 | } 945 | 946 | // BenchmarkSliceReplaceFirstOrAdd benchmarks the SliceReplaceFirstOrAdd function 947 | func BenchmarkSliceReplaceFirstOrAdd(b *testing.B) { 948 | sizes := []int{10, 1000} 949 | 950 | for _, size := range sizes { 951 | b.Run(fmt.Sprintf("size_%d_found", size), func(b *testing.B) { 952 | slice := createIntSlice(size) 953 | newElem := -1 954 | 955 | b.ResetTimer() 956 | for i := 0; i < b.N; i++ { 957 | _ = just.SliceReplaceFirstOrAdd(slice, func(idx int, v int) bool { 958 | return v == size/2 959 | }, newElem) 960 | } 961 | }) 962 | 963 | b.Run(fmt.Sprintf("size_%d_not_found", size), func(b *testing.B) { 964 | slice := createIntSlice(size) 965 | newElem := -1 966 | 967 | b.ResetTimer() 968 | for i := 0; i < b.N; i++ { 969 | _ = just.SliceReplaceFirstOrAdd(slice, func(idx int, v int) bool { 970 | return v < 0 971 | }, newElem) 972 | } 973 | }) 974 | } 975 | } 976 | 977 | // BenchmarkSliceLastDefault benchmarks the SliceLastDefault function 978 | func BenchmarkSliceLastDefault(b *testing.B) { 979 | sizes := []int{10, 1000} 980 | 981 | for _, size := range sizes { 982 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 983 | slice := createIntSlice(size) 984 | defaultVal := -1 985 | 986 | b.ResetTimer() 987 | for i := 0; i < b.N; i++ { 988 | _ = just.SliceLastDefault(slice, defaultVal) 989 | } 990 | }) 991 | } 992 | 993 | b.Run("empty_slice", func(b *testing.B) { 994 | var slice []int 995 | defaultVal := -1 996 | 997 | b.ResetTimer() 998 | for i := 0; i < b.N; i++ { 999 | _ = just.SliceLastDefault(slice, defaultVal) 1000 | } 1001 | }) 1002 | } 1003 | 1004 | // BenchmarkSlice2Iter benchmarks the Slice2Iter function 1005 | func BenchmarkSlice2Iter(b *testing.B) { 1006 | sizes := []int{10, 1000} 1007 | 1008 | for _, size := range sizes { 1009 | b.Run(fmt.Sprintf("size_%d_full_iteration", size), func(b *testing.B) { 1010 | slice := createIntSlice(size) 1011 | 1012 | b.ResetTimer() 1013 | for i := 0; i < b.N; i++ { 1014 | iter := just.Slice2Iter(slice) 1015 | sum := 0 1016 | iter(func(idx int, v int) bool { 1017 | sum += v 1018 | return true 1019 | }) 1020 | } 1021 | }) 1022 | 1023 | b.Run(fmt.Sprintf("size_%d_early_exit", size), func(b *testing.B) { 1024 | slice := createIntSlice(size) 1025 | 1026 | b.ResetTimer() 1027 | for i := 0; i < b.N; i++ { 1028 | iter := just.Slice2Iter(slice) 1029 | count := 0 1030 | iter(func(idx int, v int) bool { 1031 | count++ 1032 | return count < 10 1033 | }) 1034 | } 1035 | }) 1036 | } 1037 | } 1038 | 1039 | // BenchmarkSliceIter benchmarks the SliceIter function 1040 | func BenchmarkSliceIter(b *testing.B) { 1041 | sizes := []int{10, 1000} 1042 | 1043 | for _, size := range sizes { 1044 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 1045 | slice := createIntSlice(size) 1046 | 1047 | b.ResetTimer() 1048 | for i := 0; i < b.N; i++ { 1049 | iter := just.SliceIter(slice) 1050 | sum := 0 1051 | iter(func(ctx just.IterContext, v int) bool { 1052 | if ctx.IsFirst() || ctx.IsLast() { 1053 | sum += v * 2 1054 | } else { 1055 | sum += v 1056 | } 1057 | return true 1058 | }) 1059 | } 1060 | }) 1061 | } 1062 | } 1063 | 1064 | // BenchmarkSliceShuffle benchmarks the SliceShuffle function 1065 | func BenchmarkSliceShuffle(b *testing.B) { 1066 | sizes := []int{10, 1000} 1067 | 1068 | for _, size := range sizes { 1069 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 1070 | b.ResetTimer() 1071 | for i := 0; i < b.N; i++ { 1072 | slice := createIntSlice(size) 1073 | just.SliceShuffle(slice) 1074 | } 1075 | }) 1076 | } 1077 | } 1078 | 1079 | // BenchmarkSliceShuffleCopy benchmarks the SliceShuffleCopy function 1080 | func BenchmarkSliceShuffleCopy(b *testing.B) { 1081 | sizes := []int{10, 1000} 1082 | 1083 | for _, size := range sizes { 1084 | b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { 1085 | slice := createIntSlice(size) 1086 | 1087 | b.ResetTimer() 1088 | for i := 0; i < b.N; i++ { 1089 | _ = just.SliceShuffleCopy(slice) 1090 | } 1091 | }) 1092 | } 1093 | } 1094 | 1095 | // BenchmarkSliceLastN benchmarks the SliceLastN function 1096 | func BenchmarkSliceLastN(b *testing.B) { 1097 | sizes := []int{10, 1000} 1098 | nValues := []int{1, 10, 100} 1099 | 1100 | for _, size := range sizes { 1101 | for _, n := range nValues { 1102 | if n > size { 1103 | continue 1104 | } 1105 | b.Run(fmt.Sprintf("size_%d_n_%d", size, n), func(b *testing.B) { 1106 | slice := createIntSlice(size) 1107 | 1108 | b.ResetTimer() 1109 | for i := 0; i < b.N; i++ { 1110 | _ = just.SliceLastN(slice, n) 1111 | } 1112 | }) 1113 | } 1114 | } 1115 | } 1116 | -------------------------------------------------------------------------------- /slice_test.go: -------------------------------------------------------------------------------- 1 | package just_test 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "testing" 7 | "time" 8 | 9 | "github.com/kazhuravlev/just" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | var less = func(a, b int) bool { return a < b } 15 | 16 | func TestUniq(t *testing.T) { 17 | t.Parallel() 18 | 19 | table := []struct { 20 | name string 21 | in []int 22 | exp []int 23 | }{ 24 | { 25 | name: "empty_nil", 26 | in: nil, 27 | exp: []int{}, 28 | }, 29 | { 30 | name: "empty_len0", 31 | in: []int{}, 32 | exp: []int{}, 33 | }, 34 | { 35 | name: "uniq_1", 36 | in: []int{1}, 37 | exp: []int{1}, 38 | }, 39 | { 40 | name: "uniq_3", 41 | in: []int{1, 2, 3}, 42 | exp: []int{1, 2, 3}, 43 | }, 44 | { 45 | name: "non_uniq_3", 46 | in: []int{1, 1, 1}, 47 | exp: []int{1}, 48 | }, 49 | { 50 | name: "non_uniq_6_unordered", 51 | in: []int{1, 2, 1, 3, 1, 4}, 52 | exp: []int{1, 2, 3, 4}, 53 | }, 54 | { 55 | name: "non_uniq_100", 56 | in: make([]int, 100), 57 | exp: []int{0}, 58 | }, 59 | } 60 | 61 | for _, row := range table { 62 | row := row 63 | t.Run(row.name, func(t *testing.T) { 64 | t.Parallel() 65 | 66 | res := just.SliceUniq(row.in) 67 | require.EqualValues(t, row.exp, just.SliceSortCopy(res, func(a, b int) bool { return a < b })) 68 | }) 69 | } 70 | } 71 | 72 | func TestSliceUniqStable(t *testing.T) { 73 | t.Parallel() 74 | 75 | table := []struct { 76 | name string 77 | in []int 78 | exp []int 79 | }{ 80 | { 81 | name: "empty_nil", 82 | in: nil, 83 | exp: []int{}, 84 | }, 85 | { 86 | name: "empty_len0", 87 | in: []int{}, 88 | exp: []int{}, 89 | }, 90 | { 91 | name: "uniq_1", 92 | in: []int{1}, 93 | exp: []int{1}, 94 | }, 95 | { 96 | name: "uniq_3", 97 | in: []int{1, 2, 3}, 98 | exp: []int{1, 2, 3}, 99 | }, 100 | { 101 | name: "non_uniq_3", 102 | in: []int{1, 2, 1, 3, 1}, 103 | exp: []int{1, 2, 3}, 104 | }, 105 | { 106 | name: "non_uniq_6_unordered", 107 | in: []int{1, 2, 1, 3, 1, 4}, 108 | exp: []int{1, 2, 3, 4}, 109 | }, 110 | { 111 | name: "non_uniq_100", 112 | in: make([]int, 100), 113 | exp: []int{0}, 114 | }, 115 | } 116 | 117 | for _, row := range table { 118 | row := row 119 | t.Run(row.name, func(t *testing.T) { 120 | t.Parallel() 121 | 122 | res := just.SliceUniqStable(row.in) 123 | require.EqualValues(t, row.exp, res) 124 | }) 125 | } 126 | } 127 | 128 | func TestSliceReverse(t *testing.T) { 129 | t.Parallel() 130 | 131 | table := []struct { 132 | name string 133 | in []int 134 | exp []int 135 | }{ 136 | { 137 | name: "empty_nil", 138 | in: nil, 139 | exp: []int{}, 140 | }, 141 | { 142 | name: "empty_len0", 143 | in: []int{}, 144 | exp: []int{}, 145 | }, 146 | { 147 | name: "one_element", 148 | in: []int{1}, 149 | exp: []int{1}, 150 | }, 151 | { 152 | name: "three_elements", 153 | in: []int{1, 2, 3}, 154 | exp: []int{3, 2, 1}, 155 | }, 156 | } 157 | 158 | for _, row := range table { 159 | row := row 160 | t.Run(row.name, func(t *testing.T) { 161 | t.Parallel() 162 | 163 | res := just.SliceReverse(row.in) 164 | require.Equal(t, row.exp, res) 165 | }) 166 | } 167 | } 168 | 169 | func TestSliceZip(t *testing.T) { 170 | t.Parallel() 171 | 172 | table := []struct { 173 | name string 174 | in [][]int 175 | exp [][]int 176 | }{ 177 | { 178 | name: "empty", 179 | in: nil, 180 | exp: [][]int{}, 181 | }, 182 | { 183 | name: "empty_len0", 184 | in: [][]int{}, 185 | exp: [][]int{}, 186 | }, 187 | { 188 | name: "one_slice_in_args", 189 | in: [][]int{ 190 | {1, 2, 3}, 191 | }, 192 | exp: [][]int{ 193 | {1}, 194 | {2}, 195 | {3}, 196 | }, 197 | }, 198 | { 199 | name: "two_slice_in_args", 200 | in: [][]int{ 201 | {10, 11, 12}, 202 | {20, 21, 22}, 203 | }, 204 | exp: [][]int{ 205 | {10, 20}, 206 | {11, 21}, 207 | {12, 22}, 208 | }, 209 | }, 210 | { 211 | name: "three_slices_diff_len", 212 | in: [][]int{ 213 | {10}, 214 | {20, 21}, 215 | {30, 31, 32}, 216 | }, 217 | exp: [][]int{ 218 | {10, 20, 30}, 219 | }, 220 | }, 221 | { 222 | name: "two_slices_one_empty", 223 | in: [][]int{ 224 | {20, 21}, 225 | {}, 226 | }, 227 | exp: [][]int{}, 228 | }, 229 | } 230 | 231 | for _, row := range table { 232 | row := row 233 | t.Run(row.name, func(t *testing.T) { 234 | t.Parallel() 235 | 236 | res := just.SliceZip(row.in...) 237 | assert.Equal(t, row.exp, res) 238 | }) 239 | } 240 | } 241 | 242 | func TestSliceChunk(t *testing.T) { 243 | t.Parallel() 244 | 245 | table := []struct { 246 | name string 247 | in []int 248 | fn func(int, int) bool 249 | exp [][]int 250 | }{ 251 | { 252 | name: "empty", 253 | in: nil, 254 | fn: nil, 255 | exp: [][]int{}, 256 | }, 257 | { 258 | name: "split_fn_always_true", 259 | in: []int{1, 2, 3, 4}, 260 | fn: func(i int, v int) bool { return true }, 261 | exp: [][]int{ 262 | {1}, 263 | {2}, 264 | {3}, 265 | {4}, 266 | }, 267 | }, 268 | { 269 | name: "split_fn_always_false", 270 | in: []int{1, 2, 3, 4}, 271 | fn: func(i int, v int) bool { return false }, 272 | exp: [][]int{ 273 | {1, 2, 3, 4}, 274 | }, 275 | }, 276 | { 277 | name: "split_every_2", 278 | in: []int{1, 2, 3, 4}, 279 | fn: func(i int, v int) bool { return i%2 == 0 }, 280 | exp: [][]int{ 281 | {1, 2}, 282 | {3, 4}, 283 | }, 284 | }, 285 | { 286 | name: "split_every_3", 287 | in: []int{1, 2, 3, 4}, 288 | fn: func(i int, v int) bool { return i%3 == 0 }, 289 | exp: [][]int{ 290 | {1, 2, 3}, 291 | {4}, 292 | }, 293 | }, 294 | } 295 | 296 | for _, row := range table { 297 | row := row 298 | t.Run(row.name, func(t *testing.T) { 299 | t.Parallel() 300 | 301 | res := just.SliceChunk(row.in, row.fn) 302 | assert.Equal(t, row.exp, res) 303 | }) 304 | } 305 | } 306 | 307 | func TestSliceChunkEvery(t *testing.T) { 308 | t.Parallel() 309 | 310 | table := []struct { 311 | name string 312 | in []int 313 | every int 314 | exp [][]int 315 | }{ 316 | { 317 | name: "empty", 318 | in: nil, 319 | every: 1, 320 | exp: [][]int{}, 321 | }, 322 | { 323 | name: "split_every_1", 324 | in: []int{1, 2, 3, 4}, 325 | every: 1, 326 | exp: [][]int{ 327 | {1}, 328 | {2}, 329 | {3}, 330 | {4}, 331 | }, 332 | }, 333 | { 334 | name: "split_every_2", 335 | in: []int{1, 2, 3, 4}, 336 | every: 2, 337 | exp: [][]int{ 338 | {1, 2}, 339 | {3, 4}, 340 | }, 341 | }, 342 | { 343 | name: "split_every_minus_2", 344 | in: []int{1, 2, 3, 4}, 345 | every: -2, 346 | exp: [][]int{ 347 | {1, 2}, 348 | {3, 4}, 349 | }, 350 | }, 351 | { 352 | name: "split_every_3", 353 | in: []int{1, 2, 3, 4}, 354 | every: 3, 355 | exp: [][]int{ 356 | {1, 2, 3}, 357 | {4}, 358 | }, 359 | }, 360 | } 361 | 362 | for _, row := range table { 363 | row := row 364 | t.Run(row.name, func(t *testing.T) { 365 | t.Parallel() 366 | 367 | res := just.SliceChunkEvery(row.in, row.every) 368 | assert.Equal(t, row.exp, res) 369 | }) 370 | } 371 | 372 | t.Run("split_every_0_invalid", func(t *testing.T) { 373 | assert.Panics(t, func() { 374 | just.SliceChunkEvery([]int{1, 2, 3, 4}, 0) 375 | }) 376 | }) 377 | } 378 | 379 | func TestSliceFillElem(t *testing.T) { 380 | t.Parallel() 381 | 382 | res := just.SliceFillElem(3, "Hello") 383 | assert.Equal(t, []string{"Hello", "Hello", "Hello"}, res) 384 | } 385 | 386 | func TestSliceNotNil(t *testing.T) { 387 | t.Parallel() 388 | 389 | table := []struct { 390 | in []int 391 | exp []int 392 | }{ 393 | { 394 | in: nil, 395 | exp: []int{}, 396 | }, 397 | { 398 | in: []int{}, 399 | exp: []int{}, 400 | }, 401 | { 402 | in: []int{1}, 403 | exp: []int{1}, 404 | }, 405 | } 406 | 407 | for _, row := range table { 408 | t.Run("", func(t *testing.T) { 409 | res := just.SliceNotNil(row.in) 410 | assert.Equal(t, row.exp, res) 411 | }) 412 | } 413 | } 414 | 415 | func TestSliceFindFirstElem(t *testing.T) { 416 | t.Parallel() 417 | 418 | table := []struct { 419 | name string 420 | in []int 421 | elem int 422 | exp int 423 | expIndex int 424 | }{ 425 | { 426 | name: "empty", 427 | in: nil, 428 | elem: 0, 429 | exp: 0, 430 | expIndex: -1, 431 | }, 432 | { 433 | name: "found_index_0", 434 | in: []int{1, 1, 1}, 435 | elem: 1, 436 | exp: 1, 437 | expIndex: 0, 438 | }, 439 | { 440 | name: "found_index_2", 441 | in: []int{3, 2, 1}, 442 | elem: 1, 443 | exp: 1, 444 | expIndex: 2, 445 | }, 446 | { 447 | name: "not_found", 448 | in: []int{3, 2, 1}, 449 | elem: 42, 450 | exp: 0, 451 | expIndex: -1, 452 | }, 453 | } 454 | 455 | for _, row := range table { 456 | row := row 457 | t.Run(row.name, func(t *testing.T) { 458 | t.Parallel() 459 | 460 | res := just.SliceFindFirstElem(row.in, row.elem) 461 | assert.Equal(t, row.expIndex, res.Idx) 462 | assert.Equal(t, row.exp, res.Val) 463 | }) 464 | } 465 | } 466 | 467 | func TestSliceFindLastElem(t *testing.T) { 468 | t.Parallel() 469 | 470 | table := []struct { 471 | name string 472 | in []int 473 | elem int 474 | exp int 475 | expIndex int 476 | }{ 477 | { 478 | name: "empty", 479 | in: nil, 480 | elem: 0, 481 | exp: 0, 482 | expIndex: -1, 483 | }, 484 | { 485 | name: "found_index_0", 486 | in: []int{1, 2, 3}, 487 | elem: 1, 488 | exp: 1, 489 | expIndex: 0, 490 | }, 491 | { 492 | name: "found_index_2", 493 | in: []int{3, 2, 1}, 494 | elem: 1, 495 | exp: 1, 496 | expIndex: 2, 497 | }, 498 | { 499 | name: "not_found", 500 | in: []int{3, 2, 1}, 501 | elem: 42, 502 | exp: 0, 503 | expIndex: -1, 504 | }, 505 | } 506 | 507 | for _, row := range table { 508 | row := row 509 | t.Run(row.name, func(t *testing.T) { 510 | t.Parallel() 511 | 512 | res := just.SliceFindLastElem(row.in, row.elem) 513 | assert.Equal(t, row.expIndex, res.Idx) 514 | assert.Equal(t, row.exp, res.Val) 515 | }) 516 | } 517 | } 518 | 519 | func TestSliceFindAllElements(t *testing.T) { 520 | t.Parallel() 521 | 522 | table := []struct { 523 | name string 524 | in []int 525 | fn func(int, int) bool 526 | exp []int 527 | }{ 528 | { 529 | name: "empty", 530 | in: nil, 531 | fn: nil, 532 | exp: []int{}, 533 | }, 534 | { 535 | name: "found_gte_2", 536 | in: []int{1, 2, 3}, 537 | fn: func(i int, v int) bool { 538 | return v >= 2 539 | }, 540 | exp: []int{2, 3}, 541 | }, 542 | { 543 | name: "not_found", 544 | in: []int{3, 2, 1}, 545 | fn: func(i int, v int) bool { 546 | return v == 42 547 | }, 548 | exp: []int{}, 549 | }, 550 | } 551 | 552 | for _, row := range table { 553 | row := row 554 | t.Run(row.name, func(t *testing.T) { 555 | t.Parallel() 556 | 557 | res := just.SliceFindAllElements(row.in, row.fn) 558 | assert.Equal(t, row.exp, res) 559 | }) 560 | } 561 | } 562 | 563 | func TestSliceFindAllIndexes(t *testing.T) { 564 | t.Parallel() 565 | 566 | table := []struct { 567 | name string 568 | in []int 569 | fn func(int, int) bool 570 | exp []int 571 | }{ 572 | { 573 | name: "empty", 574 | in: nil, 575 | fn: nil, 576 | exp: []int{}, 577 | }, 578 | { 579 | name: "found_gte_20", 580 | in: []int{11, 21, 31}, 581 | fn: func(i int, v int) bool { 582 | return v >= 20 583 | }, 584 | exp: []int{1, 2}, 585 | }, 586 | { 587 | name: "not_found", 588 | in: []int{3, 2, 1}, 589 | fn: func(i int, v int) bool { 590 | return v == 42 591 | }, 592 | exp: []int{}, 593 | }, 594 | } 595 | 596 | for _, row := range table { 597 | row := row 598 | t.Run(row.name, func(t *testing.T) { 599 | t.Parallel() 600 | 601 | res := just.SliceFindAllIndexes(row.in, row.fn) 602 | assert.Equal(t, row.exp, res) 603 | }) 604 | } 605 | } 606 | 607 | func TestSliceFindAll(t *testing.T) { 608 | t.Parallel() 609 | 610 | table := []struct { 611 | name string 612 | in []int 613 | fn func(int, int) bool 614 | exp []just.SliceElem[int] 615 | }{ 616 | { 617 | name: "empty", 618 | in: nil, 619 | fn: nil, 620 | exp: []just.SliceElem[int]{}, 621 | }, 622 | { 623 | name: "found_gte_20", 624 | in: []int{11, 21, 31}, 625 | fn: func(i int, v int) bool { 626 | return v >= 20 627 | }, 628 | exp: []just.SliceElem[int]{ 629 | {Idx: 1, Val: 21}, 630 | {Idx: 2, Val: 31}, 631 | }, 632 | }, 633 | { 634 | name: "not_found", 635 | in: []int{3, 2, 1}, 636 | fn: func(i int, v int) bool { 637 | return v == 42 638 | }, 639 | exp: []just.SliceElem[int]{}, 640 | }, 641 | } 642 | 643 | for _, row := range table { 644 | row := row 645 | t.Run(row.name, func(t *testing.T) { 646 | t.Parallel() 647 | 648 | res := just.SliceFindAll(row.in, row.fn) 649 | assert.Equal(t, row.exp, res) 650 | }) 651 | } 652 | } 653 | 654 | func TestSliceRange(t *testing.T) { 655 | t.Parallel() 656 | 657 | table := []struct { 658 | name string 659 | start, stop, step int 660 | exp []int 661 | }{ 662 | { 663 | name: "from_zero_to_zero", 664 | start: 0, 665 | stop: 0, 666 | step: 0, 667 | exp: []int{}, 668 | }, 669 | { 670 | name: "from_0_to_5_by_2", 671 | start: 0, 672 | stop: 5, 673 | step: 2, 674 | exp: []int{0, 2, 4}, 675 | }, 676 | { 677 | name: "from_0_to_5_by_minus_2", 678 | start: 0, 679 | stop: 5, 680 | step: -2, 681 | exp: []int{}, 682 | }, 683 | { 684 | name: "from_5_to_0_by_2", 685 | start: 5, 686 | stop: 0, 687 | step: 2, 688 | exp: []int{}, 689 | }, 690 | { 691 | name: "from_minus_5_to_minus_1_by_2", 692 | start: -5, 693 | stop: -1, 694 | step: 2, 695 | exp: []int{-5, -3}, 696 | }, 697 | { 698 | name: "from_0_to_minus_10_by_minus_10", 699 | start: 0, 700 | stop: -10, 701 | step: -10, 702 | exp: []int{0}, 703 | }, 704 | { 705 | name: "from_0_to_10_by_0", 706 | start: 0, 707 | stop: 10, 708 | step: 0, 709 | exp: []int{}, 710 | }, 711 | } 712 | 713 | for _, row := range table { 714 | row := row 715 | t.Run(row.name, func(t *testing.T) { 716 | t.Parallel() 717 | 718 | res := just.SliceRange(row.start, row.stop, row.step) 719 | assert.Equal(t, row.exp, res) 720 | }) 721 | } 722 | } 723 | 724 | func TestSliceDifference(t *testing.T) { 725 | t.Parallel() 726 | 727 | table := []struct { 728 | name string 729 | in1, in2 []int 730 | exp []int 731 | }{ 732 | { 733 | name: "empty_both", 734 | in1: nil, 735 | in2: nil, 736 | exp: nil, 737 | }, 738 | { 739 | name: "empty_first", 740 | in1: nil, 741 | in2: []int{1, 2, 3}, 742 | exp: []int{1, 2, 3}, 743 | }, 744 | { 745 | name: "empty_second", 746 | in1: []int{1, 2, 3}, 747 | in2: nil, 748 | exp: nil, 749 | }, 750 | { 751 | name: "equal", 752 | in1: []int{1, 2, 3}, 753 | in2: []int{1, 2, 3}, 754 | exp: []int{}, 755 | }, 756 | { 757 | name: "has_diff_1", 758 | in1: []int{1, 2, 3}, 759 | in2: []int{1, 2, 3, 4}, 760 | exp: []int{4}, 761 | }, 762 | { 763 | name: "has_diff_2", 764 | in1: []int{1, 2}, 765 | in2: []int{1, 2, 3, 4}, 766 | exp: []int{3, 4}, 767 | }, 768 | { 769 | name: "has_diff_3_duplicated", 770 | in1: []int{1, 2}, 771 | in2: []int{2, 4, 4, 2, 2, 4}, 772 | exp: []int{4}, 773 | }, 774 | } 775 | 776 | for _, row := range table { 777 | row := row 778 | t.Run(row.name, func(t *testing.T) { 779 | t.Parallel() 780 | 781 | res := just.SliceDifference(row.in1, row.in2) 782 | assert.Equal(t, just.SliceSortCopy(row.exp, less), just.SliceSortCopy(res, less)) 783 | }) 784 | } 785 | } 786 | 787 | func TestSliceIntersection(t *testing.T) { 788 | t.Parallel() 789 | 790 | table := []struct { 791 | name string 792 | in1, in2 []int 793 | exp []int 794 | }{ 795 | { 796 | name: "empty_both", 797 | in1: nil, 798 | in2: nil, 799 | exp: nil, 800 | }, 801 | { 802 | name: "empty_first", 803 | in1: nil, 804 | in2: []int{1, 2, 3}, 805 | exp: nil, 806 | }, 807 | { 808 | name: "empty_second", 809 | in1: []int{1, 2, 3}, 810 | in2: nil, 811 | exp: nil, 812 | }, 813 | { 814 | name: "equal", 815 | in1: []int{1, 2, 3}, 816 | in2: []int{1, 2, 3}, 817 | exp: []int{1, 2, 3}, 818 | }, 819 | { 820 | name: "has_diff_1", 821 | in1: []int{1, 2, 3}, 822 | in2: []int{1, 2, 3, 4}, 823 | exp: []int{1, 2, 3}, 824 | }, 825 | { 826 | name: "has_diff_2", 827 | in1: []int{1, 2}, 828 | in2: []int{1, 2, 3, 4}, 829 | exp: []int{1, 2}, 830 | }, 831 | { 832 | name: "has_diff_3_duplicated", 833 | in1: []int{1, 2}, 834 | in2: []int{2, 4, 4, 2, 2, 4, 1}, 835 | exp: []int{1, 2}, 836 | }, 837 | } 838 | 839 | for _, row := range table { 840 | row := row 841 | t.Run(row.name, func(t *testing.T) { 842 | t.Parallel() 843 | 844 | res := just.SliceIntersection(row.in1, row.in2) 845 | assert.Equal(t, just.SliceSortCopy(row.exp, less), just.SliceSortCopy(res, less)) 846 | }) 847 | } 848 | } 849 | 850 | func TestSliceEqualUnordered(t *testing.T) { 851 | t.Parallel() 852 | 853 | table := []struct { 854 | name string 855 | in1, in2 []int 856 | exp bool 857 | }{ 858 | { 859 | name: "empty", 860 | in1: nil, 861 | in2: nil, 862 | exp: true, 863 | }, 864 | { 865 | name: "empty_first", 866 | in1: nil, 867 | in2: []int{1}, 868 | exp: false, 869 | }, 870 | { 871 | name: "empty_second", 872 | in1: []int{1}, 873 | in2: nil, 874 | exp: false, 875 | }, 876 | { 877 | name: "equal_full", 878 | in1: []int{1}, 879 | in2: []int{1}, 880 | exp: true, 881 | }, 882 | { 883 | name: "equal_dupl", 884 | in1: []int{1, 1, 1}, 885 | in2: []int{1}, 886 | exp: true, 887 | }, 888 | { 889 | name: "equal_dupl2", 890 | in1: []int{1, 1}, 891 | in2: []int{1, 1, 1, 1}, 892 | exp: true, 893 | }, 894 | { 895 | name: "equal2", 896 | in1: []int{1, 1, 2, 3, 2, 1}, 897 | in2: []int{1, 2, 3, 3}, 898 | exp: true, 899 | }, 900 | { 901 | name: "equal3", 902 | in1: []int{1, 2, 3}, 903 | in2: []int{4, 5, 6}, 904 | exp: false, 905 | }, 906 | } 907 | 908 | for _, row := range table { 909 | row := row 910 | t.Run(row.name, func(t *testing.T) { 911 | t.Parallel() 912 | 913 | res := just.SliceEqualUnordered(row.in1, row.in2) 914 | assert.Equal(t, row.exp, res) 915 | }) 916 | } 917 | } 918 | 919 | func TestSliceChain(t *testing.T) { 920 | t.Parallel() 921 | 922 | res1 := just.SliceChain[int]() 923 | assert.Equal(t, []int{}, res1) 924 | 925 | res := just.SliceChain([]int{1, 2, 3}, []int{4, 5, 6}, []int{7, 8, 9}) 926 | assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, res) 927 | } 928 | 929 | func TestSliceSort(t *testing.T) { 930 | t.Parallel() 931 | 932 | a := []int{1, 3, 2} 933 | just.SliceSort(a, less) 934 | assert.Equal(t, []int{1, 2, 3}, a) 935 | } 936 | 937 | func TestSliceElem(t *testing.T) { 938 | t.Parallel() 939 | 940 | notExists := just.SliceElem[int]{Idx: -1, Val: 0} 941 | existsFirst := just.SliceElem[int]{Idx: 0, Val: 10} 942 | existsSecond := just.SliceElem[int]{Idx: 1, Val: 20} 943 | 944 | t.Run("ok", func(t *testing.T) { 945 | t.Parallel() 946 | 947 | assert.False(t, notExists.Ok()) 948 | assert.True(t, existsFirst.Ok()) 949 | assert.True(t, existsSecond.Ok()) 950 | }) 951 | 952 | t.Run("value_ok", func(t *testing.T) { 953 | t.Parallel() 954 | 955 | var v int 956 | var ok bool 957 | 958 | v, ok = notExists.ValueOk() 959 | assert.Equal(t, 0, v) 960 | assert.False(t, ok) 961 | 962 | v, ok = existsFirst.ValueOk() 963 | assert.Equal(t, 10, v) 964 | assert.True(t, ok) 965 | 966 | v, ok = existsSecond.ValueOk() 967 | assert.Equal(t, 20, v) 968 | assert.True(t, ok) 969 | }) 970 | 971 | t.Run("value_idx", func(t *testing.T) { 972 | t.Parallel() 973 | 974 | var v int 975 | var idx int 976 | 977 | v, idx = notExists.ValueIdx() 978 | assert.Equal(t, 0, v) 979 | assert.Equal(t, -1, idx) 980 | 981 | v, idx = existsFirst.ValueIdx() 982 | assert.Equal(t, 10, v) 983 | assert.Equal(t, 0, idx) 984 | 985 | v, idx = existsSecond.ValueIdx() 986 | assert.Equal(t, 20, v) 987 | assert.Equal(t, 1, idx) 988 | }) 989 | } 990 | 991 | func TestSliceWithout(t *testing.T) { 992 | t.Parallel() 993 | 994 | table := []struct { 995 | name string 996 | in []int 997 | elem int 998 | exp []int 999 | }{ 1000 | { 1001 | name: "empty", 1002 | in: nil, 1003 | elem: 0, 1004 | exp: []int{}, 1005 | }, 1006 | { 1007 | name: "exclude_two", 1008 | in: []int{1, 2, 3, 4, 5, 6}, 1009 | elem: 2, 1010 | exp: []int{1, 3, 4, 5, 6}, 1011 | }, 1012 | { 1013 | name: "exclude_not_found", 1014 | in: []int{1, 2, 3, 4, 5, 6}, 1015 | elem: 10000, 1016 | exp: []int{1, 2, 3, 4, 5, 6}, 1017 | }, 1018 | } 1019 | 1020 | for _, row := range table { 1021 | row := row 1022 | t.Run(row.name, func(t *testing.T) { 1023 | t.Parallel() 1024 | 1025 | res := just.SliceWithoutElem(row.in, row.elem) 1026 | assert.Equal(t, row.exp, res) 1027 | }) 1028 | } 1029 | } 1030 | 1031 | func TestSliceUnion(t *testing.T) { 1032 | t.Parallel() 1033 | 1034 | table := []struct { 1035 | name string 1036 | in [][]int 1037 | exp []int 1038 | }{ 1039 | { 1040 | name: "empty", 1041 | in: nil, 1042 | exp: []int{}, 1043 | }, 1044 | { 1045 | name: "case1", 1046 | in: [][]int{ 1047 | {1, 2, 3}, 1048 | }, 1049 | exp: []int{1, 2, 3}, 1050 | }, 1051 | { 1052 | name: "case2", 1053 | in: [][]int{ 1054 | {1, 2, 3}, 1055 | {1, 2, 3, 1, 1, 1}, 1056 | {3, 4, 5}, 1057 | {4, 5, 1, 12}, 1058 | }, 1059 | exp: []int{1, 2, 3, 4, 5, 12}, 1060 | }, 1061 | } 1062 | 1063 | for _, row := range table { 1064 | row := row 1065 | t.Run(row.name, func(t *testing.T) { 1066 | t.Parallel() 1067 | 1068 | res := just.SliceUnion(row.in...) 1069 | assert.Equal(t, just.SliceSortCopy(row.exp, less), just.SliceSortCopy(res, less)) 1070 | }) 1071 | } 1072 | } 1073 | 1074 | func TestSliceAddNotExists(t *testing.T) { 1075 | t.Parallel() 1076 | 1077 | table := []struct { 1078 | name string 1079 | in []int 1080 | elem int 1081 | exp []int 1082 | }{ 1083 | { 1084 | name: "empty", 1085 | in: nil, 1086 | elem: 11, 1087 | exp: []int{11}, 1088 | }, 1089 | { 1090 | name: "case1", 1091 | in: []int{1, 1, 1}, 1092 | elem: 1, 1093 | exp: []int{1, 1, 1}, 1094 | }, 1095 | { 1096 | name: "case2", 1097 | in: []int{1, 2, 3}, 1098 | elem: 4, 1099 | exp: []int{1, 2, 3, 4}, 1100 | }, 1101 | } 1102 | 1103 | for _, row := range table { 1104 | row := row 1105 | t.Run(row.name, func(t *testing.T) { 1106 | t.Parallel() 1107 | 1108 | res := just.SliceAddNotExists(row.in, row.elem) 1109 | assert.Equal(t, row.exp, res) 1110 | }) 1111 | } 1112 | } 1113 | 1114 | func TestSliceContainsElem(t *testing.T) { 1115 | t.Parallel() 1116 | 1117 | table := []struct { 1118 | name string 1119 | in []int 1120 | elem int 1121 | exp bool 1122 | }{ 1123 | { 1124 | name: "empty", 1125 | in: nil, 1126 | elem: 11, 1127 | exp: false, 1128 | }, 1129 | { 1130 | name: "case1", 1131 | in: []int{1, 1, 1}, 1132 | elem: 1, 1133 | exp: true, 1134 | }, 1135 | { 1136 | name: "case2", 1137 | in: []int{1, 2, 3}, 1138 | elem: 4, 1139 | exp: false, 1140 | }, 1141 | } 1142 | 1143 | for _, row := range table { 1144 | row := row 1145 | t.Run(row.name, func(t *testing.T) { 1146 | t.Parallel() 1147 | 1148 | res := just.SliceContainsElem(row.in, row.elem) 1149 | assert.Equal(t, row.exp, res) 1150 | }) 1151 | } 1152 | } 1153 | 1154 | func TestSliceAll(t *testing.T) { 1155 | t.Parallel() 1156 | 1157 | table := []struct { 1158 | name string 1159 | in []int 1160 | fn func(int) bool 1161 | exp bool 1162 | }{ 1163 | { 1164 | name: "true_on_empty", 1165 | in: nil, 1166 | fn: func(v int) bool { return true }, 1167 | exp: true, 1168 | }, 1169 | { 1170 | name: "case1", 1171 | in: []int{1, 1, 1}, 1172 | fn: func(v int) bool { return v == 1 }, 1173 | exp: true, 1174 | }, 1175 | { 1176 | name: "case2", 1177 | in: []int{1, 2, 3}, 1178 | fn: func(v int) bool { return v == 1 }, 1179 | exp: false, 1180 | }, 1181 | } 1182 | 1183 | for _, row := range table { 1184 | row := row 1185 | t.Run(row.name, func(t *testing.T) { 1186 | t.Parallel() 1187 | 1188 | res := just.SliceAll(row.in, row.fn) 1189 | assert.Equal(t, row.exp, res) 1190 | }) 1191 | } 1192 | } 1193 | 1194 | func TestSlice2MapFn(t *testing.T) { 1195 | t.Parallel() 1196 | 1197 | table := []struct { 1198 | name string 1199 | in []int 1200 | fn func(int, int) (string, string) 1201 | exp map[string]string 1202 | }{ 1203 | { 1204 | name: "empty", 1205 | in: nil, 1206 | fn: func(k, v int) (string, string) { return strconv.Itoa(k), strconv.Itoa(v) }, 1207 | exp: map[string]string{}, 1208 | }, 1209 | { 1210 | name: "uniq_values", 1211 | in: []int{10, 20, 30}, 1212 | fn: func(k, v int) (string, string) { return strconv.Itoa(v), strconv.Itoa(k) }, 1213 | exp: map[string]string{"10": "0", "20": "1", "30": "2"}, 1214 | }, 1215 | { 1216 | name: "non_uniq_values", 1217 | in: []int{10, 10, 10}, 1218 | fn: func(k, v int) (string, string) { return strconv.Itoa(v), strconv.Itoa(k) }, 1219 | exp: map[string]string{"10": "2"}, 1220 | }, 1221 | } 1222 | 1223 | for _, row := range table { 1224 | row := row 1225 | t.Run(row.name, func(t *testing.T) { 1226 | t.Parallel() 1227 | 1228 | res := just.Slice2MapFn(row.in, row.fn) 1229 | assert.Equal(t, row.exp, res) 1230 | }) 1231 | } 1232 | } 1233 | 1234 | func TestSlice2MapFnErr(t *testing.T) { 1235 | t.Parallel() 1236 | 1237 | atoi := func(k int, v string) (int, int, error) { 1238 | x, err := strconv.Atoi(v) 1239 | return k, x, err 1240 | } 1241 | 1242 | t.Run("error_case", func(t *testing.T) { 1243 | res, err := just.Slice2MapFnErr([]string{"1", "lol", "2"}, atoi) 1244 | require.Error(t, err) 1245 | require.Empty(t, res) 1246 | }) 1247 | 1248 | table := []struct { 1249 | name string 1250 | in []string 1251 | fn func(int, string) (int, int, error) 1252 | exp map[int]int 1253 | }{ 1254 | { 1255 | name: "empty", 1256 | in: nil, 1257 | fn: atoi, 1258 | exp: map[int]int{}, 1259 | }, 1260 | { 1261 | name: "uniq_values", 1262 | in: []string{"10", "20", "30"}, 1263 | fn: atoi, 1264 | exp: map[int]int{ 1265 | 0: 10, 1266 | 1: 20, 1267 | 2: 30, 1268 | }, 1269 | }, 1270 | } 1271 | 1272 | for _, row := range table { 1273 | row := row 1274 | t.Run(row.name, func(t *testing.T) { 1275 | t.Parallel() 1276 | 1277 | res, err := just.Slice2MapFnErr(row.in, row.fn) 1278 | assert.Equal(t, row.exp, res) 1279 | assert.NoError(t, err) 1280 | }) 1281 | } 1282 | } 1283 | 1284 | func TestSliceMap(t *testing.T) { 1285 | t.Parallel() 1286 | 1287 | table := []struct { 1288 | name string 1289 | in []int 1290 | fn func(int) string 1291 | exp []string 1292 | }{ 1293 | { 1294 | name: "empty", 1295 | in: nil, 1296 | fn: strconv.Itoa, 1297 | exp: []string{}, 1298 | }, 1299 | { 1300 | name: "case1", 1301 | in: []int{1, 1, 1}, 1302 | fn: strconv.Itoa, 1303 | exp: []string{"1", "1", "1"}, 1304 | }, 1305 | { 1306 | name: "case2", 1307 | in: []int{1, 2, 3}, 1308 | fn: strconv.Itoa, 1309 | exp: []string{"1", "2", "3"}, 1310 | }, 1311 | } 1312 | 1313 | for _, row := range table { 1314 | row := row 1315 | t.Run(row.name, func(t *testing.T) { 1316 | t.Parallel() 1317 | 1318 | res := just.SliceMap(row.in, row.fn) 1319 | assert.Equal(t, row.exp, res) 1320 | }) 1321 | } 1322 | } 1323 | 1324 | func TestSliceFlatMap(t *testing.T) { 1325 | t.Parallel() 1326 | 1327 | fn := func(v int) []int { 1328 | return []int{v, 10 * v, 100 * v} 1329 | } 1330 | 1331 | table := []struct { 1332 | name string 1333 | in []int 1334 | fn func(int) []int 1335 | exp []int 1336 | }{ 1337 | { 1338 | name: "empty", 1339 | in: nil, 1340 | fn: fn, 1341 | exp: []int{}, 1342 | }, 1343 | { 1344 | name: "case1", 1345 | in: []int{1, 2, 3}, 1346 | fn: fn, 1347 | exp: []int{1, 10, 100, 2, 20, 200, 3, 30, 300}, 1348 | }, 1349 | { 1350 | name: "case2", 1351 | in: []int{1, 2, 3}, 1352 | fn: func(v int) []int { return nil }, 1353 | exp: []int{}, 1354 | }, 1355 | } 1356 | 1357 | for _, row := range table { 1358 | row := row 1359 | t.Run(row.name, func(t *testing.T) { 1360 | t.Parallel() 1361 | 1362 | res := just.SliceFlatMap(row.in, row.fn) 1363 | assert.Equal(t, row.exp, res) 1364 | }) 1365 | } 1366 | } 1367 | 1368 | func TestSliceFlatMap2(t *testing.T) { 1369 | t.Parallel() 1370 | 1371 | fn := func(i int, v int) []int { 1372 | if i == 0 { 1373 | return nil 1374 | } 1375 | 1376 | return []int{v, 10 * v, 100 * v} 1377 | } 1378 | 1379 | table := []struct { 1380 | name string 1381 | in []int 1382 | fn func(int, int) []int 1383 | exp []int 1384 | }{ 1385 | { 1386 | name: "empty", 1387 | in: nil, 1388 | fn: fn, 1389 | exp: []int{}, 1390 | }, 1391 | { 1392 | name: "case1", 1393 | in: []int{1, 2, 3}, 1394 | fn: fn, 1395 | exp: []int{2, 20, 200, 3, 30, 300}, 1396 | }, 1397 | { 1398 | name: "case2", 1399 | in: []int{1, 2, 3}, 1400 | fn: func(i int, v int) []int { return []int{i, v} }, 1401 | exp: []int{0, 1, 1, 2, 2, 3}, 1402 | }, 1403 | } 1404 | 1405 | for _, row := range table { 1406 | row := row 1407 | t.Run(row.name, func(t *testing.T) { 1408 | t.Parallel() 1409 | 1410 | res := just.SliceFlatMap2(row.in, row.fn) 1411 | assert.Equal(t, row.exp, res) 1412 | }) 1413 | } 1414 | } 1415 | 1416 | func TestSliceApply(t *testing.T) { 1417 | t.Parallel() 1418 | 1419 | t.Run("empty", func(t *testing.T) { 1420 | var s int 1421 | just.SliceApply([]int{}, func(idx int, v int) { s += v }) 1422 | assert.Equal(t, 0, s) 1423 | }) 1424 | 1425 | t.Run("case1", func(t *testing.T) { 1426 | var s int 1427 | just.SliceApply([]int{1, 2, 3}, func(idx int, v int) { s += v }) 1428 | assert.Equal(t, 6, s) 1429 | }) 1430 | } 1431 | 1432 | func TestSliceMapErr(t *testing.T) { 1433 | t.Parallel() 1434 | 1435 | table := []struct { 1436 | name string 1437 | in []int 1438 | fn func(int) (string, error) 1439 | exp []string 1440 | err bool 1441 | }{ 1442 | { 1443 | name: "empty", 1444 | in: nil, 1445 | fn: func(v int) (string, error) { 1446 | return "", nil 1447 | }, 1448 | exp: []string{}, 1449 | err: false, 1450 | }, 1451 | { 1452 | name: "case1", 1453 | in: []int{1, 2, 3}, 1454 | fn: func(v int) (string, error) { 1455 | return strconv.Itoa(v), nil 1456 | }, 1457 | exp: []string{"1", "2", "3"}, 1458 | err: false, 1459 | }, 1460 | { 1461 | name: "case2", 1462 | in: []int{1, 2, 3}, 1463 | fn: func(v int) (string, error) { 1464 | return "", errors.New("the sky is falling") 1465 | }, 1466 | exp: nil, 1467 | err: true, 1468 | }, 1469 | } 1470 | 1471 | for _, row := range table { 1472 | row := row 1473 | t.Run(row.name, func(t *testing.T) { 1474 | t.Parallel() 1475 | 1476 | res, err := just.SliceMapErr(row.in, row.fn) 1477 | if row.err { 1478 | require.Error(t, err) 1479 | } else { 1480 | require.NoError(t, err) 1481 | } 1482 | assert.Equal(t, row.exp, res) 1483 | }) 1484 | } 1485 | } 1486 | 1487 | func TestSliceGroupBy(t *testing.T) { 1488 | t.Parallel() 1489 | 1490 | table := []struct { 1491 | name string 1492 | in []int 1493 | fn func(int) string 1494 | exp map[string][]int 1495 | }{ 1496 | { 1497 | name: "empty", 1498 | in: nil, 1499 | fn: func(v int) string { 1500 | return strconv.Itoa(v % 2) 1501 | }, 1502 | exp: map[string][]int{}, 1503 | }, 1504 | { 1505 | name: "group_odd_even", 1506 | in: []int{1, 2, 3, 4}, 1507 | fn: func(v int) string { 1508 | return strconv.Itoa(v % 2) 1509 | }, 1510 | exp: map[string][]int{ 1511 | "0": {2, 4}, 1512 | "1": {1, 3}, 1513 | }, 1514 | }, 1515 | { 1516 | name: "group_nothing", 1517 | in: []int{1, 2, 3, 4}, 1518 | fn: func(v int) string { 1519 | return strconv.Itoa(v) 1520 | }, 1521 | exp: map[string][]int{ 1522 | "1": {1}, 1523 | "2": {2}, 1524 | "3": {3}, 1525 | "4": {4}, 1526 | }, 1527 | }, 1528 | } 1529 | 1530 | for _, row := range table { 1531 | row := row 1532 | t.Run(row.name, func(t *testing.T) { 1533 | t.Parallel() 1534 | 1535 | res := just.SliceGroupBy(row.in, row.fn) 1536 | assert.Equal(t, row.exp, res) 1537 | }) 1538 | } 1539 | } 1540 | 1541 | func TestSlice2Chan(t *testing.T) { 1542 | t.Run("do_not_run_goroutine_on_capacity_is_equal_to_input_len", func(t *testing.T) { 1543 | in := []int{1, 2, 3} 1544 | capacity := len(in) 1545 | ch := just.Slice2Chan(in, capacity) 1546 | require.Equal(t, len(in), len(ch)) 1547 | 1548 | res := just.ChanReadN(ch, len(in)) 1549 | require.Equal(t, in, res) 1550 | }) 1551 | 1552 | t.Run("capacity_is_lt_input_len", func(t *testing.T) { 1553 | in := []int{10, 20, 30} 1554 | capacity := 1 1555 | ch := just.Slice2Chan(in, capacity) 1556 | time.Sleep(100 * time.Microsecond) 1557 | 1558 | // NOTE(zhuravlev): floating tests 1559 | require.Equal(t, capacity, len(ch)) 1560 | 1561 | res := just.ChanReadN(ch, capacity) 1562 | require.Equal(t, []int{10}, res) 1563 | }) 1564 | 1565 | t.Run("capacity_is_gt_input_len", func(t *testing.T) { 1566 | in := []int{10, 20, 30} 1567 | capacity := 100 1568 | ch := just.Slice2Chan(in, capacity) 1569 | time.Sleep(100 * time.Microsecond) 1570 | 1571 | // NOTE(zhuravlev): floating tests 1572 | require.Equal(t, len(in), len(ch)) 1573 | require.Equal(t, capacity, cap(ch)) 1574 | 1575 | res := just.ChanReadN(ch, len(in)) 1576 | require.Equal(t, in, res) 1577 | }) 1578 | } 1579 | 1580 | func TestSlice2ChanFill(t *testing.T) { 1581 | in := []int{1, 2, 3} 1582 | ch := just.Slice2ChanFill(in) 1583 | require.Equal(t, len(in), len(ch)) 1584 | 1585 | res := just.ChanReadN(ch, len(in)) 1586 | require.Equal(t, in, res) 1587 | } 1588 | 1589 | func TestSliceFromElem(t *testing.T) { 1590 | elem := 10 1591 | res := just.SliceFromElem(elem) 1592 | 1593 | require.Equal(t, []int{elem}, res) 1594 | } 1595 | 1596 | func TestSliceGetFirstN(t *testing.T) { 1597 | t.Parallel() 1598 | 1599 | table := []struct { 1600 | in []int 1601 | maxElems int 1602 | exp []int 1603 | }{ 1604 | { 1605 | in: nil, 1606 | maxElems: 0, 1607 | exp: nil, 1608 | }, 1609 | { 1610 | in: []int{1}, 1611 | maxElems: 0, 1612 | exp: []int{}, 1613 | }, 1614 | { 1615 | in: []int{1}, 1616 | maxElems: 1, 1617 | exp: []int{1}, 1618 | }, 1619 | { 1620 | in: []int{1}, 1621 | maxElems: 10, 1622 | exp: []int{1}, 1623 | }, 1624 | { 1625 | in: []int{1, 2, 3}, 1626 | maxElems: 10, 1627 | exp: []int{1, 2, 3}, 1628 | }, 1629 | } 1630 | 1631 | for _, row := range table { 1632 | t.Run("", func(t *testing.T) { 1633 | res := just.SliceGetFirstN(row.in, row.maxElems) 1634 | assert.Equal(t, row.exp, res) 1635 | }) 1636 | } 1637 | } 1638 | 1639 | func TestSliceCopy(t *testing.T) { 1640 | table := []struct { 1641 | in []int 1642 | exp []int 1643 | }{ 1644 | { 1645 | in: nil, 1646 | exp: []int{}, 1647 | }, 1648 | { 1649 | in: []int{}, 1650 | exp: []int{}, 1651 | }, 1652 | { 1653 | in: []int{1}, 1654 | exp: []int{1}, 1655 | }, 1656 | { 1657 | in: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 1658 | exp: []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, 1659 | }, 1660 | } 1661 | 1662 | for _, row := range table { 1663 | t.Run("", func(t *testing.T) { 1664 | res := just.SliceCopy(row.in) 1665 | 1666 | assert.Equal(t, row.exp, res) 1667 | }) 1668 | } 1669 | } 1670 | 1671 | func TestSliceReplaceFirst(t *testing.T) { 1672 | find41 := func(_, v int) bool { return v == 41 } 1673 | newElem := 42 1674 | 1675 | table := []struct { 1676 | in []int 1677 | findFn func(int, int) bool 1678 | exp []int 1679 | }{ 1680 | { 1681 | in: nil, 1682 | findFn: find41, 1683 | exp: nil, 1684 | }, 1685 | { 1686 | in: []int{}, 1687 | findFn: find41, 1688 | exp: []int{}, 1689 | }, 1690 | { 1691 | in: []int{1, 2, 3}, 1692 | findFn: find41, 1693 | exp: []int{1, 2, 3}, 1694 | }, 1695 | { 1696 | in: []int{0, 41, 2, 3}, 1697 | findFn: find41, 1698 | exp: []int{0, 42, 2, 3}, 1699 | }, 1700 | { 1701 | in: []int{0, 41, 41, 41}, 1702 | findFn: find41, 1703 | exp: []int{0, 42, 41, 41}, 1704 | }, 1705 | { 1706 | in: []int{1, 1, 1, 41}, 1707 | findFn: find41, 1708 | exp: []int{1, 1, 1, 42}, 1709 | }, 1710 | } 1711 | 1712 | for _, row := range table { 1713 | t.Run("", func(t *testing.T) { 1714 | just.SliceReplaceFirst(row.in, row.findFn, newElem) 1715 | assert.Equal(t, row.exp, row.in) 1716 | }) 1717 | } 1718 | } 1719 | 1720 | func TestSliceReplaceFirstOrAdd(t *testing.T) { 1721 | find41 := func(_, v int) bool { return v == 41 } 1722 | newElem := 42 1723 | 1724 | table := []struct { 1725 | in []int 1726 | findFn func(int, int) bool 1727 | exp []int 1728 | }{ 1729 | { 1730 | in: nil, 1731 | findFn: find41, 1732 | exp: []int{newElem}, 1733 | }, 1734 | { 1735 | in: []int{}, 1736 | findFn: find41, 1737 | exp: []int{newElem}, 1738 | }, 1739 | { 1740 | in: []int{1, 2, 3}, 1741 | findFn: find41, 1742 | exp: []int{1, 2, 3, newElem}, 1743 | }, 1744 | { 1745 | in: []int{0, 41, 2, 3}, 1746 | findFn: find41, 1747 | exp: []int{0, 42, 2, 3}, 1748 | }, 1749 | { 1750 | in: []int{0, 41, 41, 41}, 1751 | findFn: find41, 1752 | exp: []int{0, 42, 41, 41}, 1753 | }, 1754 | { 1755 | in: []int{1, 1, 1, 41}, 1756 | findFn: find41, 1757 | exp: []int{1, 1, 1, 42}, 1758 | }, 1759 | } 1760 | 1761 | for _, row := range table { 1762 | t.Run("", func(t *testing.T) { 1763 | res := just.SliceReplaceFirstOrAdd(row.in, row.findFn, newElem) 1764 | assert.Equal(t, row.exp, res) 1765 | }) 1766 | } 1767 | } 1768 | 1769 | func TestSliceLastDefault(t *testing.T) { 1770 | table := []struct { 1771 | in []int 1772 | defaultVal int 1773 | exp int 1774 | }{ 1775 | { 1776 | in: nil, 1777 | defaultVal: -1, 1778 | exp: -1, 1779 | }, 1780 | { 1781 | in: []int{}, 1782 | defaultVal: -1, 1783 | exp: -1, 1784 | }, 1785 | { 1786 | in: []int{15}, 1787 | defaultVal: -1, 1788 | exp: 15, 1789 | }, 1790 | { 1791 | in: []int{15, 16}, 1792 | defaultVal: -1, 1793 | exp: 16, 1794 | }, 1795 | } 1796 | 1797 | for _, row := range table { 1798 | t.Run("", func(t *testing.T) { 1799 | res := just.SliceLastDefault(row.in, row.defaultVal) 1800 | assert.Equal(t, row.exp, res) 1801 | }) 1802 | } 1803 | } 1804 | 1805 | func TestSliceShuffleCopy(t *testing.T) { 1806 | input := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 1807 | 1808 | var res []int 1809 | for range just.SliceRange(0, 5, 1) { 1810 | t.Run("", func(t *testing.T) { 1811 | res2 := just.SliceShuffleCopy(input) 1812 | assert.NotEqual(t, input, res2) 1813 | assert.NotEqual(t, res2, res) 1814 | 1815 | res = res2 1816 | }) 1817 | } 1818 | } 1819 | 1820 | func TestSliceLastN(t *testing.T) { 1821 | table := []struct { 1822 | in []int 1823 | n int 1824 | exp []int 1825 | }{ 1826 | { 1827 | in: nil, 1828 | n: 10, 1829 | exp: []int{}, 1830 | }, 1831 | { 1832 | in: nil, 1833 | n: 0, 1834 | exp: []int{}, 1835 | }, 1836 | { 1837 | in: []int{10}, 1838 | n: 0, 1839 | exp: []int{}, 1840 | }, 1841 | { 1842 | in: []int{10}, 1843 | n: 1, 1844 | exp: []int{10}, 1845 | }, 1846 | { 1847 | in: []int{10}, 1848 | n: 6, 1849 | exp: []int{10}, 1850 | }, 1851 | { 1852 | in: []int{10, 20, 30, 40, 50}, 1853 | n: 2, 1854 | exp: []int{40, 50}, 1855 | }, 1856 | } 1857 | 1858 | for _, row := range table { 1859 | t.Run("", func(t *testing.T) { 1860 | res := just.SliceLastN(row.in, row.n) 1861 | require.Equal(t, row.exp, res) 1862 | }) 1863 | } 1864 | } 1865 | 1866 | func TestSliceAny(t *testing.T) { 1867 | t.Parallel() 1868 | 1869 | t.Run("empty_slice_returns_false", func(t *testing.T) { 1870 | result := just.SliceAny([]int{}, func(int) bool { return true }) 1871 | assert.False(t, result) 1872 | }) 1873 | 1874 | t.Run("returns_true_when_at_least_one_element_matches", func(t *testing.T) { 1875 | result := just.SliceAny([]int{1, 2, 3}, func(int) bool { return true }) 1876 | assert.True(t, result) 1877 | }) 1878 | 1879 | t.Run("returns_false_when_no_elements_match", func(t *testing.T) { 1880 | result := just.SliceAny([]int{1, 2, 3}, func(int) bool { return false }) 1881 | assert.False(t, result) 1882 | }) 1883 | 1884 | t.Run("works_with_strings", func(t *testing.T) { 1885 | result := just.SliceAny([]string{"apple", "banana", "cherry"}, func(s string) bool { 1886 | return len(s) > 6 1887 | }) 1888 | assert.False(t, result) 1889 | 1890 | result = just.SliceAny([]string{"apple", "banana", "cherry"}, func(s string) bool { 1891 | return s == "banana" 1892 | }) 1893 | assert.True(t, result) 1894 | }) 1895 | } 1896 | 1897 | func TestSliceFilter(t *testing.T) { 1898 | t.Parallel() 1899 | 1900 | t.Run("empty_slice", func(t *testing.T) { 1901 | result := just.SliceFilter([]int{}, func(n int) bool { return n > 0 }) 1902 | assert.Equal(t, []int{}, result) 1903 | }) 1904 | 1905 | t.Run("filters_positive_numbers", func(t *testing.T) { 1906 | result := just.SliceFilter([]int{-2, -1, 0, 1, 2, 3}, func(n int) bool { return n > 0 }) 1907 | assert.Equal(t, []int{1, 2, 3}, result) 1908 | }) 1909 | 1910 | t.Run("empty_slice_on_no_elements_match", func(t *testing.T) { 1911 | result := just.SliceFilter([]int{1, 2, 3}, func(int) bool { return false }) 1912 | assert.Equal(t, []int{}, result) 1913 | }) 1914 | 1915 | t.Run("all_elements_match", func(t *testing.T) { 1916 | result := just.SliceFilter([]int{1, 2, 3}, func(int) bool { return true }) 1917 | assert.Equal(t, []int{1, 2, 3}, result) 1918 | }) 1919 | 1920 | t.Run("duplicate_elements_match", func(t *testing.T) { 1921 | result := just.SliceFilter([]int{1, 1, 2, 2, 3, 3}, func(int) bool { return true }) 1922 | assert.Equal(t, []int{1, 1, 2, 2, 3, 3}, result) 1923 | }) 1924 | } 1925 | 1926 | func TestSliceFindFirst(t *testing.T) { 1927 | t.Parallel() 1928 | 1929 | t.Run("empty_slice", func(t *testing.T) { 1930 | result := just.SliceFindFirst([]int{}, func(i int, n int) bool { return n > 0 }) 1931 | assert.Equal(t, -1, result.Idx) 1932 | }) 1933 | 1934 | t.Run("finds_first_matching_element", func(t *testing.T) { 1935 | result := just.SliceFindFirst([]int{-2, -1, 0, 1, 2, 3}, func(i int, n int) bool { return n > 0 }) 1936 | assert.Equal(t, 3, result.Idx) 1937 | assert.Equal(t, 1, result.Val) 1938 | }) 1939 | 1940 | t.Run("no_matching_element", func(t *testing.T) { 1941 | result := just.SliceFindFirst([]int{-2, -1, 0}, func(i int, n int) bool { return n > 0 }) 1942 | assert.Equal(t, -1, result.Idx) 1943 | }) 1944 | 1945 | t.Run("uses_index_in_predicate", func(t *testing.T) { 1946 | result := just.SliceFindFirst([]int{10, 20, 30, 40}, func(i int, n int) bool { return i >= 2 }) 1947 | assert.Equal(t, 2, result.Idx) 1948 | assert.Equal(t, 30, result.Val) 1949 | }) 1950 | } 1951 | 1952 | func TestSliceFindLast(t *testing.T) { 1953 | t.Parallel() 1954 | 1955 | t.Run("empty_slice", func(t *testing.T) { 1956 | result := just.SliceFindLast([]int{}, func(i int, n int) bool { return n > 0 }) 1957 | assert.Equal(t, -1, result.Idx) 1958 | }) 1959 | 1960 | t.Run("finds_last_matching_element", func(t *testing.T) { 1961 | result := just.SliceFindLast([]int{1, 2, 3, -1, -2}, func(i int, n int) bool { return n > 0 }) 1962 | assert.Equal(t, 2, result.Idx) 1963 | assert.Equal(t, 3, result.Val) 1964 | }) 1965 | 1966 | t.Run("no_matching_element", func(t *testing.T) { 1967 | result := just.SliceFindLast([]int{-2, -1, 0}, func(i int, n int) bool { return n > 0 }) 1968 | assert.Equal(t, -1, result.Idx) 1969 | }) 1970 | 1971 | t.Run("uses_index_in_predicate", func(t *testing.T) { 1972 | result := just.SliceFindLast([]int{10, 20, 30, 40}, func(i int, n int) bool { return i <= 2 }) 1973 | assert.Equal(t, 2, result.Idx) 1974 | assert.Equal(t, 30, result.Val) 1975 | }) 1976 | } 1977 | 1978 | func TestSliceWithoutElem(t *testing.T) { 1979 | t.Parallel() 1980 | 1981 | t.Run("empty_slice", func(t *testing.T) { 1982 | result := just.SliceWithoutElem([]int{}, 5) 1983 | assert.Equal(t, []int{}, result) 1984 | }) 1985 | 1986 | t.Run("removes_single_occurrence", func(t *testing.T) { 1987 | result := just.SliceWithoutElem([]int{1, 2, 3, 4, 5}, 3) 1988 | assert.Equal(t, []int{1, 2, 4, 5}, result) 1989 | }) 1990 | 1991 | t.Run("removes_multiple_occurrences", func(t *testing.T) { 1992 | result := just.SliceWithoutElem([]int{1, 2, 3, 2, 4, 2, 5}, 2) 1993 | assert.Equal(t, []int{1, 3, 4, 5}, result) 1994 | }) 1995 | 1996 | t.Run("element_not_in_slice", func(t *testing.T) { 1997 | result := just.SliceWithoutElem([]int{1, 2, 3, 4, 5}, 6) 1998 | assert.Equal(t, []int{1, 2, 3, 4, 5}, result) 1999 | }) 2000 | 2001 | t.Run("removes_all_elements", func(t *testing.T) { 2002 | result := just.SliceWithoutElem([]int{2, 2, 2, 2}, 2) 2003 | assert.Equal(t, []int{}, result) 2004 | }) 2005 | } 2006 | 2007 | func TestSliceShuffle(t *testing.T) { 2008 | t.Parallel() 2009 | 2010 | t.Run("empty_slice", func(t *testing.T) { 2011 | slice := []int{} 2012 | just.SliceShuffle(slice) 2013 | assert.Equal(t, []int{}, slice) 2014 | }) 2015 | 2016 | t.Run("empty_slice_returns_orig_slice", func(t *testing.T) { 2017 | var slice []int 2018 | just.SliceShuffle(slice) 2019 | assert.Nil(t, slice) 2020 | }) 2021 | 2022 | t.Run("single_element", func(t *testing.T) { 2023 | slice := []int{42} 2024 | just.SliceShuffle(slice) 2025 | assert.Equal(t, []int{42}, slice) 2026 | }) 2027 | 2028 | t.Run("shuffles_in_place", func(t *testing.T) { 2029 | original := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 2030 | slice := make([]int, len(original)) 2031 | copy(slice, original) 2032 | 2033 | just.SliceShuffle(slice) 2034 | 2035 | // Should contain same elements 2036 | assert.ElementsMatch(t, original, slice) 2037 | 2038 | // Should modify the original slice (in-place) 2039 | // Note: there's a very small chance they could be in the same order 2040 | // but for 10 elements this is highly unlikely 2041 | 2042 | // Run multiple times to reduce chance of false positive 2043 | differentOrder := false 2044 | for i := 0; i < 5; i++ { 2045 | testSlice := make([]int, len(original)) 2046 | copy(testSlice, original) 2047 | just.SliceShuffle(testSlice) 2048 | if !assert.ObjectsAreEqual(original, testSlice) { 2049 | differentOrder = true 2050 | break 2051 | } 2052 | } 2053 | assert.True(t, differentOrder, "shuffle should change the order (ran 5 times)") 2054 | }) 2055 | } 2056 | 2057 | func TestSlice2Map(t *testing.T) { 2058 | t.Parallel() 2059 | 2060 | t.Run("empty_slice", func(t *testing.T) { 2061 | result := just.Slice2Map([]int{}) 2062 | assert.Equal(t, map[int]struct{}{}, result) 2063 | }) 2064 | 2065 | t.Run("unique_elements", func(t *testing.T) { 2066 | result := just.Slice2Map([]int{1, 2, 3}) 2067 | expected := map[int]struct{}{ 2068 | 1: {}, 2069 | 2: {}, 2070 | 3: {}, 2071 | } 2072 | assert.Equal(t, expected, result) 2073 | }) 2074 | 2075 | t.Run("duplicate_elements", func(t *testing.T) { 2076 | result := just.Slice2Map([]int{1, 2, 2, 3, 3, 3}) 2077 | expected := map[int]struct{}{ 2078 | 1: {}, 2079 | 2: {}, 2080 | 3: {}, 2081 | } 2082 | assert.Equal(t, expected, result) 2083 | }) 2084 | 2085 | t.Run("string_slice", func(t *testing.T) { 2086 | result := just.Slice2Map([]string{"apple", "banana", "apple", "cherry"}) 2087 | expected := map[string]struct{}{ 2088 | "apple": {}, 2089 | "banana": {}, 2090 | "cherry": {}, 2091 | } 2092 | assert.Equal(t, expected, result) 2093 | }) 2094 | } 2095 | 2096 | func TestSlice2Iter(t *testing.T) { 2097 | t.Parallel() 2098 | 2099 | t.Run("empty slice", func(t *testing.T) { 2100 | slice := []int{} 2101 | iterator := just.Slice2Iter(slice) 2102 | 2103 | count := 0 2104 | iterator(func(idx int, val int) bool { 2105 | count++ 2106 | return true 2107 | }) 2108 | assert.Equal(t, 0, count) 2109 | }) 2110 | 2111 | t.Run("iterates all elements", func(t *testing.T) { 2112 | slice := []int{10, 20, 30} 2113 | iterator := just.Slice2Iter(slice) 2114 | 2115 | var indices []int 2116 | var values []int 2117 | iterator(func(idx int, val int) bool { 2118 | indices = append(indices, idx) 2119 | values = append(values, val) 2120 | return true 2121 | }) 2122 | 2123 | assert.Equal(t, []int{0, 1, 2}, indices) 2124 | assert.Equal(t, []int{10, 20, 30}, values) 2125 | }) 2126 | 2127 | t.Run("early termination", func(t *testing.T) { 2128 | slice := []int{1, 2, 3, 4, 5} 2129 | iterator := just.Slice2Iter(slice) 2130 | 2131 | var values []int 2132 | iterator(func(idx int, val int) bool { 2133 | values = append(values, val) 2134 | return val < 3 // stop when we reach 3 2135 | }) 2136 | 2137 | assert.Equal(t, []int{1, 2, 3}, values) 2138 | }) 2139 | } 2140 | --------------------------------------------------------------------------------