1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-17 11:12:28 +08:00

Compare commits

...

36 Commits

Author SHA1 Message Date
dudaodong
4a539a23c8 doc: add doc for Xnor and Nand in function package 2024-02-26 10:10:37 +08:00
dudaodong
0b1dab0399 Merge branch 'main' into v2 2024-02-26 10:02:00 +08:00
donutloop
805e2543d0 Add functional predicate NAND (#182)
Add new function, NAND, designed to create a composed predicate representing the logical NAND operation
applied to a list of predicates. The NAND operation is a logical operation
that returns true only if all perdicate result in false otherwise false
2024-02-26 09:58:19 +08:00
dudaodong
a3d518da76 remove test_src directory 2024-02-25 20:29:49 +08:00
dudaodong
e3e2d8394c Merge branch 'v2' of github.com:duke-git/lancet into v2 2024-02-25 20:25:44 +08:00
yunxuan
0eeaa06055 feat(fileutil): add CopyDir func (#180)
* add fileutil.CopyDir

* remove debug code
2024-02-25 20:25:00 +08:00
donutloop
a43bc554ee Add functional predicate XNOR (#181)
Add new function, Xnor, designed to create a composed predicate representing
the logical Exclusive NOR (XNOR) operation applied to a list of predicates.
The XNOR operation is a logical operation that returns true only
if all operands have the same boolean value
2024-02-25 20:24:47 +08:00
dudaodong
aebab7c944 refactor: break change, rename constructor of set (NewSet->New, NewSetFromSlice->FromSlice) 2024-02-25 09:32:32 +08:00
dudaodong
665bad4ca3 doc: update doc for IndexOfFunc and LastIndexOfFunc 2024-02-25 09:25:07 +08:00
donutloop
e4901e99e9 Fix optional doc links (#179) 2024-02-24 18:12:44 +08:00
donutloop
4277e8eca5 CopyOnWriteList add IndexOfFunc and LastIndexOfFunc (#178)
Allow users to apply functional predicates alongside 'index of' and 'last index of' search methods in this specialized list variant
2024-02-24 17:53:58 +08:00
donutloop
fdc93c8cc7 Change naming (#177)
Utilize terminology from the Go SDK rather than introducing novel terms to describe concepts.
2024-02-24 17:25:31 +08:00
donutloop
860a499f98 Add custom backoff setter (#176)
Users should have the capability to customize the backoff pattern and accordingly adjust it within the retry mechanism.
2024-02-23 10:04:38 +08:00
dudaodong
2e1c2276a5 test: add test coverageg 2024-02-22 14:39:53 +08:00
donutloop
d367397dab Add exponential With jitter backoff (#174)
* Add exponential With jitter backoff

Adds exponential + jitter retry policy. To enable drastic slow down of sending out requests to any external system.

Jitter in computational contexts refers to the addition of a small random variation to a value
to break the symmetric patterns

* Retry with exp: Allow shift for all multiple of 2
2024-02-22 10:39:45 +08:00
dudaodong
66fd8cf651 fix: fix go vet issue 2024-02-21 11:14:19 +08:00
dudaodong
a6be1828b9 doc: add doc and example for predicate logic of function package 2024-02-21 11:13:47 +08:00
dudaodong
8f5d297572 . 2024-02-21 11:11:52 +08:00
dudaodong
a1a4fdc598 doc: update doc for optional 2024-02-21 10:38:26 +08:00
dudaodong
1610076d22 Merge branch 'main' into v2 2024-02-21 10:21:42 +08:00
donutloop
cacbf97223 Add Retry backoff policy (#173)
The aim of this policy is to enable the configuration of various types of backoff mathematical curves. Should this modification be deemed suitable,
I will proceed to implement an exponential curve backoff policy and set of custom backoff policy
Warning: It's major break
2024-02-21 10:20:24 +08:00
donutloop
cd156dba5f Add functional nor predicate (#172) 2024-02-21 10:05:54 +08:00
dudaodong
3a71a8697d fix: fix issue #169 2024-02-20 17:29:32 +08:00
dudaodong
c88fd3db86 fix: fix go vet issue, method Unwrap() []error 2024-02-20 11:41:58 +08:00
dudaodong
27d19d1717 fix: rename Seek to SeekOffset fix go vet check issue 2024-02-20 11:39:41 +08:00
dudaodong
da24bae6b4 doc: add doc for Optional type 2024-02-20 11:22:39 +08:00
donutloop
3cd9d6b68c Add functional predicate (#171)
Enable the execution of assertion functions in a functional manner.
2024-02-20 09:55:39 +08:00
dudaodong
874d09f331 refactor: make stream struct exported 2024-02-19 15:55:08 +08:00
dudaodong
fdf251ac98 add govet check to github action file 2024-02-19 15:50:19 +08:00
dudaodong
7ec2533b7a feat: add MapToStruct 2024-02-19 13:50:06 +08:00
dudaodong
9fd0603f4a fix: fix copylocks warning in Optional struct methods 2024-02-19 10:22:28 +08:00
dudaodong
9f7b416a8d Merge branch 'main' into v2 2024-02-19 10:00:21 +08:00
donutloop
bf4b2b5fd6 Add optional (#170)
* Add optional

Wrapper container with easy to understand helper methods

* Add test and rewrite test

* Add panic test

* Add TestOrElseGetHappyPath
2024-02-19 09:59:42 +08:00
dudaodong
22af59565e fix: fix issue #168 2024-02-06 16:59:01 +08:00
dudaodong
f9e047f190 feat: add SubInBetween 2024-02-06 16:47:30 +08:00
dudaodong
fa298b740d add playground demo 2024-02-01 10:41:09 +08:00
44 changed files with 3116 additions and 219 deletions

View File

@@ -3,11 +3,9 @@ on:
push:
branches:
- main
# - v2
pull_request:
branches:
- main
# - v2
jobs:
build:
runs-on: ubuntu-latest
@@ -17,8 +15,10 @@ jobs:
fetch-depth: 2
- uses: actions/setup-go@v2
with:
go-version: "1.18"
go-version: "1.20"
- name: Run coverage
run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic
- name: Run govet
run: go vet -v ./...
- name: Upload coverage to Codecov
run: bash <(curl -s https://codecov.io/bash)

View File

@@ -608,6 +608,7 @@ import set "github.com/duke-git/lancet/v2/datastructure/set"
import tree "github.com/duke-git/lancet/v2/datastructure/tree"
import heap "github.com/duke-git/lancet/v2/datastructure/heap"
import hashmap "github.com/duke-git/lancet/v2/datastructure/hashmap"
import optional "github.com/duke-git/lancet/v2/datastructure/optional"
```
#### Structure list:
@@ -630,6 +631,9 @@ import hashmap "github.com/duke-git/lancet/v2/datastructure/hashmap"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datastructure/heap.md)]
- **<big>Hashmap</big>** : hash map structure.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datastructure/hashmap.md)]
- **<big>Optional</big>** : Optional container.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/datastructure/optional.md)]
<h3 id="fileutil"> 9. Fileutil package implements some basic functions for file operations. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -708,7 +712,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteCsvFile)]
- **<big>WriteMapsToCsv</big>** : write slice of map to csv file.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteMapsToCsv)]
[[play](https://go.dev/play/p/dAXm58Q5U1o)]
[[play](https://go.dev/play/p/umAIomZFV1c)]
- **<big>WriteBytesToFile</big>** : write bytes to target file.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/fileutil.md#WriteBytesToFile)]
[[play](https://go.dev/play/p/s7QlDxMj3P8)]
@@ -1182,8 +1186,10 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/v2U2deugKuV)]
- **<big>DeleteAt</big>** : delete the element of slice at index.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DeleteAt)]
[[play](https://go.dev/play/p/800B1dPBYyd)]
- **<big>DeleteRange</big>** : delete the element of slice from start index to end indexexclude).
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#DeleteRange)]
[[play](https://go.dev/play/p/945HwiNrnle)]
- **<big>Drop</big>** : drop n elements from the start of a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Drop)]
[[play](https://go.dev/play/p/jnPO2yQsT8H)]

View File

@@ -709,7 +709,7 @@ import "github.com/duke-git/lancet/v2/fileutil"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteCsvFile)]
- **<big>WriteMapsToCsv</big>** : 将map切片写入csv文件中。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteMapsToCsv)]
[[play](https://go.dev/play/p/dAXm58Q5U1o)]
[[play](https://go.dev/play/p/umAIomZFV1c)]
- **<big>WriteBytesToFile</big>** : 将 bytes 写入文件。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/fileutil.md#WriteBytesToFile)]
[[play](https://go.dev/play/p/s7QlDxMj3P8)]
@@ -1181,8 +1181,10 @@ import "github.com/duke-git/lancet/v2/slice"
[[play](https://go.dev/play/p/v2U2deugKuV)]
- **<big>DeleteAt</big>** : 删除切片中指定索引到的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DeleteAt)]
[[play](https://go.dev/play/p/800B1dPBYyd)]
- **<big>DeleteRange</big>** : 删除切片中指定开始索引到结束索引的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#DeleteRange)]
[[play](https://go.dev/play/p/945HwiNrnle)]
- **<big>Drop</big>** : 从切片头部删除 n 个元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Drop)]
[[play](https://go.dev/play/p/jnPO2yQsT8H)]

View File

@@ -1,6 +1,7 @@
package compare
import (
"encoding/json"
"testing"
"time"
@@ -11,6 +12,7 @@ func TestEqual(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestEqual")
// basic type
assert.Equal(true, Equal(1, 1))
assert.Equal(true, Equal(int64(1), int64(1)))
assert.Equal(true, Equal("a", "a"))
@@ -25,6 +27,7 @@ func TestEqual(t *testing.T) {
assert.Equal(false, Equal([]int{1, 2}, []int{1, 2, 3}))
assert.Equal(false, Equal(map[int]string{1: "a", 2: "b"}, map[int]string{1: "a"}))
// time
time1 := time.Now()
time2 := time1.Add(time.Second)
time3 := time1.Add(time.Second)
@@ -32,6 +35,7 @@ func TestEqual(t *testing.T) {
assert.Equal(false, Equal(time1, time2))
assert.Equal(true, Equal(time2, time3))
// struct
st1 := struct {
A string
B string
@@ -58,6 +62,19 @@ func TestEqual(t *testing.T) {
assert.Equal(true, Equal(st1, st2))
assert.Equal(false, Equal(st1, st3))
//byte slice
bs1 := []byte("hello")
bs2 := []byte("hello")
assert.Equal(true, Equal(bs1, bs2))
// json.Number
var jsonNumber1, jsonNumber2 json.Number
json.Unmarshal([]byte(`123`), &jsonNumber1)
json.Unmarshal([]byte(`123`), &jsonNumber2)
assert.Equal(true, Equal(jsonNumber1, jsonNumber2))
}
func TestEqualValue(t *testing.T) {
@@ -69,6 +86,13 @@ func TestEqualValue(t *testing.T) {
assert.Equal(true, EqualValue(1, "1"))
assert.Equal(false, EqualValue(1, "2"))
// json.Number
var jsonNumber1, jsonNumber2 json.Number
json.Unmarshal([]byte(`123`), &jsonNumber1)
json.Unmarshal([]byte(`123`), &jsonNumber2)
assert.Equal(true, EqualValue(jsonNumber1, 123))
}
func TestLessThan(t *testing.T) {
@@ -85,6 +109,17 @@ func TestLessThan(t *testing.T) {
assert.Equal(false, LessThan(1, 1))
assert.Equal(false, LessThan(1, int64(1)))
bs1 := []byte("hello1")
bs2 := []byte("hello2")
assert.Equal(true, LessThan(bs1, bs2))
// json.Number
var jsonNumber1, jsonNumber2 json.Number
json.Unmarshal([]byte(`123`), &jsonNumber1)
json.Unmarshal([]byte(`124`), &jsonNumber2)
assert.Equal(true, LessThan(jsonNumber1, jsonNumber2))
}
func TestGreaterThan(t *testing.T) {
@@ -102,6 +137,17 @@ func TestGreaterThan(t *testing.T) {
assert.Equal(false, GreaterThan(1, 2))
assert.Equal(false, GreaterThan(int64(2), 1))
assert.Equal(false, GreaterThan("b", "c"))
bs1 := []byte("hello1")
bs2 := []byte("hello2")
assert.Equal(true, GreaterThan(bs2, bs1))
// json.Number
var jsonNumber1, jsonNumber2 json.Number
json.Unmarshal([]byte(`123`), &jsonNumber1)
json.Unmarshal([]byte(`124`), &jsonNumber2)
assert.Equal(true, GreaterThan(jsonNumber2, jsonNumber1))
}
func TestLessOrEqual(t *testing.T) {
@@ -119,6 +165,17 @@ func TestLessOrEqual(t *testing.T) {
assert.Equal(false, LessOrEqual(2, 1))
assert.Equal(false, LessOrEqual(1, int64(2)))
bs1 := []byte("hello1")
bs2 := []byte("hello2")
assert.Equal(true, LessOrEqual(bs1, bs2))
// json.Number
var jsonNumber1, jsonNumber2 json.Number
json.Unmarshal([]byte(`123`), &jsonNumber1)
json.Unmarshal([]byte(`124`), &jsonNumber2)
assert.Equal(true, LessOrEqual(jsonNumber1, jsonNumber2))
}
func TestGreaterOrEqual(t *testing.T) {
@@ -137,6 +194,17 @@ func TestGreaterOrEqual(t *testing.T) {
assert.Equal(false, GreaterOrEqual(1, 2))
assert.Equal(false, GreaterOrEqual(int64(2), 1))
assert.Equal(false, GreaterOrEqual("b", "c"))
bs1 := []byte("hello1")
bs2 := []byte("hello2")
assert.Equal(true, GreaterOrEqual(bs2, bs1))
// json.Number
var jsonNumber1, jsonNumber2 json.Number
json.Unmarshal([]byte(`123`), &jsonNumber1)
json.Unmarshal([]byte(`124`), &jsonNumber2)
assert.Equal(true, GreaterOrEqual(jsonNumber2, jsonNumber1))
}
func TestInDelta(t *testing.T) {

View File

@@ -90,6 +90,35 @@ func lastIndexOf[T any](o T, e []T, start int, end int) int {
return -1
}
// LastIndexOfFunc returns the index of the last occurrence of the value in this list satisfying the
// functional predicate f(T) bool
// if not found return -1.
func (l *CopyOnWriteList[T]) LastIndexOfFunc(f func(T) bool) int {
index := -1
data := l.getList()
for i := len(data) - 1; i >= 0; i-- {
if f(data[i]) {
index = i
break
}
}
return index
}
// IndexOfFunc returns the first index satisfying the functional predicate f(v) bool
// if not found return -1.
func (l *CopyOnWriteList[T]) IndexOfFunc(f func(T) bool) int {
index := -1
data := l.getList()
for i, v := range data {
if f(v) {
index = i
break
}
}
return index
}
// get returns the element at the specified position in this list.
func get[T any](o []T, index int) *T {
return &o[index]

View File

@@ -1,8 +1,9 @@
package datastructure
import (
"github.com/duke-git/lancet/v2/internal"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestCopyOnWriteList_ValueOf(t *testing.T) {
@@ -233,3 +234,35 @@ func TestCopyOnWriteList_SubList(t *testing.T) {
subList = list.SubList(11, 1)
assert.Equal([]int{}, subList)
}
func TestCopyOnWriteListIndexOfFunc(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestIndexOfFunc")
list := NewCopyOnWriteList([]int{1, 2, 3})
i := list.IndexOfFunc(func(a int) bool { return a == 1 })
assert.Equal(0, i)
i = list.IndexOfFunc(func(a int) bool { return a == 4 })
assert.Equal(-1, i)
}
func TestNewCopyOnWriteListLastIndexOfFunc(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestLastIndexOfFunc")
list := NewCopyOnWriteList([]int{1, 2, 3, 3, 3, 3, 4, 5, 6, 9})
i := list.LastIndexOfFunc(func(a int) bool { return a == 3 })
assert.Equal(5, i)
i = list.LastIndexOfFunc(func(a int) bool { return a == 10 })
assert.Equal(-1, i)
i = list.LastIndexOfFunc(func(a int) bool { return a == 4 })
assert.Equal(6, i)
i = list.LastIndexOfFunc(func(a int) bool { return a == 1 })
assert.Equal(0, i)
}

View File

@@ -0,0 +1,108 @@
package optional
import (
"sync"
)
// Optional is a type that may or may not contain a non-nil value.
type Optional[T any] struct {
value *T
mu *sync.RWMutex
}
// Default returns an default Optional instance.
func Default[T any]() Optional[T] {
return Optional[T]{mu: &sync.RWMutex{}}
}
// Of returns an Optional with a non-nil value.
func Of[T any](value T) Optional[T] {
return Optional[T]{value: &value, mu: &sync.RWMutex{}}
}
// FromNillable returns an Optional for a given value, which may be nil.
func FromNillable[T any](value *T) Optional[T] {
if value == nil {
return Default[T]()
}
return Optional[T]{value: value, mu: &sync.RWMutex{}}
}
// IsNotNil checks if there is a value present.
func (o Optional[T]) IsNotNil() bool {
o.mu.RLock()
defer o.mu.RUnlock()
return o.value != nil
}
// IsNil checks if the Optional is nil.
func (o Optional[T]) IsNil() bool {
return !o.IsNotNil()
}
// IfNotNil performs the given action with the value if a value is not nil.
func (o Optional[T]) IfNotNil(action func(value T)) {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value != nil {
action(*o.value)
}
}
// IfNotNilOrElse performs the action with the value if present, otherwise performs the fallback action.
func (o Optional[T]) IfNotNilOrElse(action func(value T), fallbackAction func()) {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value != nil {
action(*o.value)
} else {
fallbackAction()
}
}
// Unwarp returns the value if not nil, otherwise panics.
func (o Optional[T]) Unwarp() T {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value == nil {
panic("Optional.Get: no value present")
}
return *o.value
}
// OrElse returns the value if is not nil, otherwise returns other.
func (o Optional[T]) OrElse(other T) T {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value != nil {
return *o.value
}
return other
}
// OrElseGet returns the value if is not nil, otherwise invokes action and returns the result.
func (o Optional[T]) OrElseGet(action func() T) T {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value != nil {
return *o.value
}
return action()
}
// OrElseTrigger returns the value if present, otherwise returns an error.
func (o Optional[T]) OrElseTrigger(errorHandler func() error) (T, error) {
o.mu.RLock()
defer o.mu.RUnlock()
if o.value == nil {
return *new(T), errorHandler()
}
return *o.value, nil
}

View File

@@ -0,0 +1,151 @@
package optional
import (
"errors"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestDefault(t *testing.T) {
assert := internal.NewAssert(t, "TestEmpty")
opt := Default[int]()
assert.ShouldBeTrue(opt.IsNil())
}
func TestOf(t *testing.T) {
assert := internal.NewAssert(t, "TestOf")
value := 42
opt := Of(value)
assert.ShouldBeTrue(opt.IsNotNil())
assert.Equal(opt.Unwarp(), value)
}
func TestFromNillable(t *testing.T) {
assert := internal.NewAssert(t, "TestOfNullable")
var value *int = nil
opt := FromNillable(value)
assert.ShouldBeFalse(opt.IsNotNil())
value = new(int)
*value = 42
opt = FromNillable(value)
assert.ShouldBeTrue(opt.IsNotNil())
}
func TestOrElse(t *testing.T) {
assert := internal.NewAssert(t, "TestOrElse")
optDefault := Default[int]()
defaultValue := 100
val := optDefault.OrElse(defaultValue)
assert.Equal(val, defaultValue)
optWithValue := Of(42)
val = optWithValue.OrElse(defaultValue)
assert.Equal(val, 42)
}
func TestOrElseGetHappyPath(t *testing.T) {
assert := internal.NewAssert(t, "TestOrElseGetHappyPath")
optWithValue := Of(42)
action := func() int { return 100 }
val := optWithValue.OrElseGet(action)
assert.Equal(val, 42)
}
func TestOrElseGet(t *testing.T) {
assert := internal.NewAssert(t, "TestOrElseGet")
optDefault := Default[int]()
action := func() int { return 100 }
val := optDefault.OrElseGet(action)
assert.Equal(val, action())
}
func TestOrElseTrigger(t *testing.T) {
assert := internal.NewAssert(t, "OrElseTrigger")
optDefault := Default[int]()
_, err := optDefault.OrElseTrigger(func() error { return errors.New("no value") })
assert.Equal(err.Error(), "no value")
optWithValue := Of(42)
val, err := optWithValue.OrElseTrigger(func() error { return errors.New("no value") })
assert.IsNil(err)
assert.Equal(val, 42)
}
func TestIfNotNil(t *testing.T) {
assert := internal.NewAssert(t, "IfNotNil")
called := false
action := func(value int) { called = true }
optDefault := Default[int]()
optDefault.IfNotNil(action)
assert.ShouldBeFalse(called)
called = false // Reset for next test
optWithValue := Of(42)
optWithValue.IfNotNil(action)
assert.ShouldBeTrue(called)
}
func TestIfNotNilOrElse(t *testing.T) {
assert := internal.NewAssert(t, "TestIfNotNilOrElse")
// Test when value is present
calledWithValue := false
valueAction := func(value int) { calledWithValue = true }
fallbackAction := func() { t.Errorf("Empty action should not be called when value is present") }
optWithValue := Of(42)
optWithValue.IfNotNilOrElse(valueAction, fallbackAction)
assert.ShouldBeTrue(calledWithValue)
// Test when value is not present
calledWithEmpty := false
valueAction = func(value int) { t.Errorf("Value action should not be called when value is not present") }
fallbackAction = func() { calledWithEmpty = true }
optDefault := Default[int]()
optDefault.IfNotNilOrElse(valueAction, fallbackAction)
assert.ShouldBeTrue(calledWithEmpty)
}
func TestGetWithPanicStandard(t *testing.T) {
assert := internal.NewAssert(t, "TestGetWithPanicStandard")
// Test when value is present
optWithValue := Of(42)
func() {
defer func() {
r := recover()
assert.IsNil(r)
}()
val := optWithValue.Unwarp()
if val != 42 {
t.Errorf("Expected Unwarp to return 42, got %v", val)
}
}()
// Test when value is not present
optDefault := Default[int]()
func() {
defer func() {
r := recover()
assert.IsNotNil(r)
}()
_ = optDefault.Unwarp()
}()
}

View File

@@ -7,15 +7,15 @@ package datastructure
// Set is a data container, like slice, but element of set is not duplicate.
type Set[T comparable] map[T]struct{}
// NewSet return a instance of set
func NewSet[T comparable](items ...T) Set[T] {
// New create a instance of set from given values.
func New[T comparable](items ...T) Set[T] {
set := make(Set[T])
set.Add(items...)
return set
}
// NewSetFromSlice create a set from slice
func NewSetFromSlice[T comparable](items []T) Set[T] {
// FromSlice create a set from given slice.
func FromSlice[T comparable](items []T) Set[T] {
set := make(Set[T])
for _, item := range items {
set.Add(item)
@@ -77,7 +77,7 @@ func (s Set[T]) ContainAll(other Set[T]) bool {
// Clone return a copy of set
func (s Set[T]) Clone() Set[T] {
set := NewSet[T]()
set := New[T]()
set.Add(s.Values()...)
return set
}
@@ -135,7 +135,7 @@ func (s Set[T]) Union(other Set[T]) Set[T] {
// Intersection creates a new set whose element both be contained in set s and other
func (s Set[T]) Intersection(other Set[T]) Set[T] {
set := NewSet[T]()
set := New[T]()
s.Iterate(func(value T) {
if other.Contain(value) {
set.Add(value)
@@ -147,7 +147,7 @@ func (s Set[T]) Intersection(other Set[T]) Set[T] {
// SymmetricDifference creates a new set whose element is in set1 or set2, but not in both sets
func (s Set[T]) SymmetricDifference(other Set[T]) Set[T] {
set := NewSet[T]()
set := New[T]()
s.Iterate(func(value T) {
if !other.Contain(value) {
set.Add(value)
@@ -165,7 +165,7 @@ func (s Set[T]) SymmetricDifference(other Set[T]) Set[T] {
// Minus creates an set of whose element in origin set but not in compared set
func (s Set[T]) Minus(comparedSet Set[T]) Set[T] {
set := NewSet[T]()
set := New[T]()
s.Iterate(func(value T) {
if !comparedSet.Contain(value) {

View File

@@ -6,18 +6,18 @@ import (
"github.com/duke-git/lancet/v2/internal"
)
func TestSet_NewSetFromSlice(t *testing.T) {
func TestSet_FromSlice(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSet_NewSetFromSlice")
assert := internal.NewAssert(t, "TestSet_FromSlice")
s1 := NewSetFromSlice([]int{1, 2, 2, 3})
s1 := FromSlice([]int{1, 2, 2, 3})
assert.Equal(3, s1.Size())
assert.Equal(true, s1.Contain(1))
assert.Equal(true, s1.Contain(2))
assert.Equal(true, s1.Contain(3))
s2 := NewSetFromSlice([]int{})
s2 := FromSlice([]int{})
assert.Equal(0, s2.Size())
}
@@ -26,10 +26,10 @@ func TestSet_Add(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Add")
set := NewSet[int]()
set := New[int]()
set.Add(1, 2, 3)
cmpSet := NewSet(1, 2, 3)
cmpSet := New(1, 2, 3)
assert.Equal(true, set.Equal(cmpSet))
}
@@ -39,12 +39,12 @@ func TestSet_AddIfNotExist(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_AddIfNotExist")
set := NewSet[int]()
set := New[int]()
set.Add(1, 2, 3)
assert.Equal(false, set.AddIfNotExist(1))
assert.Equal(true, set.AddIfNotExist(4))
assert.Equal(NewSet(1, 2, 3, 4), set)
assert.Equal(New(1, 2, 3, 4), set)
}
func TestSet_AddIfNotExistBy(t *testing.T) {
@@ -52,7 +52,7 @@ func TestSet_AddIfNotExistBy(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_AddIfNotExistBy")
set := NewSet[int]()
set := New[int]()
set.Add(1, 2)
ok := set.AddIfNotExistBy(3, func(val int) bool {
@@ -75,7 +75,7 @@ func TestSet_Contain(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Contain")
set := NewSet[int]()
set := New[int]()
set.Add(1, 2, 3)
assert.Equal(true, set.Contain(1))
@@ -87,9 +87,9 @@ func TestSet_ContainAll(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_ContainAll")
set1 := NewSet(1, 2, 3)
set2 := NewSet(1, 2)
set3 := NewSet(1, 2, 3, 4)
set1 := New(1, 2, 3)
set2 := New(1, 2)
set3 := New(1, 2, 3, 4)
assert.Equal(true, set1.ContainAll(set2))
assert.Equal(false, set1.ContainAll(set3))
@@ -100,7 +100,7 @@ func TestSet_Clone(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Clone")
set1 := NewSet(1, 2, 3)
set1 := New(1, 2, 3)
set2 := set1.Clone()
assert.Equal(true, set1.Size() == set2.Size())
@@ -112,11 +112,11 @@ func TestSet_Delete(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Delete")
set := NewSet[int]()
set := New[int]()
set.Add(1, 2, 3)
set.Delete(3)
assert.Equal(true, set.Equal(NewSet(1, 2)))
assert.Equal(true, set.Equal(New(1, 2)))
}
func TestSet_Equal(t *testing.T) {
@@ -124,9 +124,9 @@ func TestSet_Equal(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Equal")
set1 := NewSet(1, 2, 3)
set2 := NewSet(1, 2, 3)
set3 := NewSet(1, 2, 3, 4)
set1 := New(1, 2, 3)
set2 := New(1, 2, 3)
set3 := New(1, 2, 3, 4)
assert.Equal(true, set1.Equal(set2))
assert.Equal(false, set1.Equal(set3))
@@ -137,7 +137,7 @@ func TestSet_Iterate(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Iterate")
set := NewSet(1, 2, 3)
set := New(1, 2, 3)
arr := []int{}
set.Iterate(func(value int) {
arr = append(arr, value)
@@ -151,7 +151,7 @@ func TestSet_IsEmpty(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_IsEmpty")
set := NewSet[int]()
set := New[int]()
assert.Equal(true, set.IsEmpty())
}
@@ -160,7 +160,7 @@ func TestSet_Size(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Size")
set := NewSet(1, 2, 3)
set := New(1, 2, 3)
assert.Equal(3, set.Size())
}
@@ -169,7 +169,7 @@ func TestSet_Values(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Values")
set := NewSet(1, 2, 3)
set := New(1, 2, 3)
values := set.Values()
assert.Equal(3, len(values))
@@ -180,12 +180,12 @@ func TestSet_Union(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Union")
set1 := NewSet(1, 2, 3)
set2 := NewSet(2, 3, 4, 5)
set1 := New(1, 2, 3)
set2 := New(2, 3, 4, 5)
unionSet := set1.Union(set2)
assert.Equal(NewSet(1, 2, 3, 4, 5), unionSet)
assert.Equal(New(1, 2, 3, 4, 5), unionSet)
}
func TestSet_Intersection(t *testing.T) {
@@ -193,11 +193,11 @@ func TestSet_Intersection(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Intersection")
set1 := NewSet(1, 2, 3)
set2 := NewSet(2, 3, 4, 5)
set1 := New(1, 2, 3)
set2 := New(2, 3, 4, 5)
intersectionSet := set1.Intersection(set2)
assert.Equal(NewSet(2, 3), intersectionSet)
assert.Equal(New(2, 3), intersectionSet)
}
func TestSet_SymmetricDifference(t *testing.T) {
@@ -205,10 +205,10 @@ func TestSet_SymmetricDifference(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_SymmetricDifference")
set1 := NewSet(1, 2, 3)
set2 := NewSet(2, 3, 4, 5)
set1 := New(1, 2, 3)
set2 := New(2, 3, 4, 5)
assert.Equal(NewSet(1, 4, 5), set1.SymmetricDifference(set2))
assert.Equal(New(1, 4, 5), set1.SymmetricDifference(set2))
}
func TestSet_Minus(t *testing.T) {
@@ -216,16 +216,16 @@ func TestSet_Minus(t *testing.T) {
assert := internal.NewAssert(t, "TestSet_Minus")
set1 := NewSet(1, 2, 3)
set2 := NewSet(2, 3, 4, 5)
set3 := NewSet(2, 3)
set1 := New(1, 2, 3)
set2 := New(2, 3, 4, 5)
set3 := New(2, 3)
assert.Equal(NewSet(1), set1.Minus(set2))
assert.Equal(NewSet(4, 5), set2.Minus(set3))
assert.Equal(New(1), set1.Minus(set2))
assert.Equal(New(4, 5), set2.Minus(set3))
}
func TestEachWithBreak(t *testing.T) {
// s := NewSet(1, 2, 3, 4, 5)
// s := New(1, 2, 3, 4, 5)
// var sum int
@@ -244,7 +244,7 @@ func TestEachWithBreak(t *testing.T) {
// func TestPop(t *testing.T) {
// assert := internal.NewAssert(t, "TestPop")
// s := NewSet[int]()
// s := New[int]()
// val, ok := s.Pop()
// assert.Equal(0, val)
@@ -254,7 +254,7 @@ func TestEachWithBreak(t *testing.T) {
// s.Add(2)
// s.Add(3)
// // s = NewSet(1, 2, 3, 4, 5)
// // s = New(1, 2, 3, 4, 5)
// val, ok = s.Pop()
// assert.Equal(3, val)

View File

@@ -26,6 +26,8 @@ import (
- [Remove](#Remove)
- [IndexOf](#IndexOf)
- [LastIndexOf](#LastIndexOf)
- [IndexOfFunc](#IndexOfFunc)
- [LastIndexOfFunc](#LastIndexOfFunc)
- [IsEmpty](#IsEmpty)
- [Contain](#Contain)
- [ValueOf](#ValueOf)
@@ -198,6 +200,58 @@ func main() {
```
### <span id="IndexOfFunc">IndexOfFunc</span>
<p>返回第一个满足判断函数f(v)的元素的索引,如果找不到则返回-1。</p>
<b>函数签名:</b>
```go
func (l *CopyOnWriteList[T]) IndexOfFunc(f func(T) bool) int
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/list"
)
func main() {
l := list.NewCopyOnWriteList([]int{1, 2, 3})
fmt.Println(l.IndexOfFunc(func(a int) bool { return a == 1 })) //0
fmt.Println(l.IndexOfFunc(func(a int) bool { return a == 0 })) //-1
}
```
### <span id="LastIndexOfFunc">LastIndexOfFunc</span>
<p>返回最后一个满足判断函数f(v)的元素的索引,如果找不到则返回-1。</p>
<b>函数签名:</b>
```go
func (l *CopyOnWriteList[T]) LastIndexOfFunc(f func(T) bool) int
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/list"
)
func main() {
l := list.NewCopyOnWriteList([]int{1, 2, 3, 1})
fmt.Println(l.LastIndexOfFunc(func(a int) bool { return a == 1 })) // 3
fmt.Println(l.LastIndexOfFunc(func(a int) bool { return a == 0 })) //-1
}
```
### IsEmpty
如果此列表不包含任何元素,则返回 true。

View File

@@ -0,0 +1,412 @@
# Optional
Optional类型代表一个可选的值它要么包含一个实际值要么为空。
<div STYLE="page-break-after: always;"></div>
## 源码
- [https://github.com/duke-git/lancet/blob/main/datastructure/optional/optional.go](https://github.com/duke-git/lancet/blob/main/datastructure/optional/optional.go)
<div STYLE="page-break-after: always;"></div>
## 用法
```go
import (
"github.com/duke-git/lancet/v2/datastructure/optional"
)
```
<div STYLE="page-break-after: always;"></div>
## 目录
- [Of](#Of)
- [FromNillable](#FromNillable)
- [Default](#Default)
- [IsNotNil](#IsNotNil)
- [IsNil](#IsNil)
- [IsNotNil](#IsNotNil)
- [IfNotNilOrElse](#IfNotNilOrElse)
- [Umwarp](#Umwarp)
- [OrElse](#OrElse)
- [OrElseGet](#OrElseGet)
- [OrElseTrigger](#OrElseTrigger)
<div STYLE="page-break-after: always;"></div>
## 文档
### <span id="Of">Of</span>
<p>返回一个包含非空值的Optional。</p>
<b>函数签名:</b>
```go
func Of[T any](value T) Optional[T]
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
value := 42
opt := optional.Of(value)
fmt.Println(opt.Get())
// Output:
// 42
}
```
### <span id="FromNillable">FromNillable</span>
<p>返回一个包含给定值的Optional该值可能为空 (nil)。</p>
<b>函数签名:</b>
```go
func FromNillable[T any](value *T) Optional[T]
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
var value *int = nil
opt := optional.FromNillable(value)
fmt.Println(opt.IsNotNil())
value = new(int)
*value = 42
opt = optional.FromNillable(value)
fmt.Println(opt.IsNotNil())
// Output:
// false
// true
}
```
### <span id="Default">Default</span>
<p>返回一个空Optional实例。</p>
<b>函数签名:</b>
```go
func Default[T any]() Optional[T]
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
fmt.Println(optDefault.IsNil())
// Output:
// true
}
```
### <span id="IsNil">IsNil</span>
<p>验证Optional是否为空。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) IsNil() bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
fmt.Println(optDefault.IsNil())
// Output:
// true
}
```
### <span id="IsNotNil">IsNotNil</span>
<p>检查当前Optional内是否存在值。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) IsNotNil() bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
var value *int = nil
opt := optional.FromNillable(value)
fmt.Println(opt.IsNotNil())
value = new(int)
*value = 42
opt = optional.FromNillable(value)
fmt.Println(opt.IsNotNil())
// Output:
// false
// true
}
```
### <span id="IfNotNil">IfNotNil</span>
<p>如果值存在则使用action方法执行给定的操作。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) IfNotNil(action func(value T))
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
called := false
action := func(value int) { called = true }
optDefault := optional.Default[int]()
optDefault.IfNotNil(action)
fmt.Println(called)
called = false // Reset for next test
optWithValue := optional.Of(42)
optWithValue.IfNotNil(action)
fmt.Println(optWithValue.IsNotNil())
// Output:
// false
// true
}
```
### <span id="IfNotNilOrElse">IfNotNilOrElse</span>
<p>根据是否存在值执行相应的操作:有值则执行指定操作,没有值则执行默认操作。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) IfNotNilOrElse(action func(value T), fallbackAction func())
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
calledWithValue := false
valueAction := func(value int) { calledWithValue = true }
emptyAction := func() { t.Errorf("Empty action should not be called when value is present") }
optWithValue := optional.Of(42)
optWithValue.IfNotNilOrElse(valueAction, emptyAction)
fmt.Println(calledWithValue)
calledWithEmpty := false
valueAction = func(value int) { t.Errorf("Value action should not be called when value is not present") }
emptyAction = func() { calledWithEmpty = true }
optDefault := optional.Default[int]()
optDefault.IfNotNilOrElse(valueAction, emptyAction)
fmt.Println(calledWithEmpty)
// Output:
// true
// true
}
```
### <span id="Unwrap">Unwrap</span>
<p>如果存在返回该值否则引发panic。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) Unwrap() T
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
value := 42
opt := optional.Of(value)
fmt.Println(opt.Unwrap())
// Output:
// 42
}
```
### <span id="OrElse">OrElse</span>
<p>检查Optional值是否存在如果存在则直接返回该值。如果不存在返回参数other值。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) OrElse(other T) T
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Empty[int]()
val := optDefault.OrElse(100)
fmt.Println(val)
optWithValue := optional.Of(42)
val = optWithValue.OrElse(100)
fmt.Println(val)
// Output:
// 100
// 42
}
```
### <span id="OrElseGet">OrElseGet</span>
<p>检查Optional值是否存在如果存在则直接返回该值。如果不存在则调用一个提供的函数 (supplier),并返回该函数的执行结果。</p>
<b>函数签名:</b>
```go
func (o Optional[T]) OrElseGet(action func() T) T
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
action := func() int { return 100 }
val := optDefault.OrElseGet(action)
fmt.Println(val)
// Output:
// 100
}
```
### <span id="OrElseTrigger">OrElseTrigger</span>
<p>检查Optional值是否存在如果存在则直接返回该值否则返回错误。</p>
<b>函数签名:</b>
```go
OrElseTrigger(errorHandler func() error) (T, error)
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
_, err := optDefault.OrElseTrigger(func() error { return errors.New("no value") })
fmt.Println(err.Error())
optWithValue := optional.Of(42)
val, err := optWithValue.OrElseTrigger(func() error { return errors.New("no value") })
fmt.Println(val)
fmt.Println(err)
// Output:
// no value
// 42
// nil
}
```

View File

@@ -1,6 +1,6 @@
# Set
Set 集合数据结构类似列表。Set 中元素不重复。
集合数据结构类似列表。Set中元素不重复。
<div STYLE="page-break-after: always;"></div>
@@ -22,8 +22,8 @@ import (
## 目录
- [NewSet](#NewSet)
- [NewSetFromSlice](#NewSetFromSlice)
- [New](#New)
- [FromSlice](#FromSlice)
- [Values](#Values)
- [Add](#Add)
- [AddIfNotExist](#AddIfNotExist)
@@ -45,7 +45,7 @@ import (
## 文档
### <span id="NewSet">NewSet</span>
### <span id="New">New</span>
<p>返回Set结构体对象</p>
@@ -53,7 +53,7 @@ import (
```go
type Set[T comparable] map[T]bool
func NewSet[T comparable](items ...T) Set[T]
func New[T comparable](items ...T) Set[T]
```
<b>示例:</b>
@@ -67,19 +67,19 @@ import (
)
func main() {
st := set.NewSet[int](1,2,2,3)
st := set.New[int](1,2,2,3)
fmt.Println(st.Values()) //1,2,3
}
```
### <span id="NewSetFromSlice">NewSetFromSlice</span>
### <span id="FromSlice">FromSlice</span>
<p>基于切片创建集合</p>
<b>函数签名:</b>
```go
func NewSetFromSlice[T comparable](items []T) Set[T]
func FromSlice[T comparable](items []T) Set[T]
```
<b>示例:</b>
@@ -93,7 +93,7 @@ import (
)
func main() {
st := set.NewSetFromSlice([]int{1, 2, 2, 3})
st := set.FromSlice([]int{1, 2, 2, 3})
fmt.Println(st.Values()) //1,2,3
}
```
@@ -119,7 +119,7 @@ import (
)
func main() {
st := set.NewSet[int](1,2,2,3)
st := set.New[int](1,2,2,3)
fmt.Println(st.Values()) //1,2,3
}
```
@@ -145,7 +145,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2, 3)
fmt.Println(st.Values()) //1,2,3
@@ -173,7 +173,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2, 3)
r1 := st.AddIfNotExist(1)
@@ -206,7 +206,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2)
ok := st.AddIfNotExistBy(3, func(val int) bool {
@@ -245,7 +245,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2, 3)
set.Delete(3)
@@ -274,7 +274,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2, 3)
fmt.Println(st.Contain(1)) //true
@@ -303,9 +303,9 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(1, 2)
set3 := set.NewSet(1, 2, 3, 4)
set1 := set.New(1, 2, 3)
set2 := set.New(1, 2)
set3 := set.New(1, 2, 3, 4)
fmt.Println(set1.ContainAll(set2)) //true
fmt.Println(set1.ContainAll(set3)) //false
@@ -333,7 +333,7 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set1 := set.New(1, 2, 3)
fmt.Println(set1.Size()) //3
}
@@ -360,7 +360,7 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set1 := set.New(1, 2, 3)
set2 := set1.Clone()
fmt.Println(set1.Size() == set2.Size()) //true
@@ -389,9 +389,9 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(1, 2, 3)
set3 := set.NewSet(1, 2, 3, 4)
set1 := set.New(1, 2, 3)
set2 := set.New(1, 2, 3)
set3 := set.New(1, 2, 3, 4)
fmt.Println(set1.Equal(set2)) //true
fmt.Println(set1.Equal(set3)) //false
@@ -419,7 +419,7 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set1 := set.New(1, 2, 3)
arr := []int{}
set.Iterate(func(item int) {
arr = append(arr, item)
@@ -450,7 +450,7 @@ import (
)
func main() {
s := set.NewSet(1, 2, 3, 4, 5)
s := set.New(1, 2, 3, 4, 5)
var sum int
@@ -487,8 +487,8 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet()
set1 := set.New(1, 2, 3)
set2 := set.New()
fmt.Println(set1.IsEmpty()) //false
fmt.Println(set2.IsEmpty()) //true
@@ -516,8 +516,8 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(2, 3, 4, 5)
set1 := set.New(1, 2, 3)
set2 := set.New(2, 3, 4, 5)
set3 := set1.Union(set2)
fmt.Println(set3.Values()) //1,2,3,4,5
@@ -545,8 +545,8 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(2, 3, 4, 5)
set1 := set.New(1, 2, 3)
set2 := set.New(2, 3, 4, 5)
set3 := set1.Intersection(set2)
fmt.Println(set3.Values()) //2,3
@@ -574,8 +574,8 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(2, 3, 4, 5)
set1 := set.New(1, 2, 3)
set2 := set.New(2, 3, 4, 5)
set3 := set1.SymmetricDifference(set2)
fmt.Println(set3.Values()) //1,4,5
@@ -603,9 +603,9 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(2, 3, 4, 5)
set3 := set.NewSet(2, 3)
set1 := set.New(1, 2, 3)
set2 := set.New(2, 3, 4, 5)
set3 := set.New(2, 3)
res1 := set1.Minus(set2)
fmt.Println(res1.Values()) //1
@@ -636,7 +636,7 @@ import (
)
func main() {
s := set.NewSet[int]()
s := set.New[int]()
s.Add(1)
s.Add(2)
s.Add(3)

View File

@@ -26,6 +26,7 @@ import (
- [CreateFile](#CreateFile)
- [CreateDir](#CreateDir)
- [CopyFile](#CopyFile)
- [CopyDir](#CopyDir)
- [CurrentPath](#CurrentPath)
- [FileMode](#FileMode)
- [MiMeType](#MiMeType)
@@ -162,6 +163,34 @@ func main() {
}
```
### <span id="CopyDir">CopyDir</span>
<p>拷贝文件夹到目标路径会递归复制文件夹下所有的文件及文件夹并且访问权限也与源文件夹保持一致。当dstPath存在时会返回error</p>
<b>函数签名:</b>
```go
func CopyDir(srcPath string, dstPath string) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/YAyFTA_UuPb)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/fileutil"
)
func main() {
err := fileutil.CopyFile("./test_src", "./test_dest")
if err != nil {
fmt.Println(err)
}
}
```
### <span id="CurrentPath">CurrentPath</span>
<p>返回当前位置的绝对路径。</p>
@@ -759,7 +788,7 @@ func main() {
func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune, headers ...[]string) error
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/umAIomZFV1c)</span></b>
```go
package main
@@ -782,7 +811,7 @@ func main() {
}
headers := []string{"Name", "Age", "Gender"}
err := WriteMapsToCsv(csvFilePath, records, false, ';', headers)
err := fileutil.WriteMapsToCsv(csvFilePath, records, false, ';', headers)
if err != nil {
log.Fatal(err)

View File

@@ -7,6 +7,7 @@ function 函数包控制函数执行流程,包含部分函数式编程。
## 源码:
- [https://github.com/duke-git/lancet/blob/main/function/function.go](https://github.com/duke-git/lancet/blob/main/function/function.go)
- [https://github.com/duke-git/lancet/blob/main/function/predicate.go](https://github.com/duke-git/lancet/blob/main/function/predicate.go)
- [https://github.com/duke-git/lancet/blob/main/function/watcher.go](https://github.com/duke-git/lancet/blob/main/function/watcher.go)
<div STYLE="page-break-after: always;"></div>
@@ -32,6 +33,12 @@ import (
- [Schedule](#Schedule)
- [Pipeline](#Pipeline)
- [Watcher](#Watcher)
- [And](#And)
- [Or](#Or)
- [Negate](#Negate)
- [Nor](#Nor)
- [Xnor](#Xnor)
- [Nand](#Nand)
<div STYLE="page-break-after: always;"></div>
@@ -404,3 +411,231 @@ func longRunningTask() {
}
```
### <span id="And">And</span>
<p>返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑and操作。只有当所有谓词判断函数对于给定的值都返回true时返回true, 否则返回false。</p>
<b>函数签名:</b>
```go
func And[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
isNumericAndLength5 := function.And(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(isNumericAndLength5("12345"))
fmt.Println(isNumericAndLength5("1234"))
fmt.Println(isNumericAndLength5("abcde"))
// Output:
// true
// false
// false
}
```
### <span id="Or">Or</span>
<p>返回一个复合谓词判断函数该判断函数表示一组谓词的逻辑or操作。只有当所有谓词判断函数对于给定的值都返回false时返回false, 否则返回true。</p>
<b>函数签名:</b>
```go
func Or[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
containsDigitOrSpecialChar := function.Or(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return strings.ContainsAny(s, "!@#$%") },
)
fmt.Println(containsDigitOrSpecialChar("hello!"))
fmt.Println(containsDigitOrSpecialChar("hello"))
// Output:
// true
// false
}
```
### <span id="Negate">Negate</span>
<p>返回一个谓词函数,该谓词函数表示当前谓词的逻辑否定。</p>
<b>函数签名:</b>
```go
func Negate[T any](predicate func(T) bool) func(T) bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
// Define some simple predicates for demonstration
isUpperCase := func(s string) bool {
return strings.ToUpper(s) == s
}
isLowerCase := func(s string) bool {
return strings.ToLower(s) == s
}
isMixedCase := function.Negate(function.Or(isUpperCase, isLowerCase))
fmt.Println(isMixedCase("ABC"))
fmt.Println(isMixedCase("AbC"))
// Output:
// false
// true
}
```
### <span id="Nor">Nor</span>
<p>返回一个组合谓词函数,表示给定值上所有谓词逻辑非或 (nor) 的结果。只有当所有谓词函数对给定值都返回false时该组合谓词函数才返回true。</p>
<b>函数签名:</b>
```go
func Nor[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
match := function.Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(match("dbcdckkeee"))
match = function.Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(match("0123456789"))
// Output:
// true
// false
}
```
### <span id="Nand">Nand</span>
<p>返回一个复合谓词函数,表示给定谓词函数列表的逻辑非与 (NAND)。仅当列表中所有函数对给定参数返回false时才返回true否则返回false。</p>
<b>函数签名:</b>
```go
func Nand[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
isNumericAndLength5 := function.Nand(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(isNumericAndLength5("12345"))
fmt.Println(isNumericAndLength5("1234"))
fmt.Println(isNumericAndLength5("abcdef"))
// Output:
// false
// false
// true
}
```
### <span id="Xnor">Xnor</span>
<p>返回一个复合谓词函数,表示给定一组谓词函数的逻辑异或 (XNOR)。只有当所有 谓词函数对给参数都返回true或false时该谓词函数才返回true。</p>
<b>函数签名:</b>
```go
func Xnor[T any](predicates ...func(T) bool) func(T) bool
```
<b>示例:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
isEven := func(i int) bool { return i%2 == 0 }
isPositive := func(i int) bool { return i > 0 }
match := function.Xnor(isEven, isPositive)
fmt.Println(match(2))
fmt.Println(match(-3))
fmt.Println(match(3))
// Output:
// true
// true
// false
}
```

View File

@@ -525,7 +525,7 @@ func main() {
func DeleteAt[T any](slice []T, index int) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/pJ-d6MUWcvK)</span></b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/800B1dPBYyd)</span></b>
```go
import (
@@ -565,7 +565,7 @@ func main() {
func DeleteRange[T any](slice []T, start, end int) []T
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/945HwiNrnle)</span></b>
```go
import (

View File

@@ -60,6 +60,7 @@ import (
- [ContainsAll](#ContainsAll)
- [ContainsAny](#ContainsAny)
- [RemoveWhiteSpace](#RemoveWhiteSpace)
- [SubInBetween](#SubInBetween)
<div STYLE="page-break-after: always;"></div>
@@ -1096,10 +1097,10 @@ import (
func main() {
result1 := strutil.IsNotBlank("")
result2 := strutil.IsNotBlank(" ")
result2 := strutil.IsNotBlank(" ")
result3 := strutil.IsNotBlank("\t\v\f\n")
result4 := strutil.IsNotBlank(" 中文")
result5 := strutil.IsNotBlank(" world ")
result5 := strutil.IsNotBlank(" world ")
fmt.Println(result1)
fmt.Println(result2)
@@ -1462,3 +1463,36 @@ func main() {
// hello world
}
```
### <span id="SubInBetween">SubInBetween</span>
<p>获取字符串中指定的起始字符串start和终止字符串end直接的子字符串。</p>
<b>函数签名:</b>
```go
func SubInBetween(str string, start string, end string) string
```
<b>示例:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/strutil"
)
func main() {
str := "abcde"
result1 := strutil.SubInBetween(str, "", "de")
result2 := strutil.SubInBetween(str, "a", "d")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// abc
// bc
}
```

View File

@@ -26,6 +26,8 @@ import (
- [Remove](#Remove)
- [IndexOf](#IndexOf)
- [LastIndexOf](#LastIndexOf)
- [IndexOfFunc](#IndexOfFunc)
- [LastIndexOfFunc](#LastIndexOfFunc)
- [IsEmpty](#IsEmpty)
- [Contain](#Contain)
- [ValueOf](#ValueOf)
@@ -197,6 +199,59 @@ func main() {
```
### <span id="IndexOfFunc">IndexOfFunc</span>
<p> IndexOfFunc returns the first index satisfying the functional predicate f(v) bool. if not found return -1.</p>
<b>Signature:</b>
```go
func (l *CopyOnWriteList[T]) IndexOfFunc(f func(T) bool) int
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/list"
)
func main() {
l := list.NewCopyOnWriteList([]int{1, 2, 3})
fmt.Println(l.IndexOfFunc(func(a int) bool { return a == 1 })) //0
fmt.Println(l.IndexOfFunc(func(a int) bool { return a == 0 })) //-1
}
```
### <span id="LastIndexOfFunc">LastIndexOfFunc</span>
<p>LastIndexOfFunc returns the index of the last occurrence of the value in this list satisfying the functional predicate f(T) bool. if not found return -1.</p>
<b>Signature:</b>
```go
func (l *CopyOnWriteList[T]) LastIndexOfFunc(f func(T) bool) int
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/list"
)
func main() {
l := list.NewCopyOnWriteList([]int{1, 2, 3, 1})
fmt.Println(l.LastIndexOfFunc(func(a int) bool { return a == 1 })) // 3
fmt.Println(l.LastIndexOfFunc(func(a int) bool { return a == 0 })) //-1
}
```
### IsEmpty
Returns true if this list does not contain any elements.

View File

@@ -0,0 +1,416 @@
# Optional
Optional is a type that may or may not contain a non-nil value.
<div STYLE="page-break-after: always;"></div>
## Source
- [https://github.com/duke-git/lancet/blob/main/datastructure/optional/optional.go](https://github.com/duke-git/lancet/blob/main/datastructure/optional/optional.go)
<div STYLE="page-break-after: always;"></div>
## Usage
```go
import (
"github.com/duke-git/lancet/v2/datastructure/optional"
)
```
<div STYLE="page-break-after: always;"></div>
## Index
- [Of](#Of)
- [FromNillable](#FromNillable)
- [Default](#Default)
- [IsNotNil](#IsNotNil)
- [IsNil](#IsNil)
- [IsNotNil](#IsNotNil)
- [IfNotNilOrElse](#IfNotNilOrElse)
- [Umwarp](#Umwarp)
- [OrElse](#OrElse)
- [OrElseGet](#OrElseGet)
- [OrElseTrigger](#OrElseTrigger)
<div STYLE="page-break-after: always;"></div>
## Documentation
### <span id="Of">Of</span>
<p>Returns an Optional with a non-nil value.</p>
<b>Signature:</b>
```go
func Of[T any](value T) Optional[T]
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
value := 42
opt := optional.Of(value)
fmt.Println(opt.Get())
// Output:
// 42
}
```
### <span id="FromNillable">FromNillable</span>
<p>Returns an Optional for a given value, which may be nil.</p>
<b>Signature:</b>
```go
func FromNillable[T any](value *T) Optional[T]
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
var value *int = nil
opt := optional.FromNillable(value)
fmt.Println(opt.IsNotNil())
value = new(int)
*value = 42
opt = optional.FromNillable(value)
fmt.Println(opt.IsNotNil())
// Output:
// false
// true
}
```
### <span id="Default">Default</span>
<p>Returns an default Optional instance.</p>
<b>Signature:</b>
```go
func Default[T any]() Optional[T]
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
fmt.Println(optDefault.IsNil())
// Output:
// true
}
```
### <span id="IsNil">IsNil</span>
<p>Checks if the Optional is nil.</p>
<b>Signature:</b>
```go
func (o Optional[T]) IsNil() bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
fmt.Println(optDefault.IsNil())
// Output:
// true
}
```
### <span id="IsNotNil">IsNotNil</span>
<p>Checks if there is a value not nil.</p>
<b>Signature:</b>
```go
func (o Optional[T]) IsNotNil() bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
var value *int = nil
opt := optional.FromNillable(value)
fmt.Println(opt.IsNotNil())
value = new(int)
*value = 42
opt = optional.FromNillable(value)
fmt.Println(opt.IsNotNil())
// Output:
// false
// true
}
```
### <span id="IfNotNil">IfNotNil</span>
<p>Performs the given action with the value if a value is present.</p>
<b>Signature:</b>
```go
func (o Optional[T]) IfNotNil(action func(value T))
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
called := false
action := func(value int) { called = true }
optDefault := optional.Default[int]()
optDefault.IfNotNil(action)
fmt.Println(called)
called = false // Reset for next test
optWithValue := optional.Of(42)
optWithValue.IfNotNil(action)
fmt.Println(optWithValue.IsNotNil())
// Output:
// false
// true
}
```
### <span id="IfNotNilOrElse">IfNotNilOrElse</span>
<p>Performs the action with the value if not nil, otherwise performs the fallback action.</p>
<b>Signature:</b>
```go
func (o Optional[T]) IfNotNilOrElse(action func(value T), fallbackAction func())
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
calledWithValue := false
valueAction := func(value int) { calledWithValue = true }
emptyAction := func() { t.Errorf("Empty action should not be called when value is present") }
optWithValue := optional.Of(42)
optWithValue.IfNotNilOrElse(valueAction, emptyAction)
fmt.Println(calledWithValue)
calledWithEmpty := false
valueAction = func(value int) { t.Errorf("Value action should not be called when value is not present") }
emptyAction = func() { calledWithEmpty = true }
optDefault := optional.Default[int]()
optDefault.IfNotNilOrElse(valueAction, emptyAction)
fmt.Println(calledWithEmpty)
// Output:
// true
// true
}
```
### <span id="Unwrap">Unwrap</span>
<p>Returns the value if not nil, otherwise panics.</p>
<b>Signature:</b>
```go
func (o Optional[T]) Unwrap() T
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
value := 42
opt := optional.Of(value)
fmt.Println(opt.Unwrap())
// Output:
// 42
}
```
### <span id="OrElse">OrElse</span>
<p>Returns the value if not nill, otherwise returns other.</p>
<b>Signature:</b>
```go
func (o Optional[T]) OrElse(other T) T
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
val := optDefault.OrElse(100)
fmt.Println(val)
optWithValue := optional.Of(42)
val = optWithValue.OrElse(100)
fmt.Println(val)
// Output:
// 100
// 42
}
```
### <span id="OrElseGet">OrElseGet</span>
<p>Returns the value if not nil, otherwise invokes action and returns the result.</p>
<b>Signature:</b>
```go
func (o Optional[T]) OrElseGet(action func() T) T
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
action := func() int { return 100 }
val := optDefault.OrElseGet(action)
fmt.Println(val)
// Output:
// 100
}
```
### <span id="OrElseTrigger">OrElseTrigger</span>
<p>Returns the value if present, otherwise returns an error.</p>
<b>Signature:</b>
```go
OrElseTrigger(errorHandler func() error) (T, error)
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/datastructure/optional"
)
func main() {
optDefault := optional.Default[int]()
_, err := optDefault.OrElseTrigger(func() error { return errors.New("no value") })
fmt.Println(err.Error())
optWithValue := optional.Of(42)
val, err := optWithValue.OrElseTrigger(func() error { return errors.New("no value") })
fmt.Println(val)
fmt.Println(err)
// Output:
// no value
// 42
// nil
}
```

View File

@@ -22,8 +22,8 @@ import (
## Index
- [NewSet](#NewSet)
- [NewSetFromSlice](#NewSetFromSlice)
- [New](#New)
- [FromSlice](#FromSlice)
- [Values](#Values)
- [Add](#Add)
- [AddIfNotExist](#AddIfNotExist)
@@ -46,7 +46,7 @@ import (
## Documentation
### <span id="NewSet">NewSet</span>
### <span id="New">New</span>
<p>Create a set instance</p>
@@ -54,7 +54,7 @@ import (
```go
type Set[T comparable] map[T]bool
func NewSet[T comparable](items ...T) Set[T]
func New[T comparable](items ...T) Set[T]
```
<b>Example:</b>
@@ -68,19 +68,19 @@ import (
)
func main() {
st := set.NewSet[int](1,2,2,3)
st := set.New[int](1,2,2,3)
fmt.Println(st.Values()) //1,2,3
}
```
### <span id="NewSetFromSlice">NewSetFromSlice</span>
### <span id="FromSlice">FromSlice</span>
<p>Create a set from slice</p>
<b>Signature:</b>
```go
func NewSetFromSlice[T comparable](items []T) Set[T]
func FromSlice[T comparable](items []T) Set[T]
```
<b>Example:</b>
@@ -94,7 +94,7 @@ import (
)
func main() {
st := set.NewSetFromSlice([]int{1, 2, 2, 3})
st := set.FromSlice([]int{1, 2, 2, 3})
fmt.Println(st.Values()) //1,2,3
}
```
@@ -120,7 +120,7 @@ import (
)
func main() {
st := set.NewSet[int](1,2,2,3)
st := set.New[int](1,2,2,3)
fmt.Println(st.Values()) //1,2,3
}
```
@@ -146,7 +146,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2, 3)
fmt.Println(st.Values()) //1,2,3
@@ -174,7 +174,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2, 3)
r1 := st.AddIfNotExist(1)
@@ -207,7 +207,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2)
ok := st.AddIfNotExistBy(3, func(val int) bool {
@@ -246,7 +246,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2, 3)
set.Delete(3)
@@ -275,7 +275,7 @@ import (
)
func main() {
st := set.NewSet[int]()
st := set.New[int]()
st.Add(1, 2, 3)
fmt.Println(st.Contain(1)) //true
@@ -304,9 +304,9 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(1, 2)
set3 := set.NewSet(1, 2, 3, 4)
set1 := set.New(1, 2, 3)
set2 := set.New(1, 2)
set3 := set.New(1, 2, 3, 4)
fmt.Println(set1.ContainAll(set2)) //true
fmt.Println(set1.ContainAll(set3)) //false
@@ -334,7 +334,7 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set1 := set.New(1, 2, 3)
fmt.Println(set1.Size()) //3
}
@@ -361,7 +361,7 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set1 := set.New(1, 2, 3)
set2 := set1.Clone()
fmt.Println(set1.Size() == set2.Size()) //true
@@ -390,9 +390,9 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(1, 2, 3)
set3 := set.NewSet(1, 2, 3, 4)
set1 := set.New(1, 2, 3)
set2 := set.New(1, 2, 3)
set3 := set.New(1, 2, 3, 4)
fmt.Println(set1.Equal(set2)) //true
fmt.Println(set1.Equal(set3)) //false
@@ -420,7 +420,7 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set1 := set.New(1, 2, 3)
arr := []int{}
set.Iterate(func(item int) {
arr = append(arr, item)
@@ -451,7 +451,7 @@ import (
)
func main() {
s := set.NewSet(1, 2, 3, 4, 5)
s := set.New(1, 2, 3, 4, 5)
var sum int
@@ -488,8 +488,8 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet()
set1 := set.New(1, 2, 3)
set2 := set.New()
fmt.Println(set1.IsEmpty()) //false
fmt.Println(set2.IsEmpty()) //true
@@ -517,8 +517,8 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(2, 3, 4, 5)
set1 := set.New(1, 2, 3)
set2 := set.New(2, 3, 4, 5)
set3 := set1.Union(set2)
fmt.Println(set3.Values()) //1,2,3,4,5
@@ -546,8 +546,8 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(2, 3, 4, 5)
set1 := set.New(1, 2, 3)
set2 := set.New(2, 3, 4, 5)
set3 := set1.Intersection(set2)
fmt.Println(set3.Values()) //2,3
@@ -575,8 +575,8 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(2, 3, 4, 5)
set1 := set.New(1, 2, 3)
set2 := set.New(2, 3, 4, 5)
set3 := set1.SymmetricDifference(set2)
fmt.Println(set3.Values()) //1,4,5
@@ -604,9 +604,9 @@ import (
)
func main() {
set1 := set.NewSet(1, 2, 3)
set2 := set.NewSet(2, 3, 4, 5)
set3 := set.NewSet(2, 3)
set1 := set.New(1, 2, 3)
set2 := set.New(2, 3, 4, 5)
set3 := set.New(2, 3)
res1 := set1.Minus(set2)
fmt.Println(res1.Values()) //1
@@ -637,7 +637,7 @@ import (
)
func main() {
s := set.NewSet[int]()
s := set.New[int]()
s.Add(1)
s.Add(2)
s.Add(3)

View File

@@ -26,6 +26,7 @@ import (
- [CreateFile](#CreateFile)
- [CreateDir](#CreateDir)
- [CopyFile](#CopyFile)
- [CopyDir](#CopyDir)
- [CurrentPath](#CurrentPath)
- [FileMode](#FileMode)
- [MiMeType](#MiMeType)
@@ -45,7 +46,6 @@ import (
- [Sha](#Sha)
- [ReadCsvFile](#ReadCsvFile)
- [WriteCsvFile](#WriteCsvFile)
- [WriteCsvFile](#WriteCsvFile)
- [WriteMapsToCsv](#WriteMapsToCsv)
- [WriteStringToFile](#WriteStringToFile)
- [WriteBytesToFile](#WriteBytesToFile)
@@ -163,6 +163,34 @@ func main() {
}
```
### <span id="CopyDir">CopyDir</span>
<p>copy src directory to dst directory, it will copy all files and directories recursively. the access permission will be the same as the source directory. if dstPath exists, it will return an error.</p>
<b>Signature:</b>
```go
func CopyDir(srcPath string, dstPath string) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/YAyFTA_UuPb)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/fileutil"
)
func main() {
err := fileutil.CopyFile("./test_src", "./test_dest")
if err != nil {
fmt.Println(err)
}
}
```
### <span id="CurrentPath">CurrentPath</span>
<p>return current absolute path.</p>
@@ -760,7 +788,7 @@ func main() {
func WriteMapsToCsv(filepath string, records []map[string]any, appendToExistingFile bool, delimiter rune, headers ...[]string) error
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/umAIomZFV1c)</span></b>
```go
package main

View File

@@ -7,6 +7,7 @@ Package function can control the flow of function execution and support part of
## Source:
- [https://github.com/duke-git/lancet/blob/main/function/function.go](https://github.com/duke-git/lancet/blob/main/function/function.go)
- [https://github.com/duke-git/lancet/blob/main/function/predicate.go](https://github.com/duke-git/lancet/blob/main/function/predicate.go)
- [https://github.com/duke-git/lancet/blob/main/function/watcher.go](https://github.com/duke-git/lancet/blob/main/function/watcher.go)
<div STYLE="page-break-after: always;"></div>
@@ -32,6 +33,12 @@ import (
- [Schedule](#Schedule)
- [Pipeline](#Pipeline)
- [Watcher](#Watcher)
- [And](#And)
- [Or](#Or)
- [Negate](#Negate)
- [Nor](#Nor)
- [Xnor](#Xnor)
- [Nand](#Nand)
<div STYLE="page-break-after: always;"></div>
@@ -403,3 +410,232 @@ func longRunningTask() {
}
```
### <span id="And">And</span>
<p>Returns a composed predicate that represents the logical AND of a list of predicates. It evaluates to true only if all predicates evaluate to true for the given value.</p>
<b>Signature:</b>
```go
func And[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
isNumericAndLength5 := function.And(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(isNumericAndLength5("12345"))
fmt.Println(isNumericAndLength5("1234"))
fmt.Println(isNumericAndLength5("abcde"))
// Output:
// true
// false
// false
}
```
### <span id="Or">Or</span>
<p>Returns a composed predicate that represents the logical OR of a list of predicates. It evaluates to true if at least one of the predicates evaluates to true for the given value.</p>
<b>Signature:</b>
```go
func Or[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
containsDigitOrSpecialChar := function.Or(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return strings.ContainsAny(s, "!@#$%") },
)
fmt.Println(containsDigitOrSpecialChar("hello!"))
fmt.Println(containsDigitOrSpecialChar("hello"))
// Output:
// true
// false
}
```
### <span id="Negate">Negate</span>
<p>Returns a predicate that represents the logical negation of this predicate.</p>
<b>Signature:</b>
```go
func Negate[T any](predicate func(T) bool) func(T) bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
// Define some simple predicates for demonstration
isUpperCase := func(s string) bool {
return strings.ToUpper(s) == s
}
isLowerCase := func(s string) bool {
return strings.ToLower(s) == s
}
isMixedCase := function.Negate(function.Or(isUpperCase, isLowerCase))
fmt.Println(isMixedCase("ABC"))
fmt.Println(isMixedCase("AbC"))
// Output:
// false
// true
}
```
### <span id="Nor">Nor</span>
<p>Returns a composed predicate that represents the logical NOR of a list of predicates. It evaluates to true only if all predicates evaluate to false for the given value.</p>
<b>Signature:</b>
```go
func Nor[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
match := function.Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(match("dbcdckkeee"))
match = function.Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(match("0123456789"))
// Output:
// true
// false
}
```
### <span id="Nand">Nand</span>
<p>Returns a composed predicate that represents the logical NAND of a list of predicates. It evaluates to true only if all predicates evaluate to false for the given value.</p>
<b>Signature:</b>
```go
func Nand[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
isNumericAndLength5 := function.Nand(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(isNumericAndLength5("12345"))
fmt.Println(isNumericAndLength5("1234"))
fmt.Println(isNumericAndLength5("abcdef"))
// Output:
// false
// false
// true
}
```
### <span id="Xnor">Xnor</span>
<p>Returns a composed predicate that represents the logical XNOR of a list of predicates. It evaluates to true only if all predicates evaluate to true or false for the given value.</p>
<b>Signature:</b>
```go
func Xnor[T any](predicates ...func(T) bool) func(T) bool
```
<b>Example:</b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/function"
)
func main() {
isEven := func(i int) bool { return i%2 == 0 }
isPositive := func(i int) bool { return i > 0 }
match := function.Xnor(isEven, isPositive)
fmt.Println(match(2))
fmt.Println(match(-3))
fmt.Println(match(3))
// Output:
// true
// true
// false
}
```

View File

@@ -524,7 +524,7 @@ func main() {
func DeleteAt[T any](slice []T, index int)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/pJ-d6MUWcvK)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/800B1dPBYyd)</span></b>
```go
import (
@@ -563,7 +563,7 @@ func main() {
func DeleteRange[T any](slice []T, start, end int) []T
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/945HwiNrnle)</span></b>
```go
import (
@@ -574,11 +574,11 @@ import (
func main() {
chars := []string{"a", "b", "c", "d", "e"}
result1 := DeleteRange(chars, 0, 0)
result2 := DeleteRange(chars, 0, 1)
result3 := DeleteRange(chars, 0, 3)
result4 := DeleteRange(chars, 0, 4)
result5 := DeleteRange(chars, 0, 5)
result1 := slice.DeleteRange(chars, 0, 0)
result2 := slice.DeleteRange(chars, 0, 1)
result3 := slice.DeleteRange(chars, 0, 3)
result4 := slice.DeleteRange(chars, 0, 4)
result5 := slice.DeleteRange(chars, 0, 5)
fmt.Println(result1)
fmt.Println(result2)

View File

@@ -1463,3 +1463,37 @@ func main() {
// hello world
}
```
### <span id="SubInBetween">SubInBetween</span>
<p>Return substring between the start and end position(excluded) of source string.</p>
<b>Signature:</b>
```go
func SubInBetween(str string, start string, end string) string
```
<b>Example:</b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/strutil"
)
func main() {
str := "abcde"
result1 := strutil.SubInBetween(str, "", "de")
result2 := strutil.SubInBetween(str, "a", "d")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// abc
// bc
}
```

View File

@@ -65,8 +65,8 @@ func (f *FileReader) Offset() int64 {
return f.offset
}
// Seek sets the current offset of the reading
func (f *FileReader) Seek(offset int64) error {
// SeekOffset sets the current offset of the reading
func (f *FileReader) SeekOffset(offset int64) error {
_, err := f.file.Seek(offset, 0)
if err != nil {
return err
@@ -113,6 +113,65 @@ func CreateDir(absPath string) error {
return os.MkdirAll(absPath, os.ModePerm)
}
// CopyDir copy src directory to dst directory, it will copy all files and directories recursively.
// the access permission will be the same as the source directory.
// if dstPath exists, it will return an error.
// Play: https://go.dev/play/p/YAyFTA_UuPb
func CopyDir(srcPath string, dstPath string) error {
if !IsDir(srcPath) {
return errors.New("source path is not a directory")
}
var err error
srcPath, err = filepath.Abs(srcPath)
if err != nil {
return err
}
if IsExist(dstPath) {
return errors.New("destination path already exists")
}
dstPath, err = filepath.Abs(dstPath)
if err != nil {
return err
}
// get srcPath's file info
srcFileInfo, err := os.Stat(srcPath)
if err != nil {
return err
}
// create dstPath with srcPath's mode
err = os.MkdirAll(dstPath, srcFileInfo.Mode())
if err != nil {
return err
}
err = filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
if srcPath == path {
return nil
}
curDstPath := filepath.Join(dstPath, filepath.Base(path))
if info.IsDir() {
err = CopyDir(path, curDstPath)
if err != nil {
return err
}
} else {
err = CopyFile(path, curDstPath)
if err != nil {
return err
}
err = os.Chmod(curDstPath, info.Mode())
if err != nil {
return err
}
}
return err
})
return err
}
// IsDir checks if the path is directory or not.
// Play: https://go.dev/play/p/WkVwEKqtOWk
func IsDir(path string) bool {
@@ -377,7 +436,7 @@ func UnZip(zipFile string, destPath string) error {
defer zipReader.Close()
for _, f := range zipReader.File {
//issue#62: fix ZipSlip bug
// issue#62: fix ZipSlip bug
path, err := safeFilepathJoin(destPath, f.Name)
if err != nil {
return err
@@ -754,7 +813,7 @@ func escapeCSVField(field string, delimiter rune) string {
}
// WriteMapsToCsv write slice of map to csv file.
// Play: todo
// Play: https://go.dev/play/p/umAIomZFV1c
// filepath: Path to the CSV file.
// records: Slice of maps to be written. the value of map should be basic type.
// the maps will be sorted by key in alphabeta order, then be written into csv file.

View File

@@ -3,6 +3,8 @@ package fileutil
import (
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/duke-git/lancet/v2/internal"
@@ -534,7 +536,7 @@ func TestReadlineFile(t *testing.T) {
if !ok {
t.Fail()
}
if err = reader.Seek(offset); err != nil {
if err = reader.SeekOffset(offset); err != nil {
t.Fail()
}
lineRead, err := reader.ReadLine()
@@ -544,3 +546,23 @@ func TestReadlineFile(t *testing.T) {
internal.NewAssert(t, "TestReadlineFile").Equal(line, lineRead)
}
}
func TestCopyDir(t *testing.T) {
assert := internal.NewAssert(t, "TestCopyDir")
src := "./testdata"
dest := "./testdata_copy"
err := CopyDir(src, dest)
assert.IsNil(err)
assert.Equal(true, IsExist(dest))
filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
destPath := strings.Replace(path, src, dest, 1)
assert.Equal(true, IsExist(destPath))
return nil
})
os.RemoveAll(dest)
}

1
fileutil/testdata/test01/demo2.csv vendored Normal file
View File

@@ -0,0 +1 @@
makj1
1 makj1

91
function/predicate.go Normal file
View File

@@ -0,0 +1,91 @@
package function
// And returns a composed predicate that represents the logical AND of a list of predicates.
// It evaluates to true only if all predicates evaluate to true for the given value.
func And[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
}
return func(value T) bool {
for _, predicate := range predicates {
if !predicate(value) {
return false // Short-circuit on the first false predicate
}
}
return true // True if all predicates are true
}
}
// Nand returns a composed predicate that represents the logical NAND of a list of predicates.
// It evaluates to true only if all predicates evaluate to false for the given value.
func Nand[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
}
return func(value T) bool {
for _, predicate := range predicates {
if predicate(value) {
return false // Short-circuit on the first true predicate
}
}
return true // True if all predicates are false
}
}
// Negate returns a predicate that represents the logical negation of this predicate.
func Negate[T any](predicate func(T) bool) func(T) bool {
return func(value T) bool {
return !predicate(value)
}
}
// Or returns a composed predicate that represents the logical OR of a list of predicates.
// It evaluates to true if at least one of the predicates evaluates to true for the given value.
func Or[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
}
return func(value T) bool {
for _, predicate := range predicates {
if predicate(value) {
return true // Short-circuit on the first true predicate
}
}
return false // False if all predicates are false
}
}
// Nor returns a composed predicate that represents the logical NOR of a list of predicates.
// It evaluates to true only if all predicates evaluate to false for the given value.
func Nor[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
}
return func(value T) bool {
for _, predicate := range predicates {
if predicate(value) {
return false // If any predicate evaluates to true, the NOR result is false
}
}
return true // Only returns true if all predicates evaluate to false
}
}
// Xnor returns a composed predicate that represents the logical XNOR of a list of predicates.
// It evaluates to true only if all predicates evaluate to true or false for the given value.
func Xnor[T any](predicates ...func(T) bool) func(T) bool {
if len(predicates) < 2 {
panic("programming error: predicates count must be at least 2")
}
return func(value T) bool {
trueCount := 0
for _, predicate := range predicates {
if predicate(value) {
trueCount++
}
}
// XNOR is true if either all predicates are true or all are false
// This is the same as saying trueCount is 0 (all false) or trueCount is len(predicates) (all true)
return trueCount == 0 || trueCount == len(predicates)
}
}

View File

@@ -0,0 +1,128 @@
package function
import (
"fmt"
"strings"
)
func ExampleNegate() {
// Define some simple predicates for demonstration
isUpperCase := func(s string) bool {
return strings.ToUpper(s) == s
}
isLowerCase := func(s string) bool {
return strings.ToLower(s) == s
}
isMixedCase := Negate(Or(isUpperCase, isLowerCase))
fmt.Println(isMixedCase("ABC"))
fmt.Println(isMixedCase("AbC"))
// Output:
// false
// true
}
func ExampleOr() {
containsDigitOrSpecialChar := Or(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return strings.ContainsAny(s, "!@#$%") },
)
fmt.Println(containsDigitOrSpecialChar("hello!"))
fmt.Println(containsDigitOrSpecialChar("hello"))
// Output:
// true
// false
}
func ExampleAnd() {
isNumericAndLength5 := And(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(isNumericAndLength5("12345"))
fmt.Println(isNumericAndLength5("1234"))
fmt.Println(isNumericAndLength5("abcde"))
// Output:
// true
// false
// false
}
func ExampleNand() {
isNumericAndLength5 := Nand(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(isNumericAndLength5("12345"))
fmt.Println(isNumericAndLength5("1234"))
fmt.Println(isNumericAndLength5("abcdef"))
// Output:
// false
// false
// true
}
func ExampleNor() {
match := Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(match("dbcdckkeee"))
match = Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
fmt.Println(match("0123456789"))
// Output:
// true
// false
}
func ExampleXnor() {
isEven := func(i int) bool { return i%2 == 0 }
isPositive := func(i int) bool { return i > 0 }
match := Xnor(isEven, isPositive)
fmt.Println(match(2))
fmt.Println(match(-3))
fmt.Println(match(3))
// Output:
// true
// true
// false
}
// func ExamplePredicatesMix() {
// a := Or(
// func(s string) bool { return strings.ContainsAny(s, "0123456789") },
// func(s string) bool { return strings.ContainsAny(s, "!") },
// )
// b := And(
// func(s string) bool { return strings.ContainsAny(s, "hello") },
// func(s string) bool { return strings.ContainsAny(s, "!") },
// )
// c := Negate(And(a, b))
// fmt.Println(c("hello!"))
// c = Nor(a, b)
// fmt.Println(c("hello!"))
// // Output:
// // false
// // false
// }

134
function/predicate_test.go Normal file
View File

@@ -0,0 +1,134 @@
package function
import (
"strings"
"testing"
"github.com/duke-git/lancet/v2/internal"
)
func TestPredicatesNegatePure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesNegatePure")
// Define some simple predicates for demonstration
isUpperCase := func(s string) bool {
return strings.ToUpper(s) == s
}
isLowerCase := func(s string) bool {
return strings.ToLower(s) == s
}
isMixedCase := Negate(Or(isUpperCase, isLowerCase))
assert.ShouldBeFalse(isMixedCase("ABC"))
assert.ShouldBeTrue(isMixedCase("AbC"))
}
func TestPredicatesOrPure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesOrPure")
containsDigitOrSpecialChar := Or(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return strings.ContainsAny(s, "!@#$%") },
)
assert.ShouldBeTrue(containsDigitOrSpecialChar("hello!"))
assert.ShouldBeFalse(containsDigitOrSpecialChar("hello"))
}
func TestPredicatesAndPure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesAndPure")
isNumericAndLength5 := And(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
assert.ShouldBeTrue(isNumericAndLength5("12345"))
assert.ShouldBeFalse(isNumericAndLength5("1234"))
assert.ShouldBeFalse(isNumericAndLength5("abcde"))
}
func TestPredicatesNandPure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesNandPure")
isNumericAndLength5 := Nand(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
assert.ShouldBeFalse(isNumericAndLength5("12345"))
assert.ShouldBeFalse(isNumericAndLength5("1234"))
assert.ShouldBeTrue(isNumericAndLength5("abcdef"))
}
func TestPredicatesNorPure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesNorPure")
match := Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
assert.ShouldBeTrue(match("dbcdckkeee"))
match = Nor(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return len(s) == 5 },
)
assert.ShouldBeFalse(match("0123456789"))
}
func TestPredicatesXnorPure(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesXnorPure")
isEven := func(i int) bool { return i%2 == 0 }
isPositive := func(i int) bool { return i > 0 }
match := Xnor(isEven, isPositive)
assert.ShouldBeTrue(match(2))
assert.ShouldBeTrue(match(-3))
assert.ShouldBeFalse(match(3))
}
func TestPredicatesMix(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestPredicatesMix")
a := Or(
func(s string) bool { return strings.ContainsAny(s, "0123456789") },
func(s string) bool { return strings.ContainsAny(s, "!") },
)
b := And(
func(s string) bool { return strings.ContainsAny(s, "hello") },
func(s string) bool { return strings.ContainsAny(s, "!") },
)
c := Negate(And(a, b))
assert.ShouldBeFalse(c("hello!"))
k := Nor(a, b)
assert.ShouldBeFalse(k("hello!"))
o := Xnor(a, b)
assert.ShouldBeTrue(o("hello!"))
p := Nand(c, k)
assert.ShouldBeTrue(p("hello!"))
}

View File

@@ -37,6 +37,20 @@ func (a *Assert) Equal(expected, actual any) {
}
}
// ShouldBeFalse check if expected is false
func (a *Assert) ShouldBeFalse(actual any) {
if compare(false, actual) != compareEqual {
makeTestFailed(a.T, a.CaseName, false, actual)
}
}
// ShouldBeTrue check if expected is true
func (a *Assert) ShouldBeTrue(actual any) {
if compare(true, actual) != compareEqual {
makeTestFailed(a.T, a.CaseName, true, actual)
}
}
// NotEqual check if expected is not equal with actual
func (a *Assert) NotEqual(expected, actual any) {
if compare(expected, actual) == compareEqual {

View File

@@ -6,10 +6,11 @@ import (
func TestAssert(t *testing.T) {
assert := NewAssert(t, "TestAssert")
assert.Equal(0, 0)
assert.NotEqual(1, 0)
assert.NotEqual("1", 1)
var uInt1 uint
var uInt2 uint
var uInt8 uint8
@@ -47,4 +48,11 @@ func TestAssert(t *testing.T) {
assert.IsNil(nil)
assert.IsNotNil("abc")
var valA int = 1
var valB int64 = 1
assert.NotEqual(valA, valB)
assert.EqualValues(valA, valB)
assert.ShouldBeFalse(false)
assert.ShouldBeTrue(true)
}

View File

@@ -5,6 +5,7 @@
package maputil
import (
"fmt"
"reflect"
"github.com/duke-git/lancet/v2/slice"
@@ -303,3 +304,83 @@ func HasKey[K comparable, V any](m map[K]V, key K) bool {
_, haskey := m[key]
return haskey
}
// MapToStruct converts map to struct
// Play: todo
func MapToStruct(m map[string]any, structObj any) error {
for k, v := range m {
err := setStructField(structObj, k, v)
if err != nil {
return err
}
}
return nil
}
func setStructField(structObj any, fieldName string, fieldValue any) error {
structVal := reflect.ValueOf(structObj).Elem()
fName := getFieldNameByJsonTag(structObj, fieldName)
if fName == "" {
return fmt.Errorf("Struct field json tag don't match map key : %s in obj", fieldName)
}
fieldVal := structVal.FieldByName(fName)
if !fieldVal.IsValid() {
return fmt.Errorf("No such field: %s in obj", fieldName)
}
if !fieldVal.CanSet() {
return fmt.Errorf("Cannot set %s field value", fieldName)
}
val := reflect.ValueOf(fieldValue)
if fieldVal.Type() != val.Type() {
if val.CanConvert(fieldVal.Type()) {
fieldVal.Set(val.Convert(fieldVal.Type()))
return nil
}
if m, ok := fieldValue.(map[string]any); ok {
if fieldVal.Kind() == reflect.Struct {
return MapToStruct(m, fieldVal.Addr().Interface())
}
if fieldVal.Kind() == reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
if fieldVal.IsNil() {
fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
}
return MapToStruct(m, fieldVal.Interface())
}
}
return fmt.Errorf("Map value type don't match struct field type")
}
fieldVal.Set(val)
return nil
}
func getFieldNameByJsonTag(structObj any, jsonTag string) string {
s := reflect.TypeOf(structObj).Elem()
for i := 0; i < s.NumField(); i++ {
field := s.Field(i)
tag := field.Tag
name := tag.Get("json")
if name == jsonTag {
return field.Name
}
}
return ""
}

View File

@@ -472,3 +472,42 @@ func TestHasKey(t *testing.T) {
assert.Equal(true, HasKey(m, "a"))
assert.Equal(false, HasKey(m, "c"))
}
func TestMapToStruct(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestMapToStruct")
type (
Person struct {
Name string `json:"name"`
Age int `json:"age"`
Phone string `json:"phone"`
Addr *Address `json:"address"`
}
Address struct {
Street string `json:"street"`
Number int `json:"number"`
}
)
m := map[string]interface{}{
"name": "Nothin",
"age": 28,
"phone": "123456789",
"address": map[string]interface{}{
"street": "test",
"number": 1,
},
}
var p Person
err := MapToStruct(m, &p)
assert.IsNil(err)
assert.Equal(m["name"], p.Name)
assert.Equal(m["age"], p.Age)
assert.Equal(m["phone"], p.Phone)
assert.Equal("test", p.Addr.Street)
assert.Equal(1, p.Addr.Number)
}

View File

@@ -62,18 +62,25 @@ var _ = func() struct{} {
*/
// Play: https://go.dev/play/p/4K7KBEPgS5M
func MapTo(src any, dst any) error {
dstRef := reflect.ValueOf(dst)
if dstRef.Kind() != reflect.Ptr {
return fmt.Errorf("dst is not ptr")
}
dstElem := dstRef.Type().Elem()
if dstElem.Kind() == reflect.Struct {
srcMap := src.(map[string]interface{})
return MapToStruct(srcMap, dst)
}
dstRef = reflect.Indirect(dstRef)
srcRef := reflect.ValueOf(src)
if srcRef.Kind() == reflect.Ptr || srcRef.Kind() == reflect.Interface {
srcRef = srcRef.Elem()
}
if f, ok := mapHandlers[srcRef.Kind()]; ok {
return f(srcRef, dstRef)
}
@@ -121,7 +128,7 @@ func convertSlice(src reflect.Value, dst reflect.Value) error {
func convertMap(src reflect.Value, dst reflect.Value) error {
if src.Kind() != reflect.Map || dst.Kind() != reflect.Struct {
if src.Kind() == reflect.Interface {
if src.Kind() == reflect.Interface && dst.IsValid() {
return convertMap(src.Elem(), dst)
} else {
return fmt.Errorf("src or dst type error,%s,%s", src.Type().String(), dst.Type().String())
@@ -129,7 +136,9 @@ func convertMap(src reflect.Value, dst reflect.Value) error {
}
dstType := dst.Type()
num := dstType.NumField()
exist := map[string]int{}
for i := 0; i < num; i++ {
k := dstType.Field(i).Tag.Get("json")
if k == "" {
@@ -138,7 +147,6 @@ func convertMap(src reflect.Value, dst reflect.Value) error {
if strings.Contains(k, ",") {
taglist := strings.Split(k, ",")
if taglist[0] == "" {
k = dstType.Field(i).Name
} else {
k = taglist[0]
@@ -150,9 +158,11 @@ func convertMap(src reflect.Value, dst reflect.Value) error {
}
keys := src.MapKeys()
for _, key := range keys {
if index, ok := exist[key.String()]; ok {
v := dst.Field(index)
if v.Kind() == reflect.Struct {
err := convertMap(src.MapIndex(key), v)
if err != nil {

View File

@@ -8,10 +8,10 @@ import (
type (
Person struct {
Name string `json:"name"`
Age int `json:"age"`
Phone string `json:"phone"`
Addr Address `json:"address"`
Name string `json:"name"`
Age int `json:"age"`
Phone string `json:"phone"`
Address *Address `json:"address"`
}
Address struct {
@@ -38,11 +38,12 @@ func TestStructType(t *testing.T) {
var p Person
err := MapTo(src, &p)
assert.IsNil(err)
assert.Equal(src["name"], p.Name)
assert.Equal(src["age"], p.Age)
assert.Equal(src["phone"], p.Phone)
assert.Equal("test", p.Addr.Street)
assert.Equal(1, p.Addr.Number)
assert.Equal("test", p.Address.Street)
assert.Equal(1, p.Address.Number)
}
func TestBaseType(t *testing.T) {

View File

@@ -8,6 +8,8 @@ import (
"context"
"errors"
"fmt"
"math"
"math/rand"
"reflect"
"runtime"
"strings"
@@ -18,14 +20,14 @@ const (
// DefaultRetryTimes times of retry
DefaultRetryTimes = 5
// DefaultRetryDuration time duration of two retries
DefaultRetryDuration = time.Second * 3
DefaultRetryLinearInterval = time.Second * 3
)
// RetryConfig is config for retry
type RetryConfig struct {
context context.Context
retryTimes uint
retryDuration time.Duration
context context.Context
retryTimes uint
backoffStrategy BackoffStrategy
}
// RetryFunc is function that retry executes
@@ -42,11 +44,59 @@ func RetryTimes(n uint) Option {
}
}
// RetryDuration set duration of retries.
// Play: https://go.dev/play/p/nk2XRmagfVF
func RetryDuration(d time.Duration) Option {
// RetryWithCustomBackoff set abitary custom backoff strategy
// todo: Add playground link
func RetryWithCustomBackoff(backoffStrategy BackoffStrategy) Option {
if backoffStrategy == nil {
panic("programming error: backoffStrategy must be not nil")
}
return func(rc *RetryConfig) {
rc.retryDuration = d
rc.backoffStrategy = backoffStrategy
}
}
// RetryWithLinearBackoff set linear strategy backoff
// todo: Add playground link
func RetryWithLinearBackoff(interval time.Duration) Option {
if interval <= 0 {
panic("programming error: retry interval should not be lower or equal to 0")
}
return func(rc *RetryConfig) {
rc.backoffStrategy = &linear{
interval: interval,
}
}
}
// RetryWithExponentialWithJitterBackoff set exponential strategy backoff
// todo: Add playground link
func RetryWithExponentialWithJitterBackoff(interval time.Duration, base uint64, maxJitter time.Duration) Option {
if interval <= 0 {
panic("programming error: retry interval should not be lower or equal to 0")
}
if maxJitter < 0 {
panic("programming error: retry maxJitter should not be lower to 0")
}
if base%2 == 0 {
return func(rc *RetryConfig) {
rc.backoffStrategy = &shiftExponentialWithJitter{
interval: interval,
maxJitter: maxJitter,
shifter: uint64(math.Log2(float64(base))),
}
}
}
return func(rc *RetryConfig) {
rc.backoffStrategy = &exponentialWithJitter{
interval: interval,
base: time.Duration(base),
maxJitter: maxJitter,
}
}
}
@@ -63,21 +113,26 @@ func Context(ctx context.Context) Option {
// Play: https://go.dev/play/p/nk2XRmagfVF
func Retry(retryFunc RetryFunc, opts ...Option) error {
config := &RetryConfig{
retryTimes: DefaultRetryTimes,
retryDuration: DefaultRetryDuration,
context: context.TODO(),
retryTimes: DefaultRetryTimes,
context: context.TODO(),
}
for _, opt := range opts {
opt(config)
}
if config.backoffStrategy == nil {
config.backoffStrategy = &linear{
interval: DefaultRetryLinearInterval,
}
}
var i uint
for i < config.retryTimes {
err := retryFunc()
if err != nil {
select {
case <-time.After(config.retryDuration):
case <-time.After(config.backoffStrategy.CalculateInterval()):
case <-config.context.Done():
return errors.New("retry is cancelled")
}
@@ -93,3 +148,58 @@ func Retry(retryFunc RetryFunc, opts ...Option) error {
return fmt.Errorf("function %s run failed after %d times retry", funcName, i)
}
// BackoffStrategy is an interface that defines a method for calculating backoff intervals.
type BackoffStrategy interface {
// CalculateInterval returns the time.Duration after which the next retry attempt should be made.
CalculateInterval() time.Duration
}
// linear is a struct that implements the BackoffStrategy interface using a linear backoff strategy.
type linear struct {
// interval specifies the fixed duration to wait between retry attempts.
interval time.Duration
}
// CalculateInterval calculates the next interval returns a constant.
func (l *linear) CalculateInterval() time.Duration {
return l.interval
}
// exponentialWithJitter is a struct that implements the BackoffStrategy interface using a exponential backoff strategy.
type exponentialWithJitter struct {
base time.Duration // base is the multiplier for the exponential backoff.
interval time.Duration // interval is the current backoff interval, which will be adjusted over time.
maxJitter time.Duration // maxJitter is the maximum amount of jitter to apply to the backoff interval.
}
// CalculateInterval calculates the next backoff interval with jitter and updates the interval.
func (e *exponentialWithJitter) CalculateInterval() time.Duration {
current := e.interval
e.interval = e.interval * e.base
return current + jitter(e.maxJitter)
}
// shiftExponentialWithJitter is a struct that implements the BackoffStrategy interface using a exponential backoff strategy.
type shiftExponentialWithJitter struct {
interval time.Duration // interval is the current backoff interval, which will be adjusted over time.
maxJitter time.Duration // maxJitter is the maximum amount of jitter to apply to the backoff interval.
shifter uint64 // shift by n faster than multiplication
}
// CalculateInterval calculates the next backoff interval with jitter and updates the interval.
// Uses shift instead of multiplication
func (e *shiftExponentialWithJitter) CalculateInterval() time.Duration {
current := e.interval
e.interval = e.interval << e.shifter
return current + jitter(e.maxJitter)
}
// Jitter adds a random duration, up to maxJitter,
// to the current interval to introduce randomness and avoid synchronized patterns in retry behavior
func jitter(maxJitter time.Duration) time.Duration {
if maxJitter == 0 {
return 0
}
return time.Duration(rand.Int63n(int64(maxJitter)) + 1)
}

View File

@@ -20,7 +20,7 @@ func ExampleContext() {
}
Retry(increaseNumber,
RetryDuration(time.Microsecond*50),
RetryWithLinearBackoff(time.Microsecond*50),
Context(ctx),
)
@@ -30,7 +30,7 @@ func ExampleContext() {
// 4
}
func ExampleRetryDuration() {
func ExampleRetryWithLinearBackoff() {
number := 0
increaseNumber := func() error {
number++
@@ -40,7 +40,57 @@ func ExampleRetryDuration() {
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50))
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
if err != nil {
return
}
fmt.Println(number)
// Output:
// 3
}
type ExampleCustomBackoffStrategy struct {
interval time.Duration
}
func (c *ExampleCustomBackoffStrategy) CalculateInterval() time.Duration {
return c.interval + 1
}
func ExampleRetryWithCustomBackoff() {
number := 0
increaseNumber := func() error {
number++
if number == 3 {
return nil
}
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryWithCustomBackoff(&ExampleCustomBackoffStrategy{interval: time.Microsecond * 50}))
if err != nil {
return
}
fmt.Println(number)
// Output:
// 3
}
func ExampleRetryWithExponentialWithJitterBackoff() {
number := 0
increaseNumber := func() error {
number++
if number == 3 {
return nil
}
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryWithExponentialWithJitterBackoff(time.Microsecond*50, 2, time.Microsecond*25))
if err != nil {
return
}
@@ -81,7 +131,7 @@ func ExampleRetry() {
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50))
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
if err != nil {
return
}

View File

@@ -20,12 +20,86 @@ func TestRetryFailed(t *testing.T) {
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50))
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
assert.IsNotNil(err)
assert.Equal(DefaultRetryTimes, number)
}
func TestRetryShiftExponentialWithJitterFailed(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryShiftExponentialWithJitterFailed")
var number int
increaseNumber := func() error {
number++
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryWithExponentialWithJitterBackoff(time.Microsecond*50, 2, time.Microsecond*25))
assert.IsNotNil(err)
assert.Equal(DefaultRetryTimes, number)
}
func TestRetryExponentialWithJitterFailed(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryExponentialWithJitterFailed")
var number int
increaseNumber := func() error {
number++
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryWithExponentialWithJitterBackoff(time.Microsecond*50, 3, time.Microsecond*25))
assert.IsNotNil(err)
assert.Equal(DefaultRetryTimes, number)
}
func TestRetryWithExponentialSucceeded(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryWithExponentialSucceeded")
var number int
increaseNumber := func() error {
number++
if number == DefaultRetryTimes {
return nil
}
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryWithExponentialWithJitterBackoff(time.Microsecond*50, 3, time.Microsecond*25))
assert.IsNil(err)
assert.Equal(DefaultRetryTimes, number)
}
func TestRetryWithExponentialShiftSucceeded(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryWithExponentialShiftSucceeded")
var number int
increaseNumber := func() error {
number++
if number == DefaultRetryTimes {
return nil
}
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryWithExponentialWithJitterBackoff(time.Microsecond*50, 4, time.Microsecond*25))
assert.IsNil(err)
assert.Equal(DefaultRetryTimes, number)
}
func TestRetrySucceeded(t *testing.T) {
t.Parallel()
@@ -40,12 +114,108 @@ func TestRetrySucceeded(t *testing.T) {
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50))
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
assert.IsNil(err)
assert.Equal(DefaultRetryTimes, number)
}
func TestRetryOneShotSucceeded(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryOneShotSucceeded")
var number int
increaseNumber := func() error {
number++
return nil
}
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50))
assert.IsNil(err)
assert.Equal(1, number)
}
func TestRetryWitCustomBackoffOneShotSucceeded(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryWitCustomBackoffOneShotSucceeded")
var number int
increaseNumber := func() error {
number++
if number == DefaultRetryTimes {
return nil
}
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryWithCustomBackoff(&TestCustomBackoffStrategy{interval: time.Microsecond * 50}))
assert.IsNil(err)
assert.Equal(5, number)
}
type TestCustomBackoffStrategy struct {
interval time.Duration
}
func (c *TestCustomBackoffStrategy) CalculateInterval() time.Duration {
return c.interval + 1
}
func TestRetryWithExponentialWithJitterBackoffShiftOneShotSucceeded(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryWithExponentialWithJitterBackoffShiftOneShotSucceeded")
var number int
increaseNumber := func() error {
number++
return nil
}
err := Retry(increaseNumber, RetryWithExponentialWithJitterBackoff(time.Microsecond*50, 2, time.Microsecond*25))
assert.IsNil(err)
assert.Equal(1, number)
}
func TestRetryWithExponentialWithJitterBackoffOneShotSucceeded(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryWithExponentialWithJitterBackoffOneShotSucceeded")
var number int
increaseNumber := func() error {
number++
return nil
}
err := Retry(increaseNumber, RetryWithExponentialWithJitterBackoff(time.Microsecond*50, 3, time.Microsecond*25))
assert.IsNil(err)
assert.Equal(1, number)
}
func TestRetryWithExponentialWithJitterBackoffNoJitterOneShotSucceeded(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRetryWithExponentialWithJitterBackoffNoJitterOneShotSucceeded")
var number int
increaseNumber := func() error {
number++
return nil
}
err := Retry(increaseNumber, RetryWithExponentialWithJitterBackoff(time.Microsecond*50, 3, 0))
assert.IsNil(err)
assert.Equal(1, number)
}
func TestSetRetryTimes(t *testing.T) {
t.Parallel()
@@ -57,7 +227,7 @@ func TestSetRetryTimes(t *testing.T) {
return errors.New("error occurs")
}
err := Retry(increaseNumber, RetryDuration(time.Microsecond*50), RetryTimes(3))
err := Retry(increaseNumber, RetryWithLinearBackoff(time.Microsecond*50), RetryTimes(3))
assert.IsNotNil(err)
assert.Equal(3, number)
@@ -79,7 +249,7 @@ func TestCancelRetry(t *testing.T) {
}
err := Retry(increaseNumber,
RetryDuration(time.Microsecond*50),
RetryWithLinearBackoff(time.Microsecond*50),
Context(ctx),
)

View File

@@ -619,7 +619,7 @@ func IntSlice(slice any) []int {
}
// DeleteAt delete the element of slice at index.
// Play: https://go.dev/play/p/pJ-d6MUWcvK
// Play: https://go.dev/play/p/800B1dPBYyd
func DeleteAt[T any](slice []T, index int) []T {
if index >= len(slice) {
index = len(slice) - 1
@@ -633,7 +633,7 @@ func DeleteAt[T any](slice []T, index int) []T {
}
// DeleteRange delete the element of slice from start index to end indexexclude).
// Play: todo
// Play: https://go.dev/play/p/945HwiNrnle
func DeleteRange[T any](slice []T, start, end int) []T {
result := make([]T, 0, len(slice)-(end-start))

View File

@@ -47,19 +47,19 @@ import (
// Concat(streams ...StreamI[T]) StreamI[T]
// }
type stream[T any] struct {
type Stream[T any] struct {
source []T
}
// Of creates a stream whose elements are the specified values.
// Play: https://go.dev/play/p/jI6_iZZuVFE
func Of[T any](elems ...T) stream[T] {
func Of[T any](elems ...T) Stream[T] {
return FromSlice(elems)
}
// Generate stream where each element is generated by the provided generater function
// Play: https://go.dev/play/p/rkOWL1yA3j9
func Generate[T any](generator func() func() (item T, ok bool)) stream[T] {
func Generate[T any](generator func() func() (item T, ok bool)) Stream[T] {
source := make([]T, 0)
var zeroValue T
@@ -76,13 +76,13 @@ func Generate[T any](generator func() func() (item T, ok bool)) stream[T] {
// FromSlice creates stream from slice.
// Play: https://go.dev/play/p/wywTO0XZtI4
func FromSlice[T any](source []T) stream[T] {
return stream[T]{source: source}
func FromSlice[T any](source []T) Stream[T] {
return Stream[T]{source: source}
}
// FromChannel creates stream from channel.
// Play: https://go.dev/play/p/9TZYugGMhXZ
func FromChannel[T any](source <-chan T) stream[T] {
func FromChannel[T any](source <-chan T) Stream[T] {
s := make([]T, 0)
for v := range source {
@@ -94,7 +94,7 @@ func FromChannel[T any](source <-chan T) stream[T] {
// FromRange creates a number stream from start to end. both start and end are included. [start, end]
// Play: https://go.dev/play/p/9Ex1-zcg-B-
func FromRange[T constraints.Integer | constraints.Float](start, end, step T) stream[T] {
func FromRange[T constraints.Integer | constraints.Float](start, end, step T) Stream[T] {
if end < start {
panic("stream.FromRange: param start should be before param end")
} else if step <= 0 {
@@ -113,7 +113,7 @@ func FromRange[T constraints.Integer | constraints.Float](start, end, step T) st
// Concat creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream.
// Play: https://go.dev/play/p/HM4OlYk_OUC
func Concat[T any](a, b stream[T]) stream[T] {
func Concat[T any](a, b Stream[T]) Stream[T] {
source := make([]T, 0)
source = append(source, a.source...)
@@ -124,7 +124,7 @@ func Concat[T any](a, b stream[T]) stream[T] {
// Distinct returns a stream that removes the duplicated items.
// Play: https://go.dev/play/p/eGkOSrm64cB
func (s stream[T]) Distinct() stream[T] {
func (s Stream[T]) Distinct() Stream[T] {
source := make([]T, 0)
distinct := map[string]bool{}
@@ -153,7 +153,7 @@ func hashKey(data any) string {
// Filter returns a stream consisting of the elements of this stream that match the given predicate.
// Play: https://go.dev/play/p/MFlSANo-buc
func (s stream[T]) Filter(predicate func(item T) bool) stream[T] {
func (s Stream[T]) Filter(predicate func(item T) bool) Stream[T] {
source := make([]T, 0)
for _, v := range s.source {
@@ -167,7 +167,7 @@ func (s stream[T]) Filter(predicate func(item T) bool) stream[T] {
// Map returns a stream consisting of the elements of this stream that apply the given function to elements of stream.
// Play: https://go.dev/play/p/OtNQUImdYko
func (s stream[T]) Map(mapper func(item T) T) stream[T] {
func (s Stream[T]) Map(mapper func(item T) T) Stream[T] {
source := make([]T, s.Count())
for i, v := range s.source {
@@ -179,7 +179,7 @@ func (s stream[T]) Map(mapper func(item T) T) stream[T] {
// Peek returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream.
// Play: https://go.dev/play/p/u1VNzHs6cb2
func (s stream[T]) Peek(consumer func(item T)) stream[T] {
func (s Stream[T]) Peek(consumer func(item T)) Stream[T] {
for _, v := range s.source {
consumer(v)
}
@@ -190,7 +190,7 @@ func (s stream[T]) Peek(consumer func(item T)) stream[T] {
// Skip returns a stream consisting of the remaining elements of this stream after discarding the first n elements of the stream.
// If this stream contains fewer than n elements then an empty stream will be returned.
// Play: https://go.dev/play/p/fNdHbqjahum
func (s stream[T]) Skip(n int) stream[T] {
func (s Stream[T]) Skip(n int) Stream[T] {
if n <= 0 {
return s
}
@@ -211,7 +211,7 @@ func (s stream[T]) Skip(n int) stream[T] {
// Limit returns a stream consisting of the elements of this stream, truncated to be no longer than maxSize in length.
// Play: https://go.dev/play/p/qsO4aniDcGf
func (s stream[T]) Limit(maxSize int) stream[T] {
func (s Stream[T]) Limit(maxSize int) Stream[T] {
if s.source == nil {
return s
}
@@ -231,7 +231,7 @@ func (s stream[T]) Limit(maxSize int) stream[T] {
// AllMatch returns whether all elements of this stream match the provided predicate.
// Play: https://go.dev/play/p/V5TBpVRs-Cx
func (s stream[T]) AllMatch(predicate func(item T) bool) bool {
func (s Stream[T]) AllMatch(predicate func(item T) bool) bool {
for _, v := range s.source {
if !predicate(v) {
return false
@@ -243,7 +243,7 @@ func (s stream[T]) AllMatch(predicate func(item T) bool) bool {
// AnyMatch returns whether any elements of this stream match the provided predicate.
// Play: https://go.dev/play/p/PTCnWn4OxSn
func (s stream[T]) AnyMatch(predicate func(item T) bool) bool {
func (s Stream[T]) AnyMatch(predicate func(item T) bool) bool {
for _, v := range s.source {
if predicate(v) {
return true
@@ -255,13 +255,13 @@ func (s stream[T]) AnyMatch(predicate func(item T) bool) bool {
// NoneMatch returns whether no elements of this stream match the provided predicate.
// Play: https://go.dev/play/p/iWS64pL1oo3
func (s stream[T]) NoneMatch(predicate func(item T) bool) bool {
func (s Stream[T]) NoneMatch(predicate func(item T) bool) bool {
return !s.AnyMatch(predicate)
}
// ForEach performs an action for each element of this stream.
// Play: https://go.dev/play/p/Dsm0fPqcidk
func (s stream[T]) ForEach(action func(item T)) {
func (s Stream[T]) ForEach(action func(item T)) {
for _, v := range s.source {
action(v)
}
@@ -269,7 +269,7 @@ func (s stream[T]) ForEach(action func(item T)) {
// Reduce performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any.
// Play: https://go.dev/play/p/6uzZjq_DJLU
func (s stream[T]) Reduce(initial T, accumulator func(a, b T) T) T {
func (s Stream[T]) Reduce(initial T, accumulator func(a, b T) T) T {
for _, v := range s.source {
initial = accumulator(initial, v)
}
@@ -279,13 +279,13 @@ func (s stream[T]) Reduce(initial T, accumulator func(a, b T) T) T {
// Count returns the count of elements in the stream.
// Play: https://go.dev/play/p/r3koY6y_Xo-
func (s stream[T]) Count() int {
func (s Stream[T]) Count() int {
return len(s.source)
}
// FindFirst returns the first element of this stream and true, or zero value and false if the stream is empty.
// Play: https://go.dev/play/p/9xEf0-6C1e3
func (s stream[T]) FindFirst() (T, bool) {
func (s Stream[T]) FindFirst() (T, bool) {
var result T
if s.source == nil || len(s.source) == 0 {
@@ -297,7 +297,7 @@ func (s stream[T]) FindFirst() (T, bool) {
// FindLast returns the last element of this stream and true, or zero value and false if the stream is empty.
// Play: https://go.dev/play/p/WZD2rDAW-2h
func (s stream[T]) FindLast() (T, bool) {
func (s Stream[T]) FindLast() (T, bool) {
var result T
if s.source == nil || len(s.source) == 0 {
@@ -309,7 +309,7 @@ func (s stream[T]) FindLast() (T, bool) {
// Reverse returns a stream whose elements are reverse order of given stream.
// Play: https://go.dev/play/p/A8_zkJnLHm4
func (s stream[T]) Reverse() stream[T] {
func (s Stream[T]) Reverse() Stream[T] {
l := len(s.source)
source := make([]T, l)
@@ -321,7 +321,7 @@ func (s stream[T]) Reverse() stream[T] {
// Range returns a stream whose elements are in the range from start(included) to end(excluded) original stream.
// Play: https://go.dev/play/p/indZY5V2f4j
func (s stream[T]) Range(start, end int) stream[T] {
func (s Stream[T]) Range(start, end int) Stream[T] {
if start < 0 {
start = 0
}
@@ -347,7 +347,7 @@ func (s stream[T]) Range(start, end int) stream[T] {
// Sorted returns a stream consisting of the elements of this stream, sorted according to the provided less function.
// Play: https://go.dev/play/p/XXtng5uonFj
func (s stream[T]) Sorted(less func(a, b T) bool) stream[T] {
func (s Stream[T]) Sorted(less func(a, b T) bool) Stream[T] {
source := []T{}
source = append(source, s.source...)
@@ -359,7 +359,7 @@ func (s stream[T]) Sorted(less func(a, b T) bool) stream[T] {
// Max returns the maximum element of this stream according to the provided less function.
// less: a > b
// Play: https://go.dev/play/p/fm-1KOPtGzn
func (s stream[T]) Max(less func(a, b T) bool) (T, bool) {
func (s Stream[T]) Max(less func(a, b T) bool) (T, bool) {
var max T
if len(s.source) == 0 {
@@ -377,7 +377,7 @@ func (s stream[T]) Max(less func(a, b T) bool) (T, bool) {
// Min returns the minimum element of this stream according to the provided less function.
// less: a < b
// Play: https://go.dev/play/p/vZfIDgGNRe_0
func (s stream[T]) Min(less func(a, b T) bool) (T, bool) {
func (s Stream[T]) Min(less func(a, b T) bool) (T, bool) {
var min T
if len(s.source) == 0 {
@@ -395,6 +395,6 @@ func (s stream[T]) Min(less func(a, b T) bool) (T, bool) {
// ToSlice return the elements in the stream.
// Play: https://go.dev/play/p/jI6_iZZuVFE
func (s stream[T]) ToSlice() []T {
func (s Stream[T]) ToSlice() []T {
return s.source
}

View File

@@ -121,40 +121,48 @@ func UpperSnakeCase(s string) string {
// Before returns the substring of the source string up to the first occurrence of the specified string.
// Play: https://go.dev/play/p/JAWTZDS4F5w
func Before(s, char string) string {
if s == "" || char == "" {
i := strings.Index(s, char)
if s == "" || char == "" || i == -1 {
return s
}
i := strings.Index(s, char)
return s[0:i]
}
// BeforeLast returns the substring of the source string up to the last occurrence of the specified string.
// Play: https://go.dev/play/p/pJfXXAoG_Te
func BeforeLast(s, char string) string {
if s == "" || char == "" {
i := strings.LastIndex(s, char)
if s == "" || char == "" || i == -1 {
return s
}
i := strings.LastIndex(s, char)
return s[0:i]
}
// After returns the substring after the first occurrence of a specified string in the source string.
// Play: https://go.dev/play/p/RbCOQqCDA7m
func After(s, char string) string {
if s == "" || char == "" {
i := strings.Index(s, char)
if s == "" || char == "" || i == -1 {
return s
}
i := strings.Index(s, char)
return s[i+len(char):]
}
// AfterLast returns the substring after the last occurrence of a specified string in the source string.
// Play: https://go.dev/play/p/1TegARrb8Yn
func AfterLast(s, char string) string {
if s == "" || char == "" {
i := strings.LastIndex(s, char)
if s == "" || char == "" || i == -1 {
return s
}
i := strings.LastIndex(s, char)
return s[i+len(char):]
}
@@ -574,3 +582,15 @@ func RemoveWhiteSpace(str string, repalceAll bool) string {
return strings.TrimSpace(str)
}
// SubInBetween return substring between the start and end position(excluded) of source string.
// Play: todo
func SubInBetween(str string, start string, end string) string {
if _, after, ok := strings.Cut(str, start); ok {
if before, _, ok := strings.Cut(after, end); ok {
return before
}
}
return ""
}

View File

@@ -653,3 +653,17 @@ func ExampleRemoveWhiteSpace() {
// helloworld
// hello world
}
func ExampleSubInBetween() {
str := "abcde"
result1 := SubInBetween(str, "", "de")
result2 := SubInBetween(str, "a", "d")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// abc
// bc
}

View File

@@ -230,6 +230,8 @@ func TestBefore(t *testing.T) {
assert.Equal("lancet", Before("lancet", ""))
assert.Equal("", Before("lancet", "lancet"))
assert.Equal("lancet", Before("lancet", "abcdef"))
assert.Equal("github.com", Before("github.com/test/lancet", "/"))
assert.Equal("github.com/", Before("github.com/test/lancet", "test"))
}
@@ -240,10 +242,10 @@ func TestBeforeLast(t *testing.T) {
assert := internal.NewAssert(t, "TestBeforeLast")
assert.Equal("lancet", BeforeLast("lancet", ""))
assert.Equal("lancet", BeforeLast("lancet", "abcdef"))
assert.Equal("github.com/test", BeforeLast("github.com/test/lancet", "/"))
assert.Equal("github.com/test/", BeforeLast("github.com/test/test/lancet", "test"))
assert.NotEqual("github.com/", BeforeLast("github.com/test/test/lancet", "test"))
}
func TestAfter(t *testing.T) {
@@ -255,6 +257,8 @@ func TestAfter(t *testing.T) {
assert.Equal("", After("lancet", "lancet"))
assert.Equal("test/lancet", After("github.com/test/lancet", "/"))
assert.Equal("/lancet", After("github.com/test/lancet", "test"))
assert.Equal("lancet", After("lancet", "abcdef"))
}
func TestAfterLast(t *testing.T) {
@@ -266,8 +270,7 @@ func TestAfterLast(t *testing.T) {
assert.Equal("lancet", AfterLast("github.com/test/lancet", "/"))
assert.Equal("/lancet", AfterLast("github.com/test/lancet", "test"))
assert.Equal("/lancet", AfterLast("github.com/test/test/lancet", "test"))
assert.NotEqual("/test/lancet", AfterLast("github.com/test/test/lancet", "test"))
assert.Equal("lancet", AfterLast("lancet", "abcdef"))
}
func TestIsString(t *testing.T) {
@@ -566,3 +569,15 @@ func TestRemoveWhiteSpace(t *testing.T) {
assert.Equal("helloworld", RemoveWhiteSpace(str, true))
assert.Equal("hello world", RemoveWhiteSpace(str, false))
}
func TestSubInBetween(t *testing.T) {
assert := internal.NewAssert(t, "TestSubInBetween")
str := "abcde"
assert.Equal("", SubInBetween(str, "", ""))
assert.Equal("ab", SubInBetween(str, "", "c"))
assert.Equal("bc", SubInBetween(str, "a", "d"))
assert.Equal("", SubInBetween(str, "a", ""))
assert.Equal("", SubInBetween(str, "a", "f"))
}