mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-15 18:22:27 +08:00
Compare commits
33 Commits
004dbdc32e
...
v2.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73fb8fefd2 | ||
|
|
9cf535055d | ||
|
|
8be7b3e396 | ||
|
|
dac706d700 | ||
|
|
2097277a7d | ||
|
|
95b516e278 | ||
|
|
ca373b00a7 | ||
|
|
a220220f09 | ||
|
|
aeef0418a4 | ||
|
|
9b7d8d7abf | ||
|
|
4d21e81263 | ||
|
|
ce2397422e | ||
|
|
4b3a62b36a | ||
|
|
e054680d20 | ||
|
|
5381842eec | ||
|
|
6e0498514c | ||
|
|
967e6a3493 | ||
|
|
5b24801e49 | ||
|
|
974ba525a6 | ||
|
|
f0235c40b6 | ||
|
|
712a215ea6 | ||
|
|
7893f828d3 | ||
|
|
53fa210f09 | ||
|
|
de9ee08be4 | ||
|
|
5381450bea | ||
|
|
6853d627f4 | ||
|
|
e461acdb72 | ||
|
|
2a796adf85 | ||
|
|
5e6e8d82a8 | ||
|
|
e9280b8c25 | ||
|
|
bb6f10a1fb | ||
|
|
33b4cffe60 | ||
|
|
2b765b49e0 |
28
README.md
28
README.md
@@ -4,7 +4,7 @@
|
||||
<br/>
|
||||
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](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. <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!
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<br/>
|
||||
|
||||

|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://github.com/duke-git/lancet/releases)
|
||||
[](https://pkg.go.dev/github.com/duke-git/lancet/v2)
|
||||
[](https://goreportcard.com/report/github.com/duke-git/lancet/v2)
|
||||
[](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 的相关函数。 <a href="#index">回到目录</a></h3>
|
||||
|
||||
```go
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -40,6 +40,7 @@ export const slugify = (str: string): string =>
|
||||
export const commonConfig = defineConfig({
|
||||
title: 'Lancet',
|
||||
appearance: true,
|
||||
ignoreDeadLinks: true,
|
||||
|
||||
markdown: {
|
||||
theme: {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
```
|
||||
@@ -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 day,keeps the doctor away
|
||||
}
|
||||
```
|
||||
@@ -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 key’s 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
|
||||
}
|
||||
```
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 day,keeps the doctor away
|
||||
}
|
||||
```
|
||||
@@ -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>
|
||||
|
||||
@@ -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
1171
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,6 @@
|
||||
"docs:preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.0.0-rc.4"
|
||||
"vitepress": "^1.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 map’s 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 map’s 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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
111
slice/slice.go
111
slice/slice.go
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 day,keeps the doctor away
|
||||
}
|
||||
|
||||
@@ -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!", ""))
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user