From 04c737f2d4c1a07301fd76d81466e5d55a1dd8d4 Mon Sep 17 00:00:00 2001 From: Jiawen Date: Thu, 30 Oct 2025 17:15:31 +0800 Subject: [PATCH 1/2] feat: add ContainAny --- README.md | 3 +++ README_zh-CN.md | 3 +++ docs/api/packages/slice.md | 38 +++++++++++++++++++++++++++++++++ docs/en/api/packages/slice.md | 38 +++++++++++++++++++++++++++++++++ slice/slice.go | 21 ++++++++++++++++++ slice/slice_example_test.go | 18 ++++++++++++++++ slice/slice_test.go | 40 +++++++++++++++++++++++++++++++++++ 7 files changed, 161 insertions(+) diff --git a/README.md b/README.md index bf6d5df7..f17251da 100644 --- a/README.md +++ b/README.md @@ -1461,6 +1461,9 @@ import "github.com/duke-git/lancet/v2/slice" - **ContainSubSlice** : check if the slice contain a given subslice or not. [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainSubSlice)] [[play](https://go.dev/play/p/bcuQ3UT6Sev)] +- **ContainAny** : check if the slice contains any element from the targets slice. + [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#ContainAny)] + [[play](https://go.dev/play/p/4xoxhc9XSSw)] - **Chunk** : creates a slice of elements split into groups the length of size. [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Chunk)] [[play](https://go.dev/play/p/b4Pou5j2L_C)] diff --git a/README_zh-CN.md b/README_zh-CN.md index 2c7bb846..69aab527 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -1468,6 +1468,9 @@ import "github.com/duke-git/lancet/v2/slice" - **ContainSubSlice** : 判断 slice 是否包含 subslice。 [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ContainSubSlice)] [[play](https://go.dev/play/p/bcuQ3UT6Sev)] +- **ContainAny** : 判断 slice 是否包含 targets 切片中的任意一个元素。 + [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#ContainAny)] + [[play](https://go.dev/play/p/4xoxhc9XSSw)] - **Chunk** : 按照 size 参数均分 slice。 [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Chunk)] [[play](https://go.dev/play/p/b4Pou5j2L_C)] diff --git a/docs/api/packages/slice.md b/docs/api/packages/slice.md index 7787c3d5..e2d00539 100644 --- a/docs/api/packages/slice.md +++ b/docs/api/packages/slice.md @@ -27,6 +27,7 @@ import ( - [Contain](#Contain) - [ContainBy](#ContainBy) - [ContainSubSlice](#ContainSubSlice) +- [ContainAny](#ContainAny) - [Chunk](#Chunk) - [Compact](#Compact) - [Concat](#Concat) @@ -256,6 +257,43 @@ func main() { } ``` +### ContainAny + +

判断slice是否包含targets切片中的任意一个元素

+ +函数签名: + +```go +func ContainAny[T comparable](slice []T, targets []T) bool +``` + +示例:[运行](https://go.dev/play/p/4xoxhc9XSSw) + +```go +import ( + "fmt" + "github.com/duke-git/lancet/v2/slice" +) + +func main() { + result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"}) + result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"}) + result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"}) + result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{}) + + fmt.Println(result1) + fmt.Println(result2) + fmt.Println(result3) + fmt.Println(result4) + + // Output: + // true + // false + // true + // false +} +``` + ### Chunk

按照size参数均分slice

diff --git a/docs/en/api/packages/slice.md b/docs/en/api/packages/slice.md index 243981c7..b674e0f5 100644 --- a/docs/en/api/packages/slice.md +++ b/docs/en/api/packages/slice.md @@ -27,6 +27,7 @@ import ( - [Contain](#Contain) - [ContainBy](#ContainBy) - [ContainSubSlice](#ContainSubSlice) +- [ContainAny](#ContainAny) - [Chunk](#Chunk) - [Compact](#Compact) - [Concat](#Concat) @@ -256,6 +257,43 @@ func main() { } ``` +### ContainAny + +

Check if the slice contains any element from the targets slice.

+ +Signature: + +```go +func ContainAny[T comparable](slice []T, targets []T) bool +``` + +Example:[Run](https://go.dev/play/p/4xoxhc9XSSw) + +```go +import ( + "fmt" + "github.com/duke-git/lancet/v2/slice" +) + +func main() { + result1 := slice.ContainAny([]string{"a", "b", "c"}, []string{"a"}) + result2 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "e"}) + result3 := slice.ContainAny([]string{"a", "b", "c"}, []string{"d", "a"}) + result4 := slice.ContainAny([]string{"a", "b", "c"}, []string{}) + + fmt.Println(result1) + fmt.Println(result2) + fmt.Println(result3) + fmt.Println(result4) + + // Output: + // true + // false + // true + // false +} +``` + ### Chunk

Creates an slice of elements split into groups the length of `size`.

diff --git a/slice/slice.go b/slice/slice.go index 1264c0cc..adce6989 100644 --- a/slice/slice.go +++ b/slice/slice.go @@ -74,6 +74,27 @@ func ContainSubSlice[T comparable](slice, subSlice []T) bool { return true } +// ContainAny check if the slice contains any element from the targets slice. +// Play: https://go.dev/play/p/4xoxhc9XSSw +func ContainAny[T comparable](slice []T, targets []T) bool { + if len(targets) == 0 { + return false + } + + sliceMap := make(map[T]struct{}, len(slice)) + for _, item := range slice { + sliceMap[item] = struct{}{} + } + + for _, target := range targets { + if _, exists := sliceMap[target]; exists { + return true + } + } + + return false +} + // Chunk creates a slice of elements split into groups the length of size. // Play: https://go.dev/play/p/b4Pou5j2L_C func Chunk[T any](slice []T, size int) [][]T { diff --git a/slice/slice_example_test.go b/slice/slice_example_test.go index 339abdfa..ab6ad5f2 100644 --- a/slice/slice_example_test.go +++ b/slice/slice_example_test.go @@ -58,6 +58,24 @@ func ExampleContainSubSlice() { // false } +func ExampleContainAny() { + result1 := ContainAny([]string{"a", "b", "c"}, []string{"a"}) + result2 := ContainAny([]string{"a", "b", "c"}, []string{"d", "e"}) + result3 := ContainAny([]string{"a", "b", "c"}, []string{"d", "a"}) + result4 := ContainAny([]string{"a", "b", "c"}, []string{}) + + fmt.Println(result1) + fmt.Println(result2) + fmt.Println(result3) + fmt.Println(result4) + + // Output: + // true + // false + // true + // false +} + func ExampleChunk() { arr := []string{"a", "b", "c", "d", "e"} diff --git a/slice/slice_test.go b/slice/slice_test.go index fbbed930..3dffcfbf 100644 --- a/slice/slice_test.go +++ b/slice/slice_test.go @@ -89,6 +89,46 @@ func TestContainSubSlice(t *testing.T) { } } +func TestContainAny(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestContainAny") + + tests := []struct { + slice []string + targets []string + want bool + }{ + {[]string{"a", "b", "c"}, []string{"a"}, true}, + {[]string{"a", "b", "c"}, []string{"a", "b"}, true}, + {[]string{"a", "b", "c"}, []string{"d", "e"}, false}, + {[]string{"a", "b", "c"}, []string{"d", "a"}, true}, + {[]string{"a", "b", "c"}, []string{}, false}, + {[]string{}, []string{"a"}, false}, + {[]string{}, []string{}, false}, + {[]string{"a", "b", "c"}, []string{"c", "d", "e"}, true}, + } + + for _, tt := range tests { + assert.Equal(tt.want, ContainAny(tt.slice, tt.targets)) + } + + intTests := []struct { + slice []int + targets []int + want bool + }{ + {[]int{1, 2, 3, 4, 5}, []int{3}, true}, + {[]int{1, 2, 3, 4, 5}, []int{6, 7}, false}, + {[]int{1, 2, 3, 4, 5}, []int{5, 6, 7}, true}, + {[]int{1, 2, 3, 4, 5}, []int{}, false}, + } + + for _, tt := range intTests { + assert.Equal(tt.want, ContainAny(tt.slice, tt.targets)) + } +} + func TestChunk(t *testing.T) { t.Parallel() From 23f7c3f29cc157baf29ac6345399692666906c7b Mon Sep 17 00:00:00 2001 From: Jiawen Date: Mon, 3 Nov 2025 17:47:12 +0800 Subject: [PATCH 2/2] fix: fix issue #339 --- README.md | 2 +- README_zh-CN.md | 2 +- docs/api/packages/validator.md | 19 +++++++++++++++++-- docs/en/api/packages/validator.md | 19 +++++++++++++++++-- validator/validator.go | 7 +++---- validator/validator_test.go | 4 ++++ 6 files changed, 43 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6c7bd29d..c08b2c84 100644 --- a/README.md +++ b/README.md @@ -2309,7 +2309,7 @@ import "github.com/duke-git/lancet/v2/validator" [[play](https://go.dev/play/p/jlYApVLLGTZ)] - **IsEmail** : check if the string is a email address. [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsEmail)] - [[play](https://go.dev/play/p/Os9VaFlT33G)] + [[play](https://go.dev/play/p/HVQ5LAe-vFz)] - **IsEmptyString** : check if the string is empty. [[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/validator.md#IsEmptyString)] [[play](https://go.dev/play/p/dpzgUjFnBCX)] diff --git a/README_zh-CN.md b/README_zh-CN.md index e50b18bb..fff699e8 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -2316,7 +2316,7 @@ import "github.com/duke-git/lancet/v2/validator" [[play](https://go.dev/play/p/jlYApVLLGTZ)] - **IsEmail** : 验证字符串是否是有效电子邮件地址。 [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsEmail)] - [[play](https://go.dev/play/p/Os9VaFlT33G)] + [[play](https://go.dev/play/p/HVQ5LAe-vFz)] - **IsEmptyString** : 验证字符串是否是空字符串。 [[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/validator.md#IsEmptyString)] [[play](https://go.dev/play/p/dpzgUjFnBCX)] diff --git a/docs/api/packages/validator.md b/docs/api/packages/validator.md index 222821f8..8dd194d0 100644 --- a/docs/api/packages/validator.md +++ b/docs/api/packages/validator.md @@ -549,7 +549,7 @@ func main() { func IsEmail(email string) bool ``` -示例:[运行](https://go.dev/play/p/Os9VaFlT33G) +示例:[运行](https://go.dev/play/p/HVQ5LAe-vFz) ```go import ( @@ -559,13 +559,28 @@ import ( func main() { result1 := validator.IsEmail("abc@xyz.com") - result2 := validator.IsEmail("a.b@@com") + result2 := validator.IsEmail("user@domain.co") + result3 := validator.IsEmail("test.user@example.org") + result4 := validator.IsEmail("@abc@xyz.com") + result5 := validator.IsEmail("a.b@@com") + result6 := validator.IsEmail("a.b@com") + result7 := validator.IsEmail("test@example") fmt.Println(result1) fmt.Println(result2) + fmt.Println(result3) + fmt.Println(result4) + fmt.Println(result5) + fmt.Println(result6) + fmt.Println(result7) // Output: // true + // true + // true + // false + // false + // false // false } ``` diff --git a/docs/en/api/packages/validator.md b/docs/en/api/packages/validator.md index e811d8cb..05b22553 100644 --- a/docs/en/api/packages/validator.md +++ b/docs/en/api/packages/validator.md @@ -551,7 +551,7 @@ func main() { func IsEmail(email string) bool ``` -Example:[Run](https://go.dev/play/p/Os9VaFlT33G) +Example:[Run](https://go.dev/play/p/HVQ5LAe-vFz) ```go import ( @@ -561,13 +561,28 @@ import ( func main() { result1 := validator.IsEmail("abc@xyz.com") - result2 := validator.IsEmail("a.b@@com") + result2 := validator.IsEmail("user@domain.co") + result3 := validator.IsEmail("test.user@example.org") + result4 := validator.IsEmail("@abc@xyz.com") + result5 := validator.IsEmail("a.b@@com") + result6 := validator.IsEmail("a.b@com") + result7 := validator.IsEmail("test@example") fmt.Println(result1) fmt.Println(result2) + fmt.Println(result3) + fmt.Println(result4) + fmt.Println(result5) + fmt.Println(result6) + fmt.Println(result7) // Output: // true + // true + // true + // false + // false + // false // false } ``` diff --git a/validator/validator.go b/validator/validator.go index 18f88256..e72bdeeb 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "net" - "net/mail" "net/url" "reflect" "regexp" @@ -26,6 +25,7 @@ var ( intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`) // dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`) dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)*(?:xn--[a-zA-Z0-9\-]{1,59}|[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)$`) + emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`) chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`) chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`) chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]") @@ -321,10 +321,9 @@ func IsDns(dns string) bool { } // IsEmail check if the string is a email address. -// Play: https://go.dev/play/p/Os9VaFlT33G +// Play: https://go.dev/play/p/HVQ5LAe-vFz func IsEmail(email string) bool { - _, err := mail.ParseAddress(email) - return err == nil + return emailMatcher.MatchString(strings.ToLower(email)) } // IsChineseMobile check if the string is chinese mobile number. diff --git a/validator/validator_test.go b/validator/validator_test.go index 3adcd72a..af780046 100644 --- a/validator/validator_test.go +++ b/validator/validator_test.go @@ -494,8 +494,12 @@ func TestIsEmail(t *testing.T) { assert := internal.NewAssert(t, "TestIsEmail") assert.Equal(true, IsEmail("abc@xyz.com")) + assert.Equal(true, IsEmail("user@domain.co")) + assert.Equal(true, IsEmail("test.user@example.org")) assert.Equal(false, IsEmail("@abc@xyz.com")) assert.Equal(false, IsEmail("a.b@@com")) + assert.Equal(false, IsEmail("a.b@com")) + assert.Equal(false, IsEmail("test@example")) } func TestContainChinese(t *testing.T) {