1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-15 18:22:27 +08:00

Compare commits

...

33 Commits

Author SHA1 Message Date
dudaodong
73fb8fefd2 release v2.3.2 2024-07-18 10:52:54 +08:00
dudaodong
9cf535055d doc: update readme file 2024-07-18 10:50:29 +08:00
dudaodong
8be7b3e396 rename UnwrapOr 2024-07-18 10:12:16 +08:00
梧桐
dac706d700 🐛 Fixing a bug. about pointer package function (#230)
update:
1. UnwarpOr
2. UnwarpOrDefault
3. ExtractPointer

add:
1. UnwrapOr
2. IsNil
2024-07-18 10:05:07 +08:00
dudaodong
2097277a7d doc: add doc for UniqueByField 2024-06-25 15:20:03 +08:00
dudaodong
95b516e278 feat: add UniqueByField 2024-06-24 19:36:02 +08:00
dudaodong
ca373b00a7 feat: add GetOrSet 2024-06-24 17:29:12 +08:00
dudaodong
a220220f09 feat: add GetOrSet 2024-06-24 17:28:51 +08:00
dudaodong
aeef0418a4 fix: fix bug of CopyDir 2024-06-24 17:09:31 +08:00
dudaodong
9b7d8d7abf doc: update vitepress version 2024-06-11 10:06:33 +08:00
dudaodong
4d21e81263 feat: add Timeout config for http client 2024-06-04 16:28:00 +08:00
残念
ce2397422e perf(slice): make a clearer panic description (#223) 2024-05-30 16:55:30 +08:00
Cannian
4b3a62b36a perf(validator): check Ipv4、Ipv6 by more graceful method (#220) 2024-05-25 08:58:33 +08:00
dudaodong
e054680d20 doc: ignoreDeadLinks in doc 2024-05-14 11:42:48 +08:00
dudaodong
5381842eec doc: update doc for v2.3.1 2024-05-14 11:26:14 +08:00
dudaodong
6e0498514c doc: update doc for v2.3.1 2024-05-14 11:25:01 +08:00
dudaodong
967e6a3493 doc: update doc for v2.3.1 2024-05-14 10:47:27 +08:00
dudaodong
5b24801e49 merge qa branch 2024-05-14 10:11:29 +08:00
dudaodong
974ba525a6 Merge branch 'rc' into v2 2024-05-14 10:10:45 +08:00
chentong
f0235c40b6 fix: json tag omitempty convert error (#218) 2024-05-14 10:08:56 +08:00
dudaodong
712a215ea6 reset 2024-05-14 10:02:56 +08:00
dudaodong
7893f828d3 fix: fix get tag failed 2024-05-13 17:49:34 +08:00
Yang Li
53fa210f09 refactor slice.Unique() (#215) 2024-05-09 10:43:59 +08:00
dudaodong
de9ee08be4 test: update net error handle 2024-04-18 14:23:36 +08:00
dudaodong
5381450bea feat: fix email validation failed 2024-04-18 14:18:33 +08:00
Cannian
6853d627f4 refactor(slice): optimize function (#211) 2024-04-06 09:16:28 +08:00
Joker-desire
e461acdb72 fix(netutil): Add proxy IP to send request (#210)
* fix(netutil): Add proxy ip to send request

* fix(netutil): Add proxy IP to send request

---------

Co-authored-by: 杨崟 <yangyin@addcn.com>
2024-04-03 16:52:53 +08:00
dudaodong
2a796adf85 fix: support nest struct in StructToUrlValues 2024-04-02 17:38:40 +08:00
Cannian
5e6e8d82a8 feat(maputil): add ToSortedSlicesDefault method and ToSortedSlicesWithComparator method (#206) 2024-03-31 21:04:55 +08:00
Cannian
e9280b8c25 add Concat method (#204)
* feat(strutil): add Concat method

* feat(strutil): add Concat method
2024-03-25 10:26:37 +08:00
dudaodong
bb6f10a1fb rename CONTRIBUTING file 2024-03-17 10:30:24 +08:00
dudaodong
33b4cffe60 rename CONTRIBUTING file 2024-03-17 10:30:07 +08:00
Cannian
2b765b49e0 refactor(set): pop method randomly removes an element and return (#202) 2024-03-17 10:28:25 +08:00
35 changed files with 2193 additions and 650 deletions

View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.0-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.2-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
@@ -901,6 +901,17 @@ import "github.com/duke-git/lancet/v2/maputil"
- **<big>HasKey</big>** : checks if map has key or not.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#HasKey)]
[[play](https://go.dev/play/p/isZZHOsDhFc)]
- **<big>GetOrSet</big>** : returns value of the given key or set the given value value if not present.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#GetOrSet)]
- **<big>MapToStruct</big>** : converts map to struct.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#MapToStruct)]
[[play](https://go.dev/play/p/7wYyVfX38Dp)]
- **<big>ToSortedSlicesDefault</big>** : converts a map to two slices sorted by key: one for the keys and another for the values.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#ToSortedSlicesDefault)]
[[play](https://go.dev/play/p/43gEM2po-qy)]
- **<big>ToSortedSlicesWithComparator</big>** : converts a map to two slices sorted by key and using a custom comparison function: one for the keys and another for the values.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#ToSortedSlicesWithComparator)]
[[play](https://go.dev/play/p/0nlPo6YLdt3)]
- **<big>NewConcurrentMap</big>** : creates a ConcurrentMap with specific shard count.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/maputil.md#NewConcurrentMap)]
[[play](https://go.dev/play/p/3PenTPETJT0)]
@@ -1116,7 +1127,7 @@ import "github.com/duke-git/lancet/v2/pointer"
- **<big>Unwrap</big>** : return the value from the pointer.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/pointer.md#Unwrap)]
[[play](https://go.dev/play/p/cgeu3g7cjWb)]
- **<big>UnwarpOr</big>** : UnwarpOr returns the value from the pointer or fallback if the pointer is nil.
- **<big>UnwrapOr</big>** : UnwrapOr returns the value from the pointer or fallback if the pointer is nil.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/pointer.md#UnwrapOr)]
[[play](https://go.dev/play/p/mmNaLC38W8C)]
- **<big>UnwarpOrDefault</big>** : UnwarpOrDefault returns the value from the pointer or the default value if the pointer is nil.
@@ -1405,6 +1416,8 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>UniqueBy</big>** : call iteratee func with every item of slice, then remove duplicated.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#UniqueBy)]
[[play](https://go.dev/play/p/UR323iZLDpv)]
- **<big>UniqueByField</big>** : remove duplicate elements in struct slice by struct field.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#UniqueByField)]
- **<big>Union</big>** : creates a slice of unique elements, in order, from all given slices.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Union)]
[[play](https://go.dev/play/p/hfXV1iRIZOf)]
@@ -1432,7 +1445,14 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>SetToDefaultIf</big>** : set elements to their default value if they match the given predicate.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#SetToDefaultIf)]
[[play](https://go.dev/play/p/9AXGlPRC0-A)]
- **<big>Break</big>** : breaks a list into two parts at the point where the predicate for the first time is true.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#Break)]
- **<big>RightPadding</big>** : adds padding to the right end of a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#RightPadding)]
[[play](https://go.dev/play/p/0_2rlLEMBXL)]
- **<big>LeftPadding</big>** : adds padding to the left begin of a slice.
[[doc](https://github.com/duke-git/lancet/blob/main/docs/en/api/packages/slice.md#LeftPadding)]
[[play](https://go.dev/play/p/jlQVoelLl2k)]
<h3 id="stream"> 19. Stream package implements a sequence of elements supporting sequential and operations. this package is an experiment to explore if stream in go can work as the way java does. its function is very limited. &nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">index</a></h3>
@@ -2024,7 +2044,7 @@ import "github.com/duke-git/lancet/v2/xerror"
## How to Contribute
#### [Contributing Guide](./CONTRIBUTING.en-US.md)
#### [Contributing Guide](./CONTRIBUTING.md)
## Contributors
Thank you to all the people who contributed to lancet!

View File

@@ -4,7 +4,7 @@
<br/>
![Go version](https://img.shields.io/badge/go-%3E%3Dv1.18-9cf)
[![Release](https://img.shields.io/badge/release-2.3.0-green.svg)](https://github.com/duke-git/lancet/releases)
[![Release](https://img.shields.io/badge/release-2.3.2-green.svg)](https://github.com/duke-git/lancet/releases)
[![GoDoc](https://godoc.org/github.com/duke-git/lancet/v2?status.svg)](https://pkg.go.dev/github.com/duke-git/lancet/v2)
[![Go Report Card](https://goreportcard.com/badge/github.com/duke-git/lancet/v2)](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
[![test](https://github.com/duke-git/lancet/actions/workflows/codecov.yml/badge.svg?branch=main&event=push)](https://github.com/duke-git/lancet/actions/workflows/codecov.yml)
@@ -902,7 +902,18 @@ import "github.com/duke-git/lancet/v2/maputil"
[[play](https://go.dev/play/p/N9qgYg_Ho6f)]
- **<big>HasKey</big>** : 检查 map 是否包含某个 key。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#HasKey)]
- **<big>GetOrSet</big>** : 返回给定键的值,如果不存在则设置该值。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#GetOrSet)]
[[play](https://go.dev/play/p/isZZHOsDhFc)]
- **<big>MapToStruct</big>** : 将map转成struct。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#MapToStruct)]
[[play](https://go.dev/play/p/7wYyVfX38Dp)]
- **<big>ToSortedSlicesDefault</big>** : 将map的key和value转化成两个根据key的值从小到大排序的切片value切片中元素的位置与key对应。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToSortedSlicesDefault)]
[[play](https://go.dev/play/p/43gEM2po-qy)]
- **<big>ToSortedSlicesWithComparator</big>** : 将map的key和value转化成两个使用比较器函数根据key的值自定义排序规则的切片value切片中元素的位置与key对应。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#ToSortedSlicesWithComparator)]
[[play](https://go.dev/play/p/0nlPo6YLdt3)]
- **<big>NewConcurrentMap</big>** : ConcurrentMap 协程安全的 map 结构。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/maputil.md#NewConcurrentMap)]
[[play](https://go.dev/play/p/3PenTPETJT0)]
@@ -1405,6 +1416,8 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>UniqueBy</big>** : 对切片的每个元素调用 iteratee 函数,然后删除重复元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#UniqueBy)]
[[play](https://go.dev/play/p/UR323iZLDpv)]
- **<big>UniqueByField</big>** : 根据struct字段对struct切片去重复
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#UniqueByField)]
- **<big>Union</big>** : 合并多个切片。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Union)]
[[play](https://go.dev/play/p/hfXV1iRIZOf)]
@@ -1431,6 +1444,14 @@ import "github.com/duke-git/lancet/v2/slice"
- **<big>SetToDefaultIf</big>** : 根据给定给定的predicate判定函数来修改切片中的元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#SetToDefaultIf)]
[[play](https://go.dev/play/p/9AXGlPRC0-A)]
- **<big>Break</big>** : 根据判断函数将切片分成两部分。它开始附加到与函数匹配的第一个元素之后的第二个切片。第一个匹配之后的所有元素都包含在第二个切片中,无论它们是否与函数匹配。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#Break)]
- **<big>RightPadding</big>** : 在切片的右部添加元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#RightPadding)]
[[play](https://go.dev/play/p/0_2rlLEMBXL)]
- **<big>LeftPadding</big>** : 在切片的左部添加元素。
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/slice.md#LeftPadding)]
[[play](https://go.dev/play/p/jlQVoelLl2k)]
@@ -1521,6 +1542,7 @@ import "github.com/duke-git/lancet/v2/stream"
[[doc](https://github.com/duke-git/lancet/blob/main/docs/api/packages/stream.md#ToSlice)]
[[play](https://go.dev/play/p/jI6_iZZuVFE)]
<h3 id="structs"> 20. structs 提供操作 struct, tag, field 的相关函数。&nbsp; &nbsp; &nbsp; &nbsp;<a href="#index">回到目录</a></h3>
```go

View File

@@ -187,10 +187,11 @@ func (s Set[T]) EachWithBreak(iteratee func(item T) bool) {
// Pop delete the top element of set then return it, if set is empty, return nil-value of T and false.
func (s Set[T]) Pop() (v T, ok bool) {
if len(s) > 0 {
items := s.Values()
item := items[len(s)-1]
delete(s, item)
return item, true
for item := range s {
v = item
delete(s, item)
return v, true
}
}
return v, false

View File

@@ -243,25 +243,34 @@ func TestEachWithBreak(t *testing.T) {
// assert.Equal(6, sum)
}
// func TestPop(t *testing.T) {
// assert := internal.NewAssert(t, "TestPop")
func TestPop(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSet_Pop")
// s := New[int]()
s := New[int]()
// val, ok := s.Pop()
// assert.Equal(0, val)
// assert.Equal(false, ok)
val, ok := s.Pop()
assert.Equal(0, val)
assert.Equal(false, ok)
// s.Add(1)
// s.Add(2)
// s.Add(3)
s = New(1, 2, 3, 4, 5)
sl := s.ToSlice()
// // s = New(1, 2, 3, 4, 5)
val, ok = s.Pop()
assert.Equal(false, s.Contain(val))
assert.Equal(true, ok)
assert.Equal(len(sl)-1, s.Size())
// val, ok = s.Pop()
// assert.Equal(3, val)
// assert.Equal(true, ok)
// }
var found bool
for _, v := range sl {
if v == val {
found = true
}
}
assert.Equal(true, found)
}
func TestSet_ToSlice(t *testing.T) {
t.Parallel()

View File

@@ -40,6 +40,7 @@ export const slugify = (str: string): string =>
export const commonConfig = defineConfig({
title: 'Lancet',
appearance: true,
ignoreDeadLinks: true,
markdown: {
theme: {

View File

@@ -44,6 +44,9 @@ import (
- [Minus](#Minus)
- [IsDisjoint](#IsDisjoint)
- [HasKey](#HasKey)
- [MapToStruct](#MapToStruct)
- [ToSortedSlicesDefault](#ToSortedSlicesDefault)
- [ToSortedSlicesWithComparator](#ToSortedSlicesWithComparator)
- [NewConcurrentMap](#NewConcurrentMap)
- [ConcurrentMap_Get](#ConcurrentMap_Get)
- [ConcurrentMap_Set](#ConcurrentMap_Set)
@@ -52,6 +55,8 @@ import (
- [ConcurrentMap_GetAndDelete](#ConcurrentMap_GetAndDelete)
- [ConcurrentMap_Has](#ConcurrentMap_Has)
- [ConcurrentMap_Range](#ConcurrentMap_Range)
- [GetOrSet](#GetOrSet)
<div STYLE="page-break-after: always;"></div>
@@ -946,14 +951,6 @@ func main() {
<p>检查map是否包含某个key。用于代替以下样板代码:</p>
```go
_, haskey := amap["baz"];
if haskey {
fmt.Println("map has key baz")
}
```
<b>函数签名:</b>
```go
@@ -988,6 +985,141 @@ func main() {
}
```
### <span id="MapToStruct">MapToStruct</span>
<p>将map转成struct。</p>
<b>函数签名:</b>
```go
func MapToStruct(m map[string]any, structObj any) error
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/7wYyVfX38Dp)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
personReqMap := map[string]any{
"name": "Nothin",
"max_age": 35,
"page": 1,
"pageSize": 10,
}
type PersonReq struct {
Name string `json:"name"`
MaxAge int `json:"max_age"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
var personReq PersonReq
_ = maputil.MapToStruct(personReqMap, &personReq)
fmt.Println(personReq)
// Output:
// {Nothin 35 1 10}
}
```
### <span id="ToSortedSlicesDefault">ToSortedSlicesDefault</span>
<p>将map的key和value转化成两个根据key的值从小到大排序的切片value切片中元素的位置与key对应。</p>
<b>函数签名:</b>
```go
func ToSortedSlicesDefault[K constraints.Ordered, V any](m map[K]V) ([]K, []V)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/43gEM2po-qy)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m := map[int]string{
1: "a",
3: "c",
2: "b",
}
keys, values := ToSortedSlicesDefault(m)
fmt.Println(keys)
fmt.Println(values)
// Output:
// [1 2 3]
// [a b c]
}
```
### <span id="ToSortedSlicesWithComparator">ToSortedSlicesWithComparator</span>
<p>将map的key和value转化成两个使用比较器函数根据key的值自定义排序规则的切片value切片中元素的位置与key对应。</p>
<b>函数签名:</b>
```go
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/0nlPo6YLdt3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m1 := map[time.Time]string{
time.Date(2024, 3, 31, 0, 0, 0, 0, time.UTC): "today",
time.Date(2024, 3, 30, 0, 0, 0, 0, time.UTC): "yesterday",
time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC): "tomorrow",
}
keys1, values1 := maputil.ToSortedSlicesWithComparator(m1, func(a, b time.Time) bool {
return a.Before(b)
})
m2 := map[int]string{
1: "a",
3: "c",
2: "b",
}
keys2, values2 := maputil.ToSortedSlicesWithComparator(m2, func(a, b int) bool {
return a > b
})
fmt.Println(keys2)
fmt.Println(values2)
fmt.Println(keys1)
fmt.Println(values1)
// Output:
// [3 2 1]
// [c b a]
// [2024-03-30 00:00:00 +0000 UTC 2024-03-31 00:00:00 +0000 UTC 2024-04-01 00:00:00 +0000 UTC]
// [yesterday today tomorrow]
}
```
### <span id="NewConcurrentMap">NewConcurrentMap</span>
<p>ConcurrentMap协程安全的map结构。</p>
@@ -1050,15 +1182,15 @@ func main() {
wg1.Wait()
var wg2 sync.WaitGroup
wg2.Add(5)
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
val, ok := cm.Get(fmt.Sprintf("%d", n))
fmt.Println(val, ok)
wg2.Done()
wg2.Done()
}(j)
}
wg2.Wait()
wg2.Wait()
// output: (order may change)
// 1 true
@@ -1104,15 +1236,15 @@ func main() {
wg1.Wait()
var wg2 sync.WaitGroup
wg2.Add(5)
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
val, ok := cm.Get(fmt.Sprintf("%d", n))
fmt.Println(val, ok)
wg2.Done()
wg2.Done()
}(j)
}
wg2.Wait()
wg2.Wait()
// output: (order may change)
// 1 true
@@ -1202,7 +1334,7 @@ func main() {
wg1.Wait()
var wg2 sync.WaitGroup
wg2.Add(5)
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
cm.Delete(fmt.Sprintf("%d", n))
@@ -1248,7 +1380,7 @@ func main() {
wg1.Wait()
var wg2 sync.WaitGroup
wg2.Add(5)
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
val, ok := cm.GetAndDelete(fmt.Sprintf("%d", n))
@@ -1298,7 +1430,7 @@ func main() {
wg1.Wait()
var wg2 sync.WaitGroup
wg2.Add(5)
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
@@ -1353,3 +1485,41 @@ func main() {
})
}
```
### <span id="GetOrSet">GetOrSet</span>
<p>返回给定键的值,如果不存在则设置该值。</p>
<b>函数签名:</b>
```go
func GetOrSet[K comparable, V any](m map[K]V, key K, value V) V
```
<b>示例:<span style="float:right;display:inline-block;"></span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m := map[int]string{
1: "a",
}
result1 := maputil.GetOrSet(m, 1, "1")
result2 := maputil.GetOrSet(m, 2, "b")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// a
// b
}
```

View File

@@ -25,8 +25,8 @@ import (
- [Of](#Of)
- [Unwrap](#Unwrap)
- [ExtractPointer](#ExtractPointer)
- [UnwarpOr](#UnwarpOr)
- [UnwarpOrDefault](#UnwarpOrDefault)
- [UnwrapOr](#UnwrapOr)
- [UnwrapOrDefault](#UnwrapOrDefault)
<div STYLE="page-break-after: always;"></div>
@@ -136,14 +136,14 @@ func main() {
}
```
### <span id="UnwarpOr">UnwarpOr</span>
### <span id="UnwrapOr">UnwrapOr</span>
<p>返回指针的值如果指针为零值则返回fallback。</p>
<b>函数签名:</b>
```go
UnwarpOr[T any](p *T, fallback T) T
UnwrapOr[T any](p *T, fallback T) T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/mmNaLC38W8C)</span></b>
@@ -163,10 +163,10 @@ func main() {
var c *int
var d *string
result1 := pointer.UnwarpOr(&a, 456)
result2 := pointer.UnwarpOr(&b, "abc")
result3 := pointer.UnwarpOr(c, 456)
result4 := pointer.UnwarpOr(d, "def")
result1 := pointer.UnwrapOr(&a, 456)
result2 := pointer.UnwrapOr(&b, "abc")
result3 := pointer.UnwrapOr(c, 456)
result4 := pointer.UnwrapOr(d, "def")
fmt.Println(result1)
fmt.Println(result2)
@@ -181,14 +181,14 @@ func main() {
}
```
### <span id="UnwarpOrDefault">UnwarpOrDefault</span>
### <span id="UnwrapOrDefault">UnwrapOrDefault</span>
<p>返回指针的值,如果指针为零值,则返回相应零值。</p>
<b>函数签名:</b>
```go
UnwarpOrDefault[T any](p *T) T
UnwrapOrDefault[T any](p *T) T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/ZnGIHf8_o4E)</span></b>
@@ -208,10 +208,10 @@ func main() {
var c *int
var d *string
result1 := pointer.UnwarpOrDefault(&a)
result2 := pointer.UnwarpOrDefault(&b)
result3 := pointer.UnwarpOrDefault(c)
result4 := pointer.UnwarpOrDefault(d)
result1 := pointer.UnwrapOrDefault(&a)
result2 := pointer.UnwrapOrDefault(&b)
result3 := pointer.UnwrapOrDefault(c)
result4 := pointer.UnwrapOrDefault(d)
fmt.Println(result1)
fmt.Println(result2)

View File

@@ -86,6 +86,7 @@ import (
- [ToSlicePointer](#ToSlicePointer)
- [Unique](#Unique)
- [UniqueBy](#UniqueBy)
- [UniqueByField](#UniqueByField)
- [Union](#Union)
- [UnionBy](#UnionBy)
- [UpdateAt](#UpdateAt)
@@ -322,12 +323,12 @@ func main() {
### <span id="Concat">Concat</span>
<p>合并多个slices到slice中</p>
<p>创建一个新的切片,将传入的切片拼接起来返回。</p>
<b>函数签名:</b>
```go
func Concat[T any](slice []T, slices ...[]T) []T
func Concat[T any](slices ...[]T) []T
```
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/gPt-q7zr5mk)</span></b>
@@ -1542,7 +1543,7 @@ func main() {
}
```
### <span id="Merge">Merge</span>
### <span id="Merge">Merge废弃使用Concat</span>
<p>合并多个切片(不会消除重复元素).</p>
@@ -2312,6 +2313,47 @@ func main() {
}
```
### <span id="UniqueByField">UniqueByField</span>
<p>根据struct字段对struct切片去重复。</p>
<b>函数签名:</b>
```go
func UniqueByField[T any](slice []T, field string) ([]T, error)
```
<b>示例:<span style="float:right;display:inline-block;"></span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/slice"
)
func main() {
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
users := []User{
{ID: 1, Name: "a"},
{ID: 2, Name: "b"},
{ID: 1, Name: "c"},
}
result, err := slice.UniqueByField(users, "ID")
if err != nil {
}
fmt.Println(result)
// Output:
// [{1 a} {2 b}]
}
```
### <span id="Union">Union</span>
<p>合并多个切片</p>
@@ -2594,14 +2636,14 @@ import (
func main() {
strs := []string{"a", "b", "a", "c", "d", "a"}
modifiedStrs, count := slice.SetToDefaultIf(strs, func(s string) bool { return "a" == s })
modifiedStrs, count := slice.SetToDefaultIf(strs, func(s string) bool { return "a" == s })
fmt.Println(modifiedStrs)
fmt.Println(count)
fmt.Println(count)
// Output:
// [ b c d ]
// 3
// [ b c d ]
// 3
}
```
@@ -2615,7 +2657,7 @@ func main() {
func Break[T any](values []T, predicate func(T) bool) ([]T, []T)
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/yLYcBTyeQIz)</span></b>
```go
import (
@@ -2648,7 +2690,7 @@ func main() {
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/0_2rlLEMBXL)</span></b>
```go
import (
@@ -2657,11 +2699,11 @@ import (
)
func main() {
nums := []int{1, 2, 3, 4, 5}
padded := slice.RightPadding(nums, 0, 3)
fmt.Println(padded)
// Output:
// [1 2 3 4 5 0 0 0]
nums := []int{1, 2, 3, 4, 5}
padded := slice.RightPadding(nums, 0, 3)
fmt.Println(padded)
// Output:
// [1 2 3 4 5 0 0 0]
}
```
@@ -2675,7 +2717,7 @@ func main() {
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T
```
<b>示例:</b>
<b>示例:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/jlQVoelLl2k)</span></b>
```go
import (
@@ -2684,10 +2726,10 @@ import (
)
func main() {
nums := []int{1, 2, 3, 4, 5}
padded := slice.LeftPadding(nums, 0, 3)
fmt.Println(padded)
// Output:
// [0 0 0 1 2 3 4 5]
nums := []int{1, 2, 3, 4, 5}
padded := slice.LeftPadding(nums, 0, 3)
fmt.Println(padded)
// Output:
// [0 0 0 1 2 3 4 5]
}
```

View File

@@ -62,6 +62,7 @@ import (
- [RemoveWhiteSpace](#RemoveWhiteSpace)
- [SubInBetween](#SubInBetween)
- [HammingDistance](#HammingDistance)
- [Concat](#Concat)
<div STYLE="page-break-after: always;"></div>
@@ -1528,4 +1529,38 @@ func main() {
// 0
// 1
}
```
### <span id="Concat">Concat</span>
<p>拼接字符串。length是拼接后字符串的长度如果不确定则传0或负数。</p>
<b>函数签名:</b>
```go
func Concat(length int, str ...string) string
```
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/strutil"
)
func main() {
result1 := strutil.Concat(12, "Hello", " ", "World", "!")
result2 := strutil.Concat(11, "Go", " ", "Language")
result3 := strutil.Concat(0, "An apple a ", "day", "keeps the", " doctor away")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// Hello World!
// Go Language
// An apple a daykeeps the doctor away
}
```

View File

@@ -44,6 +44,9 @@ import (
- [Minus](#Minus)
- [IsDisjoint](#IsDisjoint)
- [HasKey](#HasKey)
- [MapToStruct](#MapToStruct)
- [ToSortedSlicesDefault](#ToSortedSlicesDefault)
- [ToSortedSlicesWithComparator](#ToSortedSlicesWithComparator)
- [NewConcurrentMap](#NewConcurrentMap)
- [ConcurrentMap_Get](#ConcurrentMap_Get)
- [ConcurrentMap_Set](#ConcurrentMap_Set)
@@ -52,6 +55,7 @@ import (
- [ConcurrentMap_GetAndDelete](#ConcurrentMap_GetAndDelete)
- [ConcurrentMap_Has](#ConcurrentMap_Has)
- [ConcurrentMap_Range](#ConcurrentMap_Range)
- [GetOrSet](#GetOrSet)
<div STYLE="page-break-after: always;"></div>
@@ -992,6 +996,144 @@ func main() {
}
```
### <span id="MapToStruct">MapToStruct</span>
<p>Converts map to struct</p>
<b>Signature:</b>
```go
func MapToStruct(m map[string]any, structObj any) error
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/7wYyVfX38Dp)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
personReqMap := map[string]any{
"name": "Nothin",
"max_age": 35,
"page": 1,
"pageSize": 10,
}
type PersonReq struct {
Name string `json:"name"`
MaxAge int `json:"max_age"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
var personReq PersonReq
_ = maputil.MapToStruct(personReqMap, &personReq)
fmt.Println(personReq)
// Output:
// {Nothin 35 1 10}
}
```
### <span id="ToSortedSlicesDefault">ToSortedSlicesDefault</span>
<p>
Translate the key and value of the map into two slices that are sorted in ascending order according to the keys value, with the position of the elements in the value slice corresponding to the key.</p>
<b>Signature:</b>
```go
func ToSortedSlicesDefault[K constraints.Ordered, V any](m map[K]V) ([]K, []V)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/43gEM2po-qy)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m := map[int]string{
1: "a",
3: "c",
2: "b",
}
keys, values := maputil.ToSortedSlicesDefault(m)
fmt.Println(keys)
fmt.Println(values)
// Output:
// [1 2 3]
// [a b c]
}
```
### <span id="ToSortedSlicesWithComparator">ToSortedSlicesWithComparator</span>
<p>
Translate the key and value of the map into two slices that are sorted according to a custom sorting rule defined by a comparator function based on the key's value, with the position of the elements in the value slice corresponding to the key.
</p>
<b>Signature:</b>
```go
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V)
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/0nlPo6YLdt3)</span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m1 := map[time.Time]string{
time.Date(2024, 3, 31, 0, 0, 0, 0, time.UTC): "today",
time.Date(2024, 3, 30, 0, 0, 0, 0, time.UTC): "yesterday",
time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC): "tomorrow",
}
keys1, values1 := maputil.ToSortedSlicesWithComparator(m1, func(a, b time.Time) bool {
return a.Before(b)
})
m2 := map[int]string{
1: "a",
3: "c",
2: "b",
}
keys2, values2 := maputil.ToSortedSlicesWithComparator(m2, func(a, b int) bool {
return a > b
})
fmt.Println(keys2)
fmt.Println(values2)
fmt.Println(keys1)
fmt.Println(values1)
// Output:
// [3 2 1]
// [c b a]
// [2024-03-30 00:00:00 +0000 UTC 2024-03-31 00:00:00 +0000 UTC 2024-04-01 00:00:00 +0000 UTC]
// [yesterday today tomorrow]
}
```
### <span id="NewConcurrentMap">NewConcurrentMap</span>
<p>ConcurrentMap is like map, but is safe for concurrent use by multiple goroutines.</p>
@@ -1055,15 +1197,15 @@ func main() {
var wg2 sync.WaitGroup
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
val, ok := cm.Get(fmt.Sprintf("%d", n))
fmt.Println(val, ok)
wg2.Done()
}(j)
}
wg2.Wait()
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
val, ok := cm.Get(fmt.Sprintf("%d", n))
fmt.Println(val, ok)
wg2.Done()
}(j)
}
wg2.Wait()
// output: (order may change)
// 1 true
@@ -1110,15 +1252,15 @@ func main() {
var wg2 sync.WaitGroup
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
val, ok := cm.Get(fmt.Sprintf("%d", n))
fmt.Println(val, ok)
wg2.Done()
}(j)
}
wg2.Wait()
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
val, ok := cm.Get(fmt.Sprintf("%d", n))
fmt.Println(val, ok)
wg2.Done()
}(j)
}
wg2.Wait()
// output: (order may change)
// 1 true
@@ -1208,7 +1350,7 @@ func main() {
wg1.Wait()
var wg2 sync.WaitGroup
wg2.Add(5)
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
cm.Delete(fmt.Sprintf("%d", n))
@@ -1255,7 +1397,7 @@ func main() {
wg1.Wait()
var wg2 sync.WaitGroup
wg2.Add(5)
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
val, ok := cm.GetAndDelete(fmt.Sprintf("%d", n))
@@ -1307,7 +1449,7 @@ func main() {
wg1.Wait()
var wg2 sync.WaitGroup
wg2.Add(5)
wg2.Add(5)
for j := 0; j < 5; j++ {
go func(n int) {
ok := cm.Has(fmt.Sprintf("%d", n))
@@ -1360,3 +1502,40 @@ func main() {
})
}
```
### <span id="GetOrSet">GetOrSet</span>
<p>Returns value of the given key or set the given value value if not present.</p>
<b>Signature:</b>
```go
func GetOrSet[K comparable, V any](m map[K]V, key K, value V) V
```
<b>Example:<span style="float:right;display:inline-block;"></span></b>
```go
package main
import (
"fmt"
"github.com/duke-git/lancet/v2/maputil"
)
func main() {
m := map[int]string{
1: "a",
}
result1 := maputil.GetOrSet(m, 1, "1")
result2 := maputil.GetOrSet(m, 2, "b")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// a
// b
}
```

View File

@@ -24,8 +24,8 @@ import (
- [Of](#Of)
- [Unwrap](#Unwrap)
- [UnwarpOr](#UnwarpOr)
- [UnwarpOrDefault](#UnwarpOrDefault)
- [UnwrapOr](#UnwrapOr)
- [UnwrapOrDefault](#UnwrapOrDefault)
- [ExtractPointer](#ExtractPointer)
<div STYLE="page-break-after: always;"></div>
@@ -103,13 +103,13 @@ func main() {
```
### <span id="UnwarpOr">UnwarpOr</span>
### <span id="UnwrapOr">UnwrapOr</span>
<p>Returns the value from the pointer or fallback if the pointer is nil.</p>
<b>Signature:</b>
```go
UnwarpOr[T any](p *T, fallback T) T
UnwrapOr[T any](p *T, fallback T) T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/mmNaLC38W8C)</span></b>
@@ -129,10 +129,10 @@ func main() {
var c *int
var d *string
result1 := pointer.UnwarpOr(&a, 456)
result2 := pointer.UnwarpOr(&b, "abc")
result3 := pointer.UnwarpOr(c, 456)
result4 := pointer.UnwarpOr(d, "def")
result1 := pointer.UnwrapOr(&a, 456)
result2 := pointer.UnwrapOr(&b, "abc")
result3 := pointer.UnwrapOr(c, 456)
result4 := pointer.UnwrapOr(d, "def")
fmt.Println(result1)
fmt.Println(result2)
@@ -148,13 +148,13 @@ func main() {
```
### <span id="UnwarpOrDefault">UnwarpOrDefault</span>
### <span id="UnwrapOrDefault">UnwrapOrDefault</span>
<p>Returns the value from the pointer or the default value if the pointer is nil.</p>
<b>Signature:</b>
```go
UnwarpOrDefault[T any](p *T) T
UnwrapOrDefault[T any](p *T) T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/ZnGIHf8_o4E)</span></b>
@@ -174,10 +174,10 @@ func main() {
var c *int
var d *string
result1 := pointer.UnwarpOrDefault(&a)
result2 := pointer.UnwarpOrDefault(&b)
result3 := pointer.UnwarpOrDefault(c)
result4 := pointer.UnwarpOrDefault(d)
result1 := pointer.UnwrapOrDefault(&a)
result2 := pointer.UnwrapOrDefault(&b)
result3 := pointer.UnwrapOrDefault(c)
result4 := pointer.UnwrapOrDefault(d)
fmt.Println(result1)
fmt.Println(result2)

View File

@@ -86,6 +86,7 @@ import (
- [ToSlicePointer](#ToSlicePointer)
- [Unique](#Unique)
- [UniqueBy](#UniqueBy)
- [UniqueByField](#UniqueByField)
- [Union](#Union)
- [UnionBy](#UnionBy)
- [UpdateAt](#UpdateAt)
@@ -321,12 +322,12 @@ func main() {
### <span id="Concat">Concat</span>
<p>Creates a new slice concatenating slice with any additional slices.</p>
<p>Concat creates a new slice concatenating slice with any additional slices.</p>
<b>Signature:</b>
```go
func Concat[T any](slice []T, slices ...[]T) []T
func Concat[T any](slices ...[]T) []T
```
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/gPt-q7zr5mk)</span></b>
@@ -1540,7 +1541,7 @@ func main() {
}
```
### <span id="Merge">Merge</span>
### <span id="Merge">Merge(deprecated: use Concat)</span>
<p>Merge all given slices into one slice.</p>
@@ -2310,6 +2311,47 @@ func main() {
}
```
### <span id="UniqueByField">UniqueByField</span>
<p>Remove duplicate elements in struct slice by struct field.</p>
<b>Signature:</b>
```go
func UniqueByField[T any](slice []T, field string) ([]T, error)
```
<b>Example:<span style="float:right;display:inline-block;"></span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/slice"
)
func main() {
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
users := []User{
{ID: 1, Name: "a"},
{ID: 2, Name: "b"},
{ID: 1, Name: "c"},
}
result, err := slice.UniqueByField(users, "ID")
if err != nil {
}
fmt.Println(result)
// Output:
// [{1 a} {2 b}]
}
```
### <span id="Union">Union</span>
<p>Creates a slice of unique values, in order, from all given slices. using == for equality comparisons.</p>
@@ -2612,7 +2654,7 @@ func main() {
func Break[T any](values []T, predicate func(T) bool) ([]T, []T)
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/yLYcBTyeQIz)</span></b>
```go
import (
@@ -2645,7 +2687,7 @@ func main() {
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/0_2rlLEMBXL)</span></b>
```go
import (
@@ -2655,7 +2697,7 @@ import (
func main() {
nums := []int{1, 2, 3, 4, 5}
padded := RightPadding(nums, 0, 3)
padded := slice.RightPadding(nums, 0, 3)
fmt.Println(padded)
// Output:
// [1 2 3 4 5 0 0 0]
@@ -2672,7 +2714,7 @@ func main() {
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T
```
<b>Example:</b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/jlQVoelLl2k)</span></b>
```go
import (
@@ -2682,7 +2724,7 @@ import (
func main() {
nums := []int{1, 2, 3, 4, 5}
padded := LeftPadding(nums, 0, 3)
padded := slice.LeftPadding(nums, 0, 3)
fmt.Println(padded)
// Output:
// [0 0 0 1 2 3 4 5]

View File

@@ -62,6 +62,7 @@ import (
- [RemoveWhiteSpace](#RemoveWhiteSpace)
- [SubInBetween](#SubInBetween)
- [HammingDistance](#HammingDistance)
- [Concat](#Concat)
<div STYLE="page-break-after: always;"></div>
@@ -1477,7 +1478,7 @@ func main() {
func SubInBetween(str string, start string, end string) string
```
<b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/EDbaRvjeNsv)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/EDbaRvjeNsv)</span></b>
```go
import (
@@ -1510,7 +1511,7 @@ func main() {
func HammingDistance(a, b string) (int, error)
```
<b>Example:<span style="float:right;display:inline-block;">[运行](https://go.dev/play/p/glNdQEA9HUi)</span></b>
<b>Example:<span style="float:right;display:inline-block;">[Run](https://go.dev/play/p/glNdQEA9HUi)</span></b>
```go
import (
@@ -1530,4 +1531,39 @@ func main() {
// 0
// 1
}
```
### <span id="Concat">Concat</span>
<p>Concatenates strings. <b>length</b> is the length of the concatenated string. If unsure, pass 0 or a negative number.</p>
<b>Signature:</b>
```go
func Concat(length int, str ...string) string
```
<b>Example:<span style="float:right;display:inline-block;">[Run]()</span></b>
```go
import (
"fmt"
"github.com/duke-git/lancet/v2/strutil"
)
func main() {
result1 := strutil.Concat(12, "Hello", " ", "World", "!")
result2 := strutil.Concat(11, "Go", " ", "Language")
result3 := strutil.Concat(0, "An apple a ", "day", "keeps the", " doctor away")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// Hello World!
// Go Language
// An apple a daykeeps the doctor away
}
```

View File

@@ -33,7 +33,7 @@ features:
details: Well structure, test for every exported function.
---
<p style="position:relative; top: -316px;left: 560px;">
<p style="position:relative; inline-block;top: -330px;left: 30%">
<img style="display: inline-block;margin-right:10px;" src="https://img.shields.io/github/stars/duke-git/lancet?style=social" alt="">
<img style="display: inline-block" src="https://img.shields.io/github/forks/duke-git/lancet?style=social" alt="">
</p>

View File

@@ -33,7 +33,7 @@ features:
details: 结构良好,测试每个导出的函数。
---
<p style="position:relative;display: inline-block;top: -316px;left: 32%">
<p style="position:relative;display: inline-block;top: -330px;left: 30%">
<img style="display: inline-block;margin-right:10px;" src="https://img.shields.io/github/stars/duke-git/lancet?style=social" alt="">
<img style="display: inline-block" src="https://img.shields.io/github/forks/duke-git/lancet?style=social" alt="">
</p>

1171
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,6 @@
"docs:preview": "vitepress preview"
},
"devDependencies": {
"vitepress": "^1.0.0-rc.4"
"vitepress": "^1.2.3"
}
}

View File

@@ -119,58 +119,43 @@ func CreateDir(absPath string) error {
// 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)
srcInfo, err := os.Stat(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
return fmt.Errorf("failed to get source directory info: %w", err)
}
// get srcPath's file info
srcFileInfo, err := os.Stat(srcPath)
if err != nil {
return err
if !srcInfo.IsDir() {
return fmt.Errorf("source path is not a directory: %s", srcPath)
}
// create dstPath with srcPath's mode
err = os.MkdirAll(dstPath, srcFileInfo.Mode())
err = os.MkdirAll(dstPath, 0755)
if err != nil {
return err
return fmt.Errorf("failed to create destination directory: %w", 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)
entries, err := os.ReadDir(srcPath)
if err != nil {
return fmt.Errorf("failed to read source directory: %w", err)
}
for _, entry := range entries {
srcDir := filepath.Join(srcPath, entry.Name())
dstDir := filepath.Join(dstPath, entry.Name())
if entry.IsDir() {
err := CopyDir(srcDir, dstDir)
if err != nil {
return err
}
} else {
err = CopyFile(path, curDstPath)
if err != nil {
return err
}
err = os.Chmod(curDstPath, info.Mode())
err := CopyFile(srcDir, dstDir)
if err != nil {
return err
}
}
return err
})
}
return err
return nil
}
// IsDir checks if the path is directory or not.

View File

@@ -7,6 +7,10 @@ package maputil
import (
"fmt"
"reflect"
"sort"
"strings"
"golang.org/x/exp/constraints"
"github.com/duke-git/lancet/v2/slice"
)
@@ -306,7 +310,7 @@ func HasKey[K comparable, V any](m map[K]V, key K) bool {
}
// MapToStruct converts map to struct
// Play: todo
// Play: https://go.dev/play/p/7wYyVfX38Dp
func MapToStruct(m map[string]any, structObj any) error {
for k, v := range m {
err := setStructField(structObj, k, v)
@@ -375,8 +379,7 @@ func getFieldNameByJsonTag(structObj any, jsonTag string) string {
for i := 0; i < s.NumField(); i++ {
field := s.Field(i)
tag := field.Tag
name := tag.Get("json")
name, _, _ := strings.Cut(tag.Get("json"), ",")
if name == jsonTag {
return field.Name
}
@@ -384,3 +387,64 @@ func getFieldNameByJsonTag(structObj any, jsonTag string) string {
return ""
}
// ToSortedSlicesDefault converts a map to two slices sorted by key: one for the keys and another for the values.
// Play: https://go.dev/play/p/43gEM2po-qy
func ToSortedSlicesDefault[K constraints.Ordered, V any](m map[K]V) ([]K, []V) {
keys := make([]K, 0, len(m))
// store the maps keys into a slice
for k := range m {
keys = append(keys, k)
}
// sort the slice of keys
sort.Slice(keys, func(i, j int) bool {
return keys[i] < keys[j]
})
// adjust the order of values according to the sorted keys
sortedValues := make([]V, len(keys))
for i, k := range keys {
sortedValues[i] = m[k]
}
return keys, sortedValues
}
// ToSortedSlicesWithComparator converts a map to two slices sorted by key and using a custom comparison function:
// one for the keys and another for the values.
// Play: https://go.dev/play/p/0nlPo6YLdt3
func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V) {
keys := make([]K, 0, len(m))
// store the maps keys into a slice
for k := range m {
keys = append(keys, k)
}
// sort the key slice using the provided comparison function
sort.Slice(keys, func(i, j int) bool {
return comparator(keys[i], keys[j])
})
// adjust the order of values according to the sorted keys
sortedValues := make([]V, len(keys))
for i, k := range keys {
sortedValues[i] = m[k]
}
return keys, sortedValues
}
// GetOrSet returns value of the given key or set the given value value if not present.
// Play: todo
func GetOrSet[K comparable, V any](m map[K]V, key K, value V) V {
if v, ok := m[key]; ok {
return v
}
m[key] = value
return value
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"sort"
"strconv"
"time"
)
func ExampleKeys() {
@@ -450,3 +451,92 @@ func ExampleHasKey() {
// true
// false
}
func ExampleMapToStruct() {
personReqMap := map[string]any{
"name": "Nothin",
"max_age": 35,
"page": 1,
"pageSize": 10,
}
type PersonReq struct {
Name string `json:"name"`
MaxAge int `json:"max_age"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
var personReq PersonReq
_ = MapToStruct(personReqMap, &personReq)
fmt.Println(personReq)
// Output:
// {Nothin 35 1 10}
}
func ExampleToSortedSlicesDefault() {
m := map[int]string{
1: "a",
3: "c",
2: "b",
}
keys, values := ToSortedSlicesDefault(m)
fmt.Println(keys)
fmt.Println(values)
// Output:
// [1 2 3]
// [a b c]
}
func ExampleToSortedSlicesWithComparator() {
m1 := map[time.Time]string{
time.Date(2024, 3, 31, 0, 0, 0, 0, time.UTC): "today",
time.Date(2024, 3, 30, 0, 0, 0, 0, time.UTC): "yesterday",
time.Date(2024, 4, 1, 0, 0, 0, 0, time.UTC): "tomorrow",
}
keys1, values1 := ToSortedSlicesWithComparator(m1, func(a, b time.Time) bool {
return a.Before(b)
})
m2 := map[int]string{
1: "a",
3: "c",
2: "b",
}
keys2, values2 := ToSortedSlicesWithComparator(m2, func(a, b int) bool {
return a > b
})
fmt.Println(keys1)
fmt.Println(values1)
fmt.Println(keys2)
fmt.Println(values2)
// Output:
// [2024-03-30 00:00:00 +0000 UTC 2024-03-31 00:00:00 +0000 UTC 2024-04-01 00:00:00 +0000 UTC]
// [yesterday today tomorrow]
// [3 2 1]
// [c b a]
}
func ExampleGetOrSet() {
m := map[int]string{
1: "a",
}
result1 := GetOrSet(m, 1, "1")
result2 := GetOrSet(m, 2, "b")
fmt.Println(result1)
fmt.Println(result2)
// Output:
// a
// b
}

View File

@@ -1,9 +1,11 @@
package maputil
import (
"math/cmplx"
"sort"
"strconv"
"testing"
"time"
"github.com/duke-git/lancet/v2/internal"
)
@@ -483,7 +485,7 @@ func TestMapToStruct(t *testing.T) {
Name string `json:"name"`
Age int `json:"age"`
Phone string `json:"phone"`
Addr *Address `json:"address"`
Addr *Address `json:"address,omitempty"`
}
Address struct {
@@ -511,3 +513,197 @@ func TestMapToStruct(t *testing.T) {
assert.Equal("test", p.Addr.Street)
assert.Equal(1, p.Addr.Number)
}
func TestToSortedSlicesDefault(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestToSortedSlicesDefault")
testCases1 := []struct {
name string
inputMap map[string]any
expKeys []string
expValues []any
}{
{
name: "Empty Map",
inputMap: map[string]any{},
expKeys: []string{},
expValues: []any{},
},
{
name: "Unsorted Map",
inputMap: map[string]any{"c": 3, "a": 1.6, "b": 2},
expKeys: []string{"a", "b", "c"},
expValues: []any{1.6, 2, 3},
},
}
for _, tc := range testCases1 {
t.Run(tc.name, func(t *testing.T) {
keys, values := ToSortedSlicesDefault(tc.inputMap)
assert.Equal(tc.expKeys, keys)
assert.Equal(tc.expValues, values)
})
}
testCases2 := map[int64]string{
7: "seven",
3: "three",
5: "five",
}
actualK2, actualV2 := ToSortedSlicesDefault(testCases2)
case2Keys := []int64{3, 5, 7}
case2Values := []string{"three", "five", "seven"}
assert.Equal(case2Keys, actualK2)
assert.Equal(case2Values, actualV2)
}
func TestToSortedSlicesWithComparator(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestToSortedSlicesWithComparator")
type Account struct {
ID int
Name string
CreateTime time.Time
FavorComplexNumber complex128
Signal chan struct{}
}
type testCase struct {
name string
inputMap map[Account]any
lessFunc func(a, b Account) bool
expKeys []Account
expValues []any
}
now := time.Now()
tomorrow := now.AddDate(0, 0, 1)
yesterday := now.AddDate(0, 0, -1)
account1 := Account{ID: 1, Name: "cya", CreateTime: now, FavorComplexNumber: complex(1.2, 3),
Signal: make(chan struct{}, 10)}
account2 := Account{ID: 2, Name: "Cannian", CreateTime: tomorrow, FavorComplexNumber: complex(1.2, 2),
Signal: make(chan struct{}, 7)}
account3 := Account{ID: 3, Name: "Clauviou", CreateTime: yesterday, FavorComplexNumber: complex(3, 4),
Signal: make(chan struct{}, 3)}
account1.Signal <- struct{}{}
account2.Signal <- struct{}{}
account2.Signal <- struct{}{}
testCases := []testCase{
{
name: "Sorted by Account ID",
inputMap: map[Account]any{
account1: 100,
account2: 200,
account3: 300,
},
lessFunc: func(a, b Account) bool { return a.ID < b.ID },
expKeys: []Account{account1, account2, account3},
expValues: []any{100, 200, 300},
},
{
name: "Reverse Sorted by Account ID",
inputMap: map[Account]any{
account1: 100,
account2: 200,
account3: 300,
},
lessFunc: func(a, b Account) bool { return a.ID > b.ID },
expKeys: []Account{account3, account2, account1},
expValues: []any{300, 200, 100},
},
{
name: "Sorted by Account Name",
inputMap: map[Account]any{
account1: 100,
account2: 200,
account3: 300,
},
lessFunc: func(a, b Account) bool { return a.Name < b.Name },
expKeys: []Account{account2, account3, account1},
expValues: []any{200, 300, 100},
},
{
name: "Empty Map",
inputMap: map[Account]any{},
lessFunc: func(a, b Account) bool { return a.ID < b.ID },
expKeys: []Account{},
expValues: []any{},
},
{
name: "Sorted by Account CreateTime",
inputMap: map[Account]any{
account1: "now",
account2: "tomorrow",
account3: "yesterday",
},
lessFunc: func(a, b Account) bool { return a.CreateTime.Before(b.CreateTime) },
expKeys: []Account{account3, account1, account2},
expValues: []any{"yesterday", "now", "tomorrow"},
},
{
name: "Sorted by Account FavorComplexNumber",
inputMap: map[Account]any{
account1: "1.2+3i",
account2: "1.2+2i",
account3: "3+4i",
},
lessFunc: func(a, b Account) bool { return cmplx.Abs(a.FavorComplexNumber) < cmplx.Abs(b.FavorComplexNumber) },
expKeys: []Account{account2, account1, account3},
expValues: []any{"1.2+2i", "1.2+3i", "3+4i"},
},
{
name: "Sort by the buffer capacity of the channel",
inputMap: map[Account]any{
account1: 10,
account2: 7,
account3: 3,
},
lessFunc: func(a, b Account) bool {
return cap(a.Signal) < cap(b.Signal)
},
expKeys: []Account{account3, account2, account1},
expValues: []any{3, 7, 10},
},
{
name: "Sort by the number of elements in the channel",
inputMap: map[Account]any{
account1: 1,
account2: 2,
account3: 0,
},
lessFunc: func(a, b Account) bool { return len(a.Signal) < len(b.Signal) },
expKeys: []Account{account3, account1, account2},
expValues: []any{0, 1, 2},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
keys, values := ToSortedSlicesWithComparator(tc.inputMap, tc.lessFunc)
assert.Equal(tc.expKeys, keys)
assert.Equal(tc.expValues, values)
})
}
}
func TestGetOrSet(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestGetOrSet")
m := map[int]string{
1: "a",
}
result1 := GetOrSet(m, 1, "1")
result2 := GetOrSet(m, 2, "b")
assert.Equal("a", result1)
assert.Equal("b", result2)
}

View File

@@ -28,7 +28,6 @@ import (
"strings"
"time"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/slice"
)
@@ -103,19 +102,22 @@ type HttpRequest struct {
// HttpClientConfig contains some configurations for http client
type HttpClientConfig struct {
Timeout time.Duration
SSLEnabled bool
TLSConfig *tls.Config
Compressed bool
HandshakeTimeout time.Duration
ResponseTimeout time.Duration
Verbose bool
Proxy *url.URL
}
// defaultHttpClientConfig defalut client config.
var defaultHttpClientConfig = &HttpClientConfig{
Timeout: 50 * time.Second,
Compressed: false,
HandshakeTimeout: 20 * time.Second,
ResponseTimeout: 40 * time.Second,
HandshakeTimeout: 10 * time.Second,
ResponseTimeout: 10 * time.Second,
}
// HttpClient is used for sending http request.
@@ -131,6 +133,7 @@ type HttpClient struct {
func NewHttpClient() *HttpClient {
client := &HttpClient{
Client: &http.Client{
Timeout: defaultHttpClientConfig.Timeout,
Transport: &http.Transport{
TLSHandshakeTimeout: defaultHttpClientConfig.HandshakeTimeout,
ResponseHeaderTimeout: defaultHttpClientConfig.ResponseTimeout,
@@ -164,6 +167,11 @@ func NewHttpClientWithConfig(config *HttpClientConfig) *HttpClient {
client.TLS = config.TLSConfig
}
if config.Proxy != nil {
transport := client.Client.Transport.(*http.Transport)
transport.Proxy = http.ProxyURL(config.Proxy)
}
return client
}
@@ -363,11 +371,20 @@ func validateRequest(req *HttpRequest) error {
// Play: https://go.dev/play/p/pFqMkM40w9z
func StructToUrlValues(targetStruct any) (url.Values, error) {
result := url.Values{}
s, err := convertor.StructToMap(targetStruct)
var m map[string]interface{}
jsonBytes, err := json.Marshal(targetStruct)
if err != nil {
return nil, err
}
for k, v := range s {
err = json.Unmarshal(jsonBytes, &m)
if err != nil {
return nil, err
}
for k, v := range m {
result.Add(k, fmt.Sprintf("%v", v))
}

View File

@@ -5,12 +5,12 @@ import (
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
"github.com/duke-git/lancet/v2/internal"
)
@@ -23,7 +23,8 @@ func TestHttpGet(t *testing.T) {
resp, err := HttpGet(url, header)
if err != nil {
log.Fatal(err)
t.Log("net error: " + err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
@@ -44,8 +45,10 @@ func TestHttpPost(t *testing.T) {
resp, err := HttpPost(url, header, nil, bodyParams)
if err != nil {
log.Fatal(err)
t.Log("net error: " + err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -54,21 +57,18 @@ func TestHttpPostFormData(t *testing.T) {
apiUrl := "https://jsonplaceholder.typicode.com/todos"
header := map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
// "Content-Type": "multipart/form-data",
}
postData := url.Values{}
postData.Add("userId", "1")
postData.Add("title", "TestToDo")
// postData := make(map[string]string)
// postData["userId"] = "1"
// postData["title"] = "title"
resp, err := HttpPost(apiUrl, header, nil, postData)
if err != nil {
log.Fatal(err)
t.Log("net error: " + err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -88,8 +88,10 @@ func TestHttpPut(t *testing.T) {
resp, err := HttpPut(url, header, nil, bodyParams)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -109,8 +111,10 @@ func TestHttpPatch(t *testing.T) {
resp, err := HttpPatch(url, header, nil, bodyParams)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -119,8 +123,10 @@ func TestHttpDelete(t *testing.T) {
url := "https://jsonplaceholder.typicode.com/todos/1"
resp, err := HttpDelete(url)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
t.Log("response: ", resp.StatusCode, string(body))
}
@@ -147,7 +153,8 @@ func TestParseResponse(t *testing.T) {
resp, err := HttpGet(url, header)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
type Todo struct {
@@ -160,8 +167,10 @@ func TestParseResponse(t *testing.T) {
toDoResp := &Todo{}
err = ParseHttpResponse(resp, toDoResp)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
t.Log("response: ", toDoResp)
}
@@ -178,7 +187,8 @@ func TestHttpClient_Get(t *testing.T) {
httpClient := NewHttpClient()
resp, err := httpClient.SendRequest(request)
if err != nil || resp.StatusCode != 200 {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
type Todo struct {
@@ -215,7 +225,8 @@ func TestHttpClent_Post(t *testing.T) {
httpClient := NewHttpClient()
resp, err := httpClient.SendRequest(request)
if err != nil {
log.Fatal(err)
t.Log("net error: ", err.Error())
return
}
body, _ := io.ReadAll(resp.Body)
@@ -227,16 +238,25 @@ func TestStructToUrlValues(t *testing.T) {
assert := internal.NewAssert(t, "TestStructToUrlValues")
type CommReq struct {
Version string `json:"version"`
}
type TodoQuery struct {
Id int `json:"id"`
UserId int `json:"userId"`
Name string `json:"name,omitempty"`
Id int `json:"id"`
UserId int `json:"userId"`
Name string `json:"name,omitempty"`
CommReq `json:",inline"`
}
item1 := TodoQuery{
Id: 1,
UserId: 123,
Name: "",
CommReq: CommReq{
Version: "1.0",
},
}
todoValues, err := StructToUrlValues(item1)
if err != nil {
t.Errorf("params is invalid: %v", err)
@@ -245,19 +265,10 @@ func TestStructToUrlValues(t *testing.T) {
assert.Equal("1", todoValues.Get("id"))
assert.Equal("123", todoValues.Get("userId"))
assert.Equal("", todoValues.Get("name"))
item2 := TodoQuery{
Id: 2,
UserId: 456,
}
queryValues2, _ := StructToUrlValues(item2)
assert.Equal("2", queryValues2.Get("id"))
assert.Equal("456", queryValues2.Get("userId"))
assert.Equal("", queryValues2.Get("name"))
assert.Equal("1.0", todoValues.Get("version"))
}
func handleFileRequest(t *testing.T, w http.ResponseWriter, r *http.Request) {
func handleFileRequest(t *testing.T, _ http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(1024)
if err != nil {
t.Fatal(err)
@@ -361,3 +372,25 @@ func TestSendRequestWithFilePath(t *testing.T) {
t.Fatalf("expected %d, got %d", http.StatusOK, resp.StatusCode)
}
}
func TestProxy(t *testing.T) {
config := &HttpClientConfig{
HandshakeTimeout: 20 * time.Second,
ResponseTimeout: 40 * time.Second,
// Use the proxy ip to add it here
//Proxy: &url.URL{
// Scheme: "http",
// Host: "46.17.63.166:18888",
//},
}
httpClient := NewHttpClientWithConfig(config)
resp, err := httpClient.Get("https://www.ipplus360.com/getLocation")
if err != nil {
t.Log("net error: ", err.Error())
return
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected %d, got %d", http.StatusOK, resp.StatusCode)
}
}

View File

@@ -11,17 +11,24 @@ import (
// Of returns a pointer to the value `v`.
// Play: https://go.dev/play/p/HFd70x4DrMj
func Of[T any](v T) *T {
if IsNil(v) {
return nil
}
return &v
}
// Unwrap returns the value from the pointer.
// Play: https://go.dev/play/p/cgeu3g7cjWb
//
// Play: https://go.dev/play/p/cgeu3g7cjWb
// Deprecated: Please use UnwrapOr
func Unwrap[T any](p *T) T {
return *p
}
// UnwarpOr returns the value from the pointer or fallback if the pointer is nil.
// Play: https://go.dev/play/p/mmNaLC38W8C
//
// Play: https://go.dev/play/p/mmNaLC38W8C
// Deprecated: Please use UnwrapOr
func UnwarpOr[T any](p *T, fallback T) T {
if p == nil {
return fallback
@@ -30,7 +37,9 @@ func UnwarpOr[T any](p *T, fallback T) T {
}
// UnwarpOrDefault returns the value from the pointer or the default value if the pointer is nil.
// Play: https://go.dev/play/p/ZnGIHf8_o4E
//
// Play: https://go.dev/play/p/ZnGIHf8_o4E
// Deprecated: Please use UnwrapOr
func UnwarpOrDefault[T any](p *T) T {
var v T
@@ -40,9 +49,24 @@ func UnwarpOrDefault[T any](p *T) T {
return *p
}
// UnwrapOr returns the value from the pointer or fallback if the pointer is nil.
func UnwrapOr[T any](p *T, fallback ...T) T {
if !IsNil(p) {
return *p
}
if len(fallback) > 0 {
return fallback[0]
}
var t T
return t
}
// ExtractPointer returns the underlying value by the given interface type
// Play: https://go.dev/play/p/D7HFjeWU2ZP
func ExtractPointer(value any) any {
if IsNil(value) {
return value
}
t := reflect.TypeOf(value)
v := reflect.ValueOf(value)
@@ -56,3 +80,8 @@ func ExtractPointer(value any) any {
return nil
}
// IsNil returns true if the given interface is nil or the underlying value is nil.
func IsNil(i interface{}) bool {
return i == nil || (reflect.ValueOf(i).Kind() == reflect.Ptr && reflect.ValueOf(i).IsNil())
}

View File

@@ -90,3 +90,61 @@ func ExampleExtractPointer() {
// Output:
// 1
}
func ExampleIsNil() {
a := 1
b := &a
c := &b
d := &c
e := &d
var f *int
result1 := IsNil(a)
result2 := IsNil(b)
result3 := IsNil(c)
result4 := IsNil(d)
result5 := IsNil(e)
result6 := IsNil(f)
result7 := IsNil(nil)
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
fmt.Println(result5)
fmt.Println(result6)
fmt.Println(result7)
// Output:
// false
// false
// false
// false
// false
// true
// true
}
func ExampleUnwrapOr() {
a := 123
b := "abc"
var c *int
var d *string
result1 := UnwrapOr(&a, 456)
result2 := UnwrapOr(&b, "efg")
result3 := UnwrapOr(c, 456)
result4 := UnwrapOr(d, "def")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
fmt.Println(result4)
// Output:
// 123
// abc
// 456
// def
}

View File

@@ -47,10 +47,10 @@ func TestUnwarpOr(t *testing.T) {
assert.Equal("def", UnwarpOr(d, "def"))
}
func TestUnwarpOrDefault(t *testing.T) {
func TestUnwrapOrDefault(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestUnwarpOrDefault")
assert := internal.NewAssert(t, "TestUnwrapOrDefault")
a := 123
b := "abc"

View File

@@ -50,12 +50,23 @@ func ContainBy[T any](slice []T, predicate func(item T) bool) bool {
// ContainSubSlice check if the slice contain a given subslice or not.
// Play: https://go.dev/play/p/bcuQ3UT6Sev
func ContainSubSlice[T comparable](slice, subSlice []T) bool {
for _, v := range subSlice {
if !Contain(slice, v) {
if len(subSlice) == 0 {
return true
}
if len(slice) == 0 {
return false
}
elementMap := make(map[T]struct{}, len(slice))
for _, item := range slice {
elementMap[item] = struct{}{}
}
for _, item := range subSlice {
if _, ok := elementMap[item]; !ok {
return false
}
}
return true
}
@@ -81,26 +92,32 @@ func Chunk[T any](slice []T, size int) [][]T {
return result
}
// Compact creates an slice with all falsey values removed. The values false, nil, 0, and "" are falsey.
// Compact creates a slice with all falsey values removed. The values false, nil, 0, and "" are falsey.
// Play: https://go.dev/play/p/pO5AnxEr3TK
func Compact[T comparable](slice []T) []T {
var zero T
result := []T{}
result := make([]T, 0, len(slice))
for _, v := range slice {
if v != zero {
result = append(result, v)
}
}
return result
return result[:len(result):len(result)]
}
// Concat creates a new slice concatenating slice with any additional slices.
// Play: https://go.dev/play/p/gPt-q7zr5mk
func Concat[T any](slice []T, slices ...[]T) []T {
result := append([]T{}, slice...)
func Concat[T any](slices ...[]T) []T {
totalLen := 0
for _, v := range slices {
totalLen += len(v)
if totalLen < 0 {
panic("len out of range")
}
}
result := make([]T, 0, totalLen)
for _, v := range slices {
result = append(result, v...)
@@ -109,7 +126,7 @@ func Concat[T any](slice []T, slices ...[]T) []T {
return result
}
// Difference creates an slice of whose element in slice but not in comparedSlice.
// Difference creates a slice of whose element in slice but not in comparedSlice.
// Play: https://go.dev/play/p/VXvadzLzhDa
func Difference[T comparable](slice, comparedSlice []T) []T {
result := []T{}
@@ -755,21 +772,14 @@ func UpdateAt[T any](slice []T, index int, value T) []T {
// Play: https://go.dev/play/p/AXw0R3ZTE6a
func Unique[T comparable](slice []T) []T {
result := []T{}
for i := 0; i < len(slice); i++ {
v := slice[i]
skip := true
for j := range result {
if v == result[j] {
skip = false
break
}
}
if skip {
result = append(result, v)
exists := map[T]bool{}
for _, t := range slice {
if exists[t] {
continue
}
exists[t] = true
result = append(result, t)
}
return result
}
@@ -786,6 +796,46 @@ func UniqueBy[T comparable](slice []T, iteratee func(item T) T) []T {
return Unique(result)
}
// UniqueByField remove duplicate elements in struct slice by struct field.
// Play: todo
func UniqueByField[T any](slice []T, field string) ([]T, error) {
seen := map[any]struct{}{}
var result []T
for _, item := range slice {
val, err := getField(item, field)
if err != nil {
return nil, fmt.Errorf("get field %s failed: %v", field, err)
}
if _, ok := seen[val]; !ok {
seen[val] = struct{}{}
result = append(result, item)
}
}
return result, nil
}
func getField[T any](item T, field string) (interface{}, error) {
v := reflect.ValueOf(item)
t := reflect.TypeOf(item)
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("data type %T not support, shuld be struct or pointer to struct", item)
}
f := v.FieldByName(field)
if !f.IsValid() {
return nil, fmt.Errorf("field name %s not found", field)
}
return v.FieldByName(field).Interface(), nil
}
// Union creates a slice of unique elements, in order, from all given slices.
// Play: https://go.dev/play/p/hfXV1iRIZOf
func Union[T comparable](slices ...[]T) []T {
@@ -823,16 +873,11 @@ func UnionBy[T any, V comparable](predicate func(item T) V, slices ...[]T) []T {
return result
}
// Deprecated: Please use Concat() function instead.
// Merge all given slices into one slice.
// Play: https://go.dev/play/p/lbjFp784r9N
func Merge[T any](slices ...[]T) []T {
result := make([]T, 0)
for _, v := range slices {
result = append(result, v...)
}
return result
return Concat(slices...)
}
// Intersection creates a slice of unique elements that included by all slices.
@@ -1240,7 +1285,7 @@ func Partition[T any](slice []T, predicates ...func(item T) bool) [][]T {
}
// Breaks a list into two parts at the point where the predicate for the first time is true.
// Play: Todo
// Play: https://go.dev/play/p/yLYcBTyeQIz
func Break[T any](values []T, predicate func(T) bool) ([]T, []T) {
a := make([]T, 0)
b := make([]T, 0)
@@ -1275,7 +1320,7 @@ func Random[T any](slice []T) (val T, idx int) {
}
// RightPadding adds padding to the right end of a slice.
// Play: Todo
// Play: https://go.dev/play/p/0_2rlLEMBXL
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
if paddingLength == 0 {
return slice
@@ -1287,7 +1332,7 @@ func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
}
// LeftPadding adds padding to the left begin of a slice.
// Play: Todo
// Play: https://go.dev/play/p/jlQVoelLl2k
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
if paddingLength == 0 {
return slice

View File

@@ -780,6 +780,28 @@ func ExampleUniqueBy() {
// [1 2 0]
}
func ExampleUniqueByField() {
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
users := []User{
{ID: 1, Name: "a"},
{ID: 2, Name: "b"},
{ID: 1, Name: "c"},
}
result, err := UniqueByField(users, "ID")
if err != nil {
}
fmt.Println(result)
// Output:
// [{1 a} {2 b}]
}
func ExampleUnion() {
nums1 := []int{1, 3, 4, 6}
nums2 := []int{1, 2, 5, 6}

View File

@@ -109,6 +109,19 @@ func TestConcat(t *testing.T) {
assert.Equal([]int{1, 2, 3, 4, 5}, Concat([]int{1, 2, 3}, []int{4}, []int{5}))
}
func BenchmarkConcat(b *testing.B) {
slice1 := []int{1, 2, 3}
slice2 := []int{4, 5, 6}
slice3 := []int{7, 8, 9}
for i := 0; i < b.N; i++ {
result := Concat(slice1, slice2, slice3)
if len(result) == 0 {
b.Fatal("unexpected empty result")
}
}
}
func TestEqual(t *testing.T) {
t.Parallel()
@@ -723,6 +736,33 @@ func TestUniqueBy(t *testing.T) {
assert.Equal([]int{1, 2, 3, 0}, actual)
}
func TestUniqueByField(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestUniqueByField")
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
users := []User{
{ID: 1, Name: "a"},
{ID: 2, Name: "b"},
{ID: 1, Name: "c"},
}
uniqueUsers, err := UniqueByField(users, "ID")
if err != nil {
t.Error(err)
}
assert.Equal([]User{
{ID: 1, Name: "a"},
{ID: 2, Name: "b"},
}, uniqueUsers)
}
func TestUnion(t *testing.T) {
t.Parallel()

View File

@@ -617,3 +617,24 @@ func HammingDistance(a, b string) (int, error) {
return distance, nil
}
// Concat uses the strings.Builder to concatenate the input strings.
// - `length` is the expected length of the concatenated string.
// - if you are unsure about the length of the string to be concatenated, please pass 0 or a negative number.
func Concat(length int, str ...string) string {
if len(str) == 0 {
return ""
}
sb := strings.Builder{}
if length <= 0 {
sb.Grow(len(str[0]) * len(str))
} else {
sb.Grow(length)
}
for _, s := range str {
sb.WriteString(s)
}
return sb.String()
}

View File

@@ -680,3 +680,17 @@ func ExampleHammingDistance() {
// 3
// 1
}
func ExampleConcat() {
result1 := Concat(12, "Hello", " ", "World", "!")
result2 := Concat(11, "Go", " ", "Language")
result3 := Concat(0, "An apple a ", "day", "keeps the", " doctor away")
fmt.Println(result1)
fmt.Println(result2)
fmt.Println(result3)
// Output:
// Hello World!
// Go Language
// An apple a daykeeps the doctor away
}

View File

@@ -561,6 +561,7 @@ func TestContainsAny(t *testing.T) {
}
func TestRemoveWhiteSpace(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRemoveWhiteSpace")
str := " hello \r\n \t world"
@@ -571,6 +572,7 @@ func TestRemoveWhiteSpace(t *testing.T) {
}
func TestSubInBetween(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestSubInBetween")
str := "abcde"
@@ -583,6 +585,7 @@ func TestSubInBetween(t *testing.T) {
}
func TestHammingDistance(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "HammingDistance")
hd := func(a, b string) int {
@@ -604,3 +607,16 @@ func TestHammingDistance(t *testing.T) {
assert.Equal(0, hd("日本語", "日本語"))
assert.Equal(3, hd("日本語", "語日本"))
}
func TestConcat(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestConcat")
assert.Equal("", Concat(0))
assert.Equal("a", Concat(1, "a"))
assert.Equal("ab", Concat(2, "a", "b"))
assert.Equal("abc", Concat(3, "a", "b", "c"))
assert.Equal("abc", Concat(3, "a", "", "b", "c", ""))
assert.Equal("你好,世界!", Concat(0, "你好", "", "", "世界!", ""))
assert.Equal("Hello World!", Concat(0, "Hello", " Wo", "r", "ld!", ""))
}

View File

@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"net"
"net/mail"
"net/url"
"reflect"
"regexp"
@@ -24,7 +25,7 @@ var (
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`)
emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
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(`^(\d{17})([0-9]|X|x)$`)
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
@@ -213,7 +214,7 @@ func IsIpV4(ipstr string) bool {
if ip == nil {
return false
}
return strings.Contains(ipstr, ".")
return ip.To4() != nil
}
// IsIpV6 check if the string is a ipv6 address.
@@ -223,7 +224,7 @@ func IsIpV6(ipstr string) bool {
if ip == nil {
return false
}
return strings.Contains(ipstr, ":")
return ip.To4() == nil && len(ip) == net.IPv6len
}
// IsPort check if the string is a valid net port.
@@ -264,7 +265,10 @@ func IsDns(dns string) bool {
// IsEmail check if the string is a email address.
// Play: https://go.dev/play/p/Os9VaFlT33G
func IsEmail(email string) bool {
return emailMatcher.MatchString(email)
_, err := mail.ParseAddress(email)
return err == nil
// return emailMatcher.MatchString(email)
}
// IsChineseMobile check if the string is chinese mobile number.

View File

@@ -285,6 +285,7 @@ func TestIsEmail(t *testing.T) {
assert := internal.NewAssert(t, "TestIsEmail")
assert.Equal(true, IsEmail("abc@xyz.com"))
assert.Equal(false, IsEmail("@abc@xyz.com"))
assert.Equal(false, IsEmail("a.b@@com"))
}