Merge branch 'develop'

This commit is contained in:
kercylan98 2024-01-12 18:45:38 +08:00
commit 3f0f60252a
171 changed files with 7489 additions and 4212 deletions

View File

@ -18,4 +18,4 @@ jobs:
bump-minor-pre-major: true bump-minor-pre-major: true
bump-patch-for-minor-pre-major: true bump-patch-for-minor-pre-major: true
changelog-types: '[{"type":"other","section":"Other | 其他更改","hidden":false},{"type":"revert","section":"Reverts | 回退","hidden":false},{"type":"feat","section":"Features | 新特性","hidden":false},{"type":"fix","section":"Bug Fixes | 修复","hidden":false},{"type":"improvement","section":"Feature Improvements | 改进","hidden":false},{"type":"docs","section":"Docs | 文档优化","hidden":false},{"type":"style","section":"Styling | 可读性优化","hidden":false},{"type":"refactor","section":"Code Refactoring | 重构","hidden":false},{"type":"perf","section":"Performance Improvements | 性能优化","hidden":false},{"type":"test","section":"Tests | 新增或优化测试用例","hidden":false},{"type":"build","section":"Build System | 影响构建的修改","hidden":false},{"type":"ci","section":"CI | 更改我们的 CI 配置文件和脚本","hidden":false}]' changelog-types: '[{"type":"other","section":"Other | 其他更改","hidden":false},{"type":"revert","section":"Reverts | 回退","hidden":false},{"type":"feat","section":"Features | 新特性","hidden":false},{"type":"fix","section":"Bug Fixes | 修复","hidden":false},{"type":"improvement","section":"Feature Improvements | 改进","hidden":false},{"type":"docs","section":"Docs | 文档优化","hidden":false},{"type":"style","section":"Styling | 可读性优化","hidden":false},{"type":"refactor","section":"Code Refactoring | 重构","hidden":false},{"type":"perf","section":"Performance Improvements | 性能优化","hidden":false},{"type":"test","section":"Tests | 新增或优化测试用例","hidden":false},{"type":"build","section":"Build System | 影响构建的修改","hidden":false},{"type":"ci","section":"CI | 更改我们的 CI 配置文件和脚本","hidden":false}]'
# release-as: 0.4.0 release-as: 0.5.0

View File

