mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
feat(maputil): add ToSortedSlicesDefault method and ToSortedSlicesWithComparator method (#206)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user