feat: 新增 steram 包,支持 map 和 slice 的链式操作

This commit is contained in:
kercylan98 2023-07-14 11:38:49 +08:00
parent 8f9589df42
commit 10fcb54322
14 changed files with 570 additions and 14 deletions

View File

@ -2,7 +2,7 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/asynchronization"
"github.com/kercylan98/minotaur/utils/asynchronous"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/log"
"go.uber.org/zap"
@ -12,7 +12,7 @@ import (
func NewRoom[PlayerID comparable, Player game.Player[PlayerID]](guid int64, options ...RoomOption[PlayerID, Player]) *Room[PlayerID, Player] {
room := &Room[PlayerID, Player]{
guid: guid,
players: asynchronization.NewMap[PlayerID, Player](),
players: asynchronous.NewMap[PlayerID, Player](),
}
for _, option := range options {
option(room)

View File

@ -2,7 +2,7 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/asynchronization"
"github.com/kercylan98/minotaur/utils/asynchronous"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/slice"
"sync"
@ -12,7 +12,7 @@ import (
func NewRoomSeat[PlayerID comparable, Player game.Player[PlayerID]](room game.Room[PlayerID, Player], options ...RoomSeatOption[PlayerID, Player]) *RoomSeat[PlayerID, Player] {
roomSeat := &RoomSeat[PlayerID, Player]{
Room: room,
seatPS: asynchronization.NewMap[PlayerID, int](),
seatPS: asynchronous.NewMap[PlayerID, int](),
}
for _, option := range options {
option(roomSeat)

View File

@ -1,7 +1,7 @@
package report
import (
"github.com/kercylan98/minotaur/utils/asynchronization"
"github.com/kercylan98/minotaur/utils/asynchronous"
"github.com/kercylan98/minotaur/utils/hash"
"sync"
)
@ -10,7 +10,7 @@ import (
func NewDataBuried[DataID comparable, Data any](name string, hitLogic HitLogic[Data], options ...DataBuriedOption[DataID, Data]) *DataBuried[DataID, Data] {
buried := &DataBuried[DataID, Data]{
name: name,
data: asynchronization.NewMap[DataID, Data](),
data: asynchronous.NewMap[DataID, Data](),
hitLogic: hitLogic,
}
buried.setData = func(id DataID, data Data) {

View File

@ -1,14 +1,18 @@
package asynchronization
package asynchronous
import (
"encoding/json"
"github.com/kercylan98/minotaur/utils/hash"
)
func NewMap[Key comparable, value any]() *Map[Key, value] {
return &Map[Key, value]{
data: make(map[Key]value),
func NewMap[Key comparable, Value any](options ...MapOption[Key, Value]) *Map[Key, Value] {
m := &Map[Key, Value]{
data: make(map[Key]Value),
}
for _, option := range options {
option(m)
}
return m
}
// Map 非并发安全的字典数据结构

View File

@ -0,0 +1,10 @@
package asynchronous
type MapOption[Key comparable, Value any] func(m *Map[Key, Value])
// WithMapSource 通过传入的 map 初始化
func WithMapSource[Key comparable, Value any](source map[Key]Value) MapOption[Key, Value] {
return func(m *Map[Key, Value]) {
m.data = source
}
}

View File

@ -37,3 +37,8 @@ func IntN(n int) int {
}
return rand.Intn(n)
}
// Bool 返回一个随机的布尔值
func Bool() bool {
return rand.Intn(2) == 1
}

View File

@ -1,6 +1,9 @@
package slice
import "math/rand"
import (
"math/rand"
"reflect"
)
// Del 删除特定索引的元素
func Del[V any](slice *[]V, index int) {
@ -98,6 +101,24 @@ func Shuffle[V any](slice []V) {
}
}
// Distinct 去重
func Distinct[V any](slice []V) []V {
var result []V
for i := range slice {
flag := true
for j := range result {
if reflect.DeepEqual(slice[i], result[j]) {
flag = false
break
}
}
if flag {
result = append(result, slice[i])
}
}
return result
}
// Swap 交换数组中的两个元素
func Swap[V any](slice []V, i, j int) {
slice[i], slice[j] = slice[j], slice[i]
@ -146,7 +167,7 @@ func GetEndPart[V any](slice []V, n int) []V {
return slice[len(slice)-n:]
}
// GetPart 获取数组的部分元素
// GetPart 获取指定区间的元素
func GetPart[V any](slice []V, start, end int) []V {
if start < 0 {
start = 0
@ -156,3 +177,23 @@ func GetPart[V any](slice []V, start, end int) []V {
}
return slice[start:end]
}
// Contains 判断数组是否包含某个元素
func Contains[V comparable](slice []V, value V) bool {
for _, v := range slice {
if v == value {
return true
}
}
return false
}
// ContainsAny 判断数组是否包含某个元素
func ContainsAny[V any](slice []V, values V) bool {
for _, v := range slice {
if reflect.DeepEqual(v, values) {
return true
}
}
return false
}

196
utils/stream/map.go Normal file
View File

@ -0,0 +1,196 @@
package stream
import (
"github.com/kercylan98/minotaur/utils/asynchronous"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/synchronization"
"reflect"
)
// WithMap 使用传入的 map 执行链式操作
// - 该函数将会直接影响到传入的 map
func WithMap[K comparable, V any](m map[K]V) Map[K, V] {
return m
}
// WithMapCopy 使用传入的 map 执行链式操作
// - 该函数不会影响到传入的 map
func WithMapCopy[K comparable, V any](m map[K]V) Map[K, V] {
return hash.Copy(m)
}
// WithHashMap 使用传入的 map 执行链式操作
func WithHashMap[K comparable, V any](m hash.Map[K, V]) Map[K, V] {
return m.Map()
}
// Map 提供了 map 的链式操作
type Map[K comparable, V any] map[K]V
// Set 设置一个值
func (slf Map[K, V]) Set(key K, value V) Map[K, V] {
slf[key] = value
return slf
}
// Delete 删除一个值
func (slf Map[K, V]) Delete(key K) Map[K, V] {
delete(slf, key)
return slf
}
// Filter 过滤 handle 返回 false 的元素
func (slf Map[K, V]) Filter(handle func(key K, value V) bool) Map[K, V] {
for k, v := range slf {
if !handle(k, v) {
delete(slf, k)
}
}
return slf
}
// FilterKey 过滤特定的 key
func (slf Map[K, V]) FilterKey(keys ...K) Map[K, V] {
for _, key := range keys {
delete(slf, key)
}
return slf
}
// FilterValue 过滤特定的 value
func (slf Map[K, V]) FilterValue(values ...V) Map[K, V] {
for k, v := range slf {
for _, value := range values {
if reflect.DeepEqual(v, value) {
delete(slf, k)
}
}
}
return slf
}
// RandomKeep 随机保留 n 个元素
func (slf Map[K, V]) RandomKeep(n int) Map[K, V] {
length := len(slf)
if n >= length {
return slf
}
for k := range slf {
if n > 0 {
n--
} else {
delete(slf, k)
}
}
return slf
}
// RandomDelete 随机删除 n 个元素
func (slf Map[K, V]) RandomDelete(n int) Map[K, V] {
var count int
for k := range slf {
if count < n {
count++
delete(slf, k)
} else {
return slf
}
}
return slf
}
// RandomReplace 将 values 覆盖到当前的 map 中
// - 如果 values 的长度大于当前 map 的长度,则只会覆盖当前 map 的长度
func (slf Map[K, V]) RandomReplace(values ...V) Map[K, V] {
var record []K
var valuesLen = len(values)
for k := range slf {
record = append(record, k)
if len(record) >= valuesLen {
break
}
}
for i, k := range record {
slf.Set(k, values[i])
}
return slf
}
// Distinct 去重,如果 handle 返回 true则认为是重复的元素
func (slf Map[K, V]) Distinct(handle func(key K, value V) bool) Map[K, V] {
for k, v := range slf {
if handle(k, v) {
delete(slf, k)
}
}
return slf
}
// Range 遍历当前 Map, handle 返回 false 则停止遍历
func (slf Map[K, V]) Range(handle func(key K, value V) bool) Map[K, V] {
for k, v := range slf {
if !handle(k, v) {
break
}
}
return slf
}
// ValueOr 当 key 不存在时,设置一个默认值
func (slf Map[K, V]) ValueOr(key K, value V) Map[K, V] {
if _, ok := slf[key]; !ok {
slf[key] = value
}
return slf
}
// GetValueOr 当 key 不存在时,返回一个默认值
func (slf Map[K, V]) GetValueOr(key K, value V) V {
if v, ok := slf[key]; ok {
return v
}
return value
}
// Clear 清空当前 Map
func (slf Map[K, V]) Clear() Map[K, V] {
for k := range slf {
delete(slf, k)
}
return slf
}
// Merge 合并多个 Map
func (slf Map[K, V]) Merge(maps ...map[K]V) Map[K, V] {
for _, m := range maps {
for k, v := range m {
slf[k] = v
}
}
return slf
}
// ToSliceStream 将当前 Map stream 转换为 Slice stream
func (slf Map[K, V]) ToSliceStream() Slice[V] {
return hash.ToSlice(slf)
}
// ToSliceStreamWithKey 将当前 Map stream 转换为 Slice streamkey 为 Slice 的元素
func (slf Map[K, V]) ToSliceStreamWithKey() Slice[K] {
return hash.KeyToSlice(slf)
}
// ToSyncMap 将当前 Map 转换为 synchronization.Map
func (slf Map[K, V]) ToSyncMap() *synchronization.Map[K, V] {
return synchronization.NewMap[K, V](synchronization.WithMapSource(slf))
}
// ToAsyncMap 将当前 Map 转换为 asynchronous.Map
func (slf Map[K, V]) ToAsyncMap() *asynchronous.Map[K, V] {
return asynchronous.NewMap[K, V](asynchronous.WithMapSource(slf))
}
// ToMap 将当前 Map 转换为 map
func (slf Map[K, V]) ToMap() map[K]V {
return slf
}

View File

@ -0,0 +1,26 @@
package stream_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/stream"
)
func ExampleWithMap() {
m := stream.WithMap(map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "d"}).Filter(func(key int, value string) bool {
return key > 3
})
fmt.Println(len(m))
// Output:
// 2
}
func ExampleWithMapCopy() {
m := stream.WithMapCopy(map[int]string{1: "a", 2: "b", 3: "c", 4: "d", 5: "d"}).Filter(func(key int, value string) bool {
return key > 3
})
fmt.Println(len(m))
// Output:
// 2
}

90
utils/stream/map_test.go Normal file
View File

@ -0,0 +1,90 @@
package stream_test
import (
"github.com/kercylan98/minotaur/utils/stream"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func initMap() map[int]int {
return map[int]int{
1: 100,
2: 200,
3: 300,
}
}
func TestWithMap(t *testing.T) {
Convey("TestWithMap", t, func() {
var s = initMap()
var m = stream.WithMap(s).RandomDelete(1)
So(m, ShouldNotBeNil)
So(len(s), ShouldEqual, 2)
})
}
func TestWithMapCopy(t *testing.T) {
Convey("TestWithMapCopy", t, func() {
var s = initMap()
var m = stream.WithMapCopy(s).RandomDelete(1)
So(m, ShouldNotBeNil)
So(len(s), ShouldEqual, 3)
})
}
func TestMap_Set(t *testing.T) {
Convey("TestMap_Set", t, func() {
var m = stream.WithMap(initMap()).Set(4, 400)
So(m[4], ShouldEqual, 400)
})
}
func TestMap_Filter(t *testing.T) {
Convey("TestMap_Filter", t, func() {
var m = stream.WithMap(initMap()).Filter(func(key int, value int) bool {
return key == 1
})
So(len(m), ShouldEqual, 1)
})
}
func TestMap_FilterKey(t *testing.T) {
Convey("TestMap_FilterKey", t, func() {
var m = stream.WithMap(initMap()).FilterKey(1)
So(len(m), ShouldEqual, 2)
})
}
func TestMap_FilterValue(t *testing.T) {
Convey("TestMap_FilterValue", t, func() {
var m = stream.WithMap(initMap()).FilterValue(100)
So(len(m), ShouldEqual, 2)
})
}
func TestMap_RandomKeep(t *testing.T) {
Convey("TestMap_RandomKeep", t, func() {
var m = stream.WithMap(initMap()).RandomKeep(1)
So(len(m), ShouldEqual, 1)
})
}
func TestMap_RandomDelete(t *testing.T) {
Convey("TestMap_RandomDelete", t, func() {
var m = stream.WithMap(initMap()).RandomDelete(1)
So(len(m), ShouldEqual, 2)
})
}
func TestMap_Distinct(t *testing.T) {
Convey("TestMap_Distinct", t, func() {
var m = stream.WithMap(map[int]int{
1: 100,
2: 200,
3: 100,
}).Distinct(func(key int, value int) bool {
return value == 100
})
So(len(m), ShouldEqual, 1)
})
}

152
utils/stream/slice.go Normal file
View File

@ -0,0 +1,152 @@
package stream
import (
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/slice"
"reflect"
)
// WithSlice 创建一个 Slice
// - 该函数不会影响到传入的 slice
func WithSlice[V any](values []V) Slice[V] {
return slice.Copy(values)
}
// Slice 提供了 slice 的链式操作
type Slice[V any] []V
// Filter 过滤 handle 返回 false 的元素
func (slf Slice[V]) Filter(handle func(index int, value V) bool) Slice[V] {
var ns = make([]V, 0, len(slf))
for i, v := range slf {
if handle(i, v) {
ns = append(ns, v)
}
}
return ns
}
// FilterValue 过滤特定的 value
func (slf Slice[V]) FilterValue(values ...V) Slice[V] {
return slf.Filter(func(index int, value V) bool {
for _, v := range values {
if reflect.DeepEqual(v, value) {
return false
}
}
return true
})
}
// FilterIndex 过滤特定的 index
func (slf Slice[V]) FilterIndex(indexes ...int) Slice[V] {
return slf.Filter(func(index int, value V) bool {
return !slice.Contains(indexes, index)
})
}
// RandomKeep 随机保留 n 个元素
func (slf Slice[V]) RandomKeep(n int) Slice[V] {
length := len(slf)
if n >= length {
return slf
}
var hit = make([]int, length, length)
for i := 0; i < n; i++ {
hit[i] = 1
}
slice.Shuffle(hit)
var ns = make([]V, 0, n)
for i, v := range slf {
if hit[i] == 1 {
ns = append(ns, v)
}
}
return ns
}
// RandomDelete 随机删除 n 个元素
func (slf Slice[V]) RandomDelete(n int) Slice[V] {
length := len(slf)
if n >= length {
return slf[:0]
}
var hit = make([]int, length, length)
for i := 0; i < n; i++ {
hit[i] = 1
}
slice.Shuffle(hit)
var ns = make([]V, 0, n)
for i, v := range slf {
if hit[i] == 0 {
ns = append(ns, v)
}
}
return ns
}
// Shuffle 随机打乱
func (slf Slice[V]) Shuffle() Slice[V] {
slice.Shuffle(slf)
return slf
}
// Reverse 反转
func (slf Slice[V]) Reverse() Slice[V] {
slice.Reverse(slf)
return slf
}
// Clear 清空
func (slf Slice[V]) Clear() Slice[V] {
return slf[:0]
}
// Distinct 去重,如果 handle 返回 true 则认为是重复元素
func (slf Slice[V]) Distinct() Slice[V] {
return slice.Distinct(slf)
}
// Merge 合并
func (slf Slice[V]) Merge(values ...V) Slice[V] {
return append(slf, values...)
}
// GetStartPart 获取前 n 个元素
func (slf Slice[V]) GetStartPart(n int) Slice[V] {
return slf[:n]
}
// GetEndPart 获取后 n 个元素
func (slf Slice[V]) GetEndPart(n int) Slice[V] {
return slice.GetEndPart(slf, n)
}
// GetPart 获取指定区间的元素
func (slf Slice[V]) GetPart(start, end int) Slice[V] {
return slice.GetPart(slf, start, end)
}
// ContainsHandle 如果包含指定的元素则执行 handle
func (slf Slice[V]) ContainsHandle(value V, handle func(slice Slice[V]) Slice[V]) Slice[V] {
if slice.ContainsAny(slf, value) {
return handle(slf)
}
return slf
}
// Set 设置指定位置的元素
func (slf Slice[V]) Set(index int, value V) Slice[V] {
slf[index] = value
return slf
}
// Delete 删除指定位置的元素
func (slf Slice[V]) Delete(index int) Slice[V] {
return append(slf, slf[index+1:]...)
}
// ToMapStream 将当前的 Slice stream 转换为 Map stream
func (slf Slice[V]) ToMapStream() Map[int, V] {
return hash.ToMap(slf)
}

View File

@ -0,0 +1,18 @@
package stream_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/stream"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestSlice_Filter(t *testing.T) {
Convey("TestSlice_Filter", t, func() {
d := []int{1, 2, 3, 4, 5}
var s = stream.WithSlice(d).Reverse()
fmt.Println(s)
fmt.Println(d)
So(len(s), ShouldEqual, 2)
})
}

View File

@ -6,10 +6,14 @@ import (
"sync"
)
func NewMap[Key comparable, value any]() *Map[Key, value] {
return &Map[Key, value]{
func NewMap[Key comparable, value any](options ...MapOption[Key, value]) *Map[Key, value] {
m := &Map[Key, value]{
data: make(map[Key]value),
}
for _, option := range options {
option(m)
}
return m
}
// Map 并发安全的字典数据结构

View File

@ -0,0 +1,10 @@
package synchronization
type MapOption[Key comparable, Value any] func(m *Map[Key, Value])
// WithMapSource 通过传入的 map 初始化
func WithMapSource[Key comparable, Value any](source map[Key]Value) MapOption[Key, Value] {
return func(m *Map[Key, Value]) {
m.data = source
}
}