mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-16 18:52:27 +08:00
Compare commits
14 Commits
aa74400607
...
de9ee08be4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de9ee08be4 | ||
|
|
5381450bea | ||
|
|
6853d627f4 | ||
|
|
e461acdb72 | ||
|
|
2a796adf85 | ||
|
|
5e6e8d82a8 | ||
|
|
e9280b8c25 | ||
|
|
bb6f10a1fb | ||
|
|
33b4cffe60 | ||
|
|
2b765b49e0 | ||
|
|
004dbdc32e | ||
|
|
ab50e8120a | ||
|
|
73c97af7d8 | ||
|
|
5e8a065eaa |
@@ -2024,7 +2024,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!
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -44,6 +44,8 @@ import (
|
||||
- [Minus](#Minus)
|
||||
- [IsDisjoint](#IsDisjoint)
|
||||
- [HasKey](#HasKey)
|
||||
- [ToSortedSlicesDefault](#ToSortedSlicesDefault)
|
||||
- [ToSortedSlicesWithComparator](#ToSortedSlicesWithComparator)
|
||||
- [NewConcurrentMap](#NewConcurrentMap)
|
||||
- [ConcurrentMap_Get](#ConcurrentMap_Get)
|
||||
- [ConcurrentMap_Set](#ConcurrentMap_Set)
|
||||
@@ -988,6 +990,98 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <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;">[运行](todo)</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;">[运行](todo)</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 := 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(keys2)
|
||||
fmt.Println(values2)
|
||||
|
||||
fmt.Println(keys1)
|
||||
fmt.Println(values1)
|
||||
|
||||
// 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]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewConcurrentMap">NewConcurrentMap</span>
|
||||
|
||||
<p>ConcurrentMap协程安全的map结构。</p>
|
||||
|
||||
@@ -94,6 +94,9 @@ import (
|
||||
- [Join](#Join)
|
||||
- [Partition](#Partition)
|
||||
- [SetToDefaultIf](#SetToDefaultIf)
|
||||
- [Break](#Break)
|
||||
- [RightPadding](#RightPadding)
|
||||
- [LeftPadding](#LeftPadding)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -2600,4 +2603,91 @@ func main() {
|
||||
// [ b c d ]
|
||||
// 3
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Break">Break</span>
|
||||
|
||||
<p>根据判断函数将切片分成两部分。它开始附加到与函数匹配的第一个元素之后的第二个切片。第一个匹配之后的所有元素都包含在第二个切片中,无论它们是否与函数匹配。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func Break[T any](values []T, predicate func(T) bool) ([]T, []T)
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
even := func(n int) bool { return n%2 == 0 }
|
||||
|
||||
resultEven, resultAfterFirstEven := slice.Break(nums, even)
|
||||
|
||||
fmt.Println(resultEven)
|
||||
fmt.Println(resultAfterFirstEven)
|
||||
|
||||
// Output:
|
||||
// [1]
|
||||
// [2 3 4 5]
|
||||
}
|
||||
```
|
||||
|
||||
<span id="RightPadding">RightPadding</span>
|
||||
|
||||
<p>在切片的右部添加元素。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
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]
|
||||
}
|
||||
```
|
||||
|
||||
<span id="LeftPadding">LeftPadding</span>
|
||||
|
||||
<p>在切片的左部添加元素。</p>
|
||||
|
||||
<b>函数签名:</b>
|
||||
|
||||
```go
|
||||
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T
|
||||
```
|
||||
|
||||
<b>示例:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
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]
|
||||
}
|
||||
```
|
||||
@@ -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,8 @@ import (
|
||||
- [Minus](#Minus)
|
||||
- [IsDisjoint](#IsDisjoint)
|
||||
- [HasKey](#HasKey)
|
||||
- [ToSortedSlicesDefault](#ToSortedSlicesDefault)
|
||||
- [ToSortedSlicesWithComparator](#ToSortedSlicesWithComparator)
|
||||
- [NewConcurrentMap](#NewConcurrentMap)
|
||||
- [ConcurrentMap_Get](#ConcurrentMap_Get)
|
||||
- [ConcurrentMap_Set](#ConcurrentMap_Set)
|
||||
@@ -992,6 +994,101 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### <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](Todo)</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>
|
||||
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](Todo)</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 := 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(keys2)
|
||||
fmt.Println(values2)
|
||||
|
||||
fmt.Println(keys1)
|
||||
fmt.Println(values1)
|
||||
|
||||
// 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]
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="NewConcurrentMap">NewConcurrentMap</span>
|
||||
|
||||
<p>ConcurrentMap is like map, but is safe for concurrent use by multiple goroutines.</p>
|
||||
|
||||
@@ -94,6 +94,9 @@ import (
|
||||
- [Join](#Join)
|
||||
- [Partition](#Partition)
|
||||
- [SetToDefaultIf](#SetToDefaultIf)
|
||||
- [Break](#Break)
|
||||
- [RightPadding](#RightPadding)
|
||||
- [LeftPadding](#LeftPadding)
|
||||
|
||||
<div STYLE="page-break-after: always;"></div>
|
||||
|
||||
@@ -2597,4 +2600,91 @@ func main() {
|
||||
// [ b c d ]
|
||||
// 3
|
||||
}
|
||||
```
|
||||
|
||||
### <span id="Break">Break</span>
|
||||
|
||||
<p>Splits a slice into two based on a predicate function. It starts appending to the second slice after the first element that matches the predicate. All elements after the first match are included in the second slice, regardless of whether they match the predicate or not.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func Break[T any](values []T, predicate func(T) bool) ([]T, []T)
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
even := func(n int) bool { return n%2 == 0 }
|
||||
|
||||
resultEven, resultAfterFirstEven := slice.Break(nums, even)
|
||||
|
||||
fmt.Println(resultEven)
|
||||
fmt.Println(resultAfterFirstEven)
|
||||
|
||||
// Output:
|
||||
// [1]
|
||||
// [2 3 4 5]
|
||||
}
|
||||
```
|
||||
|
||||
<span id="c">RightPadding</span>
|
||||
|
||||
<p>RightPadding adds padding to the right end of a slice.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := RightPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [1 2 3 4 5 0 0 0]
|
||||
}
|
||||
```
|
||||
|
||||
<span id="LeftPadding">LeftPadding</span>
|
||||
|
||||
<p>LeftPadding adds padding to the left begin of a slice.</p>
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```go
|
||||
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T
|
||||
```
|
||||
|
||||
<b>Example:</b>
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
func main() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := 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
|
||||
}
|
||||
```
|
||||
@@ -6,7 +6,9 @@ package maputil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/exp/constraints"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
@@ -384,3 +386,50 @@ 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.
|
||||
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.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleKeys() {
|
||||
@@ -450,3 +451,76 @@ 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]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package maputil
|
||||
|
||||
import (
|
||||
"math/cmplx"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
@@ -511,3 +513,181 @@ 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/duke-git/lancet/v2/slice"
|
||||
)
|
||||
|
||||
@@ -109,6 +108,7 @@ type HttpClientConfig struct {
|
||||
HandshakeTimeout time.Duration
|
||||
ResponseTimeout time.Duration
|
||||
Verbose bool
|
||||
Proxy *url.URL
|
||||
}
|
||||
|
||||
// defaultHttpClientConfig defalut client config.
|
||||
@@ -164,6 +164,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 +368,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,35 +92,41 @@ 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...)
|
||||
totalLen := len(slice)
|
||||
|
||||
for _, v := range slices {
|
||||
result = append(result, v...)
|
||||
totalLen += len(v)
|
||||
}
|
||||
|
||||
result := make([]T, 0, totalLen)
|
||||
|
||||
result = append(result, slice...)
|
||||
for _, s := range slices {
|
||||
result = append(result, s...)
|
||||
}
|
||||
|
||||
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{}
|
||||
@@ -826,7 +843,11 @@ func UnionBy[T any, V comparable](predicate func(item T) V, slices ...[]T) []T {
|
||||
// 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)
|
||||
totalLen := 0
|
||||
for _, v := range slices {
|
||||
totalLen += len(v)
|
||||
}
|
||||
result := make([]T, 0, totalLen)
|
||||
|
||||
for _, v := range slices {
|
||||
result = append(result, v...)
|
||||
@@ -1239,6 +1260,30 @@ func Partition[T any](slice []T, predicates ...func(item T) bool) [][]T {
|
||||
return result
|
||||
}
|
||||
|
||||
// Breaks a list into two parts at the point where the predicate for the first time is true.
|
||||
// Play: Todo
|
||||
func Break[T any](values []T, predicate func(T) bool) ([]T, []T) {
|
||||
a := make([]T, 0)
|
||||
b := make([]T, 0)
|
||||
if len(values) == 0 {
|
||||
return a, b
|
||||
}
|
||||
matched := false
|
||||
for _, value := range values {
|
||||
|
||||
if !matched && predicate(value) {
|
||||
matched = true
|
||||
}
|
||||
|
||||
if matched {
|
||||
b = append(b, value)
|
||||
} else {
|
||||
a = append(a, value)
|
||||
}
|
||||
}
|
||||
return a, b
|
||||
}
|
||||
|
||||
// Random get a random item of slice, return idx=-1 when slice is empty
|
||||
// Play: https://go.dev/play/p/UzpGQptWppw
|
||||
func Random[T any](slice []T) (val T, idx int) {
|
||||
@@ -1249,3 +1294,35 @@ func Random[T any](slice []T) (val T, idx int) {
|
||||
idx = random.RandInt(0, len(slice))
|
||||
return slice[idx], idx
|
||||
}
|
||||
|
||||
// RightPadding adds padding to the right end of a slice.
|
||||
// Play: Todo
|
||||
func RightPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
if paddingLength == 0 {
|
||||
return slice
|
||||
}
|
||||
for i := 0; i < paddingLength; i++ {
|
||||
slice = append(slice, paddingValue)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// LeftPadding adds padding to the left begin of a slice.
|
||||
// Play: Todo
|
||||
func LeftPadding[T any](slice []T, paddingValue T, paddingLength int) []T {
|
||||
if paddingLength == 0 {
|
||||
return slice
|
||||
}
|
||||
|
||||
paddedSlice := make([]T, len(slice)+paddingLength)
|
||||
i := 0
|
||||
for ; i < paddingLength; i++ {
|
||||
paddedSlice[i] = paddingValue
|
||||
}
|
||||
for j := 0; j < len(slice); j++ {
|
||||
paddedSlice[i] = slice[j]
|
||||
i++
|
||||
}
|
||||
|
||||
return paddedSlice
|
||||
}
|
||||
|
||||
@@ -1112,3 +1112,32 @@ func ExampleSetToDefaultIf() {
|
||||
// [ b c d ]
|
||||
// 3
|
||||
}
|
||||
|
||||
func ExampleBreak() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
even := func(n int) bool { return n%2 == 0 }
|
||||
|
||||
resultEven, resultAfterFirstEven := Break(nums, even)
|
||||
fmt.Println(resultEven)
|
||||
fmt.Println(resultAfterFirstEven)
|
||||
|
||||
// Output:
|
||||
// [1]
|
||||
// [2 3 4 5]
|
||||
}
|
||||
|
||||
func ExampleRightPadding() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := RightPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [1 2 3 4 5 0 0 0]
|
||||
}
|
||||
|
||||
func ExampleLeftPadding() {
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
padded := LeftPadding(nums, 0, 3)
|
||||
fmt.Println(padded)
|
||||
// Output:
|
||||
// [0 0 0 1 2 3 4 5]
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ package slice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/duke-git/lancet/v2/internal"
|
||||
)
|
||||
|
||||
func TestContain(t *testing.T) {
|
||||
@@ -109,6 +108,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()
|
||||
|
||||
@@ -1357,3 +1369,54 @@ func TestSetToDefaultIf(t *testing.T) {
|
||||
assert.Equal(2, count)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBreak(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "TestBreak")
|
||||
|
||||
// Test with integers
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
even := func(n int) bool { return n%2 == 0 }
|
||||
|
||||
resultEven, resultAfterFirstEven := Break(nums, even)
|
||||
assert.Equal([]int{1}, resultEven)
|
||||
assert.Equal([]int{2, 3, 4, 5}, resultAfterFirstEven)
|
||||
|
||||
// Test with strings
|
||||
strings := []string{"apple", "banana", "cherry", "date", "elderberry"}
|
||||
startsWithA := func(s string) bool { return s[0] == 'a' }
|
||||
|
||||
resultStartsWithA, resultAfterFirstStartsWithA := Break(strings, startsWithA)
|
||||
assert.Equal([]string{}, resultStartsWithA)
|
||||
assert.Equal([]string{"apple", "banana", "cherry", "date", "elderberry"}, resultAfterFirstStartsWithA)
|
||||
|
||||
// Test with empty slice
|
||||
emptySlice := []int{}
|
||||
resultEmpty, _ := Break(emptySlice, even)
|
||||
assert.Equal([]int{}, resultEmpty)
|
||||
|
||||
// Test with all elements satisfying the predicate
|
||||
allEven := []int{2, 4, 6, 8, 10}
|
||||
emptyResult, resultAllEven := Break(allEven, even)
|
||||
assert.Equal([]int{2, 4, 6, 8, 10}, resultAllEven)
|
||||
assert.Equal([]int{}, emptyResult)
|
||||
|
||||
// Test with no elements satisfying the predicate
|
||||
allOdd := []int{1, 3, 5, 7, 9}
|
||||
resultAllOdd, emptyResult := Break(allOdd, even)
|
||||
assert.Equal([]int{1, 3, 5, 7, 9}, resultAllOdd)
|
||||
assert.Equal([]int{}, emptyResult)
|
||||
}
|
||||
|
||||
func TestRightPaddingAndLeftPadding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := internal.NewAssert(t, "RightPaddingAndLeftPadding")
|
||||
|
||||
// Test with integers
|
||||
nums := []int{1, 2, 3, 4, 5}
|
||||
|
||||
padded := LeftPadding(RightPadding(nums, 0, 3), 0, 3)
|
||||
assert.Equal([]int{0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0}, padded)
|
||||
}
|
||||
|
||||
@@ -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]")
|
||||
@@ -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