@ -2,8 +2,8 @@ package activity
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/times" "github.com/kercylan98/minotaur/utils/times"
"reflect" "reflect"
"time" "time"
@ -94,7 +94,7 @@ func regController[Type, ID generic.Basic, Data any, EntityID generic.Basic, Ent
// 实体数据加载器 // 实体数据加载器
activityEntityDataLoader = append(activityEntityDataLoader, func(handler func(activityType any, activityId any, entityId any, data any)) { activityEntityDataLoader = append(activityEntityDataLoader, func(handler func(activityType any, activityId any, entityId any, data any)) {
controller.mutex.RLock() controller.mutex.RLock()
entities := hash.Copy(controller.entityData[activityId]) entities := collection.CloneMap(controller.entityData[activityId])
controller.mutex.RUnlock() controller.mutex.RUnlock()
for entityId, data := range entities { for entityId, data := range entities {
handler(controller.t, activityId, entityId, data) handler(controller.t, activityId, entityId, data)

View File

@ -2,9 +2,10 @@ package activity
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/collection/listings"
"github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/slice"
"github.com/kercylan98/minotaur/utils/timer" "github.com/kercylan98/minotaur/utils/timer"
"github.com/kercylan98/minotaur/utils/times" "github.com/kercylan98/minotaur/utils/times"
"reflect" "reflect"
@ -21,28 +22,28 @@ type (
) )
var ( var (
upcomingEventHandlers map[any]*slice.Priority[func(activityId any)] // 即将开始的活动事件处理器 upcomingEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 即将开始的活动事件处理器
startedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动开始事件处理器 startedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动开始事件处理器
endedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束事件处理器 endedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束事件处理器
extShowStartedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束后延长展示开始事件处理器 extShowStartedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束后延长展示开始事件处理器
extShowEndedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束后延长展示结束事件处理器 extShowEndedEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 活动结束后延长展示结束事件处理器
newDayEventHandlers map[any]*slice.Priority[func(activityId any)] // 新的一天事件处理器 newDayEventHandlers map[any]*listings.PrioritySlice[func(activityId any)] // 新的一天事件处理器
) )
func init() { func init() {
upcomingEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) upcomingEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
startedEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) startedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
endedEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) endedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
extShowStartedEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) extShowStartedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
extShowEndedEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) extShowEndedEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
newDayEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) newDayEventHandlers = make(map[any]*listings.PrioritySlice[func(activityId any)])
} }
// RegUpcomingEvent 注册即将开始的活动事件处理器 // RegUpcomingEvent 注册即将开始的活动事件处理器
func RegUpcomingEvent[Type, ID generic.Basic](activityType Type, handler UpcomingEventHandler[ID], priority ...int) { func RegUpcomingEvent[Type, ID generic.Basic](activityType Type, handler UpcomingEventHandler[ID], priority ...int) {
handlers, exist := upcomingEventHandlers[activityType] handlers, exist := upcomingEventHandlers[activityType]
if !exist { if !exist {
handlers = slice.NewPriority[func(activityId any)]() handlers = listings.NewPrioritySlice[func(activityId any)]()
upcomingEventHandlers[activityType] = handlers upcomingEventHandlers[activityType] = handlers
} }
handlers.Append(func(activityId any) { handlers.Append(func(activityId any) {
@ -50,7 +51,7 @@ func RegUpcomingEvent[Type, ID generic.Basic](activityType Type, handler Upcomin
return return
} }
handler(activityId.(ID)) handler(activityId.(ID))
}, slice.GetValue(priority, 0)) }, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
// OnUpcomingEvent 即将开始的活动事件 // OnUpcomingEvent 即将开始的活动事件
@ -75,7 +76,7 @@ func OnUpcomingEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
func RegStartedEvent[Type, ID generic.Basic](activityType Type, handler StartedEventHandler[ID], priority ...int) { func RegStartedEvent[Type, ID generic.Basic](activityType Type, handler StartedEventHandler[ID], priority ...int) {
handlers, exist := startedEventHandlers[activityType] handlers, exist := startedEventHandlers[activityType]
if !exist { if !exist {
handlers = slice.NewPriority[func(activityId any)]() handlers = listings.NewPrioritySlice[func(activityId any)]()
startedEventHandlers[activityType] = handlers startedEventHandlers[activityType] = handlers
} }
handlers.Append(func(activityId any) { handlers.Append(func(activityId any) {
@ -83,7 +84,7 @@ func RegStartedEvent[Type, ID generic.Basic](activityType Type, handler StartedE
return return
} }
handler(activityId.(ID)) handler(activityId.(ID))
}, slice.GetValue(priority, 0)) }, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
// OnStartedEvent 活动开始事件 // OnStartedEvent 活动开始事件
@ -116,7 +117,7 @@ func OnStartedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
func RegEndedEvent[Type, ID generic.Basic](activityType Type, handler EndedEventHandler[ID], priority ...int) { func RegEndedEvent[Type, ID generic.Basic](activityType Type, handler EndedEventHandler[ID], priority ...int) {
handlers, exist := endedEventHandlers[activityType] handlers, exist := endedEventHandlers[activityType]
if !exist { if !exist {
handlers = slice.NewPriority[func(activityId any)]() handlers = listings.NewPrioritySlice[func(activityId any)]()
endedEventHandlers[activityType] = handlers endedEventHandlers[activityType] = handlers
} }
handlers.Append(func(activityId any) { handlers.Append(func(activityId any) {
@ -124,7 +125,7 @@ func RegEndedEvent[Type, ID generic.Basic](activityType Type, handler EndedEvent
return return
} }
handler(activityId.(ID)) handler(activityId.(ID))
}, slice.GetValue(priority, 0)) }, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
// OnEndedEvent 活动结束事件 // OnEndedEvent 活动结束事件
@ -149,7 +150,7 @@ func OnEndedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) {
func RegExtendedShowStartedEvent[Type, ID generic.Basic](activityType Type, handler ExtendedShowStartedEventHandler[ID], priority ...int) { func RegExtendedShowStartedEvent[Type, ID generic.Basic](activityType Type, handler ExtendedShowStartedEventHandler[ID], priority ...int) {
handlers, exist := extShowStartedEventHandlers[activityType] handlers, exist := extShowStartedEventHandlers[activityType]
if !exist { if !exist {
handlers = slice.NewPriority[func(activityId any)]() handlers = listings.NewPrioritySlice[func(activityId any)]()
extShowStartedEventHandlers[activityType] = handlers extShowStartedEventHandlers[activityType] = handlers
} }
handlers.Append(func(activityId any) { handlers.Append(func(activityId any) {
@ -157,7 +158,7 @@ func RegExtendedShowStartedEvent[Type, ID generic.Basic](activityType Type, hand
return return
} }
handler(activityId.(ID)) handler(activityId.(ID))
}, slice.GetValue(priority, 0)) }, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
// OnExtendedShowStartedEvent 活动结束后延长展示开始事件 // OnExtendedShowStartedEvent 活动结束后延长展示开始事件
@ -182,7 +183,7 @@ func OnExtendedShowStartedEvent[Type, ID generic.Basic](activity *Activity[Type,
func RegExtendedShowEndedEvent[Type, ID generic.Basic](activityType Type, handler ExtendedShowEndedEventHandler[ID], priority ...int) { func RegExtendedShowEndedEvent[Type, ID generic.Basic](activityType Type, handler ExtendedShowEndedEventHandler[ID], priority ...int) {
handlers, exist := extShowEndedEventHandlers[activityType] handlers, exist := extShowEndedEventHandlers[activityType]
if !exist { if !exist {
handlers = slice.NewPriority[func(activityId any)]() handlers = listings.NewPrioritySlice[func(activityId any)]()
extShowEndedEventHandlers[activityType] = handlers extShowEndedEventHandlers[activityType] = handlers
} }
handlers.Append(func(activityId any) { handlers.Append(func(activityId any) {
@ -190,7 +191,7 @@ func RegExtendedShowEndedEvent[Type, ID generic.Basic](activityType Type, handle
return return
} }
handler(activityId.(ID)) handler(activityId.(ID))
}, slice.GetValue(priority, 0)) }, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
// OnExtendedShowEndedEvent 活动结束后延长展示结束事件 // OnExtendedShowEndedEvent 活动结束后延长展示结束事件
@ -215,7 +216,7 @@ func OnExtendedShowEndedEvent[Type, ID generic.Basic](activity *Activity[Type, I
func RegNewDayEvent[Type, ID generic.Basic](activityType Type, handler NewDayEventHandler[ID], priority ...int) { func RegNewDayEvent[Type, ID generic.Basic](activityType Type, handler NewDayEventHandler[ID], priority ...int) {
handlers, exist := newDayEventHandlers[activityType] handlers, exist := newDayEventHandlers[activityType]
if !exist { if !exist {
handlers = slice.NewPriority[func(activityId any)]() handlers = listings.NewPrioritySlice[func(activityId any)]()
newDayEventHandlers[activityType] = handlers newDayEventHandlers[activityType] = handlers
} }
handlers.Append(func(activityId any) { handlers.Append(func(activityId any) {
@ -223,7 +224,7 @@ func RegNewDayEvent[Type, ID generic.Basic](activityType Type, handler NewDayEve
return return
} }
handler(activityId.(ID)) handler(activityId.(ID))
}, slice.GetValue(priority, 0)) }, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
// OnNewDayEvent 新的一天事件 // OnNewDayEvent 新的一天事件

View File

@ -1,9 +1,8 @@
package space package space
import ( import (
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/slice"
"sync" "sync"
) )
@ -201,7 +200,7 @@ func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetNotEmptySeat() []in
// GetEmptySeat 获取空座位 // GetEmptySeat 获取空座位
// - 空座位需要在有对象离开座位后才可能出现 // - 空座位需要在有对象离开座位后才可能出现
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEmptySeat() []int { func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEmptySeat() []int {
return slice.Copy(rc.vacancy) return collection.CloneSlice(rc.vacancy)
} }
// HasSeat 判断是否有座位 // HasSeat 判断是否有座位
@ -291,7 +290,7 @@ func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetRoom() Room {
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntities() map[EntityID]Entity { func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntities() map[EntityID]Entity {
rc.entitiesRWMutex.RLock() rc.entitiesRWMutex.RLock()
defer rc.entitiesRWMutex.RUnlock() defer rc.entitiesRWMutex.RUnlock()
return hash.Copy(rc.entities) return collection.CloneMap(rc.entities)
} }
// HasEntity 判断是否有实体 // HasEntity 判断是否有实体
@ -321,7 +320,7 @@ func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntityExist(id Enti
func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntityIDs() []EntityID { func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetEntityIDs() []EntityID {
rc.entitiesRWMutex.RLock() rc.entitiesRWMutex.RLock()
defer rc.entitiesRWMutex.RUnlock() defer rc.entitiesRWMutex.RUnlock()
return hash.KeyToSlice(rc.entities) return collection.ConvertMapKeysToSlice(rc.entities)
} }
// GetEntityCount 获取实体数量 // GetEntityCount 获取实体数量
@ -454,7 +453,7 @@ func (rc *RoomController[EntityID, RoomID, Entity, Room]) GetRoomID() RoomID {
// Broadcast 广播,该函数会将所有房间中满足 conditions 的对象传入 handler 中进行处理 // Broadcast 广播,该函数会将所有房间中满足 conditions 的对象传入 handler 中进行处理
func (rc *RoomController[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) { func (rc *RoomController[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) {
rc.entitiesRWMutex.RLock() rc.entitiesRWMutex.RLock()
entities := hash.Copy(rc.entities) entities := collection.CloneMap(rc.entities)
rc.entitiesRWMutex.RUnlock() rc.entitiesRWMutex.RUnlock()
for _, entity := range entities { for _, entity := range entities {
for _, condition := range conditions { for _, condition := range conditions {

View File

@ -1,8 +1,8 @@
package space package space
import ( import (
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/hash"
"sync" "sync"
) )
@ -54,7 +54,7 @@ func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoom(id RoomID) *RoomC
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRooms() map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] { func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRooms() map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] {
rm.roomsRWMutex.RLock() rm.roomsRWMutex.RLock()
defer rm.roomsRWMutex.RUnlock() defer rm.roomsRWMutex.RUnlock()
return hash.Copy(rm.rooms) return collection.CloneMap(rm.rooms)
} }
// GetRoomCount 获取房间管理器接管的房间数量 // GetRoomCount 获取房间管理器接管的房间数量
@ -68,13 +68,13 @@ func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomCount() int {
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomIDs() []RoomID { func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomIDs() []RoomID {
rm.roomsRWMutex.RLock() rm.roomsRWMutex.RLock()
defer rm.roomsRWMutex.RUnlock() defer rm.roomsRWMutex.RUnlock()
return hash.KeyToSlice(rm.rooms) return collection.ConvertMapKeysToSlice(rm.rooms)
} }
// HasEntity 判断特定对象是否在任一房间中,当对象不在任一房间中时将返回 false // HasEntity 判断特定对象是否在任一房间中,当对象不在任一房间中时将返回 false
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) HasEntity(entityId EntityID) bool { func (rm *RoomManager[EntityID, RoomID, Entity, Room]) HasEntity(entityId EntityID) bool {
rm.roomsRWMutex.RLock() rm.roomsRWMutex.RLock()
rooms := hash.Copy(rm.rooms) rooms := collection.CloneMap(rm.rooms)
rm.roomsRWMutex.RUnlock() rm.roomsRWMutex.RUnlock()
for _, room := range rooms { for _, room := range rooms {
if room.HasEntity(entityId) { if room.HasEntity(entityId) {
@ -88,7 +88,7 @@ func (rm *RoomManager[EntityID, RoomID, Entity, Room]) HasEntity(entityId Entity
// - 由于一个对象可能在多个房间中,因此返回值为 map 类型 // - 由于一个对象可能在多个房间中,因此返回值为 map 类型
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetEntityRooms(entityId EntityID) map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] { func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetEntityRooms(entityId EntityID) map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] {
rm.roomsRWMutex.RLock() rm.roomsRWMutex.RLock()
rooms := hash.Copy(rm.rooms) rooms := collection.CloneMap(rm.rooms)
rm.roomsRWMutex.RUnlock() rm.roomsRWMutex.RUnlock()
var result = make(map[RoomID]*RoomController[EntityID, RoomID, Entity, Room]) var result = make(map[RoomID]*RoomController[EntityID, RoomID, Entity, Room])
for id, room := range rooms { for id, room := range rooms {
@ -102,7 +102,7 @@ func (rm *RoomManager[EntityID, RoomID, Entity, Room]) GetEntityRooms(entityId E
// Broadcast 向所有房间对象广播消息,该方法将会遍历所有房间控制器并调用 RoomController.Broadcast 方法 // Broadcast 向所有房间对象广播消息,该方法将会遍历所有房间控制器并调用 RoomController.Broadcast 方法
func (rm *RoomManager[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) { func (rm *RoomManager[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) {
rm.roomsRWMutex.RLock() rm.roomsRWMutex.RLock()
rooms := hash.Copy(rm.rooms) rooms := collection.CloneMap(rm.rooms)
rm.roomsRWMutex.RUnlock() rm.roomsRWMutex.RUnlock()
for _, room := range rooms { for _, room := range rooms {
room.Broadcast(handler, conditions...) room.Broadcast(handler, conditions...)

View File

@ -37,7 +37,7 @@ func TestCond(t *testing.T) {
player := &Player{ player := &Player{
tasks: map[string][]*Task{ tasks: map[string][]*Task{
task.Type: []*Task{task}, task.Type: {task},
}, },
} }
OnRefreshTaskCounterEvent(task.Type, player, 1) OnRefreshTaskCounterEvent(task.Type, player, 1)

View File

@ -5,8 +5,8 @@ import (
"github.com/kercylan98/minotaur/planner/pce" "github.com/kercylan98/minotaur/planner/pce"
"github.com/kercylan98/minotaur/planner/pce/cs" "github.com/kercylan98/minotaur/planner/pce/cs"
"github.com/kercylan98/minotaur/planner/pce/tmpls" "github.com/kercylan98/minotaur/planner/pce/tmpls"
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/file" "github.com/kercylan98/minotaur/utils/file"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/str" "github.com/kercylan98/minotaur/utils/str"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tealeg/xlsx" "github.com/tealeg/xlsx"
@ -64,7 +64,7 @@ func init() {
var exporter = pce.NewExporter() var exporter = pce.NewExporter()
loader := pce.NewLoader(pce.GetFields()) loader := pce.NewLoader(pce.GetFields())
excludes := hash.ToMapBool(str.SplitTrimSpace(exclude, ",")) excludes := collection.ConvertSliceToBoolMap(str.SplitTrimSpace(exclude, ","))
for _, xlsxFile := range xlsxFiles { for _, xlsxFile := range xlsxFiles {
xf, err := xlsx.OpenFile(xlsxFile) xf, err := xlsx.OpenFile(xlsxFile)
if err != nil { if err != nil {

View File

@ -6,8 +6,8 @@ import (
"github.com/kercylan98/minotaur/planner/pce" "github.com/kercylan98/minotaur/planner/pce"
"github.com/kercylan98/minotaur/planner/pce/cs" "github.com/kercylan98/minotaur/planner/pce/cs"
"github.com/kercylan98/minotaur/planner/pce/tmpls" "github.com/kercylan98/minotaur/planner/pce/tmpls"
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/file" "github.com/kercylan98/minotaur/utils/file"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/str" "github.com/kercylan98/minotaur/utils/str"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tealeg/xlsx" "github.com/tealeg/xlsx"
@ -61,7 +61,7 @@ func init() {
var exporter = pce.NewExporter() var exporter = pce.NewExporter()
loader := pce.NewLoader(pce.GetFields()) loader := pce.NewLoader(pce.GetFields())
excludes := hash.ToMapBool(str.SplitTrimSpace(exclude, ",")) excludes := collection.ConvertSliceToBoolMap(str.SplitTrimSpace(exclude, ","))
for _, xlsxFile := range xlsxFiles { for _, xlsxFile := range xlsxFiles {
xf, err := xlsx.OpenFile(xlsxFile) xf, err := xlsx.OpenFile(xlsxFile)
if err != nil { if err != nil {

View File

@ -6,8 +6,8 @@ import (
"github.com/kercylan98/minotaur/planner/pce" "github.com/kercylan98/minotaur/planner/pce"
"github.com/kercylan98/minotaur/planner/pce/cs" "github.com/kercylan98/minotaur/planner/pce/cs"
"github.com/kercylan98/minotaur/planner/pce/tmpls" "github.com/kercylan98/minotaur/planner/pce/tmpls"
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/file" "github.com/kercylan98/minotaur/utils/file"
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/str" "github.com/kercylan98/minotaur/utils/str"
"github.com/tealeg/xlsx" "github.com/tealeg/xlsx"
"os" "os"
@ -61,7 +61,7 @@ func TestExecute(t *testing.T) {
var exporter = pce.NewExporter() var exporter = pce.NewExporter()
loader := pce.NewLoader(pce.GetFields()) loader := pce.NewLoader(pce.GetFields())
excludes := hash.ToMapBool(str.SplitTrimSpace(exclude, ",")) excludes := collection.ConvertSliceToBoolMap(str.SplitTrimSpace(exclude, ","))
for _, xlsxFile := range xlsxFiles { for _, xlsxFile := range xlsxFiles {
xf, err := xlsx.OpenFile(xlsxFile) xf, err := xlsx.OpenFile(xlsxFile)
if err != nil { if err != nil {

Binary file not shown.

View File

@ -2,7 +2,7 @@ package pce
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/str" "github.com/kercylan98/minotaur/utils/str"
"strings" "strings"
) )
@ -96,7 +96,7 @@ func (slf *TmplField) handleSlice(fieldName, fieldType string, fields map[string
} }
slf.slice = true slf.slice = true
t := strings.TrimPrefix(fieldType, "[]") t := strings.TrimPrefix(fieldType, "[]")
if hash.Exist(fields, t) { if collection.KeyInMap(fields, t) {
slf.Struct = nil slf.Struct = nil
slf.Type = t slf.Type = t
} else { } else {

View File

@ -1,7 +1,7 @@
package pce package pce
import ( import (
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/collection"
) )
// TmplStruct 模板结构 // TmplStruct 模板结构
@ -19,7 +19,7 @@ func (slf *TmplStruct) addField(parent, name, desc, fieldType string, fields map
Desc: desc, Desc: desc,
Type: fieldType, Type: fieldType,
} }
if !hash.Exist(fields, fieldType) { if !collection.KeyInMap(fields, fieldType) {
field.setStruct(parent, name, desc, fieldType, fields) field.setStruct(parent, name, desc, fieldType, fields)
} else { } else {
field.Type = GetFieldGolangType(fields[fieldType]) field.Type = GetFieldGolangType(fields[fieldType])

View File

@ -27,7 +27,7 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) {
import ( import (
jsonIter "github.com/json-iterator/go" jsonIter "github.com/json-iterator/go"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/collection"
"sync" "sync"
"sync/atomic" "sync/atomic"
) )
@ -196,7 +196,7 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) {
func GetConfigs() map[Sign]any { func GetConfigs() map[Sign]any {
mutex.RLock() mutex.RLock()
defer mutex.RUnlock() defer mutex.RUnlock()
return hash.Copy(*configs.Load()) return collection.CloneMap(*configs.Load())
} }
// GetConfigSigns 获取所有配置的标识 // GetConfigSigns 获取所有配置的标识
@ -208,7 +208,7 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) {
func Sync(handle func(configs map[Sign]any)) { func Sync(handle func(configs map[Sign]any)) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
handle(hash.Copy(*configs.Load())) handle(collection.CloneMap(*configs.Load()))
} }
{{- range .Templates}} {{- range .Templates}}

View File

@ -45,5 +45,5 @@ func TestNewBot(t *testing.T) {
bot.SendPacket([]byte("hello")) bot.SendPacket([]byte("hello"))
}) })
srv.Run(":9600") _ = srv.Run(":9600")
} }

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/kercylan98/minotaur/server/writeloop" "github.com/kercylan98/minotaur/server/writeloop"
"github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hub"
"sync" "sync"
) )
@ -30,7 +30,7 @@ type Client struct {
core Core core Core
mutex sync.Mutex mutex sync.Mutex
closed bool // 是否已关闭 closed bool // 是否已关闭
pool *concurrent.Pool[*Packet] // 数据包缓冲池 pool *hub.ObjectPool[*Packet] // 数据包缓冲池
loop *writeloop.Channel[*Packet] // 写入循环 loop *writeloop.Channel[*Packet] // 写入循环
loopBufferSize int // 写入循环缓冲区大小 loopBufferSize int // 写入循环缓冲区大小
block chan struct{} // 以阻塞方式运行 block chan struct{} // 以阻塞方式运行
@ -73,7 +73,7 @@ func (slf *Client) RunByBufferSize(size int, block ...bool) error {
return err return err
} }
slf.closed = false slf.closed = false
slf.pool = concurrent.NewPool[Packet](func() *Packet { slf.pool = hub.NewObjectPool[Packet](func() *Packet {
return new(Packet) return new(Packet)
}, func(data *Packet) { }, func(data *Packet) {
data.wst = 0 data.wst = 0

View File

@ -6,8 +6,8 @@ import (
"fmt" "fmt"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/kercylan98/minotaur/server/writeloop" "github.com/kercylan98/minotaur/server/writeloop"
"github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/hub"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/random" "github.com/kercylan98/minotaur/utils/random"
"github.com/kercylan98/minotaur/utils/timer" "github.com/kercylan98/minotaur/utils/timer"
@ -125,7 +125,7 @@ type connection struct {
gw func(packet []byte) gw func(packet []byte)
data map[any]any data map[any]any
closed bool closed bool
pool *concurrent.Pool[*connPacket] pool *hub.ObjectPool[*connPacket]
loop writeloop.WriteLoop[*connPacket] loop writeloop.WriteLoop[*connPacket]
mu sync.Mutex mu sync.Mutex
openTime time.Time openTime time.Time
@ -203,7 +203,7 @@ func (slf *Conn) GetData(key any) any {
// ViewData 查看只读的连接数据 // ViewData 查看只读的连接数据
func (slf *Conn) ViewData() map[any]any { func (slf *Conn) ViewData() map[any]any {
return hash.Copy(slf.data) return collection.CloneMap(slf.data)
} }
// SetMessageData 设置消息数据,该数据将在消息处理完成后释放 // SetMessageData 设置消息数据,该数据将在消息处理完成后释放
@ -286,7 +286,7 @@ func (slf *Conn) init() {
})) }))
} }
} }
slf.pool = concurrent.NewPool[connPacket]( slf.pool = hub.NewObjectPool[connPacket](
func() *connPacket { func() *connPacket {
return &connPacket{} return &connPacket{}
}, func(data *connPacket) { }, func(data *connPacket) {
@ -358,11 +358,6 @@ func (slf *Conn) Close(err ...error) {
} }
if slf.ticker != nil { if slf.ticker != nil {
slf.ticker.Release() slf.ticker.Release()
}
if !slf.server.runtime.disableAutomaticReleaseShunt {
slf.server.releaseDispatcher(slf)
} else {
} }
slf.loop.Close() slf.loop.Close()
slf.mu.Unlock() slf.mu.Unlock()

View File

@ -2,11 +2,11 @@ package server
import ( import (
"context" "context"
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/collection"
"sync" "sync"
) )
type hub struct { type connMgr struct {
connections map[string]*Conn // 所有连接 connections map[string]*Conn // 所有连接
register chan *Conn // 注册连接 register chan *Conn // 注册连接
@ -26,12 +26,12 @@ type hubBroadcast struct {
filter func(conn *Conn) bool // 过滤掉返回 false 的连接 filter func(conn *Conn) bool // 过滤掉返回 false 的连接
} }
func (h *hub) run(ctx context.Context) { func (h *connMgr) run(ctx context.Context) {
h.connections = make(map[string]*Conn) h.connections = make(map[string]*Conn)
h.register = make(chan *Conn, DefaultConnHubBufferSize) h.register = make(chan *Conn, DefaultConnHubBufferSize)
h.unregister = make(chan string, DefaultConnHubBufferSize) h.unregister = make(chan string, DefaultConnHubBufferSize)
h.broadcast = make(chan hubBroadcast, DefaultConnHubBufferSize) h.broadcast = make(chan hubBroadcast, DefaultConnHubBufferSize)
go func(ctx context.Context, h *hub) { go func(ctx context.Context, h *connMgr) {
for { for {
select { select {
case conn := <-h.register: case conn := <-h.register:
@ -54,7 +54,7 @@ func (h *hub) run(ctx context.Context) {
} }
// registerConn 注册连接 // registerConn 注册连接
func (h *hub) registerConn(conn *Conn) { func (h *connMgr) registerConn(conn *Conn) {
select { select {
case h.register <- conn: case h.register <- conn:
default: default:
@ -63,7 +63,7 @@ func (h *hub) registerConn(conn *Conn) {
} }
// unregisterConn 注销连接 // unregisterConn 注销连接
func (h *hub) unregisterConn(id string) { func (h *connMgr) unregisterConn(id string) {
select { select {
case h.unregister <- id: case h.unregister <- id:
default: default:
@ -72,21 +72,21 @@ func (h *hub) unregisterConn(id string) {
} }
// GetOnlineCount 获取在线人数 // GetOnlineCount 获取在线人数
func (h *hub) GetOnlineCount() int { func (h *connMgr) GetOnlineCount() int {
h.chanMutex.RLock() h.chanMutex.RLock()
defer h.chanMutex.RUnlock() defer h.chanMutex.RUnlock()
return h.onlineCount return h.onlineCount
} }
// GetOnlineBotCount 获取在线机器人数量 // GetOnlineBotCount 获取在线机器人数量
func (h *hub) GetOnlineBotCount() int { func (h *connMgr) GetOnlineBotCount() int {
h.chanMutex.RLock() h.chanMutex.RLock()
defer h.chanMutex.RUnlock() defer h.chanMutex.RUnlock()
return h.botCount return h.botCount
} }
// IsOnline 是否在线 // IsOnline 是否在线
func (h *hub) IsOnline(id string) bool { func (h *connMgr) IsOnline(id string) bool {
h.chanMutex.RLock() h.chanMutex.RLock()
_, exist := h.connections[id] _, exist := h.connections[id]
h.chanMutex.RUnlock() h.chanMutex.RUnlock()
@ -94,15 +94,15 @@ func (h *hub) IsOnline(id string) bool {
} }
// GetOnlineAll 获取所有在线连接 // GetOnlineAll 获取所有在线连接
func (h *hub) GetOnlineAll() map[string]*Conn { func (h *connMgr) GetOnlineAll() map[string]*Conn {
h.chanMutex.RLock() h.chanMutex.RLock()
cop := hash.Copy(h.connections) cop := collection.CloneMap(h.connections)
h.chanMutex.RUnlock() h.chanMutex.RUnlock()
return cop return cop
} }
// GetOnline 获取在线连接 // GetOnline 获取在线连接
func (h *hub) GetOnline(id string) *Conn { func (h *connMgr) GetOnline(id string) *Conn {
h.chanMutex.RLock() h.chanMutex.RLock()
conn := h.connections[id] conn := h.connections[id]
h.chanMutex.RUnlock() h.chanMutex.RUnlock()
@ -110,7 +110,7 @@ func (h *hub) GetOnline(id string) *Conn {
} }
// CloseConn 关闭连接 // CloseConn 关闭连接
func (h *hub) CloseConn(id string) { func (h *connMgr) CloseConn(id string) {
h.chanMutex.RLock() h.chanMutex.RLock()
conn := h.connections[id] conn := h.connections[id]
h.chanMutex.RUnlock() h.chanMutex.RUnlock()
@ -120,7 +120,7 @@ func (h *hub) CloseConn(id string) {
} }
// Broadcast 广播消息 // Broadcast 广播消息
func (h *hub) Broadcast(packet []byte, filter ...func(conn *Conn) bool) { func (h *connMgr) Broadcast(packet []byte, filter ...func(conn *Conn) bool) {
m := hubBroadcast{ m := hubBroadcast{
packet: packet, packet: packet,
} }
@ -134,7 +134,7 @@ func (h *hub) Broadcast(packet []byte, filter ...func(conn *Conn) bool) {
} }
} }
func (h *hub) onRegister(conn *Conn) { func (h *connMgr) onRegister(conn *Conn) {
h.chanMutex.Lock() h.chanMutex.Lock()
if h.closed { if h.closed {
conn.Close() conn.Close()
@ -148,7 +148,7 @@ func (h *hub) onRegister(conn *Conn) {
h.chanMutex.Unlock() h.chanMutex.Unlock()
} }
func (h *hub) onUnregister(id string) { func (h *connMgr) onUnregister(id string) {
h.chanMutex.Lock() h.chanMutex.Lock()
if conn, ok := h.connections[id]; ok { if conn, ok := h.connections[id]; ok {
h.onlineCount-- h.onlineCount--
@ -160,7 +160,7 @@ func (h *hub) onUnregister(id string) {
h.chanMutex.Unlock() h.chanMutex.Unlock()
} }
func (h *hub) onBroadcast(packet hubBroadcast) { func (h *connMgr) onBroadcast(packet hubBroadcast) {
h.chanMutex.RLock() h.chanMutex.RLock()
defer h.chanMutex.RUnlock() defer h.chanMutex.RUnlock()
for _, conn := range h.connections { for _, conn := range h.connections {

View File

@ -7,18 +7,19 @@ import (
) )
const ( const (
serverMultipleMark = "Minotaur Multiple Server" serverMultipleMark = "Minotaur Multiple Server"
serverMark = "Minotaur Server" serverMark = "Minotaur Server"
serverSystemDispatcher = "__system" // 系统消息分发器
) )
const ( const (
DefaultAsyncPoolSize = 256 DefaultAsyncPoolSize = 256
DefaultWebsocketReadDeadline = 30 * time.Second DefaultWebsocketReadDeadline = 30 * time.Second
DefaultPacketWarnSize = 1024 * 1024 * 1 // 1MB DefaultPacketWarnSize = 1024 * 1024 * 1 // 1MB
DefaultDispatcherBufferSize = 1024 * 16 DefaultDispatcherBufferSize = 1024 * 16
DefaultConnWriteBufferSize = 1024 * 1 DefaultConnWriteBufferSize = 1024 * 1
DefaultConnHubBufferSize = 1024 * 1 DefaultConnHubBufferSize = 1024 * 1
DefaultLowMessageDuration = 100 * time.Millisecond
DefaultAsyncLowMessageDuration = time.Second
) )
func DefaultWebsocketUpgrader() *websocket.Upgrader { func DefaultWebsocketUpgrader() *websocket.Upgrader {

View File

@ -1,110 +0,0 @@
package server
import (
"context"
"github.com/alphadose/haxmap"
"sync"
)
var dispatcherUnique = struct{}{}
// generateDispatcher 生成消息分发器
func generateDispatcher(size int, name string, handler func(dispatcher *dispatcher, message *Message)) *dispatcher {
d := &dispatcher{
name: name,
buffer: make(chan *Message, size),
handler: handler,
uniques: haxmap.New[string, struct{}](),
queueMutex: new(sync.Mutex),
}
d.ctx, d.cancel = context.WithCancel(context.Background())
d.queueCond = sync.NewCond(d.queueMutex)
return d
}
// dispatcher 消息分发器
type dispatcher struct {
name string
buffer chan *Message
uniques *haxmap.Map[string, struct{}]
handler func(dispatcher *dispatcher, message *Message)
ctx context.Context
cancel context.CancelFunc
queue []*Message
queueMutex *sync.Mutex
queueCond *sync.Cond
}
func (d *dispatcher) unique(name string) bool {
_, loaded := d.uniques.GetOrSet(name, dispatcherUnique)
return loaded
}
func (d *dispatcher) antiUnique(name string) {
d.uniques.Del(name)
}
func (d *dispatcher) start() {
d.process()
for {
select {
case message, ok := <-d.buffer:
if !ok {
return
}
d.handler(d, message)
}
}
}
func (d *dispatcher) process() {
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
d.queueMutex.Lock()
if len(d.queue) == 0 {
d.queueCond.Wait()
}
messages := make([]*Message, len(d.queue))
copy(messages, d.queue)
d.queue = d.queue[:0]
d.queueMutex.Unlock()
for _, message := range messages {
select {
case d.buffer <- message:
}
}
}
}
}(d.ctx)
}
func (d *dispatcher) put(message *Message) {
d.queueMutex.Lock()
d.queue = append(d.queue, message)
d.queueCond.Signal()
defer d.queueMutex.Unlock()
}
func (d *dispatcher) close() {
close(d.buffer)
d.cancel()
}
func (d *dispatcher) transfer(target *dispatcher) {
if target == nil {
return
}
for {
select {
case message, ok := <-d.buffer:
if !ok {
return
}
target.buffer <- message
}
}
}

View File

@ -2,9 +2,10 @@ package server
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/collection/listings"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/runtimes" "github.com/kercylan98/minotaur/utils/runtimes"
"github.com/kercylan98/minotaur/utils/slice"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
"net/url" "net/url"
"os" "os"
@ -42,51 +43,51 @@ type (
func newEvent(srv *Server) *event { func newEvent(srv *Server) *event {
return &event{ return &event{
Server: srv, Server: srv,
startBeforeEventHandlers: slice.NewPriority[StartBeforeEventHandler](), startBeforeEventHandlers: listings.NewPrioritySlice[StartBeforeEventHandler](),
startFinishEventHandlers: slice.NewPriority[StartFinishEventHandler](), startFinishEventHandlers: listings.NewPrioritySlice[StartFinishEventHandler](),
stopEventHandlers: slice.NewPriority[StopEventHandler](), stopEventHandlers: listings.NewPrioritySlice[StopEventHandler](),
connectionReceivePacketEventHandlers: slice.NewPriority[ConnectionReceivePacketEventHandler](), connectionReceivePacketEventHandlers: listings.NewPrioritySlice[ConnectionReceivePacketEventHandler](),
connectionOpenedEventHandlers: slice.NewPriority[ConnectionOpenedEventHandler](), connectionOpenedEventHandlers: listings.NewPrioritySlice[ConnectionOpenedEventHandler](),
connectionClosedEventHandlers: slice.NewPriority[ConnectionClosedEventHandler](), connectionClosedEventHandlers: listings.NewPrioritySlice[ConnectionClosedEventHandler](),
messageErrorEventHandlers: slice.NewPriority[MessageErrorEventHandler](), messageErrorEventHandlers: listings.NewPrioritySlice[MessageErrorEventHandler](),
messageLowExecEventHandlers: slice.NewPriority[MessageLowExecEventHandler](), messageLowExecEventHandlers: listings.NewPrioritySlice[MessageLowExecEventHandler](),
connectionOpenedAfterEventHandlers: slice.NewPriority[ConnectionOpenedAfterEventHandler](), connectionOpenedAfterEventHandlers: listings.NewPrioritySlice[ConnectionOpenedAfterEventHandler](),
connectionWritePacketBeforeHandlers: slice.NewPriority[ConnectionWritePacketBeforeEventHandler](), connectionWritePacketBeforeHandlers: listings.NewPrioritySlice[ConnectionWritePacketBeforeEventHandler](),
shuntChannelCreatedEventHandlers: slice.NewPriority[ShuntChannelCreatedEventHandler](), shuntChannelCreatedEventHandlers: listings.NewPrioritySlice[ShuntChannelCreatedEventHandler](),
shuntChannelClosedEventHandlers: slice.NewPriority[ShuntChannelClosedEventHandler](), shuntChannelClosedEventHandlers: listings.NewPrioritySlice[ShuntChannelClosedEventHandler](),
connectionPacketPreprocessEventHandlers: slice.NewPriority[ConnectionPacketPreprocessEventHandler](), connectionPacketPreprocessEventHandlers: listings.NewPrioritySlice[ConnectionPacketPreprocessEventHandler](),
messageExecBeforeEventHandlers: slice.NewPriority[MessageExecBeforeEventHandler](), messageExecBeforeEventHandlers: listings.NewPrioritySlice[MessageExecBeforeEventHandler](),
messageReadyEventHandlers: slice.NewPriority[MessageReadyEventHandler](), messageReadyEventHandlers: listings.NewPrioritySlice[MessageReadyEventHandler](),
deadlockDetectEventHandlers: slice.NewPriority[OnDeadlockDetectEventHandler](), deadlockDetectEventHandlers: listings.NewPrioritySlice[OnDeadlockDetectEventHandler](),
} }
} }
type event struct { type event struct {
*Server *Server
startBeforeEventHandlers *slice.Priority[StartBeforeEventHandler] startBeforeEventHandlers *listings.PrioritySlice[StartBeforeEventHandler]
startFinishEventHandlers *slice.Priority[StartFinishEventHandler] startFinishEventHandlers *listings.PrioritySlice[StartFinishEventHandler]
stopEventHandlers *slice.Priority[StopEventHandler] stopEventHandlers *listings.PrioritySlice[StopEventHandler]
connectionReceivePacketEventHandlers *slice.Priority[ConnectionReceivePacketEventHandler] connectionReceivePacketEventHandlers *listings.PrioritySlice[ConnectionReceivePacketEventHandler]
connectionOpenedEventHandlers *slice.Priority[ConnectionOpenedEventHandler] connectionOpenedEventHandlers *listings.PrioritySlice[ConnectionOpenedEventHandler]
connectionClosedEventHandlers *slice.Priority[ConnectionClosedEventHandler] connectionClosedEventHandlers *listings.PrioritySlice[ConnectionClosedEventHandler]
messageErrorEventHandlers *slice.Priority[MessageErrorEventHandler] messageErrorEventHandlers *listings.PrioritySlice[MessageErrorEventHandler]
messageLowExecEventHandlers *slice.Priority[MessageLowExecEventHandler] messageLowExecEventHandlers *listings.PrioritySlice[MessageLowExecEventHandler]
connectionOpenedAfterEventHandlers *slice.Priority[ConnectionOpenedAfterEventHandler] connectionOpenedAfterEventHandlers *listings.PrioritySlice[ConnectionOpenedAfterEventHandler]
connectionWritePacketBeforeHandlers *slice.Priority[ConnectionWritePacketBeforeEventHandler] connectionWritePacketBeforeHandlers *listings.PrioritySlice[ConnectionWritePacketBeforeEventHandler]
shuntChannelCreatedEventHandlers *slice.Priority[ShuntChannelCreatedEventHandler] shuntChannelCreatedEventHandlers *listings.PrioritySlice[ShuntChannelCreatedEventHandler]
shuntChannelClosedEventHandlers *slice.Priority[ShuntChannelClosedEventHandler] shuntChannelClosedEventHandlers *listings.PrioritySlice[ShuntChannelClosedEventHandler]
connectionPacketPreprocessEventHandlers *slice.Priority[ConnectionPacketPreprocessEventHandler] connectionPacketPreprocessEventHandlers *listings.PrioritySlice[ConnectionPacketPreprocessEventHandler]
messageExecBeforeEventHandlers *slice.Priority[MessageExecBeforeEventHandler] messageExecBeforeEventHandlers *listings.PrioritySlice[MessageExecBeforeEventHandler]
messageReadyEventHandlers *slice.Priority[MessageReadyEventHandler] messageReadyEventHandlers *listings.PrioritySlice[MessageReadyEventHandler]
deadlockDetectEventHandlers *slice.Priority[OnDeadlockDetectEventHandler] deadlockDetectEventHandlers *listings.PrioritySlice[OnDeadlockDetectEventHandler]
consoleCommandEventHandlers map[string]*slice.Priority[ConsoleCommandEventHandler] consoleCommandEventHandlers map[string]*listings.PrioritySlice[ConsoleCommandEventHandler]
consoleCommandEventHandlerInitOnce sync.Once consoleCommandEventHandlerInitOnce sync.Once
} }
// RegStopEvent 服务器停止时将立即执行被注册的事件处理函数 // RegStopEvent 服务器停止时将立即执行被注册的事件处理函数
func (slf *event) RegStopEvent(handler StopEventHandler, priority ...int) { func (slf *event) RegStopEvent(handler StopEventHandler, priority ...int) {
slf.stopEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.stopEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -108,7 +109,7 @@ func (slf *event) RegConsoleCommandEvent(command string, handler ConsoleCommandE
} }
slf.consoleCommandEventHandlerInitOnce.Do(func() { slf.consoleCommandEventHandlerInitOnce.Do(func() {
slf.consoleCommandEventHandlers = map[string]*slice.Priority[ConsoleCommandEventHandler]{} slf.consoleCommandEventHandlers = map[string]*listings.PrioritySlice[ConsoleCommandEventHandler]{}
go func() { go func() {
for { for {
var input string var input string
@ -123,10 +124,10 @@ func (slf *event) RegConsoleCommandEvent(command string, handler ConsoleCommandE
}) })
list, exist := slf.consoleCommandEventHandlers[command] list, exist := slf.consoleCommandEventHandlers[command]
if !exist { if !exist {
list = slice.NewPriority[ConsoleCommandEventHandler]() list = listings.NewPrioritySlice[ConsoleCommandEventHandler]()
slf.consoleCommandEventHandlers[command] = list slf.consoleCommandEventHandlers[command] = list
} }
list.Append(handler, slice.GetValue(priority, 0)) list.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -161,7 +162,7 @@ func (slf *event) OnConsoleCommandEvent(command string, paramsStr string) {
// RegStartBeforeEvent 在服务器初始化完成启动前立刻执行被注册的事件处理函数 // RegStartBeforeEvent 在服务器初始化完成启动前立刻执行被注册的事件处理函数
func (slf *event) RegStartBeforeEvent(handler StartBeforeEventHandler, priority ...int) { func (slf *event) RegStartBeforeEvent(handler StartBeforeEventHandler, priority ...int) {
slf.startBeforeEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.startBeforeEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -181,7 +182,7 @@ func (slf *event) OnStartBeforeEvent() {
// RegStartFinishEvent 在服务器启动完成时将立刻执行被注册的事件处理函数 // RegStartFinishEvent 在服务器启动完成时将立刻执行被注册的事件处理函数
// - 需要注意该时刻服务器已经启动完成,但是还有可能未开始处理消息,客户端有可能无法连接,如果需要在消息处理器准备就绪后执行,请使用 RegMessageReadyEvent 函数 // - 需要注意该时刻服务器已经启动完成,但是还有可能未开始处理消息,客户端有可能无法连接,如果需要在消息处理器准备就绪后执行,请使用 RegMessageReadyEvent 函数
func (slf *event) RegStartFinishEvent(handler StartFinishEventHandler, priority ...int) { func (slf *event) RegStartFinishEvent(handler StartFinishEventHandler, priority ...int) {
slf.startFinishEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.startFinishEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -205,7 +206,7 @@ func (slf *event) RegConnectionClosedEvent(handler ConnectionClosedEventHandler,
if slf.network == NetworkHttp { if slf.network == NetworkHttp {
panic(ErrNetworkIncompatibleHttp) panic(ErrNetworkIncompatibleHttp)
} }
slf.connectionClosedEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.connectionClosedEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -216,6 +217,7 @@ func (slf *event) OnConnectionClosedEvent(conn *Conn, err any) {
value(slf.Server, conn, err) value(slf.Server, conn, err)
return true return true
}) })
slf.Server.dispatcherMgr.UnBindProducer(conn.GetID())
}, log.String("Event", "OnConnectionClosedEvent")) }, log.String("Event", "OnConnectionClosedEvent"))
} }
@ -225,7 +227,7 @@ func (slf *event) RegConnectionOpenedEvent(handler ConnectionOpenedEventHandler,
if slf.network == NetworkHttp { if slf.network == NetworkHttp {
panic(ErrNetworkIncompatibleHttp) panic(ErrNetworkIncompatibleHttp)
} }
slf.connectionOpenedEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.connectionOpenedEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -245,7 +247,7 @@ func (slf *event) RegConnectionReceivePacketEvent(handler ConnectionReceivePacke
if slf.network == NetworkHttp { if slf.network == NetworkHttp {
panic(ErrNetworkIncompatibleHttp) panic(ErrNetworkIncompatibleHttp)
} }
slf.connectionReceivePacketEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.connectionReceivePacketEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -261,7 +263,7 @@ func (slf *event) OnConnectionReceivePacketEvent(conn *Conn, packet []byte) {
// RegMessageErrorEvent 在处理消息发生错误时将立即执行被注册的事件处理函数 // RegMessageErrorEvent 在处理消息发生错误时将立即执行被注册的事件处理函数
func (slf *event) RegMessageErrorEvent(handler MessageErrorEventHandler, priority ...int) { func (slf *event) RegMessageErrorEvent(handler MessageErrorEventHandler, priority ...int) {
slf.messageErrorEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.messageErrorEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -283,7 +285,7 @@ func (slf *event) OnMessageErrorEvent(message *Message, err error) {
// RegMessageLowExecEvent 在处理消息缓慢时将立即执行被注册的事件处理函数 // RegMessageLowExecEvent 在处理消息缓慢时将立即执行被注册的事件处理函数
func (slf *event) RegMessageLowExecEvent(handler MessageLowExecEventHandler, priority ...int) { func (slf *event) RegMessageLowExecEvent(handler MessageLowExecEventHandler, priority ...int) {
slf.messageLowExecEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.messageLowExecEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -304,7 +306,7 @@ func (slf *event) RegConnectionOpenedAfterEvent(handler ConnectionOpenedAfterEve
if slf.network == NetworkHttp { if slf.network == NetworkHttp {
panic(ErrNetworkIncompatibleHttp) panic(ErrNetworkIncompatibleHttp)
} }
slf.connectionOpenedAfterEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.connectionOpenedAfterEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -322,7 +324,7 @@ func (slf *event) RegConnectionWritePacketBeforeEvent(handler ConnectionWritePac
if slf.network == NetworkHttp { if slf.network == NetworkHttp {
panic(ErrNetworkIncompatibleHttp) panic(ErrNetworkIncompatibleHttp)
} }
slf.connectionWritePacketBeforeHandlers.Append(handler, slice.GetValue(priority, 0)) slf.connectionWritePacketBeforeHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -340,7 +342,7 @@ func (slf *event) OnConnectionWritePacketBeforeEvent(conn *Conn, packet []byte)
// RegShuntChannelCreatedEvent 在分流通道创建时将立刻执行被注册的事件处理函数 // RegShuntChannelCreatedEvent 在分流通道创建时将立刻执行被注册的事件处理函数
func (slf *event) RegShuntChannelCreatedEvent(handler ShuntChannelCreatedEventHandler, priority ...int) { func (slf *event) RegShuntChannelCreatedEvent(handler ShuntChannelCreatedEventHandler, priority ...int) {
slf.shuntChannelCreatedEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.shuntChannelCreatedEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -355,7 +357,7 @@ func (slf *event) OnShuntChannelCreatedEvent(name string) {
// RegShuntChannelCloseEvent 在分流通道关闭时将立刻执行被注册的事件处理函数 // RegShuntChannelCloseEvent 在分流通道关闭时将立刻执行被注册的事件处理函数
func (slf *event) RegShuntChannelCloseEvent(handler ShuntChannelClosedEventHandler, priority ...int) { func (slf *event) RegShuntChannelCloseEvent(handler ShuntChannelClosedEventHandler, priority ...int) {
slf.shuntChannelClosedEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.shuntChannelClosedEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -377,7 +379,7 @@ func (slf *event) OnShuntChannelClosedEvent(name string) {
// - 数据包格式校验 // - 数据包格式校验
// - 数据包分包等情况处理 // - 数据包分包等情况处理
func (slf *event) RegConnectionPacketPreprocessEvent(handler ConnectionPacketPreprocessEventHandler, priority ...int) { func (slf *event) RegConnectionPacketPreprocessEvent(handler ConnectionPacketPreprocessEventHandler, priority ...int) {
slf.connectionPacketPreprocessEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.connectionPacketPreprocessEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -401,7 +403,7 @@ func (slf *event) OnConnectionPacketPreprocessEvent(conn *Conn, packet []byte, u
// //
// 适用于限流等场景 // 适用于限流等场景
func (slf *event) RegMessageExecBeforeEvent(handler MessageExecBeforeEventHandler, priority ...int) { func (slf *event) RegMessageExecBeforeEvent(handler MessageExecBeforeEventHandler, priority ...int) {
slf.messageExecBeforeEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.messageExecBeforeEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String())) log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handler", reflect.TypeOf(handler).String()))
} }
@ -425,7 +427,7 @@ func (slf *event) OnMessageExecBeforeEvent(message *Message) bool {
// RegMessageReadyEvent 在服务器消息处理器准备就绪时立即执行被注册的事件处理函数 // RegMessageReadyEvent 在服务器消息处理器准备就绪时立即执行被注册的事件处理函数
func (slf *event) RegMessageReadyEvent(handler MessageReadyEventHandler, priority ...int) { func (slf *event) RegMessageReadyEvent(handler MessageReadyEventHandler, priority ...int) {
slf.messageReadyEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.messageReadyEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
func (slf *event) OnMessageReadyEvent() { func (slf *event) OnMessageReadyEvent() {
@ -446,7 +448,7 @@ func (slf *event) OnMessageReadyEvent() {
// RegDeadlockDetectEvent 在死锁检测触发时立即执行被注册的事件处理函数 // RegDeadlockDetectEvent 在死锁检测触发时立即执行被注册的事件处理函数
func (slf *event) RegDeadlockDetectEvent(handler OnDeadlockDetectEventHandler, priority ...int) { func (slf *event) RegDeadlockDetectEvent(handler OnDeadlockDetectEventHandler, priority ...int) {
slf.deadlockDetectEventHandlers.Append(handler, slice.GetValue(priority, 0)) slf.deadlockDetectEventHandlers.Append(handler, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
func (slf *event) OnDeadlockDetectEvent(message *Message) { func (slf *event) OnDeadlockDetectEvent(message *Message) {

View File

@ -2,7 +2,8 @@ package gateway
import ( import (
"github.com/kercylan98/minotaur/server" "github.com/kercylan98/minotaur/server"
"github.com/kercylan98/minotaur/utils/slice" "github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/collection/listings"
) )
type ( type (
@ -16,27 +17,27 @@ type (
func newEvents() *events { func newEvents() *events {
return &events{ return &events{
connectionOpenedEventHandles: slice.NewPriority[ConnectionOpenedEventHandle](), connectionOpenedEventHandles: listings.NewPrioritySlice[ConnectionOpenedEventHandle](),
connectionClosedEventHandles: slice.NewPriority[ConnectionClosedEventHandle](), connectionClosedEventHandles: listings.NewPrioritySlice[ConnectionClosedEventHandle](),
connectionReceivePacketEventHandles: slice.NewPriority[ConnectionReceivePacketEventHandle](), connectionReceivePacketEventHandles: listings.NewPrioritySlice[ConnectionReceivePacketEventHandle](),
endpointConnectOpenedEventHandles: slice.NewPriority[EndpointConnectOpenedEventHandle](), endpointConnectOpenedEventHandles: listings.NewPrioritySlice[EndpointConnectOpenedEventHandle](),
endpointConnectClosedEventHandles: slice.NewPriority[EndpointConnectClosedEventHandle](), endpointConnectClosedEventHandles: listings.NewPrioritySlice[EndpointConnectClosedEventHandle](),
endpointConnectReceivePacketEventHandles: slice.NewPriority[EndpointConnectReceivePacketEventHandle](), endpointConnectReceivePacketEventHandles: listings.NewPrioritySlice[EndpointConnectReceivePacketEventHandle](),
} }
} }
type events struct { type events struct {
connectionOpenedEventHandles *slice.Priority[ConnectionOpenedEventHandle] connectionOpenedEventHandles *listings.PrioritySlice[ConnectionOpenedEventHandle]
connectionClosedEventHandles *slice.Priority[ConnectionClosedEventHandle] connectionClosedEventHandles *listings.PrioritySlice[ConnectionClosedEventHandle]
connectionReceivePacketEventHandles *slice.Priority[ConnectionReceivePacketEventHandle] connectionReceivePacketEventHandles *listings.PrioritySlice[ConnectionReceivePacketEventHandle]
endpointConnectOpenedEventHandles *slice.Priority[EndpointConnectOpenedEventHandle] endpointConnectOpenedEventHandles *listings.PrioritySlice[EndpointConnectOpenedEventHandle]
endpointConnectClosedEventHandles *slice.Priority[EndpointConnectClosedEventHandle] endpointConnectClosedEventHandles *listings.PrioritySlice[EndpointConnectClosedEventHandle]
endpointConnectReceivePacketEventHandles *slice.Priority[EndpointConnectReceivePacketEventHandle] endpointConnectReceivePacketEventHandles *listings.PrioritySlice[EndpointConnectReceivePacketEventHandle]
} }
// RegConnectionOpenedEventHandle 注册客户端连接打开事件处理函数 // RegConnectionOpenedEventHandle 注册客户端连接打开事件处理函数
func (slf *events) RegConnectionOpenedEventHandle(handle ConnectionOpenedEventHandle, priority ...int) { func (slf *events) RegConnectionOpenedEventHandle(handle ConnectionOpenedEventHandle, priority ...int) {
slf.connectionOpenedEventHandles.Append(handle, slice.GetValue(priority, 0)) slf.connectionOpenedEventHandles.Append(handle, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
func (slf *events) OnConnectionOpenedEvent(gateway *Gateway, conn *server.Conn) { func (slf *events) OnConnectionOpenedEvent(gateway *Gateway, conn *server.Conn) {
@ -48,7 +49,7 @@ func (slf *events) OnConnectionOpenedEvent(gateway *Gateway, conn *server.Conn)
// RegConnectionClosedEventHandle 注册客户端连接关闭事件处理函数 // RegConnectionClosedEventHandle 注册客户端连接关闭事件处理函数
func (slf *events) RegConnectionClosedEventHandle(handle ConnectionClosedEventHandle, priority ...int) { func (slf *events) RegConnectionClosedEventHandle(handle ConnectionClosedEventHandle, priority ...int) {
slf.connectionClosedEventHandles.Append(handle, slice.GetValue(priority, 0)) slf.connectionClosedEventHandles.Append(handle, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
func (slf *events) OnConnectionClosedEvent(gateway *Gateway, conn *server.Conn) { func (slf *events) OnConnectionClosedEvent(gateway *Gateway, conn *server.Conn) {
@ -60,7 +61,7 @@ func (slf *events) OnConnectionClosedEvent(gateway *Gateway, conn *server.Conn)
// RegConnectionReceivePacketEventHandle 注册客户端连接接收数据包事件处理函数 // RegConnectionReceivePacketEventHandle 注册客户端连接接收数据包事件处理函数
func (slf *events) RegConnectionReceivePacketEventHandle(handle ConnectionReceivePacketEventHandle, priority ...int) { func (slf *events) RegConnectionReceivePacketEventHandle(handle ConnectionReceivePacketEventHandle, priority ...int) {
slf.connectionReceivePacketEventHandles.Append(handle, slice.GetValue(priority, 0)) slf.connectionReceivePacketEventHandles.Append(handle, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
func (slf *events) OnConnectionReceivePacketEvent(gateway *Gateway, conn *server.Conn, packet []byte) { func (slf *events) OnConnectionReceivePacketEvent(gateway *Gateway, conn *server.Conn, packet []byte) {
@ -72,7 +73,7 @@ func (slf *events) OnConnectionReceivePacketEvent(gateway *Gateway, conn *server
// RegEndpointConnectOpenedEventHandle 注册端点连接打开事件处理函数 // RegEndpointConnectOpenedEventHandle 注册端点连接打开事件处理函数
func (slf *events) RegEndpointConnectOpenedEventHandle(handle EndpointConnectOpenedEventHandle, priority ...int) { func (slf *events) RegEndpointConnectOpenedEventHandle(handle EndpointConnectOpenedEventHandle, priority ...int) {
slf.endpointConnectOpenedEventHandles.Append(handle, slice.GetValue(priority, 0)) slf.endpointConnectOpenedEventHandles.Append(handle, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
func (slf *events) OnEndpointConnectOpenedEvent(gateway *Gateway, endpoint *Endpoint) { func (slf *events) OnEndpointConnectOpenedEvent(gateway *Gateway, endpoint *Endpoint) {
@ -84,7 +85,7 @@ func (slf *events) OnEndpointConnectOpenedEvent(gateway *Gateway, endpoint *Endp
// RegEndpointConnectClosedEventHandle 注册端点连接关闭事件处理函数 // RegEndpointConnectClosedEventHandle 注册端点连接关闭事件处理函数
func (slf *events) RegEndpointConnectClosedEventHandle(handle EndpointConnectClosedEventHandle, priority ...int) { func (slf *events) RegEndpointConnectClosedEventHandle(handle EndpointConnectClosedEventHandle, priority ...int) {
slf.endpointConnectClosedEventHandles.Append(handle, slice.GetValue(priority, 0)) slf.endpointConnectClosedEventHandles.Append(handle, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
func (slf *events) OnEndpointConnectClosedEvent(gateway *Gateway, endpoint *Endpoint) { func (slf *events) OnEndpointConnectClosedEvent(gateway *Gateway, endpoint *Endpoint) {
@ -96,7 +97,7 @@ func (slf *events) OnEndpointConnectClosedEvent(gateway *Gateway, endpoint *Endp
// RegEndpointConnectReceivePacketEventHandle 注册端点连接接收数据包事件处理函数 // RegEndpointConnectReceivePacketEventHandle 注册端点连接接收数据包事件处理函数
func (slf *events) RegEndpointConnectReceivePacketEventHandle(handle EndpointConnectReceivePacketEventHandle, priority ...int) { func (slf *events) RegEndpointConnectReceivePacketEventHandle(handle EndpointConnectReceivePacketEventHandle, priority ...int) {
slf.endpointConnectReceivePacketEventHandles.Append(handle, slice.GetValue(priority, 0)) slf.endpointConnectReceivePacketEventHandles.Append(handle, collection.FindFirstOrDefaultInSlice(priority, 0))
} }
func (slf *events) OnEndpointConnectReceivePacketEvent(gateway *Gateway, endpoint *Endpoint, conn *server.Conn, packet []byte) { func (slf *events) OnEndpointConnectReceivePacketEvent(gateway *Gateway, endpoint *Endpoint, conn *server.Conn, packet []byte) {

View File

@ -7,19 +7,19 @@ import (
// NewHttpContext 基于 gin.Context 创建一个新的 HttpContext // NewHttpContext 基于 gin.Context 创建一个新的 HttpContext
func NewHttpContext(ctx *gin.Context) *HttpContext { func NewHttpContext(ctx *gin.Context) *HttpContext {
hc := &HttpContext{ hc := &HttpContext{
ctx: ctx, Context: ctx,
} }
return hc return hc
} }
// HttpContext 基于 gin.Context 的 http 请求上下文 // HttpContext 基于 gin.Context 的 http 请求上下文
type HttpContext struct { type HttpContext struct {
ctx *gin.Context *gin.Context
} }
// Gin 获取 gin.Context // Gin 获取 gin.Context
func (slf *HttpContext) Gin() *gin.Context { func (slf *HttpContext) Gin() *gin.Context {
return slf.ctx return slf.Context
} }
// ReadTo 读取请求数据到指定结构体,如果失败则返回错误 // ReadTo 读取请求数据到指定结构体,如果失败则返回错误

View File

@ -27,7 +27,7 @@ func (slf *HttpRouter[Context]) handlesConvert(handlers []HandlerFunc[Context])
hc := slf.packer(ctx) hc := slf.packer(ctx)
var now = time.Now() var now = time.Now()
handler(hc) handler(hc)
slf.srv.low(nil, now, time.Second, "HTTP ["+ctx.Request.Method+"] "+ctx.Request.RequestURI) slf.srv.low(nil, now, slf.srv.asyncLowMessageDuration, true, "HTTP ["+ctx.Request.Method+"] "+ctx.Request.RequestURI)
}) })
} }
return handles return handles
@ -146,3 +146,9 @@ func (slf *HttpRouter[Context]) Group(relativePath string, handlers ...HandlerFu
packer: slf.packer, packer: slf.packer,
} }
} }
// Use 将中间件附加到路由组。
func (slf *HttpRouter[Context]) Use(middleware ...HandlerFunc[Context]) *HttpRouter[Context] {
slf.group.Use(slf.handlesConvert(middleware)...)
return slf
}

View File

@ -0,0 +1,30 @@
package dispatcher
// Action 消息分发器操作器,用于暴露外部可操作的消息分发器函数
type Action[P Producer, M Message[P]] struct {
unlock bool
d *Dispatcher[P, M]
}
// Name 获取消息分发器名称
func (a *Action[P, M]) Name() string {
return a.d.Name()
}
// UnExpel 取消特定生产者的驱逐计划
func (a *Action[P, M]) UnExpel() {
if !a.unlock {
a.d.UnExpel()
} else {
a.d.noLockUnExpel()
}
}
// Expel 设置该消息分发器即将被驱逐,当消息分发器中没有任何消息时,会自动关闭
func (a *Action[P, M]) Expel() {
if !a.unlock {
a.d.Expel()
} else {
a.d.noLockExpel()
}
}

View File

@ -0,0 +1,210 @@
package dispatcher
import (
"fmt"
"github.com/alphadose/haxmap"
"github.com/kercylan98/minotaur/utils/buffer"
"github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/super"
"sync"
"sync/atomic"
)
var unique = struct{}{}
// Handler 消息处理器
type Handler[P Producer, M Message[P]] func(dispatcher *Dispatcher[P, M], message M)
// NewDispatcher 创建一个新的消息分发器 Dispatcher 实例
func NewDispatcher[P Producer, M Message[P]](bufferSize int, name string, handler Handler[P, M]) *Dispatcher[P, M] {
if bufferSize <= 0 || handler == nil {
panic(fmt.Errorf("bufferSize must be greater than 0 and handler must not be nil, but got bufferSize: %d, handler is nil: %v", bufferSize, handler == nil))
}
d := &Dispatcher[P, M]{
name: name,
buf: buffer.NewRingUnbounded[M](bufferSize),
handler: handler,
uniques: haxmap.New[string, struct{}](),
pmc: make(map[P]int64),
pmcF: make(map[P]func(p P, dispatcher *Action[P, M])),
abort: make(chan struct{}),
}
return d
}
// Dispatcher 用于服务器消息处理的消息分发器
//
// 这个消息分发器为并发安全的生产者和消费者模型,生产者可以是任意类型,消费者必须是 Message 接口的实现。
// 生产者可以通过 Put 方法并发安全地将消息放入消息分发器,消息执行过程不会阻塞到 Put 方法,同时允许在 Start 方法之前调用 Put 方法。
// 在执行 Start 方法后,消息分发器会阻塞地从消息缓冲区中读取消息,然后执行消息处理器,消息处理器的执行过程不会阻塞到消息的生产。
//
// 为了保证消息不丢失,内部采用了 buffer.RingUnbounded 作为缓冲区实现,并且消息分发器不提供 Close 方法。
// 如果需要关闭消息分发器,可以通过 Expel 方法设置驱逐计划,当消息分发器中没有任何消息时,将会被释放。
// 同时,也可以使用 UnExpel 方法取消驱逐计划。
//
// 为什么提供 Expel 和 UnExpel 方法:
// - 在连接断开时,当需要执行一系列消息处理时,如果直接关闭消息分发器,可能会导致消息丢失。所以提供了 Expel 方法,可以在消息处理完成后再关闭消息分发器。
// - 当消息还未处理完成时连接重连,如果没有取消驱逐计划,可能会导致消息分发器被关闭。所以提供了 UnExpel 方法,可以在连接重连后取消驱逐计划。
type Dispatcher[P Producer, M Message[P]] struct {
buf *buffer.RingUnbounded[M]
uniques *haxmap.Map[string, struct{}]
handler Handler[P, M]
expel bool
mc int64
pmc map[P]int64
pmcF map[P]func(p P, dispatcher *Action[P, M])
lock sync.RWMutex
name string
closedHandler atomic.Pointer[func(dispatcher *Action[P, M])]
abort chan struct{}
}
// SetProducerDoneHandler 设置特定生产者所有消息处理完成时的回调函数
// - 如果 handler 为 nil则会删除该生产者的回调函数
//
// 需要注意的是,该 handler 中
func (d *Dispatcher[P, M]) SetProducerDoneHandler(p P, handler func(p P, dispatcher *Action[P, M])) *Dispatcher[P, M] {
d.lock.Lock()
if handler == nil {
delete(d.pmcF, p)
} else {
d.pmcF[p] = handler
if pmc := d.pmc[p]; pmc <= 0 {
func(producer P, handler func(p P, dispatcher *Action[P, M])) {
defer func(producer P) {
if err := super.RecoverTransform(recover()); err != nil {
log.Error("Dispatcher.ProducerDoneHandler", log.Any("producer", producer), log.Err(err))
}
}(p)
handler(p, &Action[P, M]{d: d, unlock: true})
}(p, handler)
}
}
d.lock.Unlock()
return d
}
// SetClosedHandler 设置消息分发器关闭时的回调函数
func (d *Dispatcher[P, M]) SetClosedHandler(handler func(dispatcher *Action[P, M])) *Dispatcher[P, M] {
d.closedHandler.Store(&handler)
return d
}
// Name 获取消息分发器名称
func (d *Dispatcher[P, M]) Name() string {
return d.name
}
// Unique 设置唯一消息键,返回是否已存在
func (d *Dispatcher[P, M]) Unique(name string) bool {
_, loaded := d.uniques.GetOrSet(name, unique)
return loaded
}
// AntiUnique 取消唯一消息键
func (d *Dispatcher[P, M]) AntiUnique(name string) {
d.uniques.Del(name)
}
// Expel 设置该消息分发器即将被驱逐,当消息分发器中没有任何消息时,会自动关闭
func (d *Dispatcher[P, M]) Expel() {
d.lock.Lock()
d.noLockExpel()
d.lock.Unlock()
}
func (d *Dispatcher[P, M]) noLockExpel() {
d.expel = true
if d.mc <= 0 {
close(d.abort)
}
}
// UnExpel 取消特定生产者的驱逐计划
func (d *Dispatcher[P, M]) UnExpel() {
d.lock.Lock()
d.noLockUnExpel()
d.lock.Unlock()
}
func (d *Dispatcher[P, M]) noLockUnExpel() {
d.expel = false
}
// IncrCount 主动增量设置特定生产者的消息计数,这在等待异步消息完成后再关闭消息分发器时非常有用
// - 如果 i 为负数,则会减少消息计数
func (d *Dispatcher[P, M]) IncrCount(producer P, i int64) {
d.lock.Lock()
defer d.lock.Unlock()
d.mc += i
pmc := d.pmc[producer] + i
d.pmc[producer] = pmc
if d.mc <= 0 {
if f := d.pmcF[producer]; f != nil && pmc <= 0 {
func(producer P) {
defer func(producer P) {
if err := super.RecoverTransform(recover()); err != nil {
log.Error("Dispatcher.ProducerDoneHandler", log.Any("producer", producer), log.Err(err))
}
}(producer)
f(producer, &Action[P, M]{d: d, unlock: true})
}(producer)
}
}
}
// Put 将消息放入分发器
func (d *Dispatcher[P, M]) Put(message M) {
d.lock.Lock()
d.mc++
d.pmc[message.GetProducer()]++
d.lock.Unlock()
d.buf.Write(message)
}
// Start 以非阻塞的方式开始进行消息分发,当消息分发器中没有任何消息并且处于驱逐计划 Expel 时,将会自动关闭
func (d *Dispatcher[P, M]) Start() *Dispatcher[P, M] {
go func(d *Dispatcher[P, M]) {
process:
for {
select {
case <-d.abort:
d.buf.Close()
break process
case message := <-d.buf.Read():
// 先取出生产者信息,避免处理函数中将消息释放
p := message.GetProducer()
d.handler(d, message)
d.lock.Lock()
d.mc--
pmc := d.pmc[p] - 1
d.pmc[p] = pmc
if f := d.pmcF[p]; f != nil && pmc <= 0 {
func(producer P) {
defer func(producer P) {
if err := super.RecoverTransform(recover()); err != nil {
log.Error("Dispatcher.ProducerDoneHandler", log.Any("producer", producer), log.Err(err))
}
}(p)
f(p, &Action[P, M]{d: d, unlock: true})
}(p)
}
if d.mc <= 0 && d.expel {
d.buf.Close()
d.lock.Unlock()
break process
}
d.lock.Unlock()
}
}
if ch := d.closedHandler.Load(); ch != nil {
(*ch)(&Action[P, M]{d: d, unlock: true})
}
}(d)
return d
}
// Closed 判断消息分发器是否已关闭
func (d *Dispatcher[P, M]) Closed() bool {
return d.buf.Closed()
}

View File

@ -0,0 +1,37 @@
package dispatcher_test
import (
"fmt"
"github.com/kercylan98/minotaur/server/internal/dispatcher"
"sync"
"sync/atomic"
)
func ExampleNewDispatcher() {
m := new(atomic.Int64)
fm := new(atomic.Int64)
w := new(sync.WaitGroup)
w.Add(1)
d := dispatcher.NewDispatcher(1024, "example-dispatcher", func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
m.Add(1)
})
d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) {
w.Done()
})
var producers = []string{"producer1", "producer2", "producer3"}
for i := 0; i < len(producers); i++ {
p := producers[i]
for i := 0; i < 10; i++ {
d.Put(&TestMessage{producer: p})
}
d.SetProducerDoneHandler(p, func(p string, dispatcher *dispatcher.Action[string, *TestMessage]) {
fm.Add(1)
})
}
d.Start()
d.Expel()
w.Wait()
fmt.Println(fmt.Sprintf("producer num: %d, producer done: %d, finished: %d", len(producers), fm.Load(), m.Load()))
// Output:
// producer num: 3, producer done: 3, finished: 30
}

View File

@ -0,0 +1,332 @@
package dispatcher_test
import (
"github.com/kercylan98/minotaur/server/internal/dispatcher"
"sync"
"sync/atomic"
"testing"
"time"
)
type TestMessage struct {
producer string
v int
}
func (m *TestMessage) GetProducer() string {
return m.producer
}
func TestNewDispatcher(t *testing.T) {
var cases = []struct {
name string
bufferSize int
handler dispatcher.Handler[string, *TestMessage]
shouldPanic bool
}{
{name: "TestNewDispatcher_BufferSize0AndHandlerNil", bufferSize: 0, handler: nil, shouldPanic: true},
{name: "TestNewDispatcher_BufferSize0AndHandlerNotNil", bufferSize: 0, handler: func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {}, shouldPanic: true},
{name: "TestNewDispatcher_BufferSize1AndHandlerNil", bufferSize: 1, handler: nil, shouldPanic: true},
{name: "TestNewDispatcher_BufferSize1AndHandlerNotNil", bufferSize: 1, handler: func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {}, shouldPanic: false},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil && !c.shouldPanic {
t.Errorf("NewDispatcher() should not panic, but panic: %v", r)
}
}()
dispatcher.NewDispatcher(c.bufferSize, c.name, c.handler)
})
}
}
func TestDispatcher_SetProducerDoneHandler(t *testing.T) {
var cases = []struct {
name string
producer string
messageFinish *atomic.Bool
cancel bool
}{
{name: "TestDispatcher_SetProducerDoneHandlerNotCancel", producer: "producer", cancel: false},
{name: "TestDispatcher_SetProducerDoneHandlerCancel", producer: "producer", cancel: true},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
c.messageFinish = &atomic.Bool{}
w := new(sync.WaitGroup)
d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
w.Done()
})
d.Put(&TestMessage{producer: c.producer})
d.SetProducerDoneHandler(c.producer, func(p string, dispatcher *dispatcher.Action[string, *TestMessage]) {
c.messageFinish.Store(true)
})
if c.cancel {
d.SetProducerDoneHandler(c.producer, nil)
}
w.Add(1)
d.Start()
w.Wait()
if c.cancel && c.messageFinish.Load() {
t.Errorf("%s should cancel, but not", c.name)
}
})
}
}
func TestDispatcher_SetClosedHandler(t *testing.T) {
var cases = []struct {
name string
handlerFinishMsgCount *atomic.Int64
msgTime time.Duration
msgCount int
}{
{name: "TestDispatcher_SetClosedHandler_Normal", msgTime: 0, msgCount: 1},
{name: "TestDispatcher_SetClosedHandler_MessageCount1024", msgTime: 0, msgCount: 1024},
{name: "TestDispatcher_SetClosedHandler_MessageTime1sMessageCount3", msgTime: 1 * time.Second, msgCount: 3},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
c.handlerFinishMsgCount = &atomic.Int64{}
w := new(sync.WaitGroup)
d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
time.Sleep(c.msgTime)
c.handlerFinishMsgCount.Add(1)
})
d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) {
w.Done()
})
for i := 0; i < c.msgCount; i++ {
d.Put(&TestMessage{producer: "producer"})
}
w.Add(1)
d.Start()
d.Expel()
w.Wait()
if c.handlerFinishMsgCount.Load() != int64(c.msgCount) {
t.Errorf("%s should finish %d messages, but finish %d", c.name, c.msgCount, c.handlerFinishMsgCount.Load())
}
})
}
}
func TestIncrCount(t *testing.T) {
var cases = []struct {
name string
producer string
messageDone *atomic.Bool
}{
{name: "TestIncrCount_Normal", producer: "producer"},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
c.messageDone = &atomic.Bool{}
w := new(sync.WaitGroup)
w.Add(1)
var d *dispatcher.Dispatcher[string, *TestMessage]
d = dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
c.messageDone.Store(true)
d.IncrCount(c.producer, -1)
})
d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) {
w.Done()
})
d.Start()
d.IncrCount(c.producer, 1)
d.Expel()
d.Put(&TestMessage{producer: c.producer})
w.Wait()
if !c.messageDone.Load() {
t.Errorf("%s should done, but not", c.name)
}
})
}
}
func TestDispatcher_Expel(t *testing.T) {
var cases = []struct {
name string
handlerFinishMsgCount *atomic.Int64
msgTime time.Duration
msgCount int
}{
{name: "TestDispatcher_Expel_Normal", msgTime: 0, msgCount: 1},
{name: "TestDispatcher_Expel_MessageCount1024", msgTime: 0, msgCount: 1024},
{name: "TestDispatcher_Expel_MessageTime1sMessageCount3", msgTime: 1 * time.Second, msgCount: 3},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
c.handlerFinishMsgCount = &atomic.Int64{}
w := new(sync.WaitGroup)
d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
time.Sleep(c.msgTime)
c.handlerFinishMsgCount.Add(1)
})
d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) {
w.Done()
})
for i := 0; i < c.msgCount; i++ {
d.Put(&TestMessage{producer: "producer"})
}
w.Add(1)
d.Start()
d.Expel()
w.Wait()
if c.handlerFinishMsgCount.Load() != int64(c.msgCount) {
t.Errorf("%s should finish %d messages, but finish %d", c.name, c.msgCount, c.handlerFinishMsgCount.Load())
}
})
}
}
func TestDispatcher_UnExpel(t *testing.T) {
var cases = []struct {
name string
closed *atomic.Bool
isUnExpel bool
expect bool
}{
{name: "TestDispatcher_UnExpel_Normal", isUnExpel: true, expect: false},
{name: "TestDispatcher_UnExpel_NotExpel", isUnExpel: false, expect: true},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
c.closed = &atomic.Bool{}
w := new(sync.WaitGroup)
d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
w.Done()
})
d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) {
c.closed.Store(true)
})
d.Put(&TestMessage{producer: "producer"})
w.Add(1)
if c.isUnExpel {
d.Expel()
d.UnExpel()
} else {
d.Expel()
}
d.Start()
w.Wait()
if c.closed.Load() != c.expect {
t.Errorf("%s should %v, but %v", c.name, c.expect, c.closed.Load())
}
})
}
}
func TestDispatcher_Put(t *testing.T) {
var cases = []struct {
name string
producer string
messageDone *atomic.Bool
}{
{name: "TestDispatcher_Put_Normal", producer: "producer"},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
c.messageDone = &atomic.Bool{}
w := new(sync.WaitGroup)
w.Add(1)
d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
c.messageDone.Store(true)
w.Done()
})
d.Start()
d.Put(&TestMessage{producer: c.producer})
d.Expel()
w.Wait()
if !c.messageDone.Load() {
t.Errorf("%s should done, but not", c.name)
}
})
}
}
func TestDispatcher_Start(t *testing.T) {
var cases = []struct {
name string
producer string
messageDone *atomic.Bool
}{
{name: "TestDispatcher_Start_Normal", producer: "producer"},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
c.messageDone = &atomic.Bool{}
w := new(sync.WaitGroup)
w.Add(1)
d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
c.messageDone.Store(true)
w.Done()
})
d.Start()
d.Put(&TestMessage{producer: c.producer})
d.Expel()
w.Wait()
if !c.messageDone.Load() {
t.Errorf("%s should done, but not", c.name)
}
})
}
}
func TestDispatcher_Name(t *testing.T) {
var cases = []struct {
name string
}{
{name: "TestDispatcher_Name_Normal"},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
if d.Name() != c.name {
t.Errorf("%s should equal %s, but not", c.name, c.name)
}
})
}
}
func TestDispatcher_Closed(t *testing.T) {
var cases = []struct {
name string
}{
{name: "TestDispatcher_Closed_Normal"},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
w := new(sync.WaitGroup)
w.Add(1)
d := dispatcher.NewDispatcher(1024, c.name, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
d.SetClosedHandler(func(dispatcher *dispatcher.Action[string, *TestMessage]) { w.Done() })
d.Start()
d.Expel()
w.Wait()
if !d.Closed() {
t.Errorf("%s should closed, but not", c.name)
}
})
}
}

View File

@ -0,0 +1,166 @@
package dispatcher
import (
"sync"
)
const SystemName = "*system"
// NewManager 生成消息分发器管理器
func NewManager[P Producer, M Message[P]](bufferSize int, handler Handler[P, M]) *Manager[P, M] {
mgr := &Manager[P, M]{
handler: handler,
dispatchers: make(map[string]*Dispatcher[P, M]),
member: make(map[string]map[P]struct{}),
sys: NewDispatcher(bufferSize, SystemName, handler),
curr: make(map[P]*Dispatcher[P, M]),
size: bufferSize,
}
mgr.sys.SetClosedHandler(func(dispatcher *Action[P, M]) {
mgr.w.Done()
}).Start()
return mgr
}
// Manager 消息分发器管理器
type Manager[P Producer, M Message[P]] struct {
handler Handler[P, M] // 消息处理器
sys *Dispatcher[P, M] // 系统消息分发器
dispatchers map[string]*Dispatcher[P, M] // 当前所有正在工作的消息分发器
member map[string]map[P]struct{} // 当前正在工作的消息分发器对应的生产者
curr map[P]*Dispatcher[P, M] // 当前特定生产者正在使用的消息分发器
lock sync.RWMutex // 消息分发器锁
w sync.WaitGroup // 消息分发器等待组
size int // 消息分发器缓冲区大小
closedHandler func(name string)
createdHandler func(name string)
}
// Wait 等待所有消息分发器关闭
func (m *Manager[P, M]) Wait() {
m.w.Wait()
m.w.Add(1)
m.sys.Expel()
m.w.Wait()
}
// SetDispatcherClosedHandler 设置消息分发器关闭时的回调函数
func (m *Manager[P, M]) SetDispatcherClosedHandler(handler func(name string)) *Manager[P, M] {
m.closedHandler = handler
return m
}
// SetDispatcherCreatedHandler 设置消息分发器创建时的回调函数
func (m *Manager[P, M]) SetDispatcherCreatedHandler(handler func(name string)) *Manager[P, M] {
m.createdHandler = handler
return m
}
// HasDispatcher 检查是否存在指定名称的消息分发器
func (m *Manager[P, M]) HasDispatcher(name string) bool {
m.lock.RLock()
defer m.lock.RUnlock()
_, exist := m.dispatchers[name]
return exist
}
// GetDispatcherNum 获取当前正在工作的消息分发器数量
func (m *Manager[P, M]) GetDispatcherNum() int {
m.lock.RLock()
defer m.lock.RUnlock()
return len(m.dispatchers) + 1 // +1 系统消息分发器
}
// GetSystemDispatcher 获取系统消息分发器
func (m *Manager[P, M]) GetSystemDispatcher() *Dispatcher[P, M] {
return m.sys
}
// GetDispatcher 获取生产者正在使用的消息分发器,如果生产者没有绑定消息分发器,则会返回系统消息分发器
func (m *Manager[P, M]) GetDispatcher(p P) *Dispatcher[P, M] {
m.lock.Lock()
defer m.lock.Unlock()
curr, exist := m.curr[p]
if exist {
return curr
}
return m.sys
}
// BindProducer 绑定生产者使用特定的消息分发器,如果生产者已经绑定了消息分发器,则会先解绑
func (m *Manager[P, M]) BindProducer(p P, name string) {
if name == SystemName {
return
}
m.lock.Lock()
defer m.lock.Unlock()
member, exist := m.member[name]
if !exist {
member = make(map[P]struct{})
m.member[name] = member
}
if _, exist = member[p]; exist {
d := m.dispatchers[name]
d.SetProducerDoneHandler(p, nil)
d.UnExpel()
return
}
curr, exist := m.curr[p]
if exist {
delete(m.member[curr.name], p)
if len(m.member[curr.name]) == 0 {
curr.Expel()
}
}
dispatcher, exist := m.dispatchers[name]
if !exist {
m.w.Add(1)
dispatcher = NewDispatcher(m.size, name, m.handler).SetClosedHandler(func(dispatcher *Action[P, M]) {
// 消息分发器关闭时,将会将其从管理器中移除
m.lock.Lock()
delete(m.dispatchers, dispatcher.Name())
delete(m.member, dispatcher.Name())
m.lock.Unlock()
if m.closedHandler != nil {
m.closedHandler(dispatcher.Name())
}
m.w.Done()
}).Start()
m.dispatchers[name] = dispatcher
defer func(m *Manager[P, M], name string) {
if m.createdHandler != nil {
m.createdHandler(name)
}
}(m, dispatcher.Name())
}
m.curr[p] = dispatcher
member[p] = struct{}{}
}
// UnBindProducer 解绑生产者使用特定的消息分发器
func (m *Manager[P, M]) UnBindProducer(p P) {
m.lock.Lock()
curr, exist := m.curr[p]
m.lock.Unlock()
if !exist {
return
}
curr.SetProducerDoneHandler(p, func(p P, dispatcher *Action[P, M]) {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.member[dispatcher.Name()], p)
delete(m.curr, p)
if len(m.member[dispatcher.Name()]) == 0 {
dispatcher.Expel()
}
})
}

View File

@ -0,0 +1,23 @@
package dispatcher_test
import (
"fmt"
"github.com/kercylan98/minotaur/server/internal/dispatcher"
)
func ExampleNewManager() {
mgr := dispatcher.NewManager[string, *TestMessage](10124*16, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {
// do something
})
mgr.BindProducer("player_001", "shunt-001")
mgr.BindProducer("player_002", "shunt-002")
mgr.BindProducer("player_003", "shunt-sys")
mgr.BindProducer("player_004", "shunt-sys")
mgr.UnBindProducer("player_001")
mgr.UnBindProducer("player_002")
mgr.UnBindProducer("player_003")
mgr.UnBindProducer("player_004")
mgr.Wait()
fmt.Println("done")
// Output: done
}

View File

@ -0,0 +1,225 @@
package dispatcher_test
import (
"fmt"
"github.com/kercylan98/minotaur/server/internal/dispatcher"
"sync/atomic"
"testing"
)
func TestNewManager(t *testing.T) {
var cases = []struct {
name string
bufferSize int
handler dispatcher.Handler[string, *TestMessage]
shouldPanic bool
}{
{name: "TestNewManager_BufferSize0AndHandlerNil", bufferSize: 0, handler: nil, shouldPanic: true},
{name: "TestNewManager_BufferSize0AndHandlerNotNil", bufferSize: 0, handler: func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {}, shouldPanic: true},
{name: "TestNewManager_BufferSize1AndHandlerNil", bufferSize: 1, handler: nil, shouldPanic: true},
{name: "TestNewManager_BufferSize1AndHandlerNotNil", bufferSize: 1, handler: func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {}, shouldPanic: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil && !c.shouldPanic {
t.Errorf("NewManager() should not panic, but panic: %v", r)
}
}()
dispatcher.NewManager[string, *TestMessage](c.bufferSize, c.handler)
})
}
}
func TestManager_SetDispatcherClosedHandler(t *testing.T) {
var cases = []struct {
name string
setCloseHandler bool
}{
{name: "TestManager_SetDispatcherClosedHandler_Set", setCloseHandler: true},
{name: "TestManager_SetDispatcherClosedHandler_NotSet", setCloseHandler: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var closed atomic.Bool
m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
if c.setCloseHandler {
m.SetDispatcherClosedHandler(func(name string) {
closed.Store(true)
})
}
m.BindProducer(c.name, c.name)
m.UnBindProducer(c.name)
m.Wait()
if c.setCloseHandler && !closed.Load() {
t.Errorf("SetDispatcherClosedHandler() should be called")
}
})
}
}
func TestManager_SetDispatcherCreatedHandler(t *testing.T) {
var cases = []struct {
name string
setCreatedHandler bool
}{
{name: "TestManager_SetDispatcherCreatedHandler_Set", setCreatedHandler: true},
{name: "TestManager_SetDispatcherCreatedHandler_NotSet", setCreatedHandler: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var created atomic.Bool
m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
if c.setCreatedHandler {
m.SetDispatcherCreatedHandler(func(name string) {
created.Store(true)
})
}
m.BindProducer(c.name, c.name)
m.UnBindProducer(c.name)
m.Wait()
if c.setCreatedHandler && !created.Load() {
t.Errorf("SetDispatcherCreatedHandler() should be called")
}
})
}
}
func TestManager_HasDispatcher(t *testing.T) {
var cases = []struct {
name string
bindName string
has bool
}{
{name: "TestManager_HasDispatcher_Has", bindName: "TestManager_HasDispatcher_Has", has: true},
{name: "TestManager_HasDispatcher_NotHas", bindName: "TestManager_HasDispatcher_NotHas", has: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
m.BindProducer(c.bindName, c.bindName)
var cond string
if c.has {
cond = c.bindName
}
if m.HasDispatcher(cond) != c.has {
t.Errorf("HasDispatcher() should return %v", c.has)
}
})
}
}
func TestManager_GetDispatcherNum(t *testing.T) {
var cases = []struct {
name string
num int
}{
{name: "TestManager_GetDispatcherNum_N1", num: -1},
{name: "TestManager_GetDispatcherNum_0", num: 0},
{name: "TestManager_GetDispatcherNum_1", num: 1},
{name: "TestManager_GetDispatcherNum_2", num: 2},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
switch {
case c.num <= 0:
return
case c.num == 1:
if m.GetDispatcherNum() != 1 {
t.Errorf("GetDispatcherNum() should return 1")
}
return
default:
for i := 0; i < c.num-1; i++ {
m.BindProducer(fmt.Sprintf("%s_%d", c.name, i), fmt.Sprintf("%s_%d", c.name, i))
}
if m.GetDispatcherNum() != c.num {
t.Errorf("GetDispatcherNum() should return %v", c.num)
}
}
})
}
}
func TestManager_GetSystemDispatcher(t *testing.T) {
var cases = []struct {
name string
}{
{name: "TestManager_GetSystemDispatcher"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
if m.GetSystemDispatcher() == nil {
t.Errorf("GetSystemDispatcher() should not return nil")
}
})
}
}
func TestManager_GetDispatcher(t *testing.T) {
var cases = []struct {
name string
bindName string
}{
{name: "TestManager_GetDispatcher", bindName: "TestManager_GetDispatcher"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
m.BindProducer(c.bindName, c.bindName)
if m.GetDispatcher(c.bindName) == nil {
t.Errorf("GetDispatcher() should not return nil")
}
})
}
}
func TestManager_BindProducer(t *testing.T) {
var cases = []struct {
name string
bindName string
}{
{name: "TestManager_BindProducer", bindName: "TestManager_BindProducer"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
m.BindProducer(c.bindName, c.bindName)
if m.GetDispatcher(c.bindName) == nil {
t.Errorf("GetDispatcher() should not return nil")
}
})
}
}
func TestManager_UnBindProducer(t *testing.T) {
var cases = []struct {
name string
bindName string
}{
{name: "TestManager_UnBindProducer", bindName: "TestManager_UnBindProducer"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
m := dispatcher.NewManager[string, *TestMessage](1024, func(dispatcher *dispatcher.Dispatcher[string, *TestMessage], message *TestMessage) {})
m.BindProducer(c.bindName, c.bindName)
m.UnBindProducer(c.bindName)
if m.GetDispatcher(c.bindName) != m.GetSystemDispatcher() {
t.Errorf("GetDispatcher() should return SystemDispatcher")
}
})
}
}

View File

@ -0,0 +1,6 @@
package dispatcher
type Message[P comparable] interface {
// GetProducer 获取消息生产者
GetProducer() P
}

View File

@ -0,0 +1,5 @@
package dispatcher
type Producer interface {
comparable
}

View File

@ -96,7 +96,7 @@ func (slf *Lockstep[ClientID, Command]) GetClientCount() int {
func (slf *Lockstep[ClientID, Command]) DropCache(handler func(frame int64) bool) { func (slf *Lockstep[ClientID, Command]) DropCache(handler func(frame int64) bool) {
slf.frameCacheLock.Lock() slf.frameCacheLock.Lock()
defer slf.frameCacheLock.Unlock() defer slf.frameCacheLock.Unlock()
for frame, _ := range slf.frameCache { for frame := range slf.frameCache {
if handler(frame) { if handler(frame) {
delete(slf.frameCache, frame) delete(slf.frameCache, frame)
} }

View File

@ -1,7 +1,8 @@
package server package server
import ( import (
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/server/internal/dispatcher"
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/super" "github.com/kercylan98/minotaur/utils/super"
) )
@ -70,20 +71,32 @@ type (
// HasMessageType 检查是否存在指定的消息类型 // HasMessageType 检查是否存在指定的消息类型
func HasMessageType(mt MessageType) bool { func HasMessageType(mt MessageType) bool {
return hash.Exist(messageNames, mt) return collection.KeyInMap(messageNames, mt)
} }
// Message 服务器消息 // Message 服务器消息
type Message struct { type Message struct {
dis *dispatcher.Dispatcher[string, *Message] // 指定消息发送到特定的分发器
conn *Conn conn *Conn
err error
ordinaryHandler func() ordinaryHandler func()
exceptionHandler func() error exceptionHandler func() error
errHandler func(err error) errHandler func(err error)
marks []log.Field
packet []byte packet []byte
err error producer string
name string name string
t MessageType t MessageType
marks []log.Field }
// bindDispatcher 绑定分发器
func (slf *Message) bindDispatcher(dis *dispatcher.Dispatcher[string, *Message]) *Message {
slf.dis = dis
return slf
}
func (slf *Message) GetProducer() string {
return slf.producer
} }
// reset 重置消息结构体 // reset 重置消息结构体
@ -97,6 +110,8 @@ func (slf *Message) reset() {
slf.name = "" slf.name = ""
slf.t = 0 slf.t = 0
slf.marks = nil slf.marks = nil
slf.producer = ""
slf.dis = nil
} }
// MessageType 返回消息类型 // MessageType 返回消息类型
@ -126,78 +141,91 @@ func (slf MessageType) String() string {
// castToPacketMessage 将消息转换为数据包消息 // castToPacketMessage 将消息转换为数据包消息
func (slf *Message) castToPacketMessage(conn *Conn, packet []byte, mark ...log.Field) *Message { func (slf *Message) castToPacketMessage(conn *Conn, packet []byte, mark ...log.Field) *Message {
slf.producer = conn.GetID()
slf.t, slf.conn, slf.packet, slf.marks = MessageTypePacket, conn, packet, mark slf.t, slf.conn, slf.packet, slf.marks = MessageTypePacket, conn, packet, mark
return slf return slf
} }
// castToTickerMessage 将消息转换为定时器消息 // castToTickerMessage 将消息转换为定时器消息
func (slf *Message) castToTickerMessage(name string, caller func(), mark ...log.Field) *Message { func (slf *Message) castToTickerMessage(name string, caller func(), mark ...log.Field) *Message {
slf.producer = "sys"
slf.t, slf.name, slf.ordinaryHandler, slf.marks = MessageTypeTicker, name, caller, mark slf.t, slf.name, slf.ordinaryHandler, slf.marks = MessageTypeTicker, name, caller, mark
return slf return slf
} }
// castToShuntTickerMessage 将消息转换为分发器定时器消息 // castToShuntTickerMessage 将消息转换为分发器定时器消息
func (slf *Message) castToShuntTickerMessage(conn *Conn, name string, caller func(), mark ...log.Field) *Message { func (slf *Message) castToShuntTickerMessage(conn *Conn, name string, caller func(), mark ...log.Field) *Message {
slf.producer = conn.GetID()
slf.t, slf.conn, slf.name, slf.ordinaryHandler, slf.marks = MessageTypeShuntTicker, conn, name, caller, mark slf.t, slf.conn, slf.name, slf.ordinaryHandler, slf.marks = MessageTypeShuntTicker, conn, name, caller, mark
return slf return slf
} }
// castToAsyncMessage 将消息转换为异步消息 // castToAsyncMessage 将消息转换为异步消息
func (slf *Message) castToAsyncMessage(caller func() error, callback func(err error), mark ...log.Field) *Message { func (slf *Message) castToAsyncMessage(caller func() error, callback func(err error), mark ...log.Field) *Message {
slf.producer = "sys"
slf.t, slf.exceptionHandler, slf.errHandler, slf.marks = MessageTypeAsync, caller, callback, mark slf.t, slf.exceptionHandler, slf.errHandler, slf.marks = MessageTypeAsync, caller, callback, mark
return slf return slf
} }
// castToAsyncCallbackMessage 将消息转换为异步回调消息 // castToAsyncCallbackMessage 将消息转换为异步回调消息
func (slf *Message) castToAsyncCallbackMessage(err error, caller func(err error), mark ...log.Field) *Message { func (slf *Message) castToAsyncCallbackMessage(err error, caller func(err error), mark ...log.Field) *Message {
slf.producer = "sys"
slf.t, slf.err, slf.errHandler, slf.marks = MessageTypeAsyncCallback, err, caller, mark slf.t, slf.err, slf.errHandler, slf.marks = MessageTypeAsyncCallback, err, caller, mark
return slf return slf
} }
// castToShuntAsyncMessage 将消息转换为分流异步消息 // castToShuntAsyncMessage 将消息转换为分流异步消息
func (slf *Message) castToShuntAsyncMessage(conn *Conn, caller func() error, callback func(err error), mark ...log.Field) *Message { func (slf *Message) castToShuntAsyncMessage(conn *Conn, caller func() error, callback func(err error), mark ...log.Field) *Message {
slf.producer = conn.GetID()
slf.t, slf.conn, slf.exceptionHandler, slf.errHandler, slf.marks = MessageTypeShuntAsync, conn, caller, callback, mark slf.t, slf.conn, slf.exceptionHandler, slf.errHandler, slf.marks = MessageTypeShuntAsync, conn, caller, callback, mark
return slf return slf
} }
// castToShuntAsyncCallbackMessage 将消息转换为分流异步回调消息 // castToShuntAsyncCallbackMessage 将消息转换为分流异步回调消息
func (slf *Message) castToShuntAsyncCallbackMessage(conn *Conn, err error, caller func(err error), mark ...log.Field) *Message { func (slf *Message) castToShuntAsyncCallbackMessage(conn *Conn, err error, caller func(err error), mark ...log.Field) *Message {
slf.producer = conn.GetID()
slf.t, slf.conn, slf.err, slf.errHandler, slf.marks = MessageTypeShuntAsyncCallback, conn, err, caller, mark slf.t, slf.conn, slf.err, slf.errHandler, slf.marks = MessageTypeShuntAsyncCallback, conn, err, caller, mark
return slf return slf
} }
// castToUniqueAsyncMessage 将消息转换为唯一异步消息 // castToUniqueAsyncMessage 将消息转换为唯一异步消息
func (slf *Message) castToUniqueAsyncMessage(unique string, caller func() error, callback func(err error), mark ...log.Field) *Message { func (slf *Message) castToUniqueAsyncMessage(unique string, caller func() error, callback func(err error), mark ...log.Field) *Message {
slf.producer = "sys"
slf.t, slf.name, slf.exceptionHandler, slf.errHandler, slf.marks = MessageTypeUniqueAsync, unique, caller, callback, mark slf.t, slf.name, slf.exceptionHandler, slf.errHandler, slf.marks = MessageTypeUniqueAsync, unique, caller, callback, mark
return slf return slf
} }
// castToUniqueAsyncCallbackMessage 将消息转换为唯一异步回调消息 // castToUniqueAsyncCallbackMessage 将消息转换为唯一异步回调消息
func (slf *Message) castToUniqueAsyncCallbackMessage(unique string, err error, caller func(err error), mark ...log.Field) *Message { func (slf *Message) castToUniqueAsyncCallbackMessage(unique string, err error, caller func(err error), mark ...log.Field) *Message {
slf.producer = "sys"
slf.t, slf.name, slf.err, slf.errHandler, slf.marks = MessageTypeUniqueAsyncCallback, unique, err, caller, mark slf.t, slf.name, slf.err, slf.errHandler, slf.marks = MessageTypeUniqueAsyncCallback, unique, err, caller, mark
return slf return slf
} }
// castToUniqueShuntAsyncMessage 将消息转换为唯一分流异步消息 // castToUniqueShuntAsyncMessage 将消息转换为唯一分流异步消息
func (slf *Message) castToUniqueShuntAsyncMessage(conn *Conn, unique string, caller func() error, callback func(err error), mark ...log.Field) *Message { func (slf *Message) castToUniqueShuntAsyncMessage(conn *Conn, unique string, caller func() error, callback func(err error), mark ...log.Field) *Message {
slf.producer = conn.GetID()
slf.t, slf.conn, slf.name, slf.exceptionHandler, slf.errHandler, slf.marks = MessageTypeUniqueShuntAsync, conn, unique, caller, callback, mark slf.t, slf.conn, slf.name, slf.exceptionHandler, slf.errHandler, slf.marks = MessageTypeUniqueShuntAsync, conn, unique, caller, callback, mark
return slf return slf
} }
// castToUniqueShuntAsyncCallbackMessage 将消息转换为唯一分流异步回调消息 // castToUniqueShuntAsyncCallbackMessage 将消息转换为唯一分流异步回调消息
func (slf *Message) castToUniqueShuntAsyncCallbackMessage(conn *Conn, unique string, err error, caller func(err error), mark ...log.Field) *Message { func (slf *Message) castToUniqueShuntAsyncCallbackMessage(conn *Conn, unique string, err error, caller func(err error), mark ...log.Field) *Message {
slf.producer = conn.GetID()
slf.t, slf.conn, slf.name, slf.err, slf.errHandler, slf.marks = MessageTypeUniqueShuntAsyncCallback, conn, unique, err, caller, mark slf.t, slf.conn, slf.name, slf.err, slf.errHandler, slf.marks = MessageTypeUniqueShuntAsyncCallback, conn, unique, err, caller, mark
return slf return slf
} }
// castToSystemMessage 将消息转换为系统消息 // castToSystemMessage 将消息转换为系统消息
func (slf *Message) castToSystemMessage(caller func(), mark ...log.Field) *Message { func (slf *Message) castToSystemMessage(caller func(), mark ...log.Field) *Message {
slf.producer = "sys"
slf.t, slf.ordinaryHandler, slf.marks = MessageTypeSystem, caller, mark slf.t, slf.ordinaryHandler, slf.marks = MessageTypeSystem, caller, mark
return slf return slf
} }
// castToShuntMessage 将消息转换为分流消息 // castToShuntMessage 将消息转换为分流消息
func (slf *Message) castToShuntMessage(conn *Conn, caller func(), mark ...log.Field) *Message { func (slf *Message) castToShuntMessage(conn *Conn, caller func(), mark ...log.Field) *Message {
slf.producer = conn.GetID()
slf.t, slf.conn, slf.ordinaryHandler, slf.marks = MessageTypeShunt, conn, caller, mark slf.t, slf.conn, slf.ordinaryHandler, slf.marks = MessageTypeShunt, conn, caller, mark
return slf return slf
} }

View File

@ -4,9 +4,8 @@ import (
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/kercylan98/minotaur/server/internal/logger" "github.com/kercylan98/minotaur/server/internal/logger"
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/slice"
"github.com/kercylan98/minotaur/utils/super" "github.com/kercylan98/minotaur/utils/super"
"github.com/panjf2000/gnet" "github.com/panjf2000/gnet"
"github.com/xtaci/kcp-go/v5" "github.com/xtaci/kcp-go/v5"
@ -42,6 +41,17 @@ var (
networks = []Network{ networks = []Network{
NetworkNone, NetworkTcp, NetworkTcp4, NetworkTcp6, NetworkUdp, NetworkUdp4, NetworkUdp6, NetworkUnix, NetworkHttp, NetworkWebsocket, NetworkKcp, NetworkGRPC, NetworkNone, NetworkTcp, NetworkTcp4, NetworkTcp6, NetworkUdp, NetworkUdp4, NetworkUdp6, NetworkUnix, NetworkHttp, NetworkWebsocket, NetworkKcp, NetworkGRPC,
} }
socketNetworks = map[Network]struct{}{
NetworkTcp: {},
NetworkTcp4: {},
NetworkTcp6: {},
NetworkUdp: {},
NetworkUdp4: {},
NetworkUdp6: {},
NetworkUnix: {},
NetworkKcp: {},
NetworkWebsocket: {},
}
) )
func init() { func init() {
@ -53,12 +63,12 @@ func init() {
// GetNetworks 获取所有支持的网络模式 // GetNetworks 获取所有支持的网络模式
func GetNetworks() []Network { func GetNetworks() []Network {
return slice.Copy(networks) return collection.CloneSlice(networks)
} }
// check 检查网络模式是否支持 // check 检查网络模式是否支持
func (n Network) check() { func (n Network) check() {
if !hash.Exist(networkNameMap, string(n)) { if !collection.KeyInMap(networkNameMap, string(n)) {
panic(fmt.Errorf("unsupported network mode: %s", n)) panic(fmt.Errorf("unsupported network mode: %s", n))
} }
} }
@ -306,3 +316,8 @@ func (n Network) websocketMode(state chan<- error, srv *Server) {
} }
}((&listener{srv: srv, Listener: l, state: state}).init()) }((&listener{srv: srv, Listener: l, state: state}).init())
} }
// IsSocket 返回当前服务器的网络模式是否为 Socket 模式
func (n Network) IsSocket() bool {
return collection.KeyInMap(socketNetworks, n)
}

View File

@ -32,27 +32,46 @@ type option struct {
} }
type runtime struct { type runtime struct {
deadlockDetect time.Duration // 是否开启死锁检测 deadlockDetect time.Duration // 是否开启死锁检测
supportMessageTypes map[int]bool // websocket 模式下支持的消息类型 supportMessageTypes map[int]bool // websocket 模式下支持的消息类型
certFile, keyFile string // TLS文件 certFile, keyFile string // TLS文件
tickerPool *timer.Pool // 定时器池 tickerPool *timer.Pool // 定时器池
ticker *timer.Ticker // 定时器 ticker *timer.Ticker // 定时器
tickerAutonomy bool // 定时器是否独立运行 tickerAutonomy bool // 定时器是否独立运行
connTickerSize int // 连接定时器大小 connTickerSize int // 连接定时器大小
websocketReadDeadline time.Duration // websocket 连接超时时间 websocketReadDeadline time.Duration // websocket 连接超时时间
websocketCompression int // websocket 压缩等级 websocketCompression int // websocket 压缩等级
websocketWriteCompression bool // websocket 写入压缩 websocketWriteCompression bool // websocket 写入压缩
limitLife time.Duration // 限制最大生命周期 limitLife time.Duration // 限制最大生命周期
packetWarnSize int // 数据包大小警告 packetWarnSize int // 数据包大小警告
messageStatisticsDuration time.Duration // 消息统计时长 messageStatisticsDuration time.Duration // 消息统计时长
messageStatisticsLimit int // 消息统计数量 messageStatisticsLimit int // 消息统计数量
messageStatistics []*atomic.Int64 // 消息统计数量 messageStatistics []*atomic.Int64 // 消息统计数量
messageStatisticsLock *sync.RWMutex // 消息统计锁 messageStatisticsLock *sync.RWMutex // 消息统计锁
dispatcherBufferSize int // 消息分发器缓冲区大小 connWriteBufferSize int // 连接写入缓冲区大小
connWriteBufferSize int // 连接写入缓冲区大小 websocketUpgrader *websocket.Upgrader // websocket 升级器
disableAutomaticReleaseShunt bool // 是否禁用自动释放分流渠道 websocketConnInitializer func(writer http.ResponseWriter, request *http.Request, conn *websocket.Conn) error // websocket 连接初始化
websocketUpgrader *websocket.Upgrader // websocket 升级器 dispatcherBufferSize int // 消息分发器缓冲区大小
websocketConnInitializer func(writer http.ResponseWriter, request *http.Request, conn *websocket.Conn) error // websocket 连接初始化 lowMessageDuration time.Duration // 慢消息时长
asyncLowMessageDuration time.Duration // 异步慢消息时长
}
// WithLowMessageDuration 通过指定慢消息时长的方式创建服务器,当消息处理时间超过指定时长时,将会输出 WARN 类型的日志
// - 默认值为 DefaultLowMessageDuration
// - 当 duration <= 0 时,表示关闭慢消息检测
func WithLowMessageDuration(duration time.Duration) Option {
return func(srv *Server) {
srv.lowMessageDuration = duration
}
}
// WithAsyncLowMessageDuration 通过指定异步消息的慢消息时长的方式创建服务器,当消息处理时间超过指定时长时,将会输出 WARN 类型的日志
// - 默认值为 DefaultAsyncLowMessageDuration
// - 当 duration <= 0 时,表示关闭慢消息检测
func WithAsyncLowMessageDuration(duration time.Duration) Option {
return func(srv *Server) {
srv.asyncLowMessageDuration = duration
}
} }
// WithWebsocketConnInitializer 通过 websocket 连接初始化的方式创建服务器,当 initializer 返回错误时,服务器将不会处理该连接的后续逻辑 // WithWebsocketConnInitializer 通过 websocket 连接初始化的方式创建服务器,当 initializer 返回错误时,服务器将不会处理该连接的后续逻辑
@ -78,14 +97,6 @@ func WithWebsocketUpgrade(upgrader *websocket.Upgrader) Option {
} }
} }
// WithDisableAutomaticReleaseShunt 通过禁用自动释放分流渠道的方式创建服务器
// - 默认不开启,当禁用自动释放分流渠道时,服务器将不会在连接断开时自动释放分流渠道,需要手动调用 ReleaseShunt 方法释放
func WithDisableAutomaticReleaseShunt() Option {
return func(srv *Server) {
srv.runtime.disableAutomaticReleaseShunt = true
}
}
// WithConnWriteBufferSize 通过连接写入缓冲区大小的方式创建服务器 // WithConnWriteBufferSize 通过连接写入缓冲区大小的方式创建服务器
// - 默认值为 DefaultConnWriteBufferSize // - 默认值为 DefaultConnWriteBufferSize
// - 设置合适的缓冲区大小可以提高服务器性能,但是会占用更多的内存 // - 设置合适的缓冲区大小可以提高服务器性能,但是会占用更多的内存

View File

@ -5,11 +5,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/kercylan98/minotaur/server/internal/dispatcher"
"github.com/kercylan98/minotaur/server/internal/logger" "github.com/kercylan98/minotaur/server/internal/logger"
"github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/hub"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/network" "github.com/kercylan98/minotaur/utils/network"
"github.com/kercylan98/minotaur/utils/sher"
"github.com/kercylan98/minotaur/utils/str" "github.com/kercylan98/minotaur/utils/str"
"github.com/kercylan98/minotaur/utils/super" "github.com/kercylan98/minotaur/utils/super"
"github.com/kercylan98/minotaur/utils/timer" "github.com/kercylan98/minotaur/utils/timer"
@ -21,7 +22,6 @@ import (
"os" "os"
"os/signal" "os/signal"
"runtime/debug" "runtime/debug"
"sync"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"time" "time"
@ -32,18 +32,17 @@ func New(network Network, options ...Option) *Server {
network.check() network.check()
server := &Server{ server := &Server{
runtime: &runtime{ runtime: &runtime{
packetWarnSize: DefaultPacketWarnSize, packetWarnSize: DefaultPacketWarnSize,
dispatcherBufferSize: DefaultDispatcherBufferSize, connWriteBufferSize: DefaultConnWriteBufferSize,
connWriteBufferSize: DefaultConnWriteBufferSize, dispatcherBufferSize: DefaultDispatcherBufferSize,
lowMessageDuration: DefaultLowMessageDuration,
asyncLowMessageDuration: DefaultAsyncLowMessageDuration,
}, },
hub: &hub{}, connMgr: &connMgr{},
option: &option{}, option: &option{},
network: network, network: network,
closeChannel: make(chan struct{}, 1), closeChannel: make(chan struct{}, 1),
systemSignal: make(chan os.Signal, 1), systemSignal: make(chan os.Signal, 1),
dispatchers: make(map[string]*dispatcher),
dispatcherMember: map[string]map[string]*Conn{},
currDispatcher: map[string]*dispatcher{},
} }
server.ctx, server.cancel = context.WithCancel(context.Background()) server.ctx, server.cancel = context.WithCancel(context.Background())
server.event = newEvent(server) server.event = newEvent(server)
@ -68,32 +67,29 @@ func New(network Network, options ...Option) *Server {
// Server 网络服务器 // Server 网络服务器
type Server struct { type Server struct {
*event // 事件 *event // 事件
*runtime // 运行时 *runtime // 运行时
*option // 可选项 *option // 可选项
*hub // 连接集合 *connMgr // 连接集合
ginServer *gin.Engine // HTTP模式下的路由器 dispatcherMgr *dispatcher.Manager[string, *Message] // 消息分发器管理器
httpServer *http.Server // HTTP模式下的服务器 ginServer *gin.Engine // HTTP模式下的路由器
grpcServer *grpc.Server // GRPC模式下的服务器 httpServer *http.Server // HTTP模式下的服务器
gServer *gNet // TCP或UDP模式下的服务器 grpcServer *grpc.Server // GRPC模式下的服务器
multiple *MultipleServer // 多服务器模式下的服务器 gServer *gNet // TCP或UDP模式下的服务器
ants *ants.Pool // 协程池 multiple *MultipleServer // 多服务器模式下的服务器
messagePool *concurrent.Pool[*Message] // 消息池 ants *ants.Pool // 协程池
ctx context.Context // 上下文 messagePool *hub.ObjectPool[*Message] // 消息池
cancel context.CancelFunc // 停止上下文 ctx context.Context // 上下文
systemDispatcher *dispatcher // 系统消息分发器 cancel context.CancelFunc // 停止上下文
systemSignal chan os.Signal // 系统信号 systemSignal chan os.Signal // 系统信号
closeChannel chan struct{} // 关闭信号 closeChannel chan struct{} // 关闭信号
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误 multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
dispatchers map[string]*dispatcher // 消息分发器集合
dispatcherMember map[string]map[string]*Conn // 消息分发器包含的连接 messageCounter atomic.Int64 // 消息计数器
currDispatcher map[string]*dispatcher // 当前连接所处消息分发器 addr string // 侦听地址
dispatcherLock sync.RWMutex // 消息分发器锁 network Network // 网络类型
messageCounter atomic.Int64 // 消息计数器 closed uint32 // 服务器是否已关闭
addr string // 侦听地址 services []func() // 服务
network Network // 网络类型
closed uint32 // 服务器是否已关闭
services []func() // 服务
} }
// preCheckAndAdaptation 预检查及适配 // preCheckAndAdaptation 预检查及适配
@ -106,7 +102,7 @@ func (srv *Server) preCheckAndAdaptation(addr string) (startState <-chan error,
kcp.SystemTimedSched.Close() kcp.SystemTimedSched.Close()
} }
srv.hub.run(srv.ctx) srv.connMgr.run(srv.ctx)
return srv.network.adaptation(srv), nil return srv.network.adaptation(srv), nil
} }
@ -155,9 +151,7 @@ func (srv *Server) Run(addr string) (err error) {
// IsSocket 是否是 Socket 模式 // IsSocket 是否是 Socket 模式
func (srv *Server) IsSocket() bool { func (srv *Server) IsSocket() bool {
return srv.network == NetworkTcp || srv.network == NetworkTcp4 || srv.network == NetworkTcp6 || return srv.network.IsSocket()
srv.network == NetworkUdp || srv.network == NetworkUdp4 || srv.network == NetworkUdp6 ||
srv.network == NetworkUnix || srv.network == NetworkKcp || srv.network == NetworkWebsocket
} }
// RunNone 是 Run("") 的简写,仅适用于运行 NetworkNone 服务器 // RunNone 是 Run("") 的简写,仅适用于运行 NetworkNone 服务器
@ -197,11 +191,41 @@ func (srv *Server) shutdown(err error) {
log.Error("Server", log.String("state", "shutdown"), log.Err(err)) log.Error("Server", log.String("state", "shutdown"), log.Err(err))
} }
var infoCount int
for srv.messageCounter.Load() > 0 { for srv.messageCounter.Load() > 0 {
log.Info("Server", log.Any("network", srv.network), log.String("listen", srv.addr), if infoCount%10 == 0 || infoCount == 0 {
log.String("action", "shutdown"), log.String("state", "waiting"), log.Int64("message", srv.messageCounter.Load())) log.Info("Server",
log.Any("network", srv.network),
log.String("listen", srv.addr),
log.String("action", "shutdown"),
log.String("state", "waiting"),
log.Int64("message", srv.messageCounter.Load()))
}
time.Sleep(time.Second) time.Sleep(time.Second)
infoCount++
} }
dispatcherMgrStopSignal := make(chan struct{})
go func(srv *Server, c <-chan struct{}) {
var infoCount int
for {
select {
case <-c:
return
case <-time.After(time.Second):
if infoCount%10 == 0 || infoCount == 0 {
log.Info("Server",
log.Any("network", srv.network),
log.String("listen", srv.addr),
log.String("action", "shutdown"),
log.String("state", "waiting"),
log.Int64("dispatcher", srv.dispatcherMgr.GetDispatcherNum()))
}
infoCount++
}
}
}(srv, dispatcherMgrStopSignal)
srv.dispatcherMgr.Wait()
close(dispatcherMgrStopSignal)
if srv.multiple == nil { if srv.multiple == nil {
srv.OnStopEvent() srv.OnStopEvent()
} }
@ -222,13 +246,6 @@ func (srv *Server) shutdown(err error) {
srv.ants.Release() srv.ants.Release()
srv.ants = nil srv.ants = nil
} }
srv.dispatcherLock.Lock()
for s, d := range srv.dispatchers {
srv.OnShuntChannelClosedEvent(d.name)
d.close()
delete(srv.dispatchers, s)
}
srv.dispatcherLock.Unlock()
if srv.grpcServer != nil { if srv.grpcServer != nil {
srv.grpcServer.GracefulStop() srv.grpcServer.GracefulStop()
} }
@ -300,109 +317,25 @@ func (srv *Server) GetMessageCount() int64 {
} }
// UseShunt 切换连接所使用的消息分流渠道,当分流渠道 name 不存在时将会创建一个新的分流渠道,否则将会加入已存在的分流渠道 // UseShunt 切换连接所使用的消息分流渠道,当分流渠道 name 不存在时将会创建一个新的分流渠道,否则将会加入已存在的分流渠道
// - 默认情况下,所有连接都使用系统通道进行消息分发,当指定消息分流渠道时,将会使用指定的消息分流渠道进行消息分发 // - 默认情况下,所有连接都使用系统通道进行消息分发,当指定消息分流渠道且为分流消息类型时,将会使用指定的消息分流渠道进行消息分发
// - 在使用 WithDisableAutomaticReleaseShunt 创建服务器后,必须始终在连接不再使用后主动通过 ReleaseShunt 释放消息分流渠道,否则将造成内存泄漏 // - 分流渠道会在连接断开时标记为驱逐状态,当分流渠道中的所有消息处理完毕且没有新连接使用时,将会被清除
func (srv *Server) UseShunt(conn *Conn, name string) { func (srv *Server) UseShunt(conn *Conn, name string) {
srv.dispatcherLock.Lock() srv.dispatcherMgr.BindProducer(conn.GetID(), name)
defer srv.dispatcherLock.Unlock()
d, exist := srv.dispatchers[name]
if !exist {
d = generateDispatcher(srv.dispatcherBufferSize, name, srv.dispatchMessage)
srv.OnShuntChannelCreatedEvent(d.name)
go d.start()
srv.dispatchers[name] = d
}
curr, exist := srv.currDispatcher[conn.GetID()]
if exist {
if curr.name == name {
return
}
delete(srv.dispatcherMember[curr.name], conn.GetID())
if curr.name != serverSystemDispatcher && len(srv.dispatcherMember[curr.name]) == 0 {
delete(srv.dispatchers, curr.name)
curr.transfer(d)
srv.OnShuntChannelClosedEvent(d.name)
curr.close()
}
}
srv.currDispatcher[conn.GetID()] = d
member, exist := srv.dispatcherMember[name]
if !exist {
member = map[string]*Conn{}
srv.dispatcherMember[name] = member
}
member[conn.GetID()] = conn
} }
// HasShunt 检查特定消息分流渠道是否存在 // HasShunt 检查特定消息分流渠道是否存在
func (srv *Server) HasShunt(name string) bool { func (srv *Server) HasShunt(name string) bool {
srv.dispatcherLock.RLock() return srv.dispatcherMgr.HasDispatcher(name)
defer srv.dispatcherLock.RUnlock()
_, exist := srv.dispatchers[name]
return exist
} }
// GetConnCurrShunt 获取连接当前所使用的消息分流渠道 // GetConnCurrShunt 获取连接当前所使用的消息分流渠道
func (srv *Server) GetConnCurrShunt(conn *Conn) string { func (srv *Server) GetConnCurrShunt(conn *Conn) string {
srv.dispatcherLock.RLock() return srv.dispatcherMgr.GetDispatcher(conn.GetID()).Name()
defer srv.dispatcherLock.RUnlock()
d, exist := srv.currDispatcher[conn.GetID()]
if exist {
return d.name
}
return serverSystemDispatcher
} }
// GetShuntNum 获取消息分流渠道数量 // GetShuntNum 获取消息分流渠道数量
func (srv *Server) GetShuntNum() int { func (srv *Server) GetShuntNum() int {
srv.dispatcherLock.RLock() return srv.dispatcherMgr.GetDispatcherNum()
defer srv.dispatcherLock.RUnlock()
return len(srv.dispatchers)
}
// getConnDispatcher 获取连接所使用的消息分发器
func (srv *Server) getConnDispatcher(conn *Conn) *dispatcher {
if conn == nil {
return srv.systemDispatcher
}
srv.dispatcherLock.RLock()
defer srv.dispatcherLock.RUnlock()
d, exist := srv.currDispatcher[conn.GetID()]
if exist {
return d
}
return srv.systemDispatcher
}
// ReleaseShunt 释放分流渠道中的连接,当分流渠道中不再存在连接时将会自动释放分流渠道
// - 在未使用 WithDisableAutomaticReleaseShunt 选项时,当连接关闭时将会自动释放分流渠道中连接的资源占用
// - 若执行过程中连接正在使用,将会切换至系统通道
func (srv *Server) ReleaseShunt(conn *Conn) {
srv.releaseDispatcher(conn)
}
// releaseDispatcher 关闭消息分发器
func (srv *Server) releaseDispatcher(conn *Conn) {
if conn == nil {
return
}
cid := conn.GetID()
srv.dispatcherLock.Lock()
defer srv.dispatcherLock.Unlock()
d, exist := srv.currDispatcher[cid]
if exist && d.name != serverSystemDispatcher {
delete(srv.dispatcherMember[d.name], cid)
if len(srv.dispatcherMember[d.name]) == 0 {
srv.OnShuntChannelClosedEvent(d.name)
d.close()
delete(srv.dispatchers, d.name)
}
delete(srv.currDispatcher, cid)
}
} }
// pushMessage 向服务器中写入特定类型的消息,需严格遵守消息属性要求 // pushMessage 向服务器中写入特定类型的消息,需严格遵守消息属性要求
@ -411,28 +344,40 @@ func (srv *Server) pushMessage(message *Message) {
srv.messagePool.Release(message) srv.messagePool.Release(message)
return return
} }
var dispatcher *dispatcher var d = message.dis
switch message.t { if d == nil {
case MessageTypePacket, switch message.t {
MessageTypeShuntTicker, MessageTypeShuntAsync, MessageTypeShuntAsyncCallback, case MessageTypePacket,
MessageTypeUniqueShuntAsync, MessageTypeUniqueShuntAsyncCallback, MessageTypeShuntTicker, MessageTypeShuntAsync, MessageTypeShuntAsyncCallback,
MessageTypeShunt: MessageTypeUniqueShuntAsync, MessageTypeUniqueShuntAsyncCallback,
dispatcher = srv.getConnDispatcher(message.conn) MessageTypeShunt:
case MessageTypeSystem, MessageTypeAsync, MessageTypeUniqueAsync, MessageTypeAsyncCallback, MessageTypeUniqueAsyncCallback, MessageTypeTicker: d = srv.dispatcherMgr.GetDispatcher(message.conn.GetID())
dispatcher = srv.systemDispatcher case MessageTypeSystem, MessageTypeAsync, MessageTypeUniqueAsync, MessageTypeAsyncCallback, MessageTypeUniqueAsyncCallback, MessageTypeTicker:
d = srv.dispatcherMgr.GetSystemDispatcher()
}
} }
if dispatcher == nil { if d == nil {
return return
} }
if (message.t == MessageTypeUniqueShuntAsync || message.t == MessageTypeUniqueAsync) && dispatcher.unique(message.name) { if (message.t == MessageTypeUniqueShuntAsync || message.t == MessageTypeUniqueAsync) && d.Unique(message.name) {
srv.messagePool.Release(message) srv.messagePool.Release(message)
return return
} }
switch message.t {
case MessageTypeShuntAsync, MessageTypeUniqueShuntAsync:
d.IncrCount(message.conn.GetID(), 1)
}
srv.hitMessageStatistics() srv.hitMessageStatistics()
dispatcher.put(message) d.Put(message)
} }
func (srv *Server) low(message *Message, present time.Time, expect time.Duration, messageReplace ...string) { func (srv *Server) low(message *Message, present time.Time, expect time.Duration, async bool, messageReplace ...string) {
switch {
case async && srv.asyncLowMessageDuration <= 0:
return
case !async && srv.lowMessageDuration <= 0:
return
}
cost := time.Since(present) cost := time.Since(present)
if cost > expect { if cost > expect {
if message == nil { if message == nil {
@ -452,13 +397,13 @@ func (srv *Server) low(message *Message, present time.Time, expect time.Duration
fields = append(fields, log.String("type", messageNames[message.t]), log.String("cost", cost.String()), log.String("message", message.String())) fields = append(fields, log.String("type", messageNames[message.t]), log.String("cost", cost.String()), log.String("message", message.String()))
fields = append(fields, message.marks...) fields = append(fields, message.marks...)
//fields = append(fields, log.Stack("stack")) //fields = append(fields, log.Stack("stack"))
log.Warn("ServerLowMessage", sher.SliceCastToAny(fields)...) log.Warn("ServerLowMessage", collection.ConvertSliceToAny(fields)...)
srv.OnMessageLowExecEvent(message, cost) srv.OnMessageLowExecEvent(message, cost)
} }
} }
// dispatchMessage 消息分发 // dispatchMessage 消息分发
func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) { func (srv *Server) dispatchMessage(dispatcherIns *dispatcher.Dispatcher[string, *Message], msg *Message) {
var ( var (
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
@ -478,7 +423,7 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
present := time.Now() present := time.Now()
if msg.t != MessageTypeAsync && msg.t != MessageTypeUniqueAsync && msg.t != MessageTypeShuntAsync && msg.t != MessageTypeUniqueShuntAsync { if msg.t != MessageTypeAsync && msg.t != MessageTypeUniqueAsync && msg.t != MessageTypeShuntAsync && msg.t != MessageTypeUniqueShuntAsync {
defer func(cancel context.CancelFunc, srv *Server, dispatcherIns *dispatcher, msg *Message, present time.Time) { defer func(cancel context.CancelFunc, srv *Server, dispatcherIns *dispatcher.Dispatcher[string, *Message], msg *Message, present time.Time) {
super.Handle(cancel) super.Handle(cancel)
if err := super.RecoverTransform(recover()); err != nil { if err := super.RecoverTransform(recover()); err != nil {
stack := string(debug.Stack()) stack := string(debug.Stack())
@ -486,11 +431,15 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
fmt.Println(stack) fmt.Println(stack)
srv.OnMessageErrorEvent(msg, err) srv.OnMessageErrorEvent(msg, err)
} }
if msg.t == MessageTypeUniqueAsyncCallback || msg.t == MessageTypeUniqueShuntAsyncCallback { switch msg.t {
dispatcherIns.antiUnique(msg.name) case MessageTypeAsyncCallback, MessageTypeShuntAsyncCallback:
dispatcherIns.IncrCount(msg.producer, -1)
case MessageTypeUniqueAsyncCallback, MessageTypeUniqueShuntAsyncCallback:
dispatcherIns.AntiUnique(msg.name)
dispatcherIns.IncrCount(msg.producer, -1)
} }
srv.low(msg, present, time.Millisecond*100) srv.low(msg, present, srv.lowMessageDuration, false)
srv.messageCounter.Add(-1) srv.messageCounter.Add(-1)
if atomic.CompareAndSwapUint32(&srv.closed, 0, 0) { if atomic.CompareAndSwapUint32(&srv.closed, 0, 0) {
@ -514,10 +463,14 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
msg.ordinaryHandler() msg.ordinaryHandler()
case MessageTypeAsync, MessageTypeShuntAsync, MessageTypeUniqueAsync, MessageTypeUniqueShuntAsync: case MessageTypeAsync, MessageTypeShuntAsync, MessageTypeUniqueAsync, MessageTypeUniqueShuntAsync:
if err := srv.ants.Submit(func() { if err := srv.ants.Submit(func() {
defer func(cancel context.CancelFunc, srv *Server, dispatcherIns *dispatcher, msg *Message, present time.Time) { defer func(cancel context.CancelFunc, srv *Server, dispatcherIns *dispatcher.Dispatcher[string, *Message], msg *Message, present time.Time) {
switch msg.t {
case MessageTypeShuntAsync, MessageTypeUniqueShuntAsync:
dispatcherIns.IncrCount(msg.conn.GetID(), -1)
}
if err := super.RecoverTransform(recover()); err != nil { if err := super.RecoverTransform(recover()); err != nil {
if msg.t == MessageTypeUniqueAsync || msg.t == MessageTypeUniqueShuntAsync { if msg.t == MessageTypeUniqueAsync || msg.t == MessageTypeUniqueShuntAsync {
dispatcherIns.antiUnique(msg.name) dispatcherIns.AntiUnique(msg.name)
} }
stack := string(debug.Stack()) stack := string(debug.Stack())
log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", stack)) log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", stack))
@ -525,7 +478,7 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
srv.OnMessageErrorEvent(msg, err) srv.OnMessageErrorEvent(msg, err)
} }
super.Handle(cancel) super.Handle(cancel)
srv.low(msg, present, time.Second) srv.low(msg, present, srv.asyncLowMessageDuration, true)
srv.messageCounter.Add(-1) srv.messageCounter.Add(-1)
if atomic.CompareAndSwapUint32(&srv.closed, 0, 0) { if atomic.CompareAndSwapUint32(&srv.closed, 0, 0) {
@ -534,25 +487,27 @@ func (srv *Server) dispatchMessage(dispatcherIns *dispatcher, msg *Message) {
}(cancel, srv, dispatcherIns, msg, present) }(cancel, srv, dispatcherIns, msg, present)
var err error var err error
if msg.exceptionHandler != nil { if msg.exceptionHandler != nil {
dispatcherIns.IncrCount(msg.producer, 1)
err = msg.exceptionHandler() err = msg.exceptionHandler()
} }
if msg.errHandler != nil { if msg.errHandler != nil {
if msg.conn == nil { if msg.conn == nil {
if msg.t == MessageTypeUniqueAsync { if msg.t == MessageTypeUniqueAsync {
srv.PushUniqueAsyncCallbackMessage(msg.name, err, msg.errHandler) srv.pushUniqueAsyncCallbackMessage(dispatcherIns, msg.name, err, msg.errHandler)
return return
} }
srv.PushAsyncCallbackMessage(err, msg.errHandler) srv.pushAsyncCallbackMessage(dispatcherIns, err, msg.errHandler)
return return
} }
if msg.t == MessageTypeUniqueShuntAsync { if msg.t == MessageTypeUniqueShuntAsync {
srv.PushUniqueShuntAsyncCallbackMessage(msg.conn, msg.name, err, msg.errHandler) srv.pushUniqueShuntAsyncCallbackMessage(dispatcherIns, msg.conn, msg.name, err, msg.errHandler)
return return
} }
srv.PushShuntAsyncCallbackMessage(msg.conn, err, msg.errHandler) srv.pushShuntAsyncCallbackMessage(dispatcherIns, msg.conn, err, msg.errHandler)
return return
} }
dispatcherIns.antiUnique(msg.name) dispatcherIns.AntiUnique(msg.name)
dispatcherIns.IncrCount(msg.producer, -1)
if err != nil { if err != nil {
log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", string(debug.Stack()))) log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", string(debug.Stack())))
} }
@ -584,11 +539,11 @@ func (srv *Server) PushAsyncMessage(caller func() error, callback func(err error
srv.pushMessage(srv.messagePool.Get().castToAsyncMessage(caller, callback, mark...)) srv.pushMessage(srv.messagePool.Get().castToAsyncMessage(caller, callback, mark...))
} }
// PushAsyncCallbackMessage 向服务器中推送 MessageTypeAsyncCallback 消息 // pushAsyncCallbackMessage 向服务器中推送 MessageTypeAsyncCallback 消息
// - 异步消息回调将会通过一个接收 error 的函数进行处理,该函数将在系统分发器中执行 // - 异步消息回调将会通过一个接收 error 的函数进行处理,该函数将在系统分发器中执行
// - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现 // - mark 为可选的日志标记,当发生异常时,将会在日志中进行体现
func (srv *Server) PushAsyncCallbackMessage(err error, callback func(err error), mark ...log.Field) { func (srv *Server) pushAsyncCallbackMessage(dis *dispatcher.Dispatcher[string, *Message], err error, callback func(err error), mark ...log.Field) {
srv.pushMessage(srv.messagePool.Get().castToAsyncCallbackMessage(err, callback, mark...)) srv.pushMessage(srv.messagePool.Get().castToAsyncCallbackMessage(err, callback, mark...).bindDispatcher(dis))
} }
// PushShuntAsyncMessage 向特定分发器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致 // PushShuntAsyncMessage 向特定分发器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致
@ -598,10 +553,10 @@ func (srv *Server) PushShuntAsyncMessage(conn *Conn, caller func() error, callba
srv.pushMessage(srv.messagePool.Get().castToShuntAsyncMessage(conn, caller, callback, mark...)) srv.pushMessage(srv.messagePool.Get().castToShuntAsyncMessage(conn, caller, callback, mark...))
} }
// PushShuntAsyncCallbackMessage 向特定分发器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致 // pushShuntAsyncCallbackMessage 向特定分发器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
// - 需要注意的是,当未指定 UseShunt 时,将会通过 PushAsyncCallbackMessage 进行转发 // - 需要注意的是,当未指定 UseShunt 时,将会通过 pushAsyncCallbackMessage 进行转发
func (srv *Server) PushShuntAsyncCallbackMessage(conn *Conn, err error, callback func(err error), mark ...log.Field) { func (srv *Server) pushShuntAsyncCallbackMessage(dis *dispatcher.Dispatcher[string, *Message], conn *Conn, err error, callback func(err error), mark ...log.Field) {
srv.pushMessage(srv.messagePool.Get().castToShuntAsyncCallbackMessage(conn, err, callback, mark...)) srv.pushMessage(srv.messagePool.Get().castToShuntAsyncCallbackMessage(conn, err, callback, mark...).bindDispatcher(dis))
} }
// PushPacketMessage 向服务器中推送 MessageTypePacket 消息 // PushPacketMessage 向服务器中推送 MessageTypePacket 消息
@ -637,9 +592,9 @@ func (srv *Server) PushUniqueAsyncMessage(unique string, caller func() error, ca
srv.pushMessage(srv.messagePool.Get().castToUniqueAsyncMessage(unique, caller, callback, mark...)) srv.pushMessage(srv.messagePool.Get().castToUniqueAsyncMessage(unique, caller, callback, mark...))
} }
// PushUniqueAsyncCallbackMessage 向服务器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致 // pushUniqueAsyncCallbackMessage 向服务器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
func (srv *Server) PushUniqueAsyncCallbackMessage(unique string, err error, callback func(err error), mark ...log.Field) { func (srv *Server) pushUniqueAsyncCallbackMessage(dis *dispatcher.Dispatcher[string, *Message], unique string, err error, callback func(err error), mark ...log.Field) {
srv.pushMessage(srv.messagePool.Get().castToUniqueAsyncCallbackMessage(unique, err, callback, mark...)) srv.pushMessage(srv.messagePool.Get().castToUniqueAsyncCallbackMessage(unique, err, callback, mark...).bindDispatcher(dis))
} }
// PushUniqueShuntAsyncMessage 向特定分发器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致 // PushUniqueShuntAsyncMessage 向特定分发器中推送 MessageTypeAsync 消息,消息执行与 MessageTypeAsync 一致
@ -649,10 +604,10 @@ func (srv *Server) PushUniqueShuntAsyncMessage(conn *Conn, unique string, caller
srv.pushMessage(srv.messagePool.Get().castToUniqueShuntAsyncMessage(conn, unique, caller, callback, mark...)) srv.pushMessage(srv.messagePool.Get().castToUniqueShuntAsyncMessage(conn, unique, caller, callback, mark...))
} }
// PushUniqueShuntAsyncCallbackMessage 向特定分发器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致 // pushUniqueShuntAsyncCallbackMessage 向特定分发器中推送 MessageTypeAsyncCallback 消息,消息执行与 MessageTypeAsyncCallback 一致
// - 需要注意的是,当未指定 UseShunt 时,将会通过系统分流渠道进行转发 // - 需要注意的是,当未指定 UseShunt 时,将会通过系统分流渠道进行转发
func (srv *Server) PushUniqueShuntAsyncCallbackMessage(conn *Conn, unique string, err error, callback func(err error), mark ...log.Field) { func (srv *Server) pushUniqueShuntAsyncCallbackMessage(dis *dispatcher.Dispatcher[string, *Message], conn *Conn, unique string, err error, callback func(err error), mark ...log.Field) {
srv.pushMessage(srv.messagePool.Get().castToUniqueShuntAsyncCallbackMessage(conn, unique, err, callback, mark...)) srv.pushMessage(srv.messagePool.Get().castToUniqueShuntAsyncCallbackMessage(conn, unique, err, callback, mark...).bindDispatcher(dis))
} }
// PushShuntMessage 向特定分发器中推送 MessageTypeShunt 消息,消息执行与 MessageTypeSystem 一致,不同的是将会在特定分发器中执行 // PushShuntMessage 向特定分发器中推送 MessageTypeShunt 消息,消息执行与 MessageTypeSystem 一致,不同的是将会在特定分发器中执行
@ -762,7 +717,7 @@ func onServicesInit(srv *Server) {
// onMessageSystemInit 消息系统初始化 // onMessageSystemInit 消息系统初始化
func onMessageSystemInit(srv *Server) { func onMessageSystemInit(srv *Server) {
srv.messagePool = concurrent.NewPool[Message]( srv.messagePool = hub.NewObjectPool[Message](
func() *Message { func() *Message {
return &Message{} return &Message{}
}, },
@ -771,7 +726,8 @@ func onMessageSystemInit(srv *Server) {
}, },
) )
srv.startMessageStatistics() srv.startMessageStatistics()
srv.systemDispatcher = generateDispatcher(srv.dispatcherBufferSize, serverSystemDispatcher, srv.dispatchMessage) srv.dispatcherMgr = dispatcher.NewManager[string, *Message](srv.dispatcherBufferSize, srv.dispatchMessage).
go srv.systemDispatcher.start() SetDispatcherCreatedHandler(srv.OnShuntChannelCreatedEvent).
SetDispatcherClosedHandler(srv.OnShuntChannelClosedEvent)
srv.OnMessageReadyEvent() srv.OnMessageReadyEvent()
} }

View File

@ -18,7 +18,7 @@ func TestNew(t *testing.T) {
fmt.Println("启动完成") fmt.Println("启动完成")
}) })
srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) {
fmt.Println("关闭", conn.GetID(), err, "Count", srv.GetOnlineCount()) fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount())
}) })
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {
@ -38,7 +38,7 @@ func TestNew2(t *testing.T) {
fmt.Println("启动完成") fmt.Println("启动完成")
}) })
srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) {
fmt.Println("关闭", conn.GetID(), err, "Count", srv.GetOnlineCount()) fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount())
}) })
srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) {

View File

@ -14,7 +14,8 @@ type Service interface {
// BindService 绑定服务到特定 Server被绑定的服务将会在 Server 初始化时执行 Service.OnInit 方法 // BindService 绑定服务到特定 Server被绑定的服务将会在 Server 初始化时执行 Service.OnInit 方法
func BindService(srv *Server, services ...Service) { func BindService(srv *Server, services ...Service) {
for _, service := range services { for i := 0; i < len(services); i++ {
service := services[i]
srv.services = append(srv.services, func() { srv.services = append(srv.services, func() {
name := reflect.TypeOf(service).String() name := reflect.TypeOf(service).String()
defer func(name string) { defer func(name string) {

View File

@ -29,9 +29,9 @@
package main package main
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/server/writeloop" "github.com/kercylan98/minotaur/server/writeloop"
"github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hub"
) )
func main() { func main() {

View File

@ -1,7 +1,7 @@
package writeloop package writeloop
import ( import (
"github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hub"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
) )
@ -12,7 +12,7 @@ import (
// - errorHandler 错误处理函数 // - errorHandler 错误处理函数
// //
// 传入 writeHandler 的消息对象是从 Channel 中获取的,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象 // 传入 writeHandler 的消息对象是从 Channel 中获取的,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象
func NewChannel[Message any](pool *concurrent.Pool[Message], channelSize int, writeHandler func(message Message) error, errorHandler func(err any)) *Channel[Message] { func NewChannel[Message any](pool *hub.ObjectPool[Message], channelSize int, writeHandler func(message Message) error, errorHandler func(err any)) *Channel[Message] {
wl := &Channel[Message]{ wl := &Channel[Message]{
c: make(chan Message, channelSize), c: make(chan Message, channelSize),
} }
@ -45,7 +45,7 @@ type Channel[T any] struct {
c chan T c chan T
} }
// Put 将数据放入写循环message 应该来源于 concurrent.Pool // Put 将数据放入写循环message 应该来源于 hub.ObjectPool
func (slf *Channel[T]) Put(message T) { func (slf *Channel[T]) Put(message T) {
slf.c <- message slf.c <- message
} }

View File

@ -2,7 +2,7 @@ package writeloop
import ( import (
"github.com/kercylan98/minotaur/utils/buffer" "github.com/kercylan98/minotaur/utils/buffer"
"github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hub"
"github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/log"
) )
@ -12,7 +12,7 @@ import (
// - errorHandler 错误处理函数 // - errorHandler 错误处理函数
// //
// 传入 writeHandler 的消息对象是从 pool 中获取的,并且在 writeHandler 执行完成后会被放回 pool 中,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象 // 传入 writeHandler 的消息对象是从 pool 中获取的,并且在 writeHandler 执行完成后会被放回 pool 中,因此 writeHandler 不应该持有消息对象的引用,同时也不应该主动释放消息对象
func NewUnbounded[Message any](pool *concurrent.Pool[Message], writeHandler func(message Message) error, errorHandler func(err any)) *Unbounded[Message] { func NewUnbounded[Message any](pool *hub.ObjectPool[Message], writeHandler func(message Message) error, errorHandler func(err any)) *Unbounded[Message] {
wl := &Unbounded[Message]{ wl := &Unbounded[Message]{
buf: buffer.NewUnbounded[Message](), buf: buffer.NewUnbounded[Message](),
} }
@ -47,7 +47,7 @@ type Unbounded[Message any] struct {
buf *buffer.Unbounded[Message] buf *buffer.Unbounded[Message]
} }
// Put 将数据放入写循环message 应该来源于 concurrent.Pool // Put 将数据放入写循环message 应该来源于 hub.ObjectPool
func (slf *Unbounded[Message]) Put(message Message) { func (slf *Unbounded[Message]) Put(message Message) {
slf.buf.Put(message) slf.buf.Put(message)
} }

View File

@ -3,12 +3,12 @@ package writeloop_test
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/server/writeloop" "github.com/kercylan98/minotaur/server/writeloop"
"github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hub"
"sync" "sync"
) )
func ExampleNewUnbounded() { func ExampleNewUnbounded() {
pool := concurrent.NewPool[Message](func() *Message { pool := hub.NewObjectPool[Message](func() *Message {
return &Message{} return &Message{}
}, func(data *Message) { }, func(data *Message) {
data.ID = 0 data.ID = 0

View File

@ -2,7 +2,7 @@ package writeloop_test
import ( import (
"github.com/kercylan98/minotaur/server/writeloop" "github.com/kercylan98/minotaur/server/writeloop"
"github.com/kercylan98/minotaur/utils/concurrent" "github.com/kercylan98/minotaur/utils/hub"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"testing" "testing"
) )
@ -11,7 +11,7 @@ type Message struct {
ID int ID int
} }
var wp = concurrent.NewPool(func() *Message { var wp = hub.NewObjectPool(func() *Message {
return &Message{} return &Message{}
}, func(data *Message) { }, func(data *Message) {
data.ID = 0 data.ID = 0

View File

@ -1,9 +1,9 @@
package aoi package aoi
import ( import (
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/geometry" "github.com/kercylan98/minotaur/utils/geometry"
"github.com/kercylan98/minotaur/utils/hash"
"math" "math"
"sync" "sync"
) )
@ -54,7 +54,7 @@ func (slf *TwoDimensional[EID, PosType, E]) Refresh(entity E) {
func (slf *TwoDimensional[EID, PosType, E]) GetFocus(id EID) map[EID]E { func (slf *TwoDimensional[EID, PosType, E]) GetFocus(id EID) map[EID]E {
slf.rw.RLock() slf.rw.RLock()
defer slf.rw.RUnlock() defer slf.rw.RUnlock()
return hash.Copy(slf.focus[id]) return collection.CloneMap(slf.focus[id])
} }
func (slf *TwoDimensional[EID, PosType, E]) SetSize(width, height int) { func (slf *TwoDimensional[EID, PosType, E]) SetSize(width, height int) {

View File

@ -1,6 +1,8 @@
package arrangement package arrangement
import "github.com/kercylan98/minotaur/utils/hash" import (
"github.com/kercylan98/minotaur/utils/collection"
)
// Area 编排区域 // Area 编排区域
type Area[ID comparable, AreaInfo any] struct { type Area[ID comparable, AreaInfo any] struct {
@ -43,7 +45,7 @@ func (slf *Area[ID, AreaInfo]) IsAllow(item Item[ID]) (constraintErr error, conf
// IsConflict 检测一个成员是否会造成冲突 // IsConflict 检测一个成员是否会造成冲突
func (slf *Area[ID, AreaInfo]) IsConflict(item Item[ID]) bool { func (slf *Area[ID, AreaInfo]) IsConflict(item Item[ID]) bool {
if hash.Exist(slf.items, item.GetID()) { if collection.KeyInMap(slf.items, item.GetID()) {
return false return false
} }
for _, conflict := range slf.conflicts { for _, conflict := range slf.conflicts {
@ -56,7 +58,7 @@ func (slf *Area[ID, AreaInfo]) IsConflict(item Item[ID]) bool {
// GetConflictItems 获取与一个成员产生冲突的所有其他成员 // GetConflictItems 获取与一个成员产生冲突的所有其他成员
func (slf *Area[ID, AreaInfo]) GetConflictItems(item Item[ID]) map[ID]Item[ID] { func (slf *Area[ID, AreaInfo]) GetConflictItems(item Item[ID]) map[ID]Item[ID] {
if hash.Exist(slf.items, item.GetID()) { if collection.KeyInMap(slf.items, item.GetID()) {
return nil return nil
} }
var conflictItems map[ID]Item[ID] var conflictItems map[ID]Item[ID]
@ -79,7 +81,7 @@ func (slf *Area[ID, AreaInfo]) GetScore(extra ...Item[ID]) float64 {
if slf.evaluate == nil { if slf.evaluate == nil {
return 0 return 0
} }
var items = hash.Copy(slf.items) var items = collection.CloneMap(slf.items)
for _, item := range extra { for _, item := range extra {
items[item.GetID()] = item items[item.GetID()] = item
} }

View File

@ -1,7 +1,7 @@
package arrangement package arrangement
import ( import (
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/collection"
"sort" "sort"
) )
@ -59,8 +59,8 @@ func (slf *Arrangement[ID, AreaInfo]) Arrange() (areas []*Area[ID, AreaInfo], no
return slf.areas, slf.items return slf.areas, slf.items
} }
var items = hash.Copy(slf.items) var items = collection.CloneMap(slf.items)
var fixed = hash.Copy(slf.fixed) var fixed = collection.CloneMap(slf.fixed)
// 将固定编排的成员添加到对应的编排区域中,当成员无法添加到对应的编排区域中时,将会被转移至未编排区域 // 将固定编排的成员添加到对应的编排区域中,当成员无法添加到对应的编排区域中时,将会被转移至未编排区域
for id, isFixed := range fixed { for id, isFixed := range fixed {
@ -94,7 +94,7 @@ func (slf *Arrangement[ID, AreaInfo]) Arrange() (areas []*Area[ID, AreaInfo], no
} }
var editor = &Editor[ID, AreaInfo]{ var editor = &Editor[ID, AreaInfo]{
a: slf, a: slf,
pending: hash.ToSlice(items), pending: collection.ConvertMapValuesToSlice(items),
falls: map[ID]struct{}{}, falls: map[ID]struct{}{},
} }
sort.Slice(editor.pending, func(i, j int) bool { sort.Slice(editor.pending, func(i, j int) bool {

View File

@ -1,8 +1,7 @@
package arrangement package arrangement
import ( import (
"github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/slice"
"sort" "sort"
) )
@ -33,7 +32,7 @@ func (slf *Editor[ID, AreaInfo]) RemoveAreaItem(area *Area[ID, AreaInfo], item I
// AddAreaItem 将一个成员添加到编排区域中,如果该成员已经存在于编排区域中,则不进行任何操作 // AddAreaItem 将一个成员添加到编排区域中,如果该成员已经存在于编排区域中,则不进行任何操作
func (slf *Editor[ID, AreaInfo]) AddAreaItem(area *Area[ID, AreaInfo], item Item[ID]) { func (slf *Editor[ID, AreaInfo]) AddAreaItem(area *Area[ID, AreaInfo], item Item[ID]) {
if hash.Exist(slf.falls, item.GetID()) { if collection.KeyInMap(slf.falls, item.GetID()) {
return return
} }
area.items[item.GetID()] = item area.items[item.GetID()] = item
@ -42,12 +41,12 @@ func (slf *Editor[ID, AreaInfo]) AddAreaItem(area *Area[ID, AreaInfo], item Item
// GetAreas 获取所有的编排区域 // GetAreas 获取所有的编排区域
func (slf *Editor[ID, AreaInfo]) GetAreas() []*Area[ID, AreaInfo] { func (slf *Editor[ID, AreaInfo]) GetAreas() []*Area[ID, AreaInfo] {
return slice.Copy(slf.a.areas) return collection.CloneSlice(slf.a.areas)
} }
// GetAreasWithScoreAsc 获取所有的编排区域,并按照分数升序排序 // GetAreasWithScoreAsc 获取所有的编排区域,并按照分数升序排序
func (slf *Editor[ID, AreaInfo]) GetAreasWithScoreAsc(extra ...Item[ID]) []*Area[ID, AreaInfo] { func (slf *Editor[ID, AreaInfo]) GetAreasWithScoreAsc(extra ...Item[ID]) []*Area[ID, AreaInfo] {
areas := slice.Copy(slf.a.areas) areas := collection.CloneSlice(slf.a.areas)
sort.Slice(areas, func(i, j int) bool { sort.Slice(areas, func(i, j int) bool {
return areas[i].GetScore(extra...) < areas[j].GetScore(extra...) return areas[i].GetScore(extra...) < areas[j].GetScore(extra...)
}) })
@ -56,7 +55,7 @@ func (slf *Editor[ID, AreaInfo]) GetAreasWithScoreAsc(extra ...Item[ID]) []*Area
// GetAreasWithScoreDesc 获取所有的编排区域,并按照分数降序排序 // GetAreasWithScoreDesc 获取所有的编排区域,并按照分数降序排序
func (slf *Editor[ID, AreaInfo]) GetAreasWithScoreDesc(extra ...Item[ID]) []*Area[ID, AreaInfo] { func (slf *Editor[ID, AreaInfo]) GetAreasWithScoreDesc(extra ...Item[ID]) []*Area[ID, AreaInfo] {
areas := slice.Copy(slf.a.areas) areas := collection.CloneSlice(slf.a.areas)
sort.Slice(areas, func(i, j int) bool { sort.Slice(areas, func(i, j int) bool {
return areas[i].GetScore(extra...) > areas[j].GetScore(extra...) return areas[i].GetScore(extra...) > areas[j].GetScore(extra...)
}) })

View File

@ -1,15 +1,21 @@
package buffer package buffer
// NewRing 创建一个环形缓冲区 // NewRing 创建一个并发不安全的环形缓冲区
func NewRing[T any](initSize int) *Ring[T] { // - initSize: 初始容量
if initSize <= 1 { //
panic("initial size must be great than one") // 当初始容量小于 2 或未设置时,将会使用默认容量 2
func NewRing[T any](initSize ...int) *Ring[T] {
if len(initSize) == 0 {
initSize = append(initSize, 2)
}
if initSize[0] < 2 {
initSize[0] = 2
} }
return &Ring[T]{ return &Ring[T]{
buf: make([]T, initSize), buf: make([]T, initSize[0]),
initSize: initSize, initSize: initSize[0],
size: initSize, size: initSize[0],
} }
} }
@ -23,91 +29,120 @@ type Ring[T any] struct {
} }
// Read 读取数据 // Read 读取数据
func (slf *Ring[T]) Read() (T, error) { func (b *Ring[T]) Read() (T, error) {
var t T var t T
if slf.r == slf.w { if b.r == b.w {
return t, ErrBufferIsEmpty return t, ErrBufferIsEmpty
} }
v := slf.buf[slf.r] v := b.buf[b.r]
slf.r++ b.r++
if slf.r == slf.size { if b.r == b.size {
slf.r = 0 b.r = 0
} }
return v, nil return v, nil
} }
// ReadAll 读取所有数据
func (b *Ring[T]) ReadAll() []T {
if b.r == b.w {
return nil // 没有数据时返回空切片
}
var length int
var data []T
if b.w > b.r {
length = b.w - b.r
} else {
length = len(b.buf) - b.r + b.w
}
data = make([]T, length) // 预分配空间
if b.w > b.r {
copy(data, b.buf[b.r:b.w])
} else {
copied := copy(data, b.buf[b.r:])
copy(data[copied:], b.buf[:b.w])
}
b.r = 0
b.w = 0
return data
}
// Peek 查看数据 // Peek 查看数据
func (slf *Ring[T]) Peek() (t T, err error) { func (b *Ring[T]) Peek() (t T, err error) {
if slf.r == slf.w { if b.r == b.w {
return t, ErrBufferIsEmpty return t, ErrBufferIsEmpty
} }
return slf.buf[slf.r], nil return b.buf[b.r], nil
} }
// Write 写入数据 // Write 写入数据
func (slf *Ring[T]) Write(v T) { func (b *Ring[T]) Write(v T) {
slf.buf[slf.w] = v b.buf[b.w] = v
slf.w++ b.w++
if slf.w == slf.size { if b.w == b.size {
slf.w = 0 b.w = 0
} }
if slf.w == slf.r { if b.w == b.r {
slf.grow() b.grow()
} }
} }
// grow 扩容 // grow 扩容
func (slf *Ring[T]) grow() { func (b *Ring[T]) grow() {
var size int var size int
if slf.size < 1024 { if b.size < 1024 {
size = slf.size * 2 size = b.size * 2
} else { } else {
size = slf.size + slf.size/4 size = b.size + b.size/4
} }
buf := make([]T, size) buf := make([]T, size)
copy(buf[0:], slf.buf[slf.r:]) copy(buf[0:], b.buf[b.r:])
copy(buf[slf.size-slf.r:], slf.buf[0:slf.r]) copy(buf[b.size-b.r:], b.buf[0:b.r])
slf.r = 0 b.r = 0
slf.w = slf.size b.w = b.size
slf.size = size b.size = size
slf.buf = buf b.buf = buf
} }
// IsEmpty 是否为空 // IsEmpty 是否为空
func (slf *Ring[T]) IsEmpty() bool { func (b *Ring[T]) IsEmpty() bool {
return slf.r == slf.w return b.r == b.w
} }
// Cap 返回缓冲区容量 // Cap 返回缓冲区容量
func (slf *Ring[T]) Cap() int { func (b *Ring[T]) Cap() int {
return slf.size return b.size
} }
// Len 返回缓冲区长度 // Len 返回缓冲区长度
func (slf *Ring[T]) Len() int { func (b *Ring[T]) Len() int {
if slf.r == slf.w { if b.r == b.w {
return 0 return 0
} }
if slf.w > slf.r { if b.w > b.r {
return slf.w - slf.r return b.w - b.r
} }
return slf.size - slf.r + slf.w return b.size - b.r + b.w
} }
// Reset 重置缓冲区 // Reset 重置缓冲区
func (slf *Ring[T]) Reset() { func (b *Ring[T]) Reset() {
slf.r = 0 b.r = 0
slf.w = 0 b.w = 0
slf.size = slf.initSize b.size = b.initSize
slf.buf = make([]T, slf.initSize) b.buf = make([]T, b.initSize)
} }

View File

@ -0,0 +1,25 @@
package buffer_test
import (
"github.com/kercylan98/minotaur/utils/buffer"
"testing"
)
func BenchmarkRingWrite(b *testing.B) {
ring := buffer.NewRing[int](1024)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ring.Write(i)
}
}
func BenchmarkRingRead(b *testing.B) {
ring := buffer.NewRing[int](1024)
for i := 0; i < b.N; i++ {
ring.Write(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = ring.Read()
}
}

14
utils/buffer/ring_test.go Normal file
View File

@ -0,0 +1,14 @@
package buffer_test
import (
"github.com/kercylan98/minotaur/utils/buffer"
"testing"
)
func TestNewRing(t *testing.T) {
ring := buffer.NewRing[int]()
for i := 0; i < 100; i++ {
ring.Write(i)
t.Log(ring.Read())
}
}

View File

@ -0,0 +1,100 @@
package buffer
import (
"sync"
)
// NewRingUnbounded 创建一个并发安全的基于环形缓冲区实现的无界缓冲区
func NewRingUnbounded[T any](bufferSize int) *RingUnbounded[T] {
ru := &RingUnbounded[T]{
ring: NewRing[T](1024),
rc: make(chan T, bufferSize),
closedSignal: make(chan struct{}),
}
ru.cond = sync.NewCond(&ru.rrm)
ru.process()
return ru
}
// RingUnbounded 基于环形缓冲区实现的无界缓冲区
type RingUnbounded[T any] struct {
ring *Ring[T]
rrm sync.Mutex
cond *sync.Cond
rc chan T
closed bool
closedMutex sync.RWMutex
closedSignal chan struct{}
}
// Write 写入数据
func (b *RingUnbounded[T]) Write(v T) {
b.closedMutex.RLock()
defer b.closedMutex.RUnlock()
if b.closed {
return
}
b.rrm.Lock()
b.ring.Write(v)
b.cond.Signal()
b.rrm.Unlock()
}
// Read 读取数据
func (b *RingUnbounded[T]) Read() <-chan T {
return b.rc
}
// Closed 判断缓冲区是否已关闭
func (b *RingUnbounded[T]) Closed() bool {
b.closedMutex.RLock()
defer b.closedMutex.RUnlock()
return b.closed
}
// Close 关闭缓冲区,关闭后将不再接收新数据,但是已有数据仍然可以读取
func (b *RingUnbounded[T]) Close() <-chan struct{} {
b.closedMutex.Lock()
defer b.closedMutex.Unlock()
if b.closed {
return b.closedSignal
}
b.closed = true
b.rrm.Lock()
b.cond.Signal()
b.rrm.Unlock()
return b.closedSignal
}
func (b *RingUnbounded[T]) process() {
go func(b *RingUnbounded[T]) {
for {
b.closedMutex.RLock()
b.rrm.Lock()
vs := b.ring.ReadAll()
if len(vs) == 0 && !b.closed {
b.closedMutex.RUnlock()
b.cond.Wait()
} else {
b.closedMutex.RUnlock()
}
b.rrm.Unlock()
b.closedMutex.RLock()
if b.closed && len(vs) == 0 {
close(b.rc)
close(b.closedSignal)
b.closedMutex.RUnlock()
break
}
for _, v := range vs {
b.rc <- v
}
b.closedMutex.RUnlock()
}
}(b)
}

View File

@ -0,0 +1,48 @@
package buffer_test
import (
"github.com/kercylan98/minotaur/utils/buffer"
"testing"
)
func BenchmarkRingUnbounded_Write(b *testing.B) {
ring := buffer.NewRingUnbounded[int](1024 * 16)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ring.Write(i)
}
}
func BenchmarkRingUnbounded_Read(b *testing.B) {
ring := buffer.NewRingUnbounded[int](1024 * 16)
for i := 0; i < b.N; i++ {
ring.Write(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
<-ring.Read()
}
}
func BenchmarkRingUnbounded_Write_Parallel(b *testing.B) {
ring := buffer.NewRingUnbounded[int](1024 * 16)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ring.Write(1)
}
})
}
func BenchmarkRingUnbounded_Read_Parallel(b *testing.B) {
ring := buffer.NewRingUnbounded[int](1024 * 16)
for i := 0; i < b.N; i++ {
ring.Write(i)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
<-ring.Read()
}
})
}

View File

@ -0,0 +1,33 @@
package buffer_test
import (
"github.com/kercylan98/minotaur/utils/buffer"
"testing"
)
func TestRingUnbounded_Write2Read(t *testing.T) {
ring := buffer.NewRingUnbounded[int](1024 * 16)
for i := 0; i < 100; i++ {
ring.Write(i)
}
t.Log("write done")
for i := 0; i < 100; i++ {
t.Log(<-ring.Read())
}
t.Log("read done")
}
func TestRingUnbounded_Close(t *testing.T) {
ring := buffer.NewRingUnbounded[int](1024 * 16)
for i := 0; i < 100; i++ {
ring.Write(i)
}
t.Log("write done")
ring.Close()
t.Log("close done")
for v := range ring.Read() {
ring.Write(v)
t.Log(v)
}
t.Log("read done")
}

View File

@ -5,15 +5,46 @@ import (
"testing" "testing"
) )
func BenchmarkUnboundedBuffer(b *testing.B) { func BenchmarkUnbounded_Write(b *testing.B) {
ub := buffer.NewUnbounded[int]() u := buffer.NewUnbounded[int]()
b.ResetTimer()
for i := 0; i < b.N; i++ {
u.Put(i)
}
}
func BenchmarkUnbounded_Read(b *testing.B) {
u := buffer.NewUnbounded[int]()
for i := 0; i < b.N; i++ {
u.Put(i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
<-u.Get()
u.Load()
}
}
func BenchmarkUnbounded_Write_Parallel(b *testing.B) {
u := buffer.NewUnbounded[int]()
b.ResetTimer() b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
ub.Put(1) u.Put(1)
<-ub.Get() }
ub.Load() })
}
func BenchmarkUnbounded_Read_Parallel(b *testing.B) {
u := buffer.NewUnbounded[int]()
for i := 0; i < b.N; i++ {
u.Put(i)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
<-u.Get()
u.Load()
} }
}) })
} }

View File

@ -1,22 +1,9 @@
package sher package collection
import ( import (
"slices" "slices"
) )
// CloneMatrix 克隆二维数组
func CloneMatrix[S ~[][]V, V any](slice S) S {
if slice == nil {
return nil
}
var result = make(S, len(slice))
for i := 0; i < len(slice); i++ {
result[i] = CloneSlice(slice[i])
}
return result
}
// CloneSlice 克隆切片,该函数是 slices.Clone 的快捷方式 // CloneSlice 克隆切片,该函数是 slices.Clone 的快捷方式
func CloneSlice[S ~[]V, V any](slice S) S { func CloneSlice[S ~[]V, V any](slice S) S {
return slices.Clone(slice) return slices.Clone(slice)
@ -35,11 +22,14 @@ func CloneMap[M ~map[K]V, K comparable, V any](m M) M {
return result return result
} }
// CloneSliceN 克隆切片为 n 个切片进行返回 // CloneSliceN 克隆 slice 为 n 个切片进行返回
func CloneSliceN[S ~[]V, V any](slice S, n int) []S { func CloneSliceN[S ~[]V, V any](slice S, n int) []S {
if slice == nil { if slice == nil {
return nil return nil
} }
if n <= 0 {
return []S{}
}
var result = make([]S, n) var result = make([]S, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -54,6 +44,10 @@ func CloneMapN[M ~map[K]V, K comparable, V any](m M, n int) []M {
return nil return nil
} }
if n <= 0 {
return []M{}
}
var result = make([]M, n) var result = make([]M, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
result[i] = CloneMap(m) result[i] = CloneMap(m)

View File

@ -0,0 +1,56 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
)
func ExampleCloneSlice() {
var slice = []int{1, 2, 3}
var result = collection.CloneSlice(slice)
fmt.Println(result)
// Output:
// [1 2 3]
}
func ExampleCloneMap() {
var m = map[int]int{1: 1, 2: 2, 3: 3}
var result = collection.CloneMap(m)
fmt.Println(len(result))
// Output:
// 3
}
func ExampleCloneSliceN() {
var slice = []int{1, 2, 3}
var result = collection.CloneSliceN(slice, 2)
fmt.Println(len(result))
// Output:
// 2
}
func ExampleCloneMapN() {
var m = map[int]int{1: 1, 2: 2, 3: 3}
var result = collection.CloneMapN(m, 2)
fmt.Println(len(result))
// Output:
// 2
}
func ExampleCloneSlices() {
var slice1 = []int{1, 2, 3}
var slice2 = []int{1, 2, 3}
var result = collection.CloneSlices(slice1, slice2)
fmt.Println(len(result))
// Output:
// 2
}
func ExampleCloneMaps() {
var m1 = map[int]int{1: 1, 2: 2, 3: 3}
var m2 = map[int]int{1: 1, 2: 2, 3: 3}
var result = collection.CloneMaps(m1, m2)
fmt.Println(len(result))
// Output:
// 2
}

View File

@ -0,0 +1,190 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestCloneSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestCloneSlice_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
{"TestCloneSlice_EmptySlice", []int{}, []int{}},
{"TestCloneSlice_NilSlice", nil, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.CloneSlice(c.input)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
for i := 0; i < len(actual); i++ {
if actual[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of input is not equal")
}
}
})
}
}
func TestCloneMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]int
}{
{"TestCloneMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 1, 2: 2, 3: 3}},
{"TestCloneMap_EmptyMap", map[int]int{}, map[int]int{}},
{"TestCloneMap_NilMap", nil, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.CloneMap(c.input)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of map is not equal")
}
for k, v := range actual {
if v != c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of map is not equal")
}
}
})
}
}
func TestCloneSliceN(t *testing.T) {
var cases = []struct {
name string
input []int
inputN int
expected [][]int
}{
{"TestCloneSliceN_NonEmptySlice", []int{1, 2, 3}, 2, [][]int{{1, 2, 3}, {1, 2, 3}}},
{"TestCloneSliceN_EmptySlice", []int{}, 2, [][]int{{}, {}}},
{"TestCloneSliceN_NilSlice", nil, 2, nil},
{"TestCloneSliceN_NonEmptySlice_ZeroN", []int{1, 2, 3}, 0, [][]int{}},
{"TestCloneSliceN_EmptySlice_ZeroN", []int{}, 0, [][]int{}},
{"TestCloneSliceN_NilSlice_ZeroN", nil, 0, nil},
{"TestCloneSliceN_NonEmptySlice_NegativeN", []int{1, 2, 3}, -1, [][]int{}},
{"TestCloneSliceN_EmptySlice_NegativeN", []int{}, -1, [][]int{}},
{"TestCloneSliceN_NilSlice_NegativeN", nil, -1, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.CloneSliceN(c.input, c.inputN)
if actual == nil {
if c.expected != nil {
t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, c.inputN, actual, "after clone, the expected is nil")
}
return
}
for a, i := range actual {
for b, v := range i {
if v != c.expected[a][b] {
t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, c.inputN, actual, "after clone, the inputV of input is not equal")
}
}
}
})
}
}
func TestCloneMapN(t *testing.T) {
var cases = []struct {
name string
input map[int]int
inputN int
expected []map[int]int
}{
{"TestCloneMapN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 2, []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}},
{"TestCloneMapN_EmptyMap", map[int]int{}, 2, []map[int]int{{}, {}}},
{"TestCloneMapN_NilMap", nil, 2, nil},
{"TestCloneMapN_NonEmptyMap_ZeroN", map[int]int{1: 1, 2: 2, 3: 3}, 0, []map[int]int{}},
{"TestCloneMapN_EmptyMap_ZeroN", map[int]int{}, 0, []map[int]int{}},
{"TestCloneMapN_NilMap_ZeroN", nil, 0, nil},
{"TestCloneMapN_NonEmptyMap_NegativeN", map[int]int{1: 1, 2: 2, 3: 3}, -1, []map[int]int{}},
{"TestCloneMapN_EmptyMap_NegativeN", map[int]int{}, -1, []map[int]int{}},
{"TestCloneMapN_NilMap_NegativeN", nil, -1, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.CloneMapN(c.input, c.inputN)
if actual == nil {
if c.expected != nil {
t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, actual, c.inputN, "after clone, the expected is nil")
}
return
}
for a, i := range actual {
for b, v := range i {
if v != c.expected[a][b] {
t.Fatalf("%s failed, expected: %v, actual: %v, inputN: %d, error: %s", c.name, c.expected, actual, c.inputN, "after clone, the inputV of map is not equal")
}
}
}
})
}
}
func TestCloneSlices(t *testing.T) {
var cases = []struct {
name string
input [][]int
expected [][]int
}{
{"TestCloneSlices_NonEmptySlices", [][]int{{1, 2, 3}, {1, 2, 3}}, [][]int{{1, 2, 3}, {1, 2, 3}}},
{"TestCloneSlices_EmptySlices", [][]int{{}, {}}, [][]int{{}, {}}},
{"TestCloneSlices_NilSlices", nil, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.CloneSlices(c.input...)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
for a, i := range actual {
for b, v := range i {
if v != c.expected[a][b] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of input is not equal")
}
}
}
})
}
}
func TestCloneMaps(t *testing.T) {
var cases = []struct {
name string
input []map[int]int
expected []map[int]int
}{
{"TestCloneMaps_NonEmptyMaps", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}, []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}}},
{"TestCloneMaps_EmptyMaps", []map[int]int{{}, {}}, []map[int]int{{}, {}}},
{"TestCloneMaps_NilMaps", nil, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.CloneMaps(c.input...)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of maps is not equal")
}
for a, i := range actual {
for b, v := range i {
if v != c.expected[a][b] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the inputV of maps is not equal")
}
}
}
})
}
}

View File

@ -0,0 +1,6 @@
package collection
import "github.com/kercylan98/minotaur/utils/generic"
type ComparisonHandler[V any] func(source, target V) bool
type OrderedValueGetter[V any, N generic.Ordered] func(v V) N

View File

@ -0,0 +1,329 @@
package collection
// InSlice 检查 v 是否被包含在 slice 中,当 handler 返回 true 时,表示 v 和 slice 中的某个元素相匹配
func InSlice[S ~[]V, V any](slice S, v V, handler ComparisonHandler[V]) bool {
if len(slice) == 0 {
return false
}
for _, value := range slice {
if handler(v, value) {
return true
}
}
return false
}
// InComparableSlice 检查 v 是否被包含在 slice 中
func InComparableSlice[S ~[]V, V comparable](slice S, v V) bool {
if slice == nil {
return false
}
for _, value := range slice {
if value == v {
return true
}
}
return false
}
// AllInSlice 检查 values 中的所有元素是否均被包含在 slice 中,当 handler 返回 true 时,表示 values 中的某个元素和 slice 中的某个元素相匹配
// - 在所有 values 中的元素都被包含在 slice 中时,返回 true
// - 当 values 长度为 0 或为 nil 时,将返回 true
func AllInSlice[S ~[]V, V any](slice S, values []V, handler ComparisonHandler[V]) bool {
if len(slice) == 0 {
return false
}
for _, value := range values {
if !InSlice(slice, value, handler) {
return false
}
}
return true
}
// AllInComparableSlice 检查 values 中的所有元素是否均被包含在 slice 中
// - 在所有 values 中的元素都被包含在 slice 中时,返回 true
// - 当 values 长度为 0 或为 nil 时,将返回 true
func AllInComparableSlice[S ~[]V, V comparable](slice S, values []V) bool {
if len(slice) == 0 {
return false
}
for _, value := range values {
if !InComparableSlice(slice, value) {
return false
}
}
return true
}
// AnyInSlice 检查 values 中的任意一个元素是否被包含在 slice 中,当 handler 返回 true 时,表示 value 中的某个元素和 slice 中的某个元素相匹配
// - 当 values 中的任意一个元素被包含在 slice 中时,返回 true
func AnyInSlice[S ~[]V, V any](slice S, values []V, handler ComparisonHandler[V]) bool {
if len(slice) == 0 {
return false
}
for _, value := range values {
if InSlice(slice, value, handler) {
return true
}
}
return false
}
// AnyInComparableSlice 检查 values 中的任意一个元素是否被包含在 slice 中
// - 当 values 中的任意一个元素被包含在 slice 中时,返回 true
func AnyInComparableSlice[S ~[]V, V comparable](slice S, values []V) bool {
if len(slice) == 0 {
return false
}
for _, value := range values {
if InComparableSlice(slice, value) {
return true
}
}
return false
}
// InSlices 通过将多个切片合并后检查 v 是否被包含在 slices 中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配
// - 当传入的 v 被包含在 slices 的任一成员中时,返回 true
func InSlices[S ~[]V, V any](slices []S, v V, handler ComparisonHandler[V]) bool {
return InSlice(MergeSlices(slices...), v, handler)
}
// InComparableSlices 通过将多个切片合并后检查 v 是否被包含在 slices 中
// - 当传入的 v 被包含在 slices 的任一成员中时,返回 true
func InComparableSlices[S ~[]V, V comparable](slices []S, v V) bool {
return InComparableSlice(MergeSlices(slices...), v)
}
// AllInSlices 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配
// - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true
func AllInSlices[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool {
return AllInSlice(MergeSlices(slices...), values, handler)
}
// AllInComparableSlices 通过将多个切片合并后检查 values 中的所有元素是否被包含在 slices 中
// - 当 values 中的所有元素都被包含在 slices 的任一成员中时,返回 true
func AllInComparableSlices[S ~[]V, V comparable](slices []S, values []V) bool {
return AllInComparableSlice(MergeSlices(slices...), values)
}
// AnyInSlices 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中,当 handler 返回 true 时,表示 values 中的某个元素和 slices 中的某个元素相匹配
// - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true
func AnyInSlices[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool {
return AnyInSlice(MergeSlices(slices...), values, handler)
}
// AnyInComparableSlices 通过将多个切片合并后检查 values 中的任意一个元素是否被包含在 slices 中
// - 当 values 中的任意一个元素被包含在 slices 的任一成员中时,返回 true
func AnyInComparableSlices[S ~[]V, V comparable](slices []S, values []V) bool {
return AnyInComparableSlice(MergeSlices(slices...), values)
}
// InAllSlices 检查 v 是否被包含在 slices 的每一项元素中,当 handler 返回 true 时,表示 v 和 slices 中的某个元素相匹配
// - 当 v 被包含在 slices 的每一项元素中时,返回 true
func InAllSlices[S ~[]V, V any](slices []S, v V, handler ComparisonHandler[V]) bool {
if len(slices) == 0 {
return false
}
for _, slice := range slices {
if !InSlice(slice, v, handler) {
return false
}
}
return true
}
// InAllComparableSlices 检查 v 是否被包含在 slices 的每一项元素中
// - 当 v 被包含在 slices 的每一项元素中时,返回 true
func InAllComparableSlices[S ~[]V, V comparable](slices []S, v V) bool {
if len(slices) == 0 {
return false
}
for _, slice := range slices {
if !InComparableSlice(slice, v) {
return false
}
}
return true
}
// AnyInAllSlices 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素,当 handler 返回 true 时,表示 value 中的某个元素和 slices 中的某个元素相匹配
// - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true
func AnyInAllSlices[S ~[]V, V any](slices []S, values []V, handler ComparisonHandler[V]) bool {
if len(slices) == 0 {
return false
}
for _, slice := range slices {
if !AnyInSlice(slice, values, handler) {
return false
}
}
return true
}
// AnyInAllComparableSlices 检查 slices 中的每一个元素是否均包含至少任意一个 values 中的元素
// - 当 slices 中的每一个元素均包含至少任意一个 values 中的元素时,返回 true
func AnyInAllComparableSlices[S ~[]V, V comparable](slices []S, values []V) bool {
if len(slices) == 0 {
return false
}
for _, slice := range slices {
if !AnyInComparableSlice(slice, values) {
return false
}
}
return true
}
// KeyInMap 检查 m 中是否包含特定 key
func KeyInMap[M ~map[K]V, K comparable, V any](m M, key K) bool {
_, ok := m[key]
return ok
}
// ValueInMap 检查 m 中是否包含特定 value当 handler 返回 true 时,表示 value 和 m 中的某个元素相匹配
func ValueInMap[M ~map[K]V, K comparable, V any](m M, value V, handler ComparisonHandler[V]) bool {
if len(m) == 0 {
return false
}
for _, v := range m {
if handler(value, v) {
return true
}
}
return false
}
// AllKeyInMap 检查 m 中是否包含 keys 中所有的元素
func AllKeyInMap[M ~map[K]V, K comparable, V any](m M, keys ...K) bool {
if len(m) < len(keys) {
return false
}
for _, key := range keys {
if !KeyInMap(m, key) {
return false
}
}
return true
}
// AllValueInMap 检查 m 中是否包含 values 中所有的元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配
func AllValueInMap[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) bool {
if len(m) == 0 {
return false
}
for _, value := range values {
if !ValueInMap(m, value, handler) {
return false
}
}
return true
}
// AnyKeyInMap 检查 m 中是否包含 keys 中任意一个元素
func AnyKeyInMap[M ~map[K]V, K comparable, V any](m M, keys ...K) bool {
if len(m) == 0 {
return false
}
for _, key := range keys {
if KeyInMap(m, key) {
return true
}
}
return false
}
// AnyValueInMap 检查 m 中是否包含 values 中任意一个元素,当 handler 返回 true 时,表示 values 中的某个元素和 m 中的某个元素相匹配
func AnyValueInMap[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) bool {
if len(m) == 0 {
return false
}
for _, value := range values {
if ValueInMap(m, value, handler) {
return true
}
}
return false
}
// AllKeyInMaps 检查 maps 中的每一个元素是否均包含 keys 中所有的元素
func AllKeyInMaps[M ~map[K]V, K comparable, V any](maps []M, keys ...K) bool {
if len(maps) == 0 {
return false
}
for _, m := range maps {
if !AllKeyInMap(m, keys...) {
return false
}
}
return true
}
// AllValueInMaps 检查 maps 中的每一个元素是否均包含 value 中所有的元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配
func AllValueInMaps[M ~map[K]V, K comparable, V any](maps []M, values []V, handler ComparisonHandler[V]) bool {
if len(maps) == 0 {
return false
}
for _, m := range maps {
if !AllValueInMap(m, values, handler) {
return false
}
}
return true
}
// AnyKeyInMaps 检查 keys 中的任意一个元素是否被包含在 maps 中的任意一个元素中
// - 当 keys 中的任意一个元素被包含在 maps 中的任意一个元素中时,返回 true
func AnyKeyInMaps[M ~map[K]V, K comparable, V any](maps []M, keys ...K) bool {
if len(maps) == 0 {
return false
}
for _, m := range maps {
if AnyKeyInMap(m, keys...) {
return true
}
}
return false
}
// AnyValueInMaps 检查 maps 中的任意一个元素是否包含 value 中的任意一个元素,当 handler 返回 true 时,表示 value 中的某个元素和 maps 中的某个元素相匹配
// - 当 maps 中的任意一个元素包含 value 中的任意一个元素时,返回 true
func AnyValueInMaps[M ~map[K]V, K comparable, V any](maps []M, values []V, handler ComparisonHandler[V]) bool {
if len(maps) == 0 {
return false
}
for _, m := range maps {
if !AnyValueInMap(m, values, handler) {
return false
}
}
return true
}
// KeyInAllMaps 检查 key 是否被包含在 maps 的每一个元素中
func KeyInAllMaps[M ~map[K]V, K comparable, V any](maps []M, key K) bool {
if len(maps) == 0 {
return false
}
for _, m := range maps {
if !KeyInMap(m, key) {
return false
}
}
return true
}
// AnyKeyInAllMaps 检查 maps 中的每一个元素是否均包含 keys 中任意一个元素
// - 当 maps 中的每一个元素均包含 keys 中任意一个元素时,返回 true
func AnyKeyInAllMaps[M ~map[K]V, K comparable, V any](maps []M, keys []K) bool {
if len(maps) == 0 {
return false
}
for _, m := range maps {
if !AnyKeyInMap(m, keys...) {
return false
}
}
return true
}

View File

@ -0,0 +1,228 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
)
func ExampleInSlice() {
result := collection.InSlice([]int{1, 2, 3}, 2, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleInComparableSlice() {
result := collection.InComparableSlice([]int{1, 2, 3}, 2)
fmt.Println(result)
// Output:
// true
}
func ExampleAllInSlice() {
result := collection.AllInSlice([]int{1, 2, 3}, []int{1, 2}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleAllInComparableSlice() {
result := collection.AllInComparableSlice([]int{1, 2, 3}, []int{1, 2})
fmt.Println(result)
// Output:
// true
}
func ExampleAnyInSlice() {
result := collection.AnyInSlice([]int{1, 2, 3}, []int{1, 2}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleAnyInComparableSlice() {
result := collection.AnyInComparableSlice([]int{1, 2, 3}, []int{1, 2})
fmt.Println(result)
// Output:
// true
}
func ExampleInSlices() {
result := collection.InSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleInComparableSlices() {
result := collection.InComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2)
fmt.Println(result)
// Output:
// true
}
func ExampleAllInSlices() {
result := collection.AllInSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleAllInComparableSlices() {
result := collection.AllInComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2})
fmt.Println(result)
// Output:
// true
}
func ExampleAnyInSlices() {
result := collection.AnyInSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleAnyInComparableSlices() {
result := collection.AnyInComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2})
fmt.Println(result)
// Output:
// true
}
func ExampleInAllSlices() {
result := collection.InAllSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// false
}
func ExampleInAllComparableSlices() {
result := collection.InAllComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, 2)
fmt.Println(result)
// Output:
// false
}
func ExampleAnyInAllSlices() {
result := collection.AnyInAllSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// false
}
func ExampleAnyInAllComparableSlices() {
result := collection.AnyInAllComparableSlices([][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2})
fmt.Println(result)
// Output:
// false
}
func ExampleKeyInMap() {
result := collection.KeyInMap(map[string]int{"a": 1, "b": 2}, "a")
fmt.Println(result)
// Output:
// true
}
func ExampleValueInMap() {
result := collection.ValueInMap(map[string]int{"a": 1, "b": 2}, 2, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleAllKeyInMap() {
result := collection.AllKeyInMap(map[string]int{"a": 1, "b": 2}, "a", "b")
fmt.Println(result)
// Output:
// true
}
func ExampleAllValueInMap() {
result := collection.AllValueInMap(map[string]int{"a": 1, "b": 2}, []int{1}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleAnyKeyInMap() {
result := collection.AnyKeyInMap(map[string]int{"a": 1, "b": 2}, "a", "b")
fmt.Println(result)
// Output:
// true
}
func ExampleAnyValueInMap() {
result := collection.AnyValueInMap(map[string]int{"a": 1, "b": 2}, []int{1}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleAllKeyInMaps() {
result := collection.AllKeyInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a", "b")
fmt.Println(result)
// Output:
// true
}
func ExampleAllValueInMaps() {
result := collection.AllValueInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, []int{1}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleAnyKeyInMaps() {
result := collection.AnyKeyInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a", "b")
fmt.Println(result)
// Output:
// true
}
func ExampleAnyValueInMaps() {
result := collection.AnyValueInMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, []int{1}, func(source, target int) bool {
return source == target
})
fmt.Println(result)
// Output:
// true
}
func ExampleKeyInAllMaps() {
result := collection.KeyInAllMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, "a")
fmt.Println(result)
// Output:
// true
}
func ExampleAnyKeyInAllMaps() {
result := collection.AnyKeyInAllMaps([]map[string]int{{"a": 1, "b": 2}, {"a": 1, "b": 2}}, []string{"a"})
fmt.Println(result)
// Output:
// true
}

View File

@ -0,0 +1,709 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
var intComparisonHandler = func(source, target int) bool {
return source == target
}
func TestInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
inputV int
expected bool
}{
{"TestInSlice_NonEmptySliceIn", []int{1, 2, 3}, 1, true},
{"TestInSlice_NonEmptySliceNotIn", []int{1, 2, 3}, 4, false},
{"TestInSlice_EmptySlice", []int{}, 1, false},
{"TestInSlice_NilSlice", nil, 1, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.InSlice(c.input, c.inputV, func(source, target int) bool {
return source == target
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
inputV int
expected bool
}{
{"TestInComparableSlice_NonEmptySliceIn", []int{1, 2, 3}, 1, true},
{"TestInComparableSlice_NonEmptySliceNotIn", []int{1, 2, 3}, 4, false},
{"TestInComparableSlice_EmptySlice", []int{}, 1, false},
{"TestInComparableSlice_NilSlice", nil, 1, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.InComparableSlice(c.input, c.inputV)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAllInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
inputV []int
expected bool
}{
{"TestAllInSlice_NonEmptySliceIn", []int{1, 2, 3}, []int{1, 2}, true},
{"TestAllInSlice_NonEmptySliceNotIn", []int{1, 2, 3}, []int{1, 4}, false},
{"TestAllInSlice_EmptySlice", []int{}, []int{1, 2}, false},
{"TestAllInSlice_NilSlice", nil, []int{1, 2}, false},
{"TestAllInSlice_EmptyValueSlice", []int{1, 2, 3}, []int{}, true},
{"TestAllInSlice_NilValueSlice", []int{1, 2, 3}, nil, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AllInSlice(c.input, c.inputV, func(source, target int) bool {
return source == target
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAllInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
inputV []int
expected bool
}{
{"TestAllInComparableSlice_NonEmptySliceIn", []int{1, 2, 3}, []int{1, 2}, true},
{"TestAllInComparableSlice_NonEmptySliceNotIn", []int{1, 2, 3}, []int{1, 4}, false},
{"TestAllInComparableSlice_EmptySlice", []int{}, []int{1, 2}, false},
{"TestAllInComparableSlice_NilSlice", nil, []int{1, 2}, false},
{"TestAllInComparableSlice_EmptyValueSlice", []int{1, 2, 3}, []int{}, true},
{"TestAllInComparableSlice_NilValueSlice", []int{1, 2, 3}, nil, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AllInComparableSlice(c.input, c.inputV)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestAnyInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
inputV []int
expected bool
}{
{"TestAnyInSlice_NonEmptySliceIn", []int{1, 2, 3}, []int{1, 2}, true},
{"TestAnyInSlice_NonEmptySliceNotIn", []int{1, 2, 3}, []int{1, 4}, true},
{"TestAnyInSlice_EmptySlice", []int{}, []int{1, 2}, false},
{"TestAnyInSlice_NilSlice", nil, []int{1, 2}, false},
{"TestAnyInSlice_EmptyValueSlice", []int{1, 2, 3}, []int{}, false},
{"TestAnyInSlice_NilValueSlice", []int{1, 2, 3}, nil, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyInSlice(c.input, c.inputV, func(source, target int) bool {
return source == target
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAnyInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
inputV []int
expected bool
}{
{"TestAnyInComparableSlice_NonEmptySliceIn", []int{1, 2, 3}, []int{1, 2}, true},
{"TestAnyInComparableSlice_NonEmptySliceNotIn", []int{1, 2, 3}, []int{1, 4}, true},
{"TestAnyInComparableSlice_EmptySlice", []int{}, []int{1, 2}, false},
{"TestAnyInComparableSlice_NilSlice", nil, []int{1, 2}, false},
{"TestAnyInComparableSlice_EmptyValueSlice", []int{1, 2, 3}, []int{}, false},
{"TestAnyInComparableSlice_NilValueSlice", []int{1, 2, 3}, nil, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyInComparableSlice(c.input, c.inputV)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestInSlices(t *testing.T) {
var cases = []struct {
name string
input [][]int
inputV int
expected bool
}{
{"TestInSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, 1, true},
{"TestInSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, 5, false},
{"TestInSlices_EmptySlice", [][]int{{}, {}}, 1, false},
{"TestInSlices_NilSlice", nil, 1, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.InSlices(c.input, c.inputV, func(source, target int) bool {
return source == target
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestInComparableSlices(t *testing.T) {
var cases = []struct {
name string
input [][]int
inputV int
expected bool
}{
{"TestInComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, 1, true},
{"TestInComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, 5, false},
{"TestInComparableSlices_EmptySlice", [][]int{{}, {}}, 1, false},
{"TestInComparableSlices_NilSlice", nil, 1, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.InComparableSlices(c.input, c.inputV)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
}
})
}
}
func TestAllInSlices(t *testing.T) {
var cases = []struct {
name string
input [][]int
inputValues []int
expected bool
}{
{"TestAllInSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, []int{1, 2}, true},
{"TestAllInSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, false},
{"TestAllInSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
{"TestAllInSlices_NilSlice", nil, []int{1, 2}, false},
{"TestAllInSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, true},
{"TestAllInSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AllInSlices(c.input, c.inputValues, func(source, target int) bool {
return source == target
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAllInComparableSlices(t *testing.T) {
var cases = []struct {
name string
input [][]int
inputValues []int
expected bool
}{
{"TestAllInComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, []int{1, 2}, true},
{"TestAllInComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, false},
{"TestAllInComparableSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
{"TestAllInComparableSlices_NilSlice", nil, []int{1, 2}, false},
{"TestAllInComparableSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, true},
{"TestAllInComparableSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AllInComparableSlices(c.input, c.inputValues)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
}
})
}
}
func TestAnyInSlices(t *testing.T) {
var cases = []struct {
name string
slices [][]int
values []int
expected bool
}{
{"TestAnyInSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, []int{1, 2}, true},
{"TestAnyInSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, true},
{"TestAnyInSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
{"TestAnyInSlices_NilSlice", nil, []int{1, 2}, false},
{"TestAnyInSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, false},
{"TestAnyInSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyInSlices(c.slices, c.values, func(source, target int) bool {
return source == target
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAnyInComparableSlices(t *testing.T) {
var cases = []struct {
name string
slices [][]int
values []int
expected bool
}{
{"TestAnyInComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {3, 4}}, []int{1, 2}, true},
{"TestAnyInComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, true},
{"TestAnyInComparableSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
{"TestAnyInComparableSlices_NilSlice", nil, []int{1, 2}, false},
{"TestAnyInComparableSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, false},
{"TestAnyInComparableSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyInComparableSlices(c.slices, c.values)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
}
})
}
}
func TestInAllSlices(t *testing.T) {
var cases = []struct {
name string
slices [][]int
value int
expected bool
}{
{"TestInAllSlices_NonEmptySliceIn", [][]int{{1, 2}, {1, 3}}, 1, true},
{"TestInAllSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, 5, false},
{"TestInAllSlices_EmptySlice", [][]int{{}, {}}, 1, false},
{"TestInAllSlices_NilSlice", nil, 1, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.InAllSlices(c.slices, c.value, func(source, target int) bool {
return source == target
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestInAllComparableSlices(t *testing.T) {
var cases = []struct {
name string
slices [][]int
value int
expected bool
}{
{"TestInAllComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {1, 3}}, 1, true},
{"TestInAllComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, 5, false},
{"TestInAllComparableSlices_EmptySlice", [][]int{{}, {}}, 1, false},
{"TestInAllComparableSlices_NilSlice", nil, 1, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.InAllComparableSlices(c.slices, c.value)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
}
})
}
}
func TestAnyInAllSlices(t *testing.T) {
var cases = []struct {
name string
slices [][]int
values []int
expected bool
}{
{"TestAnyInAllSlices_NonEmptySliceIn", [][]int{{1, 2}, {1, 3}}, []int{1, 2}, true},
{"TestAnyInAllSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, false},
{"TestAnyInAllSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
{"TestAnyInAllSlices_NilSlice", nil, []int{1, 2}, false},
{"TestAnyInAllSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, false},
{"TestAnyInAllSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyInAllSlices(c.slices, c.values, func(source, target int) bool {
return source == target
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAnyInAllComparableSlices(t *testing.T) {
var cases = []struct {
name string
slices [][]int
values []int
expected bool
}{
{"TestAnyInAllComparableSlices_NonEmptySliceIn", [][]int{{1, 2}, {1, 3}}, []int{1, 2}, true},
{"TestAnyInAllComparableSlices_NonEmptySliceNotIn", [][]int{{1, 2}, {3, 4}}, []int{1, 5}, false},
{"TestAnyInAllComparableSlices_EmptySlice", [][]int{{}, {}}, []int{1, 2}, false},
{"TestAnyInAllComparableSlices_NilSlice", nil, []int{1, 2}, false},
{"TestAnyInAllComparableSlices_EmptyValueSlice", [][]int{{1, 2}, {3, 4}}, []int{}, false},
{"TestAnyInAllComparableSlices_NilValueSlice", [][]int{{1, 2}, {3, 4}}, nil, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyInAllComparableSlices(c.slices, c.values)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "")
}
})
}
}
func TestKeyInMap(t *testing.T) {
var cases = []struct {
name string
m map[int]int
key int
expected bool
}{
{"TestKeyInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, 1, true},
{"TestKeyInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, 3, false},
{"TestKeyInMap_EmptySlice", map[int]int{}, 1, false},
{"TestKeyInMap_NilSlice", nil, 1, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.KeyInMap(c.m, c.key)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestValueInMap(t *testing.T) {
var cases = []struct {
name string
m map[int]int
value int
handler collection.ComparisonHandler[int]
expected bool
}{
{"TestValueInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, 1, intComparisonHandler, true},
{"TestValueInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, 3, intComparisonHandler, false},
{"TestValueInMap_EmptySlice", map[int]int{}, 1, intComparisonHandler, false},
{"TestValueInMap_NilSlice", nil, 1, intComparisonHandler, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.ValueInMap(c.m, c.value, c.handler)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAllKeyInMap(t *testing.T) {
var cases = []struct {
name string
m map[int]int
keys []int
expected bool
}{
{"TestAllKeyInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, []int{1, 2}, true},
{"TestAllKeyInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, []int{1, 3}, false},
{"TestAllKeyInMap_EmptySlice", map[int]int{}, []int{1, 2}, false},
{"TestAllKeyInMap_NilSlice", nil, []int{1, 2}, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AllKeyInMap(c.m, c.keys...)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAllValueInMap(t *testing.T) {
var cases = []struct {
name string
m map[int]int
values []int
expected bool
}{
{"TestAllValueInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, []int{1, 2}, true},
{"TestAllValueInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, []int{1, 3}, false},
{"TestAllValueInMap_EmptySlice", map[int]int{}, []int{1, 2}, false},
{"TestAllValueInMap_NilSlice", nil, []int{1, 2}, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AllValueInMap(c.m, c.values, intComparisonHandler)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after clone, the length of input is not equal")
}
})
}
}
func TestAnyKeyInMap(t *testing.T) {
var cases = []struct {
name string
m map[int]int
keys []int
expected bool
}{
{"TestAnyKeyInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, []int{1, 2}, true},
{"TestAnyKeyInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, []int{1, 3}, true},
{"TestAnyKeyInMap_EmptySlice", map[int]int{}, []int{1, 2}, false},
{"TestAnyKeyInMap_NilSlice", nil, []int{1, 2}, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyKeyInMap(c.m, c.keys...)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestAnyValueInMap(t *testing.T) {
var cases = []struct {
name string
m map[int]int
values []int
expected bool
}{
{"TestAnyValueInMap_NonEmptySliceIn", map[int]int{1: 1, 2: 2}, []int{1, 2}, true},
{"TestAnyValueInMap_NonEmptySliceNotIn", map[int]int{1: 1, 2: 2}, []int{1, 3}, true},
{"TestAnyValueInMap_EmptySlice", map[int]int{}, []int{1, 2}, false},
{"TestAnyValueInMap_NilSlice", nil, []int{1, 2}, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyValueInMap(c.m, c.values, intComparisonHandler)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestAllKeyInMaps(t *testing.T) {
var cases = []struct {
name string
maps []map[int]int
keys []int
expected bool
}{
{"TestAllKeyInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, false},
{"TestAllKeyInMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, false},
{"TestAllKeyInMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, false},
{"TestAllKeyInMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
{"TestAllKeyInMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
{"TestAllKeyInMaps_NilSlice", nil, []int{1, 2}, false},
{"TestAllKeyInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AllKeyInMaps(c.maps, c.keys...)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestAllValueInMaps(t *testing.T) {
var cases = []struct {
name string
maps []map[int]int
values []int
expected bool
}{
{"TestAllValueInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, false},
{"TestAllValueInMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, false},
{"TestAllValueInMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, false},
{"TestAllValueInMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
{"TestAllValueInMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
{"TestAllValueInMaps_NilSlice", nil, []int{1, 2}, false},
{"TestAllValueInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AllValueInMaps(c.maps, c.values, intComparisonHandler)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestAnyKeyInMaps(t *testing.T) {
var cases = []struct {
name string
maps []map[int]int
keys []int
expected bool
}{
{"TestAnyKeyInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, true},
{"TestAnyKeyInMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, true},
{"TestAnyKeyInMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, true},
{"TestAnyKeyInMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
{"TestAnyKeyInMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
{"TestAnyKeyInMaps_NilSlice", nil, []int{1, 2}, false},
{"TestAnyKeyInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyKeyInMaps(c.maps, c.keys...)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestAnyValueInMaps(t *testing.T) {
var cases = []struct {
name string
maps []map[int]int
values []int
expected bool
}{
{"TestAnyValueInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, false},
{"TestAnyValueInMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, true},
{"TestAnyValueInMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, false},
{"TestAnyValueInMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
{"TestAnyValueInMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
{"TestAnyValueInMaps_NilSlice", nil, []int{1, 2}, false},
{"TestAnyValueInMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyValueInMaps(c.maps, c.values, intComparisonHandler)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestKeyInAllMaps(t *testing.T) {
var cases = []struct {
name string
maps []map[int]int
key int
expected bool
}{
{"TestKeyInAllMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, 1, false},
{"TestKeyInAllMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, 3, false},
{"TestKeyInAllMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, 1, false},
{"TestKeyInAllMaps_NilSlice", []map[int]int{{}, {}}, 1, false},
{"TestKeyInAllMaps_EmptySlice", []map[int]int{}, 1, false},
{"TestKeyInAllMaps_NilSlice", nil, 1, false},
{"TestKeyInAllMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, 1, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.KeyInAllMaps(c.maps, c.key)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "not as expected")
}
})
}
}
func TestAnyKeyInAllMaps(t *testing.T) {
var cases = []struct {
name string
maps []map[int]int
keys []int
expected bool
}{
{"TestAnyKeyInAllMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 2}, false},
{"TestAnyKeyInAllMaps_NonEmptySliceNotIn", []map[int]int{{1: 1, 2: 2}, {3: 3, 4: 4}}, []int{1, 3}, true},
{"TestAnyKeyInAllMaps_EmptySlice", []map[int]int{{1: 1, 2: 2}, {}}, []int{1, 2}, false},
{"TestAnyKeyInAllMaps_NilSlice", []map[int]int{{}, {}}, []int{1, 2}, false},
{"TestAnyKeyInAllMaps_EmptySlice", []map[int]int{}, []int{1, 2}, false},
{"TestAnyKeyInAllMaps_NilSlice", nil, []int{1, 2}, false},
{"TestAnyKeyInAllMaps_NonEmptySliceIn", []map[int]int{{1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 4: 4}}, []int{1, 2}, true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var actual = collection.AnyKeyInAllMaps(c.maps, c.keys)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, actual)
}
})
}
}

121
utils/collection/convert.go Normal file
View File

@ -0,0 +1,121 @@
package collection
// ConvertSliceToAny 将切片转换为任意类型的切片
func ConvertSliceToAny[S ~[]V, V any](s S) []any {
if len(s) == 0 {
return nil
}
var r = make([]any, len(s))
for i, v := range s {
r[i] = v
}
return r
}
// ConvertSliceToIndexMap 将切片转换为索引为键的映射
func ConvertSliceToIndexMap[S ~[]V, V any](s S) map[int]V {
if len(s) == 0 {
return make(map[int]V)
}
var r = make(map[int]V, len(s))
for i, v := range s {
r[i] = v
}
return r
}
// ConvertSliceToIndexOnlyMap 将切片转换为索引为键的映射
func ConvertSliceToIndexOnlyMap[S ~[]V, V any](s S) map[int]struct{} {
if len(s) == 0 {
return nil
}
var r = make(map[int]struct{}, len(s))
for i := range s {
r[i] = struct{}{}
}
return r
}
// ConvertSliceToMap 将切片转换为值为键的映射
func ConvertSliceToMap[S ~[]V, V comparable](s S) map[V]struct{} {
if len(s) == 0 {
return nil
}
var r = make(map[V]struct{}, len(s))
for _, v := range s {
r[v] = struct{}{}
}
return r
}
// ConvertSliceToBoolMap 将切片转换为值为键的映射
func ConvertSliceToBoolMap[S ~[]V, V comparable](s S) map[V]bool {
if len(s) == 0 {
return make(map[V]bool)
}
var r = make(map[V]bool, len(s))
for _, v := range s {
r[v] = true
}
return r
}
// ConvertMapKeysToSlice 将映射的键转换为切片
func ConvertMapKeysToSlice[M ~map[K]V, K comparable, V any](m M) []K {
if len(m) == 0 {
return nil
}
var r = make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
// ConvertMapValuesToSlice 将映射的值转换为切片
func ConvertMapValuesToSlice[M ~map[K]V, K comparable, V any](m M) []V {
if len(m) == 0 {
return nil
}
var r = make([]V, 0, len(m))
for _, v := range m {
r = append(r, v)
}
return r
}
// InvertMap 将映射的键和值互换
func InvertMap[M ~map[K]V, N map[V]K, K, V comparable](m M) N {
if m == nil {
return nil
}
var r = make(N, len(m))
for k, v := range m {
r[v] = k
}
return r
}
// ConvertMapValuesToBool 将映射的值转换为布尔值
func ConvertMapValuesToBool[M ~map[K]V, N map[K]bool, K comparable, V any](m M) N {
if m == nil {
return nil
}
var r = make(N, len(m))
for k := range m {
r[k] = true
}
return r
}
// ReverseSlice 将切片反转
func ReverseSlice[S ~[]V, V any](s *S) {
if s == nil {
return
}
var length = len(*s)
for i := 0; i < length/2; i++ {
(*s)[i], (*s)[length-i-1] = (*s)[length-i-1], (*s)[i]
}
}

View File

@ -0,0 +1,104 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
"reflect"
)
func ExampleConvertSliceToAny() {
result := collection.ConvertSliceToAny([]int{1, 2, 3})
fmt.Println(reflect.TypeOf(result).String(), len(result))
// Output:
// []interface {} 3
}
func ExampleConvertSliceToIndexMap() {
slice := []int{1, 2, 3}
result := collection.ConvertSliceToIndexMap(slice)
for i, v := range slice {
fmt.Println(result[i], v)
}
// Output:
// 1 1
// 2 2
// 3 3
}
func ExampleConvertSliceToIndexOnlyMap() {
slice := []int{1, 2, 3}
result := collection.ConvertSliceToIndexOnlyMap(slice)
expected := map[int]bool{0: true, 1: true, 2: true}
for k := range result {
fmt.Println(expected[k])
}
// Output:
// true
// true
// true
}
func ExampleConvertSliceToMap() {
slice := []int{1, 2, 3}
result := collection.ConvertSliceToMap(slice)
fmt.Println(collection.AllKeyInMap(result, slice...))
// Output:
// true
}
func ExampleConvertSliceToBoolMap() {
slice := []int{1, 2, 3}
result := collection.ConvertSliceToBoolMap(slice)
for _, v := range slice {
fmt.Println(v, result[v])
}
// Output:
// 1 true
// 2 true
// 3 true
}
func ExampleConvertMapKeysToSlice() {
result := collection.ConvertMapKeysToSlice(map[int]int{1: 1, 2: 2, 3: 3})
for i, v := range result {
fmt.Println(i, v)
}
// Output:
// 0 1
// 1 2
// 2 3
}
func ExampleConvertMapValuesToSlice() {
result := collection.ConvertMapValuesToSlice(map[int]int{1: 1, 2: 2, 3: 3})
expected := map[int]bool{1: true, 2: true, 3: true}
for _, v := range result {
fmt.Println(expected[v])
}
// Output:
// true
// true
// true
}
func ExampleInvertMap() {
result := collection.InvertMap(map[int]string{1: "a", 2: "b", 3: "c"})
fmt.Println(collection.AllKeyInMap(result, "a", "b", "c"))
// Output:
// true
}
func ExampleConvertMapValuesToBool() {
result := collection.ConvertMapValuesToBool(map[int]int{1: 1})
fmt.Println(result)
// Output:
// map[1:true]
}
func ExampleReverseSlice() {
var s = []int{1, 2, 3}
collection.ReverseSlice(&s)
fmt.Println(s)
// Output:
// [3 2 1]
}

View File

@ -0,0 +1,293 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"reflect"
"testing"
)
func TestConvertSliceToAny(t *testing.T) {
var cases = []struct {
name string
input []int
expected []interface{}
}{
{name: "TestConvertSliceToAny_NonEmpty", input: []int{1, 2, 3}, expected: []any{1, 2, 3}},
{name: "TestConvertSliceToAny_Empty", input: []int{}, expected: []any{}},
{name: "TestConvertSliceToAny_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ConvertSliceToAny(c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for i := 0; i < len(actual); i++ {
av, ev := actual[i], c.expected[i]
if reflect.TypeOf(av).Kind() != reflect.TypeOf(ev).Kind() {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
if av != ev {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}
func TestConvertSliceToIndexMap(t *testing.T) {
var cases = []struct {
name string
input []int
expected map[int]int
}{
{name: "TestConvertSliceToIndexMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]int{0: 1, 1: 2, 2: 3}},
{name: "TestConvertSliceToIndexMap_Empty", input: []int{}, expected: map[int]int{}},
{name: "TestConvertSliceToIndexMap_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ConvertSliceToIndexMap(c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for k, v := range actual {
if c.expected[k] != v {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}
func TestConvertSliceToIndexOnlyMap(t *testing.T) {
var cases = []struct {
name string
input []int
expected map[int]struct{}
}{
{name: "TestConvertSliceToIndexOnlyMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{0: {}, 1: {}, 2: {}}},
{name: "TestConvertSliceToIndexOnlyMap_Empty", input: []int{}, expected: map[int]struct{}{}},
{name: "TestConvertSliceToIndexOnlyMap_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ConvertSliceToIndexOnlyMap(c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for k, v := range actual {
if _, ok := c.expected[k]; !ok {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
if v != struct{}{} {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}
func TestConvertSliceToMap(t *testing.T) {
var cases = []struct {
name string
input []int
expected map[int]struct{}
}{
{name: "TestConvertSliceToMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]struct{}{1: {}, 2: {}, 3: {}}},
{name: "TestConvertSliceToMap_Empty", input: []int{}, expected: map[int]struct{}{}},
{name: "TestConvertSliceToMap_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ConvertSliceToMap(c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for k, v := range actual {
if _, ok := c.expected[k]; !ok {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
if v != struct{}{} {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}
func TestConvertSliceToBoolMap(t *testing.T) {
var cases = []struct {
name string
input []int
expected map[int]bool
}{
{name: "TestConvertSliceToBoolMap_NonEmpty", input: []int{1, 2, 3}, expected: map[int]bool{1: true, 2: true, 3: true}},
{name: "TestConvertSliceToBoolMap_Empty", input: []int{}, expected: map[int]bool{}},
{name: "TestConvertSliceToBoolMap_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ConvertSliceToBoolMap(c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for k, v := range actual {
if c.expected[k] != v {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}
func TestConvertMapKeysToSlice(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected []int
}{
{name: "TestConvertMapKeysToSlice_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, expected: []int{1, 2, 3}},
{name: "TestConvertMapKeysToSlice_Empty", input: map[int]int{}, expected: []int{}},
{name: "TestConvertMapKeysToSlice_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ConvertMapKeysToSlice(c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
var matchCount = 0
for _, av := range actual {
for _, ev := range c.expected {
if av == ev {
matchCount++
}
}
}
if matchCount != len(actual) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
})
}
}
func TestConvertMapValuesToSlice(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected []int
}{
{name: "TestConvertMapValuesToSlice_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, expected: []int{1, 2, 3}},
{name: "TestConvertMapValuesToSlice_Empty", input: map[int]int{}, expected: []int{}},
{name: "TestConvertMapValuesToSlice_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ConvertMapValuesToSlice(c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
var matchCount = 0
for _, av := range actual {
for _, ev := range c.expected {
if av == ev {
matchCount++
}
}
}
if matchCount != len(actual) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
})
}
}
func TestInvertMap(t *testing.T) {
var cases = []struct {
name string
input map[int]string
expected map[string]int
}{
{name: "TestInvertMap_NonEmpty", input: map[int]string{1: "1", 2: "2", 3: "3"}, expected: map[string]int{"1": 1, "2": 2, "3": 3}},
{name: "TestInvertMap_Empty", input: map[int]string{}, expected: map[string]int{}},
{name: "TestInvertMap_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.InvertMap[map[int]string, map[string]int](c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for k, v := range actual {
if c.expected[k] != v {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}
func TestConvertMapValuesToBool(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{name: "TestConvertMapValuesToBool_NonEmpty", input: map[int]int{1: 1, 2: 2, 3: 3}, expected: map[int]bool{1: true, 2: true, 3: true}},
{name: "TestConvertMapValuesToBool_Empty", input: map[int]int{}, expected: map[int]bool{}},
{name: "TestConvertMapValuesToBool_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ConvertMapValuesToBool[map[int]int, map[int]bool](c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for k, v := range actual {
if c.expected[k] != v {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}
func TestReverseSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{name: "TestReverseSlice_NonEmpty", input: []int{1, 2, 3}, expected: []int{3, 2, 1}},
{name: "TestReverseSlice_Empty", input: []int{}, expected: []int{}},
{name: "TestReverseSlice_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.ReverseSlice(&c.input)
if len(c.input) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
}
for i := 0; i < len(c.input); i++ {
av, ev := c.input[i], c.expected[i]
if reflect.TypeOf(av).Kind() != reflect.TypeOf(ev).Kind() {
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
}
if av != ev {
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
}
}
})
}
}

2
utils/collection/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package collection 用于对 input 和 map 操作的工具函数
package collection

82
utils/collection/drop.go Normal file
View File

@ -0,0 +1,82 @@
package collection
// ClearSlice 清空切片
func ClearSlice[S ~[]V, V any](slice *S) {
if slice == nil {
return
}
*slice = (*slice)[0:0]
}
// ClearMap 清空 map
func ClearMap[M ~map[K]V, K comparable, V any](m M) {
for k := range m {
delete(m, k)
}
}
// DropSliceByIndices 删除切片中特定索引的元素
func DropSliceByIndices[S ~[]V, V any](slice *S, indices ...int) {
if slice == nil {
return
}
if len(indices) == 0 {
return
}
excludeMap := make(map[int]bool)
for _, ex := range indices {
excludeMap[ex] = true
}
validElements := (*slice)[:0]
for i, v := range *slice {
if !excludeMap[i] {
validElements = append(validElements, v)
}
}
*slice = validElements
}
// DropSliceByCondition 删除切片中符合条件的元素
// - condition 的返回值为 true 时,将会删除该元素
func DropSliceByCondition[S ~[]V, V any](slice *S, condition func(v V) bool) {
if slice == nil {
return
}
if condition == nil {
return
}
validElements := (*slice)[:0]
for _, v := range *slice {
if !condition(v) {
validElements = append(validElements, v)
}
}
*slice = validElements
}
// DropSliceOverlappingElements 删除切片中与另一个切片重叠的元素
func DropSliceOverlappingElements[S ~[]V, V any](slice *S, anotherSlice []V, comparisonHandler ComparisonHandler[V]) {
if slice == nil {
return
}
if anotherSlice == nil {
return
}
if comparisonHandler == nil {
return
}
validElements := (*slice)[:0]
for _, v := range *slice {
if !InSlice(anotherSlice, v, comparisonHandler) {
validElements = append(validElements, v)
}
}
*slice = validElements
}

View File

@ -0,0 +1,47 @@
package collection
import "fmt"
func ExampleClearSlice() {
slice := []int{1, 2, 3, 4, 5}
ClearSlice(&slice)
fmt.Println(slice)
// Output:
// []
}
func ExampleClearMap() {
m := map[int]int{1: 1, 2: 2, 3: 3}
ClearMap(m)
fmt.Println(m)
// Output:
// map[]
}
func ExampleDropSliceByIndices() {
slice := []int{1, 2, 3, 4, 5}
DropSliceByIndices(&slice, 1, 3)
fmt.Println(slice)
// Output:
// [1 3 5]
}
func ExampleDropSliceByCondition() {
slice := []int{1, 2, 3, 4, 5}
DropSliceByCondition(&slice, func(v int) bool {
return v%2 == 0
})
fmt.Println(slice)
// Output:
// [1 3 5]
}
func ExampleDropSliceOverlappingElements() {
slice := []int{1, 2, 3, 4, 5}
DropSliceOverlappingElements(&slice, []int{1, 3, 5}, func(source, target int) bool {
return source == target
})
fmt.Println(slice)
// Output:
// [2 4]
}

View File

@ -0,0 +1,145 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestClearSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestClearSlice_NonEmptySlice", []int{1, 2, 3}, []int{}},
{"TestClearSlice_EmptySlice", []int{}, []int{}},
{"TestClearSlice_NilSlice", nil, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.ClearSlice(&c.input)
if len(c.input) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the length of input is not equal")
}
for i := 0; i < len(c.input); i++ {
if c.input[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the inputV of input is not equal")
}
}
})
}
}
func TestClearMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]int
}{
{"TestClearMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{}},
{"TestClearMap_EmptyMap", map[int]int{}, map[int]int{}},
{"TestClearMap_NilMap", nil, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.ClearMap(c.input)
if len(c.input) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the length of map is not equal")
}
for k, v := range c.input {
if v != c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after clear, the inputV of map is not equal")
}
}
})
}
}
func TestDropSliceByIndices(t *testing.T) {
var cases = []struct {
name string
input []int
indices []int
expected []int
}{
{"TestDropSliceByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{1, 3}, []int{1, 3, 5}},
{"TestDropSliceByIndices_EmptySlice", []int{}, []int{1, 3}, []int{}},
{"TestDropSliceByIndices_NilSlice", nil, []int{1, 3}, nil},
{"TestDropSliceByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{}, []int{1, 2, 3, 4, 5}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.DropSliceByIndices(&c.input, c.indices...)
if len(c.input) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
}
for i := 0; i < len(c.input); i++ {
if c.input[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
}
}
})
}
}
func TestDropSliceByCondition(t *testing.T) {
var cases = []struct {
name string
input []int
condition func(v int) bool
expected []int
}{
{"TestDropSliceByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool { return v%2 == 0 }, []int{1, 3, 5}},
{"TestDropSliceByCondition_EmptySlice", []int{}, func(v int) bool { return v%2 == 0 }, []int{}},
{"TestDropSliceByCondition_NilSlice", nil, func(v int) bool { return v%2 == 0 }, nil},
{"TestDropSliceByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool { return v%2 == 1 }, []int{2, 4}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.DropSliceByCondition(&c.input, c.condition)
if len(c.input) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
}
for i := 0; i < len(c.input); i++ {
if c.input[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
}
}
})
}
}
func TestDropSliceOverlappingElements(t *testing.T) {
var cases = []struct {
name string
input []int
anotherSlice []int
comparisonHandler collection.ComparisonHandler[int]
expected []int
expectedAnother []int
expectedComparison []int
}{
{"TestDropSliceOverlappingElements_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, []int{1, 2}, []int{6, 7}, []int{3, 4, 5}},
{"TestDropSliceOverlappingElements_EmptySlice", []int{}, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, []int{}, []int{3, 4, 5, 6, 7}, []int{}},
{"TestDropSliceOverlappingElements_NilSlice", nil, []int{3, 4, 5, 6, 7}, func(v1, v2 int) bool { return v1 == v2 }, nil, []int{3, 4, 5, 6, 7}, nil},
{"TestDropSliceOverlappingElements_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{}, func(v1, v2 int) bool { return v1 == v2 }, []int{1, 2, 3, 4, 5}, []int{}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.DropSliceOverlappingElements(&c.input, c.anotherSlice, c.comparisonHandler)
if len(c.input) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the length of input is not equal")
}
for i := 0; i < len(c.input); i++ {
if c.input[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, c.input, "after drop, the inputV of input is not equal")
}
}
})
}
}

View File

@ -0,0 +1,83 @@
package collection
// DeduplicateSliceInPlace 去除切片中的重复元素
func DeduplicateSliceInPlace[S ~[]V, V comparable](s *S) {
if s == nil || len(*s) < 2 {
return
}
var m = make(map[V]struct{}, len(*s))
var writeIndex int
for readIndex, v := range *s {
if _, ok := m[v]; !ok {
(*s)[writeIndex] = (*s)[readIndex]
writeIndex++
m[v] = struct{}{}
}
}
*s = (*s)[:writeIndex]
}
// DeduplicateSlice 去除切片中的重复元素,返回新切片
func DeduplicateSlice[S ~[]V, V comparable](s S) S {
if s == nil || len(s) < 2 {
return any(s).(S)
}
var r = make([]V, 0, len(s))
var m = make(map[V]struct{}, len(s))
for _, v := range s {
if _, ok := m[v]; !ok {
r = append(r, v)
m[v] = struct{}{}
}
}
return r
}
// DeduplicateSliceInPlaceWithCompare 去除切片中的重复元素,使用自定义的比较函数
func DeduplicateSliceInPlaceWithCompare[S ~[]V, V any](s *S, compare func(a, b V) bool) {
if s == nil || len(*s) < 2 {
return
}
seen := make(map[int]struct{})
resultIndex := 0
for i := range *s {
unique := true
for j := range seen {
if compare((*s)[i], (*s)[j]) {
unique = false // Found a duplicate
break
}
}
if unique {
seen[i] = struct{}{}
(*s)[resultIndex] = (*s)[i]
resultIndex++
}
}
*s = (*s)[:resultIndex]
}
// DeduplicateSliceWithCompare 去除切片中的重复元素,使用自定义的比较函数,返回新的切片
func DeduplicateSliceWithCompare[S ~[]V, V any](s S, compare func(a, b V) bool) S {
if s == nil || compare == nil || len(s) < 2 {
return s
}
seen := make(map[int]struct{})
var result = make([]V, 0, len(s))
for i := range s {
unique := true
for j := range result {
if compare(s[i], result[j]) {
unique = false
break
}
}
if unique {
result = append(result, s[i])
seen[i] = struct{}{}
}
}
return result
}

View File

@ -0,0 +1,37 @@
package collection
import "fmt"
func ExampleDeduplicateSliceInPlace() {
slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
DeduplicateSliceInPlace(&slice)
fmt.Println(slice)
// Output:
// [1 2 3 4 5]
}
func ExampleDeduplicateSlice() {
slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
fmt.Println(DeduplicateSlice(slice))
// Output:
// [1 2 3 4 5]
}
func ExampleDeduplicateSliceInPlaceWithCompare() {
slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
DeduplicateSliceInPlaceWithCompare(&slice, func(a, b int) bool {
return a == b
})
fmt.Println(slice)
// Output:
// [1 2 3 4 5]
}
func ExampleDeduplicateSliceWithCompare() {
slice := []int{1, 2, 3, 4, 5, 5, 4, 3, 2, 1}
fmt.Println(DeduplicateSliceWithCompare(slice, func(a, b int) bool {
return a == b
}))
// Output:
// [1 2 3 4 5]
}

View File

@ -0,0 +1,118 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestDeduplicateSliceInPlace(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{name: "TestDeduplicateSliceInPlace_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
{name: "TestDeduplicateSliceInPlace_Empty", input: []int{}, expected: []int{}},
{name: "TestDeduplicateSliceInPlace_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.DeduplicateSliceInPlace(&c.input)
if len(c.input) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
}
for i := 0; i < len(c.input); i++ {
av, ev := c.input[i], c.expected[i]
if av != ev {
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
}
}
})
}
}
func TestDeduplicateSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{name: "TestDeduplicateSlice_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
{name: "TestDeduplicateSlice_Empty", input: []int{}, expected: []int{}},
{name: "TestDeduplicateSlice_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.DeduplicateSlice(c.input)
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for i := 0; i < len(actual); i++ {
av, ev := actual[i], c.expected[i]
if av != ev {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}
func TestDeduplicateSliceInPlaceWithCompare(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{name: "TestDeduplicateSliceInPlaceWithCompare_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
{name: "TestDeduplicateSliceInPlaceWithCompare_Empty", input: []int{}, expected: []int{}},
{name: "TestDeduplicateSliceInPlaceWithCompare_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.DeduplicateSliceInPlaceWithCompare(&c.input, func(a, b int) bool {
return a == b
})
if len(c.input) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
}
for i := 0; i < len(c.input); i++ {
av, ev := c.input[i], c.expected[i]
if av != ev {
t.Errorf("expected: %v, actual: %v", c.expected, c.input)
}
}
})
}
}
func TestDeduplicateSliceWithCompare(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{name: "TestDeduplicateSliceWithCompare_NonEmpty", input: []int{1, 2, 3, 1, 2, 3}, expected: []int{1, 2, 3}},
{name: "TestDeduplicateSliceWithCompare_Empty", input: []int{}, expected: []int{}},
{name: "TestDeduplicateSliceWithCompare_Nil", input: nil, expected: nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.DeduplicateSliceWithCompare(c.input, func(a, b int) bool {
return a == b
})
if len(actual) != len(c.expected) {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
for i := 0; i < len(actual); i++ {
av, ev := actual[i], c.expected[i]
if av != ev {
t.Errorf("expected: %v, actual: %v", c.expected, actual)
}
}
})
}
}

View File

@ -1,17 +1,20 @@
package sher package collection
// FilterOutByIndices 过滤切片中特定索引的元素,返回过滤后的切片 // FilterOutByIndices 过滤切片中特定索引的元素,返回过滤后的切片
func FilterOutByIndices[S ~[]V, V any](slice S, indices ...int) S { func FilterOutByIndices[S []V, V any](slice S, indices ...int) S {
if slice == nil { if slice == nil || len(slice) == 0 || len(indices) == 0 {
return nil
}
if len(indices) == 0 {
return slice return slice
} }
excludeMap := make(map[int]bool) excludeMap := make(map[int]bool)
for _, ex := range indices { for _, ex := range indices {
excludeMap[ex] = true if ex >= 0 && ex < len(slice) {
excludeMap[ex] = true
}
}
if len(excludeMap) == 0 {
return slice
} }
validElements := make([]V, 0, len(slice)-len(excludeMap)) validElements := make([]V, 0, len(slice)-len(excludeMap))
@ -97,7 +100,7 @@ func FilterOutByKeys[M ~map[K]V, K comparable, V any](m M, keys ...K) M {
return validMap return validMap
} }
// FilterOutByValues 过滤 map 中多个 value,返回过滤后的 map // FilterOutByValues 过滤 map 中多个 values,返回过滤后的 map
func FilterOutByValues[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) M { func FilterOutByValues[M ~map[K]V, K comparable, V any](m M, values []V, handler ComparisonHandler[V]) M {
if m == nil { if m == nil {
return nil return nil
@ -128,7 +131,7 @@ func FilterOutByMap[M ~map[K]V, K comparable, V any](m M, condition func(k K, v
validMap := make(M, len(m)) validMap := make(M, len(m))
for k, v := range m { for k, v := range m {
if condition(k, v) { if !condition(k, v) {
validMap[k] = v validMap[k] = v
} }
} }

View File

@ -0,0 +1,74 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
)
func ExampleFilterOutByIndices() {
var slice = []int{1, 2, 3, 4, 5}
var result = collection.FilterOutByIndices(slice, 1, 3)
fmt.Println(result)
// Output:
// [1 3 5]
}
func ExampleFilterOutByCondition() {
var slice = []int{1, 2, 3, 4, 5}
var result = collection.FilterOutByCondition(slice, func(v int) bool {
return v%2 == 0
})
fmt.Println(result)
// Output:
// [1 3 5]
}
func ExampleFilterOutByKey() {
var m = map[string]int{"a": 1, "b": 2, "c": 3}
var result = collection.FilterOutByKey(m, "b")
fmt.Println(result)
// Output:
// map[a:1 c:3]
}
func ExampleFilterOutByValue() {
var m = map[string]int{"a": 1, "b": 2, "c": 3}
var result = collection.FilterOutByValue(m, 2, func(source, target int) bool {
return source == target
})
fmt.Println(len(result))
// Output:
// 2
}
func ExampleFilterOutByKeys() {
var m = map[string]int{"a": 1, "b": 2, "c": 3}
var result = collection.FilterOutByKeys(m, "a", "c")
fmt.Println(result)
// Output:
// map[b:2]
}
func ExampleFilterOutByValues() {
var m = map[string]int{"a": 1, "b": 2, "c": 3}
var result = collection.FilterOutByValues(m, []int{1}, func(source, target int) bool {
return source == target
})
for i, s := range []string{"a", "b", "c"} {
fmt.Println(i, result[s])
}
// Output:
// 0 0
// 1 2
// 2 3
}
func ExampleFilterOutByMap() {
var m = map[string]int{"a": 1, "b": 2, "c": 3}
var result = collection.FilterOutByMap(m, func(k string, v int) bool {
return k == "a" || v == 3
})
fmt.Println(result)
// Output:
// map[b:2]
}

View File

@ -0,0 +1,207 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestFilterOutByIndices(t *testing.T) {
var cases = []struct {
name string
input []int
indices []int
expected []int
}{
{"TestFilterOutByIndices_NonEmptySlice", []int{1, 2, 3, 4, 5}, []int{1, 3}, []int{1, 3, 5}},
{"TestFilterOutByIndices_EmptySlice", []int{}, []int{1, 3}, []int{}},
{"TestFilterOutByIndices_NilSlice", nil, []int{1, 3}, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FilterOutByIndices(c.input, c.indices...)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of input is not equal")
}
for i := 0; i < len(actual); i++ {
if actual[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of input is not equal")
}
}
})
}
}
func TestFilterOutByCondition(t *testing.T) {
var cases = []struct {
name string
input []int
condition func(int) bool
expected []int
}{
{"TestFilterOutByCondition_NonEmptySlice", []int{1, 2, 3, 4, 5}, func(v int) bool {
return v%2 == 0
}, []int{1, 3, 5}},
{"TestFilterOutByCondition_EmptySlice", []int{}, func(v int) bool {
return v%2 == 0
}, []int{}},
{"TestFilterOutByCondition_NilSlice", nil, func(v int) bool {
return v%2 == 0
}, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FilterOutByCondition(c.input, c.condition)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of input is not equal")
}
for i := 0; i < len(actual); i++ {
if actual[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of input is not equal")
}
}
})
}
}
func TestFilterOutByKey(t *testing.T) {
var cases = []struct {
name string
input map[int]int
key int
expected map[int]int
}{
{"TestFilterOutByKey_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, map[int]int{2: 2, 3: 3}},
{"TestFilterOutByKey_EmptyMap", map[int]int{}, 1, map[int]int{}},
{"TestFilterOutByKey_NilMap", nil, 1, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FilterOutByKey(c.input, c.key)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
}
for k, v := range actual {
if v != c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
}
}
})
}
}
func TestFilterOutByValue(t *testing.T) {
var cases = []struct {
name string
input map[int]int
value int
expected map[int]int
}{
{"TestFilterOutByValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, map[int]int{2: 2, 3: 3}},
{"TestFilterOutByValue_EmptyMap", map[int]int{}, 1, map[int]int{}},
{"TestFilterOutByValue_NilMap", nil, 1, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FilterOutByValue(c.input, c.value, func(source, target int) bool {
return source == target
})
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
}
for k, v := range actual {
if v != c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
}
}
})
}
}
func TestFilterOutByKeys(t *testing.T) {
var cases = []struct {
name string
input map[int]int
keys []int
expected map[int]int
}{
{"TestFilterOutByKeys_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, []int{1, 3}, map[int]int{2: 2}},
{"TestFilterOutByKeys_EmptyMap", map[int]int{}, []int{1, 3}, map[int]int{}},
{"TestFilterOutByKeys_NilMap", nil, []int{1, 3}, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FilterOutByKeys(c.input, c.keys...)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
}
for k, v := range actual {
if v != c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
}
}
})
}
}
func TestFilterOutByValues(t *testing.T) {
var cases = []struct {
name string
input map[int]int
values []int
expected map[int]int
}{
{"TestFilterOutByValues_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, []int{1, 3}, map[int]int{2: 2}},
{"TestFilterOutByValues_EmptyMap", map[int]int{}, []int{1, 3}, map[int]int{}},
{"TestFilterOutByValues_NilMap", nil, []int{1, 3}, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FilterOutByValues(c.input, c.values, func(source, target int) bool {
return source == target
})
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
}
for k, v := range actual {
if v != c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
}
}
})
}
}
func TestFilterOutByMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
filter map[int]int
expected map[int]int
}{
{"TestFilterOutByMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 1, 3: 3}, map[int]int{2: 2}},
{"TestFilterOutByMap_EmptyMap", map[int]int{}, map[int]int{1: 1, 3: 3}, map[int]int{}},
{"TestFilterOutByMap_NilMap", nil, map[int]int{1: 1, 3: 3}, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FilterOutByMap(c.input, func(k int, v int) bool {
return c.filter[k] == v
})
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the length of map is not equal")
}
for k, v := range actual {
if v != c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "after filter, the inputV of map is not equal")
}
}
})
}
}

348
utils/collection/find.go Normal file
View File

@ -0,0 +1,348 @@
package collection
import (
"github.com/kercylan98/minotaur/utils/generic"
)
// FindLoopedNextInSlice 返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始
// - 当 i 为负数时将返回第一个元素
func FindLoopedNextInSlice[S ~[]V, V any](slice S, i int) (next int, value V) {
if i < 0 {
return 0, slice[0]
}
next = i + 1
if next == len(slice) {
next = 0
}
return next, slice[next]
}
// FindLoopedPrevInSlice 返回 i 的上一个数组成员,当 i 为 0 时从数组末尾开始
// - 当 i 为负数时将返回最后一个元素
func FindLoopedPrevInSlice[S ~[]V, V any](slice S, i int) (prev int, value V) {
if i < 0 {
return len(slice) - 1, slice[len(slice)-1]
}
prev = i - 1
if prev == -1 {
prev = len(slice) - 1
}
return prev, slice[prev]
}
// FindCombinationsInSliceByRange 获取给定数组的所有组合,且每个组合的成员数量限制在指定范围内
func FindCombinationsInSliceByRange[S ~[]V, V any](s S, minSize, maxSize int) []S {
n := len(s)
if n == 0 || minSize <= 0 || maxSize <= 0 || minSize > maxSize {
return nil
}
var result []S
var currentCombination S
var backtrack func(startIndex int, currentSize int)
backtrack = func(startIndex int, currentSize int) {
if currentSize >= minSize && currentSize <= maxSize {
combination := make(S, len(currentCombination))
copy(combination, currentCombination)
result = append(result, combination)
}
for i := startIndex; i < n; i++ {
currentCombination = append(currentCombination, s[i])
backtrack(i+1, currentSize+1)
currentCombination = currentCombination[:len(currentCombination)-1]
}
}
backtrack(0, 0)
return result
}
// FindFirstOrDefaultInSlice 判断切片中是否存在元素,返回第一个元素,不存在则返回默认值
func FindFirstOrDefaultInSlice[S ~[]V, V any](slice S, defaultValue V) V {
if len(slice) == 0 {
return defaultValue
}
return slice[0]
}
// FindOrDefaultInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值
func FindOrDefaultInSlice[S ~[]V, V any](slice S, defaultValue V, handler func(v V) bool) (t V) {
if len(slice) == 0 {
return defaultValue
}
for _, v := range slice {
if handler(v) {
return v
}
}
return defaultValue
}
// FindOrDefaultInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则返回默认值
func FindOrDefaultInComparableSlice[S ~[]V, V comparable](slice S, v V, defaultValue V) (t V) {
if len(slice) == 0 {
return defaultValue
}
for _, value := range slice {
if value == v {
return value
}
}
return defaultValue
}
// FindInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1
func FindInSlice[S ~[]V, V any](slice S, handler func(v V) bool) (i int, t V) {
if len(slice) == 0 {
return -1, t
}
for i, v := range slice {
if handler(v) {
return i, v
}
}
return -1, t
}
// FindIndexInSlice 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1
func FindIndexInSlice[S ~[]V, V any](slice S, handler func(v V) bool) int {
if len(slice) == 0 {
return -1
}
for i, v := range slice {
if handler(v) {
return i
}
}
return -1
}
// FindInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引和元素,不存在则索引返回 -1
func FindInComparableSlice[S ~[]V, V comparable](slice S, v V) (i int, t V) {
if len(slice) == 0 {
return -1, t
}
for i, value := range slice {
if value == v {
return i, value
}
}
return -1, t
}
// FindIndexInComparableSlice 判断切片中是否存在某个元素,返回第一个匹配的索引,不存在则索引返回 -1
func FindIndexInComparableSlice[S ~[]V, V comparable](slice S, v V) int {
if len(slice) == 0 {
return -1
}
for i, value := range slice {
if value == v {
return i
}
}
return -1
}
// FindMinimumInComparableSlice 获取切片中的最小值
func FindMinimumInComparableSlice[S ~[]V, V generic.Ordered](slice S) (result V) {
if len(slice) == 0 {
return
}
result = slice[0]
for i := 1; i < len(slice); i++ {
if result > slice[i] {
result = slice[i]
}
}
return
}
// FindMinimumInSlice 获取切片中的最小值
func FindMinimumInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (result V) {
if len(slice) == 0 {
return
}
result = slice[0]
for i := 1; i < len(slice); i++ {
if handler(result) > handler(slice[i]) {
result = slice[i]
}
}
return
}
// FindMaximumInComparableSlice 获取切片中的最大值
func FindMaximumInComparableSlice[S ~[]V, V generic.Ordered](slice S) (result V) {
if len(slice) == 0 {
return
}
result = slice[0]
for i := 1; i < len(slice); i++ {
if result < slice[i] {
result = slice[i]
}
}
return
}
// FindMaximumInSlice 获取切片中的最大值
func FindMaximumInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (result V) {
if len(slice) == 0 {
return
}
result = slice[0]
for i := 1; i < len(slice); i++ {
if handler(result) < handler(slice[i]) {
result = slice[i]
}
}
return
}
// FindMin2MaxInComparableSlice 获取切片中的最小值和最大值
func FindMin2MaxInComparableSlice[S ~[]V, V generic.Ordered](slice S) (min, max V) {
if len(slice) == 0 {
return
}
min = slice[0]
max = slice[0]
for i := 1; i < len(slice); i++ {
if min > slice[i] {
min = slice[i]
}
if max < slice[i] {
max = slice[i]
}
}
return
}
// FindMin2MaxInSlice 获取切片中的最小值和最大值
func FindMin2MaxInSlice[S ~[]V, V any, N generic.Ordered](slice S, handler OrderedValueGetter[V, N]) (min, max V) {
if len(slice) == 0 {
return
}
min = slice[0]
max = slice[0]
for i := 1; i < len(slice); i++ {
if handler(min) > handler(slice[i]) {
min = slice[i]
}
if handler(max) < handler(slice[i]) {
max = slice[i]
}
}
return
}
// FindMinFromComparableMap 获取 map 中的最小值
func FindMinFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (result V) {
if m == nil {
return
}
var first bool
for _, v := range m {
if !first {
result = v
first = true
continue
}
if result > v {
result = v
}
}
return
}
// FindMinFromMap 获取 map 中的最小值
func FindMinFromMap[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, handler OrderedValueGetter[V, N]) (result V) {
if m == nil {
return
}
var first bool
for _, v := range m {
if !first {
result = v
first = true
continue
}
if handler(result) > handler(v) {
result = v
}
}
return
}
// FindMaxFromComparableMap 获取 map 中的最大值
func FindMaxFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (result V) {
if m == nil {
return
}
for _, v := range m {
if result < v {
result = v
}
}
return
}
// FindMaxFromMap 获取 map 中的最大值
func FindMaxFromMap[M ~map[K]V, K comparable, V any, N generic.Ordered](m M, handler OrderedValueGetter[V, N]) (result V) {
if m == nil {
return
}
for _, v := range m {
if handler(result) < handler(v) {
result = v
}
}
return
}
// FindMin2MaxFromComparableMap 获取 map 中的最小值和最大值
func FindMin2MaxFromComparableMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (min, max V) {
if m == nil {
return
}
var first bool
for _, v := range m {
if !first {
min = v
max = v
first = true
continue
}
if min > v {
min = v
}
if max < v {
max = v
}
}
return
}
// FindMin2MaxFromMap 获取 map 中的最小值和最大值
func FindMin2MaxFromMap[M ~map[K]V, K comparable, V generic.Ordered](m M) (min, max V) {
if m == nil {
return
}
var first bool
for _, v := range m {
if !first {
min = v
max = v
first = true
continue
}
if min > v {
min = v
}
if max < v {
max = v
}
}
return
}

View File

@ -0,0 +1,176 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
)
func ExampleFindLoopedNextInSlice() {
next, v := collection.FindLoopedNextInSlice([]int{1, 2, 3}, 1)
fmt.Println(next, v)
// Output:
// 2 3
}
func ExampleFindLoopedPrevInSlice() {
prev, v := collection.FindLoopedPrevInSlice([]int{1, 2, 3}, 1)
fmt.Println(prev, v)
// Output:
// 0 1
}
func ExampleFindCombinationsInSliceByRange() {
result := collection.FindCombinationsInSliceByRange([]int{1, 2, 3}, 1, 2)
fmt.Println(len(result))
// Output:
// 6
}
func ExampleFindFirstOrDefaultInSlice() {
result := collection.FindFirstOrDefaultInSlice([]int{1, 2, 3}, 0)
fmt.Println(result)
// Output:
// 1
}
func ExampleFindOrDefaultInSlice() {
result := collection.FindOrDefaultInSlice([]int{1, 2, 3}, 0, func(v int) bool {
return v == 2
})
fmt.Println(result)
// Output:
// 2
}
func ExampleFindOrDefaultInComparableSlice() {
result := collection.FindOrDefaultInComparableSlice([]int{1, 2, 3}, 2, 0)
fmt.Println(result)
// Output:
// 2
}
func ExampleFindInSlice() {
_, result := collection.FindInSlice([]int{1, 2, 3}, func(v int) bool {
return v == 2
})
fmt.Println(result)
// Output:
// 2
}
func ExampleFindIndexInSlice() {
result := collection.FindIndexInSlice([]int{1, 2, 3}, func(v int) bool {
return v == 2
})
fmt.Println(result)
// Output:
// 1
}
func ExampleFindInComparableSlice() {
index, result := collection.FindInComparableSlice([]int{1, 2, 3}, 2)
fmt.Println(index, result)
// Output:
// 1 2
}
func ExampleFindIndexInComparableSlice() {
result := collection.FindIndexInComparableSlice([]int{1, 2, 3}, 2)
fmt.Println(result)
// Output:
// 1
}
func ExampleFindMinimumInComparableSlice() {
result := collection.FindMinimumInComparableSlice([]int{1, 2, 3})
fmt.Println(result)
// Output:
// 1
}
func ExampleFindMinimumInSlice() {
result := collection.FindMinimumInSlice([]int{1, 2, 3}, func(v int) int {
return v
})
fmt.Println(result)
// Output:
// 1
}
func ExampleFindMaximumInComparableSlice() {
result := collection.FindMaximumInComparableSlice([]int{1, 2, 3})
fmt.Println(result)
// Output:
// 3
}
func ExampleFindMaximumInSlice() {
result := collection.FindMaximumInSlice([]int{1, 2, 3}, func(v int) int {
return v
})
fmt.Println(result)
// Output:
// 3
}
func ExampleFindMin2MaxInComparableSlice() {
minimum, maximum := collection.FindMin2MaxInComparableSlice([]int{1, 2, 3})
fmt.Println(minimum, maximum)
// Output:
// 1 3
}
func ExampleFindMin2MaxInSlice() {
minimum, maximum := collection.FindMin2MaxInSlice([]int{1, 2, 3}, func(v int) int {
return v
})
fmt.Println(minimum, maximum)
// Output:
// 1 3
}
func ExampleFindMinFromComparableMap() {
result := collection.FindMinFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
fmt.Println(result)
// Output:
// 1
}
func ExampleFindMinFromMap() {
result := collection.FindMinFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(v int) int {
return v
})
fmt.Println(result)
// Output:
// 1
}
func ExampleFindMaxFromComparableMap() {
result := collection.FindMaxFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
fmt.Println(result)
// Output:
// 3
}
func ExampleFindMaxFromMap() {
result := collection.FindMaxFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(v int) int {
return v
})
fmt.Println(result)
// Output:
// 3
}
func ExampleFindMin2MaxFromComparableMap() {
minimum, maximum := collection.FindMin2MaxFromComparableMap(map[int]int{1: 1, 2: 2, 3: 3})
fmt.Println(minimum, maximum)
// Output:
// 1 3
}
func ExampleFindMin2MaxFromMap() {
minimum, maximum := collection.FindMin2MaxFromMap(map[int]int{1: 1, 2: 2, 3: 3})
fmt.Println(minimum, maximum)
// Output:
// 1 3
}

View File

@ -0,0 +1,467 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestFindLoopedNextInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
i int
expected int
}{
{"TestFindLoopedNextInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 2},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, _ := collection.FindLoopedNextInSlice(c.input, c.i)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the next index of input is not equal")
}
})
}
}
func TestFindLoopedPrevInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
i int
expected int
}{
{"TestFindLoopedPrevInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual, _ := collection.FindLoopedPrevInSlice(c.input, c.i)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the prev index of input is not equal")
}
})
}
}
func TestFindCombinationsInSliceByRange(t *testing.T) {
var cases = []struct {
name string
input []int
minSize int
maxSize int
expected [][]int
}{
{"TestFindCombinationsInSliceByRange_NonEmptySlice", []int{1, 2, 3}, 1, 2, [][]int{{1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindCombinationsInSliceByRange(c.input, c.minSize, c.maxSize)
if len(actual) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the length of input is not equal")
}
})
}
}
func TestFindFirstOrDefaultInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindFirstOrDefaultInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
{"TestFindFirstOrDefaultInSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindFirstOrDefaultInSlice(c.input, 0)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the first element of input is not equal")
}
})
}
}
func TestFindOrDefaultInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindOrDefaultInSlice_NonEmptySlice", []int{1, 2, 3}, 2},
{"TestFindOrDefaultInSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindOrDefaultInSlice(c.input, 0, func(v int) bool {
return v == 2
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
}
})
}
}
func TestFindOrDefaultInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindOrDefaultInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 2},
{"TestFindOrDefaultInComparableSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindOrDefaultInComparableSlice(c.input, 2, 0)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
}
})
}
}
func TestFindInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindInSlice_NonEmptySlice", []int{1, 2, 3}, 2},
{"TestFindInSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
_, actual := collection.FindInSlice(c.input, func(v int) bool {
return v == 2
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
}
})
}
}
func TestFindIndexInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindIndexInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
{"TestFindIndexInSlice_EmptySlice", []int{}, -1},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindIndexInSlice(c.input, func(v int) bool {
return v == 2
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the index of input is not equal")
}
})
}
}
func TestFindInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 2},
{"TestFindInComparableSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
_, actual := collection.FindInComparableSlice(c.input, 2)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the element of input is not equal")
}
})
}
}
func TestFindIndexInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindIndexInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1},
{"TestFindIndexInComparableSlice_EmptySlice", []int{}, -1},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindIndexInComparableSlice(c.input, 2)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the index of input is not equal")
}
})
}
}
func TestFindMinimumInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindMinimumInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1},
{"TestFindMinimumInComparableSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindMinimumInComparableSlice(c.input)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
}
})
}
}
func TestFindMinimumInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindMinimumInSlice_NonEmptySlice", []int{1, 2, 3}, 1},
{"TestFindMinimumInSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindMinimumInSlice(c.input, func(v int) int {
return v
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
}
})
}
}
func TestFindMaximumInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindMaximumInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 3},
{"TestFindMaximumInComparableSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindMaximumInComparableSlice(c.input)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
}
})
}
}
func TestFindMaximumInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestFindMaximumInSlice_NonEmptySlice", []int{1, 2, 3}, 3},
{"TestFindMaximumInSlice_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindMaximumInSlice(c.input, func(v int) int {
return v
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
}
})
}
}
func TestFindMin2MaxInComparableSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expectedMin int
expectedMax int
}{
{"TestFindMin2MaxInComparableSlice_NonEmptySlice", []int{1, 2, 3}, 1, 3},
{"TestFindMin2MaxInComparableSlice_EmptySlice", []int{}, 0, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
minimum, maximum := collection.FindMin2MaxInComparableSlice(c.input)
if minimum != c.expectedMin || maximum != c.expectedMax {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
}
})
}
}
func TestFindMin2MaxInSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expectedMin int
expectedMax int
}{
{"TestFindMin2MaxInSlice_NonEmptySlice", []int{1, 2, 3}, 1, 3},
{"TestFindMin2MaxInSlice_EmptySlice", []int{}, 0, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
minimum, maximum := collection.FindMin2MaxInSlice(c.input, func(v int) int {
return v
})
if minimum != c.expectedMin || maximum != c.expectedMax {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
}
})
}
}
func TestFindMinFromComparableMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected int
}{
{"TestFindMinFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1},
{"TestFindMinFromComparableMap_EmptyMap", map[int]int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindMinFromComparableMap(c.input)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
}
})
}
}
func TestFindMinFromMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected int
}{
{"TestFindMinFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1},
{"TestFindMinFromMap_EmptyMap", map[int]int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindMinFromMap(c.input, func(v int) int {
return v
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the minimum of input is not equal")
}
})
}
}
func TestFindMaxFromComparableMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected int
}{
{"TestFindMaxFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 3},
{"TestFindMaxFromComparableMap_EmptyMap", map[int]int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindMaxFromComparableMap(c.input)
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
}
})
}
}
func TestFindMaxFromMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected int
}{
{"TestFindMaxFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 3},
{"TestFindMaxFromMap_EmptyMap", map[int]int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.FindMaxFromMap(c.input, func(v int) int {
return v
})
if actual != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, actual, "the maximum of input is not equal")
}
})
}
}
func TestFindMin2MaxFromComparableMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expectedMin int
expectedMax int
}{
{"TestFindMin2MaxFromComparableMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, 3},
{"TestFindMin2MaxFromComparableMap_EmptyMap", map[int]int{}, 0, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
minimum, maximum := collection.FindMin2MaxFromComparableMap(c.input)
if minimum != c.expectedMin || maximum != c.expectedMax {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
}
})
}
}
func TestFindMin2MaxFromMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expectedMin int
expectedMax int
}{
{"TestFindMin2MaxFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, 1, 3},
{"TestFindMin2MaxFromMap_EmptyMap", map[int]int{}, 0, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
minimum, maximum := collection.FindMin2MaxFromMap(c.input)
if minimum != c.expectedMin || maximum != c.expectedMax {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expectedMin, minimum, "the minimum of input is not equal")
}
})
}
}

9
utils/collection/item.go Normal file
View File

@ -0,0 +1,9 @@
package collection
// SwapSlice 将切片中的两个元素进行交换
func SwapSlice[S ~[]V, V any](slice *S, i, j int) {
if i < 0 || j < 0 || i >= len(*slice) || j >= len(*slice) {
return
}
(*slice)[i], (*slice)[j] = (*slice)[j], (*slice)[i]
}

View File

@ -0,0 +1,14 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
)
func ExampleSwapSlice() {
var s = []int{1, 2, 3}
collection.SwapSlice(&s, 0, 1)
fmt.Println(s)
// Output:
// [2 1 3]
}

View File

@ -0,0 +1,32 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestSwapSlice(t *testing.T) {
var cases = []struct {
name string
slice []int
i int
j int
expect []int
}{
{"TestSwapSliceNonEmpty", []int{1, 2, 3}, 0, 1, []int{2, 1, 3}},
{"TestSwapSliceEmpty", []int{}, 0, 0, []int{}},
{"TestSwapSliceIndexOutOfBound", []int{1, 2, 3}, 0, 3, []int{1, 2, 3}},
{"TestSwapSliceNil", nil, 0, 0, nil},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.SwapSlice(&c.slice, c.i, c.j)
for i, v := range c.slice {
if v != c.expect[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expect, c.slice, "the slice is not equal")
}
}
})
}
}

View File

@ -0,0 +1,60 @@
package listings
// NewMatrix 创建一个新的 Matrix 实例。
func NewMatrix[V any](dimensions ...int) *Matrix[V] {
total := 1
for _, dim := range dimensions {
total *= dim
}
return &Matrix[V]{
dimensions: dimensions,
data: make([]V, total),
}
}
type Matrix[V any] struct {
dimensions []int // 维度大小的切片
data []V // 存储矩阵数据的一维切片
}
// Get 获取矩阵中给定索引的元素。
func (slf *Matrix[V]) Get(index ...int) *V {
if len(index) != len(slf.dimensions) {
return nil
}
var offset = 0
for i, dim := range slf.dimensions {
if index[i] < 0 || index[i] >= dim {
return nil
}
offset = offset*dim + index[i]
}
return &slf.data[offset]
}
// Set 设置矩阵中给定索引的元素。
func (slf *Matrix[V]) Set(index []int, value V) {
if len(index) != len(slf.dimensions) {
return
}
var offset = 0
for i, dim := range slf.dimensions {
if index[i] < 0 || index[i] >= dim {
return
}
offset = offset*dim + index[i]
}
slf.data[offset] = value
}
// Dimensions 返回矩阵的维度大小。
func (slf *Matrix[V]) Dimensions() []int {
return slf.dimensions
}
// Clear 清空矩阵。
func (slf *Matrix[V]) Clear() {
slf.data = make([]V, len(slf.data))
}

View File

@ -1,4 +1,4 @@
package slice package listings
// NewPagedSlice 创建一个新的 PagedSlice 实例。 // NewPagedSlice 创建一个新的 PagedSlice 实例。
func NewPagedSlice[T any](pageSize int) *PagedSlice[T] { func NewPagedSlice[T any](pageSize int) *PagedSlice[T] {

View File

@ -0,0 +1,198 @@
package listings
import (
"fmt"
"sort"
)
// NewPrioritySlice 创建一个优先级切片
func NewPrioritySlice[V any](lengthAndCap ...int) *PrioritySlice[V] {
p := &PrioritySlice[V]{}
if len(lengthAndCap) > 0 {
var length = lengthAndCap[0]
var c int
if len(lengthAndCap) > 1 {
c = lengthAndCap[1]
}
p.items = make([]*priorityItem[V], length, c)
}
return p
}
// PrioritySlice 是一个优先级切片
type PrioritySlice[V any] struct {
items []*priorityItem[V]
}
// Len 返回切片长度
func (slf *PrioritySlice[V]) Len() int {
return len(slf.items)
}
// Cap 返回切片容量
func (slf *PrioritySlice[V]) Cap() int {
return cap(slf.items)
}
// Clear 清空切片
func (slf *PrioritySlice[V]) Clear() {
slf.items = slf.items[:0]
}
// Append 添加元素
func (slf *PrioritySlice[V]) Append(v V, p int) {
slf.items = append(slf.items, &priorityItem[V]{
v: v,
p: p,
})
slf.sort()
}
// Appends 添加元素
func (slf *PrioritySlice[V]) Appends(priority int, vs ...V) {
for _, v := range vs {
slf.Append(v, priority)
}
slf.sort()
}
// Get 获取元素
func (slf *PrioritySlice[V]) Get(index int) (V, int) {
i := slf.items[index]
return i.Value(), i.Priority()
}
// GetValue 获取元素值
func (slf *PrioritySlice[V]) GetValue(index int) V {
return slf.items[index].Value()
}
// GetPriority 获取元素优先级
func (slf *PrioritySlice[V]) GetPriority(index int) int {
return slf.items[index].Priority()
}
// Set 设置元素
func (slf *PrioritySlice[V]) Set(index int, value V, priority int) {
before := slf.items[index]
slf.items[index] = &priorityItem[V]{
v: value,
p: priority,
}
if before.Priority() != priority {
slf.sort()
}
}
// SetValue 设置元素值
func (slf *PrioritySlice[V]) SetValue(index int, value V) {
slf.items[index].v = value
}
// SetPriority 设置元素优先级
func (slf *PrioritySlice[V]) SetPriority(index int, priority int) {
slf.items[index].p = priority
slf.sort()
}
// Action 直接操作切片,如果返回值不为 nil则替换切片
func (slf *PrioritySlice[V]) Action(action func(items []*priorityItem[V]) []*priorityItem[V]) {
if len(slf.items) == 0 {
return
}
if replace := action(slf.items); replace != nil {
slf.items = replace
slf.sort()
}
}
// Range 遍历切片,如果返回值为 false则停止遍历
func (slf *PrioritySlice[V]) Range(action func(index int, item *priorityItem[V]) bool) {
for i, item := range slf.items {
if !action(i, item) {
break
}
}
}
// RangeValue 遍历切片值,如果返回值为 false则停止遍历
func (slf *PrioritySlice[V]) RangeValue(action func(index int, value V) bool) {
slf.Range(func(index int, item *priorityItem[V]) bool {
return action(index, item.Value())
})
}
// RangePriority 遍历切片优先级,如果返回值为 false则停止遍历
func (slf *PrioritySlice[V]) RangePriority(action func(index int, priority int) bool) {
slf.Range(func(index int, item *priorityItem[V]) bool {
return action(index, item.Priority())
})
}
// SyncSlice 返回切片
func (slf *PrioritySlice[V]) Slice() []V {
var vs []V
for _, item := range slf.items {
vs = append(vs, item.Value())
}
return vs
}
// String 返回切片字符串
func (slf *PrioritySlice[V]) String() string {
var vs []V
for _, item := range slf.items {
vs = append(vs, item.Value())
}
return fmt.Sprint(vs)
}
// sort 排序
func (slf *PrioritySlice[V]) sort() {
if len(slf.items) <= 1 {
return
}
sort.Slice(slf.items, func(i, j int) bool {
return slf.items[i].Priority() < slf.items[j].Priority()
})
for i := 0; i < len(slf.items); i++ {
if i == 0 {
slf.items[i].prev = nil
slf.items[i].next = slf.items[i+1]
} else if i == len(slf.items)-1 {
slf.items[i].prev = slf.items[i-1]
slf.items[i].next = nil
} else {
slf.items[i].prev = slf.items[i-1]
slf.items[i].next = slf.items[i+1]
}
}
}
// priorityItem 是一个优先级切片元素
type priorityItem[V any] struct {
next *priorityItem[V]
prev *priorityItem[V]
v V
p int
}
// Value 返回元素值
func (p *priorityItem[V]) Value() V {
return p.v
}
// Priority 返回元素优先级
func (p *priorityItem[V]) Priority() int {
return p.p
}
// Next 返回下一个元素
func (p *priorityItem[V]) Next() *priorityItem[V] {
return p.next
}
// Prev 返回上一个元素
func (p *priorityItem[V]) Prev() *priorityItem[V] {
return p.prev
}

View File

@ -0,0 +1,14 @@
package listings_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection/listings"
"testing"
)
func TestPrioritySlice_Append(t *testing.T) {
var s = listings.NewPrioritySlice[string]()
s.Append("name_1", 2)
s.Append("name_2", 1)
fmt.Println(s)
}

View File

@ -0,0 +1,61 @@
package listings
import (
"github.com/kercylan98/minotaur/utils/collection"
"sync"
)
// NewSyncSlice 创建一个 SyncSlice
func NewSyncSlice[V any](length, cap int) *SyncSlice[V] {
s := &SyncSlice[V]{}
if length > 0 || cap > 0 {
s.data = make([]V, length, cap)
}
return s
}
// SyncSlice 是基于 sync.RWMutex 实现的线程安全的 slice
type SyncSlice[V any] struct {
rw sync.RWMutex
data []V
}
func (slf *SyncSlice[V]) Get(index int) V {
slf.rw.RLock()
defer slf.rw.RUnlock()
return slf.data[index]
}
func (slf *SyncSlice[V]) GetWithRange(start, end int) []V {
return slf.data[start:end]
}
func (slf *SyncSlice[V]) Set(index int, value V) {
slf.rw.Lock()
slf.data[index] = value
slf.rw.Unlock()
}
func (slf *SyncSlice[V]) Append(values ...V) {
slf.rw.Lock()
slf.data = append(slf.data, values...)
slf.rw.Unlock()
}
func (slf *SyncSlice[V]) Release() {
slf.rw.Lock()
slf.data = nil
slf.rw.Unlock()
}
func (slf *SyncSlice[V]) Clear() {
slf.rw.Lock()
slf.data = slf.data[:0]
slf.rw.Unlock()
}
func (slf *SyncSlice[V]) GetData() []V {
slf.rw.Lock()
defer slf.rw.Unlock()
return collection.CloneSlice(slf.data)
}

View File

@ -1,7 +1,7 @@
package sher package collection
// MappingFromSlice 将切片中的元素进行转换 // MappingFromSlice 将切片中的元素进行转换
func MappingFromSlice[S ~[]V, NS ~[]N, V, N any](slice S, handler func(value V) N) NS { func MappingFromSlice[S ~[]V, NS []N, V, N any](slice S, handler func(value V) N) NS {
if slice == nil { if slice == nil {
return nil return nil
} }
@ -13,7 +13,7 @@ func MappingFromSlice[S ~[]V, NS ~[]N, V, N any](slice S, handler func(value V)
} }
// MappingFromMap 将 map 中的元素进行转换 // MappingFromMap 将 map 中的元素进行转换
func MappingFromMap[M ~map[K]V, NM ~map[K]N, K comparable, V, N any](m M, handler func(value V) N) NM { func MappingFromMap[M ~map[K]V, NM map[K]N, K comparable, V, N any](m M, handler func(value V) N) NM {
if m == nil { if m == nil {
return nil return nil
} }

View File

@ -0,0 +1,24 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
)
func ExampleMappingFromSlice() {
result := collection.MappingFromSlice([]int{1, 2, 3}, func(value int) int {
return value + 1
})
fmt.Println(result)
// Output:
// [2 3 4]
}
func ExampleMappingFromMap() {
result := collection.MappingFromMap(map[int]int{1: 1, 2: 2, 3: 3}, func(value int) int {
return value + 1
})
fmt.Println(result)
// Output:
// map[1:2 2:3 3:4]
}

View File

@ -0,0 +1,60 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestMappingFromSlice(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestMappingFromSlice_NonEmptySlice", []int{1, 2, 3}, []int{2, 3, 4}},
{"TestMappingFromSlice_EmptySlice", []int{}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.MappingFromSlice[[]int, []int](c.input, func(value int) int {
return value + 1
})
if len(result) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
for i := 0; i < len(result); i++ {
if result[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal")
}
}
})
}
}
func TestMappingFromMap(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]int
}{
{"TestMappingFromMap_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]int{1: 2, 2: 3, 3: 4}},
{"TestMappingFromMap_EmptyMap", map[int]int{}, map[int]int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.MappingFromMap[map[int]int, map[int]int](c.input, func(value int) int {
return value + 1
})
if len(result) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
for k, v := range result {
if v != c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal")
}
}
})
}
}

View File

@ -0,0 +1,221 @@
package mappings
import (
"encoding/json"
"github.com/kercylan98/minotaur/utils/collection"
"sync"
)
// NewSyncMap 创建一个 SyncMap
func NewSyncMap[K comparable, V any](source ...map[K]V) *SyncMap[K, V] {
m := &SyncMap[K, V]{
data: make(map[K]V),
}
if len(source) > 0 {
m.data = collection.MergeMaps(source...)
}
return m
}
// SyncMap 是基于 sync.RWMutex 实现的线程安全的 map
// - 适用于要考虑并发读写但是并发读写的频率不高的情况
type SyncMap[K comparable, V any] struct {
lock sync.RWMutex
data map[K]V
atom bool
}
// Set 设置一个值
func (sm *SyncMap[K, V]) Set(key K, value V) {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
sm.data[key] = value
}
// Get 获取一个值
func (sm *SyncMap[K, V]) Get(key K) V {
if !sm.atom {
sm.lock.RLock()
defer sm.lock.RUnlock()
}
return sm.data[key]
}
// Atom 原子操作
func (sm *SyncMap[K, V]) Atom(handle func(m map[K]V)) {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
handle(sm.data)
}
// Exist 判断是否存在
func (sm *SyncMap[K, V]) Exist(key K) bool {
if !sm.atom {
sm.lock.RLock()
defer sm.lock.RUnlock()
}
_, exist := sm.data[key]
return exist
}
// GetExist 获取一个值并判断是否存在
func (sm *SyncMap[K, V]) GetExist(key K) (V, bool) {
if !sm.atom {
sm.lock.RLock()
defer sm.lock.RUnlock()
}
value, exist := sm.data[key]
return value, exist
}
// Delete 删除一个值
func (sm *SyncMap[K, V]) Delete(key K) {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
delete(sm.data, key)
}
// DeleteGet 删除一个值并返回
func (sm *SyncMap[K, V]) DeleteGet(key K) V {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
v := sm.data[key]
delete(sm.data, key)
return v
}
// DeleteGetExist 删除一个值并返回是否存在
func (sm *SyncMap[K, V]) DeleteGetExist(key K) (V, bool) {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
v, exist := sm.data[key]
delete(sm.data, key)
return v, exist
}
// DeleteExist 删除一个值并返回是否存在
func (sm *SyncMap[K, V]) DeleteExist(key K) bool {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
if _, exist := sm.data[key]; !exist {
sm.lock.Unlock()
return exist
}
delete(sm.data, key)
return true
}
// Clear 清空
func (sm *SyncMap[K, V]) Clear() {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
for k := range sm.data {
delete(sm.data, k)
}
}
// ClearHandle 清空并处理
func (sm *SyncMap[K, V]) ClearHandle(handle func(key K, value V)) {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
for k, v := range sm.data {
handle(k, v)
delete(sm.data, k)
}
}
// Range 遍历所有值,如果 handle 返回 true 则停止遍历
func (sm *SyncMap[K, V]) Range(handle func(key K, value V) bool) {
if !sm.atom {
sm.lock.Lock()
defer sm.lock.Unlock()
}
for k, v := range sm.data {
key, value := k, v
if handle(key, value) {
break
}
}
}
// Keys 获取所有的键
func (sm *SyncMap[K, V]) Keys() []K {
if !sm.atom {
sm.lock.RLock()
defer sm.lock.RUnlock()
}
var s = make([]K, 0, len(sm.data))
for k := range sm.data {
s = append(s, k)
}
return s
}
// Slice 获取所有的值
func (sm *SyncMap[K, V]) Slice() []V {
if !sm.atom {
sm.lock.RLock()
defer sm.lock.RUnlock()
}
var s = make([]V, 0, len(sm.data))
for _, v := range sm.data {
s = append(s, v)
}
return s
}
// Map 转换为普通 map
func (sm *SyncMap[K, V]) Map() map[K]V {
if !sm.atom {
sm.lock.RLock()
defer sm.lock.RUnlock()
}
var m = make(map[K]V)
for k, v := range sm.data {
m[k] = v
}
return m
}
// Size 获取数量
func (sm *SyncMap[K, V]) Size() int {
if !sm.atom {
sm.lock.RLock()
defer sm.lock.RUnlock()
}
return len(sm.data)
}
func (sm *SyncMap[K, V]) MarshalJSON() ([]byte, error) {
m := sm.Map()
return json.Marshal(m)
}
func (sm *SyncMap[K, V]) UnmarshalJSON(bytes []byte) error {
var m = make(map[K]V)
if !sm.atom {
sm.lock.Lock()
sm.lock.Unlock()
}
if err := json.Unmarshal(bytes, &m); err != nil {
return err
}
sm.data = m
return nil
}

View File

@ -1,4 +1,4 @@
package sher package collection
// MergeSlices 合并切片 // MergeSlices 合并切片
func MergeSlices[S ~[]V, V any](slices ...S) (result S) { func MergeSlices[S ~[]V, V any](slices ...S) (result S) {
@ -21,7 +21,7 @@ func MergeSlices[S ~[]V, V any](slices ...S) (result S) {
// MergeMaps 合并 map当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会覆盖前面的 map 中的 key // MergeMaps 合并 map当多个 map 中存在相同的 key 时,后面的 map 中的 key 将会覆盖前面的 map 中的 key
func MergeMaps[M ~map[K]V, K comparable, V any](maps ...M) (result M) { func MergeMaps[M ~map[K]V, K comparable, V any](maps ...M) (result M) {
if len(maps) == 0 { if len(maps) == 0 {
return nil return make(M)
} }
var length int var length int

View File

@ -0,0 +1,37 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
)
func ExampleMergeSlices() {
fmt.Println(
collection.MergeSlices(
[]int{1, 2, 3},
[]int{4, 5, 6},
),
)
// Output:
// [1 2 3 4 5 6]
}
func ExampleMergeMaps() {
m := collection.MergeMaps(
map[int]int{1: 1, 2: 2, 3: 3},
map[int]int{4: 4, 5: 5, 6: 6},
)
fmt.Println(len(m))
// Output:
// 6
}
func ExampleMergeMapsWithSkip() {
m := collection.MergeMapsWithSkip(
map[int]int{1: 1},
map[int]int{1: 2},
)
fmt.Println(m[1])
// Output:
// 1
}

View File

@ -0,0 +1,71 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestMergeSlices(t *testing.T) {
var cases = []struct {
name string
input [][]int
expected []int
}{
{"TestMergeSlices_NonEmptySlice", [][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2, 3, 4, 5, 6}},
{"TestMergeSlices_EmptySlice", [][]int{{}, {}}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.MergeSlices(c.input...)
if len(result) != len(c.expected) {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
for i := 0; i < len(result); i++ {
if result[i] != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the value of input is not equal")
}
}
})
}
}
func TestMergeMaps(t *testing.T) {
var cases = []struct {
name string
input []map[int]int
expected int
}{
{"TestMergeMaps_NonEmptyMap", []map[int]int{{1: 1, 2: 2, 3: 3}, {4: 4, 5: 5, 6: 6}}, 6},
{"TestMergeMaps_EmptyMap", []map[int]int{{}, {}}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.MergeMaps(c.input...)
if len(result) != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestMergeMapsWithSkip(t *testing.T) {
var cases = []struct {
name string
input []map[int]int
expected int
}{
{"TestMergeMapsWithSkip_NonEmptyMap", []map[int]int{{1: 1}, {1: 2}}, 1},
{"TestMergeMapsWithSkip_EmptyMap", []map[int]int{{}, {}}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.MergeMapsWithSkip(c.input...)
if len(result) != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}

View File

@ -1,70 +1,67 @@
package sher package collection
import ( import (
"fmt" "fmt"
"github.com/kercylan98/minotaur/utils/random" "github.com/kercylan98/minotaur/utils/random"
) )
// ChooseRandomSliceElementRepeatN 获取切片中的 n 个随机元素,允许重复 // ChooseRandomSliceElementRepeatN 返回 slice 中的 n 个可重复随机元素
// - 如果 n 大于切片长度或小于 0 时将会发生 panic // - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil
func ChooseRandomSliceElementRepeatN[S ~[]V, V any](slice S, n int) (result []V) { func ChooseRandomSliceElementRepeatN[S ~[]V, V any](slice S, n int) (result []V) {
if slice == nil { if len(slice) == 0 || n <= 0 {
return return
} }
if n > len(slice) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the slice or less than 0, n: %d, length: %d", n, len(slice)))
}
result = make([]V, n) result = make([]V, n)
m := len(slice) - 1
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
result[i] = slice[random.Int(0, len(slice)-1)] result[i] = slice[random.Int(0, m)]
} }
return return
} }
// ChooseRandomIndexRepeatN 获取切片中的 n 个随机元素的索引,允许重复 // ChooseRandomIndexRepeatN 返回 slice 中的 n 个可重复随机元素的索引
// - 如果 n 大于切片长度或小于 0 时将会发生 panic // - 当 slice 长度为 0 或 n 小于等于 0 时将会返回 nil
func ChooseRandomIndexRepeatN[S ~[]V, V any](slice S, n int) (result []int) { func ChooseRandomIndexRepeatN[S ~[]V, V any](slice S, n int) (result []int) {
if slice == nil { if len(slice) == 0 || n <= 0 {
return return
} }
if n > len(slice) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the slice or less than 0, n: %d, length: %d", n, len(slice)))
}
result = make([]int, n) result = make([]int, n)
m := len(slice) - 1
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
result[i] = random.Int(0, len(slice)-1) result[i] = random.Int(0, m)
} }
return return
} }
// ChooseRandomSliceElement 获取切片中的随机元素 // ChooseRandomSliceElement 返回 slice 中随机一个元素,当 slice 长度为 0 时将会得到 V 的零值
func ChooseRandomSliceElement[S ~[]V, V any](slice S) (v V) { func ChooseRandomSliceElement[S ~[]V, V any](slice S) (v V) {
if slice == nil { if len(slice) == 0 {
return return
} }
return slice[random.Int(0, len(slice)-1)] return slice[random.Int(0, len(slice)-1)]
} }
// ChooseRandomIndex 获取切片中的随机元素的索引 // ChooseRandomIndex 返回 slice 中随机一个元素的索引,当 slice 长度为 0 时将会得到 -1
func ChooseRandomIndex[S ~[]V, V any](slice S) (index int) { func ChooseRandomIndex[S ~[]V, V any](slice S) (index int) {
if slice == nil { if len(slice) == 0 {
return return -1
} }
return random.Int(0, len(slice)-1) return random.Int(0, len(slice)-1)
} }
// ChooseRandomSliceElementN 获取切片中的 n 个随机元素 // ChooseRandomSliceElementN 返回 slice 中的 n 个不可重复的随机元素
// - 如果 n 大于切片长度或小于 0 时将会发生 panic // - 当 slice 长度为 0 或 n 大于 slice 长度或小于 0 时将会发生 panic
func ChooseRandomSliceElementN[S ~[]V, V any](slice S, n int) (result []V) { func ChooseRandomSliceElementN[S ~[]V, V any](slice S, n int) (result []V) {
if slice == nil { if len(slice) == 0 || n <= 0 || n > len(slice) {
return
}
if n > len(slice) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the slice or less than 0, n: %d, length: %d", n, len(slice))) panic(fmt.Errorf("n is greater than the length of the slice or less than 0, n: %d, length: %d", n, len(slice)))
} }
result = make([]V, n) result = make([]V, 0, n)
for i := 0; i < n; i++ { valid := ConvertSliceToIndexOnlyMap(slice)
result[i] = slice[random.Int(0, len(slice)-1)] for i := range valid {
result = append(result, slice[i])
if len(result) == n {
break
}
} }
return return
} }
@ -72,11 +69,11 @@ func ChooseRandomSliceElementN[S ~[]V, V any](slice S, n int) (result []V) {
// ChooseRandomIndexN 获取切片中的 n 个随机元素的索引 // ChooseRandomIndexN 获取切片中的 n 个随机元素的索引
// - 如果 n 大于切片长度或小于 0 时将会发生 panic // - 如果 n 大于切片长度或小于 0 时将会发生 panic
func ChooseRandomIndexN[S ~[]V, V any](slice S, n int) (result []int) { func ChooseRandomIndexN[S ~[]V, V any](slice S, n int) (result []int) {
if slice == nil { if len(slice) == 0 {
return return
} }
if n > len(slice) || n < 0 { if n > len(slice) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the slice or less than 0, n: %d, length: %d", n, len(slice))) panic(fmt.Errorf("inputN is greater than the length of the input or less than 0, inputN: %d, length: %d", n, len(slice)))
} }
result = make([]int, n) result = make([]int, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -92,7 +89,7 @@ func ChooseRandomMapKeyRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (res
return return
} }
if n > len(m) || n < 0 { if n > len(m) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the map or less than 0, n: %d, length: %d", n, len(m))) panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
} }
result = make([]K, n) result = make([]K, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -104,14 +101,14 @@ func ChooseRandomMapKeyRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (res
return return
} }
// ChooseRandomMapValueRepeatN 获取 map 中的 n 个随机 value,允许重复 // ChooseRandomMapValueRepeatN 获取 map 中的 n 个随机 n,允许重复
// - 如果 n 大于 map 长度或小于 0 时将会发生 panic // - 如果 n 大于 map 长度或小于 0 时将会发生 panic
func ChooseRandomMapValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (result []V) { func ChooseRandomMapValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (result []V) {
if m == nil { if m == nil {
return return
} }
if n > len(m) || n < 0 { if n > len(m) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the map or less than 0, n: %d, length: %d", n, len(m))) panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
} }
result = make([]V, n) result = make([]V, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -123,14 +120,14 @@ func ChooseRandomMapValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) (r
return return
} }
// ChooseRandomMapKeyAndValueRepeatN 获取 map 中的 n 个随机 key 和 value,允许重复 // ChooseRandomMapKeyAndValueRepeatN 获取 map 中的 n 个随机 key 和 v,允许重复
// - 如果 n 大于 map 长度或小于 0 时将会发生 panic // - 如果 n 大于 map 长度或小于 0 时将会发生 panic
func ChooseRandomMapKeyAndValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) M { func ChooseRandomMapKeyAndValueRepeatN[M ~map[K]V, K comparable, V any](m M, n int) M {
if m == nil { if m == nil {
return nil return nil
} }
if n > len(m) || n < 0 { if n > len(m) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the map or less than 0, n: %d, length: %d", n, len(m))) panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
} }
result := make(M, n) result := make(M, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
@ -164,14 +161,14 @@ func ChooseRandomMapValue[M ~map[K]V, K comparable, V any](m M) (v V) {
return return
} }
// ChooseRandomMapKeyN 获取 map 中的 n 个随机 key // ChooseRandomMapKeyN 获取 map 中的 inputN 个随机 key
// - 如果 n 大于 map 长度或小于 0 时将会发生 panic // - 如果 inputN 大于 map 长度或小于 0 时将会发生 panic
func ChooseRandomMapKeyN[M ~map[K]V, K comparable, V any](m M, n int) (result []K) { func ChooseRandomMapKeyN[M ~map[K]V, K comparable, V any](m M, n int) (result []K) {
if m == nil { if m == nil {
return return
} }
if n > len(m) || n < 0 { if n > len(m) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the map or less than 0, n: %d, length: %d", n, len(m))) panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
} }
result = make([]K, n) result = make([]K, n)
i := 0 i := 0
@ -192,7 +189,7 @@ func ChooseRandomMapValueN[M ~map[K]V, K comparable, V any](m M, n int) (result
return return
} }
if n > len(m) || n < 0 { if n > len(m) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the map or less than 0, n: %d, length: %d", n, len(m))) panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
} }
result = make([]V, n) result = make([]V, n)
i := 0 i := 0
@ -206,7 +203,7 @@ func ChooseRandomMapValueN[M ~map[K]V, K comparable, V any](m M, n int) (result
return return
} }
// ChooseRandomMapKeyAndValue 获取 map 中的随机 key 和 value // ChooseRandomMapKeyAndValue 获取 map 中的随机 key 和 v
func ChooseRandomMapKeyAndValue[M ~map[K]V, K comparable, V any](m M) (k K, v V) { func ChooseRandomMapKeyAndValue[M ~map[K]V, K comparable, V any](m M) (k K, v V) {
if m == nil { if m == nil {
return return
@ -217,14 +214,14 @@ func ChooseRandomMapKeyAndValue[M ~map[K]V, K comparable, V any](m M) (k K, v V)
return return
} }
// ChooseRandomMapKeyAndValueN 获取 map 中的 n 个随机 key 和 value // ChooseRandomMapKeyAndValueN 获取 map 中的 inputN 个随机 key 和 v
// - 如果 n 大于 map 长度或小于 0 时将会发生 panic // - 如果 n 大于 map 长度或小于 0 时将会发生 panic
func ChooseRandomMapKeyAndValueN[M ~map[K]V, K comparable, V any](m M, n int) M { func ChooseRandomMapKeyAndValueN[M ~map[K]V, K comparable, V any](m M, n int) M {
if m == nil { if m == nil {
return nil return nil
} }
if n > len(m) || n < 0 { if n > len(m) || n < 0 {
panic(fmt.Errorf("n is greater than the length of the map or less than 0, n: %d, length: %d", n, len(m))) panic(fmt.Errorf("inputN is greater than the length of the map or less than 0, inputN: %d, length: %d", n, len(m)))
} }
result := make(M, n) result := make(M, n)
i := 0 i := 0

View File

@ -0,0 +1,111 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
)
func ExampleChooseRandomSliceElementRepeatN() {
result := collection.ChooseRandomSliceElementRepeatN([]int{1}, 10)
fmt.Println(result)
// Output:
// [1 1 1 1 1 1 1 1 1 1]
}
func ExampleChooseRandomIndexRepeatN() {
result := collection.ChooseRandomIndexRepeatN([]int{1}, 10)
fmt.Println(result)
// Output:
// [0 0 0 0 0 0 0 0 0 0]
}
func ExampleChooseRandomSliceElement() {
result := collection.ChooseRandomSliceElement([]int{1})
fmt.Println(result)
// Output:
// 1
}
func ExampleChooseRandomIndex() {
result := collection.ChooseRandomIndex([]int{1})
fmt.Println(result)
// Output:
// 0
}
func ExampleChooseRandomSliceElementN() {
result := collection.ChooseRandomSliceElementN([]int{1}, 1)
fmt.Println(result)
// Output:
// [1]
}
func ExampleChooseRandomIndexN() {
result := collection.ChooseRandomIndexN([]int{1}, 1)
fmt.Println(result)
// Output:
// [0]
}
func ExampleChooseRandomMapKeyRepeatN() {
result := collection.ChooseRandomMapKeyRepeatN(map[int]int{1: 1}, 1)
fmt.Println(result)
// Output:
// [1]
}
func ExampleChooseRandomMapValueRepeatN() {
result := collection.ChooseRandomMapValueRepeatN(map[int]int{1: 1}, 1)
fmt.Println(result)
// Output:
// [1]
}
func ExampleChooseRandomMapKeyAndValueRepeatN() {
result := collection.ChooseRandomMapKeyAndValueRepeatN(map[int]int{1: 1}, 1)
fmt.Println(result)
// Output:
// map[1:1]
}
func ExampleChooseRandomMapKey() {
result := collection.ChooseRandomMapKey(map[int]int{1: 1})
fmt.Println(result)
// Output:
// 1
}
func ExampleChooseRandomMapValue() {
result := collection.ChooseRandomMapValue(map[int]int{1: 1})
fmt.Println(result)
// Output:
// 1
}
func ExampleChooseRandomMapKeyN() {
result := collection.ChooseRandomMapKeyN(map[int]int{1: 1}, 1)
fmt.Println(result)
// Output:
// [1]
}
func ExampleChooseRandomMapValueN() {
result := collection.ChooseRandomMapValueN(map[int]int{1: 1}, 1)
fmt.Println(result)
// Output:
// [1]
}
func ExampleChooseRandomMapKeyAndValue() {
k, v := collection.ChooseRandomMapKeyAndValue(map[int]int{1: 1})
fmt.Println(k, v)
// Output:
// 1 1
}
func ExampleChooseRandomMapKeyAndValueN() {
result := collection.ChooseRandomMapKeyAndValueN(map[int]int{1: 1}, 1)
fmt.Println(result)
// Output:
// map[1:1]
}

View File

@ -0,0 +1,280 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"testing"
)
func TestChooseRandomSliceElementRepeatN(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestChooseRandomSliceElementRepeatN_NonEmptySlice", []int{1, 2, 3}, 3},
{"TestChooseRandomSliceElementRepeatN_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomSliceElementRepeatN(c.input, 3)
if len(result) != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomIndexRepeatN(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestChooseRandomIndexRepeatN_NonEmptySlice", []int{1, 2, 3}, 3},
{"TestChooseRandomIndexRepeatN_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomIndexRepeatN(c.input, 3)
if len(result) != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomSliceElement(t *testing.T) {
var cases = []struct {
name string
input []int
expected map[int]bool
}{
{"TestChooseRandomSliceElement_NonEmptySlice", []int{1, 2, 3}, map[int]bool{1: true, 2: true, 3: true}},
{"TestChooseRandomSliceElement_EmptySlice", []int{}, map[int]bool{0: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomSliceElement(c.input)
if !c.expected[result] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomIndex(t *testing.T) {
var cases = []struct {
name string
input []int
expected map[int]bool
}{
{"TestChooseRandomIndex_NonEmptySlice", []int{1, 2, 3}, map[int]bool{0: true, 1: true, 2: true}},
{"TestChooseRandomIndex_EmptySlice", []int{}, map[int]bool{-1: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomIndex(c.input)
if !c.expected[result] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomSliceElementN(t *testing.T) {
var cases = []struct {
name string
input []int
}{
{"TestChooseRandomSliceElementN_NonEmptySlice", []int{1, 2, 3}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
actual := collection.ChooseRandomSliceElementN(c.input, 3)
if !collection.AllInComparableSlice(actual, c.input) {
t.Fatalf("%s failed, actual: %v, error: %s", c.name, actual, "the length of input is not equal")
}
})
}
}
func TestChooseRandomIndexN(t *testing.T) {
var cases = []struct {
name string
input []int
expected map[int]bool
}{
{"TestChooseRandomIndexN_NonEmptySlice", []int{1, 2, 3}, map[int]bool{0: true, 1: true, 2: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomIndexN(c.input, 3)
if !c.expected[result[0]] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomMapKeyRepeatN(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{"TestChooseRandomMapKeyRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomMapKeyRepeatN(c.input, 3)
if !c.expected[result[0]] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomMapValueRepeatN(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{"TestChooseRandomMapValueRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomMapValueRepeatN(c.input, 3)
if !c.expected[result[0]] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomMapKeyAndValueRepeatN(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{"TestChooseRandomMapKeyAndValueRepeatN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomMapKeyAndValueRepeatN(c.input, 3)
if !c.expected[result[1]] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomMapKey(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{"TestChooseRandomMapKey_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
{"TestChooseRandomMapKey_EmptyMap", map[int]int{}, map[int]bool{0: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomMapKey(c.input)
if !c.expected[result] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomMapValue(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{"TestChooseRandomMapValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
{"TestChooseRandomMapValue_EmptyMap", map[int]int{}, map[int]bool{0: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomMapValue(c.input)
if !c.expected[result] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomMapValueN(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{"TestChooseRandomMapValueN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ChooseRandomMapValueN(c.input, 3)
if !c.expected[result[0]] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, result, "the length of input is not equal")
}
})
}
}
func TestChooseRandomMapKeyAndValue(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{"TestChooseRandomMapKeyAndValue_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
{"TestChooseRandomMapKeyAndValue_EmptyMap", map[int]int{}, map[int]bool{0: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
k, v := collection.ChooseRandomMapKeyAndValue(c.input)
if !c.expected[k] || !c.expected[v] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, k, "the length of input is not equal")
}
})
}
}
func TestChooseRandomMapKeyAndValueN(t *testing.T) {
var cases = []struct {
name string
input map[int]int
expected map[int]bool
}{
{"TestChooseRandomMapKeyAndValueN_NonEmptyMap", map[int]int{1: 1, 2: 2, 3: 3}, map[int]bool{1: true, 2: true, 3: true}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
kvm := collection.ChooseRandomMapKeyAndValueN(c.input, 1)
for k := range kvm {
if !c.expected[k] {
t.Fatalf("%s failed, expected: %v, actual: %v, error: %s", c.name, c.expected, k, "the length of input is not equal")
}
}
})
}
}

View File

@ -1,4 +1,4 @@
package sher package collection
import ( import (
"github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/generic"
@ -6,6 +6,16 @@ import (
"sort" "sort"
) )
// DescBy 返回降序比较结果
func DescBy[Sort generic.Ordered](a, b Sort) bool {
return a > b
}
// AscBy 返回升序比较结果
func AscBy[Sort generic.Ordered](a, b Sort) bool {
return a < b
}
// Desc 对切片进行降序排序 // Desc 对切片进行降序排序
func Desc[S ~[]V, V any, Sort generic.Ordered](slice *S, getter func(index int) Sort) { func Desc[S ~[]V, V any, Sort generic.Ordered](slice *S, getter func(index int) Sort) {
sort.Slice(*slice, func(i, j int) bool { sort.Slice(*slice, func(i, j int) bool {

View File

@ -0,0 +1,83 @@
package collection_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/collection"
"sort"
)
func ExampleDescBy() {
var slice = []int{1, 2, 3}
sort.Slice(slice, func(i, j int) bool {
return collection.DescBy(slice[i], slice[j])
})
fmt.Println(slice)
// Output:
// [3 2 1]
}
func ExampleAscBy() {
var slice = []int{1, 2, 3}
sort.Slice(slice, func(i, j int) bool {
return collection.AscBy(slice[i], slice[j])
})
fmt.Println(slice)
// Output:
// [1 2 3]
}
func ExampleDesc() {
var slice = []int{1, 2, 3}
collection.Desc(&slice, func(index int) int {
return slice[index]
})
fmt.Println(slice)
// Output:
// [3 2 1]
}
func ExampleDescByClone() {
var slice = []int{1, 2, 3}
result := collection.DescByClone(slice, func(index int) int {
return slice[index]
})
fmt.Println(result)
// Output:
// [3 2 1]
}
func ExampleAsc() {
var slice = []int{1, 2, 3}
collection.Asc(&slice, func(index int) int {
return slice[index]
})
fmt.Println(slice)
// Output:
// [1 2 3]
}
func ExampleAscByClone() {
var slice = []int{1, 2, 3}
result := collection.AscByClone(slice, func(index int) int {
return slice[index]
})
fmt.Println(result)
// Output:
// [1 2 3]
}
func ExampleShuffle() {
var slice = []int{1, 2, 3}
collection.Shuffle(&slice)
fmt.Println(len(slice))
// Output:
// 3
}
func ExampleShuffleByClone() {
var slice = []int{1, 2, 3}
result := collection.ShuffleByClone(slice)
fmt.Println(len(result))
// Output:
// 3
}

View File

@ -0,0 +1,191 @@
package collection_test
import (
"github.com/kercylan98/minotaur/utils/collection"
"sort"
"testing"
)
func TestDescBy(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestDescBy_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
{"TestDescBy_EmptySlice", []int{}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
sort.Slice(c.input, func(i, j int) bool {
return collection.DescBy(c.input[i], c.input[j])
})
for i, v := range c.input {
if v != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
}
}
})
}
}
func TestAscBy(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestAscBy_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
{"TestAscBy_EmptySlice", []int{}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
sort.Slice(c.input, func(i, j int) bool {
return collection.AscBy(c.input[i], c.input[j])
})
for i, v := range c.input {
if v != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
}
}
})
}
}
func TestDesc(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestDesc_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
{"TestDesc_EmptySlice", []int{}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.Desc(&c.input, func(index int) int {
return c.input[index]
})
for i, v := range c.input {
if v != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
}
}
})
}
}
func TestDescByClone(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestDescByClone_NonEmptySlice", []int{1, 2, 3}, []int{3, 2, 1}},
{"TestDescByClone_EmptySlice", []int{}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.DescByClone(c.input, func(index int) int {
return c.input[index]
})
for i, v := range result {
if v != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
}
}
})
}
}
func TestAsc(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestAsc_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
{"TestAsc_EmptySlice", []int{}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.Asc(&c.input, func(index int) int {
return c.input[index]
})
for i, v := range c.input {
if v != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
}
}
})
}
}
func TestAscByClone(t *testing.T) {
var cases = []struct {
name string
input []int
expected []int
}{
{"TestAscByClone_NonEmptySlice", []int{1, 2, 3}, []int{1, 2, 3}},
{"TestAscByClone_EmptySlice", []int{}, []int{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.AscByClone(c.input, func(index int) int {
return c.input[index]
})
for i, v := range result {
if v != c.expected[i] {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
}
}
})
}
}
func TestShuffle(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestShuffle_NonEmptySlice", []int{1, 2, 3}, 3},
{"TestShuffle_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
collection.Shuffle(&c.input)
if len(c.input) != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, c.input)
}
})
}
}
func TestShuffleByClone(t *testing.T) {
var cases = []struct {
name string
input []int
expected int
}{
{"TestShuffleByClone_NonEmptySlice", []int{1, 2, 3}, 3},
{"TestShuffleByClone_EmptySlice", []int{}, 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
result := collection.ShuffleByClone(c.input)
if len(result) != c.expected {
t.Fatalf("%s failed, expected: %v, actual: %v", c.name, c.expected, result)
}
})
}
}

View File

@ -1,8 +1,8 @@
package combination package combination
import ( import (
"github.com/kercylan98/minotaur/utils/collection"
"github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/slice"
"reflect" "reflect"
"sort" "sort"
) )
@ -24,7 +24,7 @@ func WithMatcherEvaluation[T Item](evaluate func(items []T) float64) MatcherOpti
func WithMatcherLeastLength[T Item](length int) MatcherOption[T] { func WithMatcherLeastLength[T Item](length int) MatcherOption[T] {
return func(m *Matcher[T]) { return func(m *Matcher[T]) {
m.AddFilter(func(items []T) [][]T { m.AddFilter(func(items []T) [][]T {
return slice.LimitedCombinations(items, length, len(items)) return collection.FindCombinationsInSliceByRange(items, length, len(items))
}) })
} }
} }
@ -34,7 +34,7 @@ func WithMatcherLeastLength[T Item](length int) MatcherOption[T] {
func WithMatcherLength[T Item](length int) MatcherOption[T] { func WithMatcherLength[T Item](length int) MatcherOption[T] {
return func(m *Matcher[T]) { return func(m *Matcher[T]) {
m.AddFilter(func(items []T) [][]T { m.AddFilter(func(items []T) [][]T {
return slice.LimitedCombinations(items, length, length) return collection.FindCombinationsInSliceByRange(items, length, length)
}) })
} }
} }
@ -44,7 +44,7 @@ func WithMatcherLength[T Item](length int) MatcherOption[T] {
func WithMatcherMostLength[T Item](length int) MatcherOption[T] { func WithMatcherMostLength[T Item](length int) MatcherOption[T] {
return func(m *Matcher[T]) { return func(m *Matcher[T]) {
m.AddFilter(func(items []T) [][]T { m.AddFilter(func(items []T) [][]T {
return slice.LimitedCombinations(items, 1, length) return collection.FindCombinationsInSliceByRange(items, 1, length)
}) })
} }
} }
@ -55,7 +55,7 @@ func WithMatcherMostLength[T Item](length int) MatcherOption[T] {
func WithMatcherIntervalLength[T Item](min, max int) MatcherOption[T] { func WithMatcherIntervalLength[T Item](min, max int) MatcherOption[T] {
return func(m *Matcher[T]) { return func(m *Matcher[T]) {
m.AddFilter(func(items []T) [][]T { m.AddFilter(func(items []T) [][]T {
return slice.LimitedCombinations(items, min, max) return collection.FindCombinationsInSliceByRange(items, min, max)
}) })
} }
} }
@ -102,7 +102,7 @@ func WithMatcherSame[T Item, E generic.Ordered](count int, getType func(item T)
return func(m *Matcher[T]) { return func(m *Matcher[T]) {
m.AddFilter(func(items []T) [][]T { m.AddFilter(func(items []T) [][]T {
var combinations [][]T var combinations [][]T
groups := slice.LimitedCombinations(items, count, count) groups := collection.FindCombinationsInSliceByRange(items, count, count)
for _, items := range groups { for _, items := range groups {
if count > 0 && len(items) != count { if count > 0 && len(items) != count {
continue continue
@ -147,8 +147,12 @@ func WithMatcherNCarryM[T Item, E generic.Ordered](n, m int, getType func(item T
if len(group) != n { if len(group) != n {
continue continue
} }
other := slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) })
ms := slice.LimitedCombinations(other, m, m) other := collection.CloneSlice(items)
collection.DropSliceOverlappingElements(&other, group, func(source, target T) bool {
return reflect.DeepEqual(source, target)
})
ms := collection.FindCombinationsInSliceByRange(other, m, m)
for _, otherGroup := range ms { for _, otherGroup := range ms {
var t E var t E
var init = true var init = true
@ -163,7 +167,7 @@ func WithMatcherNCarryM[T Item, E generic.Ordered](n, m int, getType func(item T
} }
} }
if same { if same {
combinations = append(combinations, slice.Merge(group, otherGroup)) combinations = append(combinations, collection.MergeSlices(group, otherGroup))
} }
} }
} }
@ -191,9 +195,14 @@ func WithMatcherNCarryIndependentM[T Item, E generic.Ordered](n, m int, getType
if len(group) != n { if len(group) != n {
continue continue
} }
ms := slice.LimitedCombinations(slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) }), m, m)
other := collection.CloneSlice(items)
collection.DropSliceOverlappingElements(&other, group, func(source, target T) bool {
return reflect.DeepEqual(source, target)
})
ms := collection.FindCombinationsInSliceByRange(other, m, m)
for _, otherGroup := range ms { for _, otherGroup := range ms {
combinations = append(combinations, slice.Merge(group, otherGroup)) combinations = append(combinations, collection.MergeSlices(group, otherGroup))
} }
} }
return combinations return combinations

Some files were not shown because too many files have changed in this diff Show More