From 5e6e8d82a8aae4aa6de54e526e0e81049b960b4c Mon Sep 17 00:00:00 2001 From: Cannian <2081215119@qq.com> Date: Sun, 31 Mar 2024 21:04:55 +0800 Subject: [PATCH] feat(maputil): add ToSortedSlicesDefault method and ToSortedSlicesWithComparator method (#206) --- docs/api/packages/maputil.md | 94 +++++++++++++++++ docs/en/api/packages/maputil.md | 97 +++++++++++++++++ maputil/map.go | 49 +++++++++ maputil/map_example_test.go | 74 +++++++++++++ maputil/map_test.go | 180 ++++++++++++++++++++++++++++++++ 5 files changed, 494 insertions(+) diff --git a/docs/api/packages/maputil.md b/docs/api/packages/maputil.md index f6da5c3..663f761 100644 --- a/docs/api/packages/maputil.md +++ b/docs/api/packages/maputil.md @@ -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() { } ``` +### ToSortedSlicesDefault + +

将map的key和value转化成两个根据key的值从小到大排序的切片,value切片中元素的位置与key对应。

+ +函数签名: + +```go +func ToSortedSlicesDefault[K constraints.Ordered, V any](m map[K]V) ([]K, []V) +``` + +示例:[运行](todo) + +```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] +} +``` + +### ToSortedSlicesWithComparator + +

将map的key和value转化成两个使用比较器函数根据key的值自定义排序规则的切片,value切片中元素的位置与key对应。

+ +函数签名: + +```go +func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V) +``` + +示例:[运行](todo) + +```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] +} +``` + ### NewConcurrentMap

ConcurrentMap协程安全的map结构。

diff --git a/docs/en/api/packages/maputil.md b/docs/en/api/packages/maputil.md index 6e12a07..894803e 100644 --- a/docs/en/api/packages/maputil.md +++ b/docs/en/api/packages/maputil.md @@ -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() { } ``` +### ToSortedSlicesDefault + +

+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.

+ +Signature: + +```go +func ToSortedSlicesDefault[K constraints.Ordered, V any](m map[K]V) ([]K, []V) +``` + +Example:[Run](Todo) + +```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] +} +``` + +### ToSortedSlicesWithComparator + +

+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. +

+ +Signature: + +```go +func ToSortedSlicesWithComparator[K comparable, V any](m map[K]V, comparator func(a, b K) bool) ([]K, []V) +``` + +Example:[Run](Todo) + +```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] +} +``` + ### NewConcurrentMap

ConcurrentMap is like map, but is safe for concurrent use by multiple goroutines.

diff --git a/maputil/map.go b/maputil/map.go index 06106b9..e724dba 100644 --- a/maputil/map.go +++ b/maputil/map.go @@ -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 +} diff --git a/maputil/map_example_test.go b/maputil/map_example_test.go index 1472257..cadb309 100644 --- a/maputil/map_example_test.go +++ b/maputil/map_example_test.go @@ -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] +} diff --git a/maputil/map_test.go b/maputil/map_test.go index 7753637..7d01611 100644 --- a/maputil/map_test.go +++ b/maputil/map_test.go @@ -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) + }) + } +}