diff --git a/game/builtin/room.go b/game/builtin/room.go index 25cf689..546db04 100644 --- a/game/builtin/room.go +++ b/game/builtin/room.go @@ -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) diff --git a/game/builtin/room_seat.go b/game/builtin/room_seat.go index e8d2d35..2fc31ea 100644 --- a/game/builtin/room_seat.go +++ b/game/builtin/room_seat.go @@ -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) diff --git a/report/data_buried.go b/report/data_buried.go index 6249108..b6d2daa 100644 --- a/report/data_buried.go +++ b/report/data_buried.go @@ -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) { diff --git a/utils/asynchronization/map.go b/utils/asynchronous/map.go similarity index 94% rename from utils/asynchronization/map.go rename to utils/asynchronous/map.go index 21b641b..a852ff1 100644 --- a/utils/asynchronization/map.go +++ b/utils/asynchronous/map.go @@ -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 非并发安全的字典数据结构 diff --git a/utils/asynchronous/map_options.go b/utils/asynchronous/map_options.go new file mode 100644 index 0000000..9e4284c --- /dev/null +++ b/utils/asynchronous/map_options.go @@ -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 + } +} diff --git a/utils/random/number.go b/utils/random/number.go index 7891fd5..fd9c54b 100644 --- a/utils/random/number.go +++ b/utils/random/number.go @@ -37,3 +37,8 @@ func IntN(n int) int { } return rand.Intn(n) } + +// Bool 返回一个随机的布尔值 +func Bool() bool { + return rand.Intn(2) == 1 +} diff --git a/utils/slice/slice.go b/utils/slice/slice.go index a9d1d12..4c34423 100644 --- a/utils/slice/slice.go +++ b/utils/slice/slice.go @@ -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 +} diff --git a/utils/stream/map.go b/utils/stream/map.go new file mode 100644 index 0000000..1495c98 --- /dev/null +++ b/utils/stream/map.go @@ -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 stream,key 为 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 +} diff --git a/utils/stream/map_example_test.go b/utils/stream/map_example_test.go new file mode 100644 index 0000000..1989c21 --- /dev/null +++ b/utils/stream/map_example_test.go @@ -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 +} diff --git a/utils/stream/map_test.go b/utils/stream/map_test.go new file mode 100644 index 0000000..270ca2f --- /dev/null +++ b/utils/stream/map_test.go @@ -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) + }) +} diff --git a/utils/stream/slice.go b/utils/stream/slice.go new file mode 100644 index 0000000..561f2ba --- /dev/null +++ b/utils/stream/slice.go @@ -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) +} diff --git a/utils/stream/slice_test.go b/utils/stream/slice_test.go new file mode 100644 index 0000000..1dff0e4 --- /dev/null +++ b/utils/stream/slice_test.go @@ -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) + }) +} diff --git a/utils/synchronization/map.go b/utils/synchronization/map.go index a97bc92..947f35a 100644 --- a/utils/synchronization/map.go +++ b/utils/synchronization/map.go @@ -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 并发安全的字典数据结构 diff --git a/utils/synchronization/map_options.go b/utils/synchronization/map_options.go new file mode 100644 index 0000000..0286200 --- /dev/null +++ b/utils/synchronization/map_options.go @@ -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 + } +} \ No newline at end of file